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,619 @@
1
+ from typing import TYPE_CHECKING, Optional, Type
2
+
3
+ import numpy as np
4
+
5
+ from onetick import py as otp
6
+ from onetick.py import types as ott
7
+ from onetick.py.compatibility import is_existing_fields_handling_supported
8
+ from onetick.py.core._internal._state_objects import _StateColumn
9
+ from onetick.py.core.column import _Column, _ColumnAggregation, _LagOperator
10
+ from onetick.py.core.column_operations._methods.methods import is_arithmetical, is_compare
11
+ from onetick.py.core.column_operations.base import _Operation
12
+ from onetick.py.core.cut_builder import _BaseCutBuilder
13
+ from onetick.py.core.lambda_object import _LambdaIfElse
14
+ from onetick.py.otq import otq
15
+
16
+ from .misc import inplace_operation
17
+
18
+ if TYPE_CHECKING:
19
+ from onetick.py.core.source import Source
20
+
21
+
22
+ @inplace_operation
23
+ def table(self: 'Source', inplace=False, strict: bool = True, **schema) -> Optional['Source']:
24
+ """
25
+ Set the OneTick and python schemas levels according to the ``schema``
26
+ parameter. The ``schema`` should contain either (field_name -> type) pairs
27
+ or (field_name -> default value) pairs; ``None`` means no specified type, and
28
+ OneTick considers it's as a double type.
29
+
30
+ Resulting ticks have the same order as in the ``schema``. If only partial fields
31
+ are specified (i.e. when the ``strict=False``) then fields from the ``schema`` have
32
+ the most left position.
33
+
34
+ Parameters
35
+ ----------
36
+ inplace: bool
37
+ The flag controls whether operations should be applied inplace
38
+ strict: bool
39
+ If set to ``False``, all fields present in an input tick will be present in the output tick.
40
+ If ``True``, then only fields specified in the ``schema``.
41
+ schema:
42
+ field_name -> type or field_name -> default value pairs that should be applied on the source.
43
+
44
+ Returns
45
+ -------
46
+ :class:`Source` or ``None``
47
+
48
+ See Also
49
+ --------
50
+ | :attr:`Source.schema`
51
+ | :meth:`__getitem__`: the table shortcut
52
+ | **TABLE** OneTick event processor
53
+
54
+ Examples
55
+ --------
56
+
57
+ Selection case
58
+
59
+ >>> data = otp.Ticks(X1=[1, 2, 3],
60
+ ... X2=[3, 2, 1],
61
+ ... A1=["A", "A", "A"])
62
+ >>> data = data.table(X2=int, A1=str) # OTdirective: snippet-name: Arrange.set schema;
63
+ >>> otp.run(data)
64
+ Time X2 A1
65
+ 0 2003-12-01 00:00:00.000 3 A
66
+ 1 2003-12-01 00:00:00.001 2 A
67
+ 2 2003-12-01 00:00:00.002 1 A
68
+
69
+ Defining default values case (note the order)
70
+
71
+ >>> data = otp.Ticks(X=[1, 2, 3])
72
+ >>> data = data.table(Y=0.5, strict=False)
73
+ >>> otp.run(data)
74
+ Time Y X
75
+ 0 2003-12-01 00:00:00.000 0.5 1
76
+ 1 2003-12-01 00:00:00.001 0.5 2
77
+ 2 2003-12-01 00:00:00.002 0.5 3
78
+ """
79
+
80
+ def is_time_type_or_nsectime(obj):
81
+ return ott.is_time_type(obj) or isinstance(obj, ott.nsectime)
82
+
83
+ def transformer(name, obj):
84
+ if obj is None:
85
+ return name
86
+
87
+ res = f'{ott.type2str(ott.get_object_type(obj))} {name}'
88
+
89
+ if isinstance(obj, ott._inner_string):
90
+ res = f'{name} {ott.type2str(ott.get_object_type(obj))}'
91
+ if not isinstance(obj, Type) and not is_time_type_or_nsectime(obj):
92
+ res += f' ({ott.value2str(obj)})'
93
+ return res
94
+
95
+ def get_type(value):
96
+ if value is None:
97
+ return float
98
+ return ott.get_object_type(value)
99
+
100
+ for c_name in list(schema.keys()):
101
+ if self._check_key_is_meta(c_name):
102
+ # meta fields should not be propagated to Table ep
103
+ # otherwise new user-defined field with the same name will appear in schema
104
+ # and using this field will raise an "ambiguous use" error in OneTick
105
+ raise ValueError(f"Can't set meta field {c_name}")
106
+
107
+ if not schema:
108
+ return self
109
+
110
+ schema_to_set = {c_name: get_type(c_value) for c_name, c_value in schema.items()}
111
+
112
+ if strict:
113
+ self.schema.set(**schema_to_set)
114
+ else:
115
+ self.schema.update(**schema_to_set)
116
+
117
+ fields = ','.join([transformer(c_name, c_value) for c_name, c_value in schema.items()])
118
+
119
+ self.sink(otq.Table(fields=fields, keep_input_fields=not strict))
120
+ for c_name, c_value in schema.items():
121
+ # datetime and nsetime values require onetick built-in functions to be initialized
122
+ # but built-in functions can't be used in table ep so updating columns after the table
123
+ if is_time_type_or_nsectime(c_value):
124
+ self.update({c_name: c_value}, where=self[c_name] == 0, inplace=True)
125
+ self._fix_varstrings()
126
+ return self
127
+
128
+
129
+ def __add_field_parse_value(value):
130
+ if isinstance(value, tuple):
131
+ value, dtype = value
132
+ else:
133
+ dtype = ott.get_object_type(value)
134
+
135
+ # pylint: disable-next=unidiomatic-typecheck
136
+ if type(value) is str and len(value) > ott.string.DEFAULT_LENGTH:
137
+ dtype = ott.string[len(value)]
138
+
139
+ if issubclass(dtype, bool):
140
+ # according to OneTick transformations
141
+ dtype = float
142
+
143
+ if issubclass(dtype, (ott.datetime, ott.date)):
144
+ # according to OneTick transformations
145
+ dtype = ott.nsectime
146
+
147
+ # TODO: shouldn't all such logic be in ott.type2str?
148
+ if np.issubdtype(dtype, np.integer):
149
+ dtype = int
150
+ if np.issubdtype(dtype, np.floating):
151
+ dtype = float
152
+
153
+ return dtype, value
154
+
155
+
156
+ def _add_field(self: 'Source', key, value):
157
+ if isinstance(value, _ColumnAggregation):
158
+ value.apply(self, key)
159
+ return
160
+
161
+ dtype, value = __add_field_parse_value(value)
162
+ type_str = ott.type2str(dtype)
163
+ str_value = ott.value2str(value)
164
+
165
+ self.sink(otq.AddField(field=f'{type_str} {key}', value=str_value))
166
+
167
+ self.__dict__[key] = _Column(key, dtype, self)
168
+
169
+
170
+ def _update_timestamp(self: 'Source', key, value, str_value):
171
+ if not hasattr(value, "dtype"):
172
+ # A constant value: no need to pre- or post-sort
173
+ self.sink(otq.UpdateField(field=key, value=str_value))
174
+ elif (
175
+ isinstance(value, _Column)
176
+ and not isinstance(value, _StateColumn)
177
+ and hasattr(value, "name")
178
+ and value.name in self.__dict__
179
+ ):
180
+ # An existing, present column: no need to create a temporary one. See PY-253
181
+ need_to_sort = str_value not in ("_START_TIME", "_END_TIME")
182
+ if need_to_sort:
183
+ self.sort(value, inplace=True)
184
+ self.sink(otq.UpdateField(field=key, value=str_value))
185
+ if need_to_sort:
186
+ self.sort(self['Time'], inplace=True)
187
+ elif not is_compare(value) and isinstance(value, (_Operation, _LambdaIfElse)) or is_arithmetical(value):
188
+ # An expression or a statevar: create a temp column with its value, pre- and post- sort.
189
+ self.sink(otq.AddField(field="__TEMP_TIMESTAMP__", value=str_value))
190
+ self.sink(otq.OrderByEp(order_by="__TEMP_TIMESTAMP__ ASC"))
191
+ self.sink(otq.UpdateField(field=key, value="__TEMP_TIMESTAMP__"))
192
+ self.sink(otq.Passthrough(fields="__TEMP_TIMESTAMP__", drop_fields=True))
193
+ self.sort(self['Time'], inplace=True)
194
+ else:
195
+ raise TypeError(f"Illegal type for timestamp assignment: {value.__class__}")
196
+
197
+
198
+ def _replace_positive_lag_operator_with_tmp_column(value):
199
+ """
200
+ Positive lag operator can't be used in UPDATE_FIELD EP,
201
+ so we are replacing it with temporary column.
202
+ """
203
+ if not isinstance(value, _Operation):
204
+ return value, None
205
+
206
+ def fun(operation):
207
+ if isinstance(operation, _LagOperator) and operation.index > 0:
208
+ column = operation._op_params[0]
209
+ name = column.name
210
+ if name.startswith("__"):
211
+ raise ValueError(
212
+ "Column name started with two underscores should be used by system only, "
213
+ "please do not use such names."
214
+ )
215
+ name = f"__{name}_{operation.index}_NEW__"
216
+ return _Column(name, column.dtype, column.obj_ref, precision=getattr(column, "_precision", None))
217
+ return None
218
+
219
+ op, replace_tuples = value._replace_parameters(fun, return_replace_tuples=True)
220
+ return op, {str(new): old for old, new in replace_tuples}
221
+
222
+
223
+ def _update_field(self: 'Source', field, value):
224
+
225
+ if isinstance(value, _ColumnAggregation):
226
+ value.apply(self, str(field))
227
+ return
228
+
229
+ value, names_mapping = _replace_positive_lag_operator_with_tmp_column(value)
230
+ if names_mapping:
231
+ self.add_fields(names_mapping, inplace=True)
232
+
233
+ if isinstance(value, tuple):
234
+ # support to be compatible with adding fields to get rid of some strange problems
235
+ # but really we do not use passed type, because update field does not support it
236
+ value, _ = value
237
+
238
+ convert_to_type = None
239
+ str_value = ott.value2str(value)
240
+ value_dtype = ott.get_object_type(value)
241
+ base_type = ott.get_base_type(value_dtype)
242
+
243
+ if base_type is bool:
244
+ # according OneTick
245
+ base_type = float
246
+
247
+ type_changes = False # because mantis 0021194
248
+ if base_type is str:
249
+ # update_field non-string field to string field (of any length) or value
250
+ # changes type to default string
251
+ if not issubclass(field.dtype, str):
252
+ field._dtype = str
253
+ type_changes = True
254
+
255
+ else:
256
+ if (
257
+ (issubclass(field.dtype, int) or issubclass(field.dtype, float) or issubclass(field.dtype, str))
258
+ and (issubclass(value_dtype, ott.msectime) or issubclass(value_dtype, ott.nsectime))
259
+ and (str(field) != 'TIMESTAMP')
260
+ and (not isinstance(field, _StateColumn))
261
+ ):
262
+ # in OneTick after updating fields with functions that return datetime values
263
+ # the type of column will not change for long and double columns
264
+ # and will change to long (or double in older versions) when updating string column
265
+ # (see BDS-267)
266
+ # That's why we are explicitly setting type for returned value
267
+ convert_to_type = value_dtype
268
+ if issubclass(field.dtype, str):
269
+ # using update_field only for string because update_fields preserves type
270
+ # by default and raises exception if it can't be done
271
+ type_changes = True
272
+ elif issubclass(field.dtype, float) and base_type is int and not isinstance(field, _StateColumn):
273
+ # PY-574 if field was float and int, then
274
+ # it is still float in onetick, so no need to change type on otp level
275
+ convert_to_type = int
276
+ elif (issubclass(field.dtype, ott.msectime) or issubclass(field.dtype, ott.nsectime)) and base_type is int:
277
+ # if field was time type and we add something to it, then
278
+ # no need to change type
279
+ pass
280
+ elif issubclass(field.dtype, str) and base_type is int:
281
+ type_changes = True
282
+ convert_to_type = int
283
+ else:
284
+ if issubclass(value_dtype, bool):
285
+ value_dtype = float
286
+
287
+ if isinstance(field, _StateColumn):
288
+ pass # do nothing
289
+ else:
290
+ field._dtype = value_dtype
291
+ type_changes = True
292
+
293
+ # for aliases, TIMESTAMP ~ Time as an example
294
+ key = str(field)
295
+
296
+ if key == "TIMESTAMP":
297
+ self._update_timestamp(key, value, str_value)
298
+
299
+ elif type_changes:
300
+ if value_dtype in [otp.nsectime, int] and issubclass(field.dtype, str):
301
+ # PY-416 string field changing the type from str to datetime leads to losing nanoseconds
302
+ # BE-142 similar issue with int from string: OneTick convert str to float, and then to int
303
+ # so we lose some precision for big integers
304
+ # work around is: make a new column first, delete accessor column
305
+ # and then recreate it with value from temp column
306
+ self.sink(otq.AddField(field=f"_TMP_{key}", value=str_value))
307
+ self.sink(otq.Passthrough(fields=key, drop_fields=True))
308
+ self.sink(otq.AddField(field=f"{key}", value=f"_TMP_{key}"))
309
+ self.sink(otq.Passthrough(fields=f"_TMP_{key}", drop_fields=True))
310
+ else:
311
+ self.sink(otq.UpdateField(field=key, value=str_value))
312
+ else:
313
+ self.sink(otq.UpdateFields(set=key + "=" + str_value))
314
+ if names_mapping:
315
+ self.drop(list(names_mapping), inplace=True)
316
+ if convert_to_type:
317
+ # manual type conversion after update fields for some cases
318
+ self.table(**{key: convert_to_type}, inplace=True, strict=False)
319
+
320
+
321
+ def _validate_before_setting(key, value):
322
+ if key in ["Symbol", "_SYMBOL_NAME"]:
323
+ raise ValueError("Symbol setting is supported during creation only")
324
+ if key == "_state_vars":
325
+ raise ValueError("state field is necessary for keeping state variables and can't be rewritten")
326
+ if isinstance(value, ott.ExpressionDefinedTimeOffset):
327
+ value = value.n
328
+ if isinstance(value, np.generic):
329
+ value = value.item()
330
+ if not (
331
+ ott.is_type_supported(ott.get_object_type(value))
332
+ or isinstance(value, (_Operation, tuple, _ColumnAggregation))
333
+ ):
334
+ raise TypeError(f'It is not allowed to set objects of "{type(value)}" type')
335
+ return value
336
+
337
+
338
+ def __setattr__(self: 'Source', key, value):
339
+ if self._check_key_in_properties(key):
340
+ self.__dict__[key] = value
341
+ return
342
+
343
+ # we only allow TIMESTAMP field to be changed
344
+ if self._check_key_is_meta(key) and key not in {'Time', 'TIMESTAMP'}:
345
+ raise ValueError(f"Can't set meta field {key}")
346
+
347
+ if isinstance(value, _BaseCutBuilder):
348
+ value(key)
349
+ return
350
+
351
+ value = _validate_before_setting(key, value)
352
+ if key in self.__dict__:
353
+ field = self.__dict__[key]
354
+ if issubclass(type(field), _Column):
355
+ self._update_field(field, value)
356
+ else:
357
+ raise AttributeError(f'Column "{key}" not found')
358
+ else:
359
+ assert not (
360
+ isinstance(value, _StateColumn) and value.obj_ref is None
361
+ ), "State variables should be in `state` field"
362
+ self._add_field(key, value)
363
+
364
+
365
+ def __setitem__(self: 'Source', key, value):
366
+ """
367
+ Add new column to the source or update existing one.
368
+
369
+ Parameters
370
+ ----------
371
+ key: str
372
+ The name of the new or existing column.
373
+ value: int, str, float, :py:class:`datetime.datetime`, :py:class:`datetime.date`, \
374
+ :py:class:`~onetick.py.Column`, :py:class:`~onetick.py.Operation`, :py:class:`~onetick.py.string`, \
375
+ :py:class:`otp.date <onetick.py.date>`, :py:class:`otp.datetime <onetick.py.datetime>`, \
376
+ :py:class:`~onetick.py.nsectime`, :py:class:`~onetick.py.msectime`
377
+ The new value of the column.
378
+
379
+ See also
380
+ --------
381
+ | **ADD_FIELD** OneTick event processor
382
+ | **UPDATE_FIELD** OneTick event processor
383
+
384
+ Examples
385
+ --------
386
+ >>> data = otp.Tick(A='A')
387
+ >>> data['D'] = otp.datetime(2022, 2, 2)
388
+ >>> data['X'] = 1
389
+ >>> data['Y'] = data['X']
390
+ >>> data['X'] = 12345
391
+ >>> data['Z'] = data['Y'].astype(str) + 'abc'
392
+ >>> otp.run(data)
393
+ Time A D X Y Z
394
+ 0 2003-12-01 A 2022-02-02 12345 1 1abc
395
+ """
396
+
397
+ return self.__setattr__(key, value)
398
+
399
+
400
+ @inplace_operation
401
+ def add_fields(self: 'Source', fields: dict, override: bool = False, inplace=False):
402
+ """
403
+ Add new columns to the source.
404
+
405
+ Parameters
406
+ ----------
407
+ fields: dict
408
+ The dictionary of the names of the new fields and their values.
409
+ The types of supported values in the dictionary are the same as in :meth:`Source.__setitem__`.
410
+ override: bool
411
+ If *False* then exception will be raised for existing fields.
412
+ If *True* then exception will not be raised and the field will be overridden.
413
+ inplace: bool
414
+ A flag controls whether operation should be applied inplace.
415
+ If ``inplace=True``, then it returns nothing. Otherwise method
416
+ returns a new modified object.
417
+
418
+ Returns
419
+ -------
420
+ :class:`Source` or ``None``.
421
+
422
+ See also
423
+ --------
424
+ | :meth:`Source.__setitem__`
425
+ | **ADD_FIELDS** OneTick event processor
426
+
427
+ Examples
428
+ --------
429
+
430
+ Add new fields specified in the dictionary:
431
+
432
+ >>> data = otp.Tick(A=1)
433
+ >>> data = data.add_fields({
434
+ ... 'D': otp.datetime(2022, 2, 2),
435
+ ... 'X': 12345,
436
+ ... 'Y': data['A'],
437
+ ... 'Z': data['A'].astype(str) + 'abc',
438
+ ... })
439
+ >>> otp.run(data)
440
+ Time A D X Y Z
441
+ 0 2003-12-01 1 2022-02-02 12345 1 1abc
442
+
443
+ Parameter ``override`` can be used to rewrite existing fields:
444
+
445
+ .. testcode::
446
+ :skipif: not is_existing_fields_handling_supported()
447
+
448
+ data = otp.Tick(A=1)
449
+ data = data.add_fields({'A': 2, 'B': 'b'}, override=True)
450
+ df = otp.run(data)
451
+ print(df)
452
+
453
+ .. testoutput::
454
+
455
+ Time A B
456
+ 0 2003-12-01 2 b
457
+ """
458
+ fields_parsed = {}
459
+
460
+ kwargs = {}
461
+ if override:
462
+ if not is_existing_fields_handling_supported():
463
+ raise ValueError("Parameter 'override' is not supported on this OneTick build")
464
+ kwargs['existing_fields_handling'] = 'OVERRIDE'
465
+
466
+ for key, value in fields.items():
467
+ if self._check_key_in_properties(key):
468
+ raise ValueError("Class properties can't be set with add_fields method")
469
+ if self._check_key_is_meta(key):
470
+ raise ValueError(f"Can't set meta field {key}")
471
+ if not override and key in self.schema:
472
+ raise ValueError(f"Field '{key}' is already in schema")
473
+
474
+ # TODO: _validate_before_setting and __add_field_parse_value both modify value, need to refactor
475
+ value = _validate_before_setting(key, value)
476
+ dtype, value = __add_field_parse_value(value)
477
+ fields_parsed[key] = (_Column(key, dtype, self), f'{ott.type2str(dtype)} {key}={ott.value2str(value)}')
478
+
479
+ fields_str = ','.join(field_str for _, field_str in fields_parsed.values())
480
+ self.sink(otq.AddFields(fields=fields_str, **kwargs))
481
+
482
+ for key, (column, _) in fields_parsed.items():
483
+ self.__dict__[key] = column
484
+
485
+ return self
486
+
487
+
488
+ @inplace_operation
489
+ def update(self: 'Source', if_set, else_set=None, where=1, inplace=False) -> 'Source':
490
+ """
491
+ Update field of the Source or state variable.
492
+
493
+ Parameters
494
+ ----------
495
+ if_set: dict
496
+ Dictionary <field name>: <expression>.
497
+ else_set: dict, optional
498
+ Dictionary <field name>: <expression>
499
+ where: expression, optional
500
+ Condition of updating.
501
+
502
+ If ``where`` is True the fields from ``if_set`` will be updated with corresponding expression.
503
+
504
+ If ``where`` is False, the fields from ``else_set`` will be updated with corresponding expression.
505
+
506
+ inplace: bool
507
+ A flag controls whether operation should be applied inplace.
508
+ If ``inplace=True``, then it returns nothing. Otherwise method
509
+ returns a new modified object.
510
+
511
+ Returns
512
+ -------
513
+ :class:`Source` or ``None``.
514
+
515
+ See also
516
+ --------
517
+ **UPDATE_FIELD** and **UPDATE_FIELDS** OneTick event processors
518
+
519
+ Examples
520
+ --------
521
+
522
+ Columns can be updated with this method:
523
+
524
+ >>> # OTdirective: snippet-name: Arrange.conditional update;
525
+ >>> t = otp.Ticks({'X': [1, 2, 3],
526
+ ... 'Y': [4, 5, 6],
527
+ ... 'Z': [1, 0, 1]})
528
+ >>> t = t.update(if_set={'X': t['X'] + t['Y']},
529
+ ... else_set={'X': t['X'] - t['Y']},
530
+ ... where=t['Z'] == 1)
531
+ >>> otp.run(t) # OTdirective: snippet-example;
532
+ Time X Y Z
533
+ 0 2003-12-01 00:00:00.000 5 4 1
534
+ 1 2003-12-01 00:00:00.001 -3 5 0
535
+ 2 2003-12-01 00:00:00.002 9 6 1
536
+
537
+ State variables can be updated too:
538
+
539
+ >>> t = otp.Ticks({'X': [1, 2, 3],
540
+ ... 'Y': [4, 5, 6],
541
+ ... 'Z': [1, 0, 1]})
542
+ >>> t.state_vars['X'] = 0
543
+ >>> t = t.update(if_set={t.state_vars['X']: t['X'] + t['Y']},
544
+ ... else_set={t.state_vars['X']: t['X'] - t['Y']},
545
+ ... where=t['Z'] == 1)
546
+ >>> t['UX'] = t.state_vars['X']
547
+ >>> otp.run(t)
548
+ Time X Y Z UX
549
+ 0 2003-12-01 00:00:00.000 1 4 1 5
550
+ 1 2003-12-01 00:00:00.001 2 5 0 -3
551
+ 2 2003-12-01 00:00:00.002 3 6 1 9
552
+ """
553
+ if else_set is None:
554
+ else_set = {}
555
+
556
+ if len(if_set) == 0 or not isinstance(if_set, dict):
557
+ raise ValueError(f"'if_set' parameter should be non empty dict, but got '{if_set}' of type '{type(if_set)}'")
558
+
559
+ def _prepare(to_prepare):
560
+ result = {}
561
+
562
+ for in_obj, out_obj in to_prepare.items():
563
+ if isinstance(in_obj, _StateColumn):
564
+ result[in_obj] = out_obj
565
+ elif isinstance(in_obj, _Column):
566
+ result[in_obj.name] = out_obj
567
+ elif isinstance(in_obj, str):
568
+ result[in_obj.strip()] = out_obj
569
+ else:
570
+ raise AttributeError(f"It is not supported to update item '{in_obj}' of type '{type(in_obj)}'")
571
+
572
+ return result
573
+
574
+ def _validate(to_validate):
575
+ result = {}
576
+ for in_key, out_obj in to_validate.items():
577
+ if isinstance(in_key, _StateColumn):
578
+ in_dtype = in_key.dtype
579
+ else:
580
+ if not (in_key in self.__dict__ and isinstance(self.__dict__[in_key], _Column)):
581
+ raise AttributeError(f"There is no '{in_key}' column to update")
582
+
583
+ if self._check_key_is_meta(in_key):
584
+ raise ValueError(f"Can't set meta field {in_key}")
585
+
586
+ in_dtype = self.schema[in_key]
587
+
588
+ dtype = ott.get_object_type(out_obj)
589
+ if not ott.is_type_supported(dtype):
590
+ raise TypeError(f"Deduced type of object {out_obj} is not supported: {dtype}")
591
+ # will raise exception if types are incompatible
592
+ _ = ott.get_type_by_objects([dtype, in_dtype])
593
+
594
+ if isinstance(in_key, _StateColumn):
595
+ in_key = str(in_key)
596
+
597
+ if isinstance(out_obj, bool):
598
+ out_obj = int(out_obj)
599
+
600
+ result[in_key] = out_obj
601
+
602
+ return result
603
+
604
+ # prepare and validate
605
+ items = _validate(_prepare(if_set))
606
+ else_items = _validate(_prepare(else_set))
607
+
608
+ if isinstance(where, bool):
609
+ where = int(where)
610
+
611
+ if not (getattr(where, "dtype", None) is bool or isinstance(where, int)):
612
+ raise ValueError(f"Where has not supported type '{type(where)}'")
613
+
614
+ # apply
615
+ set_rules = [f"{key}=({ott.value2str(value)})" for key, value in items.items()]
616
+ else_set_rules = [f"{key}=({ott.value2str(value)})" for key, value in else_items.items()]
617
+
618
+ self.sink(otq.UpdateFields(set=",".join(set_rules), else_set=",".join(else_set_rules), where=str(where)))
619
+ return self