onetick-py 1.176.0__py3-none-any.whl → 1.179.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.
- onetick/py/__init__.py +1 -1
- onetick/py/_version.py +1 -1
- onetick/py/aggregations/_docs.py +10 -0
- onetick/py/aggregations/order_book.py +18 -6
- onetick/py/compatibility.py +88 -18
- onetick/py/core/_source/source_methods/misc.py +73 -13
- onetick/py/core/_source/tmp_otq.py +8 -0
- onetick/py/core/source.py +25 -3
- onetick/py/db/_inspection.py +113 -50
- onetick/py/oqd/sources.py +22 -8
- onetick/py/run.py +9 -5
- onetick/py/sources/__init__.py +1 -0
- onetick/py/sources/dataframe.py +370 -0
- onetick/py/sources/symbols.py +4 -6
- onetick/py/sources/ticks.py +10 -2
- onetick/py/types.py +36 -0
- onetick/py/utils/__init__.py +1 -0
- onetick/py/utils/debug.py +17 -0
- onetick/py/utils/render_cli.py +88 -0
- {onetick_py-1.176.0.dist-info → onetick_py-1.179.0.dist-info}/METADATA +2 -1
- {onetick_py-1.176.0.dist-info → onetick_py-1.179.0.dist-info}/RECORD +25 -22
- {onetick_py-1.176.0.dist-info → onetick_py-1.179.0.dist-info}/entry_points.txt +1 -0
- {onetick_py-1.176.0.dist-info → onetick_py-1.179.0.dist-info}/WHEEL +0 -0
- {onetick_py-1.176.0.dist-info → onetick_py-1.179.0.dist-info}/licenses/LICENSE +0 -0
- {onetick_py-1.176.0.dist-info → onetick_py-1.179.0.dist-info}/top_level.txt +0 -0
onetick/py/db/_inspection.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import itertools
|
|
2
2
|
import warnings
|
|
3
|
-
from
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from typing import Union, Iterable, Tuple, Optional, Literal
|
|
4
5
|
from datetime import date as dt_date, datetime, timedelta
|
|
6
|
+
from functools import wraps
|
|
5
7
|
|
|
6
8
|
import pandas as pd
|
|
7
9
|
from dateutil.tz import gettz
|
|
@@ -19,6 +21,39 @@ def _datetime2date(dt: Union[dt_date, datetime]) -> dt_date:
|
|
|
19
21
|
return dt_date(dt.year, dt.month, dt.day)
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
def _method_cache(meth):
|
|
25
|
+
"""
|
|
26
|
+
Cache the output of class method.
|
|
27
|
+
Cache is created inside of the self object (in self.__cache property)
|
|
28
|
+
and will be deleted when self object is destroyed.
|
|
29
|
+
|
|
30
|
+
This is a rewrite of functools.cache,
|
|
31
|
+
but it doesn't add self argument to cache key and thus doesn't keep reference to self forever.
|
|
32
|
+
"""
|
|
33
|
+
@wraps(meth)
|
|
34
|
+
def wrapper(self, *args, **kwargs):
|
|
35
|
+
|
|
36
|
+
# cache key is a tuple of all arguments
|
|
37
|
+
key = args
|
|
38
|
+
for kw_tup in kwargs.items():
|
|
39
|
+
key += kw_tup
|
|
40
|
+
key = hash(key)
|
|
41
|
+
|
|
42
|
+
if not hasattr(self, '__cache'):
|
|
43
|
+
self.__cache = defaultdict(dict)
|
|
44
|
+
|
|
45
|
+
method_cache = self.__cache[meth.__name__]
|
|
46
|
+
|
|
47
|
+
miss = object()
|
|
48
|
+
result = method_cache.get(key, miss)
|
|
49
|
+
if result is miss:
|
|
50
|
+
result = meth(self, *args, **kwargs)
|
|
51
|
+
method_cache[key] = result
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
return wrapper
|
|
55
|
+
|
|
56
|
+
|
|
22
57
|
class DB:
|
|
23
58
|
|
|
24
59
|
"""
|
|
@@ -37,7 +72,11 @@ class DB:
|
|
|
37
72
|
self._locator_date_ranges = None
|
|
38
73
|
|
|
39
74
|
def __eq__(self, obj):
|
|
40
|
-
return
|
|
75
|
+
return all((
|
|
76
|
+
self.name == obj.name,
|
|
77
|
+
self.description == obj.description,
|
|
78
|
+
self.context == obj.context,
|
|
79
|
+
))
|
|
41
80
|
|
|
42
81
|
def __lt__(self, obj):
|
|
43
82
|
return str(self) < str(obj)
|
|
@@ -45,6 +84,7 @@ class DB:
|
|
|
45
84
|
def __str__(self):
|
|
46
85
|
return self.name
|
|
47
86
|
|
|
87
|
+
@_method_cache
|
|
48
88
|
def access_info(self, deep_scan=False, username=None) -> Union[pd.DataFrame, dict]:
|
|
49
89
|
"""
|
|
50
90
|
Get access info for this database and ``username``.
|
|
@@ -111,13 +151,19 @@ class DB:
|
|
|
111
151
|
>> otq.WhereClause(where=f'DB_NAME = "{name}"')
|
|
112
152
|
)
|
|
113
153
|
graph = otq.GraphQuery(node)
|
|
154
|
+
|
|
155
|
+
self._set_intervals()
|
|
156
|
+
# start and end times don't matter, but need to fit in the configured time ranges
|
|
157
|
+
start, end = self._locator_date_ranges[-1]
|
|
158
|
+
|
|
114
159
|
df = otp.run(graph,
|
|
115
|
-
symbols='
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
end=db_constants.DEFAULT_END_DATE,
|
|
160
|
+
symbols=f'{self.name}::',
|
|
161
|
+
start=start,
|
|
162
|
+
end=end,
|
|
119
163
|
# and timezone is GMT, because timestamp parameters in ACL are in GMT
|
|
120
164
|
timezone='GMT',
|
|
165
|
+
# ACCESS_INFO can return ACL violation error if we use database name as symbol
|
|
166
|
+
query_properties={'IGNORE_TICKS_IN_UNENTITLED_TIME_RANGE': 'TRUE'},
|
|
121
167
|
username=username,
|
|
122
168
|
context=self.context)
|
|
123
169
|
if not df.empty:
|
|
@@ -255,6 +301,20 @@ class DB:
|
|
|
255
301
|
end = start + otp.Day(1)
|
|
256
302
|
return self._fit_time_interval_in_acl(start, end, timezone)
|
|
257
303
|
|
|
304
|
+
@_method_cache
|
|
305
|
+
def _show_configured_time_ranges(self):
|
|
306
|
+
graph = otq.GraphQuery(otq.DbShowConfiguredTimeRanges(db_name=self.name).tick_type("ANY")
|
|
307
|
+
>> otq.Table(fields='long START_DATE, long END_DATE'))
|
|
308
|
+
result = otp.run(graph,
|
|
309
|
+
symbols=f'{self.name}::',
|
|
310
|
+
# start and end times don't matter for this query, use some constants
|
|
311
|
+
start=db_constants.DEFAULT_START_DATE,
|
|
312
|
+
end=db_constants.DEFAULT_END_DATE,
|
|
313
|
+
# GMT, because start/end timestamp in locator are in GMT
|
|
314
|
+
timezone='GMT',
|
|
315
|
+
context=self.context)
|
|
316
|
+
return result
|
|
317
|
+
|
|
258
318
|
def _set_intervals(self):
|
|
259
319
|
"""
|
|
260
320
|
Finds all date ranges from locators.
|
|
@@ -264,18 +324,7 @@ class DB:
|
|
|
264
324
|
"""
|
|
265
325
|
|
|
266
326
|
if self._locator_date_ranges is None:
|
|
267
|
-
|
|
268
|
-
>> otq.Table(fields='long START_DATE, long END_DATE'))
|
|
269
|
-
|
|
270
|
-
result = otp.run(graph,
|
|
271
|
-
symbols=f'{self.name}::',
|
|
272
|
-
# start and end times don't matter for this query, use some constants
|
|
273
|
-
start=db_constants.DEFAULT_START_DATE,
|
|
274
|
-
end=db_constants.DEFAULT_END_DATE,
|
|
275
|
-
# GMT, because start/end timestamp in locator are in GMT
|
|
276
|
-
timezone='GMT',
|
|
277
|
-
context=self.context)
|
|
278
|
-
|
|
327
|
+
result = self._show_configured_time_ranges()
|
|
279
328
|
date_ranges = []
|
|
280
329
|
|
|
281
330
|
tz_gmt = gettz('GMT')
|
|
@@ -316,10 +365,20 @@ class DB:
|
|
|
316
365
|
|
|
317
366
|
def _show_loaded_time_ranges(self, start, end, only_last=False, prefer_speed_over_accuracy=False):
|
|
318
367
|
kwargs = {}
|
|
368
|
+
# PY-1421: we aim to make this query as fast as possible
|
|
369
|
+
# There are two problems with this EP:
|
|
370
|
+
# 1. executing this query without using cache
|
|
371
|
+
# and/or without setting prefer_speed_over_accuracy parameter
|
|
372
|
+
# may be very slow for big time range
|
|
373
|
+
# 2. using cache sometimes returns not precise results
|
|
374
|
+
# So in case prefer_speed_over_accuracy parameter is available we are disabling cache.
|
|
319
375
|
if prefer_speed_over_accuracy:
|
|
320
|
-
kwargs['prefer_speed_over_accuracy'] =
|
|
376
|
+
kwargs['prefer_speed_over_accuracy'] = True
|
|
377
|
+
kwargs['use_cache'] = False
|
|
378
|
+
else:
|
|
379
|
+
kwargs['use_cache'] = True
|
|
321
380
|
|
|
322
|
-
eps = otq.DbShowLoadedTimeRanges(
|
|
381
|
+
eps = otq.DbShowLoadedTimeRanges(**kwargs).tick_type('ANY')
|
|
323
382
|
eps = eps >> otq.WhereClause(where='NUM_LOADED_PARTITIONS > 0')
|
|
324
383
|
if only_last:
|
|
325
384
|
eps = eps >> otq.LastTick()
|
|
@@ -528,32 +587,45 @@ class DB:
|
|
|
528
587
|
['QTE', 'TRD']
|
|
529
588
|
"""
|
|
530
589
|
date = self.last_date if date is None else date
|
|
590
|
+
|
|
531
591
|
if timezone is None:
|
|
532
592
|
timezone = configuration.config.tz
|
|
533
|
-
time_params: dict[str, Any] = {}
|
|
534
593
|
|
|
535
|
-
if date is
|
|
536
|
-
|
|
594
|
+
if date is None:
|
|
595
|
+
# in the usual case it would mean that there is no data in the database,
|
|
596
|
+
# but _show_loaded_time_ranges doesn't return dates for database views
|
|
597
|
+
# in this case let's just try to get the database schema with default time range
|
|
598
|
+
start = end = utils.adaptive
|
|
599
|
+
# also it seems that show_schema=True doesn't work for views either
|
|
600
|
+
show_schema = False
|
|
601
|
+
else:
|
|
602
|
+
start, end = self._fit_date_in_acl(date, timezone=timezone) # type: ignore[assignment]
|
|
603
|
+
show_schema = True
|
|
537
604
|
|
|
538
605
|
# PY-458: don't use cache, it can return different result in some cases
|
|
539
|
-
result =
|
|
540
|
-
show_schema=False,
|
|
541
|
-
include_memdb=True),
|
|
542
|
-
symbols=f'{self.name}::',
|
|
543
|
-
**time_params,
|
|
544
|
-
timezone=timezone,
|
|
545
|
-
context=self.context)
|
|
546
|
-
|
|
606
|
+
result = self._get_schema(use_cache=False, start=start, end=end, timezone=timezone, show_schema=show_schema)
|
|
547
607
|
if len(result) == 0:
|
|
548
608
|
return []
|
|
549
609
|
|
|
550
|
-
return result['TICK_TYPE_NAME'].tolist()
|
|
610
|
+
return result['TICK_TYPE_NAME'].unique().tolist()
|
|
551
611
|
|
|
552
612
|
def min_locator_date(self):
|
|
553
613
|
self._set_intervals()
|
|
554
614
|
min_date = min(obj[0] for obj in self._locator_date_ranges)
|
|
555
615
|
return _datetime2date(min_date)
|
|
556
616
|
|
|
617
|
+
@_method_cache
|
|
618
|
+
def _get_schema(self, start, end, timezone, use_cache, show_schema):
|
|
619
|
+
ep = otq.DbShowTickTypes(use_cache=use_cache,
|
|
620
|
+
show_schema=show_schema,
|
|
621
|
+
include_memdb=True)
|
|
622
|
+
return otp.run(ep,
|
|
623
|
+
symbols=f'{self.name}::',
|
|
624
|
+
start=start,
|
|
625
|
+
end=end,
|
|
626
|
+
timezone=timezone,
|
|
627
|
+
context=self.context)
|
|
628
|
+
|
|
557
629
|
def schema(self, date=None, tick_type=None, timezone=None, check_index_file=utils.adaptive) -> dict[str, type]:
|
|
558
630
|
"""
|
|
559
631
|
Gets the schema of the database.
|
|
@@ -615,25 +687,18 @@ class DB:
|
|
|
615
687
|
|
|
616
688
|
start, end = self._fit_date_in_acl(date, timezone=timezone)
|
|
617
689
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
symbols=f'{self.name}::',
|
|
625
|
-
start=start,
|
|
626
|
-
end=end,
|
|
627
|
-
timezone=timezone,
|
|
628
|
-
context=self.context)
|
|
629
|
-
|
|
630
|
-
result = get_schema(use_cache=True)
|
|
690
|
+
kwargs = dict(
|
|
691
|
+
start=start, end=end, timezone=timezone, show_schema=True,
|
|
692
|
+
)
|
|
693
|
+
# PY-458, BEXRTS-1220, PY-1421
|
|
694
|
+
# the results of the query may vary depending on using use_cache parameter, so we are trying both
|
|
695
|
+
result = self._get_schema(use_cache=False, **kwargs)
|
|
631
696
|
if result.empty:
|
|
632
|
-
|
|
633
|
-
result = get_schema(use_cache=False)
|
|
697
|
+
result = self._get_schema(use_cache=True, **kwargs)
|
|
634
698
|
|
|
635
|
-
fields: Iterable
|
|
699
|
+
fields: Iterable = []
|
|
636
700
|
if len(result):
|
|
701
|
+
result = result[result['TICK_TYPE_NAME'] == tick_type]
|
|
637
702
|
# filter schema by date
|
|
638
703
|
date_to_filter = None
|
|
639
704
|
if orig_date:
|
|
@@ -648,8 +713,6 @@ class DB:
|
|
|
648
713
|
fields = zip(result['FIELD_NAME'].tolist(),
|
|
649
714
|
result['FIELD_TYPE_NAME'].tolist(),
|
|
650
715
|
result['FIELD_SIZE'].tolist())
|
|
651
|
-
else:
|
|
652
|
-
fields = []
|
|
653
716
|
|
|
654
717
|
schema = {}
|
|
655
718
|
|
onetick/py/oqd/sources.py
CHANGED
|
@@ -14,12 +14,29 @@ COMMON_SOURCE_DOC_PARAMS = [_start_doc, _end_doc, _symbol_doc]
|
|
|
14
14
|
OQD_TICK_TYPE = 'OQD::*'
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def _parse_time(time_expr):
|
|
18
|
+
# get datetime from string in OneTick
|
|
19
|
+
return f'parse_time("%Y%m%d %H:%M:%S.%q", {time_expr}, "GMT")'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_start_time_start_of_day():
|
|
23
|
+
# getting the beggining of the start date day
|
|
24
|
+
# (e.g. from 2025-01-01 14:00:00 to 2025-01-01 00:00:00)
|
|
25
|
+
return _parse_time('time_format("%Y%m%d", _START_TIME, _TIMEZONE) + " 00:00:00.000"')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _get_end_time_end_of_day():
|
|
29
|
+
# by default we change the end time to the end of the day
|
|
30
|
+
# (e.g. from 2025-01-01 15:00:00 to 2025-01-01 24:00:00)
|
|
31
|
+
end_of_day = _parse_time('time_format("%Y%m%d", _END_TIME, _TIMEZONE) + " 24:00:00.000"')
|
|
32
|
+
# but if we have end time as the start of the next day (e.g. 2025-01-02 00:00:00) then we keep it
|
|
33
|
+
return f'case(time_format("%H%M%S%J", _END_TIME, _TIMEZONE), "000000000000000", _END_TIME, {end_of_day})'
|
|
34
|
+
|
|
35
|
+
|
|
17
36
|
def _modify_query_times(src):
|
|
18
37
|
src.sink(otq.ModifyQueryTimes(
|
|
19
|
-
start_time=(
|
|
20
|
-
|
|
21
|
-
end_time=('parse_time("%Y%m%d %H:%M:%S.%q", '
|
|
22
|
-
'time_format("%Y%m%d", _END_TIME, _TIMEZONE) + " 24:00:00.000", "GMT")'),
|
|
38
|
+
start_time=_get_start_time_start_of_day(),
|
|
39
|
+
end_time=_get_end_time_end_of_day(),
|
|
23
40
|
output_timestamp='min(max(TIMESTAMP,_START_TIME),_END_TIME)'
|
|
24
41
|
))
|
|
25
42
|
|
|
@@ -111,7 +128,7 @@ class CorporateActions(otp.Source):
|
|
|
111
128
|
actions for a symbol.
|
|
112
129
|
|
|
113
130
|
This source will return all corporate action fields available for a symbol
|
|
114
|
-
with EX-Dates between the query start time and end time. The
|
|
131
|
+
with EX-Dates between the query start time and end time (end time is not inclusive). The
|
|
115
132
|
timestamp of the series is equal to the EX-Date of the corporate
|
|
116
133
|
action with a time of 0:00:00 GMT.
|
|
117
134
|
|
|
@@ -130,9 +147,6 @@ class CorporateActions(otp.Source):
|
|
|
130
147
|
CASH:0.205@USD NORMAL
|
|
131
148
|
1 2021-05-07 9706 17098817 CASH_DIVIDEND 0.220 USD 20210428 20210507 20210513 20210510\
|
|
132
149
|
CASH:0.22@USD NORMAL
|
|
133
|
-
2 2021-08-06 9706 17331864 CASH_DIVIDEND 0.220 USD 20210727 20210806 20210812 20210809\
|
|
134
|
-
CASH:0.22@USD NORMAL
|
|
135
|
-
|
|
136
150
|
"""
|
|
137
151
|
|
|
138
152
|
@docstring(parameters=COMMON_SOURCE_DOC_PARAMS, add_self=True)
|
onetick/py/run.py
CHANGED
|
@@ -465,9 +465,6 @@ def run(query: Union[Callable, Dict, otp.Source, otp.MultiOutputSource, # NOSON
|
|
|
465
465
|
"by installed onetick.query_webapi library.")
|
|
466
466
|
|
|
467
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
468
|
|
|
472
469
|
require_dict = require_dict or _is_dict_required(symbols)
|
|
473
470
|
|
|
@@ -495,6 +492,10 @@ def run(query: Union[Callable, Dict, otp.Source, otp.MultiOutputSource, # NOSON
|
|
|
495
492
|
# we assume it's a dictionary of sources for the MultiOutputSource object
|
|
496
493
|
query = otp.MultiOutputSource(query)
|
|
497
494
|
|
|
495
|
+
if symbol_date:
|
|
496
|
+
# otq.run supports only strings and datetime.date
|
|
497
|
+
symbol_date = utils.symbol_date_to_str(symbol_date)
|
|
498
|
+
|
|
498
499
|
params_saved_to_otq = {}
|
|
499
500
|
if isinstance(query, (otp.Source, otp.MultiOutputSource)):
|
|
500
501
|
start = None if start is utils.adaptive else start
|
|
@@ -512,8 +513,9 @@ def run(query: Union[Callable, Dict, otp.Source, otp.MultiOutputSource, # NOSON
|
|
|
512
513
|
end_time_expression=end_time_expression,
|
|
513
514
|
require_dict=require_dict,
|
|
514
515
|
running_query_flag=running,
|
|
515
|
-
node_name=node_name, has_output=None
|
|
516
|
-
|
|
516
|
+
node_name=node_name, has_output=None,
|
|
517
|
+
symbol_date=symbol_date)
|
|
518
|
+
query, require_dict, node_name, symbol_date_to_run = param_upd
|
|
517
519
|
# symbols and start/end times should be already stored in the query and should not be passed again
|
|
518
520
|
symbols = None
|
|
519
521
|
start = None
|
|
@@ -521,6 +523,8 @@ def run(query: Union[Callable, Dict, otp.Source, otp.MultiOutputSource, # NOSON
|
|
|
521
523
|
start_time_expression = None
|
|
522
524
|
end_time_expression = None
|
|
523
525
|
time_as_nsec = True
|
|
526
|
+
# PY-1423: except for symbol date
|
|
527
|
+
symbol_date = symbol_date_to_run
|
|
524
528
|
|
|
525
529
|
elif isinstance(query, (otq.graph_components.EpBase, otq.Chainlet)):
|
|
526
530
|
query = otq.GraphQuery(query)
|
onetick/py/sources/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from .data_source import DataSource, Custom
|
|
|
6
6
|
from .cache import ReadCache
|
|
7
7
|
from .csv import CSV, LocalCSVTicks
|
|
8
8
|
from .data_file import DataFile
|
|
9
|
+
from .dataframe import ReadFromDataFrame
|
|
9
10
|
from .empty import Empty
|
|
10
11
|
from .custom import Orders, Quotes, Trades, NBBO
|
|
11
12
|
from .order_book import ObSnapshot, ObSnapshotFlat, ObSnapshotWide, ObSummary, ObSize, ObVwap, ObNumLevels
|