Package stocks3 :: Package core :: Module stock
[hide private]
[frames] | no frames]

Source Code for Module stocks3.core.stock

  1  # -*- coding: utf-8 -*- 
  2   
  3  u""" 
  4  Котировка 
  5  ========= 
  6  X{Котировка} - это U{цена<http://ru.wikipedia.org/wiki/Цена>} товара. Так как 
  7  цена понятие всегда относительное, необходимо знать в каких единицах измеряется 
  8  эта цена (можно же подсчитать, сколько попугаев стоит один доллар:). Поэтому c 
  9  котировкой всегда связываются два товара: числитель и знаменатель котировки. 
 10   
 11  В зависимости от того, какой товар в котировке является "базовым", котировки 
 12  делятся на прямые и обратные (косвенные). 
 13   
 14  X{Прямая котировка} указывает цену единицы объекта торговли (1.20 доллар/1 литр 
 15  бензина) и цена единицы указывается в числителе котировки. 
 16   
 17  X{Обратная котировка} показывает - сколько единиц товара мы можем купить за 
 18  единицу денег (0,83 литра бензина/1 доллар). В числителе обратной котировке 
 19  указывается объем товара, который можно приорести за единицу денег. 
 20   
 21  Кроме того, котировки бывают односторонними и двусторонними. 
 22   
 23  X{Односторонняя котировка} - это объявление одной цены (покупки или продажи, L{Price}). 
 24   
 25  X{Двусторонняя котировка} - это объявление цены покупки и цены продажи дилером 
 26  (L{DualPrice}). 
 27   
 28  Вспомогательные объекты 
 29  ----------------------- 
 30  С основным объектом - котировкой - связываются второстепенные: товар, единица 
 31  товара, данные котировки. 
 32   
 33  X{Товар} - это объект, участвующий в котировке (L{Item}). Это тот объект, который 
 34  указывается либо в числителе, либо в знаменателе. Товаром также считается и 
 35  валюта. 
 36   
 37  X{Единица товара} - определяет в какой размерности представлен объем товара в 
 38  котировке (например, котировка 10 Гривен/1 Рубль отражает стоимость 10 гривен 
 39  при покупке их за рубли. В терминологии системы 10 Гривен - это виртуальная 
 40  единица (L{Unit})). 
 41   
 42  X{Курс} - зафиксированная в определенное время цена товара 
 43  (L{Price}). 
 44   
 45  Данные котировки по отношению к котировке образуют связь многие к одной. 
 46   
 47  Кросс-котировки 
 48  --------------- 
 49  X{Кроссовая котировка} отличается от обычной только методом расчета, но в 
 50  конечном счете сводится к модели числитель/знаменатель. Примером кроссовой 
 51  является котировке EUR/USD при расчете её через курс рубля. 
 52   
 53  Мелкие математические выкладки 
 54  ------------------------------ 
 55  Котировка дается в виде числа M{s}: M{s = x/y}, где M{x} - количество единиц 
 56  числителя, M{y} - количество единиц знаменателя. 
 57   
 58  Очень часто требуется ответить на вопросы: 
 59      1. Сколько единиц знаменателя стоит одна единица числителя? 
 60      2. Сколько единиц числителя стоит одна единица знаменателя (обратная задача)? 
 61   
 62  Для расчета цены числителя в единицах знаменателя (L{Price.numeratorCost}) 
 63  используем формулу M{n = ys/x}. 
 64   
 65  Для расчета цены знаменателя в единицах числителя (L{Price.denominatorCost}) - 
 66  M{d = x/ys}. 
 67   
 68  Очевидно: M{n = 1/d}. 
 69   
 70  Дельты 
 71  ------ 
 72  Часто требуется вычислить величину изменения значения котировки. В таком 
 73  расчете участвуют два значения котировки - текущее и предыдущее. 
 74   
 75  X{Абсолютное изменение котировки} вычисляется просто (L{absolutePriceDelta}): 
 76  M{c = v - v'}, где M{v} - текущее значение котировки, M{v'} - предыдущее 
 77  значение котировки. 
 78   
 79  X{Относительное изменение котировки} вычисляется в процентах от предыдущего 
 80  значения (L{relativePriceDelta}): M{o = (c / v') * 100}. 
 81   
 82  Абсолютное изменение может быть вычислено всегда. Отнсительное - при условии 
 83  присутствия предыдущего I{ненулевого} значения (см. L{safeRelativePriceDelta}). 
 84   
 85  Используемые типы чисел 
 86  ----------------------- 
 87  При вычислениях используется тип L{stocks_ft}. Для изменения этого типа следует 
 88  использовать L{set_stocks_ft}. 
 89   
 90  Управление точностью 
 91  -------------------- 
 92  Модуль считает с той точностью, которая достижима при заданных входных. 
 93  значениях. Но, можно заставить модуль выдавать значения с той точностью, 
 94  которая необходима нам. Таким полезным свойством обладает класс 
 95  L{PrecisionController}. Если через него пропускать необходимые нам значения, то 
 96  они будут выданы с нужной точностью. 
 97   
 98  По умолчанию контроль точности не выполняется, то есть используется специальный 
 99  контроллер - L{pseudoPrecisionController}. 
100   
101  В качестве вспомогательного элемента используется декоратор 
102  L{precision_control}, в который заворачиваются все функции, контролируемые по 
103  точности. 
104   
105  Все классы, которые сами управляют точностью, являются потомками класса 
106  L{Refined}. 
107  """ 
108   
109   
110  __author__ = "Zasimov Alexey" 
111  __email__ = "zasimov-a@yandex-team.ru" 
112   
113   
114  from datetime import datetime # для assert'a 
115   
116   
117  stocks_ft = float #: Этот тип используется во всех расчетов в этом модуле. 
118 119 120 -def set_stocks_ft(floatType):
121 global stocks_ft 122 stocks_ft = floatType
123
124 -class Stock:
125 u""" 126 Товар - любой объект, который имеет цену. 127 """
128 - def __init__(self, stockId):
129 u""" 130 Конструктор товара. 131 132 @type stockId: что угодно 133 @param stockId: Идентификатор товара. 134 """ 135 self._stockId = stockId
136
137 - def _get_stockid(self): return self._stockId
138 stockId = property(_get_stockid) 139
140 - def _get_scale(self): return stocks_ft(1.0)
141 scale = property(_get_scale)
142
143 144 -class Unit:
145 u""" 146 Единица измерения количества довара, например, доллар. 147 """
148 - def __init__(self, unitId, scale):
149 u""" 150 Конструктор единицы товара. 151 152 @param unitId: Идентификатор единицы товара. 153 @type scale: number 154 @param scale: Показывает, сколько единиц товара содержится в нашей виртуальной единице. 155 """ 156 assert scale != 0, "Zero unit scale." 157 self._unitId = unitId 158 self._scale = stocks_ft(scale)
159
160 - def _get_unitid(self): return self._unitId
161 unitId = property(_get_unitid) 162
163 - def _get_scale(self): return self._scale
164 scale = property(_get_scale)
165
166 167 -class StockWithUnit(Stock):
168 u""" 169 Товар с единицей измерения количества, например, бензин в литрах. 170 """
171 - def __init__(self, stockId, unit):
172 u""" 173 Создаем товар с единицей измерения количества. 174 175 @type stockId: Stock.stockId 176 @param stockId: См. Stock.stockId 177 @type unit: L{Unit} 178 @param unit: Единица измерения количества товара. 179 """ 180 Stock.__init__(self, stockId) 181 assert isinstance(unit, Unit), "Expected Unit, but received: %s" % unit.__class__ 182 self.unit = unit
183
184 - def _get_scale(self): return self.unit.scale
185 scale = property(_get_scale)
186
187 188 -class Quote:
189 u""" 190 Котировка - описывает отношение между ценами товаров. 191 192 Котировка не содержит в себе никакого значения, она описывает участвующие в 193 сравнении товары. 194 """ 195
196 - def __init__(self, quoteId, numerator, denominator):
197 u""" 198 Конструктор котировки. Так как в котировке указываются не только товары 199 но и единицы измерения количества этих товаров, котировки USD1/EUR1 и 200 USD10/EUR10 - разные котировки. 201 202 В прямых котировках в числители всегда стоит единицы, в обратных 203 единица всегда стоит в знаменателе. 204 205 Кросскотировки рассчитываются через третий товар (например, EUR/USD 206 через рубль). 207 208 Поэтому - информации о числителе и знаменателе мало, чтобы 209 идентифицировать котировку. 210 211 @type quoteId: что угодно 212 @param quoteId: Идентификатор котировки. 213 @type numerator: L{StockWithUnit} 214 @param numerator: Числитель котировки. 215 @type denominator: L{StockWithUnit} 216 @param denominator: Знаменатель котировки. 217 """ 218 assert isinstance(numerator, StockWithUnit), "Numerator must be StockWithUnit." 219 assert isinstance(denominator, StockWithUnit), "Denominator must be StockWithUnit." 220 self._quoteId = quoteId 221 self._numerator = numerator 222 self._denominator = denominator
223
224 - def _get_quoteid(self): return self._quoteId
225 quoteId = property(_get_quoteid) 226
227 - def _get_numerator(self): return self._numerator
228 numerator = property(_get_numerator) 229
230 - def _get_denominator(self): return self._denominator
231 denominator = property(_get_denominator) 232
233 - def makePrice(self, date, value, pc=None):
234 u""" 235 Синтаксический сахар. Эквивалентна вызову: 236 C{Price(quote, date, value,pc)}. 237 """ 238 return Price(self, date, value, pc)
239 240
241 - def makeDualPrice(self, date, sell_value, buy_value, pc=None):
242 sell = self.makePrice(date, sell_value, pc) 243 buy = self.makePrice(date, buy_value, pc) 244 return DualPrice(self, sell, buy)
245
246 -def absolutePriceDelta(current, previous):
247 u""" 248 Абсолютное изменение значения котировки. 249 """ 250 return current - previous
251
252 -def relativePriceDelta(absolute, previous):
253 u""" 254 Относительное изменения значения котировки. 255 """ 256 return (absolute / previous) * stocks_ft(100.0)
257
258 259 -class PseudoPrecisionController:
260 u""" 261 Контроллер точности, который ничего не делает :) 262 263 FIXME: странный элемент 264 """ 265
266 - def __init__(self, precision=None):
267 u""" 268 Конструктор. 269 270 @type precision: int 271 @param precision: Точность вычислений. 272 """ 273 self._precision = precision
274
275 - def _get_precision(self): return self._precision
276 precision = property(_get_precision) 277
278 - def __eq__(self, controller):
279 return self.precision == controller.precision
280
281 - def __call__(self, value):
282 return value
283
284 285 -class PrecisionController(PseudoPrecisionController):
286 u""" 287 Пропускает через себя значения, изменяя их точность. 288 """
289 - def __init__(self, precision):
290 u""" 291 Конструктор. 292 293 См. L{PseudoPrecisionController}. 294 """ 295 assert type(precision) == int and precision > 0, "Precision must be positive integer." 296 PseudoPrecisionController.__init__(self, precision)
297
298 - def __call__(self, value):
299 return round(value, self.precision)
300 301 302 pseudoPrecisionController = PseudoPrecisionController()
303 304 305 -class Refined:
306 u""" 307 От этого класса порождаются все классы, которые сами управляют точностью. 308 """ 309
310 - def __init__(self, pc):
311 self._pc = pc if pc else pseudoPrecisionController
312
313 - def _get_pc(self): return self._pc
314 pc = property(_get_pc) 315
316 - def _get_precision(self): return self._pc.precision if self._pc else None
317 precision = property(_get_precision) 318
319 - def check_precision(self, otherData):
320 if self.precision != otherData.precision: 321 raise RuntimeError("Precision collision.")
322
323 324 -def precision_control(func):
325 u""" 326 Декоратор, управляющий точностью. 327 """ 328 def func_with_precision(self, *args): 329 assert isinstance(self, Refined), "Precision control expected Refined class. Current: %s" % str(self.__class__) 330 value = func(self, *args) 331 return self.pc(value)
332 func_with_precision.func_name = func.func_name 333 return func_with_precision 334
335 336 -class AbstractPrice:
337 - def __init__(self, quote, date):
338 assert isinstance(quote, Quote), "Quote must be Quote." 339 assert isinstance(date, datetime), "Expected datetime, but received - %s" % date.__class__ 340 self._quote = quote 341 self._date = date 342 self._region = None 343 self.tz = None
344
345 - def _get_quote(self): return self._quote
346 quote = property(_get_quote) 347
348 - def _get_date(self): return self._date
349 date = property(_get_date) 350
351 - def _get_region(self):
352 u""" 353 Значение котировки может быть привязано к региону. 354 Мы можем привязать значение к региону вызвав L{AbstractPrice.onlyForRegion}. 355 """ 356 return self._region
357 region = property(_get_region) 358
359 - def onlyForRegion(self, region):
360 u""" 361 Может так случиться, что данные актуальны только для какого-то 362 определенного региона. Парсер может вызвать этот метод, чтобы известить 363 остальные модули конвейера об этом. 364 """ 365 assert isinstance(region, int), "Region must be integer." 366 self._region = region
367
368 - def _get_prepared_date(self):
369 u""" 370 Возвращает только дату данных. Используется из шаблона для 371 Prepared-XML. 372 """ 373 return self.date.strftime("%d.%m.%Y")
374 preparedDate = property(_get_prepared_date) 375
376 - def _get_prepared_time(self):
377 u""" 378 Возвращает только время данных. Используется из шаблона для Prepared-XML. 379 """ 380 return self.date.strftime("%H:%M:%S")
381 preparedTime = property(_get_prepared_time) 382
383 - def _get_sell(self):
384 u""" 385 Поле сделано для совместимости между котировками (одиночными и 386 двойными). Нужно для удобного формирования шаблона. 387 """ 388 return self
389 sell = property(_get_sell) 390
391 - def _get_is_dual_price(self):
392 u""" 393 Это свойство немного нарушает принцип наследования, но без него трудно 394 генерировать вывод по шаблоном. Часто надо знать, односторонняя перед 395 нами котировка или двусторонняя. 396 """ 397 return False
398 is_dual_price = property(_get_is_dual_price) 399
400 - def setTimezone(self, tz):
401 u""" 402 Может так случиться, что данные пришли в какой-то специфичной временной 403 зоне. Тогда парсер может вызвать эту функцию, чтобы проинструктировать 404 калькуляторы об этой особенности. 405 """ 406 self.tz = tz
407
408 409 -class Price(Refined, AbstractPrice):
410 u""" 411 Значение котировки (курс, price) за какое-то число. 412 """ 413
414 - def __init__(self, quote, date, value, pc=None):
415 u""" 416 Конструктор значения котировки. 417 418 @type quote: L{Qoute} 419 @param quote: Котировка. 420 @type date: datetime 421 @param date: Дата и время зафиксированного значения котировки. 422 @type value: float 423 @param value: Значение котировки. 424 @type pc: L{PrecisionController} 425 @param pc: См. L{Refined}. 426 """ 427 AbstractPrice.__init__(self, quote, date) 428 Refined.__init__(self, pc) 429 self._value = self._pc(stocks_ft(value))
430
431 - def _get_numerator(self): return self.quote.numerator
432 numerator = property(_get_numerator) 433
434 - def _get_denominator(self): return self.quote.denominator
435 denominator = property(_get_denominator) 436
437 - def _get_value(self): return self._value
438 value = property(_get_value) 439 440 @precision_control
441 - def numeratorCost(self):
442 u""" 443 Вычисляет цену числителя в единицах знаменателя. 444 445 @returns: 446 Цена числителя в единицах знаменателя. 447 """ 448 # FIXME: при большом значении numerator.scale возможно падение в 0 449 cost = (self.denominator.scale / self.numerator.scale) * self.value 450 if cost == 0: 451 raise RuntimeError("Numerator cost is Zero.") 452 return cost
453 454 @precision_control
455 - def denominatorCost(self):
456 u""" 457 Вычисляет цену знаменателя в единицах числителя. 458 459 @returns: 460 Цена знаменателя в единицах числителя. 461 """ 462 return stocks_ft(1.0) / self.numeratorCost()
463 464 @precision_control
465 - def absoluteDelta(self, previous):
466 u""" 467 Вычисляет абсолютное изменение котировки. 468 469 @type previous: L{StockData} 470 @param previous: Предыдущее значение котировки. Должно быть в той же точности, что и self. 471 """ 472 assert isinstance(previous, Price), "StockData expected for absoluteDelta calculating." 473 self.check_precision(previous) 474 return absolutePriceDelta(self.value, previous.value)
475 476 @precision_control
477 - def relativeDelta(self, previous):
478 u""" 479 Вычисляет относительное изменение котировки. 480 481 @type previous: L{StockData} 482 @param previous: Предыдущее значение котировки. 483 484 @exception ZeroDivisionError: Предыдущее значение нулевое. 485 """ 486 assert isinstance(previous, Price), "StockData expected for absoluteDelta calculating." 487 self.check_precision(previous) 488 absolute = self.absoluteDelta(previous) 489 return relativePriceDelta(absolute, previous.value)
490
491 - def toPrecision(self, precision):
492 u""" 493 Если нам надо вычислять значения с определенной точностью, используем 494 этот метод. 495 496 @type precision: int 497 @param precision: Требуемая точность. 498 """ 499 return self.__class__(self.quote, self.date, self.value, PrecisionController(precision))
500
501 502 -class DualPrice(AbstractPrice):
503 - def __init__(self, quote, sell, buy):
504 u""" 505 Котировка, включающая и цену покупки и цену продажи. 506 507 @type quote: L{Qoute} 508 @param quote: Котировка. 509 @type sell: L{Price} 510 @param sell: Цена покупки товара (для нас). 511 @type buy: L{Price} 512 @param buy: Цена продажи товара (для нас). 513 """ 514 assert isinstance(sell, Price), "Sell must be Price." 515 assert isinstance(buy, Price), "Buy must be Price." 516 AbstractPrice.__init__(self, quote, sell.date) 517 if quote != sell.quote or quote != buy.quote: 518 raise TypeError("Difference quotes for sell and buy.") 519 self._sell = sell 520 self._buy = buy
521
522 - def _get_sell(self): return self._sell
523 sell = property(_get_sell) 524
525 - def _get_buy(self): return self._buy
526 buy = property(_get_buy) 527
528 - def _get_is_dual_price(self): return True
529 is_dual_price = property(_get_is_dual_price)
530
531 # Functions 532 -def safeRelativePriceDelta(current, previous, quantumJumpValue):
533 u""" 534 Расчет относительного изменения котировки в условиях возможного отсутствия 535 предыдущего значения или нулевого предыдущего значения. Всегда возвращает 536 значение. Исключение в этой функции - ненормальный результат. 537 538 @type current: L{StockData} 539 @param current: Текущее значение котировки. 540 @type previous: L{StockData} 541 @param previous: Предыдущее значение котировки. 542 @type quantumJumpValue: float 543 @param quantumJumpValue: Значение, которое вернется в качестве результата 544 при обнаружении кватового скачка :) 545 """ 546 if not previous or previous.value == 0: 547 # Квантовый скачок - нет предыдущего значения или значение нулевое 548 if current.value == 0: 549 return 0 550 else: 551 return quantumJumpValue 552 return current.relativeDelta(previous)
553