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.
- locator_parser/__init__.py +0 -0
- locator_parser/acl.py +73 -0
- locator_parser/actions.py +262 -0
- locator_parser/common.py +368 -0
- locator_parser/io.py +43 -0
- locator_parser/locator.py +150 -0
- onetick/__init__.py +101 -0
- onetick/doc_utilities/__init__.py +3 -0
- onetick/doc_utilities/napoleon.py +40 -0
- onetick/doc_utilities/ot_doctest.py +140 -0
- onetick/doc_utilities/snippets.py +279 -0
- onetick/lib/__init__.py +4 -0
- onetick/lib/instance.py +141 -0
- onetick/py/__init__.py +293 -0
- onetick/py/_stack_info.py +89 -0
- onetick/py/_version.py +2 -0
- onetick/py/aggregations/__init__.py +11 -0
- onetick/py/aggregations/_base.py +648 -0
- onetick/py/aggregations/_docs.py +948 -0
- onetick/py/aggregations/compute.py +286 -0
- onetick/py/aggregations/functions.py +2216 -0
- onetick/py/aggregations/generic.py +104 -0
- onetick/py/aggregations/high_low.py +80 -0
- onetick/py/aggregations/num_distinct.py +83 -0
- onetick/py/aggregations/order_book.py +501 -0
- onetick/py/aggregations/other.py +1014 -0
- onetick/py/backports.py +26 -0
- onetick/py/cache.py +374 -0
- onetick/py/callback/__init__.py +5 -0
- onetick/py/callback/callback.py +276 -0
- onetick/py/callback/callbacks.py +131 -0
- onetick/py/compatibility.py +798 -0
- onetick/py/configuration.py +771 -0
- onetick/py/core/__init__.py +0 -0
- onetick/py/core/_csv_inspector.py +93 -0
- onetick/py/core/_internal/__init__.py +0 -0
- onetick/py/core/_internal/_manually_bound_value.py +6 -0
- onetick/py/core/_internal/_nodes_history.py +250 -0
- onetick/py/core/_internal/_op_utils/__init__.py +0 -0
- onetick/py/core/_internal/_op_utils/every_operand.py +9 -0
- onetick/py/core/_internal/_op_utils/is_const.py +10 -0
- onetick/py/core/_internal/_per_tick_scripts/tick_list_sort_template.script +121 -0
- onetick/py/core/_internal/_proxy_node.py +140 -0
- onetick/py/core/_internal/_state_objects.py +2312 -0
- onetick/py/core/_internal/_state_vars.py +93 -0
- onetick/py/core/_source/__init__.py +0 -0
- onetick/py/core/_source/_symbol_param.py +95 -0
- onetick/py/core/_source/schema.py +97 -0
- onetick/py/core/_source/source_methods/__init__.py +0 -0
- onetick/py/core/_source/source_methods/aggregations.py +809 -0
- onetick/py/core/_source/source_methods/applyers.py +296 -0
- onetick/py/core/_source/source_methods/columns.py +141 -0
- onetick/py/core/_source/source_methods/data_quality.py +301 -0
- onetick/py/core/_source/source_methods/debugs.py +272 -0
- onetick/py/core/_source/source_methods/drops.py +120 -0
- onetick/py/core/_source/source_methods/fields.py +619 -0
- onetick/py/core/_source/source_methods/filters.py +1002 -0
- onetick/py/core/_source/source_methods/joins.py +1413 -0
- onetick/py/core/_source/source_methods/merges.py +605 -0
- onetick/py/core/_source/source_methods/misc.py +1455 -0
- onetick/py/core/_source/source_methods/pandases.py +155 -0
- onetick/py/core/_source/source_methods/renames.py +356 -0
- onetick/py/core/_source/source_methods/sorts.py +183 -0
- onetick/py/core/_source/source_methods/switches.py +142 -0
- onetick/py/core/_source/source_methods/symbols.py +117 -0
- onetick/py/core/_source/source_methods/times.py +627 -0
- onetick/py/core/_source/source_methods/writes.py +986 -0
- onetick/py/core/_source/symbol.py +205 -0
- onetick/py/core/_source/tmp_otq.py +222 -0
- onetick/py/core/column.py +209 -0
- onetick/py/core/column_operations/__init__.py +0 -0
- onetick/py/core/column_operations/_methods/__init__.py +4 -0
- onetick/py/core/column_operations/_methods/_internal.py +28 -0
- onetick/py/core/column_operations/_methods/conversions.py +216 -0
- onetick/py/core/column_operations/_methods/methods.py +292 -0
- onetick/py/core/column_operations/_methods/op_types.py +160 -0
- onetick/py/core/column_operations/accessors/__init__.py +0 -0
- onetick/py/core/column_operations/accessors/_accessor.py +28 -0
- onetick/py/core/column_operations/accessors/decimal_accessor.py +104 -0
- onetick/py/core/column_operations/accessors/dt_accessor.py +537 -0
- onetick/py/core/column_operations/accessors/float_accessor.py +184 -0
- onetick/py/core/column_operations/accessors/str_accessor.py +1367 -0
- onetick/py/core/column_operations/base.py +1121 -0
- onetick/py/core/cut_builder.py +150 -0
- onetick/py/core/db_constants.py +20 -0
- onetick/py/core/eval_query.py +245 -0
- onetick/py/core/lambda_object.py +441 -0
- onetick/py/core/multi_output_source.py +232 -0
- onetick/py/core/per_tick_script.py +2256 -0
- onetick/py/core/query_inspector.py +464 -0
- onetick/py/core/source.py +1744 -0
- onetick/py/db/__init__.py +2 -0
- onetick/py/db/_inspection.py +1128 -0
- onetick/py/db/db.py +1327 -0
- onetick/py/db/utils.py +64 -0
- onetick/py/docs/__init__.py +0 -0
- onetick/py/docs/docstring_parser.py +112 -0
- onetick/py/docs/utils.py +81 -0
- onetick/py/functions.py +2398 -0
- onetick/py/license.py +190 -0
- onetick/py/log.py +88 -0
- onetick/py/math.py +935 -0
- onetick/py/misc.py +470 -0
- onetick/py/oqd/__init__.py +22 -0
- onetick/py/oqd/eps.py +1195 -0
- onetick/py/oqd/sources.py +325 -0
- onetick/py/otq.py +216 -0
- onetick/py/pyomd_mock.py +47 -0
- onetick/py/run.py +916 -0
- onetick/py/servers.py +173 -0
- onetick/py/session.py +1347 -0
- onetick/py/sources/__init__.py +19 -0
- onetick/py/sources/cache.py +167 -0
- onetick/py/sources/common.py +128 -0
- onetick/py/sources/csv.py +642 -0
- onetick/py/sources/custom.py +85 -0
- onetick/py/sources/data_file.py +305 -0
- onetick/py/sources/data_source.py +1045 -0
- onetick/py/sources/empty.py +94 -0
- onetick/py/sources/odbc.py +337 -0
- onetick/py/sources/order_book.py +271 -0
- onetick/py/sources/parquet.py +168 -0
- onetick/py/sources/pit.py +191 -0
- onetick/py/sources/query.py +495 -0
- onetick/py/sources/snapshots.py +419 -0
- onetick/py/sources/split_query_output_by_symbol.py +198 -0
- onetick/py/sources/symbology_mapping.py +123 -0
- onetick/py/sources/symbols.py +374 -0
- onetick/py/sources/ticks.py +825 -0
- onetick/py/sql.py +70 -0
- onetick/py/state.py +251 -0
- onetick/py/types.py +2131 -0
- onetick/py/utils/__init__.py +70 -0
- onetick/py/utils/acl.py +93 -0
- onetick/py/utils/config.py +186 -0
- onetick/py/utils/default.py +49 -0
- onetick/py/utils/file.py +38 -0
- onetick/py/utils/helpers.py +76 -0
- onetick/py/utils/locator.py +94 -0
- onetick/py/utils/perf.py +498 -0
- onetick/py/utils/query.py +49 -0
- onetick/py/utils/render.py +1374 -0
- onetick/py/utils/script.py +244 -0
- onetick/py/utils/temp.py +471 -0
- onetick/py/utils/types.py +120 -0
- onetick/py/utils/tz.py +84 -0
- onetick_py-1.177.0.dist-info/METADATA +137 -0
- onetick_py-1.177.0.dist-info/RECORD +152 -0
- onetick_py-1.177.0.dist-info/WHEEL +5 -0
- onetick_py-1.177.0.dist-info/entry_points.txt +2 -0
- onetick_py-1.177.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
}
|