onetick-py 1.177.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. locator_parser/__init__.py +0 -0
  2. locator_parser/acl.py +73 -0
  3. locator_parser/actions.py +262 -0
  4. locator_parser/common.py +368 -0
  5. locator_parser/io.py +43 -0
  6. locator_parser/locator.py +150 -0
  7. onetick/__init__.py +101 -0
  8. onetick/doc_utilities/__init__.py +3 -0
  9. onetick/doc_utilities/napoleon.py +40 -0
  10. onetick/doc_utilities/ot_doctest.py +140 -0
  11. onetick/doc_utilities/snippets.py +279 -0
  12. onetick/lib/__init__.py +4 -0
  13. onetick/lib/instance.py +141 -0
  14. onetick/py/__init__.py +293 -0
  15. onetick/py/_stack_info.py +89 -0
  16. onetick/py/_version.py +2 -0
  17. onetick/py/aggregations/__init__.py +11 -0
  18. onetick/py/aggregations/_base.py +648 -0
  19. onetick/py/aggregations/_docs.py +948 -0
  20. onetick/py/aggregations/compute.py +286 -0
  21. onetick/py/aggregations/functions.py +2216 -0
  22. onetick/py/aggregations/generic.py +104 -0
  23. onetick/py/aggregations/high_low.py +80 -0
  24. onetick/py/aggregations/num_distinct.py +83 -0
  25. onetick/py/aggregations/order_book.py +501 -0
  26. onetick/py/aggregations/other.py +1014 -0
  27. onetick/py/backports.py +26 -0
  28. onetick/py/cache.py +374 -0
  29. onetick/py/callback/__init__.py +5 -0
  30. onetick/py/callback/callback.py +276 -0
  31. onetick/py/callback/callbacks.py +131 -0
  32. onetick/py/compatibility.py +798 -0
  33. onetick/py/configuration.py +771 -0
  34. onetick/py/core/__init__.py +0 -0
  35. onetick/py/core/_csv_inspector.py +93 -0
  36. onetick/py/core/_internal/__init__.py +0 -0
  37. onetick/py/core/_internal/_manually_bound_value.py +6 -0
  38. onetick/py/core/_internal/_nodes_history.py +250 -0
  39. onetick/py/core/_internal/_op_utils/__init__.py +0 -0
  40. onetick/py/core/_internal/_op_utils/every_operand.py +9 -0
  41. onetick/py/core/_internal/_op_utils/is_const.py +10 -0
  42. onetick/py/core/_internal/_per_tick_scripts/tick_list_sort_template.script +121 -0
  43. onetick/py/core/_internal/_proxy_node.py +140 -0
  44. onetick/py/core/_internal/_state_objects.py +2312 -0
  45. onetick/py/core/_internal/_state_vars.py +93 -0
  46. onetick/py/core/_source/__init__.py +0 -0
  47. onetick/py/core/_source/_symbol_param.py +95 -0
  48. onetick/py/core/_source/schema.py +97 -0
  49. onetick/py/core/_source/source_methods/__init__.py +0 -0
  50. onetick/py/core/_source/source_methods/aggregations.py +809 -0
  51. onetick/py/core/_source/source_methods/applyers.py +296 -0
  52. onetick/py/core/_source/source_methods/columns.py +141 -0
  53. onetick/py/core/_source/source_methods/data_quality.py +301 -0
  54. onetick/py/core/_source/source_methods/debugs.py +272 -0
  55. onetick/py/core/_source/source_methods/drops.py +120 -0
  56. onetick/py/core/_source/source_methods/fields.py +619 -0
  57. onetick/py/core/_source/source_methods/filters.py +1002 -0
  58. onetick/py/core/_source/source_methods/joins.py +1413 -0
  59. onetick/py/core/_source/source_methods/merges.py +605 -0
  60. onetick/py/core/_source/source_methods/misc.py +1455 -0
  61. onetick/py/core/_source/source_methods/pandases.py +155 -0
  62. onetick/py/core/_source/source_methods/renames.py +356 -0
  63. onetick/py/core/_source/source_methods/sorts.py +183 -0
  64. onetick/py/core/_source/source_methods/switches.py +142 -0
  65. onetick/py/core/_source/source_methods/symbols.py +117 -0
  66. onetick/py/core/_source/source_methods/times.py +627 -0
  67. onetick/py/core/_source/source_methods/writes.py +986 -0
  68. onetick/py/core/_source/symbol.py +205 -0
  69. onetick/py/core/_source/tmp_otq.py +222 -0
  70. onetick/py/core/column.py +209 -0
  71. onetick/py/core/column_operations/__init__.py +0 -0
  72. onetick/py/core/column_operations/_methods/__init__.py +4 -0
  73. onetick/py/core/column_operations/_methods/_internal.py +28 -0
  74. onetick/py/core/column_operations/_methods/conversions.py +216 -0
  75. onetick/py/core/column_operations/_methods/methods.py +292 -0
  76. onetick/py/core/column_operations/_methods/op_types.py +160 -0
  77. onetick/py/core/column_operations/accessors/__init__.py +0 -0
  78. onetick/py/core/column_operations/accessors/_accessor.py +28 -0
  79. onetick/py/core/column_operations/accessors/decimal_accessor.py +104 -0
  80. onetick/py/core/column_operations/accessors/dt_accessor.py +537 -0
  81. onetick/py/core/column_operations/accessors/float_accessor.py +184 -0
  82. onetick/py/core/column_operations/accessors/str_accessor.py +1367 -0
  83. onetick/py/core/column_operations/base.py +1121 -0
  84. onetick/py/core/cut_builder.py +150 -0
  85. onetick/py/core/db_constants.py +20 -0
  86. onetick/py/core/eval_query.py +245 -0
  87. onetick/py/core/lambda_object.py +441 -0
  88. onetick/py/core/multi_output_source.py +232 -0
  89. onetick/py/core/per_tick_script.py +2256 -0
  90. onetick/py/core/query_inspector.py +464 -0
  91. onetick/py/core/source.py +1744 -0
  92. onetick/py/db/__init__.py +2 -0
  93. onetick/py/db/_inspection.py +1128 -0
  94. onetick/py/db/db.py +1327 -0
  95. onetick/py/db/utils.py +64 -0
  96. onetick/py/docs/__init__.py +0 -0
  97. onetick/py/docs/docstring_parser.py +112 -0
  98. onetick/py/docs/utils.py +81 -0
  99. onetick/py/functions.py +2398 -0
  100. onetick/py/license.py +190 -0
  101. onetick/py/log.py +88 -0
  102. onetick/py/math.py +935 -0
  103. onetick/py/misc.py +470 -0
  104. onetick/py/oqd/__init__.py +22 -0
  105. onetick/py/oqd/eps.py +1195 -0
  106. onetick/py/oqd/sources.py +325 -0
  107. onetick/py/otq.py +216 -0
  108. onetick/py/pyomd_mock.py +47 -0
  109. onetick/py/run.py +916 -0
  110. onetick/py/servers.py +173 -0
  111. onetick/py/session.py +1347 -0
  112. onetick/py/sources/__init__.py +19 -0
  113. onetick/py/sources/cache.py +167 -0
  114. onetick/py/sources/common.py +128 -0
  115. onetick/py/sources/csv.py +642 -0
  116. onetick/py/sources/custom.py +85 -0
  117. onetick/py/sources/data_file.py +305 -0
  118. onetick/py/sources/data_source.py +1045 -0
  119. onetick/py/sources/empty.py +94 -0
  120. onetick/py/sources/odbc.py +337 -0
  121. onetick/py/sources/order_book.py +271 -0
  122. onetick/py/sources/parquet.py +168 -0
  123. onetick/py/sources/pit.py +191 -0
  124. onetick/py/sources/query.py +495 -0
  125. onetick/py/sources/snapshots.py +419 -0
  126. onetick/py/sources/split_query_output_by_symbol.py +198 -0
  127. onetick/py/sources/symbology_mapping.py +123 -0
  128. onetick/py/sources/symbols.py +374 -0
  129. onetick/py/sources/ticks.py +825 -0
  130. onetick/py/sql.py +70 -0
  131. onetick/py/state.py +251 -0
  132. onetick/py/types.py +2131 -0
  133. onetick/py/utils/__init__.py +70 -0
  134. onetick/py/utils/acl.py +93 -0
  135. onetick/py/utils/config.py +186 -0
  136. onetick/py/utils/default.py +49 -0
  137. onetick/py/utils/file.py +38 -0
  138. onetick/py/utils/helpers.py +76 -0
  139. onetick/py/utils/locator.py +94 -0
  140. onetick/py/utils/perf.py +498 -0
  141. onetick/py/utils/query.py +49 -0
  142. onetick/py/utils/render.py +1374 -0
  143. onetick/py/utils/script.py +244 -0
  144. onetick/py/utils/temp.py +471 -0
  145. onetick/py/utils/types.py +120 -0
  146. onetick/py/utils/tz.py +84 -0
  147. onetick_py-1.177.0.dist-info/METADATA +137 -0
  148. onetick_py-1.177.0.dist-info/RECORD +152 -0
  149. onetick_py-1.177.0.dist-info/WHEEL +5 -0
  150. onetick_py-1.177.0.dist-info/entry_points.txt +2 -0
  151. onetick_py-1.177.0.dist-info/licenses/LICENSE +21 -0
  152. onetick_py-1.177.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1014 @@
1
+ from collections import defaultdict
2
+ from typing import List, Dict, Union, Optional, Tuple, TYPE_CHECKING
3
+ from copy import deepcopy
4
+
5
+ if TYPE_CHECKING:
6
+ from onetick.py.core.source import Source # hack for annotations
7
+
8
+ from onetick.py.core.column import _Column, _Operation
9
+ from onetick.py.core.column_operations.base import OnetickParameter
10
+ from onetick.py.core._source.tmp_otq import TmpOtq
11
+ from onetick.py import types as ott
12
+ from onetick.py.otq import otq
13
+
14
+ from ._base import (
15
+ _Aggregation,
16
+ _AggregationTSType,
17
+ _AggregationTSSelection,
18
+ _KeepTs,
19
+ _FloatAggregation,
20
+ _ExpectLargeInts,
21
+ _MultiColumnAggregation,
22
+ _AllColumnsAggregation,
23
+ validate,
24
+ )
25
+
26
+
27
+ class First(_AggregationTSType, _ExpectLargeInts):
28
+ NAME = "FIRST"
29
+ EP = otq.First
30
+
31
+ FIELDS_MAPPING = deepcopy(_AggregationTSType.FIELDS_MAPPING)
32
+ FIELDS_MAPPING.update(_ExpectLargeInts.FIELDS_MAPPING)
33
+ FIELDS_MAPPING['skip_tick_if'] = 'SKIP_TICK_IF'
34
+ FIELDS_DEFAULT = deepcopy(_AggregationTSType.FIELDS_DEFAULT)
35
+ FIELDS_DEFAULT.update(_ExpectLargeInts.FIELDS_DEFAULT)
36
+ FIELDS_DEFAULT['skip_tick_if'] = ''
37
+
38
+ def __init__(self, *args, skip_tick_if=None, **kwargs):
39
+ super().__init__(*args, **kwargs)
40
+
41
+ if skip_tick_if is ott.nan:
42
+ skip_tick_if = 'NAN'
43
+
44
+ self.skip_tick_if = '' if skip_tick_if is None else skip_tick_if
45
+
46
+
47
+ class FirstTime(_AggregationTSType):
48
+ NAME = "FIRST_TIME"
49
+ EP = otq.FirstTime
50
+ FIELDS_TO_SKIP = ["column_name"]
51
+
52
+ def __init__(self, *args, **kwargs):
53
+ super().__init__(column=_Column("TIMESTAMP"), *args, **kwargs)
54
+
55
+
56
+ class LastTime(_AggregationTSType):
57
+ NAME = "LAST_TIME"
58
+ EP = otq.LastTime
59
+ FIELDS_TO_SKIP = ["column_name"]
60
+
61
+ def __init__(self, *args, **kwargs):
62
+ super().__init__(column=_Column("TIMESTAMP"), *args, **kwargs)
63
+
64
+
65
+ class Last(First):
66
+ NAME = "LAST"
67
+ EP = otq.Last
68
+
69
+ FIELDS_MAPPING = deepcopy(First.FIELDS_MAPPING)
70
+ FIELDS_MAPPING['skip_tick_if'] = 'FWD_FILL_IF'
71
+ FIELDS_DEFAULT = deepcopy(First.FIELDS_DEFAULT)
72
+ FIELDS_DEFAULT['skip_tick_if'] = ''
73
+
74
+
75
+ class Count(_Aggregation):
76
+ NAME = "NUM_TICKS"
77
+ EP = otq.NumTicks
78
+ FIELDS_TO_SKIP = ["column_name"]
79
+ output_field_type = int
80
+
81
+ def __init__(self, *args, **kwargs):
82
+ super().__init__(column=_Column("TIMESTAMP"), *args, **kwargs)
83
+
84
+ def apply(self, src, name: str = 'VALUE', *args, **kwargs):
85
+ return super().apply(src=src, name=name, *args, **kwargs)
86
+
87
+
88
+ class Vwap(_Aggregation):
89
+
90
+ NAME = "VWAP"
91
+ EP = otq.Vwap
92
+
93
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
94
+ FIELDS_MAPPING['price_column'] = 'PRICE_FIELD_NAME'
95
+ FIELDS_MAPPING['size_column'] = 'SIZE_FIELD_NAME'
96
+ FIELDS_DEFAULT = deepcopy(_Aggregation.FIELDS_DEFAULT)
97
+ FIELDS_DEFAULT['price_column'] = 'PRICE'
98
+ FIELDS_DEFAULT['size_column'] = 'SIZE'
99
+
100
+ FIELDS_TO_SKIP: List = ['column_name']
101
+
102
+ output_field_type = float
103
+ require_type = (int, float, ott.nsectime, ott.decimal)
104
+
105
+ def __init__(self,
106
+ price_column: str,
107
+ size_column: str,
108
+ *args, **kwargs):
109
+ super().__init__(column=_Column('TIMESTAMP'), *args, **kwargs) # type: ignore
110
+ self.price_column = str(price_column)
111
+ self.size_column = str(size_column)
112
+
113
+ def validate_input_columns(self, src: 'Source'):
114
+ for column in [self.price_column, self.size_column]:
115
+ if column not in src.schema:
116
+ raise TypeError(
117
+ f"Aggregation `{self.NAME}` uses column `{column}` as input, which doesn't exist")
118
+ if not issubclass(src.schema[column], self.require_type):
119
+ raise TypeError(f"Aggregation `{self.NAME}` require {self.require_type} types, "
120
+ f"got {src.schema[column]}")
121
+
122
+ def apply(self, src: 'Source', name: str = 'VWAP', *args, **kwargs) -> 'Source':
123
+ return super().apply(src=src, name=name, *args, **kwargs)
124
+
125
+
126
+ class Correlation(_Aggregation):
127
+
128
+ NAME = 'CORRELATION'
129
+ EP = otq.Correlation
130
+
131
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
132
+ FIELDS_MAPPING['column_name_1'] = 'INPUT_FIELD1_NAME'
133
+ FIELDS_MAPPING['column_name_2'] = 'INPUT_FIELD2_NAME'
134
+
135
+ FIELDS_TO_SKIP: List = ['column_name']
136
+
137
+ output_field_type = float
138
+ require_type = (int, float)
139
+
140
+ def __init__(self,
141
+ column_name_1: str,
142
+ column_name_2: str,
143
+ *args, **kwargs):
144
+ super().__init__(column=_Column('TIMESTAMP'), *args, **kwargs) # type: ignore
145
+ self.column_name_1 = str(column_name_1)
146
+ self.column_name_2 = str(column_name_2)
147
+
148
+ def validate_input_columns(self, src: 'Source'):
149
+ for column in [self.column_name_1, self.column_name_2]:
150
+ if column not in src.schema:
151
+ raise TypeError(
152
+ f"Aggregation `{self.NAME}` uses column `{column}` as input, which doesn't exist")
153
+ if src.schema[column] not in self.require_type:
154
+ raise TypeError(f"Aggregation `{self.NAME}` require {self.require_type} types, "
155
+ f"got {src.schema[column]}")
156
+
157
+ def apply(self, src: 'Source', name: str = 'CORRELATION', *args, **kwargs) -> 'Source':
158
+ return super().apply(src=src, name=name, *args, **kwargs)
159
+
160
+
161
+ class FirstTick(_AggregationTSType, _KeepTs, _AllColumnsAggregation):
162
+ EP = otq.FirstTick
163
+ NAME = 'FIRST_TICK'
164
+ DEFAULT_OUTPUT_NAME = 'FIRST_TICK'
165
+
166
+ FIELDS_MAPPING = deepcopy(_AggregationTSType.FIELDS_MAPPING)
167
+ FIELDS_MAPPING['n'] = 'NUM_TICKS'
168
+ FIELDS_MAPPING['default_tick'] = 'DEFAULT_TICK'
169
+ FIELDS_DEFAULT = deepcopy(_AggregationTSType.FIELDS_DEFAULT)
170
+ FIELDS_DEFAULT['n'] = 1
171
+ FIELDS_DEFAULT['default_tick'] = ''
172
+
173
+ FIELDS_TO_SKIP = ['column_name', 'output_field_name', 'all_fields']
174
+
175
+ _validations_to_skip = ['running_all_fields']
176
+
177
+ def __init__(self, n: int = 1, *args, default_tick=None, **kwargs):
178
+ kwargs['all_fields'] = True
179
+ super().__init__(_Column("TIMESTAMP"), *args, **kwargs)
180
+ self.n = n
181
+ self._default_tick = default_tick
182
+ self.default_tick = ''
183
+
184
+ def validate_input_columns(self, src: 'Source'):
185
+ super().validate_input_columns(src)
186
+ if not self._default_tick:
187
+ self.default_tick = ''
188
+ return
189
+
190
+ fields = {}
191
+ for field, value in self._default_tick.items():
192
+ if field not in src.schema:
193
+ raise ValueError(f"Field '{field}' is not in schema")
194
+ dtype = src.schema[field]
195
+ if value is not None:
196
+ dtype = ott.get_object_type(value)
197
+ if dtype is not src.schema[field]:
198
+ raise ValueError(f"Incompatible types for field '{field}': {src.schema[field]} --> {dtype}")
199
+ value = ott.value2str(value)
200
+ dtype_str = ott.type2str(dtype)
201
+ fields[field] = (dtype_str, value)
202
+ self.default_tick = ','.join(
203
+ f'{field} {dtype} ({value})' if value is not None else f'{field} {dtype}'
204
+ for field, (dtype, value) in fields.items()
205
+ )
206
+
207
+
208
+ class LastTick(FirstTick):
209
+ EP = otq.LastTick
210
+ NAME = 'LAST_TICK'
211
+ DEFAULT_OUTPUT_NAME = 'LAST_TICK'
212
+
213
+
214
+ class Distinct(_AggregationTSSelection):
215
+ EP = otq.Distinct
216
+ NAME = 'DISTINCT'
217
+
218
+ FIELDS_MAPPING = deepcopy(_AggregationTSSelection.FIELDS_MAPPING)
219
+ FIELDS_MAPPING['column_name'] = 'KEYS'
220
+ FIELDS_MAPPING['key_attrs_only'] = 'KEY_ATTRS_ONLY'
221
+ FIELDS_DEFAULT = deepcopy(_AggregationTSSelection.FIELDS_DEFAULT)
222
+ FIELDS_DEFAULT['key_attrs_only'] = True
223
+
224
+ FIELDS_TO_SKIP = ['end_condition_per_group', 'group_by', 'output_field_name', 'all_fields']
225
+
226
+ def __init__(self,
227
+ keys: Union[str, List[str], _Column, List[_Column]],
228
+ key_attrs_only: bool = True,
229
+ *args, **kwargs):
230
+ keys = keys if isinstance(keys, list) else [keys] # type: ignore
231
+ super().__init__(column=keys, *args, **kwargs) # type: ignore
232
+ self.key_attrs_only = key_attrs_only
233
+
234
+ @validate
235
+ def _get_common_schema(self, src: 'Source', *args, **kwargs) -> Dict:
236
+ if self.key_attrs_only:
237
+ return super()._get_common_schema(src=src, *args, **kwargs)
238
+ return src.schema.copy()
239
+
240
+ @staticmethod
241
+ def validate_output_name(*args, **kwargs):
242
+ # Distinct aggregation doesn't have output fields
243
+ pass
244
+
245
+ def validate_input_columns(self, src: 'Source'):
246
+ for col in str(self.column_name).split(','):
247
+ if col.strip() not in src.schema:
248
+ raise TypeError(f"Aggregation `{self.NAME}` uses column `{col.strip()}` as input, which doesn't exist")
249
+
250
+ def apply(self, src: 'Source', *args, **kwargs) -> 'Source':
251
+ res = src.copy()
252
+ res.sink(self.to_ep(name=None))
253
+ schema = self._get_common_schema(src, None)
254
+ for col in str(self.column_name).split(','):
255
+ schema[col.strip()] = src.schema[col.strip()]
256
+ res.schema.set(**schema)
257
+ return res
258
+
259
+
260
+ class Sum(_FloatAggregation):
261
+ NAME = "SUM"
262
+ EP = otq.Sum
263
+
264
+
265
+ class Average(_FloatAggregation):
266
+ NAME = "AVERAGE"
267
+ EP = otq.Average
268
+ output_field_type = float
269
+
270
+
271
+ class StdDev(_Aggregation): # Stddev does not support inf, so no need to use _FloatAggregation
272
+ NAME = "STDDEV"
273
+ EP = otq.Stddev
274
+ require_type = (int, float, ott.decimal)
275
+ output_field_type = float
276
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
277
+ FIELDS_MAPPING['biased'] = 'BIASED'
278
+
279
+ def __init__(self, *args, biased: bool = True, **kwargs):
280
+ super().__init__(*args, **kwargs)
281
+ self.biased = biased
282
+
283
+
284
+ class TimeWeightedAvg(_AggregationTSType, _FloatAggregation):
285
+ NAME = "TW_AVERAGE"
286
+ EP = otq.TwAverage
287
+ output_field_type = float
288
+ FIELDS_DEFAULT = deepcopy(_AggregationTSType.FIELDS_DEFAULT)
289
+ FIELDS_DEFAULT['time_series_type'] = 'state_ts'
290
+
291
+ def __init__(self, *args, **kwargs):
292
+ kwargs.setdefault('time_series_type', 'state_ts')
293
+ super().__init__(*args, **kwargs)
294
+
295
+
296
+ class Median(_FloatAggregation):
297
+ NAME = "MEDIAN"
298
+ EP = otq.Median
299
+ output_field_type = float
300
+
301
+
302
+ class OptionPrice(_Aggregation):
303
+
304
+ NAME = 'OPTION_PRICE'
305
+ EP = otq.OptionPrice
306
+
307
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
308
+ FIELDS_MAPPING['volatility'] = 'VOLATILITY'
309
+ FIELDS_MAPPING['interest_rate'] = 'INTEREST_RATE'
310
+ FIELDS_MAPPING['compute_model'] = 'COMPUTE_MODEL'
311
+ FIELDS_MAPPING['number_of_steps'] = 'NUMBER_OF_STEPS'
312
+ FIELDS_MAPPING['compute_delta'] = 'COMPUTE_DELTA'
313
+ FIELDS_MAPPING['compute_gamma'] = 'COMPUTE_GAMMA'
314
+ FIELDS_MAPPING['compute_theta'] = 'COMPUTE_THETA'
315
+ FIELDS_MAPPING['compute_vega'] = 'COMPUTE_VEGA'
316
+ FIELDS_MAPPING['compute_rho'] = 'COMPUTE_RHO'
317
+ FIELDS_MAPPING['volatility_field_name'] = 'VOLATILITY_FIELD_NAME'
318
+ FIELDS_MAPPING['interest_rate_field_name'] = 'INTEREST_RATE_FIELD_NAME'
319
+ FIELDS_MAPPING['option_type_field_name'] = 'OPTION_TYPE_FIELD_NAME'
320
+ FIELDS_MAPPING['strike_price_field_name'] = 'STRIKE_PRICE_FIELD_NAME'
321
+ FIELDS_MAPPING['days_in_year'] = 'DAYS_IN_YEAR'
322
+ FIELDS_MAPPING['days_till_expiration_field_name'] = 'DAYS_TILL_EXPIRATION_FIELD_NAME'
323
+ FIELDS_MAPPING['expiration_date_field_name'] = 'EXPIRATION_DATE_FIELD_NAME'
324
+
325
+ FIELDS_TO_SKIP: List = ['column_name', 'all_fields', 'end_condition_per_group', 'group_by', 'output_field_name']
326
+
327
+ output_field_type = float
328
+ require_type = (float,)
329
+
330
+ def __init__(self, # NOSONAR
331
+ volatility: Optional[float] = None,
332
+ interest_rate: Optional[float] = None,
333
+ compute_model: str = 'BS',
334
+ number_of_steps: Optional[int] = None,
335
+ compute_delta: bool = False,
336
+ compute_gamma: bool = False,
337
+ compute_theta: bool = False,
338
+ compute_vega: bool = False,
339
+ compute_rho: bool = False,
340
+ volatility_field_name: str = '',
341
+ interest_rate_field_name: str = '',
342
+ option_type_field_name: str = '',
343
+ strike_price_field_name: str = '',
344
+ days_in_year: int = 365,
345
+ days_till_expiration_field_name: str = '',
346
+ expiration_date_field_name: str = '',
347
+ *args, **kwargs):
348
+ super().__init__(column=_Column('PRICE'), *args, **kwargs) # type: ignore
349
+ self.volatility = volatility
350
+ self.interest_rate = interest_rate
351
+ self.compute_model = compute_model
352
+ self.number_of_steps = number_of_steps
353
+ self.compute_delta = compute_delta
354
+ self.compute_gamma = compute_gamma
355
+ self.compute_theta = compute_theta
356
+ self.compute_vega = compute_vega
357
+ self.compute_rho = compute_rho
358
+ self.volatility_field_name = volatility_field_name
359
+ self.interest_rate_field_name = interest_rate_field_name
360
+ self.option_type_field_name = option_type_field_name
361
+ self.strike_price_field_name = strike_price_field_name
362
+ self.days_in_year = days_in_year
363
+ self.days_till_expiration_field_name = days_till_expiration_field_name
364
+ self.expiration_date_field_name = expiration_date_field_name
365
+
366
+ def apply(self, src: 'Source', name: str = 'VALUE', *args, **kwargs) -> 'Source':
367
+ return super().apply(src=src, name=name, *args, **kwargs)
368
+
369
+ def _get_output_schema(self, src: 'Source', name: Optional[str] = None) -> Dict:
370
+ output_schema = super()._get_output_schema(src, name=name)
371
+ compute = {
372
+ k.upper(): float for k in ['delta', 'gamma', 'theta', 'vega', 'rho'] if getattr(self, f'compute_{k}')
373
+ }
374
+ output_schema.update(compute)
375
+ return output_schema
376
+
377
+
378
+ class Ranking(_KeepTs):
379
+ NAME = 'RANKING'
380
+ EP = otq.Ranking
381
+ FIELDS_TO_SKIP = ['column_name', 'output_field_name']
382
+ FIELDS_MAPPING = deepcopy(_KeepTs.FIELDS_MAPPING)
383
+ FIELDS_MAPPING.update({
384
+ 'rank_by': 'RANK_BY',
385
+ 'show_rank_as': 'SHOW_RANK_AS',
386
+ 'include_tick_in_percentage': 'INCLUDE_TICK_IN_PERCENTAGE',
387
+ })
388
+ FIELDS_DEFAULT = deepcopy(_KeepTs.FIELDS_DEFAULT)
389
+ FIELDS_DEFAULT.update({
390
+ 'show_rank_as': 'ORDER',
391
+ 'include_tick_in_percentage': False,
392
+ })
393
+ _ALLOWED_RANK_BY = {'asc', 'desc'}
394
+ _ALLOWED_SHOW_RANK_AS = {'order', 'percentile_standard', 'percent_le_values', 'percent_lt_values'}
395
+
396
+ def __init__(self, rank_by, *args, show_rank_as='ORDER', include_tick=False, **kwargs):
397
+ super().__init__(column=_Column('TIMESTAMP'), *args, **kwargs)
398
+
399
+ if isinstance(rank_by, str):
400
+ rank_by = [rank_by]
401
+ if not isinstance(rank_by, dict):
402
+ rank_by = {column: 'desc' for column in rank_by}
403
+ for k in rank_by:
404
+ rank_by[k] = rank_by[k].lower()
405
+ rank_by_values = set(rank_by.values())
406
+ diff = rank_by_values.difference(self._ALLOWED_RANK_BY)
407
+ if diff:
408
+ raise ValueError(f"Only {self._ALLOWED_RANK_BY} values of parameter 'rank_by' are allowed, got {diff}.")
409
+ self._rank_by = rank_by
410
+
411
+ show_rank_as = show_rank_as.lower()
412
+ if show_rank_as not in self._ALLOWED_SHOW_RANK_AS:
413
+ raise ValueError(f"Only {self._ALLOWED_SHOW_RANK_AS} values of parameter 'show_rank_as' are allowed.")
414
+ self.show_rank_as = show_rank_as
415
+ self.include_tick_in_percentage = include_tick
416
+
417
+ @property
418
+ def rank_by(self):
419
+ return ','.join(f'{k} {w}' for k, w in self._rank_by.items())
420
+
421
+ def apply(self, src):
422
+ return super().apply(src=src, name='RANKING')
423
+
424
+ def validate_input_columns(self, src: 'Source'):
425
+ for col in self._rank_by:
426
+ if col not in src.schema:
427
+ raise TypeError(f"Ranking aggregation uses column '{col}' as input, which doesn't exist")
428
+
429
+ @staticmethod
430
+ def validate_output_name(schema, name):
431
+ if 'RANKING' in schema:
432
+ raise ValueError("Output field of ranking aggregation will be named 'RANKING',"
433
+ " but this field exists in the input schema.")
434
+ _KeepTs.validate_output_name(schema, name)
435
+
436
+ def _get_output_schema(self, src: 'Source', name: Optional[str] = None):
437
+ return {
438
+ 'RANKING': int if self.show_rank_as == 'order' else float
439
+ }
440
+
441
+
442
+ class Variance(_Aggregation):
443
+ NAME = 'VARIANCE'
444
+ EP = otq.Variance
445
+ output_field_type = float
446
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
447
+ FIELDS_MAPPING['biased'] = 'BIASED'
448
+ require_type = (int, float)
449
+
450
+ def __init__(self,
451
+ column: Union[str, _Column, _Operation],
452
+ biased: bool,
453
+ *args, **kwargs):
454
+ super().__init__(column, *args, **kwargs)
455
+ self.biased = biased
456
+
457
+ def apply(self, src: 'Source', name: str = 'VARIANCE', *args, **kwargs) -> 'Source':
458
+ return super().apply(src=src, name=name, *args, **kwargs)
459
+
460
+
461
+ class Percentile(_Aggregation):
462
+ NAME = 'PERCENTILE'
463
+ EP = otq.Percentile
464
+
465
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
466
+ FIELDS_MAPPING['number_of_quantiles'] = 'NUMBER_OF_QUANTILES'
467
+ FIELDS_MAPPING['column_name'] = 'INPUT_FIELD_NAMES'
468
+ FIELDS_MAPPING['output_field_names'] = 'OUTPUT_FIELD_NAMES'
469
+
470
+ FIELDS_TO_SKIP = ['output_field_name', 'all_fields']
471
+
472
+ def __init__(
473
+ self,
474
+ input_field_names: List[Union[Union[str, _Column], Tuple[Union[str, _Column], str]]],
475
+ output_field_names: Optional[List[str]] = None,
476
+ number_of_quantiles: int = 2,
477
+ *args, **kwargs,
478
+ ):
479
+ self.number_of_quantiles = number_of_quantiles
480
+
481
+ if not input_field_names:
482
+ raise ValueError('`input_field_names` should be passed')
483
+
484
+ if not isinstance(input_field_names, list):
485
+ raise ValueError('`input_field_names` should be the `list` of columns')
486
+
487
+ if output_field_names is not None and not isinstance(output_field_names, list):
488
+ raise ValueError('`output_field_names` should be the `list` of columns')
489
+
490
+ if output_field_names and len(input_field_names) != len(output_field_names):
491
+ raise ValueError('`output_field_names` should have the same number of elements as `input_field_names`')
492
+
493
+ columns = ', '.join(
494
+ str(field) if isinstance(field, (str, _Column)) else f'{str(field[0])} {field[1].upper()}'
495
+ for field in input_field_names
496
+ )
497
+
498
+ self._columns = [
499
+ str(field) if not isinstance(field, tuple) else str(field[0])
500
+ for field in input_field_names
501
+ ]
502
+
503
+ self.output_field_names = '' if output_field_names is None else ', '.join(output_field_names)
504
+
505
+ super().__init__(columns, *args, **kwargs)
506
+
507
+ def validate_input_columns(self, src: 'Source'):
508
+ for col in self._columns:
509
+ if col.strip() not in src.schema:
510
+ raise TypeError(f"Aggregation `{self.NAME}` uses column `{col.strip()}` as input, which doesn't exist")
511
+
512
+ def apply(self, src: 'Source', *args, **kwargs) -> 'Source':
513
+ res = src.copy()
514
+ res.sink(self.to_ep(name=None))
515
+ schema = self._get_common_schema(src, None)
516
+ for col in self._columns:
517
+ schema[col.strip()] = src.schema[col.strip()]
518
+
519
+ schema['QUANTILE'] = int
520
+ res.schema.set(**schema)
521
+ return res
522
+
523
+
524
+ class FindValueForPercentile(_FloatAggregation):
525
+ NAME = 'FIND_VALUE_FOR_PERCENTILE'
526
+ EP = None
527
+
528
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
529
+ FIELDS_MAPPING['percentile'] = 'PERCENTILE'
530
+ FIELDS_MAPPING['show_percentile_as'] = 'SHOW_PERCENTILE_AS'
531
+
532
+ FIELDS_TO_SKIP = ['all_fields']
533
+
534
+ output_field_type = float
535
+
536
+ def __init__(
537
+ self, column,
538
+ percentile: int,
539
+ show_percentile_as: str = '',
540
+ *args, **kwargs
541
+ ):
542
+ try:
543
+ self.EP = otq.FindValueForPercentile
544
+ except AttributeError as exc:
545
+ raise RuntimeError("This OneTick build doesn't support FindValueForPercentile aggregation") from exc
546
+
547
+ if not isinstance(percentile, int) or percentile < 0 or percentile > 100:
548
+ raise ValueError("Parameter 'percentile' must be a number between 0 and 100.")
549
+ if show_percentile_as and show_percentile_as not in {'interpolated_value', 'first_value_with_ge_percentile'}:
550
+ raise ValueError(f"Unsupported value for parameter 'show_percentile_as': {show_percentile_as}")
551
+ self.percentile = percentile
552
+ self.show_percentile_as = show_percentile_as.upper()
553
+
554
+ FindValueForPercentile.FIELDS_MAPPING['show_percentile_as'] = \
555
+ 'SHOW_PERCENTILE_AS' if 'show_percentile_as' in self.EP.Parameters.__dict__ else 'COMPUTE_VALUE_AS'
556
+
557
+ super().__init__(column, *args, **kwargs)
558
+
559
+
560
+ class ExpWAverage(_FloatAggregation, _AggregationTSType):
561
+ NAME = 'EXP_W_AVERAGE'
562
+ EP = otq.ExpWAverage
563
+
564
+ FIELDS_MAPPING = deepcopy(_AggregationTSType.FIELDS_MAPPING)
565
+ FIELDS_MAPPING['decay'] = 'DECAY'
566
+ FIELDS_MAPPING['decay_value_type'] = 'DECAY_VALUE_TYPE'
567
+ FIELDS_DEFAULT = deepcopy(_AggregationTSType.FIELDS_DEFAULT)
568
+ FIELDS_DEFAULT['time_series_type'] = 'state_ts'
569
+ FIELDS_DEFAULT['decay_value_type'] = 'lambda'
570
+
571
+ output_field_type = float
572
+
573
+ def __init__(self, column, decay, decay_value_type='lambda', time_series_type='state_ts', *args, **kwargs):
574
+ super().__init__(column, time_series_type=time_series_type, *args, **kwargs)
575
+
576
+ if decay_value_type not in ['lambda', 'half_life_index']:
577
+ raise ValueError(f"Parameter 'decay_value_type' has incorrect value: {decay_value_type}, "
578
+ f"should be one of next: 'lambda', 'half_life_index'")
579
+
580
+ self.decay = decay
581
+ self.decay_value_type = decay_value_type.upper()
582
+
583
+
584
+ class ExpTwAverage(_FloatAggregation):
585
+ NAME = 'EXP_TW_AVERAGE'
586
+ EP = otq.ExpTwAverage
587
+
588
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
589
+ FIELDS_MAPPING['decay'] = 'DECAY'
590
+ FIELDS_MAPPING['decay_value_type'] = 'DECAY_VALUE_TYPE'
591
+ FIELDS_DEFAULT = deepcopy(_Aggregation.FIELDS_DEFAULT)
592
+ FIELDS_DEFAULT['decay_value_type'] = 'half_life_index'
593
+
594
+ output_field_type = float
595
+
596
+ def __init__(self, column, decay, decay_value_type='half_life_index', *args, **kwargs):
597
+ if decay_value_type not in ['lambda', 'half_life_index']:
598
+ raise ValueError(f"Parameter 'decay_value_type' has incorrect value: {decay_value_type}, "
599
+ f"should be one of next: 'lambda', 'half_life_index'")
600
+
601
+ self.decay = decay
602
+ self.decay_value_type = decay_value_type.upper()
603
+
604
+ super().__init__(column, *args, **kwargs)
605
+
606
+
607
+ class StandardizedMoment(_Aggregation):
608
+ NAME = 'STANDARDIZED_MOMENT'
609
+ EP = None
610
+
611
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
612
+ FIELDS_MAPPING['degree'] = 'DEGREE'
613
+ FIELDS_DEFAULT = deepcopy(_Aggregation.FIELDS_DEFAULT)
614
+ FIELDS_DEFAULT['degree'] = 3
615
+
616
+ output_field_type = float
617
+
618
+ def __init__(self, *args, degree=3, **kwargs):
619
+ try:
620
+ self.EP = otq.StandardizedMoment
621
+ except AttributeError as exc:
622
+ raise RuntimeError("Used onetick installation not support onetick.query.StandardizedMoment") from exc
623
+
624
+ self.degree = degree
625
+
626
+ super().__init__(*args, **kwargs)
627
+
628
+
629
+ class PortfolioPrice(_Aggregation, _MultiColumnAggregation):
630
+ NAME = 'PORTFOLIO_PRICE'
631
+ EP = otq.PortfolioPrice
632
+ DEFAULT_OUTPUT_NAME = 'VALUE'
633
+
634
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
635
+ FIELDS_MAPPING['column_name'] = 'INPUT_FIELD_NAME'
636
+ FIELDS_MAPPING['weight_field_name'] = 'WEIGHT_FIELD_NAME'
637
+ FIELDS_MAPPING['side'] = 'SIDE'
638
+ FIELDS_MAPPING['weight_type'] = 'WEIGHT_TYPE'
639
+
640
+ FIELDS_DEFAULT = deepcopy(_Aggregation.FIELDS_DEFAULT)
641
+ FIELDS_DEFAULT['column_name'] = ''
642
+ FIELDS_DEFAULT['weight_field_name'] = ''
643
+ FIELDS_DEFAULT['side'] = 'BOTH'
644
+ FIELDS_DEFAULT['weight_type'] = 'ABSOLUTE'
645
+
646
+ FIELDS_TO_SKIP = ['output_field_name']
647
+
648
+ output_field_type = float
649
+
650
+ def __init__(
651
+ self, column='', weight_field_name='', side='both', weight_type='absolute', symbols=None, *args, **kwargs,
652
+ ):
653
+ if side not in ['long', 'short', 'both']:
654
+ raise ValueError(f"Parameter `side` has incorrect value: {side}, "
655
+ f"should be one of next: 'long', 'short', 'both")
656
+
657
+ if weight_type not in ['absolute', 'relative']:
658
+ raise ValueError(f"Parameter `weight_type` has incorrect value: {weight_type}, "
659
+ f"should be one of next: 'absolute', 'relative'")
660
+
661
+ if isinstance(weight_field_name, _Column):
662
+ weight_field_name = str(weight_field_name)
663
+
664
+ self.weight_field_name = weight_field_name
665
+ self.side = side.upper()
666
+ self.weight_type = weight_type.upper()
667
+ self.symbols = symbols
668
+
669
+ super().__init__(column=column, *args, **kwargs)
670
+
671
+ def apply(self, src, name='VALUE', inplace=False):
672
+ if inplace:
673
+ res = src
674
+ src = src.copy()
675
+ else:
676
+ res = src.copy()
677
+
678
+ out_name = name or self.column_name
679
+ schema = self._get_common_schema(src, out_name)
680
+ # it's important to validate input schema before sinking
681
+ self._modify_source(res)
682
+
683
+ ep = self.to_ep(name=str(out_name))
684
+
685
+ if self.symbols:
686
+ from onetick.py.core.source import Source
687
+ from onetick.py.sources import query as otp_query
688
+ from onetick.py.core.eval_query import _QueryEvalWrapper
689
+
690
+ tmp_otq = TmpOtq()
691
+
692
+ if isinstance(self.symbols, (otp_query, _QueryEvalWrapper)):
693
+ self.symbols = self.symbols.to_eval_string(tmp_otq=tmp_otq)
694
+ elif isinstance(self.symbols, (Source, otq.GraphQuery)):
695
+ self.symbols = Source._convert_symbol_to_string(self.symbols, tmp_otq=tmp_otq)
696
+
697
+ ep = ep.symbols(self.symbols)
698
+
699
+ new_res = Source(schema=schema)
700
+
701
+ new_res._copy_state_vars_from(res)
702
+ new_res._clean_sources_dates()
703
+ new_res._merge_tmp_otq(res)
704
+
705
+ if isinstance(self.symbols, Source):
706
+ new_res._merge_tmp_otq(self.symbols)
707
+
708
+ new_res._tmp_otq.merge(tmp_otq)
709
+
710
+ eps = defaultdict()
711
+ new_res.source(res.node().copy_graph(eps))
712
+ new_res.node().add_rules(res.node().copy_rules())
713
+ new_res._set_sources_dates(res, copy_symbols=not bool(self.symbols))
714
+
715
+ res = new_res
716
+
717
+ if inplace:
718
+ src = res
719
+
720
+ res.sink(ep)
721
+
722
+ schema.update(self._get_output_schema(src, str(out_name)))
723
+ res.schema.set(**schema)
724
+ if not self.all_fields:
725
+ # in this case we propagate only resulting fields, that stored in res.schema (flexible schema case)
726
+ res._add_table(strict=True)
727
+ else:
728
+ # adding table to convert types in schema, e.g. float to int
729
+ res._add_table(strict=False)
730
+
731
+ return res
732
+
733
+ def validate_input_columns(self, src: 'Source'):
734
+ if self.weight_field_name and self.weight_field_name not in src.schema:
735
+ raise TypeError(
736
+ f"Aggregation `{self.NAME}` uses column `{self.weight_field_name}` as input, which doesn't exist",
737
+ )
738
+
739
+ def _get_output_schema(self, src: 'Source', name: Optional[str] = None):
740
+ if not name:
741
+ name = self.DEFAULT_OUTPUT_NAME
742
+
743
+ return {
744
+ name: float,
745
+ 'NUM_SYMBOLS': int,
746
+ }
747
+
748
+
749
+ class MultiPortfolioPrice(PortfolioPrice):
750
+ NAME = 'MULTI_PORTFOLIO_PRICE'
751
+ EP = otq.MultiPortfolioPrice
752
+ DEFAULT_OUTPUT_NAME = 'VALUE'
753
+
754
+ FIELDS_MAPPING = deepcopy(PortfolioPrice.FIELDS_MAPPING)
755
+ FIELDS_MAPPING['weight_multiplier_field_name'] = 'WEIGHT_MULTIPLIER_FIELD_NAME'
756
+ FIELDS_MAPPING['portfolios_query'] = 'PORTFOLIOS_QUERY'
757
+ FIELDS_MAPPING['portfolios_query_params'] = 'PORTFOLIOS_QUERY_PARAMS'
758
+ FIELDS_MAPPING['portfolio_value_field_name'] = 'PORTFOLIO_VALUE_FIELD_NAME'
759
+
760
+ FIELDS_DEFAULT = deepcopy(PortfolioPrice.FIELDS_DEFAULT)
761
+ FIELDS_DEFAULT['weight_multiplier_field_name'] = ''
762
+ FIELDS_DEFAULT['portfolios_query'] = ''
763
+ FIELDS_DEFAULT['portfolios_query_params'] = ''
764
+ FIELDS_DEFAULT['portfolio_value_field_name'] = ''
765
+
766
+ FIELDS_TO_SKIP = ['output_field_name']
767
+
768
+ output_field_type = float
769
+
770
+ def __init__(
771
+ self, columns='', weight_multiplier_field_name='', portfolios_query='', portfolios_query_params='',
772
+ portfolio_value_field_name='', *args, **kwargs,
773
+ ):
774
+ if not portfolios_query:
775
+ raise ValueError('Required parameter `portfolio_query` not set')
776
+
777
+ self.weight_multiplier_field_name = weight_multiplier_field_name
778
+
779
+ from onetick.py.core.source import Source
780
+ if isinstance(portfolios_query, Source):
781
+ self.portfolios_query = portfolios_query.to_otq()
782
+ else:
783
+ self.portfolios_query = portfolios_query
784
+
785
+ if isinstance(portfolios_query_params, dict):
786
+ portfolios_query_params = ','.join([f'{k}={ott.value2str(v)}' for k, v in portfolios_query_params.items()])
787
+
788
+ self.portfolios_query_params = portfolios_query_params
789
+
790
+ if not portfolio_value_field_name:
791
+ portfolio_value_field_name = self.DEFAULT_OUTPUT_NAME
792
+
793
+ if isinstance(portfolio_value_field_name, str):
794
+ portfolio_value_field_name_str = portfolio_value_field_name
795
+ portfolio_value_field_name_list = portfolio_value_field_name.split(',')
796
+ else:
797
+ portfolio_value_field_name_str = ','.join(map(str, portfolio_value_field_name))
798
+ portfolio_value_field_name_list = list(map(str, portfolio_value_field_name))
799
+
800
+ self._portfolio_value_field_name = portfolio_value_field_name_list
801
+ self.portfolio_value_field_name = portfolio_value_field_name_str
802
+
803
+ len_columns = max(len(columns) if isinstance(columns, list) else len(columns.split(',')), 1)
804
+ if len_columns != len(portfolio_value_field_name_list):
805
+ raise RuntimeError('The number of the field names must match the number of the field names '
806
+ 'listed in the `columns` parameter')
807
+
808
+ super().__init__(column=columns, *args, **kwargs)
809
+
810
+ def validate_input_columns(self, src: 'Source'):
811
+ super().validate_input_columns(src)
812
+
813
+ if self.weight_multiplier_field_name and self.weight_multiplier_field_name not in src.schema:
814
+ raise TypeError(
815
+ f"Aggregation `{self.NAME}` uses column `{self.weight_multiplier_field_name}` as input, "
816
+ f"which doesn't exist",
817
+ )
818
+
819
+ def _get_output_schema(self, src: 'Source', name: Optional[str] = None):
820
+ if not name:
821
+ return super()._get_output_schema(src, name)
822
+
823
+ return {
824
+ **{column_name: float for column_name in self._portfolio_value_field_name},
825
+ 'NUM_SYMBOLS': int,
826
+ 'PORTFOLIO_NAME': str,
827
+ }
828
+
829
+
830
+ class Return(_Aggregation):
831
+ NAME = 'RETURN'
832
+ EP = otq.Return
833
+ DEFAULT_OUTPUT_NAME = 'RETURN'
834
+
835
+ output_field_type = float
836
+
837
+
838
+ class ImpliedVol(_Aggregation):
839
+ NAME = 'IMPLIED_VOL'
840
+ EP = otq.ImpliedVol
841
+
842
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
843
+ FIELDS_MAPPING['interest_rate'] = 'INTEREST_RATE'
844
+ FIELDS_MAPPING['price_field_name'] = 'PRICE_FIELD_NAME'
845
+ FIELDS_MAPPING['option_price_field_name'] = 'OPTION_PRICE_FIELD_NAME'
846
+ FIELDS_MAPPING['method'] = 'METHOD'
847
+ FIELDS_MAPPING['precision'] = 'PRECISION'
848
+ FIELDS_MAPPING['value_for_non_converge'] = 'VALUE_FOR_NON_CONVERGE'
849
+ FIELDS_MAPPING['interest_rate_field_name'] = 'INTEREST_RATE_FIELD_NAME'
850
+ FIELDS_MAPPING['option_type_field_name'] = 'OPTION_TYPE_FIELD_NAME'
851
+ FIELDS_MAPPING['strike_price_field_name'] = 'STRIKE_PRICE_FIELD_NAME'
852
+ FIELDS_MAPPING['days_in_year'] = 'DAYS_IN_YEAR'
853
+ FIELDS_MAPPING['days_till_expiration_field_name'] = 'DAYS_TILL_EXPIRATION_FIELD_NAME'
854
+ FIELDS_MAPPING['expiration_date_field_name'] = 'EXPIRATION_DATE_FIELD_NAME'
855
+
856
+ FIELDS_DEFAULT = deepcopy(_Aggregation.FIELDS_DEFAULT)
857
+ FIELDS_DEFAULT['interest_rate'] = None
858
+ FIELDS_DEFAULT['price_field_name'] = 'PRICE'
859
+ FIELDS_DEFAULT['option_price_field_name'] = 'OPTION_PRICE'
860
+ FIELDS_DEFAULT['method'] = 'NEWTON'
861
+ FIELDS_DEFAULT['precision'] = 0.00001
862
+ FIELDS_DEFAULT['value_for_non_converge'] = 'NAN_VAL'
863
+ FIELDS_DEFAULT['interest_rate_field_name'] = ''
864
+ FIELDS_DEFAULT['option_type_field_name'] = ''
865
+ FIELDS_DEFAULT['strike_price_field_name'] = ''
866
+ FIELDS_DEFAULT['days_in_year'] = 365
867
+ FIELDS_DEFAULT['days_till_expiration_field_name'] = ''
868
+ FIELDS_DEFAULT['expiration_date_field_name'] = ''
869
+
870
+ FIELDS_TO_SKIP = ['column_name', 'output_field_name']
871
+
872
+ output_field_type = float
873
+
874
+ def __init__(
875
+ self,
876
+ interest_rate: Optional[Union[float, int, _Column, str]] = None,
877
+ price_field: Union[_Column, str] = 'PRICE',
878
+ option_price_field: Union[_Column, str] = 'OPTION_PRICE',
879
+ method: str = 'newton',
880
+ precision: float = 0.00001,
881
+ value_for_non_converge: str = 'nan_val',
882
+ option_type_field: Union[_Column, str] = '',
883
+ strike_price_field: Union[_Column, str] = '',
884
+ days_in_year: int = 365,
885
+ days_till_expiration_field: Union[_Column, str] = '',
886
+ expiration_date_field: Union[_Column, str] = '',
887
+ *args, **kwargs,
888
+ ):
889
+ self.interest_rate_field_name = ''
890
+ self.interest_rate = None
891
+
892
+ if interest_rate is not None:
893
+ if isinstance(interest_rate, (str, _Column)):
894
+ self.interest_rate_field_name = str(interest_rate)
895
+ elif isinstance(interest_rate, (int, float)):
896
+ self.interest_rate = interest_rate
897
+ else:
898
+ raise ValueError(f'Unsupported value passed as `interest_rate`: {type(interest_rate)}')
899
+
900
+ if value_for_non_converge not in {'nan_val', 'closest_found_val'}:
901
+ raise ValueError(f'Unsupported value for `value_for_non_converge` was passed: {value_for_non_converge}')
902
+
903
+ if method not in {'newton', 'newton_with_fallback', 'bisections'}:
904
+ raise ValueError(f'Unsupported value for `method` was passed: {method}')
905
+
906
+ if isinstance(price_field, _Column):
907
+ price_field = str(price_field)
908
+ if isinstance(option_price_field, _Column):
909
+ option_price_field = str(option_price_field)
910
+
911
+ if isinstance(option_type_field, _Column):
912
+ option_type_field = str(option_type_field)
913
+ if isinstance(strike_price_field, _Column):
914
+ strike_price_field = str(strike_price_field)
915
+ if isinstance(days_till_expiration_field, _Column):
916
+ days_till_expiration_field = str(days_till_expiration_field)
917
+ if isinstance(expiration_date_field, _Column):
918
+ expiration_date_field = str(expiration_date_field)
919
+
920
+ self.price_field_name = price_field
921
+ self.option_price_field_name = option_price_field
922
+ self.method = method.upper()
923
+ self.precision = precision
924
+ self.value_for_non_converge = value_for_non_converge.upper()
925
+ self.option_type_field_name = option_type_field
926
+ self.strike_price_field_name = strike_price_field
927
+ self.days_in_year = days_in_year
928
+ self.days_till_expiration_field_name = days_till_expiration_field
929
+ self.expiration_date_field_name = expiration_date_field
930
+
931
+ super().__init__(_Column(price_field), *args, **kwargs)
932
+
933
+ def validate_input_columns(self, src: 'Source'):
934
+ columns_to_check: Dict[str, Tuple[Union[str, _Column], Union[Tuple, type]]] = {
935
+ 'price_field': (self.price_field_name, (int, float)),
936
+ 'option_price_field': (self.option_price_field_name, (int, float)),
937
+ 'interest_rate': (self.interest_rate_field_name, (int, float)),
938
+ 'option_type_field': (self.option_type_field_name, str),
939
+ 'strike_price_field': (self.strike_price_field_name, (int, float)),
940
+ 'days_till_expiration_field': (self.days_till_expiration_field_name, int),
941
+ 'expiration_date_field': (self.expiration_date_field_name, int),
942
+ }
943
+
944
+ for parameter, (column, required_type) in columns_to_check.items():
945
+ if not column:
946
+ # probably parameter will be set as symbol parameter, we couldn't check this right now
947
+ continue
948
+
949
+ if column not in src.schema:
950
+ raise TypeError(
951
+ f"Aggregation `{self.NAME}` uses column `{column}` from parameter `{parameter}` as input, "
952
+ f"which doesn't exist."
953
+ )
954
+
955
+ if not issubclass(src.schema[column], required_type):
956
+ raise TypeError(
957
+ f"Aggregation `{self.NAME}` require column `{column}` from parameter `{parameter}` "
958
+ f"to be {required_type}, got {src.schema[column]}"
959
+ )
960
+
961
+ def apply(self, src: 'Source', name: str = 'VALUE', *args, **kwargs) -> 'Source':
962
+ return super().apply(src=src, name=name, *args, **kwargs)
963
+
964
+
965
+ class LinearRegression(_Aggregation, _MultiColumnAggregation):
966
+ NAME = 'LINEAR_REGRESSION'
967
+ EP = None
968
+
969
+ FIELDS_MAPPING = deepcopy(_Aggregation.FIELDS_MAPPING)
970
+ FIELDS_MAPPING['dependent_variable_field_name'] = 'DEPENDENT_VARIABLE_FIELD_NAME'
971
+ FIELDS_MAPPING['independent_variable_field_name'] = 'INDEPENDENT_VARIABLE_FIELD_NAME'
972
+
973
+ FIELDS_TO_SKIP = ['column_name', 'output_field_name']
974
+
975
+ def __init__(
976
+ self,
977
+ dependent_variable_field_name: Union[_Column, str, OnetickParameter],
978
+ independent_variable_field_name: Union[_Column, str, OnetickParameter],
979
+ *args, **kwargs,
980
+ ):
981
+ try:
982
+ self.EP = otq.LinearRegression
983
+ except AttributeError as exc:
984
+ raise RuntimeError("Used onetick installation not support onetick.query.LinearRegression") from exc
985
+
986
+ if isinstance(dependent_variable_field_name, _Column):
987
+ dependent_variable_field_name = str(dependent_variable_field_name)
988
+ if isinstance(independent_variable_field_name, _Column):
989
+ independent_variable_field_name = str(independent_variable_field_name)
990
+
991
+ self.dependent_variable_field_name = dependent_variable_field_name
992
+ self.independent_variable_field_name = independent_variable_field_name
993
+
994
+ super().__init__(_Column('TIMESTAMP'), *args, **kwargs)
995
+
996
+ def validate_input_columns(self, src: 'Source'):
997
+ super().validate_input_columns(src)
998
+
999
+ for column in [self.dependent_variable_field_name, self.independent_variable_field_name]:
1000
+ if isinstance(column, OnetickParameter):
1001
+ continue
1002
+
1003
+ if column not in src.schema:
1004
+ raise TypeError(f"Aggregation `{self.NAME}` uses column `{column}` as input, which doesn't exist")
1005
+
1006
+ for out_field in ['SLOPE', 'INTERCEPT']:
1007
+ if out_field in src.schema:
1008
+ raise TypeError(f"Field `{out_field}`, which is `{self.NAME}` aggregation output column, is in schema.")
1009
+
1010
+ def _get_output_schema(self, src: 'Source', name: Optional[str] = None) -> dict:
1011
+ return {
1012
+ 'SLOPE': float,
1013
+ 'INTERCEPT': float,
1014
+ }