1
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
115
116
117 stocks_ft = float
121 global stocks_ft
122 stocks_ft = floatType
123
125 u"""
126 Товар - любой объект, который имеет цену.
127 """
129 u"""
130 Конструктор товара.
131
132 @type stockId: что угодно
133 @param stockId: Идентификатор товара.
134 """
135 self._stockId = stockId
136
138 stockId = property(_get_stockid)
139
141 scale = property(_get_scale)
142
145 u"""
146 Единица измерения количества довара, например, доллар.
147 """
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
161 unitId = property(_get_unitid)
162
164 scale = property(_get_scale)
165
168 u"""
169 Товар с единицей измерения количества, например, бензин в литрах.
170 """
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
185 scale = property(_get_scale)
186
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
225 quoteId = property(_get_quoteid)
226
228 numerator = property(_get_numerator)
229
231 denominator = property(_get_denominator)
232
234 u"""
235 Синтаксический сахар. Эквивалентна вызову:
236 C{Price(quote, date, value,pc)}.
237 """
238 return Price(self, date, value, pc)
239
240
245
247 u"""
248 Абсолютное изменение значения котировки.
249 """
250 return current - previous
251
253 u"""
254 Относительное изменения значения котировки.
255 """
256 return (absolute / previous) * stocks_ft(100.0)
257
260 u"""
261 Контроллер точности, который ничего не делает :)
262
263 FIXME: странный элемент
264 """
265
267 u"""
268 Конструктор.
269
270 @type precision: int
271 @param precision: Точность вычислений.
272 """
273 self._precision = precision
274
276 precision = property(_get_precision)
277
278 - def __eq__(self, controller):
280
283
286 u"""
287 Пропускает через себя значения, изменяя их точность.
288 """
297
300
301
302 pseudoPrecisionController = PseudoPrecisionController()
306 u"""
307 От этого класса порождаются все классы, которые сами управляют точностью.
308 """
309
312
313 - def _get_pc(self): return self._pc
314 pc = property(_get_pc)
315
317 precision = property(_get_precision)
318
320 if self.precision != otherData.precision:
321 raise RuntimeError("Precision collision.")
322
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
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
346 quote = property(_get_quote)
347
349 date = property(_get_date)
350
352 u"""
353 Значение котировки может быть привязано к региону.
354 Мы можем привязать значение к региону вызвав L{AbstractPrice.onlyForRegion}.
355 """
356 return self._region
357 region = property(_get_region)
358
360 u"""
361 Может так случиться, что данные актуальны только для какого-то
362 определенного региона. Парсер может вызвать этот метод, чтобы известить
363 остальные модули конвейера об этом.
364 """
365 assert isinstance(region, int), "Region must be integer."
366 self._region = region
367
369 u"""
370 Возвращает только дату данных. Используется из шаблона для
371 Prepared-XML.
372 """
373 return self.date.strftime("%d.%m.%Y")
374 preparedDate = property(_get_prepared_date)
375
377 u"""
378 Возвращает только время данных. Используется из шаблона для Prepared-XML.
379 """
380 return self.date.strftime("%H:%M:%S")
381 preparedTime = property(_get_prepared_time)
382
384 u"""
385 Поле сделано для совместимости между котировками (одиночными и
386 двойными). Нужно для удобного формирования шаблона.
387 """
388 return self
389 sell = property(_get_sell)
390
392 u"""
393 Это свойство немного нарушает принцип наследования, но без него трудно
394 генерировать вывод по шаблоном. Часто надо знать, односторонняя перед
395 нами котировка или двусторонняя.
396 """
397 return False
398 is_dual_price = property(_get_is_dual_price)
399
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
432 numerator = property(_get_numerator)
433
435 denominator = property(_get_denominator)
436
438 value = property(_get_value)
439
440 @precision_control
442 u"""
443 Вычисляет цену числителя в единицах знаменателя.
444
445 @returns:
446 Цена числителя в единицах знаменателя.
447 """
448
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
456 u"""
457 Вычисляет цену знаменателя в единицах числителя.
458
459 @returns:
460 Цена знаменателя в единицах числителя.
461 """
462 return stocks_ft(1.0) / self.numeratorCost()
463
464 @precision_control
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
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
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
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
523 sell = property(_get_sell)
524
526 buy = property(_get_buy)
527
529 is_dual_price = property(_get_is_dual_price)
530
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