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,1042 @@
1
+ import warnings
2
+ from typing import Dict, List, Union, Iterable, Tuple, Optional, Any, Literal
3
+ from datetime import date as dt_date, datetime, timedelta
4
+
5
+ import pandas as pd
6
+ from dateutil.tz import gettz
7
+
8
+ import onetick.py as otp
9
+ from onetick.py import configuration, utils
10
+ from onetick.py.compatibility import is_native_plus_zstd_supported
11
+ from onetick.py.core import db_constants
12
+ from onetick.py.otq import otq
13
+
14
+
15
+ def _datetime2date(dt: Union[dt_date, datetime]) -> dt_date:
16
+ """ Convert datetime and date explicitly into the datetime.date """
17
+ return dt_date(dt.year, dt.month, dt.day)
18
+
19
+
20
+ class DB:
21
+
22
+ """
23
+ An object of available databases that the :py:func:`otp.databases() <onetick.py.databases>` function returns.
24
+ It helps to make initial analysis on the database level: available tick types,
25
+ dates with data, symbols, tick schema, etc.
26
+ """
27
+
28
+ def __init__(self, name, context=utils.default):
29
+ self.name = name
30
+ if context is utils.default or context is None:
31
+ self.context = otp.config.context
32
+ else:
33
+ self.context = context
34
+ self._locator_date_ranges = None
35
+
36
+ def __eq__(self, obj):
37
+ return str(self) == str(obj)
38
+
39
+ def __lt__(self, obj):
40
+ return str(self) < str(obj)
41
+
42
+ def __str__(self):
43
+ return self.name
44
+
45
+ def access_info(self, deep_scan=False, username=None) -> Union[pd.DataFrame, dict]:
46
+ """
47
+ Get access info for this database and ``username``.
48
+
49
+ All dates are returned in GMT timezone.
50
+
51
+ Parameters
52
+ ----------
53
+ deep_scan:
54
+ If False (default) then the access fields are returned from the configuration of the database
55
+ (basically the same fields as specified in the locator) and the dictionary is returned.
56
+ If True then access fields are returned for each available remote host and time interval
57
+ and the :pandas:`pandas.DataFrame` object is returned.
58
+ username:
59
+ Can be used to specify the user for which the query will be executed.
60
+ By default the query is executed for the current user.
61
+
62
+ See also
63
+ --------
64
+ **ACCESS_INFO** OneTick event processor
65
+
66
+ Examples
67
+ --------
68
+
69
+ By default access fields from the basic configuration of the database are returned:
70
+
71
+ >>> some_db = otp.databases()['SOME_DB']
72
+ >>> some_db.access_info() # doctest: +SKIP
73
+ {'DB_NAME': 'SOME_DB',
74
+ 'READ_ACCESS': 1,
75
+ 'WRITE_ACCESS': 1,
76
+ 'MIN_AGE_SET': 0,
77
+ 'MIN_AGE_MSEC': 0,
78
+ 'MAX_AGE_SET': 0,
79
+ 'MAX_AGE_MSEC': 0,
80
+ 'MIN_START_DATE_SET': 0,
81
+ 'MIN_START_DATE_MSEC': Timestamp('1970-01-01 00:00:00'),
82
+ 'MAX_END_DATE_SET': 0,
83
+ 'MAX_END_DATE_MSEC': Timestamp('1970-01-01 00:00:00'),
84
+ 'MIN_AGE_DB_DAYS': 0,
85
+ 'MIN_AGE_DB_DAYS_SET': 0,
86
+ 'MAX_AGE_DB_DAYS': 0,
87
+ 'MAX_AGE_DB_DAYS_SET': 0,
88
+ 'CEP_ACCESS': 1,
89
+ 'DESTROY_ACCESS': 0,
90
+ 'MEMDB_ACCESS': 1}
91
+
92
+ Set parameter ``deep_scan`` to True to return access fields from each available host and time interval:
93
+
94
+ >>> some_db.access_info(deep_scan=True) # doctest: +SKIP
95
+ DB_NAME READ_ACCESS WRITE_ACCESS MIN_AGE_SET MIN_AGE_MSEC MAX_AGE_SET MAX_AGE_MSEC\
96
+ MIN_START_DATE_SET MIN_START_DATE_MSEC MAX_END_DATE_SET MAX_END_DATE_MSEC MIN_AGE_DB_DAYS\
97
+ MIN_AGE_DB_DAYS_SET MAX_AGE_DB_DAYS MAX_AGE_DB_DAYS_SET CEP_ACCESS DESTROY_ACCESS MEMDB_ACCESS\
98
+ SERVER_ADDRESS INTERVAL_START INTERVAL_END
99
+ 0 SOME_DB 1 1 0 0 0 0\
100
+ 0 1970-01-01 0 1970-01-01 0\
101
+ 0 0 0 1 0 1\
102
+ ... 2002-12-30 2100-01-01
103
+ """
104
+ # get parent name for derived databases, only parent databases will be listed by AccessInfo
105
+ name, _, _ = self.name.partition('//')
106
+ node = (
107
+ otq.AccessInfo(info_type='DATABASES', show_for_all_users=False, deep_scan=deep_scan)
108
+ >> otq.WhereClause(where=f'DB_NAME = "{name}"')
109
+ )
110
+ graph = otq.GraphQuery(node)
111
+ df = otp.run(graph,
112
+ symbols='LOCAL::',
113
+ # start and end times don't matter
114
+ start=db_constants.DEFAULT_START_DATE,
115
+ end=db_constants.DEFAULT_END_DATE,
116
+ # and timezone is GMT, because timestamp parameters in ACL are in GMT
117
+ timezone='GMT',
118
+ username=username,
119
+ context=self.context)
120
+ if not df.empty:
121
+ df = df.drop(columns='Time')
122
+ if deep_scan:
123
+ return df
124
+ return dict(df.iloc[0] if not df.empty else {})
125
+
126
+ def show_config(self, config_type: Literal['locator_entry', 'db_time_intervals'] = 'locator_entry') -> dict:
127
+ """
128
+ Shows the specified configuration for a database.
129
+
130
+ Parameters
131
+ ----------
132
+ config_type: str
133
+ If **'locator_entry'** is specified, a string representing db's locator entry along with VDB_FLAG
134
+ (this flag equals 1 when the database is virtual and 0 otherwise) will be returned.
135
+
136
+ If **'db_time_intervals'** is specified,
137
+ then time intervals configured in the locator file will be propagated
138
+ including additional information, such as
139
+ LOCATION, ARCHIVE_DURATION, DAY_BOUNDARY_TZ, DAY_BOUNDARY_OFFSET, ALTERNATIVE_LOCATIONS, etc.
140
+
141
+ See also
142
+ --------
143
+ **DB/SHOW_CONFIG** OneTick event processor
144
+
145
+ Examples
146
+ --------
147
+ .. testcode::
148
+ :skipif: not is_native_plus_zstd_supported()
149
+
150
+ some_db = otp.databases()['SOME_DB']
151
+ print(some_db.show_config()['LOCATOR_STRING'])
152
+
153
+ .. testoutput::
154
+ :options: +ELLIPSIS
155
+
156
+ <DB ARCHIVE_COMPRESSION_TYPE="NATIVE_PLUS_ZSTD" ID="SOME_DB" SYMBOLOGY="BZX" TICK_TIMESTAMP_TYPE="NANOS" >
157
+ <LOCATIONS >
158
+ <LOCATION ACCESS_METHOD="file" DAY_BOUNDARY_TZ="EST5EDT"
159
+ END_TIME="21000101000000" LOCATION="..." START_TIME="20021230000000" />
160
+ </LOCATIONS>
161
+ <RAW_DATA />
162
+ </DB>
163
+
164
+ >>> some_db = otp.databases()['SOME_DB']
165
+ >>> some_db.show_config(config_type='db_time_intervals') # doctest: +ELLIPSIS
166
+ {'START_DATE': 1041206400000, 'END_DATE': 4102444800000,
167
+ 'GROWABLE_ARCHIVE_FLAG': 0, 'ARCHIVE_DURATION': 0,
168
+ 'LOCATION': '...', 'DAY_BOUNDARY_TZ': 'EST5EDT', 'DAY_BOUNDARY_OFFSET': 0, 'ALTERNATIVE_LOCATIONS': ''}
169
+ """
170
+ node = otq.DbShowConfig(db_name=self.name, config_type=config_type.upper())
171
+ graph = otq.GraphQuery(node)
172
+ df = otp.run(graph,
173
+ symbols='LOCAL::',
174
+ # start and end times don't matter
175
+ start=db_constants.DEFAULT_START_DATE,
176
+ end=db_constants.DEFAULT_END_DATE,
177
+ # and timezone is GMT, because timestamp parameters in ACL are in GMT
178
+ timezone='GMT',
179
+ context=self.context)
180
+ if df.empty:
181
+ raise ValueError(f"Can't get config for database '{self.name}'")
182
+ df = df.drop(columns='Time')
183
+ return dict(df.iloc[0])
184
+
185
+ @property
186
+ def min_acl_start_date(self) -> Optional[dt_date]:
187
+ """
188
+ Minimum start date set in ACL for current user.
189
+ Returns None if not set.
190
+ """
191
+ access_info = self.access_info()
192
+ if not access_info:
193
+ return None
194
+ if access_info['MIN_START_DATE_SET'] == 0:
195
+ return None
196
+ return _datetime2date(access_info['MIN_START_DATE_MSEC'])
197
+
198
+ @property
199
+ def max_acl_end_date(self) -> Optional[dt_date]:
200
+ """
201
+ Maximum end date set in ACL for current user.
202
+ Returns None if not set.
203
+ """
204
+ access_info = self.access_info()
205
+ if not access_info:
206
+ return None
207
+ if access_info['MAX_END_DATE_SET'] == 0:
208
+ return None
209
+ return _datetime2date(access_info['MAX_END_DATE_MSEC'])
210
+
211
+ def _fit_time_interval_in_acl(self, start, end, timezone='GMT') -> Tuple[datetime, datetime]:
212
+ """
213
+ Returns the part of time interval between ``start`` and ``end`` that fits ACL start/end time rules.
214
+ ``start`` and ``end`` objects are considered to be timezone-naive and will be localized in ``timezone``.
215
+
216
+ If it's not possible to find such interval, raises ValueError.
217
+ """
218
+ # convert to GMT, because ACL timestamps are in GMT
219
+ start = otp.dt(utils.convert_timezone(start, timezone, 'GMT'))
220
+ end = otp.dt(utils.convert_timezone(end, timezone, 'GMT'))
221
+
222
+ if self.min_acl_start_date is not None:
223
+ if end < otp.dt(self.min_acl_start_date):
224
+ # fully not intersecting intervals
225
+ raise ValueError(f'Date {start.date()} {timezone} violates ACL rules for the database {self.name}:'
226
+ f' minimum start time is {otp.dt(self.min_acl_start_date)} GMT.')
227
+ # partly intersecting intervals, choose the part not violating ACL
228
+ start = max(start, otp.dt(self.min_acl_start_date))
229
+
230
+ if self.max_acl_end_date is not None:
231
+ if start >= otp.dt(self.max_acl_end_date):
232
+ # fully not intersecting intervals
233
+ raise ValueError(f'Date {start.date()} {timezone} violates ACL rules for the database {self.name}:'
234
+ f' maximum (exclusive) end time is {otp.dt(self.max_acl_end_date)} GMT.')
235
+ # partly intersecting intervals, choose the part not violating ACL
236
+ end = min(end, otp.dt(self.max_acl_end_date))
237
+
238
+ # convert back to timezone
239
+ start = utils.convert_timezone(start, 'GMT', timezone)
240
+ end = utils.convert_timezone(end, 'GMT', timezone)
241
+ return start, end
242
+
243
+ def _fit_date_in_acl(self, date, timezone='GMT') -> Tuple[datetime, datetime]:
244
+ """
245
+ Returns the part of ``date`` time interval that fits ACL start/end time rules.
246
+ ``date`` object is considered to be timezone-naive and will be localized in ``timezone``.
247
+
248
+ If it's not possible to find such interval, raises ValueError.
249
+ """
250
+ date = _datetime2date(date)
251
+ start = otp.dt(date)
252
+ end = start + otp.Day(1)
253
+ return self._fit_time_interval_in_acl(start, end, timezone)
254
+
255
+ def _set_intervals(self):
256
+ """
257
+ Finds all date ranges from locators.
258
+ These intervals are required to find all possible dates with data.
259
+ It is only possible by querying the DB_SHOW_LOADED_TIME_RANGE
260
+ against the largest possible query date range.
261
+ """
262
+
263
+ if self._locator_date_ranges is None:
264
+ graph = otq.GraphQuery(otq.DbShowConfiguredTimeRanges(db_name=self.name).tick_type("ANY")
265
+ >> otq.Table(fields='long START_DATE, long END_DATE'))
266
+
267
+ result = otp.run(graph,
268
+ symbols=f'{self.name}::',
269
+ # start and end times don't matter for this query, use some constants
270
+ start=db_constants.DEFAULT_START_DATE,
271
+ end=db_constants.DEFAULT_END_DATE,
272
+ # GMT, because start/end timestamp in locator are in GMT
273
+ timezone='GMT',
274
+ context=self.context)
275
+
276
+ date_ranges = []
277
+
278
+ tz_gmt = gettz('GMT')
279
+ for inx in range(len(result)):
280
+ start_date = result['START_DATE'][inx]
281
+ if start_date < 0:
282
+ # On Windows datetime.fromtimestamp throws an OSError for negative values
283
+ start_date = 0
284
+ start = datetime.fromtimestamp(start_date / 1000, tz=tz_gmt)
285
+ start = start.replace(tzinfo=None)
286
+ try:
287
+ end = datetime.fromtimestamp(result['END_DATE'][inx] / 1000, tz=tz_gmt)
288
+ except (ValueError, OSError):
289
+ # this may happen if value exceeds 9999-12-31 23:59:59.999999
290
+ end = datetime.max
291
+ end = end.replace(tzinfo=None)
292
+
293
+ date_ranges.append((start, end))
294
+
295
+ # merge ranges if necessary to reduce number of queries
296
+ # for `dates` property then
297
+ self._locator_date_ranges = []
298
+ start, end = None, None
299
+
300
+ for t_start, t_end in date_ranges:
301
+ if start is None:
302
+ start = t_start
303
+ if end is None:
304
+ end = t_end
305
+ else:
306
+ if t_start == end:
307
+ end = t_end
308
+ else:
309
+ self._locator_date_ranges.append((start, end))
310
+ start, end = t_start, t_end
311
+
312
+ if start and end:
313
+ self._locator_date_ranges.append((start, end))
314
+
315
+ def _show_loaded_time_ranges(self, start, end, only_last=False, prefer_speed_over_accuracy=False):
316
+ kwargs = {}
317
+ if prefer_speed_over_accuracy:
318
+ kwargs['prefer_speed_over_accuracy'] = prefer_speed_over_accuracy
319
+
320
+ eps = otq.DbShowLoadedTimeRanges(use_cache=True, **kwargs).tick_type('ANY')
321
+ eps = eps >> otq.WhereClause(where='NUM_LOADED_PARTITIONS > 0')
322
+ if only_last:
323
+ eps = eps >> otq.LastTick()
324
+
325
+ graph = otq.GraphQuery(eps)
326
+ result = otp.run(graph,
327
+ symbols=f'{self.name}::',
328
+ start=start,
329
+ end=end,
330
+ # GMT works properly for locators with gap
331
+ timezone='GMT',
332
+ context=self.context)
333
+
334
+ dates = []
335
+ # every record contains consequent intervals of data on disk
336
+ for inx in range(len(result)):
337
+ start = datetime.strptime(str(result['START_DATE'][inx]), '%Y%m%d')
338
+ end = datetime.strptime(str(result['END_DATE'][inx]), '%Y%m%d')
339
+ if only_last:
340
+ return [_datetime2date(end)]
341
+ while start <= end:
342
+ dates.append(_datetime2date(start))
343
+ start += timedelta(days=1)
344
+
345
+ return dates
346
+
347
+ def __split_loaded_time_ranges(self, locator_start, locator_end, only_last):
348
+ # locator date range can be very big, so splitting it in smaller parts
349
+ # (because _show_loaded_time_ranges() can be very slow for big time ranges)
350
+ # it is especially useful when we only need the last date
351
+ dates = []
352
+ start = end = locator_end
353
+ delta = 1 if only_last else 365
354
+ while locator_start < start:
355
+ start = end - timedelta(days=delta)
356
+ start = max(locator_start, start)
357
+ loaded_dates = self._show_loaded_time_ranges(start, end, only_last=only_last)
358
+ if only_last and loaded_dates:
359
+ return [loaded_dates[-1]]
360
+ dates = loaded_dates + dates
361
+ end = start
362
+ # if we are not getting data, then increasing time range to find it faster
363
+ if not loaded_dates:
364
+ delta *= 2
365
+ return dates
366
+
367
+ def __get_dates(self, only_last=False, respect_acl=False, check_index_file=utils.adaptive):
368
+ """ Returns list of dates in GMT timezone with data """
369
+ self._set_intervals()
370
+
371
+ dates = []
372
+ today = dt_date.today()
373
+ today = datetime(today.year, today.month, today.day)
374
+ # searching in reversed order in case we need only_last date
375
+ for locator_start, locator_end in reversed(self._locator_date_ranges):
376
+ # future is not loaded yet
377
+ if locator_start > today:
378
+ continue
379
+ if locator_end > today:
380
+ locator_end = today
381
+
382
+ if respect_acl:
383
+ try:
384
+ locator_start, locator_end = self._fit_time_interval_in_acl(locator_start, locator_end)
385
+ except ValueError:
386
+ # fully not intersecting intervals, trying next locator date range
387
+ continue
388
+
389
+ if check_index_file is utils.adaptive or check_index_file is None:
390
+ prefer_speed_over_accuracy = True
391
+ else:
392
+ prefer_speed_over_accuracy = not check_index_file
393
+ try:
394
+ loaded_dates = self._show_loaded_time_ranges(locator_start, locator_end,
395
+ only_last=only_last,
396
+ prefer_speed_over_accuracy=prefer_speed_over_accuracy)
397
+ except Exception as e:
398
+ # parameter prefer_speed_over_accuracy is not supported on all OneTick versions and servers
399
+ if check_index_file is not utils.adaptive:
400
+ raise ValueError(
401
+ "Parameter 'check_index_file' is not supported by the API or OneTick server"
402
+ ) from e
403
+ # in this case we fall back to splitting the locator range into smaller parts to increase speed
404
+ loaded_dates = self.__split_loaded_time_ranges(locator_start, locator_end, only_last)
405
+
406
+ if only_last and loaded_dates:
407
+ return loaded_dates[-1]
408
+ dates = loaded_dates + dates
409
+
410
+ if only_last and len(dates) == 0:
411
+ return None # no data on disk
412
+
413
+ return dates
414
+
415
+ def dates(self, respect_acl=False, check_index_file=utils.adaptive):
416
+ """
417
+ Returns list of dates in GMT timezone for which data is available.
418
+
419
+ Parameters
420
+ ----------
421
+ respect_acl: bool
422
+ If True then only the dates that current user has access to will be returned
423
+ check_index_file: bool
424
+ If True, then file *index* will be searched for to determine if a database is loaded for a date.
425
+ This check may be expensive, in terms of time it takes,
426
+ when the file resides on NFS or on object storage, such as S3.
427
+ If this parameter is set to False, then only the database directory for a date will be searched.
428
+ This will increase performance, but may also return the days that are configured
429
+ but where there is actually no data.
430
+ By default this option is set to False if it is supported by API and the server,
431
+ otherwise it is set to True.
432
+
433
+ Returns
434
+ -------
435
+ ``datetime.date`` or ``None``
436
+ Returns ``None`` when there is no data in the database
437
+
438
+ Examples
439
+ --------
440
+ >>> some_db = otp.databases()['SOME_DB']
441
+ >>> some_db.dates()
442
+ [datetime.date(2003, 12, 1)]
443
+ """
444
+ return self.__get_dates(respect_acl=respect_acl, check_index_file=check_index_file)
445
+
446
+ def last_not_empty_date(self, last_date, days_back, timezone=None, tick_type=None):
447
+ """
448
+ Find first day that has data
449
+ starting from ``last_date`` and going ``days_back`` number of days back.
450
+ """
451
+ min_locator_date = self.min_locator_date()
452
+ for i in range(days_back + 1):
453
+ date = _datetime2date(last_date - timedelta(days=i))
454
+ if date < min_locator_date:
455
+ break
456
+ try:
457
+ tick_types = self.tick_types(date, timezone=timezone)
458
+ except ValueError:
459
+ # acl date violation
460
+ break
461
+ if tick_type is None and tick_types:
462
+ return date
463
+ if tick_type is not None and tick_type in tick_types:
464
+ return date
465
+ return None
466
+
467
+ @property
468
+ def last_date(self):
469
+ """
470
+ The latest date on which db has data and the current user has access to.
471
+
472
+ Returns
473
+ -------
474
+ ``datetime.date`` or ``None``
475
+ Returns ``None`` when there is no data in the database
476
+
477
+ Examples
478
+ --------
479
+ >>> some_db = otp.databases()['SOME_DB']
480
+ >>> some_db.last_date
481
+ datetime.date(2003, 12, 1)
482
+ """
483
+ return self.get_last_date()
484
+
485
+ def get_last_date(self, tick_type=None, timezone=None, show_warnings=True, check_index_file=utils.adaptive):
486
+ last_date = self.__get_dates(only_last=True, respect_acl=True, check_index_file=check_index_file)
487
+ if last_date is None:
488
+ return None
489
+ # It might happen that database loading processes is configured
490
+ # to work over weekends and holidays and therefore
491
+ # there are days that are configured but have no data, tick types and schema.
492
+ # We want to find the closest not empty day because
493
+ # we want to expose the most actual schema to end user.
494
+ # For example, this is a case of OneTick Cloud US_COMP database.
495
+ # We only scan 5 previous days to cover weekends + possible conjuncted holidays.
496
+ # According to the official NYSE calendar there are no more than 5 closed days.
497
+ date = self.last_not_empty_date(last_date, days_back=5, tick_type=tick_type, timezone=timezone)
498
+ if date is None:
499
+ if show_warnings:
500
+ warnings.warn(
501
+ "Can't find not empty day for the last 5 days, using last configured day. "
502
+ "Try to use .last_not_empty_date() function to find older not empty days."
503
+ )
504
+ return last_date
505
+ return date
506
+
507
+ def tick_types(self, date=None, timezone=None) -> List[str]:
508
+ """
509
+ Returns list of tick types for the ``date``.
510
+
511
+ Parameters
512
+ ----------
513
+ date: :class:`otp.dt <onetick.py.datetime>`, :py:class:`datetime.datetime`, optional
514
+ Date for the tick types look up. ``None`` means the :attr:`last_date`
515
+ timezone: str, optional
516
+ Timezone for the look up. ``None`` means the default timezone.
517
+
518
+ Returns
519
+ -------
520
+ list
521
+ List with string values of available tick types.
522
+
523
+ Examples
524
+ --------
525
+ >>> us_comp_db = otp.databases()['US_COMP']
526
+ >>> us_comp_db.tick_types(date=otp.dt(2022, 3, 1))
527
+ ['QTE', 'TRD']
528
+ """
529
+ date = self.last_date if date is None else date
530
+ if timezone is None:
531
+ timezone = configuration.config.tz
532
+ time_params: Dict[str, Any] = {}
533
+
534
+ if date is not None:
535
+ time_params['start'], time_params['end'] = self._fit_date_in_acl(date, timezone=timezone)
536
+
537
+ # PY-458: don't use cache, it can return different result in some cases
538
+ result = otp.run(otq.DbShowTickTypes(use_cache=False,
539
+ show_schema=False,
540
+ include_memdb=True),
541
+ symbols=f'{self.name}::',
542
+ **time_params,
543
+ timezone=timezone,
544
+ context=self.context)
545
+
546
+ if len(result) == 0:
547
+ return []
548
+
549
+ return result['TICK_TYPE_NAME'].tolist()
550
+
551
+ def min_locator_date(self):
552
+ self._set_intervals()
553
+ min_date = min(obj[0] for obj in self._locator_date_ranges)
554
+ return _datetime2date(min_date)
555
+
556
+ def schema(self, date=None, tick_type=None, timezone=None, check_index_file=utils.adaptive) -> Dict[str, type]:
557
+ """
558
+ Gets the schema of the database.
559
+
560
+ Parameters
561
+ ----------
562
+ date: :class:`otp.dt <onetick.py.datetime>`, :py:class:`datetime.datetime`, optional
563
+ Date for the schema. ``None`` means the :attr:`last_date`
564
+ tick_type: str, optional
565
+ Specifies a tick type for schema. ``None`` means use the one available
566
+ tick type, if there are multiple tick types then it raises the ``Exception``.
567
+ It uses the :meth:`tick_types` method.
568
+ timezone: str, optional
569
+ Allows to specify a timezone for searching tick types.
570
+ check_index_file: bool
571
+ If True, then file *index* will be searched for to determine if a database is loaded for a date.
572
+ This check may be expensive, in terms of time it takes,
573
+ when the file resides on NFS or on object storage, such as S3.
574
+ If this parameter is set to False, then only the database directory for a date will be searched.
575
+ This will increase performance, but may also return the days that are configured
576
+ but where there is actually no data.
577
+ By default this option is set to False if it is supported by API and the server,
578
+ otherwise it is set to True.
579
+
580
+ Returns
581
+ -------
582
+ dict
583
+ Dict where keys are field names and values are ``onetick.py`` :ref:`types <schema concept>`.
584
+ It's compatible with the :attr:`onetick.py.Source.schema` methods.
585
+
586
+ Examples
587
+ --------
588
+ >>> us_comp_db = otp.databases()['US_COMP']
589
+ >>> us_comp_db.schema(tick_type='TRD', date=otp.dt(2022, 3, 1))
590
+ {'PRICE': <class 'float'>, 'SIZE': <class 'int'>}
591
+ """
592
+ orig_date = date
593
+
594
+ if date is None:
595
+ date = self.get_last_date(tick_type=tick_type, timezone=timezone, check_index_file=check_index_file)
596
+ if timezone is None:
597
+ timezone = configuration.config.tz
598
+ if tick_type is None:
599
+ tick_types = self.tick_types(date=date, timezone=timezone)
600
+ if len(tick_types) == 0:
601
+ raise ValueError("No tick types has found and specified")
602
+ if len(tick_types) > 1:
603
+ raise ValueError("Database has multiple tick types, please specify using the `tick_type` parameter")
604
+
605
+ tick_type = tick_types[0]
606
+
607
+ if date is None:
608
+ # it might happen when a database has no data on disks
609
+ return {}
610
+
611
+ # Convert explicitly into the datetime.date, because min_date and date
612
+ # could be date or datetime types, and datetime is not comparable with datetime.date
613
+ date = _datetime2date(date)
614
+
615
+ start, end = self._fit_date_in_acl(date, timezone=timezone)
616
+
617
+ # TODO: refactor into global method, use in tick_types()
618
+ def get_schema(use_cache: bool = True):
619
+ return otp.run(otq.DbShowTickTypes(use_cache=use_cache,
620
+ show_schema=True,
621
+ include_memdb=True)
622
+ >> otq.WhereClause(where=f'TICK_TYPE_NAME="{tick_type}"'),
623
+ symbols=f'{self.name}::',
624
+ start=start,
625
+ end=end,
626
+ timezone=timezone,
627
+ context=self.context)
628
+
629
+ result = get_schema(use_cache=True)
630
+ if not len(result):
631
+ # in case cache settings in database are bad (e.g. BEXRTS-1220)
632
+ result = get_schema(use_cache=False)
633
+
634
+ fields: Iterable
635
+ if len(result):
636
+ # filter schema by date
637
+ date_to_filter = None
638
+ if orig_date:
639
+ # if date is passed as a parameter -- then use it
640
+ date_to_filter = date
641
+ else:
642
+ # otherwise use the closest date
643
+ date_to_filter = result['Time'].max()
644
+
645
+ result = result[(result['Time'] >= pd.Timestamp(date_to_filter))]
646
+
647
+ fields = zip(result['FIELD_NAME'].tolist(),
648
+ result['FIELD_TYPE_NAME'].tolist(),
649
+ result['FIELD_SIZE'].tolist())
650
+ else:
651
+ fields = []
652
+
653
+ schema = {}
654
+
655
+ for fname, ftype, fsize in fields:
656
+ dtype: type
657
+
658
+ if 'UINT32' in ftype:
659
+ dtype = otp.uint
660
+ elif 'UINT64' in ftype:
661
+ dtype = otp.ulong
662
+ elif 'INT32' in ftype:
663
+ dtype = otp.int
664
+ elif 'INT64' in ftype:
665
+ # otp.long can be used too, but we use int for backward compatibility
666
+ dtype = int
667
+ elif 'INT8' in ftype:
668
+ dtype = otp.byte
669
+ elif 'INT16' in ftype:
670
+ dtype = otp.short
671
+ elif 'INT' in ftype:
672
+ dtype = int
673
+ elif 'MSEC' in ftype:
674
+ dtype = otp.msectime
675
+ elif 'NSEC' in ftype:
676
+ dtype = otp.nsectime
677
+ elif 'DOUBLE' in ftype or 'FLOAT' in ftype:
678
+ dtype = float
679
+ elif 'DECIMAL' in ftype:
680
+ dtype = otp.decimal
681
+ elif 'VARSTRING' in ftype:
682
+ dtype = otp.varstring
683
+ elif 'STRING' in ftype:
684
+ if fsize == 64:
685
+ dtype = str
686
+ else:
687
+ dtype = otp.string[fsize]
688
+ else:
689
+ warnings.warn(
690
+ f"Unsupported field type '{ftype}' for field '{fname}'. "
691
+ "Note that this field will be ignored "
692
+ "and will not be added to the python schema, "
693
+ "but will still remain in the OneTick schema."
694
+ )
695
+ continue
696
+
697
+ schema[fname] = dtype
698
+
699
+ return schema
700
+
701
+ def symbols(self, date=None, timezone=None, tick_type=None, pattern='.*') -> List[str]:
702
+ """
703
+ Finds a list of available symbols in the database
704
+
705
+ Parameters
706
+ ----------
707
+ date: :class:`otp.dt <onetick.py.datetime>`, :py:class:`datetime.datetime`, optional
708
+ Date for the symbols look up. ``None`` means the :attr:`last_date`
709
+ tick_type: str, optional
710
+ Tick type for symbols. ``None`` means union across all tick types.
711
+ timezone: str, optional
712
+ Timezone for the lookup. ``None`` means the default timezone.
713
+ pattern: str
714
+ Regular expression to select symbols.
715
+
716
+ Examples
717
+ --------
718
+ >>> us_comp_db = otp.databases()['US_COMP']
719
+ >>> us_comp_db.symbols(date=otp.dt(2022, 3, 1), tick_type='TRD', pattern='^AAP.*')
720
+ ['AAP', 'AAPL']
721
+ """
722
+ import onetick.py as otp
723
+
724
+ if date is None:
725
+ date = self.last_date
726
+ if timezone is None:
727
+ timezone = configuration.config.tz
728
+ if tick_type is None:
729
+ tick_type = ''
730
+
731
+ eps = otq.FindDbSymbols(pattern='%', tick_type_field=tick_type) \
732
+ >> otq.AddField(field='varstring SYMBOL', value='regex_replace(SYMBOL_NAME, ".*::", "")') \
733
+ >> otq.WhereClause(where=f'regex_match(SYMBOL, "{pattern}")') \
734
+ >> otq.Table(fields='SYMBOL')
735
+
736
+ result = otp.run(eps,
737
+ symbols=f'{self.name}::',
738
+ start=date,
739
+ end=date + timedelta(days=1),
740
+ timezone=timezone,
741
+ context=self.context)
742
+
743
+ if len(result) == 0:
744
+ return []
745
+
746
+ return result['SYMBOL'].tolist()
747
+
748
+ def show_archive_stats(
749
+ self,
750
+ start=utils.adaptive,
751
+ end=utils.adaptive,
752
+ date=None,
753
+ timezone='GMT',
754
+ ) -> pd.DataFrame:
755
+ """
756
+ This method shows various stats about the queried symbol,
757
+ as well as an archive as a whole for each day within the queried interval.
758
+
759
+ Accelerator databases are not supported.
760
+ Memory databases will be ignored even within their life hours.
761
+
762
+ Archive stats returned:
763
+
764
+ * COMPRESSION_TYPE - archive compression type.
765
+ In older archives native compression flag is not stored,
766
+ so for example for gzip compression this field may say "GZIP or NATIVE_PLUS_GZIP".
767
+ The meta_data_upgrader.exe tool can be used to determine and inject that information in such cases
768
+ in order to get a more precise result in this field.
769
+ * TIME_RANGE_VALIDITY - whether lowest and highest loaded timestamps (see below) are known.
770
+ Like native compression flag, this information is missing in older archives
771
+ and can be added using meta_data_upgrader.exe tool.
772
+ * LOWEST_LOADED_DATETIME - the lowest loaded timestamp for the queried interval (across all symbols)
773
+ * HIGHEST_LOADED_DATETIME - the highest loaded timestamp for the queried interval (across all symbols)
774
+ * TOTAL_TICKS - the number of ticks for the queried interval (across all symbols).
775
+ Also missing in older archives and can be added using meta_data_upgrader.exe.
776
+ If not available, -1 will be returned.
777
+ * SYMBOL_DATA_SIZE - the size of the symbol in archive in bytes.
778
+ This information is also missing in older archives, however the other options, it cannot later be added.
779
+ In such cases -1 will be returned.
780
+ * TOTAL_SYMBOLS - the number of symbols for the queried interval
781
+ * TOTAL_SIZE - archive size in bytes for the queried interval
782
+ (including the garbage potentially accumulated during appends).
783
+
784
+ Note
785
+ ----
786
+ Fields **LOWEST_LOADED_DATETIME** and **HIGHEST_LOADED_DATETIME** are returned in GMT timezone,
787
+ so the default value of parameter ``timezone`` is GMT too.
788
+
789
+ See also
790
+ --------
791
+ **SHOW_ARCHIVE_STATS** OneTick event processor
792
+
793
+ Examples
794
+ --------
795
+
796
+ Show stats for a particular date for a database SOME_DB:
797
+
798
+ .. testcode::
799
+ :skipif: not is_native_plus_zstd_supported()
800
+
801
+ db = otp.databases()['SOME_DB']
802
+ stats = db.show_archive_stats(date=otp.dt(2003, 12, 1))
803
+ print(stats)
804
+
805
+ .. testoutput::
806
+ :options: +ELLIPSIS
807
+
808
+ Time COMPRESSION_TYPE TIME_RANGE_VALIDITY LOWEST_LOADED_DATETIME HIGHEST_LOADED_DATETIME...
809
+ 0 2003-12-01 05:00:00 NATIVE_PLUS_ZSTD VALID 2003-12-01 05:00:00 2003-12-01 05:00:00.002...
810
+ """
811
+ node = otq.ShowArchiveStats()
812
+ graph = otq.GraphQuery(node)
813
+ df = otp.run(graph,
814
+ symbols=f'{self.name}::',
815
+ start=start,
816
+ end=end,
817
+ date=date,
818
+ timezone=timezone,
819
+ context=self.context)
820
+ return df
821
+
822
+ def ref_data(
823
+ self,
824
+ ref_data_type: str,
825
+ symbol_date=None,
826
+ start=utils.adaptive,
827
+ end=utils.adaptive,
828
+ date=None,
829
+ timezone='GMT',
830
+ symbol: str = '',
831
+ ) -> pd.DataFrame:
832
+ """
833
+ Shows reference data for the specified security and reference data type.
834
+
835
+ It can be used to view corporation actions,
836
+ symbol name changes,
837
+ primary exchange info and symbology mapping for a securities,
838
+ as well as the list of symbologies,
839
+ names of custom adjustment types for corporate actions present in a reference database
840
+ as well as names of continuous contracts in database symbology.
841
+
842
+ Parameters
843
+ ----------
844
+ ref_data_type: str
845
+ Type of reference data to be queried. Possible values are:
846
+
847
+ * corp_actions
848
+ * symbol_name_history
849
+ * primary_exchange
850
+ * symbol_calendar
851
+ * symbol_currency
852
+ * symbology_mapping
853
+ * symbology_list
854
+ * custom_adjustment_type_list
855
+ * all_calendars
856
+ * all_continuous_contract_names
857
+ symbol_date:
858
+ This parameter must be specified for some reference data types to be queried.
859
+ symbol:
860
+ Symbol name for the query (may be useful for some ``ref_data_type``).
861
+
862
+ See also
863
+ --------
864
+ **REF_DATA** OneTick event processor
865
+
866
+ Examples
867
+ --------
868
+
869
+ Show calendars for a database TRAIN_A_PRL_TRD in the given range:
870
+
871
+ >>> db = otp.databases()['TRAIN_A_PRL_TRD'] # doctest: +SKIP
872
+ >>> db.ref_data('all_calendars', # doctest: +SKIP
873
+ ... start=otp.dt(2018, 2, 1),
874
+ ... end=otp.dt(2018, 2, 9),
875
+ ... symbol_date=otp.dt(2018, 2, 1))
876
+ Time END_DATETIME CALENDAR_NAME SESSION_NAME SESSION_FLAGS DAY_PATTERN START_HHMMSS\
877
+ END_HHMMSS TIMEZONE PRIORITY DESCRIPTION
878
+ 0 2018-02-01 00:00:00 2018-02-06 23:59:59 FRED Regular R 0.0.12345 93000\
879
+ 160000 EST5EDT 0
880
+ 1 2018-02-06 23:59:59 2018-02-07 23:59:59 FRED Holiday H 0.0.12345 93000\
881
+ 160000 EST5EDT 1
882
+ 2 2018-02-07 23:59:59 2050-12-31 23:59:59 FRED Regular F 0.0.12345 93000\
883
+ 160000 EST5EDT 0
884
+
885
+ Set symbol name with ``symbol`` parameter:
886
+
887
+ >>> db = otp.databases()['US_COMP_SAMPLE'] # doctest: +SKIP
888
+ >>> db.ref_data(ref_data_type='corp_actions', # doctest: +SKIP
889
+ ... start=otp.dt(2025, 1, 2),
890
+ ... end=otp.dt(2025, 7, 2),
891
+ ... symbol_date=otp.dt(2025, 7, 1),
892
+ ... symbol='WMT',
893
+ ... timezone='America/New_York')
894
+ Time MULTIPLICATIVE_ADJUSTMENT ADDITIVE_ADJUSTMENT ADJUSTMENT_TYPE
895
+ 0 2025-03-21 1.000000 0.235 CASH_DIVIDEND
896
+ 1 2025-03-21 0.997261 0.000 MULTI_ADJ_CASH
897
+ 2 2025-05-09 1.000000 0.235 CASH_DIVIDEND
898
+ 3 2025-05-09 0.997588 0.000 MULTI_ADJ_CASH
899
+ """
900
+ ref_data_type = ref_data_type.upper()
901
+ node = otq.RefData(ref_data_type=ref_data_type)
902
+ graph = otq.GraphQuery(node)
903
+ df = otp.run(graph,
904
+ symbols=f'{self.name}::{symbol}',
905
+ symbol_date=symbol_date,
906
+ start=start,
907
+ end=end,
908
+ date=date,
909
+ timezone=timezone,
910
+ context=self.context)
911
+ return df
912
+
913
+
914
+ def databases(context=utils.default, derived=False) -> Dict[str, DB]:
915
+ """
916
+ Gets all available databases in the ``context``
917
+
918
+ Parameters
919
+ ----------
920
+ context: str, optional
921
+ Context to run the query.
922
+ If not set then default :py:attr:`context<onetick.py.configuration.Config.context>` is used.
923
+ See :ref:`guide about switching contexts <switching contexts>` for examples.
924
+ derived: bool, dict
925
+ If False then derived databases are not returned.
926
+ If dict then its items used as parameters to :py:func:`~onetick.py.derived_databases`
927
+ If True then default parameters for :py:func:`~onetick.py.derived_databases` are used.
928
+
929
+ See also
930
+ --------
931
+ | **SHOW_DB_LIST** OneTick event processor
932
+ | :py:func:`derived_databases`
933
+
934
+ Returns
935
+ -------
936
+ dict
937
+ Dict where keys are database names and values are :class:`DB <onetick.py.db._inspection.DB>` objects
938
+ with ``context`` specified.
939
+ """
940
+ dbs = otp.run(otq.ShowDbList().tick_type("ANY"),
941
+ symbols='LOCAL::',
942
+ # start and end times don't matter for this query, use some constants
943
+ start=db_constants.DEFAULT_START_DATE,
944
+ end=db_constants.DEFAULT_END_DATE,
945
+ context=context)
946
+
947
+ # WebAPI returns empty DataFrame (no columns) if there are no databases
948
+ if len(dbs) == 0:
949
+ return {}
950
+
951
+ db_list = list(dbs['DATABASE_NAME'])
952
+ db_dict = {db_name: DB(db_name, context=context) for db_name in db_list}
953
+ if derived:
954
+ kwargs = derived if isinstance(derived, dict) else {}
955
+ kwargs.setdefault('context', context)
956
+ db_dict.update(
957
+ derived_databases(**kwargs)
958
+ )
959
+ return db_dict
960
+
961
+
962
+ def derived_databases(
963
+ context=utils.default,
964
+ start=None, end=None,
965
+ selection_criteria='all',
966
+ db=None,
967
+ db_discovery_scope='query_host_only',
968
+ ) -> Dict[str, DB]:
969
+ """
970
+ Gets available derived databases.
971
+
972
+ Parameters
973
+ ----------
974
+ context: str, optional
975
+ Context to run the query.
976
+ If not set then default :py:attr:`context<onetick.py.configuration.Config.context>` is used.
977
+ See :ref:`guide about switching contexts <switching contexts>` for examples.
978
+ start: :py:class:`otp.datetime <onetick.py.datetime>`, optional
979
+ If both ``start`` and ``end`` are set, then listing databases in this range only.
980
+ Otherwise list databases from all configured time ranges for databases.
981
+
982
+ If ``db`` is set, then
983
+ :py:attr:`otp.config.default_start_time <onetick.py.configuration.Config.default_start_time>`
984
+ is used by default.
985
+ end: :py:class:`otp.datetime <onetick.py.datetime>`, optional
986
+ If both ``start`` and ``end`` are set, then listing databases in this range only.
987
+ Otherwise list databases from all configured time ranges for databases.
988
+
989
+ If ``db`` is set, then
990
+ :py:attr:`otp.config.default_end_time <onetick.py.configuration.Config.default_end_time>` is used by default.
991
+ selection_criteria: str
992
+ Possible values: *all*, *derived_from_current_db*, *direct_children_of_current_db*.
993
+ db: str, optional
994
+ Specifies database name if ``selection_criteria`` is set to
995
+ *derived_from_current_db* or *direct_children_of_current_db*.
996
+ Must be set in this case, otherwise does nothing.
997
+ db_discovery_scope: str
998
+ When *query_host_and_all_reachable_hosts* is specified,
999
+ an attempt will be performed to get derived databases from all reachable hosts.
1000
+ When *query_host_only* is specified,
1001
+ only derived databases from the host on which the query is performed will be returned.
1002
+
1003
+ See also
1004
+ --------
1005
+ **SHOW_DERIVED_DB_LIST** OneTick event processor
1006
+
1007
+ Returns
1008
+ -------
1009
+ dict
1010
+ Dict where keys are database names and values are :class:`DB <onetick.py.db._inspection.DB>` objects
1011
+ with ``context`` specified.
1012
+ """
1013
+ if start and end:
1014
+ time_range = otq.ShowDerivedDbList.TimeRange.QUERY_TIME_INTERVAL
1015
+ else:
1016
+ if db is None:
1017
+ # start and end times don't matter in this case, use some constants
1018
+ start = db_constants.DEFAULT_START_DATE
1019
+ end = db_constants.DEFAULT_END_DATE
1020
+ else:
1021
+ start = otp.config.default_start_time
1022
+ end = otp.config.default_end_time
1023
+ time_range = otq.ShowDerivedDbList.TimeRange.CONFIGURED_TIME_INTERVAL
1024
+
1025
+ selection_criteria = getattr(otq.ShowDerivedDbList.SelectionCriteria, selection_criteria.upper())
1026
+ db_discovery_scope = getattr(otq.ShowDerivedDbList.DbDiscoveryScope, db_discovery_scope.upper())
1027
+
1028
+ if selection_criteria != otq.ShowDerivedDbList.SelectionCriteria.ALL and not db:
1029
+ raise ValueError(f"Parameter 'db' must be set when parameter 'selection_criteria' is {selection_criteria}")
1030
+
1031
+ ep = otq.ShowDerivedDbList(
1032
+ time_range=time_range,
1033
+ selection_criteria=selection_criteria,
1034
+ db_discovery_scope=db_discovery_scope,
1035
+ )
1036
+ ep = ep.tick_type('ANY')
1037
+ db = db or 'LOCAL'
1038
+ dbs = otp.run(ep, symbols=f'{db}::', start=start, end=end, context=context)
1039
+ if len(dbs) == 0:
1040
+ return {}
1041
+ db_list = list(dbs['DERIVED_DB_NAME'])
1042
+ return {db_name: DB(db_name, context=context) for db_name in db_list}