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,441 @@
|
|
|
1
|
+
import types
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
|
|
6
|
+
from ._internal._state_vars import StateVars
|
|
7
|
+
from ._internal._state_objects import _StateBase, check_field_name_in_schema
|
|
8
|
+
from .column import _Column
|
|
9
|
+
from .column_operations.base import _Operation
|
|
10
|
+
from .. import types as ott
|
|
11
|
+
from ._source.symbol import SymbolType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _CompareTrackScope:
|
|
15
|
+
"""
|
|
16
|
+
We need it to prevent problems when some exceptions are risen, and we left non-default tracking settings.
|
|
17
|
+
This scope guarantee that code works always with the same state of tracker.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, emulation_enabled: bool = True):
|
|
20
|
+
self.emulation_enabled = emulation_enabled
|
|
21
|
+
|
|
22
|
+
def __enter__(self):
|
|
23
|
+
_Operation.emulation_enabled = self.emulation_enabled
|
|
24
|
+
_Column.emulation_enabled = self.emulation_enabled
|
|
25
|
+
|
|
26
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
27
|
+
_Operation.emulation_enabled = not self.emulation_enabled
|
|
28
|
+
_Column.emulation_enabled = not self.emulation_enabled
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _EmulateStateVars(StateVars):
|
|
32
|
+
def __init__(self, *args, **kwargs):
|
|
33
|
+
super().__init__(*args, **kwargs)
|
|
34
|
+
_EmulateStateVars.CHANGED_TICK_LISTS = {}
|
|
35
|
+
|
|
36
|
+
def __setitem__(self, key, value):
|
|
37
|
+
if key not in self._columns:
|
|
38
|
+
raise ValueError("Can't declare state variables in per-tick script")
|
|
39
|
+
|
|
40
|
+
if isinstance(value, _Operation):
|
|
41
|
+
value = self._set_var_from_operation(key, value)
|
|
42
|
+
elif isinstance(value, _StateBase):
|
|
43
|
+
value = value.copy(obj_ref=self, name=key)
|
|
44
|
+
|
|
45
|
+
return f'{str(self._columns[key])} = {ott.value2str(value)};'
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _EmulateInputObject:
|
|
49
|
+
|
|
50
|
+
Symbol = SymbolType() # NOSONAR
|
|
51
|
+
|
|
52
|
+
def __init__(self, parent_obj):
|
|
53
|
+
self.__class__._state_vars = _EmulateStateVars(self, parent_obj)
|
|
54
|
+
|
|
55
|
+
for name, dtype in parent_obj.columns().items():
|
|
56
|
+
self.__dict__[name] = _Column(name, dtype, self)
|
|
57
|
+
if name == 'TIMESTAMP':
|
|
58
|
+
self.__dict__['Time'] = _Column(name, dtype, self)
|
|
59
|
+
|
|
60
|
+
# creating class variables, so they will not go to the self.__dict__
|
|
61
|
+
# they will not be used before object initialization anyway
|
|
62
|
+
|
|
63
|
+
# collects new columns with appeared values
|
|
64
|
+
self.__class__.NEW_VALUES = defaultdict(lambda: [])
|
|
65
|
+
self.__class__.LOCAL_VARS_NEW_VALUES = defaultdict(lambda: [])
|
|
66
|
+
# collects local variables in the per-tick-script
|
|
67
|
+
self.__class__.LOCAL_VARS = {}
|
|
68
|
+
self.__class__.STATIC_VARS = {}
|
|
69
|
+
# dictionary with functions used in per-tick script
|
|
70
|
+
self.__class__.FUNCTIONS = {}
|
|
71
|
+
self.__class__.INPUT_SCHEMA = self._get_schema()
|
|
72
|
+
|
|
73
|
+
def _get_schema(self):
|
|
74
|
+
return {
|
|
75
|
+
name: value.dtype
|
|
76
|
+
for name, value in self.__dict__.items()
|
|
77
|
+
if isinstance(value, _Column)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def schema(self):
|
|
82
|
+
return self.INPUT_SCHEMA
|
|
83
|
+
|
|
84
|
+
def __getitem__(self, item):
|
|
85
|
+
if item not in self.__dict__:
|
|
86
|
+
raise NameError(f"Column '{item}' referenced before assignment")
|
|
87
|
+
|
|
88
|
+
return self.__getattr__(item)
|
|
89
|
+
|
|
90
|
+
def __getattr__(self, item):
|
|
91
|
+
if item not in self.__dict__:
|
|
92
|
+
raise AttributeError(f"There is no '{item}' attribute")
|
|
93
|
+
|
|
94
|
+
item = self.__dict__[item]
|
|
95
|
+
|
|
96
|
+
if not isinstance(item, _Column):
|
|
97
|
+
return item
|
|
98
|
+
|
|
99
|
+
dtype = item.dtype
|
|
100
|
+
name = item.name
|
|
101
|
+
if issubclass(dtype, ott.nsectime):
|
|
102
|
+
return self.get_datetime_value(name)
|
|
103
|
+
if issubclass(dtype, int):
|
|
104
|
+
return self.get_long_value(name)
|
|
105
|
+
if issubclass(dtype, float):
|
|
106
|
+
return self.get_double_value(name)
|
|
107
|
+
if issubclass(dtype, str):
|
|
108
|
+
return self.get_string_value(name)
|
|
109
|
+
raise AttributeError(f'{item} has {dtype} type, so it is not possible to return correct get method')
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def get_changed_tick_lists(cls):
|
|
113
|
+
return cls._state_vars.CHANGED_TICK_LISTS
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def state_vars(self):
|
|
117
|
+
return self._state_vars
|
|
118
|
+
|
|
119
|
+
def __str__(self):
|
|
120
|
+
return 'LOCAL::INPUT_TICK'
|
|
121
|
+
|
|
122
|
+
def copy_tick(self, tick_object):
|
|
123
|
+
"""
|
|
124
|
+
Copy fields from ``tick_obj`` to the output tick.
|
|
125
|
+
Will only rewrite fields that are presented in this tick and ``tick_object``,
|
|
126
|
+
will not remove or add any.
|
|
127
|
+
Translated to COPY_TICK() function.
|
|
128
|
+
"""
|
|
129
|
+
return f'COPY_TICK({tick_object});'
|
|
130
|
+
|
|
131
|
+
def get_long_value(self, field_name: Union[str, _Operation]) -> _Operation:
|
|
132
|
+
"""
|
|
133
|
+
Get value of the long ``field_name`` of the tick.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
138
|
+
String field name or operation which returns field name.
|
|
139
|
+
|
|
140
|
+
Examples
|
|
141
|
+
--------
|
|
142
|
+
>>> def fun(tick):
|
|
143
|
+
... tick['TOTAL_INT'] = 0
|
|
144
|
+
... for field in otp.tick_descriptor_fields():
|
|
145
|
+
... if field.get_type() == 'long':
|
|
146
|
+
... tick['TOTAL_INT'] += tick.get_long_value(field.get_name())
|
|
147
|
+
>>> t = otp.Tick(INT_1=3, INT_2=5)
|
|
148
|
+
>>> t = t.script(fun)
|
|
149
|
+
>>> otp.run(t)
|
|
150
|
+
Time INT_1 INT_2 TOTAL_INT
|
|
151
|
+
0 2003-12-01 3 5 8
|
|
152
|
+
"""
|
|
153
|
+
schema = self.schema
|
|
154
|
+
check_field_name_in_schema(field_name, int, schema=schema)
|
|
155
|
+
return _Operation(dtype=int,
|
|
156
|
+
op_str=f'{self}.GET_LONG_VALUE({ott.value2str(field_name)})')
|
|
157
|
+
|
|
158
|
+
def get_double_value(self, field_name: Union[str, _Operation]) -> _Operation:
|
|
159
|
+
"""
|
|
160
|
+
Get value of the double ``field_name`` of the tick.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
165
|
+
String field name or operation which returns field name.
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
>>> def fun(tick):
|
|
170
|
+
... tick['TOTAL_DOUBLE'] = 0.0
|
|
171
|
+
... for field in otp.tick_descriptor_fields():
|
|
172
|
+
... if field.get_type() == 'double':
|
|
173
|
+
... tick['TOTAL_DOUBLE'] += tick.get_double_value(field.get_name())
|
|
174
|
+
>>> t = otp.Tick(DOUBLE_1=3.1, DOUBLE_2=5.2)
|
|
175
|
+
>>> t = t.script(fun)
|
|
176
|
+
>>> otp.run(t)
|
|
177
|
+
Time DOUBLE_1 DOUBLE_2 TOTAL_DOUBLE
|
|
178
|
+
0 2003-12-01 3.1 5.2 8.3
|
|
179
|
+
"""
|
|
180
|
+
check_field_name_in_schema(field_name, float, schema=self.schema)
|
|
181
|
+
return _Operation(dtype=float,
|
|
182
|
+
op_str=f'{self}.GET_DOUBLE_VALUE({ott.value2str(field_name)})')
|
|
183
|
+
|
|
184
|
+
def get_string_value(self, field_name: Union[str, _Operation]) -> _Operation:
|
|
185
|
+
"""
|
|
186
|
+
Get value of the string ``field_name`` of the tick.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
191
|
+
String field name or operation which returns field name.
|
|
192
|
+
|
|
193
|
+
Examples
|
|
194
|
+
--------
|
|
195
|
+
>>> def fun(tick):
|
|
196
|
+
... tick['TOTAL_STR'] = ""
|
|
197
|
+
... for field in otp.tick_descriptor_fields():
|
|
198
|
+
... if field.get_type() == 'string':
|
|
199
|
+
... tick['TOTAL_STR'] += tick.get_string_value(field.get_name())
|
|
200
|
+
>>> t = otp.Tick(STR_1="1", STR_2="2")
|
|
201
|
+
>>> t = t.script(fun)
|
|
202
|
+
>>> otp.run(t)
|
|
203
|
+
Time STR_1 STR_2 TOTAL_STR
|
|
204
|
+
0 2003-12-01 1 2 12
|
|
205
|
+
"""
|
|
206
|
+
check_field_name_in_schema(field_name, str, schema=self.schema)
|
|
207
|
+
return _Operation(dtype=str,
|
|
208
|
+
op_str=f'{self}.GET_STRING_VALUE({ott.value2str(field_name)})')
|
|
209
|
+
|
|
210
|
+
def get_datetime_value(self, field_name: Union[str, _Operation]) -> _Operation:
|
|
211
|
+
"""
|
|
212
|
+
Get value of the datetime ``field_name`` of the tick.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
217
|
+
String field name or operation which returns field name.
|
|
218
|
+
|
|
219
|
+
Examples
|
|
220
|
+
--------
|
|
221
|
+
>>> def fun(tick):
|
|
222
|
+
... for field in otp.tick_descriptor_fields():
|
|
223
|
+
... if field.get_type() == 'nsectime':
|
|
224
|
+
... tick['SOME_DATETIME'] = tick.get_datetime_value(field.get_name())
|
|
225
|
+
>>> t = otp.Tick(DATETIME=otp.datetime(2021, 1, 1))
|
|
226
|
+
>>> t = t.script(fun)
|
|
227
|
+
>>> otp.run(t)
|
|
228
|
+
Time DATETIME SOME_DATETIME
|
|
229
|
+
0 2003-12-01 2021-01-01 2021-01-01
|
|
230
|
+
"""
|
|
231
|
+
check_field_name_in_schema(field_name, ott.nsectime, schema=self.schema)
|
|
232
|
+
return _Operation(dtype=ott.nsectime, # we don't know if it is msectime
|
|
233
|
+
op_str=f'{self}.GET_DATETIME_VALUE({ott.value2str(field_name)})')
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class _EmulateObject(_EmulateInputObject):
|
|
237
|
+
"""
|
|
238
|
+
Instances of this class are proxy to columns of a _Source object,
|
|
239
|
+
and track assignment to construct the correct per-tick-script then
|
|
240
|
+
"""
|
|
241
|
+
def __init__(self, parent_obj):
|
|
242
|
+
super().__init__(parent_obj)
|
|
243
|
+
_EmulateObject.input = _EmulateInputObject(parent_obj)
|
|
244
|
+
|
|
245
|
+
def __getattr__(self, item):
|
|
246
|
+
if item not in self.__dict__:
|
|
247
|
+
raise AttributeError(f"There is no '{item}' attribute")
|
|
248
|
+
|
|
249
|
+
return self.__dict__[item]
|
|
250
|
+
|
|
251
|
+
def __setitem__(self, key, value):
|
|
252
|
+
return self.__setattr__(key, value)
|
|
253
|
+
|
|
254
|
+
def __setattr__(self, key, value):
|
|
255
|
+
if key in self.__class__.__dict__:
|
|
256
|
+
super().__setattr__(key, value)
|
|
257
|
+
if key not in self.__dict__ or key in _EmulateObject.NEW_VALUES:
|
|
258
|
+
_EmulateObject.NEW_VALUES[key].append(value)
|
|
259
|
+
if key not in self.__dict__:
|
|
260
|
+
self.__dict__[key] = _Column(key, ott.get_object_type(value), self)
|
|
261
|
+
|
|
262
|
+
def __str__(self):
|
|
263
|
+
return 'LOCAL::OUTPUT_TICK'
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def schema(self):
|
|
267
|
+
return self._get_schema()
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def get_types_of_new_columns(cls):
|
|
271
|
+
""" method calculates types for all tracked new columns """
|
|
272
|
+
return {
|
|
273
|
+
key: ott.get_type_by_objects(values)
|
|
274
|
+
for key, values in _EmulateObject.NEW_VALUES.items()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
def set_long_value(self, field_name: Union[str, _Operation], value: Union[int, _Operation]) -> str:
|
|
278
|
+
"""
|
|
279
|
+
Set ``value`` of the long ``field_name`` of the tick.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
284
|
+
String field name or operation which returns field name.
|
|
285
|
+
value: int, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
286
|
+
Long value to set or operation which return such value.
|
|
287
|
+
|
|
288
|
+
Examples
|
|
289
|
+
--------
|
|
290
|
+
>>> def fun(tick):
|
|
291
|
+
... for field in otp.tick_descriptor_fields():
|
|
292
|
+
... if field.get_type() == 'long':
|
|
293
|
+
... tick.set_long_value(field.get_name(), 5)
|
|
294
|
+
>>> t = otp.Tick(INT_1=3)
|
|
295
|
+
>>> t = t.script(fun)
|
|
296
|
+
>>> otp.run(t)
|
|
297
|
+
Time INT_1
|
|
298
|
+
0 2003-12-01 5
|
|
299
|
+
"""
|
|
300
|
+
check_field_name_in_schema(field_name, int, value, self.schema)
|
|
301
|
+
return f'{self}.SET_LONG_VALUE({ott.value2str(field_name)}, {ott.value2str(value)});'
|
|
302
|
+
|
|
303
|
+
def set_double_value(self, field_name: Union[str, _Operation], value: Union[float, _Operation]) -> str:
|
|
304
|
+
"""
|
|
305
|
+
Set ``value`` of the double ``field_name`` of the tick.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
310
|
+
String field name or operation which returns field name.
|
|
311
|
+
value: float, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
312
|
+
Double value to set or operation which return such value.
|
|
313
|
+
|
|
314
|
+
Examples
|
|
315
|
+
--------
|
|
316
|
+
>>> def fun(tick):
|
|
317
|
+
... for field in otp.tick_descriptor_fields():
|
|
318
|
+
... if field.get_type() == 'double':
|
|
319
|
+
... tick.set_double_value(field.get_name(), 5.0)
|
|
320
|
+
>>> t = otp.Tick(DOUBLE_1=3.0)
|
|
321
|
+
>>> t = t.script(fun)
|
|
322
|
+
>>> otp.run(t)
|
|
323
|
+
Time DOUBLE_1
|
|
324
|
+
0 2003-12-01 5.0
|
|
325
|
+
"""
|
|
326
|
+
check_field_name_in_schema(field_name, float, value, self.schema)
|
|
327
|
+
return f'{self}.SET_DOUBLE_VALUE({ott.value2str(field_name)}, {ott.value2str(value)});'
|
|
328
|
+
|
|
329
|
+
def set_string_value(self, field_name: Union[str, _Operation], value: Union[str, _Operation]) -> str:
|
|
330
|
+
"""
|
|
331
|
+
Set ``value`` of the string ``field_name`` of the tick.
|
|
332
|
+
|
|
333
|
+
Parameters
|
|
334
|
+
----------
|
|
335
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
336
|
+
String field name or operation which returns field name.
|
|
337
|
+
value: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
338
|
+
String value to set or operation which return such value.
|
|
339
|
+
|
|
340
|
+
Examples
|
|
341
|
+
--------
|
|
342
|
+
>>> def fun(tick):
|
|
343
|
+
... for field in otp.tick_descriptor_fields():
|
|
344
|
+
... if field.get_type() == 'string':
|
|
345
|
+
... tick.set_string_value(field.get_name(), '5')
|
|
346
|
+
>>> t = otp.Tick(STR_1='3')
|
|
347
|
+
>>> t = t.script(fun)
|
|
348
|
+
>>> otp.run(t)
|
|
349
|
+
Time STR_1
|
|
350
|
+
0 2003-12-01 5
|
|
351
|
+
"""
|
|
352
|
+
check_field_name_in_schema(field_name, str, value, self.schema)
|
|
353
|
+
return f'{self}.SET_STRING_VALUE({ott.value2str(field_name)}, {ott.value2str(value)});'
|
|
354
|
+
|
|
355
|
+
def set_datetime_value(self, field_name: Union[str, _Operation], value: Union[int, _Operation]) -> str:
|
|
356
|
+
"""
|
|
357
|
+
Set ``value`` of the datetime ``field_name`` of the tick.
|
|
358
|
+
|
|
359
|
+
Parameters
|
|
360
|
+
----------
|
|
361
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
362
|
+
String field name or operation which returns field name.
|
|
363
|
+
value: int, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
364
|
+
Datetime value to set or operation which return such value.
|
|
365
|
+
|
|
366
|
+
Examples
|
|
367
|
+
--------
|
|
368
|
+
>>> def fun(tick):
|
|
369
|
+
... for field in otp.tick_descriptor_fields():
|
|
370
|
+
... if field.get_type() == 'nsectime':
|
|
371
|
+
... tick.set_datetime_value(field.get_name(), otp.datetime(2021, 1, 1) - otp.Day(1))
|
|
372
|
+
>>> t = otp.Tick(DATETIME_1=otp.datetime(2021, 1, 1))
|
|
373
|
+
>>> t = t.script(fun)
|
|
374
|
+
>>> otp.run(t)
|
|
375
|
+
Time DATETIME_1
|
|
376
|
+
0 2003-12-01 2020-12-31
|
|
377
|
+
"""
|
|
378
|
+
check_field_name_in_schema(field_name, ott.nsectime, value, self.schema)
|
|
379
|
+
return f'{self}.SET_DATETIME_VALUE({ott.value2str(field_name)}, {ott.value2str(value)});'
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _validate_lambda(lambda_f):
|
|
383
|
+
if not (isinstance(lambda_f, types.LambdaType) and lambda_f.__name__ == '<lambda>'
|
|
384
|
+
or isinstance(lambda_f, (types.FunctionType, types.MethodType))):
|
|
385
|
+
raise ValueError("It is expected to get a function, method or lambda,"
|
|
386
|
+
f" but got '{lambda_f}' of type '{type(lambda_f)}'")
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def apply_script(lambda_f, self_ref):
|
|
390
|
+
"""
|
|
391
|
+
In this function we parse python syntax tree for `lambda_f`
|
|
392
|
+
and converting it to OneTick's per-tick script.
|
|
393
|
+
This function returns _Column like function, that
|
|
394
|
+
keeps resulting built expression.
|
|
395
|
+
"""
|
|
396
|
+
_validate_lambda(lambda_f)
|
|
397
|
+
|
|
398
|
+
with _CompareTrackScope():
|
|
399
|
+
# TODO: remove circular imports
|
|
400
|
+
from .per_tick_script import FunctionParser
|
|
401
|
+
_script = FunctionParser(lambda_f, emulator=self_ref).per_tick_script()
|
|
402
|
+
new_columns_types = _EmulateObject.get_types_of_new_columns()
|
|
403
|
+
return new_columns_types, _script
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def apply_lambda(lambda_f, self_ref):
|
|
407
|
+
"""
|
|
408
|
+
In this function we parse python syntax tree for `lambda_f`
|
|
409
|
+
and converting it to OneTick's CASE function().
|
|
410
|
+
This function returns _Column like function, that
|
|
411
|
+
keeps resulting built expression.
|
|
412
|
+
"""
|
|
413
|
+
_validate_lambda(lambda_f)
|
|
414
|
+
|
|
415
|
+
with _CompareTrackScope():
|
|
416
|
+
# TODO: remove circular imports
|
|
417
|
+
from .per_tick_script import FunctionParser
|
|
418
|
+
res, values = FunctionParser(lambda_f, emulator=self_ref).case()
|
|
419
|
+
return _LambdaIfElse(res, ott.get_type_by_objects(values))
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class _LambdaIfElse(_Column):
|
|
423
|
+
"""
|
|
424
|
+
Behaves like a column and consists information built from the lambda calculation
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
def __init__(self, repr, dtype):
|
|
428
|
+
self._dtype = dtype
|
|
429
|
+
self._repr = repr
|
|
430
|
+
|
|
431
|
+
def __str__(self):
|
|
432
|
+
return self._repr
|
|
433
|
+
|
|
434
|
+
def __len__(self):
|
|
435
|
+
if issubclass(self.dtype, str):
|
|
436
|
+
if self.dtype is str:
|
|
437
|
+
return ott.string.DEFAULT_LENGTH
|
|
438
|
+
else:
|
|
439
|
+
return self.dtype.length
|
|
440
|
+
|
|
441
|
+
raise TypeError(f"It is not allowed to call len() method for object of type '{self.dtype}'")
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
from onetick import py as otp
|
|
2
|
+
from onetick.py import configuration
|
|
3
|
+
from onetick.py.otq import otq
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MultiOutputSource:
|
|
7
|
+
"""
|
|
8
|
+
Construct a source object with multiple outputs
|
|
9
|
+
from several connected :py:class:`~onetick.py.Source` objects.
|
|
10
|
+
|
|
11
|
+
This object can be saved to disk as a graph using :py:meth:`~onetick.py.Source.to_otq` method,
|
|
12
|
+
or passed to :py:func:`onetick.py.run` function.
|
|
13
|
+
|
|
14
|
+
If it's passed to :py:func:`onetick.py.run`,
|
|
15
|
+
then returned results for different outputs will be available as a dictionary.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
outputs : dict
|
|
20
|
+
Dictionary which keys are names of the output sources, and values are output sources themselves.
|
|
21
|
+
All the passed sources should be connected.
|
|
22
|
+
|
|
23
|
+
Examples
|
|
24
|
+
--------
|
|
25
|
+
|
|
26
|
+
Results for individual outputs can be accessed by output names
|
|
27
|
+
|
|
28
|
+
>>> # OTdirective: skip-snippet:;
|
|
29
|
+
>>> root = otp.Tick(A=1)
|
|
30
|
+
>>> branch_1 = root.copy()
|
|
31
|
+
>>> branch_2 = root.copy()
|
|
32
|
+
>>> branch_3 = root.copy()
|
|
33
|
+
>>> branch_1['B'] = 1
|
|
34
|
+
>>> branch_2['B'] = 2
|
|
35
|
+
>>> branch_3['B'] = 3
|
|
36
|
+
>>> src = otp.MultiOutputSource(dict(BRANCH1=branch_1, BRANCH2=branch_2, BRANCH3=branch_3))
|
|
37
|
+
>>> res = otp.run(src)
|
|
38
|
+
>>> sorted(list(res.keys()))
|
|
39
|
+
['BRANCH1', 'BRANCH2', 'BRANCH3']
|
|
40
|
+
>>> # OTdirective: skip-snippet:;
|
|
41
|
+
>>> res['BRANCH1'][['A', 'B']]
|
|
42
|
+
A B
|
|
43
|
+
0 1 1
|
|
44
|
+
>>> # OTdirective: skip-snippet:;
|
|
45
|
+
>>> res['BRANCH2'][['A', 'B']]
|
|
46
|
+
A B
|
|
47
|
+
0 1 2
|
|
48
|
+
>>> # OTdirective: skip-snippet:;
|
|
49
|
+
>>> res['BRANCH3'][['A', 'B']]
|
|
50
|
+
A B
|
|
51
|
+
0 1 3
|
|
52
|
+
|
|
53
|
+
node_name parameter of the otp.run() method can be used to select outputs
|
|
54
|
+
|
|
55
|
+
>>> # OTdirective: skip-snippet:;
|
|
56
|
+
>>> src = otp.MultiOutputSource(dict(BRANCH1=branch_1, BRANCH2=branch_2, BRANCH3=branch_3))
|
|
57
|
+
>>> res = otp.run(src, node_name=['BRANCH2', 'BRANCH3'])
|
|
58
|
+
>>> sorted(list(res.keys()))
|
|
59
|
+
['BRANCH2', 'BRANCH3']
|
|
60
|
+
>>> # OTdirective: skip-snippet:;
|
|
61
|
+
>>> res['BRANCH2'][['A', 'B']]
|
|
62
|
+
A B
|
|
63
|
+
0 1 2
|
|
64
|
+
>>> # OTdirective: skip-snippet:;
|
|
65
|
+
>>> res['BRANCH3'][['A', 'B']]
|
|
66
|
+
A B
|
|
67
|
+
0 1 3
|
|
68
|
+
|
|
69
|
+
If only one output is selected, then it's returned directly and not in a dictionary
|
|
70
|
+
|
|
71
|
+
>>> # OTdirective: skip-snippet:;
|
|
72
|
+
>>> src = otp.MultiOutputSource(dict(BRANCH1=branch_1, BRANCH2=branch_2, BRANCH3=branch_3))
|
|
73
|
+
>>> res = otp.run(src, node_name='BRANCH2')
|
|
74
|
+
>>> res[['A', 'B']]
|
|
75
|
+
A B
|
|
76
|
+
0 1 2
|
|
77
|
+
|
|
78
|
+
A dictionary with sources can also be passed to otp.run directly,
|
|
79
|
+
and MultiOutputSource object will be constructed internally
|
|
80
|
+
|
|
81
|
+
>>> res = otp.run(dict(BRANCH1=branch_1, BRANCH2=branch_2))
|
|
82
|
+
>>> # OTdirective: skip-snippet:;
|
|
83
|
+
>>> res['BRANCH1'][['A', 'B']]
|
|
84
|
+
A B
|
|
85
|
+
0 1 1
|
|
86
|
+
>>> # OTdirective: skip-snippet:;
|
|
87
|
+
>>> res['BRANCH2'][['A', 'B']]
|
|
88
|
+
A B
|
|
89
|
+
0 1 2
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, outputs, main_branch_name=None):
|
|
93
|
+
|
|
94
|
+
# 1. Checking that outputs have a common part:
|
|
95
|
+
# we create a set of keys for all outputs and see if all sets are connected;
|
|
96
|
+
# two sets are connected if they have any key in common
|
|
97
|
+
|
|
98
|
+
if len(outputs) < 1:
|
|
99
|
+
raise ValueError('At least one branch should be passed to a MultiOutputSource object')
|
|
100
|
+
|
|
101
|
+
def get_history_key_set(hist):
|
|
102
|
+
keys = set()
|
|
103
|
+
for rule in hist._rules:
|
|
104
|
+
if "key" in rule.key_params:
|
|
105
|
+
keys.add(rule.key)
|
|
106
|
+
return keys
|
|
107
|
+
|
|
108
|
+
source_key_sets = []
|
|
109
|
+
for source in outputs.values():
|
|
110
|
+
source_key_sets.append(get_history_key_set(source.node()._hist))
|
|
111
|
+
|
|
112
|
+
while len(source_key_sets) > 1:
|
|
113
|
+
# we take first set from the list and add to it all the other sets that have common keys with it
|
|
114
|
+
# we continue to do this until first set is the only set in the list or until it has no common keys
|
|
115
|
+
# with other sets in the list
|
|
116
|
+
new_key_sets = []
|
|
117
|
+
first_key_set = source_key_sets[0]
|
|
118
|
+
new_key_sets.append(first_key_set)
|
|
119
|
+
for s in source_key_sets[1:]:
|
|
120
|
+
if first_key_set.isdisjoint(s):
|
|
121
|
+
# no common keys
|
|
122
|
+
new_key_sets.append(s)
|
|
123
|
+
else:
|
|
124
|
+
# there are common keys
|
|
125
|
+
first_key_set = first_key_set | s
|
|
126
|
+
# checking if first_key_set had common keys with at least some other set
|
|
127
|
+
if len(source_key_sets) == len(new_key_sets):
|
|
128
|
+
raise ValueError("Cannot construct a MultiOutputSource object from outputs that are not connected!")
|
|
129
|
+
# moving first_key_set to the end; maybe it will make things work faster
|
|
130
|
+
new_key_sets = new_key_sets[1:] + [first_key_set]
|
|
131
|
+
source_key_sets = new_key_sets
|
|
132
|
+
|
|
133
|
+
# 2, 3. Assigning node names and selecting main branch
|
|
134
|
+
self.__main_branch_name = None
|
|
135
|
+
self.__main_branch = None
|
|
136
|
+
self.__side_branches = {}
|
|
137
|
+
for node_name, source in outputs.items():
|
|
138
|
+
source = source.copy()
|
|
139
|
+
# this is necessary to create different branches if a source is a branching point
|
|
140
|
+
source.sink(otq.Passthrough())
|
|
141
|
+
source.node().node_name(node_name)
|
|
142
|
+
if self.__main_branch_name is None and (main_branch_name is None or main_branch_name == node_name):
|
|
143
|
+
self.__main_branch_name = node_name
|
|
144
|
+
self.__main_branch = source
|
|
145
|
+
else:
|
|
146
|
+
self.__side_branches[node_name] = source
|
|
147
|
+
if self.__main_branch_name is None:
|
|
148
|
+
raise ValueError(f'Branch name "{main_branch_name}" not found among passed outputs!')
|
|
149
|
+
|
|
150
|
+
# 4, 5. Apply other branches to the main branch and copy dicts
|
|
151
|
+
self.__main_branch._apply_side_branches(self.__side_branches.values())
|
|
152
|
+
|
|
153
|
+
def _all_node_names(self):
|
|
154
|
+
return [self.__main_branch_name] + list(self.__side_branches.keys())
|
|
155
|
+
|
|
156
|
+
def _side_branch_list(self):
|
|
157
|
+
return list(self.__side_branches.values())
|
|
158
|
+
|
|
159
|
+
def get_branch(self, branch_name: str) -> otp.Source:
|
|
160
|
+
"""
|
|
161
|
+
Retrieve a branch by its name.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
branch_name : str
|
|
166
|
+
The name of the branch to retrieve.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
otp.Source
|
|
171
|
+
The branch corresponding to the given name.
|
|
172
|
+
"""
|
|
173
|
+
if branch_name == self.__main_branch_name:
|
|
174
|
+
return self.__main_branch
|
|
175
|
+
|
|
176
|
+
branch = self.__side_branches.get(branch_name)
|
|
177
|
+
if branch is None:
|
|
178
|
+
raise ValueError(f'Branch name "{branch_name}" not found among the outputs!')
|
|
179
|
+
|
|
180
|
+
return branch
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def main_branch(self) -> otp.Source:
|
|
184
|
+
"""
|
|
185
|
+
Get the main branch.
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
otp.Source
|
|
190
|
+
The main branch.
|
|
191
|
+
"""
|
|
192
|
+
return self.__main_branch
|
|
193
|
+
|
|
194
|
+
def _prepare_for_execution(self, symbols=None, start=None, end=None, start_time_expression=None,
|
|
195
|
+
end_time_expression=None, timezone=None,
|
|
196
|
+
has_output=None, # NOSONAR
|
|
197
|
+
running_query_flag=None, require_dict=False, node_name=None,
|
|
198
|
+
symbol_date=None):
|
|
199
|
+
|
|
200
|
+
has_output = False # to avoid sinking PASSTHROUGH to the main branch
|
|
201
|
+
if node_name is None: # if user passed a node name, we shouldn't overwrite it
|
|
202
|
+
node_name = self._all_node_names()
|
|
203
|
+
return self.__main_branch._prepare_for_execution(
|
|
204
|
+
symbols=symbols, start=start, end=end, start_time_expression=start_time_expression,
|
|
205
|
+
end_time_expression=end_time_expression, timezone=timezone, has_output=has_output,
|
|
206
|
+
running_query_flag=running_query_flag, require_dict=require_dict,
|
|
207
|
+
node_name=node_name,
|
|
208
|
+
symbol_date=symbol_date,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def to_otq(self, file_name=None, file_suffix=None, query_name=None, symbols=None, start=None, end=None,
|
|
212
|
+
timezone=None):
|
|
213
|
+
"""
|
|
214
|
+
Constructs an onetick query graph and saves it to disk
|
|
215
|
+
|
|
216
|
+
See Also
|
|
217
|
+
--------
|
|
218
|
+
:py:meth:`onetick.py.Source.to_otq`
|
|
219
|
+
"""
|
|
220
|
+
if timezone is None:
|
|
221
|
+
timezone = configuration.config.tz
|
|
222
|
+
return self.__main_branch.to_otq(file_name=file_name,
|
|
223
|
+
file_suffix=file_suffix,
|
|
224
|
+
query_name=query_name,
|
|
225
|
+
symbols=symbols,
|
|
226
|
+
start=start,
|
|
227
|
+
end=end,
|
|
228
|
+
timezone=timezone,
|
|
229
|
+
add_passthrough=False)
|
|
230
|
+
|
|
231
|
+
def _store_in_tmp_otq(self, *args, **kwargs):
|
|
232
|
+
return self.main_branch._store_in_tmp_otq(*args, **kwargs)
|