|
1 | 1 | from decimal import Decimal |
2 | | -from typing import Optional |
| 2 | +from typing import Annotated, Any, Optional |
3 | 3 |
|
4 | | -from pydantic import Field, computed_field |
| 4 | +from pydantic import ( |
| 5 | + ValidationError, |
| 6 | + ValidationInfo, |
| 7 | + ValidatorFunctionWrapHandler, |
| 8 | + WrapValidator, |
| 9 | +) |
5 | 10 |
|
6 | 11 | from .event import IndexedEvent |
7 | 12 |
|
8 | 13 | ZERO = Decimal(0) |
9 | 14 |
|
10 | 15 |
|
| 16 | +def zero_from_none( |
| 17 | + v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo |
| 18 | +) -> Decimal: |
| 19 | + try: |
| 20 | + return handler(v) |
| 21 | + except ValidationError: |
| 22 | + return ZERO |
| 23 | + |
| 24 | + |
| 25 | +ZeroFromNone = Annotated[Decimal, WrapValidator(zero_from_none)] |
| 26 | + |
| 27 | + |
11 | 28 | class Candle(IndexedEvent): |
12 | 29 | """ |
13 | 30 | A Candle event with open, high, low, close prices and other information |
@@ -35,40 +52,11 @@ class Candle(IndexedEvent): |
35 | 52 | imp_volatility: Optional[Decimal] = None |
36 | 53 | #: open interest in the candle |
37 | 54 | open_interest: Optional[int] = None |
38 | | - # these fields will not show up in serialization |
39 | | - raw_open: Optional[Decimal] = Field(validation_alias="open", exclude=True) |
40 | | - raw_high: Optional[Decimal] = Field(validation_alias="high", exclude=True) |
41 | | - raw_low: Optional[Decimal] = Field(validation_alias="low", exclude=True) |
42 | | - raw_close: Optional[Decimal] = Field(validation_alias="close", exclude=True) |
43 | | - |
44 | | - @computed_field |
45 | | - @property |
46 | | - def open(self) -> Decimal: |
47 | | - """ |
48 | | - the first (open) price of the candle |
49 | | - """ |
50 | | - return self.raw_open or ZERO |
51 | | - |
52 | | - @computed_field |
53 | | - @property |
54 | | - def high(self) -> Decimal: |
55 | | - """ |
56 | | - the maximal (high) price of the candle |
57 | | - """ |
58 | | - return self.raw_high or ZERO |
59 | | - |
60 | | - @computed_field |
61 | | - @property |
62 | | - def low(self) -> Decimal: |
63 | | - """ |
64 | | - the minimal (low) price of the candle |
65 | | - """ |
66 | | - return self.raw_low or ZERO |
67 | | - |
68 | | - @computed_field |
69 | | - @property |
70 | | - def close(self) -> Decimal: |
71 | | - """ |
72 | | - the last (close) price of the candle |
73 | | - """ |
74 | | - return self.raw_close or ZERO |
| 55 | + #: the first (open) price of the candle |
| 56 | + open: ZeroFromNone |
| 57 | + #: the maximal (high) price of the candle |
| 58 | + high: ZeroFromNone |
| 59 | + #: the minimal (low) price of the candle |
| 60 | + low: ZeroFromNone |
| 61 | + #: the last (close) price of the candle |
| 62 | + close: ZeroFromNone |
0 commit comments