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,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)