onetick-py 1.162.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. locator_parser/__init__.py +0 -0
  2. locator_parser/acl.py +73 -0
  3. locator_parser/actions.py +266 -0
  4. locator_parser/common.py +365 -0
  5. locator_parser/io.py +41 -0
  6. locator_parser/locator.py +150 -0
  7. onetick/__init__.py +101 -0
  8. onetick/doc_utilities/__init__.py +3 -0
  9. onetick/doc_utilities/napoleon.py +40 -0
  10. onetick/doc_utilities/ot_doctest.py +140 -0
  11. onetick/doc_utilities/snippets.py +280 -0
  12. onetick/lib/__init__.py +4 -0
  13. onetick/lib/instance.py +138 -0
  14. onetick/py/__init__.py +290 -0
  15. onetick/py/_stack_info.py +89 -0
  16. onetick/py/_version.py +2 -0
  17. onetick/py/aggregations/__init__.py +11 -0
  18. onetick/py/aggregations/_base.py +645 -0
  19. onetick/py/aggregations/_docs.py +912 -0
  20. onetick/py/aggregations/compute.py +286 -0
  21. onetick/py/aggregations/functions.py +2216 -0
  22. onetick/py/aggregations/generic.py +104 -0
  23. onetick/py/aggregations/high_low.py +80 -0
  24. onetick/py/aggregations/num_distinct.py +83 -0
  25. onetick/py/aggregations/order_book.py +427 -0
  26. onetick/py/aggregations/other.py +1014 -0
  27. onetick/py/backports.py +26 -0
  28. onetick/py/cache.py +373 -0
  29. onetick/py/callback/__init__.py +5 -0
  30. onetick/py/callback/callback.py +275 -0
  31. onetick/py/callback/callbacks.py +131 -0
  32. onetick/py/compatibility.py +752 -0
  33. onetick/py/configuration.py +736 -0
  34. onetick/py/core/__init__.py +0 -0
  35. onetick/py/core/_csv_inspector.py +93 -0
  36. onetick/py/core/_internal/__init__.py +0 -0
  37. onetick/py/core/_internal/_manually_bound_value.py +6 -0
  38. onetick/py/core/_internal/_nodes_history.py +250 -0
  39. onetick/py/core/_internal/_op_utils/__init__.py +0 -0
  40. onetick/py/core/_internal/_op_utils/every_operand.py +9 -0
  41. onetick/py/core/_internal/_op_utils/is_const.py +10 -0
  42. onetick/py/core/_internal/_per_tick_scripts/tick_list_sort_template.script +121 -0
  43. onetick/py/core/_internal/_proxy_node.py +140 -0
  44. onetick/py/core/_internal/_state_objects.py +2307 -0
  45. onetick/py/core/_internal/_state_vars.py +87 -0
  46. onetick/py/core/_source/__init__.py +0 -0
  47. onetick/py/core/_source/_symbol_param.py +95 -0
  48. onetick/py/core/_source/schema.py +97 -0
  49. onetick/py/core/_source/source_methods/__init__.py +0 -0
  50. onetick/py/core/_source/source_methods/aggregations.py +810 -0
  51. onetick/py/core/_source/source_methods/applyers.py +296 -0
  52. onetick/py/core/_source/source_methods/columns.py +141 -0
  53. onetick/py/core/_source/source_methods/data_quality.py +301 -0
  54. onetick/py/core/_source/source_methods/debugs.py +270 -0
  55. onetick/py/core/_source/source_methods/drops.py +120 -0
  56. onetick/py/core/_source/source_methods/fields.py +619 -0
  57. onetick/py/core/_source/source_methods/filters.py +1001 -0
  58. onetick/py/core/_source/source_methods/joins.py +1393 -0
  59. onetick/py/core/_source/source_methods/merges.py +566 -0
  60. onetick/py/core/_source/source_methods/misc.py +1325 -0
  61. onetick/py/core/_source/source_methods/pandases.py +155 -0
  62. onetick/py/core/_source/source_methods/renames.py +356 -0
  63. onetick/py/core/_source/source_methods/sorts.py +183 -0
  64. onetick/py/core/_source/source_methods/switches.py +142 -0
  65. onetick/py/core/_source/source_methods/symbols.py +117 -0
  66. onetick/py/core/_source/source_methods/times.py +627 -0
  67. onetick/py/core/_source/source_methods/writes.py +702 -0
  68. onetick/py/core/_source/symbol.py +202 -0
  69. onetick/py/core/_source/tmp_otq.py +222 -0
  70. onetick/py/core/column.py +209 -0
  71. onetick/py/core/column_operations/__init__.py +0 -0
  72. onetick/py/core/column_operations/_methods/__init__.py +4 -0
  73. onetick/py/core/column_operations/_methods/_internal.py +28 -0
  74. onetick/py/core/column_operations/_methods/conversions.py +215 -0
  75. onetick/py/core/column_operations/_methods/methods.py +294 -0
  76. onetick/py/core/column_operations/_methods/op_types.py +150 -0
  77. onetick/py/core/column_operations/accessors/__init__.py +0 -0
  78. onetick/py/core/column_operations/accessors/_accessor.py +30 -0
  79. onetick/py/core/column_operations/accessors/decimal_accessor.py +92 -0
  80. onetick/py/core/column_operations/accessors/dt_accessor.py +464 -0
  81. onetick/py/core/column_operations/accessors/float_accessor.py +160 -0
  82. onetick/py/core/column_operations/accessors/str_accessor.py +1374 -0
  83. onetick/py/core/column_operations/base.py +1061 -0
  84. onetick/py/core/cut_builder.py +149 -0
  85. onetick/py/core/db_constants.py +20 -0
  86. onetick/py/core/eval_query.py +244 -0
  87. onetick/py/core/lambda_object.py +442 -0
  88. onetick/py/core/multi_output_source.py +193 -0
  89. onetick/py/core/per_tick_script.py +2253 -0
  90. onetick/py/core/query_inspector.py +465 -0
  91. onetick/py/core/source.py +1663 -0
  92. onetick/py/db/__init__.py +2 -0
  93. onetick/py/db/_inspection.py +1042 -0
  94. onetick/py/db/db.py +1423 -0
  95. onetick/py/db/utils.py +64 -0
  96. onetick/py/docs/__init__.py +0 -0
  97. onetick/py/docs/docstring_parser.py +112 -0
  98. onetick/py/docs/utils.py +81 -0
  99. onetick/py/functions.py +2354 -0
  100. onetick/py/license.py +188 -0
  101. onetick/py/log.py +88 -0
  102. onetick/py/math.py +947 -0
  103. onetick/py/misc.py +437 -0
  104. onetick/py/oqd/__init__.py +22 -0
  105. onetick/py/oqd/eps.py +1195 -0
  106. onetick/py/oqd/sources.py +325 -0
  107. onetick/py/otq.py +211 -0
  108. onetick/py/pyomd_mock.py +47 -0
  109. onetick/py/run.py +841 -0
  110. onetick/py/servers.py +173 -0
  111. onetick/py/session.py +1342 -0
  112. onetick/py/sources/__init__.py +19 -0
  113. onetick/py/sources/cache.py +167 -0
  114. onetick/py/sources/common.py +126 -0
  115. onetick/py/sources/csv.py +642 -0
  116. onetick/py/sources/custom.py +85 -0
  117. onetick/py/sources/data_file.py +305 -0
  118. onetick/py/sources/data_source.py +1049 -0
  119. onetick/py/sources/empty.py +94 -0
  120. onetick/py/sources/odbc.py +337 -0
  121. onetick/py/sources/order_book.py +238 -0
  122. onetick/py/sources/parquet.py +168 -0
  123. onetick/py/sources/pit.py +191 -0
  124. onetick/py/sources/query.py +495 -0
  125. onetick/py/sources/snapshots.py +419 -0
  126. onetick/py/sources/split_query_output_by_symbol.py +198 -0
  127. onetick/py/sources/symbology_mapping.py +123 -0
  128. onetick/py/sources/symbols.py +357 -0
  129. onetick/py/sources/ticks.py +825 -0
  130. onetick/py/sql.py +70 -0
  131. onetick/py/state.py +256 -0
  132. onetick/py/types.py +2056 -0
  133. onetick/py/utils/__init__.py +70 -0
  134. onetick/py/utils/acl.py +93 -0
  135. onetick/py/utils/config.py +186 -0
  136. onetick/py/utils/default.py +49 -0
  137. onetick/py/utils/file.py +38 -0
  138. onetick/py/utils/helpers.py +76 -0
  139. onetick/py/utils/locator.py +94 -0
  140. onetick/py/utils/perf.py +499 -0
  141. onetick/py/utils/query.py +49 -0
  142. onetick/py/utils/render.py +1139 -0
  143. onetick/py/utils/script.py +244 -0
  144. onetick/py/utils/temp.py +471 -0
  145. onetick/py/utils/types.py +118 -0
  146. onetick/py/utils/tz.py +82 -0
  147. onetick_py-1.162.2.dist-info/METADATA +148 -0
  148. onetick_py-1.162.2.dist-info/RECORD +152 -0
  149. onetick_py-1.162.2.dist-info/WHEEL +5 -0
  150. onetick_py-1.162.2.dist-info/entry_points.txt +2 -0
  151. onetick_py-1.162.2.dist-info/licenses/LICENSE +21 -0
  152. onetick_py-1.162.2.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1001 @@
1
+ import re
2
+ import warnings
3
+ from contextlib import suppress
4
+ from datetime import time
5
+ from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union
6
+ from onetick.py.backports import Literal
7
+
8
+ from onetick import py as otp
9
+ from onetick.py import types as ott
10
+ from onetick.py import utils
11
+ from onetick.py.core.column import _Column
12
+ from onetick.py.core.column_operations.base import _Operation
13
+ from onetick.py.core.eval_query import _QueryEvalWrapper
14
+ from onetick.py.otq import otq
15
+
16
+ from .misc import inplace_operation
17
+
18
+ if TYPE_CHECKING:
19
+ from onetick.py.core.source import Source
20
+
21
+
22
+ def if_else(self: 'Source', condition: _Operation, if_expr, else_expr) -> 'otp.Column':
23
+ """
24
+ Shortcut for :meth:`~onetick.py.Source.apply` with lambda if-else expression
25
+
26
+ Parameters
27
+ ----------
28
+ condition: :class:`Operation`
29
+ - condition for matching ticks
30
+
31
+ if_expr: :class:`Operation`, value
32
+ - value or `Operation` to set if `condition` is true
33
+
34
+ else_expr: :class:`Operation`, value
35
+ - value or `Operation` to set if `condition` is false
36
+
37
+ Returns
38
+ -------
39
+ Column
40
+
41
+ Examples
42
+ --------
43
+ Basic example of apply if-else to a tick flow:
44
+
45
+ >>> data = otp.Ticks(X=[1, 2, 3])
46
+ >>> data['Y'] = data.if_else(data['X'] > 2, 1, 0)
47
+ >>> otp.run(data)
48
+ Time X Y
49
+ 0 2003-12-01 00:00:00.000 1 0
50
+ 1 2003-12-01 00:00:00.001 2 0
51
+ 2 2003-12-01 00:00:00.002 3 1
52
+
53
+ You can also set column value via :class:`Operation`:
54
+
55
+ >>> data = otp.Ticks(X=[1, 2, 3])
56
+ >>> data['Y'] = data.if_else(data['X'] > 2, data['X'] * 2, 0)
57
+ >>> otp.run(data)
58
+ Time X Y
59
+ 0 2003-12-01 00:00:00.000 1 0
60
+ 1 2003-12-01 00:00:00.001 2 0
61
+ 2 2003-12-01 00:00:00.002 3 6
62
+
63
+ See Also
64
+ --------
65
+ :py:meth:`onetick.py.Source.apply`
66
+ """
67
+ return self.apply(lambda tick: if_expr if condition else else_expr)
68
+
69
+
70
+ def where_clause(
71
+ self: 'Source', condition, discard_on_match: bool = False, stop_on_first_mismatch: bool = False
72
+ ) -> Tuple['Source', 'Source']:
73
+ """
74
+ Split source in two branches depending on ``condition``:
75
+ one branch with ticks that meet the condition and
76
+ the other branch with ticks that don't meet the condition.
77
+
78
+ Original source object is not modified.
79
+
80
+ Parameters
81
+ ----------
82
+ condition: :class:`Operation`, :func:`eval`
83
+ Condition expression to filter ticks or object evaluating another query.
84
+ In the latter case another query should have only one tick as a result with only one field.
85
+ discard_on_match: bool
86
+ Inverts the ``condition``.
87
+
88
+ Ticks that don't meet the condition will be returned in the first branch,
89
+ and ticks that meet the condition will be returned in the second branch.
90
+ stop_on_first_mismatch: bool
91
+ If set, no ticks will be propagated in the first branch
92
+ starting with the first tick that does not meet the ``condition``.
93
+
94
+ Other branch will contain all ticks starting with the first mismatch, even if they don't meet the condition.
95
+
96
+ See Also
97
+ --------
98
+ | :meth:`Source.where`
99
+ | :meth:`Source.__getitem__`
100
+ | **WHERE_CLAUSE** OneTick event processor
101
+
102
+ Examples
103
+ --------
104
+
105
+ Filtering based on expression:
106
+
107
+ >>> data = otp.Ticks(X=[1, 2, 3, 4])
108
+ >>> odd, even = data.where_clause(data['X'] % 2 == 1)
109
+ >>> otp.run(odd)
110
+ Time X
111
+ 0 2003-12-01 00:00:00.000 1
112
+ 1 2003-12-01 00:00:00.002 3
113
+ >>> otp.run(even)
114
+ Time X
115
+ 0 2003-12-01 00:00:00.001 2
116
+ 1 2003-12-01 00:00:00.003 4
117
+
118
+ Filtering based on the result of another query:
119
+
120
+ >>> another_query = otp.Tick(WHERE='mod(X, 2) = 1')
121
+ >>> data = otp.Ticks(X=[1, 2, 3, 4])
122
+ >>> data, _ = data.where_clause(otp.eval(another_query))
123
+ >>> otp.run(data)
124
+ Time X
125
+ 0 2003-12-01 00:00:00.000 1
126
+ 1 2003-12-01 00:00:00.002 3
127
+
128
+ Using ``discard_on_match`` parameter to invert the condition:
129
+
130
+ >>> data = otp.Ticks(X=[1, 2, 3, 4])
131
+ >>> even, odd = data.where_clause(data['X'] % 2 == 1, discard_on_match=True)
132
+ >>> otp.run(even)
133
+ Time X
134
+ 0 2003-12-01 00:00:00.001 2
135
+ 1 2003-12-01 00:00:00.003 4
136
+ >>> otp.run(odd)
137
+ Time X
138
+ 0 2003-12-01 00:00:00.000 1
139
+ 1 2003-12-01 00:00:00.002 3
140
+
141
+ Using ``stop_on_first_mismatch`` parameter to not propagate ticks after first mismatch:
142
+
143
+ >>> data = otp.Ticks(X=[1, 2, 3, 4])
144
+ >>> data, other = data.where_clause(data['X'] % 2 == 1, stop_on_first_mismatch=True)
145
+ >>> otp.run(data)
146
+ Time X
147
+ 0 2003-12-01 1
148
+
149
+ But other branch will contain all ticks after the mismatch, even if they don't meet the condition:
150
+
151
+ >>> otp.run(other)
152
+ Time X
153
+ 0 2003-12-01 00:00:00.001 2
154
+ 1 2003-12-01 00:00:00.002 3
155
+ 2 2003-12-01 00:00:00.003 4
156
+ """
157
+ if not isinstance(condition, (_Operation, _QueryEvalWrapper)):
158
+ raise TypeError(f"Unsupported type of value for 'condition' parameter: {type(condition)}")
159
+
160
+ if isinstance(condition, _Operation):
161
+ condition = condition._make_python_way_bool_expression()
162
+ if isinstance(condition, _QueryEvalWrapper):
163
+ condition = condition.to_eval_string(self._tmp_otq)
164
+ where = self.copy(
165
+ ep=otq.WhereClause(
166
+ where=str(condition), discard_on_match=discard_on_match, stop_on_first_mismatch=stop_on_first_mismatch
167
+ )
168
+ )
169
+
170
+ if_source = where.copy()
171
+ if_source.node().out_pin("IF")
172
+
173
+ else_source = where.copy()
174
+ else_source.node().out_pin("ELSE")
175
+ # TODO: add ability to remove then this ep, because it is required only for right output
176
+ else_source.sink(otq.Passthrough())
177
+
178
+ return if_source, else_source
179
+
180
+
181
+ def where(self: 'Source', condition, discard_on_match: bool = False, stop_on_first_mismatch: bool = False) -> 'Source':
182
+ """
183
+ Filter ticks that meet the ``condition``.
184
+
185
+ Returns new object, original source object is not modified.
186
+
187
+ Parameters
188
+ ----------
189
+ condition: :class:`Operation`, :func:`eval`
190
+ Condition expression to filter ticks or object evaluating another query.
191
+ In the latter case another query should have only one tick as a result with only one field.
192
+
193
+ discard_on_match: bool
194
+ Inverts the ``condition``.
195
+
196
+ Ticks that don't meet the condition will be returned.
197
+
198
+ stop_on_first_mismatch: bool
199
+ If set, no ticks will be propagated starting with the first tick that does not meet the ``condition``.
200
+
201
+ See Also
202
+ --------
203
+ | :meth:`Source.where_clause`
204
+ | :meth:`Source.__getitem__`
205
+ | **WHERE_CLAUSE** OneTick event processor
206
+
207
+ Examples
208
+ --------
209
+
210
+ Filtering based on expression:
211
+
212
+ >>> data = otp.Ticks(X=[1, 2, 3, 4])
213
+ >>> data = data.where(data['X'] % 2 == 1)
214
+ >>> otp.run(data)
215
+ Time X
216
+ 0 2003-12-01 00:00:00.000 1
217
+ 1 2003-12-01 00:00:00.002 3
218
+
219
+ Filtering based on the result of another query:
220
+
221
+ >>> another_query = otp.Tick(WHERE='mod(X, 2) = 1')
222
+ >>> data = otp.Ticks(X=[1, 2, 3, 4])
223
+ >>> data = data.where(otp.eval(another_query))
224
+ >>> otp.run(data)
225
+ Time X
226
+ 0 2003-12-01 00:00:00.000 1
227
+ 1 2003-12-01 00:00:00.002 3
228
+
229
+ Using ``discard_on_match`` parameter to invert the condition:
230
+
231
+ >>> data = otp.Ticks(X=[1, 2, 3, 4])
232
+ >>> data = data.where(data['X'] % 2 == 1, discard_on_match=True)
233
+ >>> otp.run(data)
234
+ Time X
235
+ 0 2003-12-01 00:00:00.001 2
236
+ 1 2003-12-01 00:00:00.003 4
237
+
238
+ Using ``stop_on_first_mismatch`` parameter to not propagate ticks after first mismatch:
239
+
240
+ >>> data = otp.Ticks(X=[1, 2, 3, 4])
241
+ >>> data = data.where(data['X'] % 2 == 1, stop_on_first_mismatch=True)
242
+ >>> otp.run(data)
243
+ Time X
244
+ 0 2003-12-01 1
245
+ """
246
+ return self.where_clause(
247
+ condition, discard_on_match=discard_on_match, stop_on_first_mismatch=stop_on_first_mismatch
248
+ )[0]
249
+
250
+
251
+ def _get_integer_slice(self: 'Source', item: slice) -> Optional['Source']:
252
+ """
253
+ Treat otp.Source object as a sequence of ticks
254
+ and apply common python integer slicing logic to it.
255
+ """
256
+ start, stop, step = item.start, item.stop, item.step
257
+ for v in (start, stop, step):
258
+ if v is not None and not isinstance(v, int):
259
+ return None
260
+
261
+ # let's filter out cases that we don't want to support
262
+ if step is not None and step <= 0:
263
+ raise ValueError("step value can't be negative or zero")
264
+ if stop is not None and stop == 0:
265
+ raise ValueError("stop value can't be zero")
266
+ if start and stop and start > 0 and stop > 0 and start >= stop:
267
+ raise ValueError("stop value can't be less than start")
268
+ if start and start < 0 and stop and stop > 0:
269
+ raise ValueError("start value can't be negative when start value is positive")
270
+
271
+ def add_counter(src, force=False):
272
+ if '__NUM__' not in src.schema or force:
273
+ if '__NUM__' in src.schema:
274
+ src = src.drop('__NUM__')
275
+ src = src.agg({'__NUM__': otp.agg.count()}, running=True, all_fields=True)
276
+ return src
277
+
278
+ result = self.copy()
279
+ if start:
280
+ if start > 0:
281
+ result = add_counter(result)
282
+ result, _ = result[result['__NUM__'] > start]
283
+ if start < 0:
284
+ result = result.last(-start)
285
+ if stop:
286
+ if stop > 0:
287
+ result = add_counter(result)
288
+ result, _ = result[result['__NUM__'] <= stop]
289
+ if stop < 0:
290
+ result = add_counter(result)
291
+ last_ticks = result.last(-stop)
292
+ last_ticks['__FLAG__'] = 1
293
+ last_ticks = last_ticks[['__FLAG__', '__NUM__']]
294
+ result = otp.join(
295
+ result, last_ticks, on=result['__NUM__'] == last_ticks['__NUM__'], how='left_outer', rprefix='RIGHT'
296
+ )
297
+ result, _ = result[result['__FLAG__'] == 0]
298
+ result = result.drop(['__FLAG__', 'RIGHT___NUM__'])
299
+ if step:
300
+ if step > 0: # NOSONAR
301
+ # resetting counter
302
+ result = add_counter(result, force=True)
303
+ result, _ = result[(result['__NUM__'] - 1) % step == 0]
304
+ if '__NUM__' in result.schema:
305
+ result = result.drop('__NUM__')
306
+ return result
307
+
308
+
309
+ def __getitem__(self: 'Source', item):
310
+ """
311
+ Allows to express multiple things:
312
+
313
+ - access a field by name
314
+
315
+ - filter ticks by condition
316
+
317
+ - select subset of fields
318
+
319
+ - set order of fields
320
+
321
+ Parameters
322
+ ----------
323
+ item: str, :class:`Operation`, :func:`eval`, list of str
324
+
325
+ - ``str`` is to access column by name or columns specified by regex.
326
+
327
+ - ``Operation`` to express filter condition.
328
+
329
+ - ``otp.eval`` to express filter condition based on external query
330
+
331
+ - ``List[str]`` select subset of specified columns or columns specified in regexes.
332
+
333
+ - ``slice[List[str]::]`` set order of columns
334
+
335
+ - ``slice[Tuple[str, Type]::]`` type defaulting
336
+
337
+ - ``slice[:]`` alias to :meth:`Source.copy()`
338
+
339
+ - ``slice[int:int:int]`` select ticks the same way as elements in python lists
340
+
341
+ Returns
342
+ -------
343
+ Column, Source or tuple of Sources
344
+ - Column if column name was specified.
345
+
346
+ - Two sources if filtering expression or eval was provided: the first one is for ticks that pass condition
347
+ and the second one that do not.
348
+
349
+ Examples
350
+ --------
351
+
352
+ Access to the `X` column: add `Y` based on `X`
353
+
354
+ >>> data = otp.Ticks(X=[1, 2, 3])
355
+ >>> data['Y'] = data['X'] * 2
356
+ >>> otp.run(data)
357
+ Time X Y
358
+ 0 2003-12-01 00:00:00.000 1 2
359
+ 1 2003-12-01 00:00:00.001 2 4
360
+ 2 2003-12-01 00:00:00.002 3 6
361
+
362
+ Filtering based on expression:
363
+
364
+ >>> data = otp.Ticks(X=[1, 2, 3])
365
+ >>> data_more, data_less = data[(data['X'] > 2)]
366
+ >>> otp.run(data_more)
367
+ Time X
368
+ 0 2003-12-01 00:00:00.002 3
369
+ >>> otp.run(data_less)
370
+ Time X
371
+ 0 2003-12-01 00:00:00.000 1
372
+ 1 2003-12-01 00:00:00.001 2
373
+
374
+ Filtering based on the result of another query. Another query should
375
+ have only one tick as a result with only one field (whatever it names).
376
+
377
+ >>> exp_to_select = otp.Ticks(WHERE=['X > 2'])
378
+ >>> data = otp.Ticks(X=[1, 2, 3], Y=['a', 'b', 'c'], Z=[.4, .3, .1])
379
+ >>> data, _ = data[otp.eval(exp_to_select)]
380
+ >>> otp.run(data)
381
+ Time X Y Z
382
+ 0 2003-12-01 00:00:00.002 3 c 0.1
383
+
384
+ Select subset of specified columns:
385
+
386
+ >>> data = otp.Ticks(X=[1, 2, 3], Y=['a', 'b', 'c'], Z=[.4, .3, .1])
387
+ >>> data = data[['X', 'Z']]
388
+ >>> otp.run(data)
389
+ Time X Z
390
+ 0 2003-12-01 00:00:00.000 1 0.4
391
+ 1 2003-12-01 00:00:00.001 2 0.3
392
+ 2 2003-12-01 00:00:00.002 3 0.1
393
+
394
+ Slice with list will keep all columns, but change order:
395
+
396
+ >>> data=otp.Tick(Y=1, X=2, Z=3)
397
+ >>> otp.run(data)
398
+ Time Y X Z
399
+ 0 2003-12-01 1 2 3
400
+ >>> data = data[['X', 'Y']:]
401
+ >>> otp.run(data)
402
+ Time X Y Z
403
+ 0 2003-12-01 2 1 3
404
+
405
+ Slice can be used as short-cut for :meth:`Source.copy`:
406
+
407
+ >>> data[:] # doctest: +ELLIPSIS
408
+ <onetick.py.sources.ticks.Tick object at ...>
409
+
410
+ Slices can use integers.
411
+ In this case ticks are selected the same way as elements in python lists.
412
+
413
+ >>> data = otp.Ticks({'A': [1, 2, 3, 4, 5]})
414
+
415
+ Select first 3 ticks:
416
+
417
+ >>> otp.run(data[:3])
418
+ Time A
419
+ 0 2003-12-01 00:00:00.000 1
420
+ 1 2003-12-01 00:00:00.001 2
421
+ 2 2003-12-01 00:00:00.002 3
422
+
423
+ Skip first 3 ticks:
424
+
425
+ >>> otp.run(data[3:])
426
+ Time A
427
+ 0 2003-12-01 00:00:00.003 4
428
+ 1 2003-12-01 00:00:00.004 5
429
+
430
+ Select last 3 ticks:
431
+
432
+ >>> otp.run(data[-3:])
433
+ Time A
434
+ 0 2003-12-01 00:00:00.002 3
435
+ 1 2003-12-01 00:00:00.003 4
436
+ 2 2003-12-01 00:00:00.004 5
437
+
438
+ Skip last 3 ticks:
439
+
440
+ >>> otp.run(data[:-3])
441
+ Time A
442
+ 0 2003-12-01 00:00:00.000 1
443
+ 1 2003-12-01 00:00:00.001 2
444
+
445
+ Skip first and last tick:
446
+
447
+ >>> otp.run(data[1:-1])
448
+ Time A
449
+ 0 2003-12-01 00:00:00.001 2
450
+ 1 2003-12-01 00:00:00.002 3
451
+ 2 2003-12-01 00:00:00.003 4
452
+
453
+ Select every second tick:
454
+
455
+ >>> otp.run(data[::2])
456
+ Time A
457
+ 0 2003-12-01 00:00:00.000 1
458
+ 1 2003-12-01 00:00:00.002 3
459
+ 2 2003-12-01 00:00:00.004 5
460
+
461
+ Select every second tick, not including first and last tick:
462
+
463
+ >>> otp.run(data[1:-1:2])
464
+ Time A
465
+ 0 2003-12-01 00:00:00.001 2
466
+ 1 2003-12-01 00:00:00.003 4
467
+
468
+ Regular expressions can be used to select fields too:
469
+
470
+ >>> data = otp.Tick(A=1, AA=2, AB=3, B=4, BB=5, BA=6)
471
+ >>> otp.run(data['A.*'])
472
+ Time A AA AB BA
473
+ 0 2003-12-01 1 2 3 6
474
+
475
+ Note that by default pattern is matched in any position of the string.
476
+ Use characters ^ and $ to specify start and end of the string:
477
+
478
+ >>> otp.run(data['^A'])
479
+ Time A AA AB
480
+ 0 2003-12-01 1 2 3
481
+
482
+ Several regular expressions can be specified too:
483
+
484
+ >>> otp.run(data[['^A+$', '^B+$']])
485
+ Time A AA B BB
486
+ 0 2003-12-01 1 2 4 5
487
+
488
+ See Also
489
+ --------
490
+ | :meth:`Source.table`: another and more generic way to select subset of specified columns
491
+ | **PASSTHROUGH** OneTick event processor
492
+ | **WHERE_CLAUSE** OneTick event processor
493
+
494
+ """
495
+
496
+ strict = True
497
+
498
+ with suppress(TypeError):
499
+ return self.where_clause(item)
500
+
501
+ if isinstance(item, slice):
502
+
503
+ result = self._get_integer_slice(item)
504
+ if result:
505
+ return result
506
+
507
+ if item.step:
508
+ raise AttributeError("Source columns slice with step set makes no sense")
509
+ if item.start and item.stop:
510
+ raise AttributeError("Source columns slice with both start and stop set is not available now")
511
+ if not item.start and item.stop:
512
+ raise AttributeError("Source columns slice with only stop set is not implemented yet")
513
+ if item.start is None and item.stop is None:
514
+ return self.copy()
515
+
516
+ item = item.start
517
+ strict = False
518
+
519
+ if isinstance(item, tuple):
520
+ item = dict([item])
521
+
522
+ elif isinstance(item, list):
523
+ if not item:
524
+ return self.copy()
525
+ item_type = list(set([type(x) for x in item]))
526
+
527
+ if len(item_type) > 1:
528
+ raise AttributeError(f"Different types {item_type} in slice list is not supported")
529
+ if item_type[0] == tuple:
530
+ item = dict(item)
531
+
532
+ if isinstance(item, (list, str)):
533
+ # check if item has regex characters
534
+ item_list = [item] if isinstance(item, str) else item
535
+ try:
536
+ items_to_passthrough, use_regex = self._columns_names_regex(item_list)
537
+ except TypeError:
538
+ use_regex = False
539
+ if use_regex:
540
+ src = self.copy()
541
+ src.sink(otq.Passthrough(fields=','.join(items_to_passthrough), use_regex=True))
542
+ return src
543
+
544
+ if isinstance(item, list):
545
+ # ---------
546
+ # TABLE
547
+ # ---------
548
+ items = []
549
+
550
+ for it in item:
551
+ if isinstance(it, _Column):
552
+ items.append(it.name)
553
+ elif isinstance(it, str):
554
+ items.append(it)
555
+ else:
556
+ raise ValueError(f"It is not supported to filter '{it}' object of '{type(it)}' type")
557
+
558
+ # validation
559
+ for item in items:
560
+ if item not in self.schema:
561
+ existing_columns = ", ".join(self.schema.keys())
562
+ raise AttributeError(f"There is no '{item}' column. There are existing columns: {existing_columns}")
563
+
564
+ columns = {
565
+ column_name: self.schema[column_name] for column_name in items if not self._check_key_is_meta(column_name)
566
+ }
567
+
568
+ return self.table(strict=strict, **columns)
569
+
570
+ if isinstance(item, dict):
571
+ return self.table(strict=strict, **item)
572
+
573
+ # way to set type
574
+ if isinstance(item, tuple):
575
+ name, dtype = item
576
+ warnings.warn(
577
+ 'Using tuple with name and type in otp.Source.__getitem__() is not supported anymore,'
578
+ ' change your code to use otp.Source.schema object instead.',
579
+ FutureWarning,
580
+ )
581
+ return self._set_field_by_tuple(name, dtype)
582
+
583
+ name = item
584
+ if name not in self.__dict__:
585
+ raise KeyError(
586
+ f'Column name {name} is not in the schema. Please, check that this column '
587
+ 'is in the schema or add it using the .schema property'
588
+ )
589
+ if not isinstance(self.__dict__[name], _Column):
590
+ raise AttributeError(f"There is no '{name}' column")
591
+ return self.__dict__[name]
592
+
593
+
594
+ @inplace_operation
595
+ def dropna(
596
+ self: 'Source', how: Literal["any", "all"] = "any", subset: Optional[List[Any]] = None, inplace=False
597
+ ) -> Optional['Source']:
598
+ """
599
+ Drops ticks that contain NaN values according to the policy in the ``how`` parameter
600
+
601
+ Parameters
602
+ ----------
603
+ how: "any" or "all"
604
+
605
+ ``any`` - filters out ticks if at least one field has NaN value
606
+
607
+ ``all`` - filters out ticks if all fields have NaN values.
608
+ subset: list of str
609
+ list of columns to check for NaN values. If ``None`` then all columns are checked.
610
+ inplace: bool
611
+ the flag controls whether operation should be applied inplace.
612
+
613
+ Returns
614
+ -------
615
+ :class:`Source` or ``None``
616
+
617
+ Examples
618
+ --------
619
+
620
+ Drop ticks where **at least one** field has ``nan`` value.
621
+
622
+ >>> data = otp.Ticks([[ 'X', 'Y'],
623
+ ... [ 0.0, 1.0],
624
+ ... [ otp.nan, 2.0],
625
+ ... [ 4.0, otp.nan],
626
+ ... [ otp.nan, otp.nan],
627
+ ... [ 6.0, 7.0]])
628
+ >>> data = data.dropna()
629
+ >>> otp.run(data)[['X', 'Y']]
630
+ X Y
631
+ 0 0.0 1.0
632
+ 1 6.0 7.0
633
+
634
+ Drop ticks where **all** fields have ``nan`` values.
635
+
636
+ >>> data = otp.Ticks([[ 'X', 'Y'],
637
+ ... [ 0.0, 1.0],
638
+ ... [ otp.nan, 2.0],
639
+ ... [ 4.0, otp.nan],
640
+ ... [ otp.nan, otp.nan],
641
+ ... [ 6.0, 7.0]])
642
+ >>> data = data.dropna(how='all')
643
+ >>> otp.run(data)[['X', 'Y']]
644
+ X Y
645
+ 0 0.0 1.0
646
+ 1 NaN 2.0
647
+ 2 4.0 NaN
648
+ 3 6.0 7.0
649
+
650
+ Drop ticks where **all** fields in **subset** of columns have ``nan`` values.
651
+
652
+ >>> data = otp.Ticks([[ 'X', 'Y', 'Z'],
653
+ ... [ 0.0, 1.0, otp.nan],
654
+ ... [ otp.nan, 2.0, otp.nan],
655
+ ... [ 4.0, otp.nan, otp.nan],
656
+ ... [ otp.nan, otp.nan, otp.nan],
657
+ ... [ 6.0, 7.0, otp.nan]])
658
+ >>> data = data.dropna(how='all', subset=['X', 'Y'])
659
+ >>> otp.run(data)[['X', 'Y', 'Z']]
660
+ X Y Z
661
+ 0 0.0 1.0 NaN
662
+ 1 NaN 2.0 NaN
663
+ 2 4.0 NaN NaN
664
+ 3 6.0 7.0 NaN
665
+
666
+ """
667
+ if how not in ["any", "all"]:
668
+ raise ValueError(f"It is expected to see 'any' or 'all' values for 'how' parameter, but got '{how}'")
669
+
670
+ condition = None
671
+ columns = self.columns(skip_meta_fields=True)
672
+ if subset is not None:
673
+ for column_name in subset:
674
+ if column_name not in columns:
675
+ raise ValueError(f"There is no '{column_name}' column in the source")
676
+ if columns[column_name] is not float:
677
+ raise ValueError(f"Column '{column_name}' is not float type")
678
+
679
+ for column_name, dtype in columns.items():
680
+ if subset is not None and column_name not in subset:
681
+ continue
682
+ if dtype is float:
683
+ if condition is None:
684
+ condition = self[column_name] != ott.nan
685
+ else:
686
+ if how == "any":
687
+ condition &= self[column_name] != ott.nan
688
+ elif how == "all":
689
+ condition |= self[column_name] != ott.nan
690
+
691
+ self.sink(otq.WhereClause(where=str(condition)))
692
+ return self
693
+
694
+
695
+ @inplace_operation
696
+ def time_filter(
697
+ self: 'Source',
698
+ discard_on_match: bool = False,
699
+ start_time: Union[str, int, time] = 0,
700
+ end_time: Union[str, int, time] = 0,
701
+ day_patterns: Union[str, List[str]] = "",
702
+ timezone=utils.default, # type: ignore
703
+ end_time_tick_matches: bool = False,
704
+ inplace=False,
705
+ ) -> Optional['Source']:
706
+ """
707
+ Filters ticks by time.
708
+
709
+ Parameters
710
+ ----------
711
+ discard_on_match : bool, optional
712
+ If ``True``, then ticks that match the filter will be discarded.
713
+ Otherwise, only ticks that match the filter will be passed.
714
+ start_time : str or int or :py:class:`datetime.time`, optional
715
+ Start time of the filter, string must be in the format ``HHMMSSmmm``.
716
+ Default value is 0.
717
+ end_time : str or int or :py:class:`datetime.time`, optional
718
+ End time of the filter, string must be in the format ``HHMMSSmmm``.
719
+ To filter ticks for an entire day, this parameter should be set to 240000000.
720
+ Default value is 0.
721
+ day_patterns : list or str
722
+ Pattern or list of patterns that determines days for which the ticks can be propagated.
723
+ A tick can be propagated if its date matches one or more of the patterns.
724
+ Three supported pattern formats are:
725
+
726
+ 1. ``month.week.weekdays``, 0 month means any month, 0 week means any week,
727
+ 6 week means the last week of the month for a given weekday(s),
728
+ weekdays are digits for each day, 0 being Sunday.
729
+
730
+ 2. ``month/day``, 0 month means any month.
731
+
732
+ 3. ``year/month/day``, 0 year means any year, 0 month means any month.
733
+
734
+ timezone : str, optional
735
+ Timezone of the filter.
736
+ Default value is ``configuration.config.tz``
737
+ or timezone set in the parameter of :py:func:`onetick.py.run`.
738
+ end_time_tick_matches : bool, optional
739
+ If ``True``, then the end time is inclusive.
740
+ Otherwise, the end time is exclusive.
741
+ inplace : bool, optional
742
+ The flag controls whether operation should be applied inplace or not.
743
+ If ``inplace=True``, then it returns nothing. Otherwise method returns a new modified
744
+ object. Default value is ``False``.
745
+
746
+ Returns
747
+ -------
748
+ :class:`Source` or ``None``
749
+ Returns ``None`` if ``inplace=True``.
750
+
751
+ See also
752
+ --------
753
+ **TIME_FILTER** OneTick event processor
754
+
755
+ Examples
756
+ --------
757
+ >>> data = otp.DataSource(db='US_COMP', tick_type='TRD', symbols='AAPL')
758
+ >>> data = data.time_filter(start_time='000000001', end_time='000000003')
759
+ >>> otp.run(data, start=otp.dt(2022, 3, 1), end=otp.dt(2022, 3, 2))
760
+ Time PRICE SIZE
761
+ 0 2022-03-01 00:00:00.001 1.4 10
762
+ 1 2022-03-01 00:00:00.002 1.4 50
763
+
764
+ """
765
+ if timezone is utils.default:
766
+ # doesn't work without expr for some reason
767
+ timezone = 'expr(_TIMEZONE)'
768
+
769
+ if day_patterns:
770
+ if isinstance(day_patterns, str):
771
+ day_patterns = [day_patterns]
772
+ for day_pattern in day_patterns:
773
+ if not re.match(r"(^\d\d?\.[0-6].\d\d?$)|(^\d\d?\/\d\d?$)|(^\d{1,4}\/\d\d?\/\d\d?$)", day_pattern):
774
+ raise ValueError(f"Invalid day pattern: {day_pattern}")
775
+
776
+ if isinstance(start_time, time):
777
+ start_time = start_time.strftime('%H%M%S%f')[:-3]
778
+
779
+ if isinstance(end_time, time):
780
+ end_time = end_time.strftime('%H%M%S%f')[:-3]
781
+
782
+ day_patterns = ",".join(day_patterns)
783
+ self.sink(
784
+ otq.TimeFilter(
785
+ discard_on_match=discard_on_match,
786
+ start_time=start_time,
787
+ end_time=end_time,
788
+ timezone=timezone,
789
+ day_patterns=day_patterns,
790
+ end_time_tick_matches=end_time_tick_matches,
791
+ )
792
+ )
793
+ return self
794
+
795
+
796
+ @inplace_operation
797
+ def skip_bad_tick(
798
+ self: 'Source',
799
+ field: Union[str, _Column],
800
+ discard_on_match: bool = False,
801
+ jump_threshold: float = 2.0,
802
+ num_neighbor_ticks: int = 5,
803
+ use_absolute_values: bool = False,
804
+ inplace=False,
805
+ ) -> Optional['Source']:
806
+ """
807
+ Discards ticks based on whether the value of the attribute specified by ``field`` differs from the value
808
+ of the same attribute in the surrounding ticks more times than a given threshold.
809
+ Uses SKIP_BAD_TICK EP.
810
+
811
+ Parameters
812
+ ----------
813
+ field: str, :py:class:`~onetick.py.Column`
814
+ Name of the field (must be present in the input tick descriptor).
815
+ discard_on_match: bool
816
+ When set to ``True`` only ticks that did not match the filter are propagated,
817
+ otherwise ticks that satisfy the filter condition are propagated.
818
+ jump_threshold: float
819
+ A threshold to determine if a tick is "good" or "bad."
820
+
821
+ Good ticks are the ticks whose ``field`` value differs less than ``jump_threshold`` times
822
+ from the ``field``'s value of less than or half of the surrounding ``num_neighbor_ticks`` ticks.
823
+ num_neighbor_ticks: int
824
+ The number of ticks before this tick and after this tick to compare a tick against.
825
+ use_absolute_values: bool
826
+ When set to ``True``, use absolute values of numbers when checking whether they are within the jump threshold.
827
+ inplace: bool
828
+ The flag controls whether operation should be applied inplace or not.
829
+ If ``inplace=True``, then it returns nothing.
830
+ Otherwise, method returns a new modified object.
831
+
832
+ See also
833
+ --------
834
+ **SKIP_BAD_TICK** OneTick event processor
835
+
836
+ Returns
837
+ -------
838
+ :class:`Source` or ``None``
839
+
840
+ Examples
841
+ --------
842
+ Keep ticks whose price did not jump by more than 20% relative to the surrounding ticks:
843
+
844
+ >>> data = otp.Ticks(X=[10, 11, 15, 11, 9, 10])
845
+ >>> data = data.skip_bad_tick(field="X", jump_threshold=1.2, num_neighbor_ticks=1)
846
+ >>> otp.run(data)
847
+ Time X
848
+ 0 2003-12-01 00:00:00.000 10
849
+ 1 2003-12-01 00:00:00.001 11
850
+ 2 2003-12-01 00:00:00.003 11
851
+ 3 2003-12-01 00:00:00.005 10
852
+
853
+ Same example, but with passing column as ``field`` parameter:
854
+
855
+ >>> data = otp.Ticks(X=[10, 11, 15, 11, 9, 10])
856
+ >>> data = data.skip_bad_tick(field=data["X"], jump_threshold=1.2, num_neighbor_ticks=1)
857
+ >>> otp.run(data)
858
+ Time X
859
+ 0 2003-12-01 00:00:00.000 10
860
+ 1 2003-12-01 00:00:00.001 11
861
+ 2 2003-12-01 00:00:00.003 11
862
+ 3 2003-12-01 00:00:00.005 10
863
+
864
+ If you want to keep only "bad ticks", which don't match the filter,
865
+ set ``discard_on_match`` parameter to ``True``:
866
+
867
+ >>> data = otp.Ticks(X=[10, 11, 15, 11, 9, 10])
868
+ >>> data = data.skip_bad_tick(field=data["X"], jump_threshold=1.2, num_neighbor_ticks=1, discard_on_match=True)
869
+ >>> otp.run(data)
870
+ Time X
871
+ 0 2003-12-01 00:00:00.002 15
872
+ 1 2003-12-01 00:00:00.004 9
873
+
874
+ In case, if you need to compare values on an absolute basis, set ``use_absolute_values`` parameter to ``True``:
875
+
876
+ >>> data = otp.Ticks(X=[10, -11, -15, 11, 9, 10])
877
+ >>> data = data.skip_bad_tick(field=data["X"], jump_threshold=1.2, num_neighbor_ticks=1, use_absolute_values=True)
878
+ >>> otp.run(data)
879
+ Time X
880
+ 0 2003-12-01 00:00:00.000 10
881
+ 1 2003-12-01 00:00:00.001 -11
882
+ 2 2003-12-01 00:00:00.003 11
883
+ 3 2003-12-01 00:00:00.005 10
884
+ """
885
+ if isinstance(field, _Column):
886
+ field = field.name
887
+
888
+ if field not in self.schema:
889
+ raise ValueError(f'Field {field} not in the schema.')
890
+
891
+ self.sink(otq.SkipBadTick(
892
+ discard_on_match=discard_on_match,
893
+ jump_threshold=jump_threshold,
894
+ field=field,
895
+ num_neighbor_ticks=num_neighbor_ticks,
896
+ use_absolute_values=use_absolute_values,
897
+ ))
898
+
899
+ return self
900
+
901
+
902
+ @inplace_operation
903
+ def character_present(
904
+ self: 'Source',
905
+ field: Union[str, _Column],
906
+ characters: Union[str, List[str]],
907
+ characters_field: Union[str, _Column] = "",
908
+ discard_on_match: bool = False,
909
+ inplace: bool = False,
910
+ ):
911
+ """
912
+ Propagates ticks based on whether the value of the field specified by `field` contains a character
913
+ in the set of characters specified by `characters`.
914
+ Uses **CHARACTER_PRESENT** EP.
915
+
916
+ Parameters
917
+ ----------
918
+ field: str, :py:class:`~onetick.py.Column`
919
+ Name of the field (must be present in the input tick descriptor).
920
+ characters: str, List[str]
921
+ A set of characters that are searched for in the value of the `field`.
922
+ If set as string, works as list of characters.
923
+ characters_field: str, :py:class:`~onetick.py.Column`
924
+ If specified, will take a current value of that field and append it to `characters`, if any.
925
+ discard_on_match: bool
926
+ When set to ``True`` only ticks that did not match the filter are propagated,
927
+ otherwise ticks that satisfy the filter condition are propagated.
928
+ inplace: bool
929
+ The flag controls whether operation should be applied inplace or not.
930
+ If ``inplace=True``, then it returns nothing.
931
+ Otherwise, method returns a new modified object.
932
+
933
+ See also
934
+ --------
935
+ **CHARACTER_PRESENT** OneTick event processor
936
+
937
+ Returns
938
+ -------
939
+ :class:`Source` or ``None``
940
+
941
+ Examples
942
+ --------
943
+
944
+ Select ticks that have the N or T in EXCHANGE field
945
+
946
+ >>> data = otp.DataSource('TEST_DATABASE', tick_type='TRD', symbols='A') # doctest: +SKIP
947
+ >>> data = data[['PRICE', 'SIZE', 'EXCHANGE']] # doctest: +SKIP
948
+ >>> data = data.character_present(field=data['EXCHANGE'], characters='NT') # doctest: +SKIP
949
+ >>> otp.run(data) # doctest: +SKIP
950
+ Time PRICE SIZE EXCHANGE
951
+ 0 2003-12-01 00:00:00.000 28.44 55100 N
952
+ 1 2003-12-01 00:00:00.001 28.44 100 T
953
+ 2 2003-12-01 00:00:00.002 28.44 200 T
954
+ 3 2003-12-01 00:00:00.003 28.45 100 T
955
+ 4 2003-12-01 00:00:00.004 28.44 500 T
956
+
957
+ Select ticks that have the N or T in EXCHANGE field and character set in OLD_EXCHANGE field
958
+
959
+ >>> data = otp.DataSource('TEST_DATABASE', tick_type='TRD', symbols='A') # doctest: +SKIP
960
+ >>> data = data.character_present( # doctest: +SKIP
961
+ ... field=data['EXCHANGE'], characters='NT', characters_field=data['OLD_EXCHANGE'],
962
+ ... )
963
+ >>> data = data[['PRICE', 'SIZE', 'EXCHANGE']] # doctest: +SKIP
964
+ >>> otp.run(data) # doctest: +SKIP
965
+ Time PRICE SIZE EXCHANGE
966
+ 0 2003-12-01 00:00:00.000 28.44 55100 N
967
+ 1 2003-12-01 00:00:00.001 28.44 100 B
968
+ 2 2003-12-01 00:00:00.002 28.44 200 B
969
+ 3 2003-12-01 00:00:00.003 28.45 100 T
970
+ 4 2003-12-01 00:00:00.004 28.44 200 T
971
+ """
972
+ if isinstance(field, _Column):
973
+ field = field.name
974
+
975
+ if isinstance(characters_field, _Column):
976
+ characters_field = characters_field.name
977
+
978
+ if isinstance(characters, list):
979
+ characters = ''.join(characters)
980
+
981
+ for name, value in zip(['field', 'characters_field'], [field, characters_field]):
982
+ if not value:
983
+ continue
984
+
985
+ if value not in self.schema:
986
+ raise ValueError(f'Field {value}, passed as parameter `{name}`, not in the schema.')
987
+
988
+ if not (self.schema[value] == str or issubclass(self.schema[value], otp.string)):
989
+ raise TypeError(
990
+ f'Field {value}, passed as parameter `{name}`, has incompatible type: {self.schema[value]}, '
991
+ f'expected: str',
992
+ )
993
+
994
+ self.sink(otq.CharacterPresent(
995
+ field=field,
996
+ characters=characters,
997
+ characters_field=characters_field,
998
+ discard_on_match=discard_on_match,
999
+ ))
1000
+
1001
+ return self