onetick-py 1.177.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. locator_parser/__init__.py +0 -0
  2. locator_parser/acl.py +73 -0
  3. locator_parser/actions.py +262 -0
  4. locator_parser/common.py +368 -0
  5. locator_parser/io.py +43 -0
  6. locator_parser/locator.py +150 -0
  7. onetick/__init__.py +101 -0
  8. onetick/doc_utilities/__init__.py +3 -0
  9. onetick/doc_utilities/napoleon.py +40 -0
  10. onetick/doc_utilities/ot_doctest.py +140 -0
  11. onetick/doc_utilities/snippets.py +279 -0
  12. onetick/lib/__init__.py +4 -0
  13. onetick/lib/instance.py +141 -0
  14. onetick/py/__init__.py +293 -0
  15. onetick/py/_stack_info.py +89 -0
  16. onetick/py/_version.py +2 -0
  17. onetick/py/aggregations/__init__.py +11 -0
  18. onetick/py/aggregations/_base.py +648 -0
  19. onetick/py/aggregations/_docs.py +948 -0
  20. onetick/py/aggregations/compute.py +286 -0
  21. onetick/py/aggregations/functions.py +2216 -0
  22. onetick/py/aggregations/generic.py +104 -0
  23. onetick/py/aggregations/high_low.py +80 -0
  24. onetick/py/aggregations/num_distinct.py +83 -0
  25. onetick/py/aggregations/order_book.py +501 -0
  26. onetick/py/aggregations/other.py +1014 -0
  27. onetick/py/backports.py +26 -0
  28. onetick/py/cache.py +374 -0
  29. onetick/py/callback/__init__.py +5 -0
  30. onetick/py/callback/callback.py +276 -0
  31. onetick/py/callback/callbacks.py +131 -0
  32. onetick/py/compatibility.py +798 -0
  33. onetick/py/configuration.py +771 -0
  34. onetick/py/core/__init__.py +0 -0
  35. onetick/py/core/_csv_inspector.py +93 -0
  36. onetick/py/core/_internal/__init__.py +0 -0
  37. onetick/py/core/_internal/_manually_bound_value.py +6 -0
  38. onetick/py/core/_internal/_nodes_history.py +250 -0
  39. onetick/py/core/_internal/_op_utils/__init__.py +0 -0
  40. onetick/py/core/_internal/_op_utils/every_operand.py +9 -0
  41. onetick/py/core/_internal/_op_utils/is_const.py +10 -0
  42. onetick/py/core/_internal/_per_tick_scripts/tick_list_sort_template.script +121 -0
  43. onetick/py/core/_internal/_proxy_node.py +140 -0
  44. onetick/py/core/_internal/_state_objects.py +2312 -0
  45. onetick/py/core/_internal/_state_vars.py +93 -0
  46. onetick/py/core/_source/__init__.py +0 -0
  47. onetick/py/core/_source/_symbol_param.py +95 -0
  48. onetick/py/core/_source/schema.py +97 -0
  49. onetick/py/core/_source/source_methods/__init__.py +0 -0
  50. onetick/py/core/_source/source_methods/aggregations.py +809 -0
  51. onetick/py/core/_source/source_methods/applyers.py +296 -0
  52. onetick/py/core/_source/source_methods/columns.py +141 -0
  53. onetick/py/core/_source/source_methods/data_quality.py +301 -0
  54. onetick/py/core/_source/source_methods/debugs.py +272 -0
  55. onetick/py/core/_source/source_methods/drops.py +120 -0
  56. onetick/py/core/_source/source_methods/fields.py +619 -0
  57. onetick/py/core/_source/source_methods/filters.py +1002 -0
  58. onetick/py/core/_source/source_methods/joins.py +1413 -0
  59. onetick/py/core/_source/source_methods/merges.py +605 -0
  60. onetick/py/core/_source/source_methods/misc.py +1455 -0
  61. onetick/py/core/_source/source_methods/pandases.py +155 -0
  62. onetick/py/core/_source/source_methods/renames.py +356 -0
  63. onetick/py/core/_source/source_methods/sorts.py +183 -0
  64. onetick/py/core/_source/source_methods/switches.py +142 -0
  65. onetick/py/core/_source/source_methods/symbols.py +117 -0
  66. onetick/py/core/_source/source_methods/times.py +627 -0
  67. onetick/py/core/_source/source_methods/writes.py +986 -0
  68. onetick/py/core/_source/symbol.py +205 -0
  69. onetick/py/core/_source/tmp_otq.py +222 -0
  70. onetick/py/core/column.py +209 -0
  71. onetick/py/core/column_operations/__init__.py +0 -0
  72. onetick/py/core/column_operations/_methods/__init__.py +4 -0
  73. onetick/py/core/column_operations/_methods/_internal.py +28 -0
  74. onetick/py/core/column_operations/_methods/conversions.py +216 -0
  75. onetick/py/core/column_operations/_methods/methods.py +292 -0
  76. onetick/py/core/column_operations/_methods/op_types.py +160 -0
  77. onetick/py/core/column_operations/accessors/__init__.py +0 -0
  78. onetick/py/core/column_operations/accessors/_accessor.py +28 -0
  79. onetick/py/core/column_operations/accessors/decimal_accessor.py +104 -0
  80. onetick/py/core/column_operations/accessors/dt_accessor.py +537 -0
  81. onetick/py/core/column_operations/accessors/float_accessor.py +184 -0
  82. onetick/py/core/column_operations/accessors/str_accessor.py +1367 -0
  83. onetick/py/core/column_operations/base.py +1121 -0
  84. onetick/py/core/cut_builder.py +150 -0
  85. onetick/py/core/db_constants.py +20 -0
  86. onetick/py/core/eval_query.py +245 -0
  87. onetick/py/core/lambda_object.py +441 -0
  88. onetick/py/core/multi_output_source.py +232 -0
  89. onetick/py/core/per_tick_script.py +2256 -0
  90. onetick/py/core/query_inspector.py +464 -0
  91. onetick/py/core/source.py +1744 -0
  92. onetick/py/db/__init__.py +2 -0
  93. onetick/py/db/_inspection.py +1128 -0
  94. onetick/py/db/db.py +1327 -0
  95. onetick/py/db/utils.py +64 -0
  96. onetick/py/docs/__init__.py +0 -0
  97. onetick/py/docs/docstring_parser.py +112 -0
  98. onetick/py/docs/utils.py +81 -0
  99. onetick/py/functions.py +2398 -0
  100. onetick/py/license.py +190 -0
  101. onetick/py/log.py +88 -0
  102. onetick/py/math.py +935 -0
  103. onetick/py/misc.py +470 -0
  104. onetick/py/oqd/__init__.py +22 -0
  105. onetick/py/oqd/eps.py +1195 -0
  106. onetick/py/oqd/sources.py +325 -0
  107. onetick/py/otq.py +216 -0
  108. onetick/py/pyomd_mock.py +47 -0
  109. onetick/py/run.py +916 -0
  110. onetick/py/servers.py +173 -0
  111. onetick/py/session.py +1347 -0
  112. onetick/py/sources/__init__.py +19 -0
  113. onetick/py/sources/cache.py +167 -0
  114. onetick/py/sources/common.py +128 -0
  115. onetick/py/sources/csv.py +642 -0
  116. onetick/py/sources/custom.py +85 -0
  117. onetick/py/sources/data_file.py +305 -0
  118. onetick/py/sources/data_source.py +1045 -0
  119. onetick/py/sources/empty.py +94 -0
  120. onetick/py/sources/odbc.py +337 -0
  121. onetick/py/sources/order_book.py +271 -0
  122. onetick/py/sources/parquet.py +168 -0
  123. onetick/py/sources/pit.py +191 -0
  124. onetick/py/sources/query.py +495 -0
  125. onetick/py/sources/snapshots.py +419 -0
  126. onetick/py/sources/split_query_output_by_symbol.py +198 -0
  127. onetick/py/sources/symbology_mapping.py +123 -0
  128. onetick/py/sources/symbols.py +374 -0
  129. onetick/py/sources/ticks.py +825 -0
  130. onetick/py/sql.py +70 -0
  131. onetick/py/state.py +251 -0
  132. onetick/py/types.py +2131 -0
  133. onetick/py/utils/__init__.py +70 -0
  134. onetick/py/utils/acl.py +93 -0
  135. onetick/py/utils/config.py +186 -0
  136. onetick/py/utils/default.py +49 -0
  137. onetick/py/utils/file.py +38 -0
  138. onetick/py/utils/helpers.py +76 -0
  139. onetick/py/utils/locator.py +94 -0
  140. onetick/py/utils/perf.py +498 -0
  141. onetick/py/utils/query.py +49 -0
  142. onetick/py/utils/render.py +1374 -0
  143. onetick/py/utils/script.py +244 -0
  144. onetick/py/utils/temp.py +471 -0
  145. onetick/py/utils/types.py +120 -0
  146. onetick/py/utils/tz.py +84 -0
  147. onetick_py-1.177.0.dist-info/METADATA +137 -0
  148. onetick_py-1.177.0.dist-info/RECORD +152 -0
  149. onetick_py-1.177.0.dist-info/WHEEL +5 -0
  150. onetick_py-1.177.0.dist-info/entry_points.txt +2 -0
  151. onetick_py-1.177.0.dist-info/licenses/LICENSE +21 -0
  152. onetick_py-1.177.0.dist-info/top_level.txt +2 -0
onetick/py/run.py ADDED
@@ -0,0 +1,916 @@
1
+ import asyncio
2
+ import inspect
3
+ import datetime
4
+ import warnings
5
+ from typing import Union, List, Optional, Dict, Any, Callable, Type
6
+ from collections import defaultdict
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ from onetick.py.otq import otq, pyomd, otli
11
+
12
+ from onetick import py as otp
13
+ from onetick.py import utils, configuration
14
+ from onetick.py.core.column_operations.base import _Operation
15
+ from onetick.py.types import datetime2timeval, datetime2expr
16
+ from onetick.py.core.source import _is_dict_required
17
+ from onetick.py.compatibility import (
18
+ has_max_expected_ticks_per_symbol,
19
+ has_password_param,
20
+ has_query_encoding_parameter,
21
+ _add_version_info_to_exception,
22
+ )
23
+ from onetick.py._stack_info import _add_stack_info_to_exception
24
+ from onetick.py.callback import LogCallback, ManualDataframeCallback
25
+
26
+
27
+ def run(query: Union[Callable, Dict, otp.Source, otp.MultiOutputSource, # NOSONAR
28
+ otp.query, str, otq.EpBase, otq.GraphQuery,
29
+ otq.ChainQuery, otq.Chainlet, otq.SqlQuery, otp.SqlQuery],
30
+ *,
31
+ symbols: Union[List[Union[str, otq.Symbol]], otp.Source, str, None] = None,
32
+ start: Union[datetime.datetime, otp.datetime, pyomd.timeval_t, None] = utils.adaptive, # type: ignore
33
+ end: Union[datetime.datetime, otp.datetime, pyomd.timeval_t, None] = utils.adaptive, # type: ignore
34
+ date: Union[datetime.date, otp.date, None] = None,
35
+ start_time_expression: Optional[str] = None,
36
+ end_time_expression: Optional[str] = None,
37
+ timezone=utils.default, # type: ignore
38
+ context=utils.default, # type: ignore
39
+ username: Optional[str] = None,
40
+ alternative_username: Optional[str] = None,
41
+ password: Optional[str] = None,
42
+ batch_size: Union[int, Type[utils.default], None] = utils.default,
43
+ running: Optional[bool] = False,
44
+ query_properties: Optional[pyomd.QueryProperties] = None, # type: ignore
45
+ concurrency: Union[int, Type[utils.default], None] = utils.default,
46
+ apply_times_daily: Optional[int] = None,
47
+ symbol_date: Union[datetime.datetime, int, str, None] = None,
48
+ query_params: Optional[Dict[str, Any]] = None,
49
+ time_as_nsec: bool = True,
50
+ treat_byte_arrays_as_strings: bool = True,
51
+ output_matrix_per_field: bool = False,
52
+ output_structure: Optional[str] = None,
53
+ return_utc_times: Optional[bool] = None,
54
+ connection=None,
55
+ callback=None,
56
+ svg_path=None,
57
+ use_connection_pool: bool = False,
58
+ node_name: Union[str, List[str], None] = None,
59
+ require_dict: bool = False,
60
+ max_expected_ticks_per_symbol: Optional[int] = None,
61
+ log_symbol: Union[bool, Type[utils.default]] = utils.default,
62
+ encoding: Optional[str] = None,
63
+ manual_dataframe_callback: bool = False):
64
+ """
65
+ Executes a query and returns its result.
66
+
67
+ Parameters
68
+ ----------
69
+ query: :py:class:`onetick.py.Source`, otq.Ep, otq.GraphQuery, otq.ChainQuery, str, otq.Chainlet,\
70
+ Callable, otq.SqlQuery, :py:class:`onetick.py.SqlQuery`
71
+ Query to execute can be source, path of the query on a disk or onetick.query graph or event processor.
72
+ For running OTQ files, it represents the path (including filename) to the OTQ file to run a single query within
73
+ the file. If more than one query is present, then the query to be run must be specified
74
+ (that is, ``'path_to_file/otq_file.otq::query_to_run'``).
75
+
76
+ ``query`` can also be a function that has a symbol object as the first parameter.
77
+ This object can be used to get symbol name and symbol parameters.
78
+ Function must return a :py:class:`Source <onetick.py.Source>`.
79
+ symbols: str, list of str, list of otq.Symbol, :py:class:`onetick.py.Source`, :pandas:`pandas.DataFrame`, optional
80
+ Symbol(s) to run the query for passed as a string, a list of strings,
81
+ a :pandas:`pandas.DataFrame` with the ``SYMBOL_NAME`` column,
82
+ or as a "symbols" query which results include the ``SYMBOL_NAME`` column.
83
+ The start/end times for the symbols query will taken from the params below.
84
+ See :ref:`symbols <static/concepts/symbols:Symbols: bound and unbound>` for more details.
85
+ start: :py:class:`datetime.datetime`, :py:class:`otp.datetime <onetick.py.datetime>`,\
86
+ :py:class:`pyomd.timeval_t`, optional
87
+ The start time of the query. Can be timezone-naive or timezone-aware. See also ``timezone`` argument.
88
+ onetick.py uses :py:attr:`otp.config.default_start_time<onetick.py.configuration.Config.default_start_time>`
89
+ as default value, if you don't want to specify start time, e.g. to use saved time of the query,
90
+ then you should specify None value.
91
+ end: :py:class:`datetime.datetime`, :py:class:`otp.datetime <onetick.py.datetime>`,\
92
+ :py:class:`pyomd.timeval_t`, optional
93
+ The end time of the query (note that it's non-inclusive).
94
+ Can be timezone-naive or timezone-aware. See also ``timezone`` argument.
95
+ onetick.py uses :py:attr:`otp.config.default_end_time<onetick.py.configuration.Config.default_end_time>`
96
+ as default value, if you don't want to specify end time, e.g. to use saved time of the query,
97
+ then you should specify None value.
98
+ date: :py:class:`datetime.date`, :py:class:`otp.date <onetick.py.date>`, optional
99
+ The date to run the query for. Can be set instead of ``start`` and ``end`` parameters.
100
+ If set then the interval to run the query will be from 0:00 to 24:00 of the specified date.
101
+ start_time_expression: str, :py:class:`~onetick.py.Operation`, optional
102
+ Start time onetick expression of the query. If specified, it will take precedence over ``start``.
103
+ Supported only if query is Source, Graph or Event Processor.
104
+ Not supported for WebAPI mode.
105
+ end_time_expression: str, :py:class:`~onetick.py.Operation`, optional
106
+ End time onetick expression of the query. If specified, it will take precedence over ``end``.
107
+ Supported only if query is Source, Graph or Event Processor.
108
+ Not supported for WebAPI mode.
109
+ timezone: str, optional
110
+ The timezone of output timestamps.
111
+ Also, when start and/or end arguments are timezone-naive, it will define their timezone.
112
+ If parameter is omitted timestamps of ticks will be formatted
113
+ with the default :py:attr:`otp.config.tz<onetick.py.configuration.Config.tz>`.
114
+ context: str, optional
115
+ Allows specification of different contexts from OneTick configuration to connect to.
116
+ If not set then default :py:attr:`otp.config.context<onetick.py.configuration.Config.context>` is used.
117
+ See :ref:`guide about switching contexts <switching contexts>` for examples.
118
+ username
119
+ The username to make the connection.
120
+ By default the user which executed the process is used or the value specified in
121
+ :py:attr:`otp.config.default_username<onetick.py.configuration.Config.default_username>`.
122
+ alternative_username: str
123
+ The username used for authentication.
124
+ Needs to be set only when the tick server is configured to use password-based authentication.
125
+ By default,
126
+ :py:attr:`otp.config.default_auth_username<onetick.py.configuration.Config.default_auth_username>` is used.
127
+ Not supported for WebAPI mode.
128
+ password: str, optional
129
+ The password used for authentication.
130
+ Needs to be set only when the tick server is configured to use password-based authentication.
131
+ Note: not supported and ignored on older OneTick versions.
132
+ By default, :py:attr:`otp.config.default_password<onetick.py.configuration.Config.default_password>` is used.
133
+ batch_size: int
134
+ number of symbols to run in one batch.
135
+ By default, the value from
136
+ :py:attr:`otp.config.default_batch_size<onetick.py.configuration.Config.default_batch_size>` is used.
137
+ Not supported for WebAPI mode.
138
+ running: bool, optional
139
+ Indicates whether a query is CEP or not. Default is `False`.
140
+ query_properties: :py:class:`pyomd.QueryProperties` or dict, optional
141
+ Query properties, such as ONE_TO_MANY_POLICY, ALLOW_GRAPH_REUSE, etc
142
+ concurrency: int, optional
143
+ The maximum number of CPU cores to use to process the query.
144
+ By default, the value from
145
+ :py:attr:`otp.config.default_concurrency<onetick.py.configuration.Config.default_concurrency>` is used.
146
+ apply_times_daily: bool
147
+ Runs the query for every day in the ``start``-``end`` time range,
148
+ using the time components of ``start`` and ``end`` datetimes.
149
+
150
+ Note that those daily intervals are executed separately, so you don't have access
151
+ to the data from previous or next days (see example in the next section).
152
+ symbol_date:
153
+ The symbol date used to look up symbology mapping information in the reference database,
154
+ expressed as datetime object or integer of YYYYMMDD format
155
+ query_params: dict
156
+ Parameters of the query.
157
+ time_as_nsec: bool
158
+ Outputs timestamps up to nanoseconds granularity
159
+ (defaults to False: by default we output timestamps in microseconds granularity)
160
+ treat_byte_arrays_as_strings: bool
161
+ Outputs byte arrays as strings (defaults to True)
162
+ Not supported for WebAPI mode.
163
+ output_matrix_per_field: bool
164
+ Changes output format to list of matrices per field.
165
+ Not supported for WebAPI mode.
166
+ output_structure: otp.Source.OutputStructure, optional
167
+
168
+ Structure (type) of the result. Supported values are:
169
+ - `df` (default) - the result is returned as :pandas:`pandas.DataFrame` object
170
+ or dictionary of symbol names and :pandas:`pandas.DataFrame` objects
171
+ in case of using multiple symbols or first stage query.
172
+ - `map` - the result is returned as SymbolNumpyResultMap.
173
+ - `list` - the result is returned as list.
174
+ - `polars` - the result is returned as
175
+ `polars.DataFrame <https://docs.pola.rs/api/python/stable/reference/dataframe/index.html>`_ object
176
+ or dictionary of symbol names and dataframe objects
177
+ (**Only supported in WebAPI mode**).
178
+ return_utc_times: bool
179
+ If True Return times in UTC timezone and in local timezone otherwise
180
+ Not supported for WebAPI mode.
181
+ connection: :py:class:`pyomd.Connection`
182
+ The connection to be used for discovering nested .otq files
183
+ Not supported for WebAPI mode.
184
+ callback: :py:class:`onetick.py.CallbackBase`
185
+ Class with callback methods.
186
+ If set, the output of the query should be controlled with callbacks
187
+ and this function returns nothing.
188
+ svg_path: str, optional
189
+ Not supported for WebAPI mode.
190
+ use_connection_pool: bool
191
+ Default is False. If set to True, the connection pool is used.
192
+ Not supported for WebAPI mode.
193
+ node_name: str, List[str], optional
194
+ Name of the output node to select result from. If query graph has several output nodes, you can specify the name
195
+ of the node to choose result from. If node_name was specified, query should be presented by path on the disk
196
+ and output_structure should be `df`
197
+ require_dict: bool
198
+ If set to True, result will be forced to be a dictionary even if it's returned for a single symbol
199
+ max_expected_ticks_per_symbol: int
200
+ Expected maximum number of ticks per symbol (used for performance optimizations).
201
+ By default, :py:attr:`otp.config.max_expected_ticks_per_symbol \
202
+ <onetick.py.configuration.Config.max_expected_ticks_per_symbol>` is used.
203
+ Not supported for WebAPI mode.
204
+ log_symbol: bool
205
+ Log currently executed symbol.
206
+ Note that this only works with unbound symbols.
207
+ Also in this case :py:func:`otp.run<onetick.py.run>` is executed in ``callback`` mode
208
+ and no value is returned from the function, so it should be used only for debugging purposes.
209
+ This logging will not work if some other value specified in parameter ``callback``.
210
+ By default, :py:attr:`otp.config.log_symbol<onetick.py.configuration.Config.log_symbol>` is used.
211
+ encoding: str, optional
212
+ The encoding of string fields.
213
+ manual_dataframe_callback: bool
214
+ Create dataframe manually with ``callback`` mode.
215
+ Only works if ``output_structure='df'`` is specified and parameter ``callback`` is not.
216
+ May improve performance in some cases.
217
+
218
+ Returns
219
+ -------
220
+ result, list, dict, :pandas:`pandas.DataFrame`, None
221
+ result of the query
222
+
223
+ Examples
224
+ --------
225
+
226
+ Running :py:class:`onetick.py.Source` and setting start and end times:
227
+
228
+ >>> data = otp.Tick(A=1)
229
+ >>> otp.run(data, start=otp.dt(2003, 12, 2), end=otp.dt(2003, 12, 4))
230
+ Time A
231
+ 0 2003-12-02 1
232
+
233
+ Setting query interval with ``date`` parameter:
234
+
235
+ >>> data = otp.Tick(A=1)
236
+ >>> data['START'] = data['_START_TIME']
237
+ >>> data['END'] = data['_END_TIME']
238
+ >>> otp.run(data, date=otp.dt(2003, 12, 1))
239
+ Time A START END
240
+ 0 2003-12-01 1 2003-12-01 2003-12-02
241
+
242
+ Running otq.Ep and passing query parameters:
243
+
244
+ >>> ep = otq.TickGenerator(bucket_interval=0, fields='long A = $X').tick_type('TT')
245
+ >>> otp.run(ep, symbols='LOCAL::', query_params={'X': 1})
246
+ Time A
247
+ 0 2003-12-04 1
248
+
249
+ Running in callback mode:
250
+
251
+ >>> class Callback(otp.CallbackBase):
252
+ ... def __init__(self):
253
+ ... self.result = None
254
+ ... def process_tick(self, tick, time):
255
+ ... self.result = tick
256
+ >>> data = otp.Tick(A=1)
257
+ >>> callback = Callback()
258
+ >>> otp.run(data, callback=callback)
259
+ >>> callback.result
260
+ {'A': 1}
261
+
262
+ Running with ``apply_times_daily``.
263
+ Note that daily intervals are processed separately so, for example,
264
+ we can't access column **COUNT** from previous day.
265
+
266
+ >>> trd = otp.DataSource('US_COMP', symbols='AAPL', tick_type='TRD') # doctest: +SKIP
267
+ >>> trd = trd.agg({'COUNT': otp.agg.count()},
268
+ ... bucket_interval=12 * 3600, bucket_time='start') # doctest: +SKIP
269
+ >>> trd['PREV_COUNT'] = trd['COUNT'][-1] # doctest: +SKIP
270
+ >>> otp.run(trd, apply_times_daily=True,
271
+ ... start=otp.dt(2023, 4, 3), end=otp.dt(2023, 4, 5), timezone='EST5EDT') # doctest: +SKIP
272
+ Time COUNT PREV_COUNT
273
+ 0 2023-04-03 00:00:00 328447 0
274
+ 1 2023-04-03 12:00:00 240244 328447
275
+ 2 2023-04-04 00:00:00 263293 0
276
+ 3 2023-04-04 12:00:00 193018 263293
277
+
278
+ Using a function as a ``query``, accessing symbol name and parameters:
279
+
280
+ >>> def query(symbol):
281
+ ... t = otp.Tick(X='x')
282
+ ... t['SYMBOL_NAME'] = symbol.name
283
+ ... t['SYMBOL_PARAM'] = symbol.PARAM
284
+ ... return t
285
+ >>> symbols = otp.Ticks({'SYMBOL_NAME': ['A', 'B'], 'PARAM': [1, 2]})
286
+ >>> result = otp.run(query, symbols=symbols)
287
+ >>> result['A']
288
+ Time X SYMBOL_NAME SYMBOL_PARAM
289
+ 0 2003-12-01 x A 1
290
+ >>> result['B']
291
+ Time X SYMBOL_NAME SYMBOL_PARAM
292
+ 0 2003-12-01 x B 2
293
+
294
+ Debugging unbound symbols with ``log_symbol`` parameter:
295
+
296
+ >>> data = otp.Tick(X=1)
297
+ >>> symbols = otp.Ticks({'SYMBOL_NAME': ['A', 'B'], 'PARAM': [1, 2]})
298
+ >>> otp.run(query, symbols=symbols, log_symbol=True) # doctest: +ELLIPSIS
299
+ Running query <onetick.py.sources.ticks.Tick object at ...>
300
+ Processing symbol A
301
+ Processing symbol B
302
+
303
+ By default, some non-standard characters in data strings could be processed incorrectly:
304
+
305
+ >>> data = ['AA測試AA']
306
+ >>> source = otp.Ticks({'A': data})
307
+ >>> otp.run(source)
308
+ Time A
309
+ 0 2003-12-01 AA測試AA
310
+
311
+ To fix this you can pass `encoding` parameter to `otp.run`:
312
+
313
+ .. testcode::
314
+ :skipif: not has_query_encoding_parameter()
315
+
316
+ data = ['AA測試AA']
317
+ source = otp.Ticks({'A': data})
318
+ df = otp.run(source, encoding="utf-8")
319
+ print(df)
320
+
321
+ .. testoutput::
322
+
323
+ Time A
324
+ 0 2003-12-01 AA測試AA
325
+
326
+ Note that query ``start`` time is inclusive, but query ``end`` time is not,
327
+ meaning that ticks with timestamps equal to the query end time will not be included:
328
+
329
+ >>> data = otp.Tick(A=1, bucket_interval=24*60*60)
330
+ >>> data['A'] = data['TIMESTAMP'].dt.day_of_month()
331
+ >>> otp.run(data, start=otp.dt(2003, 12, 1), end=otp.dt(2003, 12, 4))
332
+ Time A
333
+ 0 2003-12-01 1
334
+ 1 2003-12-02 2
335
+ 2 2003-12-03 3
336
+ >>> otp.run(data, start=otp.dt(2003, 12, 1), end=otp.dt(2003, 12, 2))
337
+ Time A
338
+ 0 2003-12-01 1
339
+
340
+ If you want to include such ticks, you can add one nanosecond to the query end time:
341
+
342
+ >>> otp.run(data, start=otp.dt(2003, 12, 1), end=otp.dt(2003, 12, 2) + otp.Nano(1))
343
+ Time A
344
+ 0 2003-12-01 1
345
+ 1 2003-12-02 2
346
+ """
347
+ _ = otli.OneTickLib()
348
+
349
+ query_schema = None
350
+ if isinstance(query, otp.Source):
351
+ query_schema = query.schema
352
+
353
+ if timezone is utils.default:
354
+ timezone = configuration.config.tz
355
+ if context is utils.default or context is None:
356
+ context = configuration.config.context
357
+ if concurrency is utils.default:
358
+ concurrency = configuration.default_query_concurrency()
359
+
360
+ if batch_size is utils.default:
361
+ batch_size = configuration.config.default_batch_size
362
+ if query_properties is None:
363
+ query_properties = pyomd.QueryProperties()
364
+
365
+ if isinstance(query_properties, dict):
366
+ qp_dict = query_properties
367
+ query_properties = utils.query_properties_from_dict(qp_dict)
368
+ else:
369
+ qp_dict = utils.query_properties_to_dict(query_properties)
370
+
371
+ if 'USE_FT' not in qp_dict:
372
+ query_properties.set_property_value('USE_FT', otp.config.default_fault_tolerance) # type: ignore[union-attr]
373
+
374
+ if 'IGNORE_TICKS_IN_UNENTITLED_TIME_RANGE' not in qp_dict:
375
+ query_properties.set_property_value('IGNORE_TICKS_IN_UNENTITLED_TIME_RANGE', # type: ignore[union-attr]
376
+ str(otp.config.ignore_ticks_in_unentitled_time_range).upper())
377
+
378
+ if date is not None:
379
+ for v in (start, end, start_time_expression, end_time_expression):
380
+ if v is not None and v is not utils.adaptive:
381
+ raise ValueError("Can't use 'date' parameter when other time interval parameters are specified")
382
+ start = otp.date(date)
383
+ end = start + otp.Day(1)
384
+
385
+ has_source_start, has_source_end = False, False
386
+ if isinstance(query, otp.Source):
387
+ has_source_start, has_source_end = query.has_start_end_time()
388
+
389
+ if (start is None or start is utils.adaptive) and otp.config.get('default_start_time') is None and \
390
+ not has_source_start:
391
+ warnings.warn('Start time is None and default start time is not set, '
392
+ 'onetick.query will use 19700101 as start time, '
393
+ 'which can cause unexpected results. '
394
+ 'Please set start time explicitly.')
395
+ if (end is None or end is utils.adaptive) and otp.config.get('default_end_time') is None and \
396
+ not has_source_end:
397
+ warnings.warn('End time is None and default end time is not set, '
398
+ 'onetick.query will use 19700101 as end time, '
399
+ 'which can cause unexpected results. '
400
+ 'Please set end time explicitly.')
401
+
402
+ if isinstance(start, _Operation) and start_time_expression is None:
403
+ start_time_expression = str(start)
404
+ if isinstance(end, _Operation) and end_time_expression is None:
405
+ end_time_expression = str(end)
406
+
407
+ if isinstance(start_time_expression, _Operation):
408
+ start_time_expression = str(start_time_expression)
409
+ if isinstance(end_time_expression, _Operation):
410
+ end_time_expression = str(end_time_expression)
411
+
412
+ # PY-1321: CEP-query seems to be using start and end values for some reason, so setting them to None
413
+ if start_time_expression is not None:
414
+ start = None
415
+ if end_time_expression is not None:
416
+ end = None
417
+
418
+ if inspect.ismethod(query) or inspect.isfunction(query):
419
+ t_s = None
420
+ if isinstance(symbols, otp.Source):
421
+ t_s = symbols
422
+ if isinstance(symbols, otp.query):
423
+ t_s = otp.Query(symbols)
424
+ if isinstance(symbols, str):
425
+ t_s = otp.Tick(SYMBOL_NAME=symbols)
426
+ if isinstance(symbols, list):
427
+ t_s = otp.Ticks(SYMBOL_NAME=symbols)
428
+
429
+ if isinstance(t_s, otp.Source):
430
+ query = query(t_s.to_symbol_param()) # type: ignore
431
+
432
+ query, query_params = _preprocess_otp_query(query, query_params)
433
+ # If query is an otp.Source object, then it can deal with otp.datetime and pd.Timestamp types
434
+
435
+ if log_symbol is utils.default:
436
+ log_symbol = otp.config.log_symbol
437
+ if callback is None and log_symbol:
438
+ callback = LogCallback(query)
439
+
440
+ if manual_dataframe_callback:
441
+ if output_structure and output_structure != 'df':
442
+ raise ValueError("Parameter 'output_structure' must be set to 'df'"
443
+ " if parameter 'manual_dataframe_callback' is set")
444
+ if log_symbol:
445
+ raise ValueError("Parameters 'manual_dataframe_callback' and 'log_symbol' can't be set together")
446
+ if callback is not None:
447
+ raise ValueError("Parameters 'manual_dataframe_callback' and 'callback' can't be set together")
448
+ callback = ManualDataframeCallback(timezone)
449
+
450
+ output_mode = otq.QueryOutputMode.numpy
451
+ if callback is not None:
452
+ output_mode = otq.QueryOutputMode.callback
453
+ if output_structure == 'polars':
454
+ if not otq.webapi:
455
+ raise ValueError("Parameter output_structure='polars' is only supported in WebAPI mode.")
456
+ try:
457
+ import polars as _ # type: ignore
458
+ except ImportError:
459
+ raise ValueError("Parameter output_structure='polars' is specified, but module polars can't be imported. "
460
+ "Use 'pip install onetick-py[polars]' command to install onetick-py with polars support.")
461
+ try:
462
+ output_mode = otq.QueryOutputMode.polars
463
+ except AttributeError:
464
+ raise ValueError("Parameter output_structure='polars' is specified, but it's not supported "
465
+ "by installed onetick.query_webapi library.")
466
+
467
+ output_structure, output_structure_for_otq = _process_output_structure(output_structure)
468
+ if symbol_date:
469
+ # otq.run supports only strings and datetime.date
470
+ symbol_date = utils.symbol_date_to_str(symbol_date)
471
+
472
+ require_dict = require_dict or _is_dict_required(symbols)
473
+
474
+ # converting symbols properly
475
+ if isinstance(symbols, otp.Source):
476
+ # check if SYMBOL_NAME is in schema, or if schema contains only one field
477
+ if ('SYMBOL_NAME' not in symbols.columns(skip_meta_fields=True).keys()) and \
478
+ len(symbols.columns(skip_meta_fields=True)) != 1:
479
+ warnings.warn('Using as a symbol list a source without "SYMBOL_NAME" field '
480
+ 'and with more than one field! This won\'t work unless the schema is incomplete')
481
+
482
+ symbols = otp.Source._convert_symbol_to_string(
483
+ symbol=symbols,
484
+ tmp_otq=query._tmp_otq if isinstance(query, otp.Source) else None,
485
+ start=start,
486
+ end=end,
487
+ timezone=timezone
488
+ )
489
+ if isinstance(symbols, str):
490
+ symbols = [symbols]
491
+ if isinstance(symbols, pd.DataFrame):
492
+ symbols = utils.get_symbol_list_from_df(symbols)
493
+
494
+ if isinstance(query, dict):
495
+ # we assume it's a dictionary of sources for the MultiOutputSource object
496
+ query = otp.MultiOutputSource(query)
497
+
498
+ params_saved_to_otq = {}
499
+ if isinstance(query, (otp.Source, otp.MultiOutputSource)):
500
+ start = None if start is utils.adaptive else start
501
+ end = None if end is utils.adaptive else end
502
+ params_saved_to_otq = dict(
503
+ symbols=symbols,
504
+ start=start,
505
+ end=end,
506
+ start_time_expression=start_time_expression,
507
+ end_time_expression=end_time_expression,
508
+ )
509
+ param_upd = query._prepare_for_execution(symbols=symbols, start=start, end=end,
510
+ timezone=timezone,
511
+ start_time_expression=start_time_expression,
512
+ end_time_expression=end_time_expression,
513
+ require_dict=require_dict,
514
+ running_query_flag=running,
515
+ node_name=node_name, has_output=None)
516
+ query, require_dict, node_name = param_upd
517
+ # symbols and start/end times should be already stored in the query and should not be passed again
518
+ symbols = None
519
+ start = None
520
+ end = None
521
+ start_time_expression = None
522
+ end_time_expression = None
523
+ time_as_nsec = True
524
+
525
+ elif isinstance(query, (otq.graph_components.EpBase, otq.Chainlet)):
526
+ query = otq.GraphQuery(query)
527
+
528
+ if isinstance(query, otq.SqlQuery):
529
+ # This has no impact on query result, just placeholder values
530
+ start = end = None
531
+
532
+ if start is utils.adaptive:
533
+ start = configuration.config.default_start_time
534
+
535
+ if end is utils.adaptive:
536
+ end = configuration.config.default_end_time
537
+
538
+ if not otq.webapi:
539
+ # converting to expressions, because in datetime objects nanoseconds are not supported on some OneTick versions
540
+ if start is not None and not start_time_expression:
541
+ start_time_expression = datetime2expr(start)
542
+ if end is not None and not end_time_expression:
543
+ end_time_expression = datetime2expr(end)
544
+
545
+ # start and end parameters could be set to None,
546
+ # because we use start and end time expressions,
547
+ # but because of the bug it sometimes doesn't work
548
+ # https://onemarketdata.atlassian.net/browse/BDS-454
549
+ start, end = _get_start_end(start, end, timezone)
550
+
551
+ # authentication
552
+ username = username or otp.config.default_username
553
+ alternative_username = alternative_username or otp.config.default_auth_username
554
+ password = password or otp.config.default_password
555
+ kwargs = {}
556
+ if password is not None and has_password_param(throw_warning=True):
557
+ kwargs['password'] = password
558
+
559
+ max_expected_ticks_per_symbol = max_expected_ticks_per_symbol or otp.config.max_expected_ticks_per_symbol
560
+ if max_expected_ticks_per_symbol is not None and has_max_expected_ticks_per_symbol(throw_warning=True):
561
+ kwargs['max_expected_ticks_per_symbol'] = max_expected_ticks_per_symbol
562
+ elif max_expected_ticks_per_symbol is None and has_max_expected_ticks_per_symbol(throw_warning=False):
563
+ kwargs['max_expected_ticks_per_symbol'] = 2000
564
+
565
+ if encoding is not None and has_query_encoding_parameter(throw_warning=True):
566
+ kwargs['encoding'] = encoding
567
+
568
+ run_params = dict(
569
+ query=query,
570
+ symbols=symbols, start=start, end=end, context=context, username=username,
571
+ timezone=timezone,
572
+ start_time_expression=start_time_expression,
573
+ end_time_expression=end_time_expression,
574
+ alternative_username=alternative_username, batch_size=batch_size,
575
+ running_query_flag=running, query_properties=query_properties,
576
+ max_concurrency=concurrency, apply_times_daily=apply_times_daily, symbol_date=symbol_date,
577
+ query_params=query_params, time_as_nsec=time_as_nsec,
578
+ treat_byte_arrays_as_strings=treat_byte_arrays_as_strings,
579
+ output_mode=output_mode,
580
+ output_matrix_per_field=output_matrix_per_field, output_structure=output_structure_for_otq,
581
+ return_utc_times=return_utc_times, connection=connection,
582
+ callback=callback, svg_path=svg_path, use_connection_pool=use_connection_pool, **kwargs
583
+ )
584
+
585
+ # some parameters were saved in .otq file, we need to debug them too
586
+ debug_params = dict(run_params, **params_saved_to_otq) if params_saved_to_otq else run_params
587
+ otp.get_logger(__name__).info(otp.utils.json_dumps(debug_params))
588
+
589
+ try:
590
+ result = otq.run(**run_params)
591
+ except Exception as e:
592
+ e = _add_stack_info_to_exception(e)
593
+ e = _add_version_info_to_exception(e)
594
+ raise e # noqa: W0707
595
+
596
+ if output_mode == otq.QueryOutputMode.callback:
597
+ if manual_dataframe_callback:
598
+ result = callback.result
599
+ return result
600
+
601
+ # node_names should be either a list of node names or None
602
+ node_names: Optional[List[str]]
603
+ if isinstance(node_name, str):
604
+ node_names = [node_name]
605
+ else:
606
+ node_names = node_name
607
+
608
+ if query_schema:
609
+ # check if we have empty result for any symbol to add schema to empty dataframes
610
+ _process_empty_results(result, query_schema, output_structure)
611
+
612
+ return _format_call_output(result, output_structure=output_structure,
613
+ require_dict=require_dict, node_names=node_names)
614
+
615
+
616
+ async def run_async(*args, **kwargs):
617
+ """
618
+ Asynchronous alternative to :func:`otp.run <onetick.py.run>`.
619
+
620
+ All parameters are the same.
621
+
622
+ This function can be used via built-in python ``await`` syntax
623
+ and standard `asyncio <https://docs.python.org/3/library/asyncio.html>`_ library.
624
+
625
+ Note
626
+ ----
627
+ Internally this function is implemented as :func:`otp.run <onetick.py.run>` running in a separate thread.
628
+
629
+ Threads in python are generally not interruptable,
630
+ so some `asyncio` functionality may not work as expected.
631
+
632
+ For example, canceling :func:`otp.run_async <onetick.py.run_async>` task by
633
+ `timeout <https://docs.python.org/3/library/asyncio-task.html#timeouts>`_
634
+ may block the waiting function
635
+ or exiting the python process will block until the task is finished,
636
+ depending on python and `asyncio` back-end implementation.
637
+
638
+ Examples
639
+ --------
640
+
641
+ >>> data = otp.Ticks(A=[1, 2, 3])
642
+
643
+ Calling :func:`otp.run_async <onetick.py.run_async>` will create a "coroutine" object:
644
+
645
+ >>> otp.run_async(data) # doctest: +SKIP
646
+ <coroutine object run_async at ...>
647
+
648
+ Use `asyncio.run <https://docs.python.org/3/library/asyncio-runner.html#asyncio.run>`_
649
+ to run this coroutine and wait for it to finish:
650
+
651
+ >>> asyncio.run(otp.run_async(data))
652
+ Time A
653
+ 0 2003-12-01 00:00:00.000 1
654
+ 1 2003-12-01 00:00:00.001 2
655
+ 2 2003-12-01 00:00:00.002 3
656
+
657
+ You can use standard `asyncio <https://docs.python.org/3/library/asyncio.html>`_
658
+ library functions to create and schedule tasks.
659
+
660
+ In the example below two tasks are executed in parallel,
661
+ so total execution time will be around 3 seconds instead of 6:
662
+
663
+ >>> import asyncio
664
+ >>> import time
665
+ >>> async def parallel_tasks():
666
+ ... # pause 1 second on each tick (thus 3 seconds for 3 ticks)
667
+ ... task_otp = asyncio.create_task(otp.run_async(data.pause(1000)))
668
+ ... # just sleep for 3 seconds
669
+ ... task_other = asyncio.create_task(asyncio.sleep(3))
670
+ ... otp_result = await task_otp
671
+ ... await task_other
672
+ ... print(otp_result)
673
+ >>> start_time = time.time()
674
+ >>> asyncio.run(parallel_tasks())
675
+ Time A
676
+ 0 2003-12-01 00:00:00.000 1
677
+ 1 2003-12-01 00:00:00.001 2
678
+ 2 2003-12-01 00:00:00.002 3
679
+ >>> print('Finished in', time.time() - start_time, 'seconds') # doctest: +SKIP
680
+ Finished in 3.0108885765075684 seconds
681
+ """
682
+ return await asyncio.to_thread(run, *args, **kwargs)
683
+
684
+
685
+ def _filter_returned_map_by_node(result, _node_names):
686
+ """
687
+ Here, result has the following format: {symbol: {node_name: data}}
688
+ We need to filter by correct node_name
689
+ """
690
+ # TODO: implement filtering by node_name in a way
691
+ # that no information from SymbolNumpyResultMap object is lost
692
+ return result
693
+
694
+
695
+ def _filter_returned_list_by_node(result, node_names):
696
+ """
697
+ Here, result has the following format: [(symbol, data_1, data_2, node_name)]
698
+ We need to filter by correct node_names
699
+ """
700
+ if not node_names:
701
+ return result
702
+
703
+ node_found = False
704
+
705
+ res = []
706
+ empty_result = True
707
+ for symbol, data_1, data_2, node, *_ in result:
708
+ if len(data_1) > 0:
709
+ empty_result = False
710
+ if node in node_names:
711
+ node_found = True
712
+ res.append((symbol, data_1, data_2, node))
713
+
714
+ if not empty_result and not node_found:
715
+ # TODO: Do we even want to raise it?
716
+ raise ValueError(f'No passed node name(s) were found in the results. Passed node names were: {node_names}')
717
+ return res
718
+
719
+
720
+ def _form_dict_from_list(data_list, output_structure):
721
+ """
722
+ Here, data_list has the following format: [(symbol, data_1, data_2, node_name), ...]
723
+ We need to create the following result:
724
+ either {symbol: DataFrame(data_1)} if there is only one result per symbol
725
+ or {symbol: [DataFrame(data_1)]} if there are multiple results for symbol for a single node_name
726
+ or {symbol: {node_name: DataFrame(data_1)}} if there are single results for multiple node names for a symbol
727
+ or {symbol: {node_name: [DataFrame(data_1)]}} if there are multiple results for multiple node names for a symbol
728
+ """
729
+
730
+ def form_node_name_dict(lst):
731
+ """
732
+ lst is a lit of (node, dataframe)
733
+ """
734
+ d = defaultdict(list)
735
+ for node, df in lst:
736
+ d[node].append(df)
737
+ for node, node_list in d.items():
738
+ if len(node_list) == 1:
739
+ d[node] = node_list[0]
740
+ if len(d) == 1:
741
+ d = list(d.values())[0]
742
+ else: # converting defaultdict to regular dict
743
+ d = dict(d)
744
+ return d
745
+
746
+ def get_dataframe(data):
747
+ if output_structure == 'df':
748
+ return pd.DataFrame(dict(data))
749
+ else:
750
+ import polars
751
+ if isinstance(data, polars.DataFrame):
752
+ # polars only works in webapi mode,
753
+ # and it's already returned as polars.DataFrame by onetick.query_webapi
754
+ return data
755
+ # but if there is no data, then we want to return empty polars.DataFrame
756
+ return polars.DataFrame()
757
+
758
+ symbols_dict = defaultdict(list)
759
+ for symbol, data, _, node, *_ in data_list:
760
+ df = get_dataframe(data)
761
+
762
+ list_item = (node, df)
763
+ symbols_dict[symbol].append(list_item)
764
+
765
+ for symbol, lst in symbols_dict.items():
766
+ symbols_dict[symbol] = form_node_name_dict(lst)
767
+
768
+ return dict(symbols_dict)
769
+
770
+
771
+ def _format_call_output(result, output_structure, node_names, require_dict):
772
+ """Formats output of otq.run() according to passed parameters.
773
+ See parameters' description for more information
774
+
775
+ Parameters
776
+ ----------
777
+ output_structure: ['df', 'list', 'map']
778
+ If 'df': forms pandas.DataFrame from the result.
779
+
780
+ Returns a dictionary with symbols as keys if there's more than one symbol
781
+ in returned data of if require_dict = True.
782
+
783
+ Values of the returned dictionary, or returned value itself if no dictionary is formed,
784
+ is either a list of tuples: (node_name, dataframe) if there's output for more than one node
785
+ or a dataframe
786
+
787
+ If 'list' or 'map': returns data as returned by otq.run(), possibly filtered by node_name (see below)
788
+ node_names: str, None
789
+ If not None, then selects only output returned by nodes in node_names list
790
+ for all output structures
791
+ require_dict: bool
792
+ If True, forces output for output_structure='df' to always be a dictionary, even if only one symbol is returned
793
+ Has no effect for other values of output_structure
794
+
795
+ Returns
796
+ ----------
797
+ Formatted output: pandas DataFrame, dictionary or list
798
+
799
+ """
800
+ if output_structure == 'list':
801
+ return _filter_returned_list_by_node(result, node_names)
802
+ elif output_structure == 'map':
803
+ return _filter_returned_map_by_node(result, node_names)
804
+
805
+ assert output_structure in ('df', 'polars'), (f'Output structure should be one of: "df", "map", "list", "polars" '
806
+ f'instead "{output_structure}" was passed')
807
+
808
+ # "df" output structure implies that raw results came as a list
809
+ result_list = _filter_returned_list_by_node(result, node_names)
810
+ result_dict = _form_dict_from_list(result_list, output_structure)
811
+
812
+ if len(result_dict) == 1 and not require_dict:
813
+ return list(result_dict.values())[0]
814
+ else:
815
+ return result_dict
816
+
817
+
818
+ def _process_empty_results(result, query_schema, output_structure):
819
+ """
820
+ Process query results and add columns to empty responses based on query schema.
821
+ """
822
+ schema = [
823
+ (field, np.array([], dtype=otp.types.type2np(dtype)))
824
+ for field, dtype in {**query_schema, 'Time': otp.nsectime}.items()
825
+ ]
826
+ if isinstance(result, otq.SymbolNumpyResultMap):
827
+ empty_data = dict(schema)
828
+ else:
829
+ empty_data = schema
830
+
831
+ if output_structure == 'polars':
832
+ import polars
833
+ empty_data = polars.DataFrame(dict(schema))
834
+
835
+ if isinstance(result, otq.SymbolNumpyResultMap):
836
+ for result_item in result.get_dict().values():
837
+ for node_name, symbol_result in result_item.items():
838
+ if len(symbol_result[0]) == 0:
839
+ result_item[node_name] = (empty_data, symbol_result[1])
840
+ else:
841
+ for idx, result_item in enumerate(result):
842
+ if len(result_item[1]) == 0:
843
+ result[idx] = (
844
+ result_item[0], empty_data, result_item[2], result_item[3], *result_item[4:]
845
+ )
846
+
847
+
848
+ def _preprocess_otp_query(query, query_params):
849
+
850
+ if isinstance(query, otp.query._outputs):
851
+ query = query['OUT']
852
+
853
+ if isinstance(query, otp.query):
854
+ if query.params:
855
+ if query_params:
856
+ raise ValueError("please specify parameters in query or in otp.run only")
857
+ query_params = query.params
858
+ query = query.path
859
+ return query, query_params
860
+
861
+
862
+ def _get_start_end(start, end, timezone):
863
+ """
864
+ Convert datetime objects supported by onetick-py
865
+ to datetime objects supported by onetick-query.
866
+ """
867
+ def support_nanoseconds(time):
868
+ if isinstance(time, (pd.Timestamp, otp.datetime)):
869
+ if otq.webapi:
870
+ # onetick-query_webapi supports pandas.Timestamp and strings in %Y%m%s%H%M%S.%J format
871
+ if isinstance(time, pd.Timestamp):
872
+ return time
873
+ elif isinstance(time, otp.datetime):
874
+ return time.ts
875
+ else:
876
+ if otp.compatibility.is_correct_timezone_used_in_otq_run():
877
+ time = datetime2timeval(time, timezone)
878
+ else:
879
+ # there is a bug in older onetick versions using wrong timezone
880
+ time = datetime2timeval(time, 'GMT')
881
+ return time
882
+
883
+ if start is utils.adaptive:
884
+ start = configuration.config.default_start_time
885
+
886
+ if end is utils.adaptive:
887
+ end = configuration.config.default_end_time
888
+
889
+ # `isinstance(obj, datetime.date)` is not correct because
890
+ # isinstance(<datetime.datetime object>, datetime.date) = True
891
+ # pylint: disable=unidiomatic-typecheck
892
+ if type(start) is datetime.date:
893
+ start = datetime.datetime(start.year, start.month, start.day)
894
+ if type(end) is datetime.date:
895
+ end = datetime.datetime(end.year, end.month, end.day)
896
+
897
+ start = support_nanoseconds(start)
898
+ end = support_nanoseconds(end)
899
+
900
+ return start, end
901
+
902
+
903
+ def _process_output_structure(output_structure):
904
+ if not output_structure or output_structure == "df": # otq doesn't support df
905
+ output_structure = "df"
906
+ output_structure_for_otq = "symbol_result_list"
907
+ elif output_structure == "list":
908
+ output_structure_for_otq = "symbol_result_list"
909
+ elif output_structure == "map":
910
+ output_structure_for_otq = "symbol_result_map"
911
+ elif output_structure == "polars":
912
+ output_structure = "polars"
913
+ output_structure_for_otq = "symbol_result_list"
914
+ else:
915
+ raise ValueError("output_structure support only the following values: df, list, map and polars")
916
+ return output_structure, output_structure_for_otq