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