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,1455 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import re
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
|
4
|
+
from onetick.py.backports import Literal
|
|
5
|
+
|
|
6
|
+
import onetick.py as otp
|
|
7
|
+
from onetick.py import types as ott
|
|
8
|
+
from onetick.py import utils
|
|
9
|
+
from onetick.py.core.column import _Column
|
|
10
|
+
from onetick.py.otq import otq
|
|
11
|
+
from onetick.py.compatibility import (
|
|
12
|
+
is_ob_virtual_prl_and_show_full_detail_supported,
|
|
13
|
+
is_per_tick_script_boolean_problem,
|
|
14
|
+
)
|
|
15
|
+
from onetick.py.aggregations._docs import copy_signature
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from onetick.py.core.source import Source
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def inplace_operation(method):
|
|
22
|
+
"""Decorator that adds the `inplace` parameter and logic according to this
|
|
23
|
+
flag. inplace=True means that method modifies an object, otherwise it copies
|
|
24
|
+
the object firstly, modifies copy and returns the copy.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@functools.wraps(method)
|
|
28
|
+
def _inner(self, *args, inplace=False, **kwargs):
|
|
29
|
+
kwargs['inplace'] = inplace
|
|
30
|
+
if inplace:
|
|
31
|
+
method(self, *args, **kwargs)
|
|
32
|
+
return None
|
|
33
|
+
else:
|
|
34
|
+
obj = self.copy()
|
|
35
|
+
return method(obj, *args, **kwargs)
|
|
36
|
+
|
|
37
|
+
return _inner
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _columns_names_regex(
|
|
41
|
+
self: 'Source', objs: Tuple[Union[_Column, str]], drop: bool = False
|
|
42
|
+
) -> Tuple[List[str], bool]:
|
|
43
|
+
"""
|
|
44
|
+
We can't be sure python Source has consistent columns cache, because sinking complex event processors
|
|
45
|
+
can change columns unpredictable, so if user will specify regex as a param, we will pass regex
|
|
46
|
+
as an onetick's param, but pass or delete all matched columns from python Source cache.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
objs:
|
|
51
|
+
Tuple of _Columns or string to pass or drop. String can be regex.
|
|
52
|
+
drop: bool
|
|
53
|
+
To drop columns from python schema or not.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
items_to_passthrough:
|
|
58
|
+
Items to pass to Passthrough as `field` parameter.
|
|
59
|
+
regex:
|
|
60
|
+
Value to pass to Passthrough as `use_regex` parameter.
|
|
61
|
+
"""
|
|
62
|
+
def is_regex_string(obj):
|
|
63
|
+
return isinstance(obj, str) and any(c in r"*+?\:[]{}()^$" for c in obj)
|
|
64
|
+
|
|
65
|
+
# if any object from the list is a regexp, then all other objects will be treated like regexps too
|
|
66
|
+
regex = any(is_regex_string(obj) for obj in objs)
|
|
67
|
+
|
|
68
|
+
items_to_passthrough = []
|
|
69
|
+
names_of_columns = []
|
|
70
|
+
for obj in objs:
|
|
71
|
+
if not isinstance(obj, (str, _Column)):
|
|
72
|
+
raise TypeError(f"It is not supported to select or delete item '{obj}' of type '{type(obj)}'")
|
|
73
|
+
if regex:
|
|
74
|
+
# if column object or non-regex string is specified in regex mode,
|
|
75
|
+
# then we assume that the user requested that exact column to be dropped
|
|
76
|
+
if isinstance(obj, _Column):
|
|
77
|
+
names_of_columns.append(obj.name)
|
|
78
|
+
obj = f'^{obj.name}$'
|
|
79
|
+
elif not is_regex_string(obj):
|
|
80
|
+
names_of_columns.append(obj)
|
|
81
|
+
obj = f'^{obj}$'
|
|
82
|
+
else:
|
|
83
|
+
names_of_columns.extend(col for col in self.columns() if re.search(obj, col))
|
|
84
|
+
items_to_passthrough.append(obj)
|
|
85
|
+
else:
|
|
86
|
+
name = obj.name if isinstance(obj, _Column) else obj
|
|
87
|
+
items_to_passthrough.append(name)
|
|
88
|
+
names_of_columns.append(name)
|
|
89
|
+
|
|
90
|
+
# remove duplications and meta_fields
|
|
91
|
+
names_of_columns: set[str] = set(names_of_columns) - set(self.__class__.meta_fields) # type: ignore[no-redef]
|
|
92
|
+
# TODO: we definitely have the same logic of checking columns somewhere else too, need to refactor
|
|
93
|
+
for item in names_of_columns:
|
|
94
|
+
if item not in self.__dict__ or not isinstance(self.__dict__[item], _Column):
|
|
95
|
+
raise AttributeError(f"There is no '{item}' column")
|
|
96
|
+
if drop:
|
|
97
|
+
for item in names_of_columns:
|
|
98
|
+
del self.__dict__[item]
|
|
99
|
+
return items_to_passthrough, regex
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@inplace_operation
|
|
103
|
+
def pause(self: 'Source', delay, busy_waiting=False, where=None, inplace=False) -> Optional['Source']:
|
|
104
|
+
"""
|
|
105
|
+
Pauses processing of each tick for number of milliseconds specified via ``delay`` expression.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
delay: int or :py:class:`onetick.py.Operation`
|
|
110
|
+
Integer number or OneTick expression used to calculate the delay.
|
|
111
|
+
Delay is in milliseconds.
|
|
112
|
+
Note that number can't be negative.
|
|
113
|
+
busy_waiting: bool
|
|
114
|
+
If True then delay is done via busy loop (consuming CPU time).
|
|
115
|
+
where: :py:class:`onetick.py.Operation`
|
|
116
|
+
Expression to select ticks for which processing will be paused.
|
|
117
|
+
By default, all ticks are selected.
|
|
118
|
+
inplace: bool
|
|
119
|
+
The flag controls whether operation should be applied inplace or not.
|
|
120
|
+
If ``inplace=True``, then it returns nothing.
|
|
121
|
+
Otherwise method returns a new modified object.
|
|
122
|
+
|
|
123
|
+
See also
|
|
124
|
+
--------
|
|
125
|
+
**PAUSE** OneTick event processor
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
:class:`Source` or ``None``
|
|
130
|
+
|
|
131
|
+
Examples
|
|
132
|
+
--------
|
|
133
|
+
|
|
134
|
+
Pause every tick for 100 milliseconds (0.1 second).
|
|
135
|
+
|
|
136
|
+
>>> data = otp.Tick(A=1)
|
|
137
|
+
>>> data = data.pause(100)
|
|
138
|
+
|
|
139
|
+
Set the ``delay`` with expression and use ``where`` parameter to pause conditionally:
|
|
140
|
+
|
|
141
|
+
>>> data = otp.Ticks(A=[-1, 1, 2])
|
|
142
|
+
>>> data = data.pause(data['A'] * 100, where=data['A'] > 0)
|
|
143
|
+
"""
|
|
144
|
+
if not isinstance(delay, int):
|
|
145
|
+
delay = str(delay)
|
|
146
|
+
elif delay < 0:
|
|
147
|
+
raise ValueError("Parameter 'delay' can't be negative")
|
|
148
|
+
where = '' if where is None else str(where)
|
|
149
|
+
self.sink(
|
|
150
|
+
otq.Pause(
|
|
151
|
+
delay=delay,
|
|
152
|
+
busy_waiting=busy_waiting,
|
|
153
|
+
where=where,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@inplace_operation
|
|
160
|
+
def insert_tick(
|
|
161
|
+
self: 'Source',
|
|
162
|
+
fields=None,
|
|
163
|
+
where=None,
|
|
164
|
+
preserve_input_ticks=True,
|
|
165
|
+
num_ticks_to_insert=1,
|
|
166
|
+
insert_before=True,
|
|
167
|
+
inplace=False,
|
|
168
|
+
) -> Optional['Source']:
|
|
169
|
+
"""
|
|
170
|
+
Insert tick.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
fields: dict of str to :py:class:`onetick.py.Operation`
|
|
175
|
+
Mapping of field names to some expressions or values.
|
|
176
|
+
These fields in inserted ticks will be set to corresponding values or results of expressions.
|
|
177
|
+
If field is presented in input tick, but not set in ``fields`` dict,
|
|
178
|
+
then the value of the field will be copied from input tick to inserted tick.
|
|
179
|
+
If parameter ``fields`` is not set at all, then values for inserted ticks' fields
|
|
180
|
+
will be default values for fields' types from input ticks (0 for integers etc.).
|
|
181
|
+
where: :py:class:`onetick.py.Operation`
|
|
182
|
+
Expression to select ticks near which the new ticks will be inserted.
|
|
183
|
+
By default, all ticks are selected.
|
|
184
|
+
preserve_input_ticks: bool
|
|
185
|
+
A switch controlling whether input ticks have to be preserved in output time series or not.
|
|
186
|
+
While the former case results in fields of input ticks to be present in the output time series
|
|
187
|
+
together with those defined by the ``fields`` parameter,
|
|
188
|
+
the latter case results in only defined fields to be present.
|
|
189
|
+
If a field of the input time series is defined in the ``fields`` parameter,
|
|
190
|
+
the defined value takes precedence.
|
|
191
|
+
num_ticks_to_insert: int
|
|
192
|
+
Number of ticks to insert.
|
|
193
|
+
insert_before: bool
|
|
194
|
+
Insert tick before each input tick or after.
|
|
195
|
+
inplace: bool
|
|
196
|
+
The flag controls whether operation should be applied inplace or not.
|
|
197
|
+
If ``inplace=True``, then it returns nothing.
|
|
198
|
+
Otherwise method returns a new modified object.
|
|
199
|
+
|
|
200
|
+
See also
|
|
201
|
+
--------
|
|
202
|
+
**INSERT_TICK** OneTick event processor
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
:class:`Source` or ``None``
|
|
207
|
+
|
|
208
|
+
Examples
|
|
209
|
+
--------
|
|
210
|
+
|
|
211
|
+
Insert tick before each tick with default type values.
|
|
212
|
+
|
|
213
|
+
>>> data = otp.Tick(A=1)
|
|
214
|
+
>>> data = data.insert_tick()
|
|
215
|
+
>>> otp.run(data)
|
|
216
|
+
Time A
|
|
217
|
+
0 2003-12-01 0
|
|
218
|
+
1 2003-12-01 1
|
|
219
|
+
|
|
220
|
+
Insert tick before each tick with field `A` copied from input tick
|
|
221
|
+
and field `B` set to specified value.
|
|
222
|
+
|
|
223
|
+
>>> data = otp.Tick(A=1)
|
|
224
|
+
>>> data = data.insert_tick(fields={'B': 'b'})
|
|
225
|
+
>>> otp.run(data)
|
|
226
|
+
Time B A
|
|
227
|
+
0 2003-12-01 b 1
|
|
228
|
+
1 2003-12-01 1
|
|
229
|
+
|
|
230
|
+
Insert two ticks only after first tick.
|
|
231
|
+
|
|
232
|
+
>>> data = otp.Ticks(A=[1, 2, 3])
|
|
233
|
+
>>> data = data.insert_tick(where=data['A'] == 1,
|
|
234
|
+
... insert_before=False,
|
|
235
|
+
... num_ticks_to_insert=2)
|
|
236
|
+
>>> otp.run(data)
|
|
237
|
+
Time A
|
|
238
|
+
0 2003-12-01 00:00:00.000 1
|
|
239
|
+
1 2003-12-01 00:00:00.000 0
|
|
240
|
+
2 2003-12-01 00:00:00.000 0
|
|
241
|
+
3 2003-12-01 00:00:00.001 2
|
|
242
|
+
4 2003-12-01 00:00:00.002 3
|
|
243
|
+
"""
|
|
244
|
+
if not isinstance(num_ticks_to_insert, int) or num_ticks_to_insert <= 0:
|
|
245
|
+
raise ValueError("Parameter 'num_ticks_to_insert' must be a positive integer")
|
|
246
|
+
if not preserve_input_ticks and not fields:
|
|
247
|
+
raise ValueError("Parameter 'fields' must be set if 'preserve_input_ticks' is False")
|
|
248
|
+
where = '' if where is None else str(where)
|
|
249
|
+
|
|
250
|
+
fields = fields or {}
|
|
251
|
+
update_schema = {}
|
|
252
|
+
for field, value in fields.items():
|
|
253
|
+
dtype = ott.get_object_type(value)
|
|
254
|
+
if field not in self.schema:
|
|
255
|
+
update_schema[field] = dtype
|
|
256
|
+
elif dtype is not self.schema[field]:
|
|
257
|
+
raise ValueError(f"Incompatible types for field '{field}': {self.schema[field]} --> {dtype}")
|
|
258
|
+
dtype = ott.type2str(dtype)
|
|
259
|
+
if isinstance(value, type):
|
|
260
|
+
value = ott.default_by_type(value)
|
|
261
|
+
value = ott.value2str(value)
|
|
262
|
+
fields[field] = (dtype, value)
|
|
263
|
+
fields = ','.join(
|
|
264
|
+
f'{field} {dtype}={value}' if value else f'{field} {dtype}' for field, (dtype, value) in fields.items()
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
self.sink(
|
|
268
|
+
otq.InsertTick(
|
|
269
|
+
fields=fields,
|
|
270
|
+
where=where,
|
|
271
|
+
preserve_input_ticks=preserve_input_ticks,
|
|
272
|
+
num_ticks_to_insert=num_ticks_to_insert,
|
|
273
|
+
insert_before=insert_before,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
if preserve_input_ticks:
|
|
277
|
+
self.table(inplace=True, strict=False, **update_schema)
|
|
278
|
+
else:
|
|
279
|
+
self.table(inplace=True, strict=True, **update_schema)
|
|
280
|
+
return self
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@inplace_operation
|
|
284
|
+
def insert_at_end(
|
|
285
|
+
self: 'Source',
|
|
286
|
+
*,
|
|
287
|
+
propagate_ticks: bool = True,
|
|
288
|
+
delimiter_name: str = 'AT_END',
|
|
289
|
+
inplace: bool = False,
|
|
290
|
+
) -> Optional['Source']:
|
|
291
|
+
"""
|
|
292
|
+
This function adds a field ``delimiter_name``,
|
|
293
|
+
which is set to zero for all inbound ticks
|
|
294
|
+
and set to 1 for an additional tick that is generated when the data ends.
|
|
295
|
+
|
|
296
|
+
The timestamp of the additional tick is set to the query end time.
|
|
297
|
+
The values of all fields from the input schema of additional tick are set to default values for each type.
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
propagate_ticks: bool
|
|
302
|
+
If True (default) this function returns all input ticks and an additionally generated tick,
|
|
303
|
+
otherwise it returns only the last generated tick.
|
|
304
|
+
delimiter_name: str
|
|
305
|
+
The name of the delimiter field to add.
|
|
306
|
+
inplace: bool
|
|
307
|
+
The flag controls whether operation should be applied inplace or not.
|
|
308
|
+
If ``inplace=True``, then it returns nothing.
|
|
309
|
+
Otherwise method returns a new modified object.
|
|
310
|
+
|
|
311
|
+
See also
|
|
312
|
+
--------
|
|
313
|
+
**INSERT_AT_END** OneTick event processor
|
|
314
|
+
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
:class:`Source` or ``None``
|
|
318
|
+
|
|
319
|
+
Examples
|
|
320
|
+
--------
|
|
321
|
+
Insert tick at the end of the stream:
|
|
322
|
+
|
|
323
|
+
>>> data = otp.Ticks(A=[1, 2, 3])
|
|
324
|
+
>>> data = data.insert_at_end()
|
|
325
|
+
>>> otp.run(data)
|
|
326
|
+
Time A AT_END
|
|
327
|
+
0 2003-12-01 00:00:00.000 1 0
|
|
328
|
+
1 2003-12-01 00:00:00.001 2 0
|
|
329
|
+
2 2003-12-01 00:00:00.002 3 0
|
|
330
|
+
3 2003-12-04 00:00:00.000 0 1
|
|
331
|
+
|
|
332
|
+
The name of the added field can be changed:
|
|
333
|
+
|
|
334
|
+
>>> data = otp.Ticks(A=[1, 2, 3])
|
|
335
|
+
>>> data = data.insert_at_end(delimiter_name='LAST_TICK')
|
|
336
|
+
>>> otp.run(data)
|
|
337
|
+
Time A LAST_TICK
|
|
338
|
+
0 2003-12-01 00:00:00.000 1 0
|
|
339
|
+
1 2003-12-01 00:00:00.001 2 0
|
|
340
|
+
2 2003-12-01 00:00:00.002 3 0
|
|
341
|
+
3 2003-12-04 00:00:00.000 0 1
|
|
342
|
+
|
|
343
|
+
If parameter ``propagate_ticks`` is set to False, then only the last tick is returned:
|
|
344
|
+
|
|
345
|
+
>>> data = otp.Ticks(A=[1, 2, 3])
|
|
346
|
+
>>> data = data.insert_at_end(propagate_ticks=False)
|
|
347
|
+
>>> otp.run(data)
|
|
348
|
+
Time A AT_END
|
|
349
|
+
0 2003-12-04 0 1
|
|
350
|
+
"""
|
|
351
|
+
if not hasattr(otq, 'InsertAtEnd'):
|
|
352
|
+
raise RuntimeError("Function insert_at_end() is not available on this OneTick API version")
|
|
353
|
+
if delimiter_name in self.schema:
|
|
354
|
+
raise ValueError(f"Field '{delimiter_name}' is already in schema")
|
|
355
|
+
|
|
356
|
+
self.sink(
|
|
357
|
+
otq.InsertAtEnd(
|
|
358
|
+
propagate_ticks=propagate_ticks,
|
|
359
|
+
delimiter_name=delimiter_name,
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
self.schema.update(**{delimiter_name: int})
|
|
363
|
+
return self
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@inplace_operation
|
|
367
|
+
def transpose(
|
|
368
|
+
self: 'Source',
|
|
369
|
+
inplace: bool = False,
|
|
370
|
+
direction: Literal['rows', 'columns'] = 'rows',
|
|
371
|
+
n: Optional[int] = None,
|
|
372
|
+
) -> Optional['Source']:
|
|
373
|
+
"""
|
|
374
|
+
Data transposing.
|
|
375
|
+
The main idea is joining many ticks into one or splitting one tick to many.
|
|
376
|
+
|
|
377
|
+
Parameters
|
|
378
|
+
----------
|
|
379
|
+
inplace: bool, default=False
|
|
380
|
+
if `True` method will modify current object,
|
|
381
|
+
otherwise it will return modified copy of the object.
|
|
382
|
+
direction: 'rows', 'columns', default='rows'
|
|
383
|
+
- `rows`: join certain input ticks (depending on other parameters) with preceding ones.
|
|
384
|
+
Fields of each tick will be added to the output tick and their names will be suffixed
|
|
385
|
+
with **_K** where **K** is the positional number of tick (starting from 1) in reverse order.
|
|
386
|
+
So fields of current tick will be suffixed with **_1**, fields of previous tick will be
|
|
387
|
+
suffixed with **_2** and so on.
|
|
388
|
+
- `columns`: the operation is opposite to `rows`. It splits each input tick to several
|
|
389
|
+
output ticks. Each input tick must have fields with names suffixed with **_K**
|
|
390
|
+
where **K** is the positional number of tick (starting from 1) in reverse order.
|
|
391
|
+
n: Optional[int], default=None
|
|
392
|
+
must be specified only if ``direction`` is 'rows'.
|
|
393
|
+
Joins every **n** number of ticks with **n-1** preceding ticks.
|
|
394
|
+
|
|
395
|
+
Returns
|
|
396
|
+
-------
|
|
397
|
+
If ``inplace`` parameter is `True` method will return `None`,
|
|
398
|
+
otherwise it will return modified copy of the object.
|
|
399
|
+
|
|
400
|
+
See also
|
|
401
|
+
--------
|
|
402
|
+
**TRANSPOSE** OneTick event processor
|
|
403
|
+
|
|
404
|
+
Examples
|
|
405
|
+
--------
|
|
406
|
+
Merging two ticks into one.
|
|
407
|
+
|
|
408
|
+
>>> data = otp.Ticks(dict(A=[1, 2],
|
|
409
|
+
... B=[3, 4]))
|
|
410
|
+
>>> data = data.transpose(direction='rows', n=2) # OTdirective: skip-snippet:;
|
|
411
|
+
>>> otp.run(data)
|
|
412
|
+
Time TIMESTAMP_1 A_1 B_1 TIMESTAMP_2 A_2 B_2
|
|
413
|
+
0 2003-12-01 00:00:00.001 2003-12-01 00:00:00.001 2 4 2003-12-01 1 3
|
|
414
|
+
|
|
415
|
+
And splitting them back into two.
|
|
416
|
+
|
|
417
|
+
>>> data = data.transpose(direction='columns') # OTdirective: skip-snippet:;
|
|
418
|
+
>>> otp.run(data)
|
|
419
|
+
Time A B
|
|
420
|
+
0 2003-12-01 00:00:00.000 1 3
|
|
421
|
+
1 2003-12-01 00:00:00.001 2 4
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
direction_map = {'rows': 'ROWS_TO_COLUMNS', 'columns': 'COLUMNS_TO_ROWS'}
|
|
425
|
+
n: Union[str, int] = '' if n is None else n # type: ignore[no-redef]
|
|
426
|
+
|
|
427
|
+
self.sink(otq.Transpose(direction=direction_map[direction], key_constraint_values=n))
|
|
428
|
+
# TODO: we should change source's schema after transposing
|
|
429
|
+
return self
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def cache(
|
|
433
|
+
self: 'Source',
|
|
434
|
+
cache_name: str,
|
|
435
|
+
delete_if_exists: bool = True,
|
|
436
|
+
inheritability: bool = True,
|
|
437
|
+
otq_params: Union[dict, None] = None,
|
|
438
|
+
time_granularity: int = 0,
|
|
439
|
+
time_granularity_units: Optional[str] = None,
|
|
440
|
+
timezone: str = "",
|
|
441
|
+
time_intervals_to_cache: Optional[List[tuple]] = None,
|
|
442
|
+
allow_delete_to_everyone: bool = False,
|
|
443
|
+
allow_update_to_everyone: bool = False,
|
|
444
|
+
allow_search_to_everyone: bool = True,
|
|
445
|
+
cache_expiration_interval: int = 0,
|
|
446
|
+
start: Optional[ott.datetime] = None,
|
|
447
|
+
end: Optional[ott.datetime] = None,
|
|
448
|
+
read_mode: str = "automatic",
|
|
449
|
+
update_cache: bool = True,
|
|
450
|
+
tick_type: str = "ANY",
|
|
451
|
+
symbol: Optional[str] = None,
|
|
452
|
+
db: Optional[str] = None,
|
|
453
|
+
) -> 'Source':
|
|
454
|
+
"""
|
|
455
|
+
Create cache from query and :py:class:`onetick.py.ReadCache` for created cache.
|
|
456
|
+
|
|
457
|
+
Cache will be created only for current session.
|
|
458
|
+
|
|
459
|
+
By default, if cache with specified name exists, it will be deleted and recreated.
|
|
460
|
+
You can change this behaviour via ``delete_if_exists`` parameter.
|
|
461
|
+
|
|
462
|
+
Parameters
|
|
463
|
+
----------
|
|
464
|
+
cache_name: str
|
|
465
|
+
Name of the cache to be deleted.
|
|
466
|
+
delete_if_exists: bool
|
|
467
|
+
If set to ``True`` (default), a check will be made to detect the existence of a cache
|
|
468
|
+
with the specified name. Cache will be deleted and recreated only if it exists.
|
|
469
|
+
If set to ``False``, if cache exists it won't be deleted and recreated.
|
|
470
|
+
inheritability: bool
|
|
471
|
+
Indicates whether results can be obtained by combining time intervals that were cached with intervals
|
|
472
|
+
freshly computed to obtain results for larger intervals.
|
|
473
|
+
otq_params: dict
|
|
474
|
+
OTQ params of the query to be cached.
|
|
475
|
+
time_granularity: int
|
|
476
|
+
Value N for seconds/days/months granularity means that start and end time of the query have to be on N
|
|
477
|
+
second/day/month boundaries relative to start of the day/month/year.
|
|
478
|
+
This doesn't affect the frequency of data within the cache, just the start and end dates.
|
|
479
|
+
time_granularity_units: str, None
|
|
480
|
+
Units used in ``time_granularity`` parameter. Possible values: 'none', 'days', 'months', 'seconds' or None.
|
|
481
|
+
timezone: str
|
|
482
|
+
Timezone of the query to be cached.
|
|
483
|
+
time_intervals_to_cache: List[tuple]
|
|
484
|
+
List of tuples with start and end times in ``[(<start_time_1>, <end_time_1>), ...]`` format,
|
|
485
|
+
where ``<start_time>`` and ``<end_time>`` should be one of these:
|
|
486
|
+
|
|
487
|
+
* string in ``YYYYMMDDhhmmss[.msec]`` format.
|
|
488
|
+
* :py:class:`datetime.datetime`
|
|
489
|
+
* :py:class:`onetick.py.types.datetime`
|
|
490
|
+
|
|
491
|
+
If specified only these time intervals can be cached. Ignored if ``inheritability=True``.
|
|
492
|
+
If you try to make a query outside defined interval, error will be raised.
|
|
493
|
+
allow_delete_to_everyone: bool
|
|
494
|
+
When set to ``True`` everyone is allowed to delete the cache.
|
|
495
|
+
allow_update_to_everyone: bool
|
|
496
|
+
When set to ``True`` everyone is allowed to update the cache.
|
|
497
|
+
allow_search_to_everyone: bool
|
|
498
|
+
When set to ``True`` everyone is allowed to read the cached data.
|
|
499
|
+
cache_expiration_interval: int
|
|
500
|
+
If set to a non-zero value determines the periodicity of cache clearing, in seconds.
|
|
501
|
+
The cache will be cleared every X seconds, triggering new query executions when data is requested.
|
|
502
|
+
start: :py:class:`otp.datetime <onetick.py.datetime>`
|
|
503
|
+
Start time for cache query. By default, the start time of the query will be used.
|
|
504
|
+
end: :py:class:`otp.datetime <onetick.py.datetime>`
|
|
505
|
+
End time for cache query. By default, the end time of the query will be used.
|
|
506
|
+
read_mode: str
|
|
507
|
+
Mode of querying cache. One of these:
|
|
508
|
+
|
|
509
|
+
* ``cache_only`` - only cached results are returned and queries are not performed.
|
|
510
|
+
* ``query_only`` - the query is run irrespective of whether the result is already available in the cache.
|
|
511
|
+
* ``automatic`` (default) - perform the query if the data is not found in the cache.
|
|
512
|
+
update_cache: bool
|
|
513
|
+
If set to ``True``, updates the cached data if ``read_mode=query_only`` or if ``read_mode=automatic`` and
|
|
514
|
+
the result data not found in the cache. Otherwise, the cache remains unchanged.
|
|
515
|
+
tick_type: str
|
|
516
|
+
Tick type.
|
|
517
|
+
symbol: str, list of str, list of otq.Symbol, :py:class:`onetick.py.Source`, :pandas:`pandas.DataFrame`, optional
|
|
518
|
+
``symbols`` parameter of ``otp.run()``.
|
|
519
|
+
db: str
|
|
520
|
+
Database.
|
|
521
|
+
|
|
522
|
+
See also
|
|
523
|
+
--------
|
|
524
|
+
| :py:func:`onetick.py.create_cache`
|
|
525
|
+
| :py:class:`onetick.py.ReadCache`
|
|
526
|
+
|
|
527
|
+
Examples
|
|
528
|
+
--------
|
|
529
|
+
Simple usage
|
|
530
|
+
|
|
531
|
+
>>> src = otp.DataSource("COMMON", tick_type="TRD", symbols="AAPL")
|
|
532
|
+
>>> data = src.cache(
|
|
533
|
+
... cache_name="some_cache",
|
|
534
|
+
... tick_type="TRD", symbol="SYM", db="LOCAL",
|
|
535
|
+
... )
|
|
536
|
+
>>> df = otp.run(data) # doctest: +SKIP
|
|
537
|
+
"""
|
|
538
|
+
from onetick.py.cache import create_cache, delete_cache, modify_cache_config
|
|
539
|
+
from onetick.py.sources import ReadCache
|
|
540
|
+
|
|
541
|
+
cache_exists = True
|
|
542
|
+
|
|
543
|
+
if delete_if_exists:
|
|
544
|
+
try:
|
|
545
|
+
modify_cache_config(cache_name, "TEST_PARAM", "TEST_VALUE")
|
|
546
|
+
except Exception as exc:
|
|
547
|
+
if "There is no cache" in str(exc):
|
|
548
|
+
cache_exists = False
|
|
549
|
+
|
|
550
|
+
if cache_exists and delete_if_exists:
|
|
551
|
+
delete_cache(cache_name)
|
|
552
|
+
cache_exists = False
|
|
553
|
+
|
|
554
|
+
if not cache_exists:
|
|
555
|
+
create_cache(
|
|
556
|
+
cache_name=cache_name,
|
|
557
|
+
query=self,
|
|
558
|
+
inheritability=inheritability,
|
|
559
|
+
otq_params=otq_params,
|
|
560
|
+
time_granularity=time_granularity,
|
|
561
|
+
time_granularity_units=time_granularity_units,
|
|
562
|
+
timezone=timezone,
|
|
563
|
+
time_intervals_to_cache=time_intervals_to_cache,
|
|
564
|
+
allow_delete_to_everyone=allow_delete_to_everyone,
|
|
565
|
+
allow_update_to_everyone=allow_update_to_everyone,
|
|
566
|
+
allow_search_to_everyone=allow_search_to_everyone,
|
|
567
|
+
cache_expiration_interval=cache_expiration_interval,
|
|
568
|
+
tick_type=tick_type,
|
|
569
|
+
symbol=symbol,
|
|
570
|
+
db=db,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
return ReadCache(
|
|
574
|
+
cache_name=cache_name,
|
|
575
|
+
start=start if start is not None else utils.adaptive,
|
|
576
|
+
end=end if end is not None else utils.adaptive,
|
|
577
|
+
read_mode=read_mode,
|
|
578
|
+
update_cache=update_cache,
|
|
579
|
+
tick_type=tick_type,
|
|
580
|
+
symbol=symbol if symbol is not None else utils.adaptive,
|
|
581
|
+
db=db if db is not None else utils.adaptive_to_default,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
@inplace_operation
|
|
586
|
+
def pnl_realized(
|
|
587
|
+
self: 'Source',
|
|
588
|
+
computation_method: str = 'fifo',
|
|
589
|
+
output_field_name: str = 'PNL_REALIZED',
|
|
590
|
+
size_field: Union[str, _Column] = 'SIZE',
|
|
591
|
+
price_field: Union[str, _Column] = 'PRICE',
|
|
592
|
+
buy_sell_flag_field: Union[str, _Column] = 'BUY_SELL_FLAG',
|
|
593
|
+
inplace=False,
|
|
594
|
+
) -> Optional['Source']:
|
|
595
|
+
"""
|
|
596
|
+
Computes the realized Profit and Loss (**PNL**) on each tick and is applicable to both scenarios,
|
|
597
|
+
whether selling after buying or buying after selling.
|
|
598
|
+
|
|
599
|
+
Parameters
|
|
600
|
+
----------
|
|
601
|
+
computation_method: str
|
|
602
|
+
This parameter determines the approach used to calculate the realized Profit and Loss (**PnL**).
|
|
603
|
+
|
|
604
|
+
Possible options are:
|
|
605
|
+
|
|
606
|
+
* ``fifo`` - Stands for 'First-In-First-Out,' is used to calculate Profit and Loss (**PnL**)
|
|
607
|
+
based on the principle that the first trading positions bought are the first ones to be sold,
|
|
608
|
+
or conversely, the first trading positions sold are the first ones to be bought.
|
|
609
|
+
|
|
610
|
+
output_field_name: str
|
|
611
|
+
This parameter defines the name of the output field.
|
|
612
|
+
|
|
613
|
+
Default: **PNL_REALIZED**.
|
|
614
|
+
|
|
615
|
+
size_field: str, :py:class:`otp.Column <onetick.py.Column>`
|
|
616
|
+
The name of the field with size, default is **SIZE**.
|
|
617
|
+
price_field: str, :py:class:`otp.Column <onetick.py.Column>`
|
|
618
|
+
The name of the field with price, default is **PRICE**.
|
|
619
|
+
buy_sell_flag_field: str, :py:class:`otp.Column <onetick.py.Column>`
|
|
620
|
+
The name of the field with buy/sell flag, default is **BUY_SELL_FLAG**.
|
|
621
|
+
If the type of this field is string, then possible values are 'B' or 'b' for buy and 'S' or 's' for sell.
|
|
622
|
+
If the type of this field is integer, then possible values are 0 for buy and 1 for sell.
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
See also
|
|
626
|
+
--------
|
|
627
|
+
**PNL_REALIZED** OneTick event processor
|
|
628
|
+
|
|
629
|
+
Examples
|
|
630
|
+
--------
|
|
631
|
+
Let's generate some data:
|
|
632
|
+
|
|
633
|
+
>>> trades = otp.Ticks(
|
|
634
|
+
... PRICE=[1.0, 2.0, 3.0, 2.5, 4.0, 5.0, 6.0, 7.0, 3.0, 4.0, 1.0],
|
|
635
|
+
... SIZE=[700, 20, 570, 600, 100, 100, 100, 100, 150, 10, 100],
|
|
636
|
+
... SELL_FLAG=[0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1],
|
|
637
|
+
... SIDE=['B', 'B', 'B', 'S', 'S', 'S', 'S', 'S', 'B', 'B', 'S'],
|
|
638
|
+
... )
|
|
639
|
+
>>> otp.run(trades)
|
|
640
|
+
Time PRICE SIZE SELL_FLAG SIDE
|
|
641
|
+
0 2003-12-01 00:00:00.000 1.0 700 0 B
|
|
642
|
+
1 2003-12-01 00:00:00.001 2.0 20 0 B
|
|
643
|
+
2 2003-12-01 00:00:00.002 3.0 570 0 B
|
|
644
|
+
3 2003-12-01 00:00:00.003 2.5 600 1 S
|
|
645
|
+
4 2003-12-01 00:00:00.004 4.0 100 1 S
|
|
646
|
+
5 2003-12-01 00:00:00.005 5.0 100 1 S
|
|
647
|
+
6 2003-12-01 00:00:00.006 6.0 100 1 S
|
|
648
|
+
7 2003-12-01 00:00:00.007 7.0 100 1 S
|
|
649
|
+
8 2003-12-01 00:00:00.008 3.0 150 0 B
|
|
650
|
+
9 2003-12-01 00:00:00.009 4.0 10 0 B
|
|
651
|
+
10 2003-12-01 00:00:00.010 1.0 100 1 S
|
|
652
|
+
|
|
653
|
+
And then calculate profit and loss metric for it.
|
|
654
|
+
|
|
655
|
+
First let's use string ``buy_sell_flag_field`` field:
|
|
656
|
+
|
|
657
|
+
>>> data = trades.pnl_realized(buy_sell_flag_field='SIDE') # doctest: +SKIP
|
|
658
|
+
>>> otp.run(data)[['Time', 'PRICE', 'SIZE', 'SIDE', 'PNL_REALIZED']] # doctest: +SKIP
|
|
659
|
+
Time PRICE SIZE SIDE PNL_REALIZED
|
|
660
|
+
0 2003-12-01 00:00:00.000 1.0 700 B 0.0
|
|
661
|
+
1 2003-12-01 00:00:00.001 2.0 20 B 0.0
|
|
662
|
+
2 2003-12-01 00:00:00.002 3.0 570 B 0.0
|
|
663
|
+
3 2003-12-01 00:00:00.003 2.5 600 S 900.0
|
|
664
|
+
4 2003-12-01 00:00:00.004 4.0 100 S 300.0
|
|
665
|
+
5 2003-12-01 00:00:00.005 5.0 100 S 220.0
|
|
666
|
+
6 2003-12-01 00:00:00.006 6.0 100 S 300.0
|
|
667
|
+
7 2003-12-01 00:00:00.007 7.0 100 S 400.0
|
|
668
|
+
8 2003-12-01 00:00:00.008 3.0 150 B 0.0
|
|
669
|
+
9 2003-12-01 00:00:00.009 4.0 10 B 0.0
|
|
670
|
+
10 2003-12-01 00:00:00.010 1.0 100 S -200.0
|
|
671
|
+
|
|
672
|
+
We can get the same result using integer ``buy_sell_flag_field`` field:
|
|
673
|
+
|
|
674
|
+
>>> data = trades.pnl_realized(buy_sell_flag_field='SELL_FLAG') # doctest: +SKIP
|
|
675
|
+
>>> otp.run(data)[['Time', 'PRICE', 'SIZE', 'SELL_FLAG', 'PNL_REALIZED']] # doctest: +SKIP
|
|
676
|
+
Time PRICE SIZE SELL_FLAG PNL_REALIZED
|
|
677
|
+
0 2003-12-01 00:00:00.000 1.0 700 0 0.0
|
|
678
|
+
1 2003-12-01 00:00:00.001 2.0 20 0 0.0
|
|
679
|
+
2 2003-12-01 00:00:00.002 3.0 570 0 0.0
|
|
680
|
+
3 2003-12-01 00:00:00.003 2.5 600 1 900.0
|
|
681
|
+
4 2003-12-01 00:00:00.004 4.0 100 1 300.0
|
|
682
|
+
5 2003-12-01 00:00:00.005 5.0 100 1 220.0
|
|
683
|
+
6 2003-12-01 00:00:00.006 6.0 100 1 300.0
|
|
684
|
+
7 2003-12-01 00:00:00.007 7.0 100 1 400.0
|
|
685
|
+
8 2003-12-01 00:00:00.008 3.0 150 0 0.0
|
|
686
|
+
9 2003-12-01 00:00:00.009 4.0 10 0 0.0
|
|
687
|
+
10 2003-12-01 00:00:00.010 1.0 100 1 -200.0
|
|
688
|
+
"""
|
|
689
|
+
if computation_method not in ['fifo']:
|
|
690
|
+
raise ValueError(
|
|
691
|
+
f"Parameter 'computation_method' has incorrect value: '{computation_method}', "
|
|
692
|
+
f"should be one of these: 'fifo'."
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
for field in (size_field, price_field, buy_sell_flag_field):
|
|
696
|
+
if str(field) not in self.schema:
|
|
697
|
+
raise ValueError(f'Field {field} is not in schema')
|
|
698
|
+
|
|
699
|
+
# PNL_REALIZED doesn't support choosing input fields, so we workaround by renaming fields
|
|
700
|
+
restore_fields = {}
|
|
701
|
+
added_fields = []
|
|
702
|
+
for default_field, field in (('SIZE', size_field), ('PRICE', price_field), ('BUY_SELL_FLAG', buy_sell_flag_field)):
|
|
703
|
+
if field != default_field:
|
|
704
|
+
if default_field in self.schema:
|
|
705
|
+
tmp_field_name = f'__TMP__{field}__TMP__'
|
|
706
|
+
self[tmp_field_name] = self[default_field]
|
|
707
|
+
restore_fields[default_field] = self[tmp_field_name]
|
|
708
|
+
self[default_field] = self[field]
|
|
709
|
+
added_fields.append(default_field)
|
|
710
|
+
|
|
711
|
+
if output_field_name in self.schema:
|
|
712
|
+
raise ValueError(f'Field {output_field_name} is already in schema')
|
|
713
|
+
|
|
714
|
+
computation_method = computation_method.upper()
|
|
715
|
+
self.sink(otq.PnlRealized(computation_method=computation_method, output_field_name=output_field_name))
|
|
716
|
+
self.schema[output_field_name] = float
|
|
717
|
+
|
|
718
|
+
if added_fields:
|
|
719
|
+
self.drop(added_fields, inplace=True)
|
|
720
|
+
if restore_fields:
|
|
721
|
+
# restore fields from temporary fields and delete temporary fields
|
|
722
|
+
self.add_fields(restore_fields)
|
|
723
|
+
self.drop(list(restore_fields.values()), inplace=True)
|
|
724
|
+
|
|
725
|
+
return self
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
@inplace_operation
|
|
729
|
+
def execute(self: 'Source', *operations, inplace=False) -> Optional['Source']:
|
|
730
|
+
"""
|
|
731
|
+
Execute operations without returning their values.
|
|
732
|
+
Some operations in onetick.py can be used to modify the state of some object
|
|
733
|
+
(tick sequences mostly) and in that case user may not want to save the result of the
|
|
734
|
+
operation to column.
|
|
735
|
+
|
|
736
|
+
Parameters
|
|
737
|
+
----------
|
|
738
|
+
operations : list of :py:class:`~onetick.py.Operation`
|
|
739
|
+
operations to execute.
|
|
740
|
+
inplace : bool
|
|
741
|
+
The flag controls whether operation should be applied inplace or not.
|
|
742
|
+
If ``inplace=True``, then it returns nothing. Otherwise method returns a new modified
|
|
743
|
+
object.
|
|
744
|
+
|
|
745
|
+
Returns
|
|
746
|
+
-------
|
|
747
|
+
:class:`Source` or ``None``
|
|
748
|
+
|
|
749
|
+
See Also
|
|
750
|
+
--------
|
|
751
|
+
**EXECUTE_EXPRESSIONS** OneTick event processor
|
|
752
|
+
|
|
753
|
+
Examples
|
|
754
|
+
--------
|
|
755
|
+
>>> data = otp.Tick(A=1)
|
|
756
|
+
>>> data.state_vars['SET'] = otp.state.tick_set('oldest', 'A')
|
|
757
|
+
>>> data = data.execute(data.state_vars['SET'].erase(A=1))
|
|
758
|
+
"""
|
|
759
|
+
if not operations:
|
|
760
|
+
raise ValueError('At least one operation must be specified in execute() method')
|
|
761
|
+
op_str = ';'.join(map(str, operations))
|
|
762
|
+
self.sink(otq.ExecuteExpressions(op_str))
|
|
763
|
+
return self
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
@inplace_operation
|
|
767
|
+
def fillna(self: 'Source', value=None, columns=None, inplace=False) -> Optional['Source']:
|
|
768
|
+
"""
|
|
769
|
+
Replace NaN values in floating point type fields with the ``value``.
|
|
770
|
+
|
|
771
|
+
Parameters
|
|
772
|
+
----------
|
|
773
|
+
value : float, :py:class:`~onetick.py.Operation`
|
|
774
|
+
The value to replace NaN.
|
|
775
|
+
If not specified then the value from the previous tick will be used.
|
|
776
|
+
columns: list
|
|
777
|
+
List of strings with column names or :py:class:`~onetick.py.Column` objects.
|
|
778
|
+
Only the values in specified columns will be replaced.
|
|
779
|
+
By default the values in all floating point type fields will be replaced.
|
|
780
|
+
inplace : bool
|
|
781
|
+
The flag controls whether operation should be applied inplace or not.
|
|
782
|
+
If ``inplace=True``, then it returns nothing. Otherwise method returns a new modified
|
|
783
|
+
object.
|
|
784
|
+
|
|
785
|
+
Returns
|
|
786
|
+
-------
|
|
787
|
+
:class:`Source` or ``None``
|
|
788
|
+
|
|
789
|
+
See Also
|
|
790
|
+
--------
|
|
791
|
+
:py:meth:`onetick.py.Operation.fillna`
|
|
792
|
+
|
|
793
|
+
Examples
|
|
794
|
+
--------
|
|
795
|
+
|
|
796
|
+
By default, the value of the previous tick is used as a value to replace NaN
|
|
797
|
+
(for the first tick the previous value do not exist, so it will still be NaN):
|
|
798
|
+
|
|
799
|
+
>>> data = otp.Ticks({'A': [0, 1, 2, 3], 'B': [otp.nan, 2.2, otp.nan, 3.3]})
|
|
800
|
+
>>> data = data.fillna()
|
|
801
|
+
>>> otp.run(data)
|
|
802
|
+
Time A B
|
|
803
|
+
0 2003-12-01 00:00:00.000 0 NaN
|
|
804
|
+
1 2003-12-01 00:00:00.001 1 2.2
|
|
805
|
+
2 2003-12-01 00:00:00.002 2 2.2
|
|
806
|
+
3 2003-12-01 00:00:00.003 3 3.3
|
|
807
|
+
|
|
808
|
+
The value can also be a constant:
|
|
809
|
+
|
|
810
|
+
>>> data = otp.Ticks({'A': [0, 1, 2, 3], 'B': [otp.nan, 2.2, otp.nan, 3.3]})
|
|
811
|
+
>>> data = data.fillna(777)
|
|
812
|
+
>>> otp.run(data)
|
|
813
|
+
Time A B
|
|
814
|
+
0 2003-12-01 00:00:00.000 0 777.0
|
|
815
|
+
1 2003-12-01 00:00:00.001 1 2.2
|
|
816
|
+
2 2003-12-01 00:00:00.002 2 777.0
|
|
817
|
+
3 2003-12-01 00:00:00.003 3 3.3
|
|
818
|
+
|
|
819
|
+
:py:class:`~onetick.py.Operation` objects can also be used as a ``value``:
|
|
820
|
+
|
|
821
|
+
>>> data = otp.Ticks({'A': [0, 1, 2, 3], 'B': [otp.nan, 2.2, otp.nan, 3.3]})
|
|
822
|
+
>>> data = data.fillna(data['A'])
|
|
823
|
+
>>> otp.run(data)
|
|
824
|
+
Time A B
|
|
825
|
+
0 2003-12-01 00:00:00.000 0 0.0
|
|
826
|
+
1 2003-12-01 00:00:00.001 1 2.2
|
|
827
|
+
2 2003-12-01 00:00:00.002 2 2.0
|
|
828
|
+
3 2003-12-01 00:00:00.003 3 3.3
|
|
829
|
+
|
|
830
|
+
Parameter ``columns`` can be used to specify the columns where values will be replaced:
|
|
831
|
+
|
|
832
|
+
.. testcode::
|
|
833
|
+
:skipif: is_per_tick_script_boolean_problem()
|
|
834
|
+
|
|
835
|
+
data = otp.Ticks({'A': [0, 1, 2, 3], 'B': [otp.nan, 2.2, otp.nan, 3.3], 'C': [otp.nan, 2.2, otp.nan, 3.3]})
|
|
836
|
+
data = data.fillna(columns=['B'])
|
|
837
|
+
df = otp.run(data)
|
|
838
|
+
print(df)
|
|
839
|
+
|
|
840
|
+
.. testoutput::
|
|
841
|
+
|
|
842
|
+
Time A B C
|
|
843
|
+
0 2003-12-01 00:00:00.000 0 NaN NaN
|
|
844
|
+
1 2003-12-01 00:00:00.001 1 2.2 2.2
|
|
845
|
+
2 2003-12-01 00:00:00.002 2 2.2 NaN
|
|
846
|
+
3 2003-12-01 00:00:00.003 3 3.3 3.3
|
|
847
|
+
"""
|
|
848
|
+
if columns:
|
|
849
|
+
for column in columns:
|
|
850
|
+
if column not in self.schema:
|
|
851
|
+
raise ValueError(f"Column '{column}' is not in schema")
|
|
852
|
+
if self.schema[column] is not float:
|
|
853
|
+
raise TypeError(f"Column '{column}' doesn't have float type")
|
|
854
|
+
columns = list(map(str, columns))
|
|
855
|
+
no_columns = columns is None
|
|
856
|
+
|
|
857
|
+
if value is not None:
|
|
858
|
+
if isinstance(value, int):
|
|
859
|
+
value = float(value)
|
|
860
|
+
if isinstance(value, otp.Operation) and value.dtype is int:
|
|
861
|
+
value = value.astype(float)
|
|
862
|
+
dtype = ott.get_object_type(value)
|
|
863
|
+
if dtype is not float:
|
|
864
|
+
raise ValueError(f"The type of parameter 'value' must be float, not {dtype}")
|
|
865
|
+
|
|
866
|
+
def fun(tick):
|
|
867
|
+
for field in otp.tick_descriptor_fields():
|
|
868
|
+
if (field.get_type() == 'double'
|
|
869
|
+
and tick.get_double_value(field.get_name()) == otp.nan
|
|
870
|
+
and (no_columns or field.get_name().isin(*columns))):
|
|
871
|
+
tick.set_double_value(field.get_name(), value)
|
|
872
|
+
else:
|
|
873
|
+
# deque may have already been created by previous call of fillna() method
|
|
874
|
+
if '__OTP_FILLNA_DEQUE__' not in self.state_vars:
|
|
875
|
+
self.state_vars['__OTP_FILLNA_DEQUE__'] = otp.state.tick_deque(scope='branch')
|
|
876
|
+
|
|
877
|
+
def fun(tick):
|
|
878
|
+
i = otp.static(0)
|
|
879
|
+
prev_tick = otp.tick_deque_tick()
|
|
880
|
+
# first tick doesn't have the previous tick, so skipping it
|
|
881
|
+
if i > 0:
|
|
882
|
+
# get the previous tick from the deque
|
|
883
|
+
tick.state_vars['__OTP_FILLNA_DEQUE__'].get_tick(0, prev_tick)
|
|
884
|
+
# replace value in all double fields with the value of the previous tick
|
|
885
|
+
for field in otp.tick_descriptor_fields():
|
|
886
|
+
if (field.get_type() == 'double'
|
|
887
|
+
and tick.get_double_value(field.get_name()) == otp.nan
|
|
888
|
+
and (no_columns or field.get_name().isin(*columns))):
|
|
889
|
+
tick.set_double_value(field.get_name(),
|
|
890
|
+
prev_tick.get_double_value(field.get_name()))
|
|
891
|
+
# clear the deque and add the current tick to it
|
|
892
|
+
tick.state_vars['__OTP_FILLNA_DEQUE__'].clear()
|
|
893
|
+
tick.state_vars['__OTP_FILLNA_DEQUE__'].push_back(tick)
|
|
894
|
+
i = i + 1
|
|
895
|
+
|
|
896
|
+
return self.script(fun, inplace=inplace)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
@inplace_operation
|
|
900
|
+
def mkt_activity(self: 'Source', calendar_name=None, inplace=False) -> Optional['Source']:
|
|
901
|
+
"""
|
|
902
|
+
Adds a string field named **MKT_ACTIVITY** to each tick in the input tick stream.
|
|
903
|
+
|
|
904
|
+
The value of this field is set to the union of session flags that apply for the security at the time of the tick,
|
|
905
|
+
as specified in the calendar sections of the reference database (see Reference Database Guide).
|
|
906
|
+
|
|
907
|
+
Session flags may differ between databases,
|
|
908
|
+
but the following letters have reserved meaning: L - half day, H - holiday, W - weekend, R - regular.
|
|
909
|
+
|
|
910
|
+
The calendar can either be specified explicitly by name
|
|
911
|
+
(the *CALENDAR* sections of the reference database are assumed to contain a calendar with such a name),
|
|
912
|
+
or default to the security- or exchange-level calendars for the queried symbol
|
|
913
|
+
(the *SYMBOL_CALENDAR* or *EXCH_CALENDAR* sections of the reference database).
|
|
914
|
+
|
|
915
|
+
The latter case requires a non-zero symbol date to be specified for queried symbols
|
|
916
|
+
(see parameter ``symbol_date`` in :py:func:`otp.run <onetick.py.run>`).
|
|
917
|
+
|
|
918
|
+
Parameters
|
|
919
|
+
----------
|
|
920
|
+
calendar_name : str, :py:class:`~onetick.py.Column`
|
|
921
|
+
The calendar name to choose for the respective calendar from the *CALENDAR* sections of the reference database.
|
|
922
|
+
It can be a string constant or the name of the field with per-tick calendar name.
|
|
923
|
+
|
|
924
|
+
When this parameter is not specified,
|
|
925
|
+
default security- or exchange-level calendars configured for the queried database and symbol are used
|
|
926
|
+
(but symbol date must be specified in this case).
|
|
927
|
+
inplace : bool
|
|
928
|
+
The flag controls whether operation should be applied inplace or not.
|
|
929
|
+
If ``inplace=True``, then it returns nothing. Otherwise method returns a new modified
|
|
930
|
+
object.
|
|
931
|
+
|
|
932
|
+
Note
|
|
933
|
+
----
|
|
934
|
+
When applying :py:meth:`mkt_activity` to aggregated data,
|
|
935
|
+
please take into account that session flags may change during the aggregation bucket.
|
|
936
|
+
The field **MKT_ACTIVITY**, in this case, will represent the session flags at the time assigned to the bucket,
|
|
937
|
+
which may be different from the session flags at some times during the bucket.
|
|
938
|
+
|
|
939
|
+
Returns
|
|
940
|
+
-------
|
|
941
|
+
:class:`Source` or ``None``
|
|
942
|
+
|
|
943
|
+
See Also
|
|
944
|
+
--------
|
|
945
|
+
**MKT_ACTIVITY** OneTick event processor
|
|
946
|
+
|
|
947
|
+
Examples
|
|
948
|
+
--------
|
|
949
|
+
|
|
950
|
+
By default, security- or exchange-level calendars configured for the queried database and symbol are used
|
|
951
|
+
(but symbol date must be specified in this case):
|
|
952
|
+
|
|
953
|
+
>>> data = otp.DataSource(...) # doctest: +SKIP
|
|
954
|
+
>>> data = data.mkt_activity() # doctest: +SKIP
|
|
955
|
+
>>> otp.run(data, date=otp.date(2022, 1, 1), symbol_date=otp.date(2022, 1, 1)) # doctest: +SKIP
|
|
956
|
+
|
|
957
|
+
Otherwise, parameter ``calendar_name`` must be specified:
|
|
958
|
+
|
|
959
|
+
>>> data = otp.DataSource(...) # doctest: +SKIP
|
|
960
|
+
>>> data = data.mkt_activity(calendar_name='WNY') # doctest: +SKIP
|
|
961
|
+
>>> otp.run(data, date=otp.date(2022, 1, 1)) # doctest: +SKIP
|
|
962
|
+
|
|
963
|
+
Parameter ``calendar_name`` can also be specified as a column.
|
|
964
|
+
In this case calendar name can be different for each tick:
|
|
965
|
+
|
|
966
|
+
>>> data = otp.DataSource(...) # doctest: +SKIP
|
|
967
|
+
>>> data = data.mkt_activity(calendar_name=data['CALENDAR_NAME']) # doctest: +SKIP
|
|
968
|
+
>>> otp.run(data, date=otp.date(2022, 1, 1)) # doctest: +SKIP
|
|
969
|
+
|
|
970
|
+
In this example you can see how market activity status is changing during the days.
|
|
971
|
+
We are getting first and last tick of the group each time the type of market activity is changed.
|
|
972
|
+
You can see regular trades (R) from 9:30 to 16:00, and a holiday (H) on 2018-02-07.
|
|
973
|
+
|
|
974
|
+
>>> data = otp.DataSource('TRAIN_A_PRL_TRD', tick_type='TRD', symbols='MSFT') # doctest: +SKIP
|
|
975
|
+
>>> data = data.mkt_activity('FRED') # doctest: +SKIP
|
|
976
|
+
>>> data = data[['PRICE', 'SIZE', 'MKT_ACTIVITY']] # doctest: +SKIP
|
|
977
|
+
>>> first = data.first(1, bucket_interval=(data['MKT_ACTIVITY'] != data['MKT_ACTIVITY'][-1])) # doctest: +SKIP
|
|
978
|
+
>>> last = data.last(1, bucket_interval=(data['MKT_ACTIVITY'] != data['MKT_ACTIVITY'][-1])) # doctest: +SKIP
|
|
979
|
+
>>> data = otp.merge([first, last]) # doctest: +SKIP
|
|
980
|
+
>>> df = otp.run(data, # doctest: +SKIP
|
|
981
|
+
... start=otp.dt(2018, 2, 1), end=otp.dt(2018, 2, 9),
|
|
982
|
+
... symbol_date=otp.dt(2018, 2, 1), timezone='EST5EDT')
|
|
983
|
+
>>> df[['Time', 'MKT_ACTIVITY']] # doctest: +SKIP
|
|
984
|
+
Time MKT_ACTIVITY
|
|
985
|
+
0 2018-02-01 01:31:44.466
|
|
986
|
+
1 2018-02-01 09:29:59.996
|
|
987
|
+
2 2018-02-01 09:30:00.225 R
|
|
988
|
+
3 2018-02-01 15:59:58.857 R
|
|
989
|
+
4 2018-02-01 16:00:01.858
|
|
990
|
+
5 2018-02-02 09:29:50.366
|
|
991
|
+
6 2018-02-02 09:30:01.847 R
|
|
992
|
+
7 2018-02-02 15:59:59.829 R
|
|
993
|
+
8 2018-02-02 16:00:01.782
|
|
994
|
+
9 2018-02-05 09:29:43.084
|
|
995
|
+
10 2018-02-05 09:30:00.301 R
|
|
996
|
+
11 2018-02-05 15:59:59.974 R
|
|
997
|
+
12 2018-02-05 16:00:02.438
|
|
998
|
+
13 2018-02-06 09:29:27.279
|
|
999
|
+
14 2018-02-06 09:30:00.045 R
|
|
1000
|
+
15 2018-02-06 15:59:59.903 R
|
|
1001
|
+
16 2018-02-06 16:01:03.524
|
|
1002
|
+
17 2018-02-07 09:29:56.739
|
|
1003
|
+
18 2018-02-07 09:30:00.365 H
|
|
1004
|
+
19 2018-02-07 15:59:59.940 H
|
|
1005
|
+
20 2018-02-07 16:00:00.187
|
|
1006
|
+
21 2018-02-08 09:29:28.446
|
|
1007
|
+
22 2018-02-08 09:30:00.658 F
|
|
1008
|
+
23 2018-02-08 15:59:59.564 F
|
|
1009
|
+
24 2018-02-08 16:00:02.355
|
|
1010
|
+
25 2018-02-08 19:59:57.061
|
|
1011
|
+
"""
|
|
1012
|
+
if calendar_name is None:
|
|
1013
|
+
calendar_name = ''
|
|
1014
|
+
if isinstance(calendar_name, str):
|
|
1015
|
+
calendar_field_name = ''
|
|
1016
|
+
elif isinstance(calendar_name, _Column):
|
|
1017
|
+
calendar_field_name = str(calendar_name)
|
|
1018
|
+
calendar_name = ''
|
|
1019
|
+
else:
|
|
1020
|
+
raise ValueError(f"Unsupported type for parameter 'calendar_name': {type(calendar_name)}")
|
|
1021
|
+
self.sink(
|
|
1022
|
+
otq.MktActivity(calendar_name=calendar_name, calendar_field_name=calendar_field_name)
|
|
1023
|
+
)
|
|
1024
|
+
self.schema['MKT_ACTIVITY'] = str
|
|
1025
|
+
return self
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
@inplace_operation
|
|
1029
|
+
def book_diff(self: 'Source', include_initial_book: bool = False, inplace=False) -> Optional['Source']:
|
|
1030
|
+
"""
|
|
1031
|
+
Performs book diffing for every pair of consecutive ticks, each representing a flat book of a fixed depth.
|
|
1032
|
+
|
|
1033
|
+
This method can be thought to be an operation, opposite to :py:meth:`~onetick.py.Source.ob_snapshot_flat`.
|
|
1034
|
+
Every input tick, different from the previous one, results in a series of output PRL ticks to be propagated,
|
|
1035
|
+
each carrying information about a level deletion, addition or update.
|
|
1036
|
+
|
|
1037
|
+
The input of this method is a time series of flat book ticks,
|
|
1038
|
+
just like the result of :py:meth:`~onetick.py.Source.ob_snapshot_flat`.
|
|
1039
|
+
More precisely, for a fixed depth N of the flat book,
|
|
1040
|
+
input ticks should carry the fields BID_<FIELD_NAME>K and ASK_<FIELD_NAME>K,
|
|
1041
|
+
where 1 <= K <= N and <FIELD_NAME> ranges over a specific set of fields F,
|
|
1042
|
+
among which PRICE and SIZE are mandatory.
|
|
1043
|
+
|
|
1044
|
+
The output of this method is a time series of PRL ticks,
|
|
1045
|
+
carrying information about a level deletion, addition, or update.
|
|
1046
|
+
Each tick carries the fields from the above-mentioned set F,
|
|
1047
|
+
plus BUY_SELL_FLAG, RECORD_TYPE, TICK_STATUS, and DELETED_TIME.
|
|
1048
|
+
BUY_SELL_FLAG carries the side (0 - bid, 1 - ask), the rest are for internal use.
|
|
1049
|
+
|
|
1050
|
+
Parameters
|
|
1051
|
+
----------
|
|
1052
|
+
include_initial_book : bool
|
|
1053
|
+
This method treats the first tick as an "initial state" of the book.
|
|
1054
|
+
If this parameter is set to True, then this initial tick will also be in the output.
|
|
1055
|
+
inplace : bool
|
|
1056
|
+
The flag controls whether operation should be applied inplace or not.
|
|
1057
|
+
If ``inplace=True``, then it returns nothing. Otherwise method returns a new modified
|
|
1058
|
+
object.
|
|
1059
|
+
|
|
1060
|
+
Note
|
|
1061
|
+
----
|
|
1062
|
+
Tick descriptor changes are not allowed in this event processor.
|
|
1063
|
+
|
|
1064
|
+
Returns
|
|
1065
|
+
-------
|
|
1066
|
+
:class:`Source` or ``None``
|
|
1067
|
+
|
|
1068
|
+
See Also
|
|
1069
|
+
--------
|
|
1070
|
+
**BOOK_DIFF** OneTick event processor
|
|
1071
|
+
|
|
1072
|
+
Examples
|
|
1073
|
+
--------
|
|
1074
|
+
|
|
1075
|
+
First let's get flat order book snapshot from the database (top level each 12 hours):
|
|
1076
|
+
|
|
1077
|
+
>>> data = otp.DataSource('TRAIN_A_PRL_TRD', tick_type='PRL', symbols='MSFT') # doctest: +SKIP
|
|
1078
|
+
>>> flat = data.ob_snapshot_flat(max_levels=1, bucket_interval=otp.Hour(12)) # doctest: +SKIP
|
|
1079
|
+
>>> otp.run(flat, date=otp.dt(2018, 2, 1)) # doctest: +SKIP
|
|
1080
|
+
Time BID_PRICE1 BID_SIZE1 BID_UPDATE_TIME1 ASK_PRICE1 ASK_SIZE1 ASK_UPDATE_TIME1
|
|
1081
|
+
0 2018-02-01 12:00:00 95.53 1400 2018-02-01 11:59:59.797 95.54 200 2018-02-01 11:59:59.978
|
|
1082
|
+
1 2018-02-02 00:00:00 94.52 100 2018-02-01 19:57:02.502 94.90 250 2018-02-01 18:35:38.543
|
|
1083
|
+
|
|
1084
|
+
Then we can apply ``book_diff`` method to the result to get the PRL ticks again:
|
|
1085
|
+
|
|
1086
|
+
>>> diff = flat.book_diff() # doctest: +SKIP
|
|
1087
|
+
>>> otp.run(diff, date=otp.dt(2018, 2, 1)) # doctest: +SKIP
|
|
1088
|
+
Time PRICE SIZE UPDATE_TIME BUY_SELL_FLAG RECORD_TYPE TICK_STATUS DELETED_TIME
|
|
1089
|
+
0 2018-02-02 95.53 0 2018-02-01 11:59:59.797 0 R 0 1969-12-31 19:00:00
|
|
1090
|
+
1 2018-02-02 94.52 100 2018-02-01 19:57:02.502 0 R 0 1969-12-31 19:00:00
|
|
1091
|
+
2 2018-02-02 94.90 250 2018-02-01 18:35:38.543 1 R 0 1969-12-31 19:00:00
|
|
1092
|
+
3 2018-02-02 95.54 0 2018-02-01 11:59:59.978 1 R 0 1969-12-31 19:00:00
|
|
1093
|
+
|
|
1094
|
+
By default the first tick in the query range is not included,
|
|
1095
|
+
use parameter ``include_initial_book`` to include it:
|
|
1096
|
+
|
|
1097
|
+
>>> diff = flat.book_diff(include_initial_book=True) # doctest: +SKIP
|
|
1098
|
+
>>> otp.run(diff, date=otp.dt(2018, 2, 1)) # doctest: +SKIP
|
|
1099
|
+
Time PRICE SIZE UPDATE_TIME BUY_SELL_FLAG RECORD_TYPE TICK_STATUS DELETED_TIME
|
|
1100
|
+
0 2018-02-01 12:00:00 95.53 1400 2018-02-01 11:59:59.797 0 R 0 1969-12-31 19:00:00
|
|
1101
|
+
1 2018-02-01 12:00:00 95.54 200 2018-02-01 11:59:59.978 1 R 0 1969-12-31 19:00:00
|
|
1102
|
+
2 2018-02-02 00:00:00 95.53 0 2018-02-01 11:59:59.797 0 R 0 1969-12-31 19:00:00
|
|
1103
|
+
3 2018-02-02 00:00:00 94.52 100 2018-02-01 19:57:02.502 0 R 0 1969-12-31 19:00:00
|
|
1104
|
+
4 2018-02-02 00:00:00 94.90 250 2018-02-01 18:35:38.543 1 R 0 1969-12-31 19:00:00
|
|
1105
|
+
5 2018-02-02 00:00:00 95.54 0 2018-02-01 11:59:59.978 1 R 0 1969-12-31 19:00:00
|
|
1106
|
+
"""
|
|
1107
|
+
self.sink(otq.BookDiff(include_initial_book=include_initial_book))
|
|
1108
|
+
return self
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
@inplace_operation
|
|
1112
|
+
def limit(self: 'Source', tick_limit: int, inplace=False) -> Optional['Source']:
|
|
1113
|
+
"""
|
|
1114
|
+
Propagates ticks until the count limit is reached. Once the limit is reached,
|
|
1115
|
+
hidden ticks will still continue to propagate until the next regular tick appears.
|
|
1116
|
+
|
|
1117
|
+
Parameters
|
|
1118
|
+
----------
|
|
1119
|
+
tick_limit: int
|
|
1120
|
+
The number of regular ticks to propagate. Must be a non-negative integer or -1, which will mean no limit.
|
|
1121
|
+
inplace : bool
|
|
1122
|
+
The flag controls whether operation should be applied inplace or not.
|
|
1123
|
+
If ``inplace=True``, then it returns nothing. Otherwise method returns a new modified
|
|
1124
|
+
object.
|
|
1125
|
+
|
|
1126
|
+
Returns
|
|
1127
|
+
-------
|
|
1128
|
+
:class:`Source` or ``None``
|
|
1129
|
+
|
|
1130
|
+
See Also
|
|
1131
|
+
--------
|
|
1132
|
+
**LIMIT** OneTick event processor
|
|
1133
|
+
|
|
1134
|
+
Examples
|
|
1135
|
+
--------
|
|
1136
|
+
|
|
1137
|
+
Basic example
|
|
1138
|
+
|
|
1139
|
+
.. testcode::
|
|
1140
|
+
:skipif: not otp.compatibility.is_limit_ep_supported()
|
|
1141
|
+
|
|
1142
|
+
data = otp.Ticks(X=[1, 2, 3, 4, 5, 6])
|
|
1143
|
+
data = data.limit(tick_limit=3)
|
|
1144
|
+
print(otp.run(data))
|
|
1145
|
+
|
|
1146
|
+
.. testoutput::
|
|
1147
|
+
|
|
1148
|
+
Time X
|
|
1149
|
+
0 2003-12-01 00:00:00.000 1
|
|
1150
|
+
1 2003-12-01 00:00:00.001 2
|
|
1151
|
+
2 2003-12-01 00:00:00.002 3
|
|
1152
|
+
|
|
1153
|
+
Disable limit
|
|
1154
|
+
|
|
1155
|
+
.. testcode::
|
|
1156
|
+
:skipif: not otp.compatibility.is_limit_ep_supported()
|
|
1157
|
+
|
|
1158
|
+
data = otp.Ticks(X=[1, 2, 3, 4, 5, 6])
|
|
1159
|
+
data = data.limit(tick_limit=-1)
|
|
1160
|
+
print(otp.run(data))
|
|
1161
|
+
|
|
1162
|
+
.. testoutput::
|
|
1163
|
+
|
|
1164
|
+
Time X
|
|
1165
|
+
0 2003-12-01 00:00:00.000 1
|
|
1166
|
+
1 2003-12-01 00:00:00.001 2
|
|
1167
|
+
2 2003-12-01 00:00:00.002 3
|
|
1168
|
+
3 2003-12-01 00:00:00.003 4
|
|
1169
|
+
4 2003-12-01 00:00:00.004 5
|
|
1170
|
+
5 2003-12-01 00:00:00.005 6
|
|
1171
|
+
"""
|
|
1172
|
+
if not hasattr(otq, 'Limit'):
|
|
1173
|
+
raise RuntimeError('LIMIT EP isn\'t supported by the current OneTick version.')
|
|
1174
|
+
|
|
1175
|
+
if tick_limit < 0 and tick_limit != -1:
|
|
1176
|
+
raise ValueError('Negative values, except -1, not allowed as `tick_limit` in `limit` method.')
|
|
1177
|
+
|
|
1178
|
+
self.sink(otq.Limit(tick_limit=tick_limit))
|
|
1179
|
+
return self
|
|
1180
|
+
|
|
1181
|
+
|
|
1182
|
+
def _merge_fields_by_regex(schema: dict, columns_regex: str, match_index: int):
|
|
1183
|
+
new_columns: Dict[str, type] = {}
|
|
1184
|
+
for column, column_type in list(schema.items()):
|
|
1185
|
+
match = re.match(columns_regex, column)
|
|
1186
|
+
if match:
|
|
1187
|
+
new_column = match.group(match_index)
|
|
1188
|
+
|
|
1189
|
+
if new_column in schema:
|
|
1190
|
+
raise KeyError(
|
|
1191
|
+
f'Can\'t apply `show_full_detail` for column `{column}`: '
|
|
1192
|
+
f'column `{new_column}` is already in schema.'
|
|
1193
|
+
)
|
|
1194
|
+
|
|
1195
|
+
if new_column in new_columns:
|
|
1196
|
+
if column_type != new_columns[new_column]:
|
|
1197
|
+
raise TypeError(
|
|
1198
|
+
f'Can\'t apply `show_full_detail`: '
|
|
1199
|
+
f'type mismatch for columns with suffix `{new_column}`.'
|
|
1200
|
+
)
|
|
1201
|
+
else:
|
|
1202
|
+
new_columns[new_column] = column_type
|
|
1203
|
+
|
|
1204
|
+
del schema[column]
|
|
1205
|
+
|
|
1206
|
+
schema.update(**new_columns)
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
@inplace_operation
|
|
1210
|
+
def virtual_ob(
|
|
1211
|
+
self: 'Source',
|
|
1212
|
+
quote_source_fields: Optional[List[Union[str, _Column]]] = None,
|
|
1213
|
+
quote_timeout: Optional[float] = None,
|
|
1214
|
+
show_full_detail: bool = False,
|
|
1215
|
+
output_book_format: Literal['ob', 'prl'] = 'ob',
|
|
1216
|
+
inplace=False,
|
|
1217
|
+
) -> Optional['Source']:
|
|
1218
|
+
"""
|
|
1219
|
+
Creates a series of fake orders from a time series of best bids and asks. The algorithm used is as follows:
|
|
1220
|
+
For a tick with the best bid or ask, create an order tick adding the new best bid or ask and an order
|
|
1221
|
+
withdrawing the old one. Virtual order books can be created for multiple subgroups at once
|
|
1222
|
+
by using the ``quote_source_fields`` parameter to specify a list of string fields to be used for grouping.
|
|
1223
|
+
A separate book will be created for each combination.
|
|
1224
|
+
|
|
1225
|
+
Parameters
|
|
1226
|
+
----------
|
|
1227
|
+
quote_source_fields: Optional[List[Union[str, Column]]]
|
|
1228
|
+
Specifies a list of string fields for grouping quotes.
|
|
1229
|
+
The virtual order book is then constructed for each subgroup separately and
|
|
1230
|
+
the ``SOURCE`` field is constructed to contain the description of the group.
|
|
1231
|
+
quote_timeout: Optional[float]
|
|
1232
|
+
Specifies the maximum age of a quote that is not stale.
|
|
1233
|
+
A quote that is not replaced after more than ``quote_timeout`` seconds is considered stale,
|
|
1234
|
+
and delete orders will be generated for it.
|
|
1235
|
+
|
|
1236
|
+
A value of ``quote_timeout`` can be fractional (for example, 3.51)
|
|
1237
|
+
show_full_detail: bool
|
|
1238
|
+
If set to ``True``, **virtual_ob** will attempt to include
|
|
1239
|
+
all fields in the input tick when forming the output tick.
|
|
1240
|
+
|
|
1241
|
+
``ASK_X/BID_X`` fields will combine under paired field ``X``
|
|
1242
|
+
``ASK_X`` and ``BID_X`` must have the same type.
|
|
1243
|
+
|
|
1244
|
+
If only ``ASK_X`` or ``BID_X`` exist, output will have ``X`` and the missing field
|
|
1245
|
+
will be assumed to have its default value.
|
|
1246
|
+
|
|
1247
|
+
Paired and non-paired fields must not interfere with each other and the fields originally added by this EP
|
|
1248
|
+
output_book_format: 'prl' or 'ob'
|
|
1249
|
+
Supported values are ``prl`` and ``ob``. When set to ``prl``, field ``SIZE`` of output ticks represents
|
|
1250
|
+
current size for the tick's source, price, and side, and the EP propagates ``PRICE`` and ``SOURCE``
|
|
1251
|
+
as the state keys of its output time series.
|
|
1252
|
+
|
|
1253
|
+
When set to ``ob``, field ``SIZE`` of output ticks represents the delta of size for
|
|
1254
|
+
the tick's source, price, and side, and the state key of the output ticks is empty.
|
|
1255
|
+
inplace : bool
|
|
1256
|
+
The flag controls whether operation should be applied inplace or not.
|
|
1257
|
+
If ``inplace=True``, then it returns nothing. Otherwise, method returns a new modified object.
|
|
1258
|
+
|
|
1259
|
+
Returns
|
|
1260
|
+
-------
|
|
1261
|
+
:class:`Source` or ``None``
|
|
1262
|
+
|
|
1263
|
+
See Also
|
|
1264
|
+
--------
|
|
1265
|
+
**VIRTUAL_OB** OneTick event processor
|
|
1266
|
+
|
|
1267
|
+
Examples
|
|
1268
|
+
--------
|
|
1269
|
+
|
|
1270
|
+
Basic example
|
|
1271
|
+
|
|
1272
|
+
>>> data = otp.DataSource(
|
|
1273
|
+
... db='US_COMP', symbols='AAPL', tick_type='QTE', date=otp.date(2003, 12, 1)
|
|
1274
|
+
... ) # doctest: +SKIP
|
|
1275
|
+
>>> data = data[['ASK_PRICE', 'ASK_SIZE', 'BID_PRICE', 'BID_SIZE']] # doctest: +SKIP
|
|
1276
|
+
>>> data = data.virtual_ob() # doctest: +SKIP
|
|
1277
|
+
>>> otp.run(data) # doctest: +SKIP
|
|
1278
|
+
Time PRICE DELETED_TIME SIZE BUY_SELL_FLAG TICK_STATUS SOURCE
|
|
1279
|
+
0 2003-12-01 00:00:00.000 22.28 1969-12-31 19:00:00 500 1 0 AAPL
|
|
1280
|
+
1 2003-12-01 00:00:00.000 21.66 1969-12-31 19:00:00 100 0 0 AAPL
|
|
1281
|
+
2 2003-12-01 00:00:00.001 21.8 1969-12-31 19:00:00 500 1 0 AAPL
|
|
1282
|
+
...
|
|
1283
|
+
|
|
1284
|
+
Specify columns to group quotes
|
|
1285
|
+
|
|
1286
|
+
>>> data = otp.DataSource(
|
|
1287
|
+
... db='US_COMP', symbols='AAPL', tick_type='QTE', date=otp.date(2003, 12, 1)
|
|
1288
|
+
... ) # doctest: +SKIP
|
|
1289
|
+
>>> data = data[['ASK_PRICE', 'ASK_SIZE', 'BID_PRICE', 'BID_SIZE', 'EXCHANGE']] # doctest: +SKIP
|
|
1290
|
+
>>> data = data.virtual_ob(['EXCHANGE']) # doctest: +SKIP
|
|
1291
|
+
>>> otp.run(data) # doctest: +SKIP
|
|
1292
|
+
Time PRICE DELETED_TIME SIZE BUY_SELL_FLAG TICK_STATUS SOURCE
|
|
1293
|
+
0 2003-12-01 00:00:00.000 22.28 1969-12-31 19:00:00 500 1 0 D
|
|
1294
|
+
1 2003-12-01 00:00:00.000 21.66 1969-12-31 19:00:00 100 0 0 D
|
|
1295
|
+
2 2003-12-01 00:00:00.001 21.8 1969-12-31 19:00:00 500 1 0 P
|
|
1296
|
+
...
|
|
1297
|
+
"""
|
|
1298
|
+
kwargs: Dict[str, Any] = {}
|
|
1299
|
+
|
|
1300
|
+
required_fields = {'BID_PRICE', 'BID_SIZE', 'ASK_PRICE', 'ASK_SIZE'}
|
|
1301
|
+
missing_fields = required_fields - set(self.schema.keys())
|
|
1302
|
+
|
|
1303
|
+
if missing_fields:
|
|
1304
|
+
raise ValueError('Missing fields required by `virtual_ob`: ' + ', '.join(missing_fields))
|
|
1305
|
+
|
|
1306
|
+
if output_book_format not in {'ob', 'prl'}:
|
|
1307
|
+
raise ValueError(
|
|
1308
|
+
f'Incorrect value for `output_book_format` parameter: {output_book_format}. '
|
|
1309
|
+
f'Supported values: \'ob\' or \'prl\''
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
if show_full_detail and output_book_format != 'prl':
|
|
1313
|
+
raise ValueError('`output_book_format` should be set to \'prl\' when `show_full_detail` set to `True`')
|
|
1314
|
+
|
|
1315
|
+
is_pnl_and_show_full_detail_supported = is_ob_virtual_prl_and_show_full_detail_supported()
|
|
1316
|
+
|
|
1317
|
+
if show_full_detail and not is_pnl_and_show_full_detail_supported:
|
|
1318
|
+
raise RuntimeError('`virtual_ob` not supports `show_full_detail` parameter on this OneTick version')
|
|
1319
|
+
|
|
1320
|
+
if output_book_format != 'ob' and not is_pnl_and_show_full_detail_supported:
|
|
1321
|
+
raise RuntimeError('`virtual_ob` not supports `output_book_format` parameter on this OneTick version')
|
|
1322
|
+
|
|
1323
|
+
if is_pnl_and_show_full_detail_supported:
|
|
1324
|
+
kwargs['show_full_detail'] = show_full_detail
|
|
1325
|
+
kwargs['output_book_format'] = output_book_format
|
|
1326
|
+
|
|
1327
|
+
if quote_source_fields is None:
|
|
1328
|
+
quote_source_fields = []
|
|
1329
|
+
|
|
1330
|
+
quote_source_fields_list = list(map(str, quote_source_fields))
|
|
1331
|
+
for column in quote_source_fields_list:
|
|
1332
|
+
if column not in self.schema:
|
|
1333
|
+
raise ValueError(f'Column \'{column}\' passed in `quote_source_fields` parameter is missing in the schema')
|
|
1334
|
+
|
|
1335
|
+
self.sink(
|
|
1336
|
+
otq.VirtualOb(
|
|
1337
|
+
quote_source_fields=','.join(quote_source_fields_list),
|
|
1338
|
+
quote_timeout='' if quote_timeout is None else str(quote_timeout),
|
|
1339
|
+
**kwargs,
|
|
1340
|
+
)
|
|
1341
|
+
)
|
|
1342
|
+
|
|
1343
|
+
schema = {
|
|
1344
|
+
'PRICE': float,
|
|
1345
|
+
'DELETED_TIME': otp.nsectime,
|
|
1346
|
+
'SIZE': float,
|
|
1347
|
+
'BUY_SELL_FLAG': int,
|
|
1348
|
+
'TICK_STATUS': int,
|
|
1349
|
+
'SOURCE': str,
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
if show_full_detail:
|
|
1353
|
+
exclude_fields = list(required_fields) + quote_source_fields_list
|
|
1354
|
+
schema.update({
|
|
1355
|
+
k: v for k, v in self.schema.copy().items()
|
|
1356
|
+
if k not in exclude_fields
|
|
1357
|
+
})
|
|
1358
|
+
|
|
1359
|
+
_merge_fields_by_regex(schema, r'^(ASK|BID)_(.*)$', 2)
|
|
1360
|
+
|
|
1361
|
+
self.schema.set(**schema)
|
|
1362
|
+
|
|
1363
|
+
return self
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
@copy_signature(otp.functions.corp_actions, add_self=True, drop_parameters=['source'])
|
|
1367
|
+
def corp_actions(self: 'Source', *args, **kwargs) -> 'Source':
|
|
1368
|
+
"""
|
|
1369
|
+
Adjusts values using corporate actions information loaded into OneTick
|
|
1370
|
+
from the reference data file. To use it, location of reference database must
|
|
1371
|
+
be specified via OneTick configuration.
|
|
1372
|
+
|
|
1373
|
+
Parameters
|
|
1374
|
+
----------
|
|
1375
|
+
adjustment_date : :py:class:`onetick.py.date`, :py:class:`onetick.py.datetime`, int, str, None, optional
|
|
1376
|
+
The date as of which the values are adjusted.
|
|
1377
|
+
All corporate actions of the types specified in the parameters
|
|
1378
|
+
that lie between the tick timestamp and the adjustment date will be applied to each tick.
|
|
1379
|
+
|
|
1380
|
+
This parameter can be either date or datetime .
|
|
1381
|
+
`int` and `str` format can be *YYYYMMDD* or *YYYYMMDDhhmmss*.
|
|
1382
|
+
When parameter is a date, the time is assumed to be 17:00:00 GMT
|
|
1383
|
+
and parameter ``adjustment_date_tz`` is ignored.
|
|
1384
|
+
|
|
1385
|
+
If it is not set, the values are adjusted as of the end date in the query.
|
|
1386
|
+
|
|
1387
|
+
Notice that the ``adjustment date`` is not affected neither by *_SYMBOL_PARAM._PARAM_END_TIME_NANOS*
|
|
1388
|
+
nor by the *apply_times_daily* setting in :py:func:`onetick.py.run`.
|
|
1389
|
+
|
|
1390
|
+
adjustment_date_tz : str, optional
|
|
1391
|
+
Timezone for ``adjustment date``.
|
|
1392
|
+
|
|
1393
|
+
By default global :py:attr:`tz<onetick.py.configuration.Config.tz>` value is used.
|
|
1394
|
+
Local timezone can't be used so in this case parameter is set to GMT.
|
|
1395
|
+
When ``adjustment_date`` is in YYYYMMDD format, this parameter is set to GMT.
|
|
1396
|
+
fields : str, optional
|
|
1397
|
+
A comma-separated list of fields to be adjusted. If this parameter is not set,
|
|
1398
|
+
some default adjustments will take place if appropriately named fields exist in the tick:
|
|
1399
|
+
|
|
1400
|
+
- If the ``adjust_rule`` parameter is set to PRICE, and the PRICE field is present,
|
|
1401
|
+
it will get adjusted. If the fields ASK_PRICE or BID_PRICE are present, they will get adjusted.
|
|
1402
|
+
If fields ASK_VALUE or BID_VALUE are present, they will get adjusted
|
|
1403
|
+
|
|
1404
|
+
- If the ``adjust_rule`` parameter is set to SIZE, and the SIZE field is present,
|
|
1405
|
+
it will get adjusted. If the fields ASK_SIZE or BID_SIZE are present, they will get adjusted.
|
|
1406
|
+
If fields ASK_VALUE or BID_VALUE are present, they will get adjusted.
|
|
1407
|
+
|
|
1408
|
+
adjust_rule : str, optional
|
|
1409
|
+
When set to PRICE, adjustments are applied under the assumption that fields to be adjusted contain prices
|
|
1410
|
+
(adjustment direction is determined appropriately).
|
|
1411
|
+
|
|
1412
|
+
When set to SIZE, adjustments are applied under the assumption that fields contain sizes
|
|
1413
|
+
(adjustment direction is opposite to that when the parameter's value is PRICE).
|
|
1414
|
+
|
|
1415
|
+
By default the value is PRICE.
|
|
1416
|
+
apply_split : bool, optional
|
|
1417
|
+
If True, adjustments for splits are applied.
|
|
1418
|
+
apply_spinoff : bool, optional
|
|
1419
|
+
If True, adjustments for spin-offs are applied.
|
|
1420
|
+
apply_cash_dividend : bool, optional
|
|
1421
|
+
If True, adjustments for cash dividends are applied.
|
|
1422
|
+
apply_stock_dividend : bool, optional
|
|
1423
|
+
If True, adjustments for stock dividends are applied.
|
|
1424
|
+
apply_security_splice : bool, optional
|
|
1425
|
+
If True, adjustments for security splices are applied.
|
|
1426
|
+
apply_others : str, optional
|
|
1427
|
+
A comma-separated list of names of custom adjustment types to apply.
|
|
1428
|
+
apply_all : bool, optional
|
|
1429
|
+
If True, applies all types of adjustments, both built-in and custom.
|
|
1430
|
+
|
|
1431
|
+
Returns
|
|
1432
|
+
-------
|
|
1433
|
+
:py:class:`onetick.py.Source`
|
|
1434
|
+
A new source object with applied adjustments.
|
|
1435
|
+
|
|
1436
|
+
See also
|
|
1437
|
+
--------
|
|
1438
|
+
**CORP_ACTIONS** OneTick event processor
|
|
1439
|
+
|
|
1440
|
+
Examples
|
|
1441
|
+
--------
|
|
1442
|
+
>>> src = otp.DataSource('US_COMP',
|
|
1443
|
+
... tick_type='TRD',
|
|
1444
|
+
... start=otp.dt(2022, 5, 20, 9, 30),
|
|
1445
|
+
... end=otp.dt(2022, 5, 26, 16))
|
|
1446
|
+
>>> df = otp.run(src, symbols='MKD', symbol_date=otp.date(2022, 5, 22))
|
|
1447
|
+
>>> df["PRICE"][0]
|
|
1448
|
+
0.0911
|
|
1449
|
+
>>> src = src.corp_actions(adjustment_date=otp.date(2022, 5, 22),
|
|
1450
|
+
... fields="PRICE")
|
|
1451
|
+
>>> df = otp.run(src, symbols='MKD', symbol_date=otp.date(2022, 5, 22))
|
|
1452
|
+
>>> df["PRICE"][0]
|
|
1453
|
+
1.36649931675
|
|
1454
|
+
"""
|
|
1455
|
+
return otp.functions.corp_actions(self, *args, **kwargs)
|