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,2312 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import warnings
|
|
3
|
+
from string import Template
|
|
4
|
+
from os import path
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Iterable, Optional, Union, List, Dict
|
|
7
|
+
from functools import wraps
|
|
8
|
+
|
|
9
|
+
from onetick.py.otq import otq
|
|
10
|
+
import onetick.py.types as ott
|
|
11
|
+
from onetick.py.core.column import _Column
|
|
12
|
+
from onetick.py.core.column_operations.base import Operation
|
|
13
|
+
from onetick.py.types import (
|
|
14
|
+
string, value2str, nsectime, msectime, get_object_type, type2str, varstring, default_by_type, get_base_type,
|
|
15
|
+
)
|
|
16
|
+
from onetick.py.core.column_operations._methods.op_types import are_numerics, are_strings
|
|
17
|
+
from onetick.py.core.eval_query import _QueryEvalWrapper, prepare_params
|
|
18
|
+
from onetick.py.compatibility import (
|
|
19
|
+
is_supported_varstring_in_get_string_value,
|
|
20
|
+
is_supported_modify_state_var_from_query
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _StateBase(ABC):
|
|
25
|
+
|
|
26
|
+
def __init__(self, name, scope, default_value, obj_ref):
|
|
27
|
+
self.name = name
|
|
28
|
+
self.scope = scope
|
|
29
|
+
self.default_value = default_value
|
|
30
|
+
self.obj_ref = obj_ref
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
if not self.name:
|
|
34
|
+
raise ValueError('State variable has no name')
|
|
35
|
+
return f"STATE::{self.name}"
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def copy(self, obj_ref=None, name=None):
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
|
|
41
|
+
def modify_from_query(
|
|
42
|
+
self,
|
|
43
|
+
query,
|
|
44
|
+
symbol=None, start=None, end=None,
|
|
45
|
+
params=None,
|
|
46
|
+
action: str = 'replace',
|
|
47
|
+
where=None,
|
|
48
|
+
output_field_name=None,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Modifies a :py:meth:`state variable <onetick.py.Source.state_vars>`
|
|
52
|
+
by assigning it a value that was resulted from a ``query`` evaluation.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
query: callable, Source
|
|
57
|
+
Callable ``query`` should return :class:`Source`. This object will be evaluated by OneTick (not python)
|
|
58
|
+
for every tick. Note python code will be executed only once, so all python's conditional expressions
|
|
59
|
+
will be evaluated only once too.
|
|
60
|
+
|
|
61
|
+
If ``query`` is a :class:`Source` object then it will be propagated as a query to OneTick.
|
|
62
|
+
|
|
63
|
+
If state ``var`` is a primitive (not tick sequence), then ``query`` must return only one tick,
|
|
64
|
+
otherwise exception will be raised.
|
|
65
|
+
|
|
66
|
+
symbol: str, Operation, dict, Source, or Tuple[Union[str, Operation], Union[dict, Source]]
|
|
67
|
+
Symbol name to use in ``query``. In addition, symbol params can be passed along with symbol name.
|
|
68
|
+
|
|
69
|
+
Symbol name can be passed as a string or as an :class:`Operation`.
|
|
70
|
+
|
|
71
|
+
Symbol parameters can be passed as a dictionary. Also, the main :class:`Source` object,
|
|
72
|
+
or the object containing a symbol parameter list, can be used as a list of symbol parameters.
|
|
73
|
+
|
|
74
|
+
``symbol`` will be interpreted as a symbol name or as symbol parameters, depending on its type.
|
|
75
|
+
You can pass both as a tuple.
|
|
76
|
+
|
|
77
|
+
If symbol name is not passed, then symbol name from the main source is used.
|
|
78
|
+
|
|
79
|
+
start: :py:class:`otp.datetime <onetick.py.datetime>`, :py:class:`otp.Operation <onetick.py.Operation>`
|
|
80
|
+
Start time to run the ``query``.
|
|
81
|
+
By default the start time of the main query is used.
|
|
82
|
+
|
|
83
|
+
end: :py:class:`otp.datetime <onetick.py.datetime>`, :py:class:`otp.Operation <onetick.py.Operation>`
|
|
84
|
+
End time to run the ``query``.
|
|
85
|
+
By default the end time of the main query is used.
|
|
86
|
+
|
|
87
|
+
params: dict
|
|
88
|
+
Mapping of the parameters' names and their values for the ``query``.
|
|
89
|
+
:py:class:`Columns <onetick.py.Column>` can be used as a value.
|
|
90
|
+
|
|
91
|
+
action: str
|
|
92
|
+
Specifies whether all ticks should be erased before the query results are inserted into the tick set.
|
|
93
|
+
Possible values are ``update`` and ``replace``.
|
|
94
|
+
For non-tick-sets, you can set the ``action`` only to ``replace``; otherwise, an error is thrown.
|
|
95
|
+
|
|
96
|
+
where: Operation
|
|
97
|
+
Condition to filter ticks for which the result of the ``query`` will be joined.
|
|
98
|
+
|
|
99
|
+
output_field_name: str
|
|
100
|
+
Specifies the output field name for state variables of primitive types,
|
|
101
|
+
in case if the query result contains multiple fields.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
:class:`Source`
|
|
106
|
+
Source with joined ticks from ``query``
|
|
107
|
+
|
|
108
|
+
See also
|
|
109
|
+
--------
|
|
110
|
+
**MODIFY_STATE_VAR_FROM_QUERY** OneTick event processor
|
|
111
|
+
|
|
112
|
+
Examples
|
|
113
|
+
--------
|
|
114
|
+
|
|
115
|
+
Update simple state variable from query:
|
|
116
|
+
|
|
117
|
+
.. testcode::
|
|
118
|
+
:skipif: not is_supported_modify_state_var_from_query()
|
|
119
|
+
|
|
120
|
+
data = otp.Ticks(A=[1, 2, 3])
|
|
121
|
+
data.state_vars['VAR'] = 0
|
|
122
|
+
data.state_vars['VAR'] = 7
|
|
123
|
+
def fun():
|
|
124
|
+
return otp.Tick(X=123, Y=234)
|
|
125
|
+
data = data.state_vars['VAR'].modify_from_query(fun,
|
|
126
|
+
output_field_name='X',
|
|
127
|
+
where=(data['A'] % 2 == 1))
|
|
128
|
+
data['X'] = data.state_vars['VAR']
|
|
129
|
+
df = otp.run(data)
|
|
130
|
+
print(df)
|
|
131
|
+
|
|
132
|
+
.. testoutput::
|
|
133
|
+
|
|
134
|
+
Time A X
|
|
135
|
+
0 2003-12-01 00:00:00.000 1 123
|
|
136
|
+
1 2003-12-01 00:00:00.001 2 7
|
|
137
|
+
2 2003-12-01 00:00:00.002 3 123
|
|
138
|
+
|
|
139
|
+
Update tick sequence from query:
|
|
140
|
+
|
|
141
|
+
.. testcode::
|
|
142
|
+
:skipif: not is_supported_modify_state_var_from_query()
|
|
143
|
+
|
|
144
|
+
data = otp.Tick(A=1)
|
|
145
|
+
data.state_vars['VAR'] = otp.state.tick_list()
|
|
146
|
+
def fun():
|
|
147
|
+
return otp.Ticks(X=[123, 234])
|
|
148
|
+
data = data.state_vars['VAR'].modify_from_query(fun)
|
|
149
|
+
data = data.state_vars['VAR'].dump()
|
|
150
|
+
df = otp.run(data)
|
|
151
|
+
print(df)
|
|
152
|
+
|
|
153
|
+
.. testoutput::
|
|
154
|
+
|
|
155
|
+
Time X
|
|
156
|
+
0 2003-12-01 00:00:00.000 123
|
|
157
|
+
1 2003-12-01 00:00:00.001 234
|
|
158
|
+
|
|
159
|
+
Passing parameters to the ``query``:
|
|
160
|
+
|
|
161
|
+
.. testcode::
|
|
162
|
+
:skipif: not is_supported_modify_state_var_from_query()
|
|
163
|
+
|
|
164
|
+
data = otp.Tick(A=1)
|
|
165
|
+
data.state_vars['VAR'] = otp.state.tick_list()
|
|
166
|
+
def fun(min_value):
|
|
167
|
+
t = otp.Ticks(X=[123, 234])
|
|
168
|
+
t = t.where(t['X'] > min_value)
|
|
169
|
+
return t
|
|
170
|
+
data = data.state_vars['VAR'].modify_from_query(fun, params={'min_value': 200})
|
|
171
|
+
data = data.state_vars['VAR'].dump()
|
|
172
|
+
df = otp.run(data)
|
|
173
|
+
print(df)
|
|
174
|
+
|
|
175
|
+
.. testoutput::
|
|
176
|
+
|
|
177
|
+
Time X
|
|
178
|
+
0 2003-12-01 00:00:00.001 234
|
|
179
|
+
"""
|
|
180
|
+
import onetick.py as otp
|
|
181
|
+
obj_ref: otp.Source = getattr(self.obj_ref, '_owner')
|
|
182
|
+
|
|
183
|
+
action = action.lower()
|
|
184
|
+
if action not in {'replace', 'update'}:
|
|
185
|
+
raise ValueError(f"Value '{action}' for parameter 'action' is not supported")
|
|
186
|
+
|
|
187
|
+
if isinstance(self, _TickSequence) and output_field_name:
|
|
188
|
+
raise ValueError("Parameter 'output_field_name' can't be set for tick sequences")
|
|
189
|
+
|
|
190
|
+
params = params or {}
|
|
191
|
+
converted_params = prepare_params(**params)
|
|
192
|
+
if isinstance(query, otp.Source):
|
|
193
|
+
sub_source = query
|
|
194
|
+
else:
|
|
195
|
+
sub_source = query(**converted_params)
|
|
196
|
+
if not isinstance(sub_source, otp.Source):
|
|
197
|
+
raise ValueError(f"{query} didn't return Source object")
|
|
198
|
+
|
|
199
|
+
if not isinstance(self, _TickSequence) and len(sub_source.schema) > 1 and not output_field_name:
|
|
200
|
+
raise ValueError("Parameter 'output_field_name' must be set"
|
|
201
|
+
" if there is more than one field in query schema")
|
|
202
|
+
|
|
203
|
+
if output_field_name and output_field_name not in sub_source.schema:
|
|
204
|
+
raise ValueError(f"There is no field '{output_field_name}' in query schema")
|
|
205
|
+
|
|
206
|
+
ep_params = {
|
|
207
|
+
'state_variable': str(self),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
ep_params['action'] = action.upper()
|
|
211
|
+
|
|
212
|
+
if output_field_name:
|
|
213
|
+
ep_params['output_field_name'] = ott.value2str(output_field_name)
|
|
214
|
+
|
|
215
|
+
if start is None:
|
|
216
|
+
start = otp.meta_fields['_START_TIME']
|
|
217
|
+
if end is None:
|
|
218
|
+
end = otp.meta_fields['_END_TIME']
|
|
219
|
+
|
|
220
|
+
from onetick.py.core._source.source_methods.joins import (_process_start_or_end_of_jwq,
|
|
221
|
+
_columns_to_params_for_joins,
|
|
222
|
+
_check_and_convert_symbol,
|
|
223
|
+
_convert_symbol_param_and_columns)
|
|
224
|
+
_process_start_or_end_of_jwq(ep_params, start, 'start_timestamp')
|
|
225
|
+
_process_start_or_end_of_jwq(ep_params, end, 'end_timestamp')
|
|
226
|
+
|
|
227
|
+
if params:
|
|
228
|
+
params_str = _columns_to_params_for_joins(params, query_params=True)
|
|
229
|
+
ep_params['otq_query_params'] = params_str
|
|
230
|
+
|
|
231
|
+
if where is not None:
|
|
232
|
+
ep_params['where'] = str(where)
|
|
233
|
+
|
|
234
|
+
converted_symbol_name, symbol_param = _check_and_convert_symbol(symbol)
|
|
235
|
+
|
|
236
|
+
# default symbol name should be this: _SYMBOL_NAME if it is not empty else _NON_EXISTING_SYMBOL_
|
|
237
|
+
# this way we will force JWQ to substitute symbol with any symbol parameters we may have passed
|
|
238
|
+
# otherwise (if an empty symbol name is passed to JWQ), it will not substitute either symbol name
|
|
239
|
+
# or symbol parameters, and so symbol parameters may get lost
|
|
240
|
+
# see BDS-263
|
|
241
|
+
if converted_symbol_name is None:
|
|
242
|
+
converted_symbol_name = "CASE(_SYMBOL_NAME,'','_NON_EXISTING_SYMBOL',_SYMBOL_NAME)"
|
|
243
|
+
|
|
244
|
+
converted_symbol_param_columns, converted_symbol_param = _convert_symbol_param_and_columns(symbol_param)
|
|
245
|
+
if converted_symbol_param is None:
|
|
246
|
+
# we couldn't interpret "symbols" as either symbol name or symbol parameters
|
|
247
|
+
raise ValueError('"symbol" parameter has a wrong format! It should be a symbol name, a symbol parameter '
|
|
248
|
+
'object (dict or Source), or a tuple containing both')
|
|
249
|
+
|
|
250
|
+
symbol_params_str = _columns_to_params_for_joins(converted_symbol_param_columns)
|
|
251
|
+
|
|
252
|
+
ep_params['symbol_name'] = converted_symbol_name
|
|
253
|
+
ep_params['symbol_params'] = symbol_params_str
|
|
254
|
+
|
|
255
|
+
res = obj_ref.copy()
|
|
256
|
+
|
|
257
|
+
res._merge_tmp_otq(sub_source)
|
|
258
|
+
query_name = sub_source._store_in_tmp_otq(
|
|
259
|
+
res._tmp_otq,
|
|
260
|
+
symbols='_NON_EXISTING_SYMBOL_',
|
|
261
|
+
operation_suffix='modify_state_var_from_query',
|
|
262
|
+
)
|
|
263
|
+
ep_params['otq_query'] = f'"THIS::{query_name}"'
|
|
264
|
+
|
|
265
|
+
res.sink(otq.ModifyStateVarFromQuery(**ep_params))
|
|
266
|
+
return res
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class _StateColumn(_StateBase, _Column):
|
|
270
|
+
|
|
271
|
+
def __init__(self, name, dtype, obj_ref, default_value, scope):
|
|
272
|
+
_Column.__init__(self, name, dtype, obj_ref)
|
|
273
|
+
_StateBase.__init__(self, name, scope, default_value, obj_ref=obj_ref)
|
|
274
|
+
|
|
275
|
+
def __len__(self):
|
|
276
|
+
if issubclass(self.dtype, str):
|
|
277
|
+
if self.dtype is str:
|
|
278
|
+
return string.DEFAULT_LENGTH
|
|
279
|
+
else:
|
|
280
|
+
return self.dtype.length
|
|
281
|
+
|
|
282
|
+
else:
|
|
283
|
+
raise NotImplementedError
|
|
284
|
+
|
|
285
|
+
def copy(self, obj_ref=None, name=None):
|
|
286
|
+
return _StateColumn(name if name else self.name,
|
|
287
|
+
self.dtype,
|
|
288
|
+
obj_ref,
|
|
289
|
+
self.default_value,
|
|
290
|
+
self.scope)
|
|
291
|
+
|
|
292
|
+
def __getitem__(self, item):
|
|
293
|
+
raise IndexError('Indexing is not supported for state variables')
|
|
294
|
+
|
|
295
|
+
def modify_from_query(self, *args, **kwargs):
|
|
296
|
+
if 'action' in kwargs:
|
|
297
|
+
raise ValueError("Parameter 'action' can only be used with tick sequences")
|
|
298
|
+
return super().modify_from_query(*args, **kwargs)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def inplace_operation(method):
|
|
302
|
+
"""
|
|
303
|
+
Decorator that adds the `inplace` parameter and logic according to this flag.
|
|
304
|
+
inplace=True means that method modifies an object,
|
|
305
|
+
otherwise it copies the object first, then modifies the copy and returns it.
|
|
306
|
+
Decorator can be used with _TickSequence class methods only.
|
|
307
|
+
"""
|
|
308
|
+
@wraps(method)
|
|
309
|
+
def _inner(self, *args, inplace=False, **kwargs):
|
|
310
|
+
if inplace or self._is_used_in_per_tick_script:
|
|
311
|
+
return method(self, *args, **kwargs)
|
|
312
|
+
obj = self._owner.copy()
|
|
313
|
+
return method(obj.state_vars[self.name], *args, **kwargs)
|
|
314
|
+
|
|
315
|
+
return _inner
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def check_value_dtype(value, dtype, check_string_length=False) -> bool:
|
|
319
|
+
"""
|
|
320
|
+
Check if ``value`` is compatible to the type ``dtype``.
|
|
321
|
+
``value`` can be object or type.
|
|
322
|
+
If ``check_string_length`` is set then warning is raised in case
|
|
323
|
+
string types conversion will result in losing precision.
|
|
324
|
+
"""
|
|
325
|
+
value_dtype = value if isinstance(value, type) else get_object_type(value)
|
|
326
|
+
if are_strings(value_dtype, dtype):
|
|
327
|
+
if check_string_length:
|
|
328
|
+
length = string.DEFAULT_LENGTH
|
|
329
|
+
if issubclass(dtype, string) and dtype is not string:
|
|
330
|
+
length = dtype.length
|
|
331
|
+
if dtype is not varstring and len(value) > length:
|
|
332
|
+
warnings.warn(
|
|
333
|
+
f"Value '{value}' will be truncated to {length} characters, "
|
|
334
|
+
f"because corresponding type in schema is {dtype}"
|
|
335
|
+
)
|
|
336
|
+
return True
|
|
337
|
+
elif are_numerics(value_dtype, dtype):
|
|
338
|
+
return get_base_type(value_dtype) is get_base_type(dtype)
|
|
339
|
+
else:
|
|
340
|
+
return value_dtype is dtype
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class _TickSequence(_StateBase):
|
|
344
|
+
def __init__(self, name, obj_ref, default_value, scope, schema=None, **kwargs):
|
|
345
|
+
if kwargs:
|
|
346
|
+
raise ValueError(f"Unknown parameters for '{self.__class__.__name__}': {list(kwargs)}")
|
|
347
|
+
from onetick.py.core.source import Source
|
|
348
|
+
if default_value is not None and not isinstance(default_value, (_QueryEvalWrapper, Source)):
|
|
349
|
+
raise ValueError('only otp.eval and otp.Source objects can be used as initial value for tick sequences')
|
|
350
|
+
if default_value is not None and schema is not None:
|
|
351
|
+
# TODO: check that the two schemas align or possibly that they are exactly the same
|
|
352
|
+
pass
|
|
353
|
+
super().__init__(name, scope, default_value, obj_ref=obj_ref)
|
|
354
|
+
self.obj_ref = obj_ref
|
|
355
|
+
self.dtype = self.__class__
|
|
356
|
+
self._schema = schema.copy() if schema is not None else {}
|
|
357
|
+
|
|
358
|
+
def __iter__(self):
|
|
359
|
+
raise TypeError(f'{self.__class__.__name__} objects can be iterated only in per-tick script')
|
|
360
|
+
|
|
361
|
+
def copy(self, obj_ref=None, name=None, **kwargs):
|
|
362
|
+
return self.__class__(name=name if name else self.name,
|
|
363
|
+
obj_ref=obj_ref,
|
|
364
|
+
default_value=self.default_value,
|
|
365
|
+
scope=self.scope,
|
|
366
|
+
schema=self._schema,
|
|
367
|
+
**kwargs)
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def _owner(self):
|
|
371
|
+
"""
|
|
372
|
+
Get source that owns this state variable.
|
|
373
|
+
"""
|
|
374
|
+
if self.obj_ref is None:
|
|
375
|
+
raise ValueError("Add tick sequence to the state_vars of the Source"
|
|
376
|
+
" before calling it's methods")
|
|
377
|
+
return self.obj_ref._owner
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def _is_used_in_per_tick_script(self) -> bool:
|
|
381
|
+
"""Check if this variable is used in per-tick script or on Source directly"""
|
|
382
|
+
# TODO: remove circular imports
|
|
383
|
+
from ..lambda_object import _EmulateInputObject
|
|
384
|
+
return isinstance(self._owner, _EmulateInputObject)
|
|
385
|
+
|
|
386
|
+
def _default_schema(self):
|
|
387
|
+
return dict(self._owner.schema.items())
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def schema(self) -> dict:
|
|
391
|
+
"""
|
|
392
|
+
Get schema of the tick sequence.
|
|
393
|
+
"""
|
|
394
|
+
fields = None
|
|
395
|
+
if isinstance(self._schema, list):
|
|
396
|
+
fields = self._schema
|
|
397
|
+
self._schema = None
|
|
398
|
+
if not self._schema:
|
|
399
|
+
if self.default_value is not None:
|
|
400
|
+
from onetick.py.core.source import Source
|
|
401
|
+
if isinstance(self.default_value, _QueryEvalWrapper):
|
|
402
|
+
# If tick sequence is initialized from eval,
|
|
403
|
+
# then we get schema from the Source in eval.
|
|
404
|
+
self._schema = self.default_value.query.schema.copy()
|
|
405
|
+
elif isinstance(self.default_value, Source):
|
|
406
|
+
self._schema = self.default_value.schema.copy()
|
|
407
|
+
else:
|
|
408
|
+
# If tick sequence is initialized as empty,
|
|
409
|
+
# then it's schema will be derived from the schema of the parent object (e.g. source)
|
|
410
|
+
self._schema = self._default_schema()
|
|
411
|
+
if fields is not None:
|
|
412
|
+
self._schema = {field: type for field, type in self._schema.items() if field in fields}
|
|
413
|
+
for field in fields:
|
|
414
|
+
if field not in self._schema.keys():
|
|
415
|
+
raise KeyError(f'Requested field "{field}" is not contained in the base schema!')
|
|
416
|
+
return self._schema
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def _tick_class(self):
|
|
420
|
+
"""Get corresponding class for tick when iterating over tick sequence"""
|
|
421
|
+
return TickSequenceTick
|
|
422
|
+
|
|
423
|
+
def _tick_obj(self, name):
|
|
424
|
+
"""Get corresponding object for tick when iterating over tick sequence"""
|
|
425
|
+
cls = self._tick_class
|
|
426
|
+
return cls(name, owner=self)
|
|
427
|
+
|
|
428
|
+
def _definition(self) -> str:
|
|
429
|
+
"""Get OneTick string that constructs tick sequence object"""
|
|
430
|
+
raise NotImplementedError
|
|
431
|
+
|
|
432
|
+
@property
|
|
433
|
+
def _dump_ep(self):
|
|
434
|
+
"""onetick.query ep that dumps tick sequence"""
|
|
435
|
+
raise NotImplementedError
|
|
436
|
+
|
|
437
|
+
@inplace_operation
|
|
438
|
+
def dump(self,
|
|
439
|
+
propagate_input_ticks=False,
|
|
440
|
+
when_to_dump='first_tick',
|
|
441
|
+
delimiter=None,
|
|
442
|
+
added_field_name_suffix=None):
|
|
443
|
+
"""
|
|
444
|
+
Propagates all ticks from a given tick sequence upon the arrival of input tick.
|
|
445
|
+
|
|
446
|
+
Preserves original timestamps from tick list/deque, if they fall into query start/end time range,
|
|
447
|
+
and for ticks before start time and after end time, timestamps are changed
|
|
448
|
+
to start time and end time correspondingly.
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
propagate_input_ticks: bool
|
|
453
|
+
Propagate input ticks or not.
|
|
454
|
+
when_to_dump: str
|
|
455
|
+
|
|
456
|
+
* `first_tick` - Propagates once before input ticks. There must be at least one input tick.
|
|
457
|
+
* `before_tick` - Propagates once before input ticks.
|
|
458
|
+
Content will be propagated even if there are no input ticks.
|
|
459
|
+
delimiter: 'tick', 'flag or None
|
|
460
|
+
This parameter specifies the policy for adding the delimiter field.
|
|
461
|
+
The name of the additional field is "DELIMITER" + ``added_field_name_suffix``.
|
|
462
|
+
Possible options are:
|
|
463
|
+
|
|
464
|
+
* None - No additional field is added to propagated ticks.
|
|
465
|
+
* 'tick' - An extra tick is created after the last tick.
|
|
466
|
+
Also, an additional column is added to output ticks.
|
|
467
|
+
The extra tick has values of all fields set to the defaults (0,NaN,""),
|
|
468
|
+
except the delimiter field, which is set to string "D".
|
|
469
|
+
All other ticks have this field's value set to empty string.
|
|
470
|
+
* 'flag' - The delimiter field is appended to each output tick.
|
|
471
|
+
The field's value is empty for all ticks except the last tick of the tick sequence, which is string "D".
|
|
472
|
+
added_field_name_suffix: str or None
|
|
473
|
+
The suffix to add to the name of the additional field.
|
|
474
|
+
inplace: bool
|
|
475
|
+
If ``True`` current source will be modified else modified copy will be returned
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
if ``inplace`` is False then returns :py:class:`~onetick.py.Source` copy.
|
|
480
|
+
|
|
481
|
+
Examples
|
|
482
|
+
--------
|
|
483
|
+
>>> def another_query():
|
|
484
|
+
... return otp.Ticks(B=[1, 2, 3])
|
|
485
|
+
>>> data = otp.Tick(A=1)
|
|
486
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
487
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
488
|
+
>>> otp.run(data)[['B']]
|
|
489
|
+
B
|
|
490
|
+
0 1
|
|
491
|
+
1 2
|
|
492
|
+
2 3
|
|
493
|
+
"""
|
|
494
|
+
when_to_dump = when_to_dump.upper()
|
|
495
|
+
if when_to_dump not in ('FIRST_TICK', 'BEFORE_TICK', 'EVERY_TICK'):
|
|
496
|
+
raise ValueError(
|
|
497
|
+
f"Parameter 'when_to_dump' must be one of {('FIRST_TICK', 'BEFORE_TICK', 'EVERY_TICK')}. "
|
|
498
|
+
f"Got {when_to_dump}."
|
|
499
|
+
)
|
|
500
|
+
if delimiter is not None:
|
|
501
|
+
delimiters = {
|
|
502
|
+
'tick': 'TICK_AT_END',
|
|
503
|
+
'tick_at_end': 'TICK_AT_END',
|
|
504
|
+
'flag': 'FLAG_AT_END',
|
|
505
|
+
'flag_at_end': 'FLAG_AT_END',
|
|
506
|
+
'none': 'NONE',
|
|
507
|
+
}
|
|
508
|
+
delimiter = delimiter.lower()
|
|
509
|
+
if delimiter not in delimiters:
|
|
510
|
+
raise ValueError(
|
|
511
|
+
f"Parameter 'delimiter' must be one of {list(delimiters)}. "
|
|
512
|
+
f"Got {delimiter}."
|
|
513
|
+
)
|
|
514
|
+
delimiter = delimiters[delimiter]
|
|
515
|
+
else:
|
|
516
|
+
delimiter = 'NONE'
|
|
517
|
+
|
|
518
|
+
added_field_name_suffix = added_field_name_suffix or ''
|
|
519
|
+
|
|
520
|
+
if self.default_value is not None:
|
|
521
|
+
if not propagate_input_ticks:
|
|
522
|
+
self._owner.schema.set(**self.schema)
|
|
523
|
+
else:
|
|
524
|
+
self._owner.schema.update(**self.schema)
|
|
525
|
+
|
|
526
|
+
self._owner.sink(
|
|
527
|
+
self._dump_ep(str(self),
|
|
528
|
+
propagate_input_ticks=propagate_input_ticks,
|
|
529
|
+
when_to_dump=when_to_dump,
|
|
530
|
+
delimiter=delimiter,
|
|
531
|
+
added_field_name_suffix=added_field_name_suffix or '')
|
|
532
|
+
)
|
|
533
|
+
return self._owner
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
class TickList(_TickSequence):
|
|
537
|
+
"""
|
|
538
|
+
Represents a tick list.
|
|
539
|
+
This class should only be created with :py:func:`onetick.py.state.tick_list` function
|
|
540
|
+
and should be added to the :py:meth:`onetick.py.Source.state_vars` dictionary
|
|
541
|
+
of the :py:class:`onetick.py.Source` and can be accessed only via this dictionary.
|
|
542
|
+
|
|
543
|
+
Examples
|
|
544
|
+
--------
|
|
545
|
+
>>> data = otp.Tick(A=1)
|
|
546
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list()
|
|
547
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
548
|
+
|
|
549
|
+
See also
|
|
550
|
+
--------
|
|
551
|
+
:py:class:`TickSequenceTick <onetick.py.core._internal._state_objects.TickSequenceTick>`
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
def _definition(self) -> str:
|
|
555
|
+
return 'TICK_LIST'
|
|
556
|
+
|
|
557
|
+
@property
|
|
558
|
+
def _tick_class(self):
|
|
559
|
+
return _TickListTick
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def _dump_ep(self):
|
|
563
|
+
return otq.DumpTickList
|
|
564
|
+
|
|
565
|
+
@inplace_operation
|
|
566
|
+
def clear(self):
|
|
567
|
+
"""
|
|
568
|
+
Clear tick list.
|
|
569
|
+
Can be used in per-tick script or on Source directly.
|
|
570
|
+
|
|
571
|
+
inplace: bool
|
|
572
|
+
If ``True`` current source will be modified else modified copy will be returned.
|
|
573
|
+
Makes sense only if used not in per-tick script.
|
|
574
|
+
|
|
575
|
+
Returns
|
|
576
|
+
-------
|
|
577
|
+
if ``inplace`` is False and method is not used in per-tick script
|
|
578
|
+
then returns :py:class:`~onetick.py.Source` copy.
|
|
579
|
+
|
|
580
|
+
Examples
|
|
581
|
+
--------
|
|
582
|
+
Can be used in per-tick script:
|
|
583
|
+
|
|
584
|
+
>>> def fun(tick):
|
|
585
|
+
... tick.state_vars['LIST'].clear()
|
|
586
|
+
>>> data = otp.Tick(A=1)
|
|
587
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list()
|
|
588
|
+
>>> data = data.script(fun)
|
|
589
|
+
|
|
590
|
+
Can be used in source columns operations:
|
|
591
|
+
|
|
592
|
+
>>> data = otp.Tick(A=1)
|
|
593
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(otp.Tick(A=1)))
|
|
594
|
+
>>> data = data.state_vars['LIST'].clear()
|
|
595
|
+
|
|
596
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
597
|
+
>>> otp.run(data)
|
|
598
|
+
Empty DataFrame
|
|
599
|
+
Columns: [A, Time]
|
|
600
|
+
Index: []
|
|
601
|
+
"""
|
|
602
|
+
if self._is_used_in_per_tick_script:
|
|
603
|
+
return f'{self}.CLEAR();'
|
|
604
|
+
self._owner.sink(
|
|
605
|
+
otq.ExecuteExpressions(f'{self}.CLEAR()')
|
|
606
|
+
)
|
|
607
|
+
return self._owner
|
|
608
|
+
|
|
609
|
+
def push_back(self, tick_object):
|
|
610
|
+
"""
|
|
611
|
+
Add `tick_object` to the tick list.
|
|
612
|
+
Can only be used in per-tick script.
|
|
613
|
+
|
|
614
|
+
Examples
|
|
615
|
+
--------
|
|
616
|
+
>>> def fun(tick):
|
|
617
|
+
... tick.state_vars['LIST'].push_back(tick)
|
|
618
|
+
>>> data = otp.Tick(A=1)
|
|
619
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list()
|
|
620
|
+
>>> data = data.script(fun)
|
|
621
|
+
|
|
622
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
623
|
+
>>> otp.run(data)
|
|
624
|
+
Time A
|
|
625
|
+
0 2003-12-01 1
|
|
626
|
+
"""
|
|
627
|
+
if not self._is_used_in_per_tick_script:
|
|
628
|
+
raise ValueError('method .push_back() can only be used in per-tick script')
|
|
629
|
+
self._schema.update(**tick_object.schema)
|
|
630
|
+
self.obj_ref.CHANGED_TICK_LISTS[self.name] = self.schema
|
|
631
|
+
return f'{self}.PUSH_BACK({tick_object});'
|
|
632
|
+
|
|
633
|
+
def get_size(self) -> Operation:
|
|
634
|
+
"""
|
|
635
|
+
Get size of the tick list.
|
|
636
|
+
Can be used in per-tick script or in Source operations directly.
|
|
637
|
+
|
|
638
|
+
Examples
|
|
639
|
+
--------
|
|
640
|
+
Can be used in per-tick script:
|
|
641
|
+
|
|
642
|
+
>>> def fun(tick):
|
|
643
|
+
... tick['B'] = tick.state_vars['LIST'].get_size()
|
|
644
|
+
>>> data = otp.Tick(A=1)
|
|
645
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list()
|
|
646
|
+
>>> data = data.script(fun)
|
|
647
|
+
|
|
648
|
+
Can be used in source columns operations:
|
|
649
|
+
|
|
650
|
+
>>> data = otp.Tick(A=1)
|
|
651
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list()
|
|
652
|
+
>>> data['B'] = data.state_vars['LIST'].get_size()
|
|
653
|
+
|
|
654
|
+
>>> otp.run(data)
|
|
655
|
+
Time A B
|
|
656
|
+
0 2003-12-01 1 0
|
|
657
|
+
"""
|
|
658
|
+
return Operation(dtype=int, op_str=f'{self}.GET_SIZE()')
|
|
659
|
+
|
|
660
|
+
def size(self) -> Operation:
|
|
661
|
+
"""
|
|
662
|
+
See also
|
|
663
|
+
--------
|
|
664
|
+
get_size
|
|
665
|
+
"""
|
|
666
|
+
return self.get_size()
|
|
667
|
+
|
|
668
|
+
@inplace_operation
|
|
669
|
+
def sort(self, field_name, field_type=None):
|
|
670
|
+
"""
|
|
671
|
+
Sort the tick list in the ascending order over the specified field.
|
|
672
|
+
Integer, float and datetime fields are supported for sorting.
|
|
673
|
+
Implementation is per tick script which does a merge sort algorithm.
|
|
674
|
+
Implemented algorithm is stable: it should not change the order of ticks with the same field value.
|
|
675
|
+
Can only be used Source operations directly.
|
|
676
|
+
|
|
677
|
+
Parameters
|
|
678
|
+
----------
|
|
679
|
+
field_name: str
|
|
680
|
+
Name of the field over which to sort ticks
|
|
681
|
+
|
|
682
|
+
field_type: int, float, otp.msectime, otp.nsectime or None (default: None)
|
|
683
|
+
Type of the field_name field. If None, type will be taken from the tick list schema.
|
|
684
|
+
|
|
685
|
+
Examples
|
|
686
|
+
--------
|
|
687
|
+
|
|
688
|
+
>>> def fun(tick):
|
|
689
|
+
... tick.state_vars['LIST'].push_back(tick)
|
|
690
|
+
>>> data = otp.Ticks([
|
|
691
|
+
... ['offset', 'VALUE'],
|
|
692
|
+
... [0, 2],
|
|
693
|
+
... [0, 3],
|
|
694
|
+
... [0, 1],
|
|
695
|
+
... ])
|
|
696
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list()
|
|
697
|
+
>>> data = data.script(fun)
|
|
698
|
+
>>> data = data.agg(dict(NUM_TICKS=otp.agg.count()), bucket_time='end')
|
|
699
|
+
>>> data = data.state_vars['LIST'].sort('VALUE', int)
|
|
700
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
701
|
+
|
|
702
|
+
>>> otp.run(data)
|
|
703
|
+
Time VALUE
|
|
704
|
+
0 2003-12-01 1
|
|
705
|
+
1 2003-12-01 2
|
|
706
|
+
2 2003-12-01 3
|
|
707
|
+
"""
|
|
708
|
+
if self._is_used_in_per_tick_script:
|
|
709
|
+
# TODO: implement
|
|
710
|
+
raise NotImplementedError('Sorting tick lists in per tick script is currently not implemented')
|
|
711
|
+
with open(
|
|
712
|
+
path.join(
|
|
713
|
+
path.dirname(path.abspath(__file__)),
|
|
714
|
+
"_per_tick_scripts",
|
|
715
|
+
"tick_list_sort_template.script",
|
|
716
|
+
),
|
|
717
|
+
) as script_file:
|
|
718
|
+
sorting_script_template = Template(script_file.read())
|
|
719
|
+
|
|
720
|
+
if field_type is None:
|
|
721
|
+
field_type = self.schema[field_name]
|
|
722
|
+
if field_type is int:
|
|
723
|
+
field_access_function = 'GET_LONG_VALUE'
|
|
724
|
+
elif field_type is float:
|
|
725
|
+
field_access_function = 'GET_DOUBLE_VALUE'
|
|
726
|
+
elif field_type is nsectime or field_type is msectime:
|
|
727
|
+
field_access_function = 'GET_DATETIME_VALUE'
|
|
728
|
+
else:
|
|
729
|
+
raise TypeError('Field type {field_type} is not supported for sorting!'
|
|
730
|
+
'Supported field types: int, float, otp.nsectime, otp.msectime')
|
|
731
|
+
sorting_script = sorting_script_template.substitute(
|
|
732
|
+
tick_list_var=str(self),
|
|
733
|
+
field_name=field_name,
|
|
734
|
+
field_access_function=field_access_function,
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
self._owner.sink(
|
|
738
|
+
otq.PerTickScript(
|
|
739
|
+
script=sorting_script
|
|
740
|
+
)
|
|
741
|
+
)
|
|
742
|
+
return self._owner
|
|
743
|
+
|
|
744
|
+
def erase(self, tick_object):
|
|
745
|
+
"""
|
|
746
|
+
Remove `tick_object` from the tick list.
|
|
747
|
+
Can only be used in per-tick script.
|
|
748
|
+
|
|
749
|
+
Examples
|
|
750
|
+
--------
|
|
751
|
+
>>> def another_query():
|
|
752
|
+
... return otp.Ticks(X=[1, 2])
|
|
753
|
+
>>> def fun(tick):
|
|
754
|
+
... for t in tick.state_vars['LIST']:
|
|
755
|
+
... if t['X'] == 1:
|
|
756
|
+
... tick.state_vars['LIST'].erase(t)
|
|
757
|
+
>>> data = otp.Tick(A=1)
|
|
758
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
759
|
+
>>> data = data.script(fun)
|
|
760
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
761
|
+
>>> otp.run(data)
|
|
762
|
+
Time X
|
|
763
|
+
0 2003-12-01 00:00:00.001 2
|
|
764
|
+
"""
|
|
765
|
+
if not self._is_used_in_per_tick_script:
|
|
766
|
+
raise TypeError('erase() method for tick lists is supported only in script')
|
|
767
|
+
return f'{self}.ERASE({tick_object});'
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
class TickSet(_TickSequence):
|
|
771
|
+
"""
|
|
772
|
+
Represents a tick set.
|
|
773
|
+
This class should only be created with :py:func:`onetick.py.state.tick_set` function
|
|
774
|
+
and should be added to the :py:meth:`onetick.py.Source.state_vars` dictionary
|
|
775
|
+
of the :py:class:`onetick.py.Source` and can be accessed only via this dictionary.
|
|
776
|
+
|
|
777
|
+
Examples
|
|
778
|
+
--------
|
|
779
|
+
>>> data = otp.Tick(A=1)
|
|
780
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
781
|
+
>>> data = data.state_vars['SET'].dump()
|
|
782
|
+
|
|
783
|
+
See also
|
|
784
|
+
--------
|
|
785
|
+
:py:class:`TickSequenceTick <onetick.py.core._internal._state_objects.TickSequenceTick>`
|
|
786
|
+
"""
|
|
787
|
+
|
|
788
|
+
insertion_policies = {
|
|
789
|
+
'oldest': 'OLDEST_TICK',
|
|
790
|
+
'oldest_tick': 'OLDEST_TICK',
|
|
791
|
+
'latest': 'LATEST_TICK',
|
|
792
|
+
'latest_tick': 'LATEST_TICK',
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
def __init__(self, *args, insertion_policy, key_fields, **kwargs):
|
|
796
|
+
insertion_policy = insertion_policy.lower()
|
|
797
|
+
if insertion_policy not in self.insertion_policies:
|
|
798
|
+
raise ValueError(
|
|
799
|
+
f"Parameter 'insertion_policy' must be one of {list(self.insertion_policies)}. "
|
|
800
|
+
f"Got {insertion_policy}."
|
|
801
|
+
)
|
|
802
|
+
self._insertion_policy = insertion_policy
|
|
803
|
+
if isinstance(key_fields, str) or not isinstance(key_fields, Iterable):
|
|
804
|
+
key_fields = [key_fields]
|
|
805
|
+
self._key_fields = list(key_fields)
|
|
806
|
+
super().__init__(*args, **kwargs)
|
|
807
|
+
|
|
808
|
+
@property
|
|
809
|
+
def insertion_policy(self):
|
|
810
|
+
return self.insertion_policies[self._insertion_policy]
|
|
811
|
+
|
|
812
|
+
@property
|
|
813
|
+
def key_fields(self):
|
|
814
|
+
"""Get key fields for this tick set"""
|
|
815
|
+
if not set(self._key_fields).issubset(self.schema):
|
|
816
|
+
x = set(self._key_fields).difference(self.schema)
|
|
817
|
+
raise ValueError(f"Key fields {x} not in tick set schema")
|
|
818
|
+
return self._key_fields
|
|
819
|
+
|
|
820
|
+
def copy(self, *args, **kwargs):
|
|
821
|
+
kwargs.setdefault('insertion_policy', self._insertion_policy)
|
|
822
|
+
kwargs.setdefault('key_fields', self._key_fields)
|
|
823
|
+
return super().copy(*args, **kwargs)
|
|
824
|
+
|
|
825
|
+
def _definition(self) -> str:
|
|
826
|
+
args = ','.join(map(str, [self.insertion_policy, *self.key_fields]))
|
|
827
|
+
return f'TICK_SET({args})'
|
|
828
|
+
|
|
829
|
+
@property
|
|
830
|
+
def _tick_class(self):
|
|
831
|
+
return _TickSetTick
|
|
832
|
+
|
|
833
|
+
@property
|
|
834
|
+
def _dump_ep(self):
|
|
835
|
+
return otq.DumpTickSet
|
|
836
|
+
|
|
837
|
+
def dump(self, when_to_dump='every_tick', **kwargs):
|
|
838
|
+
"""
|
|
839
|
+
Propagates all ticks from a given tick sequence upon the arrival of input tick.
|
|
840
|
+
Timestamps of all propagated ticks are equal to the input tick's TIMESTAMP.
|
|
841
|
+
|
|
842
|
+
Parameters
|
|
843
|
+
----------
|
|
844
|
+
propagate_input_ticks: bool
|
|
845
|
+
Propagate input ticks or not.
|
|
846
|
+
when_to_dump: str
|
|
847
|
+
|
|
848
|
+
* `first_tick` - Propagates once before input ticks. There must be at least one input tick.
|
|
849
|
+
* `before_tick` - Propagates once before input ticks.
|
|
850
|
+
Content will be propagated even if there are no input ticks.
|
|
851
|
+
* `every_tick` - Propagates before *each* input tick.
|
|
852
|
+
delimiter: 'tick', 'flag or None
|
|
853
|
+
This parameter specifies the policy for adding the delimiter field.
|
|
854
|
+
The name of the additional field is "DELIMITER" + ``added_field_name_suffix``.
|
|
855
|
+
Possible options are:
|
|
856
|
+
|
|
857
|
+
* None - No additional field is added to propagated ticks.
|
|
858
|
+
* 'tick' - An extra tick is created after the last tick.
|
|
859
|
+
Also, an additional column is added to output ticks.
|
|
860
|
+
The extra tick has values of all fields set to the defaults (0,NaN,""),
|
|
861
|
+
except the delimiter field, which is set to string "D".
|
|
862
|
+
All other ticks have this field's value set to empty string.
|
|
863
|
+
* 'flag' - The delimiter field is appended to each output tick.
|
|
864
|
+
The field's value is empty for all ticks except the last tick of the tick sequence, which is string "D".
|
|
865
|
+
added_field_name_suffix: str or None
|
|
866
|
+
The suffix to add to the name of the additional field.
|
|
867
|
+
inplace: bool
|
|
868
|
+
If ``True`` current source will be modified else modified copy will be returned
|
|
869
|
+
|
|
870
|
+
Returns
|
|
871
|
+
-------
|
|
872
|
+
if ``inplace`` is False then returns :py:class:`~onetick.py.Source` copy.
|
|
873
|
+
|
|
874
|
+
Examples
|
|
875
|
+
--------
|
|
876
|
+
>>> def another_query():
|
|
877
|
+
... return otp.Ticks(B=[1, 2, 3])
|
|
878
|
+
>>> data = otp.Tick(A=1)
|
|
879
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'B', otp.eval(another_query))
|
|
880
|
+
>>> data = data.state_vars['SET'].dump()
|
|
881
|
+
>>> otp.run(data)[['B']]
|
|
882
|
+
B
|
|
883
|
+
0 1
|
|
884
|
+
1 2
|
|
885
|
+
2 3
|
|
886
|
+
"""
|
|
887
|
+
return super().dump(when_to_dump=when_to_dump, **kwargs)
|
|
888
|
+
|
|
889
|
+
@inplace_operation
|
|
890
|
+
def update(self, where=1, value_fields=None, erase_condition=0):
|
|
891
|
+
"""
|
|
892
|
+
Insert into or delete ticks from tick set.
|
|
893
|
+
Can be used only on Source directly.
|
|
894
|
+
|
|
895
|
+
Parameters
|
|
896
|
+
----------
|
|
897
|
+
where: :py:class:`~onetick.py.Operation`
|
|
898
|
+
Selection of input ticks that will be inserted into tick set.
|
|
899
|
+
By default, all input ticks are selected.
|
|
900
|
+
value_fields: list of str
|
|
901
|
+
List of value fields to be inserted into tick sets.
|
|
902
|
+
If param is empty, all fields of input tick are inserted.
|
|
903
|
+
Note that this applies only to non-key fields (key-fields are always included).
|
|
904
|
+
If new fields are added to tick set, they will have default values according to their type.
|
|
905
|
+
If some fields are in tick set schema but not added in this method, they will have default values.
|
|
906
|
+
erase_condition: :py:class:`~onetick.py.Operation`
|
|
907
|
+
Selection of input ticks that will be erased from tick set.
|
|
908
|
+
If it is set then ``where`` parameter is not taken into account.
|
|
909
|
+
inplace: bool
|
|
910
|
+
If ``True`` current source will be modified else modified copy will be returned
|
|
911
|
+
|
|
912
|
+
Returns
|
|
913
|
+
-------
|
|
914
|
+
if ``inplace`` is False then returns :py:class:`~onetick.py.Source` copy.
|
|
915
|
+
|
|
916
|
+
Examples
|
|
917
|
+
--------
|
|
918
|
+
>>> data = otp.Ticks(A=[1, 2, 3], B=[4, 5, 6], C=[7, 8, 9])
|
|
919
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
920
|
+
>>> data = data.state_vars['SET'].update(value_fields=['B'])
|
|
921
|
+
>>> data = data.state_vars['SET'].update(data['A'] == 2)
|
|
922
|
+
>>> data = data.state_vars['SET'].update(erase_condition=data['A'] == 2)
|
|
923
|
+
|
|
924
|
+
>>> data = data.first().state_vars['SET'].dump(when_to_dump='first_tick')
|
|
925
|
+
>>> otp.run(data)
|
|
926
|
+
Time A B
|
|
927
|
+
0 2003-12-01 1 4
|
|
928
|
+
1 2003-12-01 3 6
|
|
929
|
+
"""
|
|
930
|
+
value_fields = value_fields or []
|
|
931
|
+
if not set(value_fields).issubset(self._owner.schema):
|
|
932
|
+
x = set(value_fields).difference(self._owner.schema)
|
|
933
|
+
raise ValueError(f"value_fields {x} not in source schema")
|
|
934
|
+
if not set(self.key_fields).issubset(self._owner.schema):
|
|
935
|
+
x = set(self.key_fields).difference(self._owner.schema)
|
|
936
|
+
raise ValueError(f"Key fields {x} not in source schema")
|
|
937
|
+
for new_field in set(value_fields or self._owner.schema).difference(self.schema):
|
|
938
|
+
self.schema[new_field] = self._owner.schema[new_field]
|
|
939
|
+
value_fields = ','.join(map(str, value_fields))
|
|
940
|
+
self._owner.sink(
|
|
941
|
+
otq.UpdateTickSets(str(self),
|
|
942
|
+
where=str(where),
|
|
943
|
+
value_fields=value_fields,
|
|
944
|
+
erase_condition=str(erase_condition))
|
|
945
|
+
)
|
|
946
|
+
return self._owner
|
|
947
|
+
|
|
948
|
+
def _parse_keys(self, key_values=None, named_keys=None) -> list:
|
|
949
|
+
"""
|
|
950
|
+
Do some validations for the key values and named keys
|
|
951
|
+
and return list of keys and values to be inserted in OneTick function.
|
|
952
|
+
"""
|
|
953
|
+
if key_values and named_keys:
|
|
954
|
+
raise ValueError("Parameters 'key_values' and 'named_keys' can't be used at the same time")
|
|
955
|
+
if key_values:
|
|
956
|
+
if len(key_values) != len(self.key_fields):
|
|
957
|
+
raise ValueError(f"Wrong number of key values specified in parameter 'key_values', "
|
|
958
|
+
f"need {len(self.key_fields)} values")
|
|
959
|
+
for key_value, key in zip(key_values, self.key_fields):
|
|
960
|
+
key_value_type = get_object_type(key_value)
|
|
961
|
+
if not check_value_dtype(key_value, self.schema[key]):
|
|
962
|
+
raise ValueError(f"Key value '{key_value}' is type {key_value_type}, "
|
|
963
|
+
f"but the type of key '{key}' is {self.schema[key]}")
|
|
964
|
+
return key_values
|
|
965
|
+
if named_keys:
|
|
966
|
+
if not set(named_keys).issuperset(self.key_fields):
|
|
967
|
+
x = set(self.key_fields).difference(named_keys)
|
|
968
|
+
raise ValueError(f"Not all keys specified in parameter 'named_keys': {x}")
|
|
969
|
+
for key, key_value in named_keys.items():
|
|
970
|
+
if key not in self.key_fields:
|
|
971
|
+
raise ValueError(f"'{key}' not in tick set's key fields")
|
|
972
|
+
key_value_type = get_object_type(key_value)
|
|
973
|
+
if not check_value_dtype(key_value, self.schema[key]):
|
|
974
|
+
raise ValueError(f"Key value {key_value} is type {key_value_type}, "
|
|
975
|
+
f"but the type of key '{key}' is {self.schema[key]}")
|
|
976
|
+
return list(itertools.chain.from_iterable(named_keys.items()))
|
|
977
|
+
if not set(self.key_fields).intersection(self._owner.schema):
|
|
978
|
+
x = set(self.key_fields).difference(self._owner.schema)
|
|
979
|
+
raise ValueError(f"Key fields {x} not in source schema")
|
|
980
|
+
return []
|
|
981
|
+
|
|
982
|
+
def find(
|
|
983
|
+
self,
|
|
984
|
+
field_name: Union[str, 'TickSequenceTick'],
|
|
985
|
+
default_value=None,
|
|
986
|
+
*key_values,
|
|
987
|
+
throw: bool = False,
|
|
988
|
+
**named_keys,
|
|
989
|
+
) -> Operation:
|
|
990
|
+
"""
|
|
991
|
+
Finds a tick in the specified tick set for the given keys.
|
|
992
|
+
If ``field_name`` is a string, it returns the value of the specified field from the found tick.
|
|
993
|
+
If a tick with the given keys is not found, the default value is returned.
|
|
994
|
+
If ``field_name`` is :py:class:`TickSequenceTick <onetick.py.core._internal._state_objects.TickSequenceTick>`,
|
|
995
|
+
the entire tick is returned.
|
|
996
|
+
|
|
997
|
+
Parameters
|
|
998
|
+
----------
|
|
999
|
+
field_name: str, :py:class:`TickSequenceTick <onetick.py.core._internal._state_objects.TickSequenceTick>`
|
|
1000
|
+
The field to return the value from or an object for returning an entire tick.
|
|
1001
|
+
default_value
|
|
1002
|
+
The value to be returned if the key is not found.
|
|
1003
|
+
If ``default_value`` omitted and the key is not found,
|
|
1004
|
+
default "zero" value for the field type will be returned.
|
|
1005
|
+
If ``throw`` is True, the positional argument for ``default_value``
|
|
1006
|
+
counts as the first ``key_values`` argument.
|
|
1007
|
+
key_values:
|
|
1008
|
+
The same number of arguments as the number of keys in the tick set, in the same order as they
|
|
1009
|
+
were specified when defining the tick set.
|
|
1010
|
+
Values can be specified explicitly or taken from the specified columns. Columns
|
|
1011
|
+
must be specified as ``source['col']`` as opposed to just ``'col'``.
|
|
1012
|
+
``key_values`` are optional: if not specified (and ``named_keys`` are not specified either),
|
|
1013
|
+
the values for the keys are taken from the tick's columns.
|
|
1014
|
+
named_keys:
|
|
1015
|
+
Can be used instead of ``key_values`` by specifying the ``key=value`` named arguments.
|
|
1016
|
+
throw: bool
|
|
1017
|
+
Raise an exception if the key is not found.
|
|
1018
|
+
If ``True``, the positional argument for ``default_value`` counts as the first ``key_values`` argument.
|
|
1019
|
+
|
|
1020
|
+
Examples
|
|
1021
|
+
--------
|
|
1022
|
+
Create a tick set keyed by the values of ``A``. Look up the value of ``B``
|
|
1023
|
+
in the tick set for the value of ``A`` in the current tick.
|
|
1024
|
+
|
|
1025
|
+
>>> data = otp.Tick(A=1, B=2)
|
|
1026
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1, B=4)))
|
|
1027
|
+
>>> data['B'] = data.state_vars['SET'].find('B', 999)
|
|
1028
|
+
>>> otp.run(data)
|
|
1029
|
+
Time A B
|
|
1030
|
+
0 2003-12-01 1 4
|
|
1031
|
+
|
|
1032
|
+
A key can be specified explicitly (i.e., not taken from the key fields of the current tick).
|
|
1033
|
+
|
|
1034
|
+
>>> data = otp.Tick(C=777, B=2)
|
|
1035
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1, B=4)))
|
|
1036
|
+
>>> data['B'] = data.state_vars['SET'].find('B', 999, 1)
|
|
1037
|
+
>>> otp.run(data)
|
|
1038
|
+
Time C B
|
|
1039
|
+
0 2003-12-01 777 4
|
|
1040
|
+
|
|
1041
|
+
A key can be specified explicitly as a ``key=value`` pair.
|
|
1042
|
+
|
|
1043
|
+
>>> data = otp.Tick(C=777, B=2)
|
|
1044
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1, B=4)))
|
|
1045
|
+
>>> data['B'] = data.state_vars['SET'].find('B', 999, A=1)
|
|
1046
|
+
>>> otp.run(data)
|
|
1047
|
+
Time C B
|
|
1048
|
+
0 2003-12-01 777 4
|
|
1049
|
+
|
|
1050
|
+
Columns can be used as keys.
|
|
1051
|
+
|
|
1052
|
+
>>> data = otp.Tick(C=1, B=2)
|
|
1053
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1, B=4)))
|
|
1054
|
+
>>> data['B'] = data.state_vars['SET'].find('B', 999, data['C'])
|
|
1055
|
+
>>> otp.run(data)
|
|
1056
|
+
Time C B
|
|
1057
|
+
0 2003-12-01 1 4
|
|
1058
|
+
|
|
1059
|
+
The ``default_value`` is returned if the key is not found.
|
|
1060
|
+
|
|
1061
|
+
>>> data = otp.Tick(A=555, B=2)
|
|
1062
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1, B=4)))
|
|
1063
|
+
>>> data['B'] = data.state_vars['SET'].find('B', 999)
|
|
1064
|
+
>>> otp.run(data)
|
|
1065
|
+
Time A B
|
|
1066
|
+
0 2003-12-01 555 999
|
|
1067
|
+
|
|
1068
|
+
Throw an exception if the key is not found (there is no ``default_value`` when ``throw=True``):
|
|
1069
|
+
|
|
1070
|
+
>>> data = otp.Tick(A=555, B=2)
|
|
1071
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1, B=4)))
|
|
1072
|
+
>>> data['B'] = data.state_vars['SET'].find('B', throw=True)
|
|
1073
|
+
>>> otp.run(data) # doctest: +IGNORE_EXCEPTION_DETAIL
|
|
1074
|
+
Traceback (most recent call last):
|
|
1075
|
+
Exception: 9: ERROR:
|
|
1076
|
+
|
|
1077
|
+
``find`` can be used in ``otp.Source.script``.
|
|
1078
|
+
|
|
1079
|
+
>>> def fun(tick):
|
|
1080
|
+
... tick['B'] = tick.state_vars['SET'].find('B', 0, 1)
|
|
1081
|
+
>>> data = otp.Tick(A=1, B=2)
|
|
1082
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1, B=4)))
|
|
1083
|
+
>>> data = data.script(fun)
|
|
1084
|
+
>>> otp.run(data)
|
|
1085
|
+
Time A B
|
|
1086
|
+
0 2003-12-01 1 4
|
|
1087
|
+
|
|
1088
|
+
In ``otp.Source.script`` ``find`` can be used with ``TickSetTick``. It allows looking up a whole tick,
|
|
1089
|
+
rather than the value of a single field:
|
|
1090
|
+
|
|
1091
|
+
>>> def fun(tick):
|
|
1092
|
+
... t = otp.tick_set_tick()
|
|
1093
|
+
... if tick.state_vars['SET'].find(t, 2):
|
|
1094
|
+
... tick['B'] = t['B']
|
|
1095
|
+
... tick['C'] = t['C']
|
|
1096
|
+
... tick['D'] = t['D']
|
|
1097
|
+
>>> def another_query():
|
|
1098
|
+
... return otp.Ticks(B=[1, 2, 3], C=[4, 5, 6,], D=[7, 8, 9])
|
|
1099
|
+
>>> data = otp.Tick(A=1)
|
|
1100
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'B', otp.eval(another_query))
|
|
1101
|
+
>>> data = data.script(fun)
|
|
1102
|
+
>>> otp.run(data)
|
|
1103
|
+
Time A B C D
|
|
1104
|
+
0 2003-12-01 1 2 5 8
|
|
1105
|
+
"""
|
|
1106
|
+
if isinstance(field_name, TickSequenceTick):
|
|
1107
|
+
return self._find_tick_sequence_tick(
|
|
1108
|
+
field_name,
|
|
1109
|
+
default_value,
|
|
1110
|
+
*key_values,
|
|
1111
|
+
**named_keys,
|
|
1112
|
+
)
|
|
1113
|
+
return self._find_str(
|
|
1114
|
+
field_name,
|
|
1115
|
+
default_value,
|
|
1116
|
+
*key_values,
|
|
1117
|
+
throw=throw,
|
|
1118
|
+
**named_keys,
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
def _find_tick_sequence_tick(
|
|
1122
|
+
self,
|
|
1123
|
+
tick_sequence_tick: 'TickSequenceTick',
|
|
1124
|
+
default_value=None,
|
|
1125
|
+
*key_values,
|
|
1126
|
+
**named_keys,
|
|
1127
|
+
) -> Operation:
|
|
1128
|
+
if default_value is not None:
|
|
1129
|
+
key_values = (default_value, *key_values)
|
|
1130
|
+
func = 'FIND'
|
|
1131
|
+
if named_keys:
|
|
1132
|
+
func = 'FIND_BY_NAMED_KEYS'
|
|
1133
|
+
args = [self, value2str(tick_sequence_tick)]
|
|
1134
|
+
args.extend(map(value2str, self._parse_keys(key_values, named_keys)))
|
|
1135
|
+
str_args = ','.join(map(str, args))
|
|
1136
|
+
# set new owner since now TickSet defines the tick_set_tick's schema
|
|
1137
|
+
tick_sequence_tick._owner = self
|
|
1138
|
+
return Operation(dtype=int, op_str=f'{func}({str_args})')
|
|
1139
|
+
|
|
1140
|
+
def _find_str(
|
|
1141
|
+
self,
|
|
1142
|
+
field_name: str,
|
|
1143
|
+
default_value=None,
|
|
1144
|
+
*key_values,
|
|
1145
|
+
throw=False,
|
|
1146
|
+
**named_keys,
|
|
1147
|
+
) -> Operation:
|
|
1148
|
+
if field_name not in self.schema:
|
|
1149
|
+
raise ValueError(f"field_name '{field_name}' not in tick set schema")
|
|
1150
|
+
if default_value is None and not throw:
|
|
1151
|
+
default_value = default_by_type(self._schema[field_name])
|
|
1152
|
+
field_name_type = self.schema[field_name]
|
|
1153
|
+
if throw:
|
|
1154
|
+
if named_keys:
|
|
1155
|
+
raise ValueError("Parameters 'throw' and 'named_keys' can't be used at the same time")
|
|
1156
|
+
key_values = (default_value, *key_values)
|
|
1157
|
+
default_value = None
|
|
1158
|
+
else:
|
|
1159
|
+
default_value_type = get_object_type(default_value)
|
|
1160
|
+
if not check_value_dtype(default_value, field_name_type, check_string_length=True):
|
|
1161
|
+
raise ValueError(
|
|
1162
|
+
f"default_value '{default_value}' type is {default_value_type!r}, "
|
|
1163
|
+
f"but the type of field '{field_name}' is {field_name_type!r}"
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1166
|
+
args = [self, value2str(field_name)]
|
|
1167
|
+
if default_value is not None:
|
|
1168
|
+
args.append(value2str(default_value))
|
|
1169
|
+
if not (key_values and key_values[0] is None):
|
|
1170
|
+
args.extend(map(value2str, self._parse_keys(key_values, named_keys)))
|
|
1171
|
+
func = 'FIND'
|
|
1172
|
+
if throw:
|
|
1173
|
+
func = 'FIND_OR_THROW'
|
|
1174
|
+
if named_keys:
|
|
1175
|
+
func = 'FIND_BY_NAMED_KEYS'
|
|
1176
|
+
str_args = ','.join(map(str, args))
|
|
1177
|
+
return Operation(dtype=field_name_type, op_str=f'{func}({str_args})')
|
|
1178
|
+
|
|
1179
|
+
def find_by_named_keys(self, field_name, default_value=None, **named_keys) -> Operation:
|
|
1180
|
+
"""
|
|
1181
|
+
Alias for find with restricted set of parameters
|
|
1182
|
+
|
|
1183
|
+
See also
|
|
1184
|
+
--------
|
|
1185
|
+
find
|
|
1186
|
+
"""
|
|
1187
|
+
return self.find(field_name, default_value, **named_keys)
|
|
1188
|
+
|
|
1189
|
+
def find_or_throw(self, field_name, *key_values) -> Operation:
|
|
1190
|
+
"""
|
|
1191
|
+
Alias for find with restricted set of parameters
|
|
1192
|
+
|
|
1193
|
+
See also
|
|
1194
|
+
--------
|
|
1195
|
+
find
|
|
1196
|
+
"""
|
|
1197
|
+
return self.find(field_name, None, *key_values, throw=True)
|
|
1198
|
+
|
|
1199
|
+
def erase(self, *key_values: List[Union[str, 'TickSequenceTick']], **named_keys: Dict) -> Operation:
|
|
1200
|
+
"""
|
|
1201
|
+
Erase tick(s) from tick set with keys or through
|
|
1202
|
+
:py:class:`TickSequenceTick <onetick.py.core._internal._state_objects.TickSequenceTick>`
|
|
1203
|
+
|
|
1204
|
+
Parameters
|
|
1205
|
+
----------
|
|
1206
|
+
key_values: list, optional
|
|
1207
|
+
List of tick set's keys values that will be used to find tick.
|
|
1208
|
+
If single TickSequenceTick is used, only it is removed.
|
|
1209
|
+
If two TickSequenceTicks are used, the whole (excluding right boundary) interval between them is removed.
|
|
1210
|
+
named_keys: dict, optional
|
|
1211
|
+
Dict of tick set's keys named and values that will be used to find tick.
|
|
1212
|
+
|
|
1213
|
+
Returns
|
|
1214
|
+
-------
|
|
1215
|
+
:py:class:`~onetick.py.Operation` that evaluates to boolean.
|
|
1216
|
+
(1 if tick was erased, and 0 if tick was not in tick set).
|
|
1217
|
+
|
|
1218
|
+
Examples
|
|
1219
|
+
--------
|
|
1220
|
+
Can be used in per-tick script:
|
|
1221
|
+
|
|
1222
|
+
>>> def fun(tick):
|
|
1223
|
+
... tick['B'] = tick.state_vars['SET'].erase(1)
|
|
1224
|
+
... tick.state_vars['SET'].erase(A=1)
|
|
1225
|
+
>>> data = otp.Tick(A=1)
|
|
1226
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
1227
|
+
>>> data = data.script(fun)
|
|
1228
|
+
|
|
1229
|
+
Can be used in source columns operations:
|
|
1230
|
+
|
|
1231
|
+
>>> data = otp.Tick(A=1)
|
|
1232
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'B', otp.eval(otp.Ticks(B=[1, 2, 3])))
|
|
1233
|
+
>>> data['C1'] = data.state_vars['SET'].erase(1)
|
|
1234
|
+
>>> data['C2'] = data.state_vars['SET'].erase(1)
|
|
1235
|
+
|
|
1236
|
+
>>> otp.run(data)
|
|
1237
|
+
Time A C1 C2
|
|
1238
|
+
0 2003-12-01 1 1 0
|
|
1239
|
+
|
|
1240
|
+
>>> data = data.state_vars['SET'].dump()
|
|
1241
|
+
>>> otp.run(data)
|
|
1242
|
+
Time B
|
|
1243
|
+
0 2003-12-01 2
|
|
1244
|
+
1 2003-12-01 3
|
|
1245
|
+
|
|
1246
|
+
Can be used with :py:meth:`~onetick.py.Source.execute` method
|
|
1247
|
+
to do erasing without returning result as :py:class:`~onetick.py.Operation`:
|
|
1248
|
+
|
|
1249
|
+
>>> data = otp.Tick(A=1)
|
|
1250
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'B', otp.eval(otp.Tick(B=2)))
|
|
1251
|
+
>>> data = data.execute(data.state_vars['SET'].erase(B=2))
|
|
1252
|
+
|
|
1253
|
+
Example with single TickSetTick:
|
|
1254
|
+
|
|
1255
|
+
>>> def fun(tick):
|
|
1256
|
+
... tick['RES'] = 0
|
|
1257
|
+
... for tt in tick.state_vars['set']:
|
|
1258
|
+
... if tick.state_vars['set'].erase(tt):
|
|
1259
|
+
... tick['RES'] += 1
|
|
1260
|
+
... tick['LEN'] = tick.state_vars['set'].get_size()
|
|
1261
|
+
>>> data = otp.Tick(X=1)
|
|
1262
|
+
>>> data.state_vars['set'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Ticks(A=[1, 2, 3])))
|
|
1263
|
+
>>> data = data.script(fun)
|
|
1264
|
+
>>> otp.run(data)
|
|
1265
|
+
Time X RES LEN
|
|
1266
|
+
0 2003-12-01 1 3 0
|
|
1267
|
+
|
|
1268
|
+
Example with two TickSetTicks:
|
|
1269
|
+
|
|
1270
|
+
>>> def fun(tick):
|
|
1271
|
+
... t1 = otp.tick_set_tick()
|
|
1272
|
+
... t2 = otp.tick_set_tick()
|
|
1273
|
+
... if tick.state_vars['set'].find(t1, 2) + tick.state_vars['set'].find(t2, 4) == 2:
|
|
1274
|
+
... tick.state_vars['set'].erase(t1, t2)
|
|
1275
|
+
... for tt in tick.state_vars['set']:
|
|
1276
|
+
... tick['RES'] += tt.get_value('A')
|
|
1277
|
+
>>> data = otp.Tick(X=1, RES=0)
|
|
1278
|
+
>>> data.state_vars['set'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Ticks(A=[1, 2, 3, 4])))
|
|
1279
|
+
>>> data = data.script(fun)
|
|
1280
|
+
>>> otp.run(data)
|
|
1281
|
+
Time X RES
|
|
1282
|
+
0 2003-12-01 1 5
|
|
1283
|
+
"""
|
|
1284
|
+
if not named_keys and len(key_values) == 1 and isinstance(key_values[0], TickSequenceTick):
|
|
1285
|
+
return self._erase_by_singe_iterator(key_values[0])
|
|
1286
|
+
if not named_keys and len(key_values) == 2 and isinstance(key_values[0], TickSequenceTick):
|
|
1287
|
+
return self._erase_by_two_iterators(key_values[0], key_values[1])
|
|
1288
|
+
|
|
1289
|
+
return self._erase_by_keys(*key_values, **named_keys)
|
|
1290
|
+
|
|
1291
|
+
def _erase_by_keys(self, *key_values, **named_keys) -> Operation:
|
|
1292
|
+
if key_values and isinstance(key_values[0], TickSequenceTick):
|
|
1293
|
+
args = [self, *map(value2str, key_values)]
|
|
1294
|
+
else:
|
|
1295
|
+
args = [self, *map(value2str, self._parse_keys(key_values, named_keys))]
|
|
1296
|
+
str_args = ','.join(map(str, args))
|
|
1297
|
+
func = 'ERASE_FROM_TICK_SET'
|
|
1298
|
+
if named_keys:
|
|
1299
|
+
func = 'ERASE_FROM_TICK_SET_BY_NAMED_KEYS'
|
|
1300
|
+
op_str = f'{func}({str_args})'
|
|
1301
|
+
return Operation(dtype=int, op_str=op_str)
|
|
1302
|
+
|
|
1303
|
+
def _erase_by_singe_iterator(self, tick_sequence_tick: 'TickSequenceTick') -> Optional[Operation]:
|
|
1304
|
+
args = [self, value2str(tick_sequence_tick)]
|
|
1305
|
+
str_args = ','.join(map(str, args))
|
|
1306
|
+
func = 'ERASE_FROM_TICK_SET'
|
|
1307
|
+
return Operation(dtype=int, op_str=f'{func}({str_args})')
|
|
1308
|
+
|
|
1309
|
+
def _erase_by_two_iterators(
|
|
1310
|
+
self,
|
|
1311
|
+
tick_sequence_tick1: 'TickSequenceTick',
|
|
1312
|
+
tick_sequence_tick2: 'TickSequenceTick',
|
|
1313
|
+
) -> Optional[Operation]:
|
|
1314
|
+
args = [self, value2str(tick_sequence_tick1), value2str(tick_sequence_tick2)]
|
|
1315
|
+
str_args = ','.join(map(str, args))
|
|
1316
|
+
func = 'ERASE_FROM_TICK_SET'
|
|
1317
|
+
return Operation(dtype=int, op_str=f'{func}({str_args})')
|
|
1318
|
+
|
|
1319
|
+
@inplace_operation
|
|
1320
|
+
def erase_by_named_keys(self, **named_keys) -> Optional[Operation]:
|
|
1321
|
+
"""
|
|
1322
|
+
Alias for erase with restricted set of parameters
|
|
1323
|
+
|
|
1324
|
+
See also
|
|
1325
|
+
--------
|
|
1326
|
+
erase
|
|
1327
|
+
"""
|
|
1328
|
+
return self.erase(**named_keys)
|
|
1329
|
+
|
|
1330
|
+
@inplace_operation
|
|
1331
|
+
def clear(self):
|
|
1332
|
+
"""
|
|
1333
|
+
Clear tick set.
|
|
1334
|
+
Can be used in per-tick script and on Source directly.
|
|
1335
|
+
|
|
1336
|
+
inplace: bool
|
|
1337
|
+
If ``True`` current source will be modified else modified copy will be returned.
|
|
1338
|
+
Makes sense only if used not in per-tick script.
|
|
1339
|
+
|
|
1340
|
+
Returns
|
|
1341
|
+
-------
|
|
1342
|
+
if ``inplace`` is False and method is not used in per-tick script
|
|
1343
|
+
then returns :py:class:`~onetick.py.Source` copy.
|
|
1344
|
+
|
|
1345
|
+
Examples
|
|
1346
|
+
--------
|
|
1347
|
+
Can be used in per-tick script:
|
|
1348
|
+
|
|
1349
|
+
>>> def fun(tick):
|
|
1350
|
+
... tick.state_vars['SET'].clear()
|
|
1351
|
+
>>> data = otp.Tick(A=1)
|
|
1352
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
1353
|
+
>>> data = data.script(fun)
|
|
1354
|
+
|
|
1355
|
+
Can be used in source columns operations:
|
|
1356
|
+
|
|
1357
|
+
>>> data = otp.Tick(A=1)
|
|
1358
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1)))
|
|
1359
|
+
>>> data = data.state_vars['SET'].clear()
|
|
1360
|
+
|
|
1361
|
+
>>> data = data.state_vars['SET'].dump()
|
|
1362
|
+
>>> otp.run(data)
|
|
1363
|
+
Empty DataFrame
|
|
1364
|
+
Columns: [A, Time]
|
|
1365
|
+
Index: []
|
|
1366
|
+
"""
|
|
1367
|
+
if self._is_used_in_per_tick_script:
|
|
1368
|
+
return f'CLEAR_TICK_SET({self});'
|
|
1369
|
+
self._owner.sink(
|
|
1370
|
+
otq.ExecuteExpressions(f'CLEAR_TICK_SET({self})')
|
|
1371
|
+
)
|
|
1372
|
+
return self._owner
|
|
1373
|
+
|
|
1374
|
+
def insert(self, tick_object=None) -> Optional[Operation]:
|
|
1375
|
+
"""
|
|
1376
|
+
Insert tick into tick set.
|
|
1377
|
+
|
|
1378
|
+
Parameters
|
|
1379
|
+
----------
|
|
1380
|
+
tick_object
|
|
1381
|
+
Can be set only in per-tick script.
|
|
1382
|
+
If not set the current tick is inserted into tick set.
|
|
1383
|
+
|
|
1384
|
+
Returns
|
|
1385
|
+
-------
|
|
1386
|
+
:py:class:`~onetick.py.Operation` that evaluates to boolean.
|
|
1387
|
+
(1 if tick was inserted, and 0 if tick was already presented in tick set).
|
|
1388
|
+
|
|
1389
|
+
Examples
|
|
1390
|
+
--------
|
|
1391
|
+
Can be used in per-tick script:
|
|
1392
|
+
|
|
1393
|
+
>>> def fun(tick):
|
|
1394
|
+
... tick.state_vars['SET'].insert(tick)
|
|
1395
|
+
... tick.state_vars['SET'].insert()
|
|
1396
|
+
>>> data = otp.Tick(A=1)
|
|
1397
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
1398
|
+
>>> data = data.script(fun)
|
|
1399
|
+
|
|
1400
|
+
Can be used in source columns operations:
|
|
1401
|
+
|
|
1402
|
+
>>> data = otp.Tick(A=1)
|
|
1403
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
1404
|
+
>>> data['B'] = data.state_vars['SET'].insert()
|
|
1405
|
+
>>> data['C'] = data.state_vars['SET'].insert()
|
|
1406
|
+
|
|
1407
|
+
>>> otp.run(data)
|
|
1408
|
+
Time A B C
|
|
1409
|
+
0 2003-12-01 1 1 0
|
|
1410
|
+
|
|
1411
|
+
>>> data = data.state_vars['SET'].dump()
|
|
1412
|
+
>>> otp.run(data)
|
|
1413
|
+
Time A
|
|
1414
|
+
0 2003-12-01 1
|
|
1415
|
+
|
|
1416
|
+
Can be used with :py:meth:`~onetick.py.Source.execute` method
|
|
1417
|
+
to do insertion without returning result as :py:class:`~onetick.py.Operation`:
|
|
1418
|
+
|
|
1419
|
+
>>> data = otp.Tick(A=1)
|
|
1420
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
1421
|
+
>>> data = data.execute(data.state_vars['SET'].insert())
|
|
1422
|
+
|
|
1423
|
+
Inserting current tick in script by passing it to `insert` method apply the changes made to tick:
|
|
1424
|
+
|
|
1425
|
+
>>> def fun(tick):
|
|
1426
|
+
... tick['A'] = 2
|
|
1427
|
+
... tick.state_vars['SET'].insert(tick)
|
|
1428
|
+
>>> data = otp.Tick(A=1)
|
|
1429
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('latest', 'A')
|
|
1430
|
+
>>> data = data.script(fun)
|
|
1431
|
+
>>> data = data.state_vars['SET'].dump()
|
|
1432
|
+
>>> otp.run(data)
|
|
1433
|
+
Time A
|
|
1434
|
+
0 2003-12-01 2
|
|
1435
|
+
|
|
1436
|
+
If you want to insert current tick without applied changes use `input` attribute:
|
|
1437
|
+
|
|
1438
|
+
>>> def fun(tick):
|
|
1439
|
+
... tick['A'] = 2
|
|
1440
|
+
... tick.state_vars['SET'].insert(tick.input)
|
|
1441
|
+
>>> data = otp.Tick(A=1)
|
|
1442
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('latest', 'A')
|
|
1443
|
+
>>> data = data.script(fun)
|
|
1444
|
+
>>> data = data.state_vars['SET'].dump()
|
|
1445
|
+
>>> otp.run(data)
|
|
1446
|
+
Time A
|
|
1447
|
+
0 2003-12-01 1
|
|
1448
|
+
"""
|
|
1449
|
+
args = [self]
|
|
1450
|
+
if tick_object is not None:
|
|
1451
|
+
args.append(tick_object)
|
|
1452
|
+
str_args = ','.join(map(str, args))
|
|
1453
|
+
op_str = f'INSERT_TO_TICK_SET({str_args})'
|
|
1454
|
+
if not self._is_used_in_per_tick_script and tick_object is not None:
|
|
1455
|
+
raise ValueError("Parameter 'tick_object' can be used only in per-tick script")
|
|
1456
|
+
return Operation(dtype=int, op_str=op_str)
|
|
1457
|
+
|
|
1458
|
+
def get_size(self) -> Operation:
|
|
1459
|
+
"""
|
|
1460
|
+
Get size of the tick set.
|
|
1461
|
+
Can be used in per-tick script or in Source operations directly.
|
|
1462
|
+
|
|
1463
|
+
Returns
|
|
1464
|
+
-------
|
|
1465
|
+
:py:class:`~onetick.py.Operation` that evaluates to float value.
|
|
1466
|
+
|
|
1467
|
+
Examples
|
|
1468
|
+
--------
|
|
1469
|
+
Can be used in per-tick script:
|
|
1470
|
+
|
|
1471
|
+
>>> def fun(tick):
|
|
1472
|
+
... tick['B'] = tick.state_vars['SET'].get_size()
|
|
1473
|
+
>>> data = otp.Tick(A=1)
|
|
1474
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
1475
|
+
>>> data = data.script(fun)
|
|
1476
|
+
|
|
1477
|
+
Can be used in source columns operations:
|
|
1478
|
+
|
|
1479
|
+
>>> data = otp.Tick(A=1)
|
|
1480
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
1481
|
+
>>> data['B'] = data.state_vars['SET'].get_size()
|
|
1482
|
+
|
|
1483
|
+
>>> otp.run(data)
|
|
1484
|
+
Time A B
|
|
1485
|
+
0 2003-12-01 1 0
|
|
1486
|
+
"""
|
|
1487
|
+
return Operation(dtype=int, op_str=f'GET_SIZE_FOR_TICK_SET({self})')
|
|
1488
|
+
|
|
1489
|
+
def size(self) -> Operation:
|
|
1490
|
+
"""
|
|
1491
|
+
See also
|
|
1492
|
+
--------
|
|
1493
|
+
get_size
|
|
1494
|
+
"""
|
|
1495
|
+
return self.get_size()
|
|
1496
|
+
|
|
1497
|
+
def present(self, *key_values) -> Operation:
|
|
1498
|
+
"""
|
|
1499
|
+
Check if tick with these key values is present in tick set.
|
|
1500
|
+
|
|
1501
|
+
Returns
|
|
1502
|
+
-------
|
|
1503
|
+
:py:class:`~onetick.py.Operation` that evaluates to boolean (float value 1 or 0).
|
|
1504
|
+
|
|
1505
|
+
Examples
|
|
1506
|
+
--------
|
|
1507
|
+
Can be used in per-tick script:
|
|
1508
|
+
|
|
1509
|
+
>>> def fun(tick):
|
|
1510
|
+
... tick['B'] = tick.state_vars['SET'].present(1)
|
|
1511
|
+
>>> data = otp.Tick(A=1)
|
|
1512
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
1513
|
+
>>> data = data.script(fun)
|
|
1514
|
+
|
|
1515
|
+
Can be used in source columns operations:
|
|
1516
|
+
|
|
1517
|
+
>>> data = otp.Tick(A=1)
|
|
1518
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A', otp.eval(otp.Tick(A=1)))
|
|
1519
|
+
>>> data['B'] = data.state_vars['SET'].present(1)
|
|
1520
|
+
>>> data['C'] = data.state_vars['SET'].present(2)
|
|
1521
|
+
|
|
1522
|
+
>>> otp.run(data)
|
|
1523
|
+
Time A B C
|
|
1524
|
+
0 2003-12-01 1 1 0
|
|
1525
|
+
"""
|
|
1526
|
+
args = [self, *map(value2str, self._parse_keys(key_values))]
|
|
1527
|
+
str_args = ','.join(map(str, args))
|
|
1528
|
+
return Operation(dtype=int, op_str=f'PRESENT_IN_SET({str_args})')
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
class TickSetUnordered(TickSet):
|
|
1532
|
+
"""
|
|
1533
|
+
Represents an unordered tick set.
|
|
1534
|
+
This class should only be created with :py:func:`onetick.py.state.tick_set_unordered` function
|
|
1535
|
+
and should be added to the :py:meth:`onetick.py.Source.state_vars` dictionary
|
|
1536
|
+
of the :py:class:`onetick.py.Source` and can be accessed only via this dictionary.
|
|
1537
|
+
|
|
1538
|
+
Examples
|
|
1539
|
+
--------
|
|
1540
|
+
>>> data = otp.Tick(A=1)
|
|
1541
|
+
>>> data.state_vars['SET'] = otp.state.tick_set_unordered('oldest', 'A', max_distinct_keys=10)
|
|
1542
|
+
>>> data = data.state_vars['SET'].dump()
|
|
1543
|
+
|
|
1544
|
+
See also
|
|
1545
|
+
--------
|
|
1546
|
+
:py:class:`TickSequenceTick <onetick.py.core._internal._state_objects.TickSequenceTick>`
|
|
1547
|
+
"""
|
|
1548
|
+
|
|
1549
|
+
def __init__(self, *args, max_distinct_keys=-1, **kwargs):
|
|
1550
|
+
self.max_distinct_keys = max_distinct_keys
|
|
1551
|
+
super().__init__(*args, **kwargs)
|
|
1552
|
+
|
|
1553
|
+
def copy(self, *args, **kwargs):
|
|
1554
|
+
kwargs.setdefault('max_distinct_keys', self.max_distinct_keys)
|
|
1555
|
+
return super().copy(*args, **kwargs)
|
|
1556
|
+
|
|
1557
|
+
def _definition(self) -> str:
|
|
1558
|
+
args = ','.join(map(str, [self.insertion_policy, self.max_distinct_keys, *self.key_fields]))
|
|
1559
|
+
return f'TICK_SET_UNORDERED({args})'
|
|
1560
|
+
|
|
1561
|
+
|
|
1562
|
+
class TickDeque(TickList):
|
|
1563
|
+
"""
|
|
1564
|
+
Represents a tick deque.
|
|
1565
|
+
This class should only be created with :py:func:`onetick.py.state.tick_deque` function
|
|
1566
|
+
and should be added to the :py:meth:`onetick.py.Source.state_vars` dictionary
|
|
1567
|
+
of the :py:class:`onetick.py.Source` and can be accessed only via this dictionary.
|
|
1568
|
+
|
|
1569
|
+
Examples
|
|
1570
|
+
--------
|
|
1571
|
+
>>> data = otp.Tick(A=1)
|
|
1572
|
+
>>> data.state_vars['DEQUE'] = otp.state.tick_deque()
|
|
1573
|
+
>>> data = data.state_vars['DEQUE'].dump()
|
|
1574
|
+
|
|
1575
|
+
See also
|
|
1576
|
+
--------
|
|
1577
|
+
:py:class:`TickSequenceTick <onetick.py.core._internal._state_objects.TickSequenceTick>`
|
|
1578
|
+
"""
|
|
1579
|
+
|
|
1580
|
+
def _definition(self) -> str:
|
|
1581
|
+
return 'TICK_DEQUE'
|
|
1582
|
+
|
|
1583
|
+
@property
|
|
1584
|
+
def _tick_class(self):
|
|
1585
|
+
return _TickDequeTick
|
|
1586
|
+
|
|
1587
|
+
@property
|
|
1588
|
+
def _dump_ep(self):
|
|
1589
|
+
return otq.DumpTickDeque
|
|
1590
|
+
|
|
1591
|
+
def pop_back(self):
|
|
1592
|
+
"""
|
|
1593
|
+
Pop element from back side of deque.
|
|
1594
|
+
Can be used only in per-tick script.
|
|
1595
|
+
For now returned value can't be assigned to local variable.
|
|
1596
|
+
|
|
1597
|
+
Examples
|
|
1598
|
+
--------
|
|
1599
|
+
>>> def fun(tick):
|
|
1600
|
+
... tick.state_vars['DEQUE'].pop_back()
|
|
1601
|
+
>>> data = otp.Tick(A=1)
|
|
1602
|
+
>>> data.state_vars['DEQUE'] = otp.state.tick_deque()
|
|
1603
|
+
>>> data = data.script(fun)
|
|
1604
|
+
"""
|
|
1605
|
+
if not self._is_used_in_per_tick_script:
|
|
1606
|
+
raise ValueError('method .pop_back() can only be used in per-tick script')
|
|
1607
|
+
return f'{self}.POP_BACK();'
|
|
1608
|
+
|
|
1609
|
+
def pop_front(self):
|
|
1610
|
+
"""
|
|
1611
|
+
Pop element from front side of deque.
|
|
1612
|
+
Can be used only in per-tick script.
|
|
1613
|
+
For now returned value can't be assigned to local variable.
|
|
1614
|
+
|
|
1615
|
+
Examples
|
|
1616
|
+
--------
|
|
1617
|
+
>>> def fun(tick):
|
|
1618
|
+
... tick.state_vars['DEQUE'].pop_front()
|
|
1619
|
+
>>> data = otp.Tick(A=1)
|
|
1620
|
+
>>> data.state_vars['DEQUE'] = otp.state.tick_deque()
|
|
1621
|
+
>>> data = data.script(fun)
|
|
1622
|
+
"""
|
|
1623
|
+
if not self._is_used_in_per_tick_script:
|
|
1624
|
+
raise ValueError('method .pop_front() can only be used in per-tick script')
|
|
1625
|
+
return f'{self}.POP_FRONT();'
|
|
1626
|
+
|
|
1627
|
+
def get_tick(self, index, tick_object):
|
|
1628
|
+
"""
|
|
1629
|
+
Get tick with this ``index`` from tick set into ``tick_object``.
|
|
1630
|
+
Can be used only in per-tick script.
|
|
1631
|
+
"""
|
|
1632
|
+
if not self._is_used_in_per_tick_script:
|
|
1633
|
+
raise ValueError('method .get_tick() can only be used in per-tick script')
|
|
1634
|
+
if getattr(tick_object, '_owner', None) is None:
|
|
1635
|
+
tick_object._owner = self._owner
|
|
1636
|
+
return f'{self}.GET_TICK({index},{tick_object});'
|
|
1637
|
+
|
|
1638
|
+
@inplace_operation
|
|
1639
|
+
def sort(self, field_name, field_type=None):
|
|
1640
|
+
raise NotImplementedError('sort() function is currently not implemented for TickDeque')
|
|
1641
|
+
|
|
1642
|
+
|
|
1643
|
+
class _TickSequenceTickBase(ABC):
|
|
1644
|
+
def __init__(self, name, **_):
|
|
1645
|
+
self._name = name
|
|
1646
|
+
|
|
1647
|
+
def __str__(self):
|
|
1648
|
+
if self._name is None:
|
|
1649
|
+
raise ValueError('Trying to use uninitialized tick variable')
|
|
1650
|
+
return f'LOCAL::{self._name}'
|
|
1651
|
+
|
|
1652
|
+
@property
|
|
1653
|
+
def _definition(self):
|
|
1654
|
+
raise NotImplementedError
|
|
1655
|
+
|
|
1656
|
+
@property
|
|
1657
|
+
def schema(self) -> dict:
|
|
1658
|
+
raise NotImplementedError
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
class _TickSequenceTickMixin(_TickSequenceTickBase, ABC):
|
|
1662
|
+
def get_long_value(self, field_name: Union[str, Operation], dtype=int, check_schema=True) -> Operation:
|
|
1663
|
+
"""
|
|
1664
|
+
Get value of the long ``field_name`` of the tick.
|
|
1665
|
+
|
|
1666
|
+
Parameters
|
|
1667
|
+
----------
|
|
1668
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1669
|
+
String field name or operation which returns field name.
|
|
1670
|
+
dtype: type
|
|
1671
|
+
Type to set for output operation. Should be set if value is integer's subclass, e.g. short or byte.
|
|
1672
|
+
Default: int
|
|
1673
|
+
check_schema: bool
|
|
1674
|
+
Check that ``field_name`` exists in tick's schema and its type is int.
|
|
1675
|
+
In some cases you may want to disable this behaviour, for example, when tick sequence
|
|
1676
|
+
is updated dynamically in different branches of per-tick script. In this case onetick-py
|
|
1677
|
+
can't deduce if ``field_name`` exists in schema or has the same type.
|
|
1678
|
+
|
|
1679
|
+
Examples
|
|
1680
|
+
--------
|
|
1681
|
+
>>> def another_query():
|
|
1682
|
+
... return otp.Ticks(X=[1, 2, 3])
|
|
1683
|
+
>>> def fun(tick):
|
|
1684
|
+
... tick['SUM'] = 0
|
|
1685
|
+
... for t in tick.state_vars['LIST']:
|
|
1686
|
+
... tick['SUM'] += t.get_long_value('X')
|
|
1687
|
+
>>> data = otp.Tick(A=1)
|
|
1688
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1689
|
+
>>> data = data.script(fun)
|
|
1690
|
+
|
|
1691
|
+
>>> otp.run(data)
|
|
1692
|
+
Time A SUM
|
|
1693
|
+
0 2003-12-01 1 6
|
|
1694
|
+
"""
|
|
1695
|
+
if not issubclass(dtype, int):
|
|
1696
|
+
raise ValueError(f'`dtype` parameter should be one on integer`s subclasses but {dtype} was passed')
|
|
1697
|
+
if check_schema:
|
|
1698
|
+
check_field_name_in_schema(field_name, dtype=dtype, schema=self.schema)
|
|
1699
|
+
return Operation(dtype=dtype,
|
|
1700
|
+
op_str=f'{self}.GET_LONG_VALUE({value2str(field_name)})')
|
|
1701
|
+
|
|
1702
|
+
def set_long_value(self, field_name: Union[str, Operation], value: Union[int, Operation], check_schema=True):
|
|
1703
|
+
"""
|
|
1704
|
+
Set ``value`` of the long ``field_name`` of the tick.
|
|
1705
|
+
|
|
1706
|
+
Parameters
|
|
1707
|
+
----------
|
|
1708
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1709
|
+
String field name or operation which returns field name.
|
|
1710
|
+
value: int, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1711
|
+
Long value to set or operation which return such value.
|
|
1712
|
+
check_schema: bool
|
|
1713
|
+
Check that ``field_name`` exists in tick's schema and its type is the same as the type of ``value``.
|
|
1714
|
+
In some cases you may want to disable this behaviour, for example, when tick sequence
|
|
1715
|
+
is updated dynamically in different branches of per-tick script. In this case onetick-py
|
|
1716
|
+
can't deduce if ``field_name`` exists in schema or has the same type.
|
|
1717
|
+
|
|
1718
|
+
Examples
|
|
1719
|
+
--------
|
|
1720
|
+
>>> def another_query():
|
|
1721
|
+
... return otp.Ticks(A=[1, 2, 3], X=[1, 2, 3])
|
|
1722
|
+
>>> def fun(tick):
|
|
1723
|
+
... for t in tick.state_vars['LIST']:
|
|
1724
|
+
... t.set_long_value('X', 1)
|
|
1725
|
+
>>> data = otp.Tick(A=1)
|
|
1726
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1727
|
+
>>> data = data.script(fun)
|
|
1728
|
+
|
|
1729
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
1730
|
+
>>> otp.run(data)
|
|
1731
|
+
Time A X
|
|
1732
|
+
0 2003-12-01 00:00:00.000 1 1
|
|
1733
|
+
1 2003-12-01 00:00:00.001 2 1
|
|
1734
|
+
2 2003-12-01 00:00:00.002 3 1
|
|
1735
|
+
"""
|
|
1736
|
+
if check_schema:
|
|
1737
|
+
check_field_name_in_schema(field_name, int, value, schema=self.schema)
|
|
1738
|
+
return f'{self}.SET_LONG_VALUE({value2str(field_name)}, {value2str(value)});'
|
|
1739
|
+
|
|
1740
|
+
def get_double_value(self, field_name: Union[str, Operation], check_schema=True) -> Operation:
|
|
1741
|
+
"""
|
|
1742
|
+
Get value of the double ``field_name`` of the tick.
|
|
1743
|
+
|
|
1744
|
+
Parameters
|
|
1745
|
+
----------
|
|
1746
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1747
|
+
String field name or operation which returns field name.
|
|
1748
|
+
check_schema: bool
|
|
1749
|
+
Check that ``field_name`` exists in tick's schema and its type is float.
|
|
1750
|
+
In some cases you may want to disable this behaviour, for example, when tick sequence
|
|
1751
|
+
is updated dynamically in different branches of per-tick script. In this case onetick-py
|
|
1752
|
+
can't deduce if ``field_name`` exists in schema or has the same type.
|
|
1753
|
+
|
|
1754
|
+
Examples
|
|
1755
|
+
--------
|
|
1756
|
+
>>> def another_query():
|
|
1757
|
+
... return otp.Ticks(X=[1.1, 2.2, 3.3])
|
|
1758
|
+
>>> def fun(tick):
|
|
1759
|
+
... tick['SUM'] = 0
|
|
1760
|
+
... for t in tick.state_vars['LIST']:
|
|
1761
|
+
... tick['SUM'] += t.get_double_value('X')
|
|
1762
|
+
>>> data = otp.Tick(A=1)
|
|
1763
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1764
|
+
>>> data = data.script(fun)
|
|
1765
|
+
|
|
1766
|
+
>>> otp.run(data)
|
|
1767
|
+
Time A SUM
|
|
1768
|
+
0 2003-12-01 1 6.6
|
|
1769
|
+
"""
|
|
1770
|
+
if check_schema:
|
|
1771
|
+
check_field_name_in_schema(field_name, dtype=float, schema=self.schema)
|
|
1772
|
+
return Operation(dtype=float,
|
|
1773
|
+
op_str=f'{self}.GET_DOUBLE_VALUE({value2str(field_name)})')
|
|
1774
|
+
|
|
1775
|
+
def set_double_value(self, field_name: Union[str, Operation], value: Union[float, Operation], check_schema=True):
|
|
1776
|
+
"""
|
|
1777
|
+
Set ``value`` of the double ``field_name`` of the tick.
|
|
1778
|
+
|
|
1779
|
+
Parameters
|
|
1780
|
+
----------
|
|
1781
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1782
|
+
String field name or operation which returns field name.
|
|
1783
|
+
value: float, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1784
|
+
Double value to set or operation which return such value.
|
|
1785
|
+
check_schema: bool
|
|
1786
|
+
Check that ``field_name`` exists in tick's schema and its type is the same as the type of ``value``.
|
|
1787
|
+
In some cases you may want to disable this behaviour, for example, when tick sequence
|
|
1788
|
+
is updated dynamically in different branches of per-tick script. In this case onetick-py
|
|
1789
|
+
can't deduce if ``field_name`` exists in schema or has the same type.
|
|
1790
|
+
|
|
1791
|
+
Examples
|
|
1792
|
+
--------
|
|
1793
|
+
>>> def another_query():
|
|
1794
|
+
... return otp.Ticks(A=[1, 2, 3], X=[1.1, 2.2, 3.3])
|
|
1795
|
+
>>> def fun(tick):
|
|
1796
|
+
... for t in tick.state_vars['LIST']:
|
|
1797
|
+
... t.set_double_value('X', 1.1)
|
|
1798
|
+
>>> data = otp.Tick(A=1)
|
|
1799
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1800
|
+
>>> data = data.script(fun)
|
|
1801
|
+
|
|
1802
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
1803
|
+
>>> otp.run(data)
|
|
1804
|
+
Time A X
|
|
1805
|
+
0 2003-12-01 00:00:00.000 1 1.1
|
|
1806
|
+
1 2003-12-01 00:00:00.001 2 1.1
|
|
1807
|
+
2 2003-12-01 00:00:00.002 3 1.1
|
|
1808
|
+
"""
|
|
1809
|
+
if check_schema:
|
|
1810
|
+
check_field_name_in_schema(field_name, float, value, schema=self.schema)
|
|
1811
|
+
return f'{self}.SET_DOUBLE_VALUE({value2str(field_name)}, {value2str(value)});'
|
|
1812
|
+
|
|
1813
|
+
def get_string_value(self, field_name: Union[str, Operation], dtype=str, check_schema=True) -> Operation:
|
|
1814
|
+
"""
|
|
1815
|
+
Get value of the string ``field_name`` of the tick.
|
|
1816
|
+
|
|
1817
|
+
Parameters
|
|
1818
|
+
----------
|
|
1819
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1820
|
+
String field name or operation which returns field name.
|
|
1821
|
+
dtype: type
|
|
1822
|
+
Type to set for output operation. Should be set if value is string's subclass, e.g. otp.varstring.
|
|
1823
|
+
Default: str
|
|
1824
|
+
check_schema: bool
|
|
1825
|
+
Check that ``field_name`` exists in tick's schema and its type is str.
|
|
1826
|
+
In some cases you may want to disable this behaviour, for example, when tick sequence
|
|
1827
|
+
is updated dynamically in different branches of per-tick script. In this case onetick-py
|
|
1828
|
+
can't deduce if ``field_name`` exists in schema or has the same type.
|
|
1829
|
+
|
|
1830
|
+
Examples
|
|
1831
|
+
--------
|
|
1832
|
+
>>> def another_query():
|
|
1833
|
+
... return otp.Ticks(X=['a', 'b', 'c'])
|
|
1834
|
+
>>> def fun(tick):
|
|
1835
|
+
... for t in tick.state_vars['LIST']:
|
|
1836
|
+
... tick['S'] = t.get_string_value('X')
|
|
1837
|
+
>>> data = otp.Tick(A=1)
|
|
1838
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1839
|
+
>>> data = data.script(fun)
|
|
1840
|
+
|
|
1841
|
+
>>> otp.run(data)
|
|
1842
|
+
Time A S
|
|
1843
|
+
0 2003-12-01 1 c
|
|
1844
|
+
"""
|
|
1845
|
+
if not issubclass(dtype, str):
|
|
1846
|
+
raise ValueError(f'`dtype` parameter should be one on string`s subclasses but {dtype} was passed')
|
|
1847
|
+
if check_schema:
|
|
1848
|
+
check_field_name_in_schema(field_name, dtype=dtype, schema=self.schema)
|
|
1849
|
+
return Operation(dtype=dtype,
|
|
1850
|
+
op_str=f'{self}.GET_STRING_VALUE({value2str(field_name)})')
|
|
1851
|
+
|
|
1852
|
+
def set_string_value(self, field_name: Union[str, Operation], value: Union[str, Operation], check_schema=True):
|
|
1853
|
+
"""
|
|
1854
|
+
Set ``value`` of the string ``field_name`` of the tick.
|
|
1855
|
+
|
|
1856
|
+
Parameters
|
|
1857
|
+
----------
|
|
1858
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1859
|
+
String field name or operation which returns field name.
|
|
1860
|
+
value: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1861
|
+
String value to set or operation which return such value.
|
|
1862
|
+
check_schema: bool
|
|
1863
|
+
Check that ``field_name`` exists in tick's schema and its type is the same as the type of ``value``.
|
|
1864
|
+
In some cases you may want to disable this behaviour, for example, when tick sequence
|
|
1865
|
+
is updated dynamically in different branches of per-tick script. In this case onetick-py
|
|
1866
|
+
can't deduce if ``field_name`` exists in schema or has the same type.
|
|
1867
|
+
|
|
1868
|
+
Examples
|
|
1869
|
+
--------
|
|
1870
|
+
>>> def another_query():
|
|
1871
|
+
... return otp.Ticks(A=[1, 2, 3], X=['a', 'b', 'c'])
|
|
1872
|
+
>>> def fun(tick):
|
|
1873
|
+
... for t in tick.state_vars['LIST']:
|
|
1874
|
+
... t.set_string_value('X', 'a')
|
|
1875
|
+
>>> data = otp.Tick(A=1)
|
|
1876
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1877
|
+
>>> data = data.script(fun)
|
|
1878
|
+
|
|
1879
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
1880
|
+
>>> otp.run(data)
|
|
1881
|
+
Time A X
|
|
1882
|
+
0 2003-12-01 00:00:00.000 1 a
|
|
1883
|
+
1 2003-12-01 00:00:00.001 2 a
|
|
1884
|
+
2 2003-12-01 00:00:00.002 3 a
|
|
1885
|
+
"""
|
|
1886
|
+
if check_schema:
|
|
1887
|
+
check_field_name_in_schema(field_name, str, value, schema=self.schema)
|
|
1888
|
+
return f'{self}.SET_STRING_VALUE({value2str(field_name)}, {value2str(value)});'
|
|
1889
|
+
|
|
1890
|
+
def get_datetime_value(self, field_name: Union[str, Operation], check_schema=True) -> Operation:
|
|
1891
|
+
"""
|
|
1892
|
+
Get value of the datetime ``field_name`` of the tick.
|
|
1893
|
+
|
|
1894
|
+
Parameters
|
|
1895
|
+
----------
|
|
1896
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1897
|
+
String field name or operation which returns field name.
|
|
1898
|
+
check_schema: bool
|
|
1899
|
+
Check that ``field_name`` exists in tick's schema and its type is datetime.
|
|
1900
|
+
In some cases you may want to disable this behaviour, for example, when tick sequence
|
|
1901
|
+
is updated dynamically in different branches of per-tick script. In this case onetick-py
|
|
1902
|
+
can't deduce if ``field_name`` exists in schema or has the same type.
|
|
1903
|
+
|
|
1904
|
+
Examples
|
|
1905
|
+
--------
|
|
1906
|
+
>>> def another_query():
|
|
1907
|
+
... t = otp.Ticks(X=['a', 'b', 'c'])
|
|
1908
|
+
... t['TS'] = t['TIMESTAMP'] + otp.Milli(1)
|
|
1909
|
+
... return t
|
|
1910
|
+
>>> def fun(tick):
|
|
1911
|
+
... for t in tick.state_vars['LIST']:
|
|
1912
|
+
... tick['TS'] = t.get_datetime_value('TS')
|
|
1913
|
+
>>> data = otp.Tick(A=1)
|
|
1914
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1915
|
+
>>> data = data.script(fun)
|
|
1916
|
+
|
|
1917
|
+
>>> otp.run(data)
|
|
1918
|
+
Time A TS
|
|
1919
|
+
0 2003-12-01 1 2003-12-01 00:00:00.003
|
|
1920
|
+
"""
|
|
1921
|
+
for i, dtype in enumerate((nsectime, msectime)):
|
|
1922
|
+
try:
|
|
1923
|
+
if check_schema:
|
|
1924
|
+
check_field_name_in_schema(field_name, dtype=dtype, schema=self.schema)
|
|
1925
|
+
break
|
|
1926
|
+
except ValueError:
|
|
1927
|
+
if i != 1:
|
|
1928
|
+
continue
|
|
1929
|
+
else:
|
|
1930
|
+
raise
|
|
1931
|
+
return Operation(dtype=dtype,
|
|
1932
|
+
op_str=f'{self}.GET_DATETIME_VALUE({value2str(field_name)})')
|
|
1933
|
+
|
|
1934
|
+
def set_datetime_value(self, field_name: Union[str, Operation], value: Union[int, Operation], check_schema=True):
|
|
1935
|
+
"""
|
|
1936
|
+
Set ``value`` of the datetime ``field_name`` of the tick.
|
|
1937
|
+
|
|
1938
|
+
Parameters
|
|
1939
|
+
----------
|
|
1940
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1941
|
+
String field name or operation which returns field name.
|
|
1942
|
+
value: int, float, str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
1943
|
+
Value to set or operation which return such value.
|
|
1944
|
+
check_schema: bool
|
|
1945
|
+
Check that ``field_name`` exists in tick's schema and its type is the same as the type of ``value``.
|
|
1946
|
+
In some cases you may want to disable this behaviour, for example, when tick sequence
|
|
1947
|
+
is updated dynamically in different branches of per-tick script. In this case onetick-py
|
|
1948
|
+
can't deduce if ``field_name`` exists in schema or has the same type.
|
|
1949
|
+
|
|
1950
|
+
Examples
|
|
1951
|
+
--------
|
|
1952
|
+
>>> def another_query():
|
|
1953
|
+
... t = otp.Ticks(X=['a', 'b', 'c'])
|
|
1954
|
+
... t['TS'] = t['TIMESTAMP'] + otp.Milli(1)
|
|
1955
|
+
... return t
|
|
1956
|
+
>>> def fun(tick):
|
|
1957
|
+
... for t in tick.state_vars['LIST']:
|
|
1958
|
+
... t.set_datetime_value('TS', otp.config['default_start_time'])
|
|
1959
|
+
>>> data = otp.Tick(A=1)
|
|
1960
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1961
|
+
>>> data = data.script(fun)
|
|
1962
|
+
|
|
1963
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
1964
|
+
>>> otp.run(data)
|
|
1965
|
+
Time X TS
|
|
1966
|
+
0 2003-12-01 00:00:00.000 a 2003-12-01
|
|
1967
|
+
1 2003-12-01 00:00:00.001 b 2003-12-01
|
|
1968
|
+
2 2003-12-01 00:00:00.002 c 2003-12-01
|
|
1969
|
+
"""
|
|
1970
|
+
if check_schema:
|
|
1971
|
+
check_field_name_in_schema(field_name, nsectime, value, schema=self.schema)
|
|
1972
|
+
return f'{self}.SET_DATETIME_VALUE({value2str(field_name)}, {value2str(value)});'
|
|
1973
|
+
|
|
1974
|
+
def get_value(self, field_name) -> Operation:
|
|
1975
|
+
"""
|
|
1976
|
+
Get value of the ``field_name`` of the tick.
|
|
1977
|
+
|
|
1978
|
+
Examples
|
|
1979
|
+
--------
|
|
1980
|
+
>>> def another_query():
|
|
1981
|
+
... return otp.Ticks(X=[1.1, 2.2, 3.3])
|
|
1982
|
+
>>> def fun(tick):
|
|
1983
|
+
... tick['SUM'] = 0
|
|
1984
|
+
... for t in tick.state_vars['LIST']:
|
|
1985
|
+
... tick['SUM'] += t.get_value('X')
|
|
1986
|
+
>>> data = otp.Tick(A=1)
|
|
1987
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
1988
|
+
>>> data = data.script(fun)
|
|
1989
|
+
|
|
1990
|
+
>>> otp.run(data)
|
|
1991
|
+
Time A SUM
|
|
1992
|
+
0 2003-12-01 1 6.6
|
|
1993
|
+
"""
|
|
1994
|
+
check_field_name_in_schema(field_name, schema=self.schema)
|
|
1995
|
+
dtype = self.schema[field_name]
|
|
1996
|
+
func_dict = {
|
|
1997
|
+
int: self.get_long_value,
|
|
1998
|
+
float: self.get_double_value,
|
|
1999
|
+
str: self.get_string_value,
|
|
2000
|
+
nsectime: self.get_datetime_value,
|
|
2001
|
+
msectime: self.get_datetime_value,
|
|
2002
|
+
}
|
|
2003
|
+
func = func_dict.get(dtype)
|
|
2004
|
+
if func is not None:
|
|
2005
|
+
return func(field_name) # type: ignore[operator]
|
|
2006
|
+
if issubclass(dtype, int):
|
|
2007
|
+
return func_dict[int](field_name, dtype=dtype) # type: ignore[operator]
|
|
2008
|
+
if (issubclass(dtype, str) and
|
|
2009
|
+
(dtype is not varstring or is_supported_varstring_in_get_string_value())):
|
|
2010
|
+
return func_dict[str](field_name, dtype=dtype) # type: ignore[operator]
|
|
2011
|
+
# decimal is unsupported
|
|
2012
|
+
raise TypeError(f'`{dtype}` is unsupported')
|
|
2013
|
+
|
|
2014
|
+
def set_value(self, field_name, value):
|
|
2015
|
+
"""
|
|
2016
|
+
Set ``value`` of the ``field_name`` of the tick.
|
|
2017
|
+
|
|
2018
|
+
Parameters
|
|
2019
|
+
----------
|
|
2020
|
+
field_name: str, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
2021
|
+
String field name or operation which returns field name.
|
|
2022
|
+
value: int, :py:class:`otp.Operation <onetick.py.core.column_operations.base.Operation>`
|
|
2023
|
+
Datetime value to set or operation which return such value.
|
|
2024
|
+
|
|
2025
|
+
Examples
|
|
2026
|
+
--------
|
|
2027
|
+
>>> def another_query():
|
|
2028
|
+
... return otp.Ticks(A=[1, 2, 3], X=[1.1, 2.2, 3.3])
|
|
2029
|
+
>>> def fun(tick):
|
|
2030
|
+
... for t in tick.state_vars['LIST']:
|
|
2031
|
+
... t.set_value('X', 0.0)
|
|
2032
|
+
>>> data = otp.Tick(A=1)
|
|
2033
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
2034
|
+
>>> data = data.script(fun)
|
|
2035
|
+
|
|
2036
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
2037
|
+
>>> otp.run(data)
|
|
2038
|
+
Time A X
|
|
2039
|
+
0 2003-12-01 00:00:00.000 1 0.0
|
|
2040
|
+
1 2003-12-01 00:00:00.001 2 0.0
|
|
2041
|
+
2 2003-12-01 00:00:00.002 3 0.0
|
|
2042
|
+
"""
|
|
2043
|
+
check_field_name_in_schema(field_name, value=value, schema=self.schema)
|
|
2044
|
+
dtype = self.schema[field_name]
|
|
2045
|
+
if issubclass(dtype, string):
|
|
2046
|
+
dtype = str
|
|
2047
|
+
return {
|
|
2048
|
+
int: self.set_long_value,
|
|
2049
|
+
float: self.set_double_value,
|
|
2050
|
+
str: self.set_string_value,
|
|
2051
|
+
nsectime: self.set_datetime_value,
|
|
2052
|
+
}[dtype](field_name, value)
|
|
2053
|
+
|
|
2054
|
+
def __getitem__(self, field_name):
|
|
2055
|
+
"""
|
|
2056
|
+
Get value of the ``field_name`` of the tick.
|
|
2057
|
+
|
|
2058
|
+
See also
|
|
2059
|
+
--------
|
|
2060
|
+
get_value
|
|
2061
|
+
|
|
2062
|
+
Examples
|
|
2063
|
+
--------
|
|
2064
|
+
>>> def another_query():
|
|
2065
|
+
... return otp.Ticks(X=[1.1, 2.2, 3.3])
|
|
2066
|
+
>>> def fun(tick):
|
|
2067
|
+
... tick['SUM'] = 0
|
|
2068
|
+
... for t in tick.state_vars['LIST']:
|
|
2069
|
+
... tick['SUM'] += t['X']
|
|
2070
|
+
>>> data = otp.Tick(A=1)
|
|
2071
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
2072
|
+
>>> data = data.script(fun)
|
|
2073
|
+
|
|
2074
|
+
>>> otp.run(data)
|
|
2075
|
+
Time A SUM
|
|
2076
|
+
0 2003-12-01 1 6.6
|
|
2077
|
+
"""
|
|
2078
|
+
return self.get_value(field_name)
|
|
2079
|
+
|
|
2080
|
+
def __setitem__(self, field_name, value):
|
|
2081
|
+
"""
|
|
2082
|
+
Set ``value`` of the ``field_name`` of the tick.
|
|
2083
|
+
|
|
2084
|
+
See also
|
|
2085
|
+
--------
|
|
2086
|
+
set_value
|
|
2087
|
+
|
|
2088
|
+
Examples
|
|
2089
|
+
--------
|
|
2090
|
+
>>> def another_query():
|
|
2091
|
+
... return otp.Ticks(A=[1, 2, 3], X=[1.1, 2.2, 3.3])
|
|
2092
|
+
>>> def fun(tick):
|
|
2093
|
+
... for t in tick.state_vars['LIST']:
|
|
2094
|
+
... t['X'] = 0.0
|
|
2095
|
+
>>> data = otp.Tick(A=1)
|
|
2096
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
2097
|
+
>>> data = data.script(fun)
|
|
2098
|
+
|
|
2099
|
+
>>> data = data.state_vars['LIST'].dump()
|
|
2100
|
+
>>> otp.run(data)
|
|
2101
|
+
Time A X
|
|
2102
|
+
0 2003-12-01 00:00:00.000 1 0.0
|
|
2103
|
+
1 2003-12-01 00:00:00.001 2 0.0
|
|
2104
|
+
2 2003-12-01 00:00:00.002 3 0.0
|
|
2105
|
+
"""
|
|
2106
|
+
return self.set_value(field_name, value)
|
|
2107
|
+
|
|
2108
|
+
|
|
2109
|
+
class TickSequenceTick(_TickSequenceTickMixin, ABC):
|
|
2110
|
+
"""
|
|
2111
|
+
Tick object that can be accessed only in per-tick script.
|
|
2112
|
+
This object is a first per-tick script function argument.
|
|
2113
|
+
This object is the loop variable of for-cycle in the code of per-tick script
|
|
2114
|
+
when for-cycle is used on tick sequence.
|
|
2115
|
+
Also tick can be defined as static local variable.
|
|
2116
|
+
|
|
2117
|
+
Examples
|
|
2118
|
+
--------
|
|
2119
|
+
>>> def another_query():
|
|
2120
|
+
... return otp.Ticks(X=[1, 2, 3])
|
|
2121
|
+
>>> def fun(tick):
|
|
2122
|
+
... dyn_t = otp.dynamic_tick()
|
|
2123
|
+
... tick['SUM'] = 0
|
|
2124
|
+
... for t in tick.state_vars['LIST']:
|
|
2125
|
+
... tick['SUM'] += t.get_long_value('X')
|
|
2126
|
+
... dyn_t['X'] = tick['SUM']
|
|
2127
|
+
>>> data = otp.Tick(A=1)
|
|
2128
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
2129
|
+
>>> data = data.script(fun)
|
|
2130
|
+
"""
|
|
2131
|
+
|
|
2132
|
+
def __init__(self, name, owner=None):
|
|
2133
|
+
super().__init__(name)
|
|
2134
|
+
self._owner = owner
|
|
2135
|
+
|
|
2136
|
+
@property
|
|
2137
|
+
def schema(self) -> dict:
|
|
2138
|
+
return self._owner.schema.copy() if self._owner is not None else {}
|
|
2139
|
+
|
|
2140
|
+
def next(self):
|
|
2141
|
+
"""
|
|
2142
|
+
Moves to the next tick in the container.
|
|
2143
|
+
"""
|
|
2144
|
+
return f'{self}.NEXT()'
|
|
2145
|
+
|
|
2146
|
+
def prev(self):
|
|
2147
|
+
"""
|
|
2148
|
+
Moves to the previous tick in the container.
|
|
2149
|
+
"""
|
|
2150
|
+
return f'{self}.PREV()'
|
|
2151
|
+
|
|
2152
|
+
def is_end(self):
|
|
2153
|
+
"""
|
|
2154
|
+
Checks if current tick is valid. If false tick can not be accessed.
|
|
2155
|
+
"""
|
|
2156
|
+
return Operation(dtype=bool, op_str=f'{self}.IS_END()')
|
|
2157
|
+
|
|
2158
|
+
def copy(self, tick_object):
|
|
2159
|
+
"""
|
|
2160
|
+
Makes passed object to point on the same tick (passed object must be of the same type).
|
|
2161
|
+
"""
|
|
2162
|
+
if getattr(tick_object, '_owner', None) is None:
|
|
2163
|
+
tick_object._owner = self._owner
|
|
2164
|
+
return f'{self}.COPY({tick_object})'
|
|
2165
|
+
|
|
2166
|
+
def get_timestamp(self) -> Operation:
|
|
2167
|
+
"""
|
|
2168
|
+
Get timestamp of the tick.
|
|
2169
|
+
|
|
2170
|
+
Examples
|
|
2171
|
+
--------
|
|
2172
|
+
>>> def another_query():
|
|
2173
|
+
... return otp.Ticks({'A': [1, 2, 3]})
|
|
2174
|
+
>>> def fun(tick):
|
|
2175
|
+
... for t in tick.state_vars['LIST']:
|
|
2176
|
+
... tick['TS'] = t.get_timestamp()
|
|
2177
|
+
>>> data = otp.Tick(A=1)
|
|
2178
|
+
>>> data.state_vars['LIST'] = otp.state.tick_list(otp.eval(another_query))
|
|
2179
|
+
>>> data = data.script(fun)
|
|
2180
|
+
|
|
2181
|
+
>>> otp.run(data)
|
|
2182
|
+
Time A TS
|
|
2183
|
+
0 2003-12-01 1 2003-12-01 00:00:00.002
|
|
2184
|
+
"""
|
|
2185
|
+
return Operation(dtype=nsectime, op_str=f'{self}.GET_TIMESTAMP()')
|
|
2186
|
+
|
|
2187
|
+
def get_value(self, field_name):
|
|
2188
|
+
if field_name in ('TIMESTAMP', 'Time'):
|
|
2189
|
+
return self.get_timestamp()
|
|
2190
|
+
return super().get_value(field_name)
|
|
2191
|
+
|
|
2192
|
+
|
|
2193
|
+
class _TickListTick(TickSequenceTick):
|
|
2194
|
+
_definition = 'TICK_LIST_TICK'
|
|
2195
|
+
|
|
2196
|
+
|
|
2197
|
+
class _TickSetTick(TickSequenceTick):
|
|
2198
|
+
_definition = 'TICK_SET_TICK'
|
|
2199
|
+
|
|
2200
|
+
|
|
2201
|
+
class _TickDequeTick(TickSequenceTick):
|
|
2202
|
+
_definition = 'TICK_DEQUE_TICK'
|
|
2203
|
+
|
|
2204
|
+
|
|
2205
|
+
class _DynamicTick(_TickSequenceTickMixin):
|
|
2206
|
+
"""
|
|
2207
|
+
This tick type allow to add and update fields dynamically.
|
|
2208
|
+
This tick can be inserted to all tick sequences.
|
|
2209
|
+
"""
|
|
2210
|
+
_definition = 'DYNAMIC_TICK'
|
|
2211
|
+
|
|
2212
|
+
def __init__(self, name):
|
|
2213
|
+
super().__init__(name)
|
|
2214
|
+
self._schema = {}
|
|
2215
|
+
|
|
2216
|
+
@property
|
|
2217
|
+
def schema(self):
|
|
2218
|
+
return self._schema
|
|
2219
|
+
|
|
2220
|
+
def add_field(self, name, value):
|
|
2221
|
+
"""
|
|
2222
|
+
Add field with `name` and `value` to the tick.
|
|
2223
|
+
"""
|
|
2224
|
+
if name in self.schema:
|
|
2225
|
+
raise ValueError(f"Can't add field '{name}' again."
|
|
2226
|
+
" Use set_value or __setitem__ functions to update it.")
|
|
2227
|
+
dtype = get_object_type(value)
|
|
2228
|
+
self._schema[name] = dtype
|
|
2229
|
+
return f'{self}.ADD_FIELD({value2str(name)},{value2str(type2str(dtype))},{value2str(value)});'
|
|
2230
|
+
|
|
2231
|
+
def __setitem__(self, key, value):
|
|
2232
|
+
try:
|
|
2233
|
+
return self.add_field(key, value)
|
|
2234
|
+
except ValueError:
|
|
2235
|
+
return super().__setitem__(key, value)
|
|
2236
|
+
|
|
2237
|
+
def copy_fields(self, tick_object, field_names):
|
|
2238
|
+
"""
|
|
2239
|
+
Copy fields with `field_names` from other `tick_object`.
|
|
2240
|
+
Fields not listed in the `field_names` argument of the function will be removed from tick.
|
|
2241
|
+
"""
|
|
2242
|
+
if not set(field_names).issubset(tick_object.schema):
|
|
2243
|
+
x = set(field_names).difference(tick_object.schema)
|
|
2244
|
+
raise ValueError(f"Fields {x} not in tick object schema")
|
|
2245
|
+
field_names = ','.join(field_names)
|
|
2246
|
+
self._schema = {
|
|
2247
|
+
k: v
|
|
2248
|
+
for k, v in tick_object.schema.items()
|
|
2249
|
+
if k in field_names
|
|
2250
|
+
}
|
|
2251
|
+
return f'{self}.COPY_FIELDS({tick_object},{value2str(field_names)})'
|
|
2252
|
+
|
|
2253
|
+
|
|
2254
|
+
def _check_field_name(field_name, dtype, value, schema):
|
|
2255
|
+
if isinstance(field_name, Operation):
|
|
2256
|
+
operation_dtype = field_name.dtype
|
|
2257
|
+
if not check_value_dtype(str, operation_dtype):
|
|
2258
|
+
raise ValueError(
|
|
2259
|
+
f"Can't get value of operation '{str(field_name)}'. "
|
|
2260
|
+
f"The type of this operation is '{operation_dtype}'. "
|
|
2261
|
+
f"But you are trying to get value with type str."
|
|
2262
|
+
)
|
|
2263
|
+
return None
|
|
2264
|
+
if value is None:
|
|
2265
|
+
action = 'get'
|
|
2266
|
+
if field_name in ('TIMESTAMP', 'Time'):
|
|
2267
|
+
raise ValueError("Please use .get_timestamp() method to get timestamp of the tick")
|
|
2268
|
+
else:
|
|
2269
|
+
action = 'set'
|
|
2270
|
+
schema = schema or {}
|
|
2271
|
+
if field_name not in schema:
|
|
2272
|
+
raise ValueError(
|
|
2273
|
+
f"Field name '{field_name}' not in tick's schema."
|
|
2274
|
+
" It may also happen if tick sequence is updated dynamically."
|
|
2275
|
+
" In this case to access the value of tick field use functions get_*_value(..., check_schema=False)"
|
|
2276
|
+
" and to set the value of tick field use functions set_*_value(..., check_schema=False).")
|
|
2277
|
+
field_dtype = schema[field_name]
|
|
2278
|
+
if dtype is not None and not check_value_dtype(dtype, field_dtype):
|
|
2279
|
+
raise ValueError(
|
|
2280
|
+
f"Can't {action} value of field '{field_name}'. "
|
|
2281
|
+
f"The type of this field is '{field_dtype}'. "
|
|
2282
|
+
f"But you are trying to {action} value with type '{dtype}'."
|
|
2283
|
+
)
|
|
2284
|
+
return field_dtype
|
|
2285
|
+
|
|
2286
|
+
|
|
2287
|
+
def _check_value(field_dtype, field_name, dtype, value):
|
|
2288
|
+
if isinstance(value, Operation):
|
|
2289
|
+
value_dtype = value.dtype
|
|
2290
|
+
else:
|
|
2291
|
+
value_dtype = get_object_type(value)
|
|
2292
|
+
|
|
2293
|
+
error = False
|
|
2294
|
+
field_dtype = field_dtype or dtype
|
|
2295
|
+
if field_dtype is not None:
|
|
2296
|
+
if not isinstance(value, Operation):
|
|
2297
|
+
if not check_value_dtype(value, field_dtype, check_string_length=True):
|
|
2298
|
+
error = True
|
|
2299
|
+
elif not check_value_dtype(value_dtype, field_dtype):
|
|
2300
|
+
error = True
|
|
2301
|
+
if error:
|
|
2302
|
+
raise ValueError(
|
|
2303
|
+
f"Can't set value of field '{str(field_name)}'. "
|
|
2304
|
+
f"The type of this field is '{field_dtype or dtype}'. "
|
|
2305
|
+
f"But you are trying to set value with type '{value_dtype}'."
|
|
2306
|
+
)
|
|
2307
|
+
|
|
2308
|
+
|
|
2309
|
+
def check_field_name_in_schema(field_name, dtype=None, value=None, schema=None):
|
|
2310
|
+
field_dtype = _check_field_name(field_name, dtype, value, schema)
|
|
2311
|
+
if value is not None:
|
|
2312
|
+
_check_value(field_dtype, field_name, dtype, value)
|