PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/marwiz/market/stream/base.py

https://github.com/hamilyon/stock_analizer
Python | 357 lines | 234 code | 10 blank | 113 comment | 22 complexity | 15c2f05d5c1f181daa6556428cc413f1 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """
  2. This module contains BASE classes of indicators ONLY.
  3. I'm using iterating conception for indicators calculating.
  4. Indicators are calculated just as it was done by our ancestors
  5. when there wasn't any computer. It works faster than a window-based
  6. calculation method which is used in the largest part of a trading software.
  7. There are two types of indicators: array-processing and step-poicessing.
  8. It's my own differentiation into two types:
  9. -> Array-Processing indicators are indicators which calculating same as
  10. window-based indicators. You have an array and calculate values on
  11. values containing in the array. The best example is the indicator
  12. which calculates a maximum or a minimum in the array. Other example is
  13. the simple moving average indicator but we have made it more effectively.
  14. -> Step-processing indicators are indicators which calculating continuously
  15. and based on all received data. You have not an data array. You have only
  16. a new value and any data you saved. The best example is the indicator
  17. which calculates an absolute maximum or an absolute minimum in the all
  18. recieved data. Other example is the exponential moving average which is
  19. calculated step by step when new data is recieved.
  20. """
  21. from __future__ import division
  22. def skipNone(x): return x == None
  23. def skipFalse(x): return not x
  24. def noskip(x): return False
  25. ########################################################################
  26. class Indicator(list):
  27. """
  28. Base class for indicators. Can explore input and output parameters and
  29. accumulate they into list.
  30. """
  31. #----------------------------------------------------------------------
  32. def __init__(self):
  33. super(Indicator, self).__init__(self.defaults)
  34. #----------------------------------------------------------------------
  35. def isvalid(self, input_values):
  36. """
  37. Function which automatically checks input values with correspond skippers.
  38. """
  39. return not any([x[0](x[1]) for x in zip(self.skippers, input_values)])
  40. #----------------------------------------------------------------------
  41. def update(self, *values):
  42. """
  43. Methods for receiving new values.
  44. """
  45. raise NotImplementedError()
  46. ########################################################################
  47. class ArrayProcessor(Indicator):
  48. """
  49. Accumulate input values in an array and calculate indicator's value with that values.
  50. For creating a new inidicator follow next steps:
  51. 1. Inherit this class at first.
  52. 2. Declare input and output parameters.
  53. 3. Create your own calculate method.
  54. 4. Add your actions when value appended or removed.
  55. """
  56. #----------------------------------------------------------------------
  57. def __init__(self, period):
  58. super(ArrayProcessor, self).__init__()
  59. self.period = period
  60. self.limit = self.period
  61. self.array = []
  62. #----------------------------------------------------------------------
  63. def update(self, *values):
  64. """
  65. Extended indicator's routine.
  66. Calling by user for update an indicator's value. This method calls until an array isn't full.
  67. When array is full this method will changed with work method.
  68. """
  69. # Accumulate data.
  70. if self.isvalid(values):
  71. self.preappend(*values)
  72. self.array.append(values)
  73. # There is enough data. Calculate first result.
  74. if len(self.array) >= self.limit:
  75. # Start to calculate.
  76. self.update = self.work
  77. # Calculate first value when array of data is full
  78. self.calculate(*values)
  79. #----------------------------------------------------------------------
  80. def work(self, *values):
  81. """
  82. Main indicator's routine.
  83. """
  84. if self.isvalid(values):
  85. # Append newest and pop oldest value if it isn't NaN.
  86. self.preappend(*values)
  87. self.array.append(values)
  88. self.postremove(*self.array.pop(0))
  89. # Return last or new result if data was updated.
  90. self.calculate(*values)
  91. #----------------------------------------------------------------------
  92. def preappend(self, *newest):
  93. """
  94. Calling before a new value will appended to the array.
  95. You can override it.
  96. """
  97. pass
  98. #----------------------------------------------------------------------
  99. def postremove(self, *oldest):
  100. """
  101. Calling after an oldest value have removed from the array.
  102. You can override it.
  103. """
  104. pass
  105. #----------------------------------------------------------------------
  106. def calculate(self, *values):
  107. """
  108. Your main calculation routine must to be here.
  109. Store indicator's values in instance's fields.
  110. You must to override it.
  111. """
  112. raise NotImplementedError()
  113. ########################################################################
  114. class StepProcessor(Indicator):
  115. """
  116. Process input values step-by-step and update (aggregate) indicator value.
  117. For creating a new inidicator follow next steps:
  118. 1. Inherit this class at first.
  119. 2. Declare input and output parameters.
  120. 3. Create your own calculate method.
  121. 4. Add your actions when first value added.
  122. """
  123. #----------------------------------------------------------------------
  124. def __init__(self, period):
  125. super(StepProcessor, self).__init__()
  126. self.period = period
  127. self.limit = self.period
  128. self.count = 0
  129. #----------------------------------------------------------------------
  130. def update(self, *values):
  131. """
  132. Extended indicator's routine.
  133. Calling by user for update an indicator's value. This method calls until method
  134. have called n times equals period.
  135. When array is full this method will changed with work method.
  136. """
  137. if self.isvalid(values):
  138. # Calculating from first when it was set in constructor
  139. if hasattr(self, '_first'):
  140. self.calculate(*values)
  141. else:
  142. self._first = values
  143. self.firstadded(*self._first)
  144. self.count += 1
  145. if self.count == self.limit:
  146. del self.count
  147. # Start to calculate.
  148. self.update = self.work
  149. #----------------------------------------------------------------------
  150. def work(self, *values):
  151. """
  152. Main indicator's routine.
  153. """
  154. if self.isvalid(values):
  155. # Return last or new result if data was updated.
  156. self.calculate(*values)
  157. #----------------------------------------------------------------------
  158. def firstadded(self, *values):
  159. """
  160. Calling when first value was get. If you need to calculate initial values for
  161. calculate method fo it here.
  162. """
  163. pass
  164. #----------------------------------------------------------------------
  165. def calculate(self, *values):
  166. """
  167. Your main calculation routine must to be here.
  168. Store indicator's values in instance's fields.
  169. You must to override it.
  170. """
  171. raise NotImplementedError()
  172. ########################################################################
  173. class PartialGenerator(object):
  174. """
  175. This class is usesd for generating partial initialized indicator classes.
  176. """
  177. #----------------------------------------------------------------------
  178. @classmethod
  179. def partial(cls, name, doc, **kwargs):
  180. """
  181. Generate a new type based on a calling class with some named
  182. parameters preset. skippers and Outpust parameters got from
  183. skippers and defaults of a super class.
  184. """
  185. attr = {}
  186. def __init__(self, *prms, **nprms):
  187. kwargs.update(nprms)
  188. super(self.__class__, self).__init__(*prms, **kwargs)
  189. attr['__init__'] = __init__
  190. attr['__doc__'] = doc
  191. #attr.update(dict(cls.skippers)) # Bug already fixed in __new__
  192. #attr.update(dict(cls.defaults)) # of ParameterSequencer metaclass
  193. return type(name, (cls,), attr)
  194. ########################################################################
  195. class FuncProcessor(ArrayProcessor, PartialGenerator):
  196. """
  197. Calculates function for each first value of each array cell of the data.
  198. """
  199. skippers = (skipNone,)
  200. defaults = (None,)
  201. #----------------------------------------------------------------------
  202. def __init__(self, period, func, reduce=False): # Don't reduce by default
  203. super(FuncProcessor, self).__init__(period)
  204. self.func = func
  205. self.calculate = self.calculateReduce if reduce else self.calculatePlain
  206. #----------------------------------------------------------------------
  207. def calculateReduce(self, *values):
  208. "Calls if self.reduce is True."
  209. result = self.array[0][0]
  210. for next in self.array[1:]:
  211. result = self.func(result, next[0])
  212. self[0] = result
  213. #----------------------------------------------------------------------
  214. def calculatePlain(self, *values):
  215. "Calls if self.reduce is False."
  216. self[0] = self.func([value[0] for value in self.array])
  217. ########################################################################
  218. class AggregateProcessor(StepProcessor, PartialGenerator):
  219. """
  220. Calculates function with the previous result and a new value.
  221. This function works like 'reduce' based on stream processing.
  222. """
  223. skippers = (skipNone,)
  224. defaults = (None,)
  225. #----------------------------------------------------------------------
  226. def __init__(self, func, period=1, plain=False): # Reduce by default
  227. super(AggregateProcessor, self).__init__(period)
  228. self.func = func
  229. if plain:
  230. # Skip calling of self.firstadded and calls self.calculate instead
  231. self._first = True
  232. self.calculate = self.calculatePlain
  233. else:
  234. self.calculate = self.calculateReduce
  235. #----------------------------------------------------------------------
  236. def firstadded(self, *values):
  237. self[0] = values[0]
  238. #----------------------------------------------------------------------
  239. def calculateReduce(self, *values):
  240. "Calls if self.plain is False."
  241. self[0] = self.func(self[0], values[0])
  242. #----------------------------------------------------------------------
  243. def calculatePlain(self, *values):
  244. "Calls if self.plain is True."
  245. self[0] = self.func(values[0])
  246. ########################################################################
  247. class Filter(StepProcessor, PartialGenerator):
  248. """
  249. Base class for creating stream filters.
  250. * Stateful is a filtor which keep the lastest value.
  251. * Stateless every time switched to None when input
  252. value haven't met the condition.
  253. """
  254. skippers = (skipNone,)
  255. defaults = (None,)
  256. #----------------------------------------------------------------------
  257. def __init__(self, comparator, base=None, asbase=False):
  258. """Constructor"""
  259. super(Filter, self).__init__(1)
  260. self._first = True
  261. self.comparator = comparator
  262. self.base = base
  263. self.asbase = asbase
  264. #----------------------------------------------------------------------
  265. def calculate(self, price):
  266. """"""
  267. if not self.asbase and self.comparator(price, self.base) or \
  268. self.asbase and self.comparator(self.base, price):
  269. self[0] = price
  270. else:
  271. self[0] = self.defaults[0]
  272. ########################################################################
  273. class Chain(Indicator):
  274. """
  275. Get input arguments from first indicator, and defaults from last.
  276. """
  277. #----------------------------------------------------------------------
  278. def __init__(self, *indicators):
  279. self.indicators = indicators
  280. # Override skippers and defaults
  281. self.skippers = self.indicators[0].skippers # Expected one or more indicators
  282. self.defaults = self.indicators[-1].defaults
  283. self.parameters = self.skippers + self.defaults
  284. #----------------------------------------------------------------------
  285. def update(self, *values):
  286. """
  287. Transfer values from one indicator to another.
  288. """
  289. for indicator in self.indicators:
  290. indicator.update(*values)
  291. values = indicator
  292. self[:] = self.indicators[-1]
  293. #----------------------------------------------------------------------
  294. def __getattr__(self, name):
  295. """
  296. Return attributes from the lastest element of a chain.
  297. """
  298. return getattr(self.indicators[-1], name)