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/types.py ADDED
@@ -0,0 +1,2131 @@
1
+ import ctypes
2
+ import functools
3
+ import inspect
4
+ import warnings
5
+ import decimal as _decimal
6
+ from typing import Optional, Type, Union
7
+ from datetime import date as _date
8
+ from datetime import datetime as _datetime
9
+ from datetime import timedelta as _timedelta
10
+
11
+ import pandas as pd
12
+ import numpy as np
13
+ from pandas.tseries import offsets
14
+ from packaging.version import parse as parse_version
15
+
16
+ import onetick.py as otp
17
+ from onetick.py.otq import otq, pyomd
18
+ from onetick.py.compatibility import has_timezone_parameter
19
+ from onetick.py.core._internal._op_utils.every_operand import every_operand
20
+ from onetick.py.utils import get_tzfile_by_name, get_timezone_from_datetime
21
+ from onetick.py.docs.utils import is_windows
22
+
23
+ # --------------------------------------------------------------- #
24
+ # TYPES IMPLEMENTATION
25
+ # --------------------------------------------------------------- #
26
+
27
+
28
+ class OTPBaseTimeStamp(type):
29
+ pass
30
+
31
+
32
+ class _nsectime(OTPBaseTimeStamp):
33
+ def __str__(cls):
34
+ return "nsectime"
35
+
36
+
37
+ class nsectime(int, metaclass=_nsectime):
38
+ """
39
+ OneTick data type representing datetime with nanoseconds precision.
40
+ Can be used to specify otp.Source column type when converting columns or creating new ones.
41
+ Note that this constructor creates datetime value in GMT timezone
42
+ and doesn't take into account the timezone with which the query is executed.
43
+
44
+ Examples
45
+ --------
46
+ >>> t = otp.Tick(A=0)
47
+ >>> t['A'] = t['A'].apply(otp.nsectime)
48
+ >>> t['B'] = otp.nsectime(24 * 60 * 60 * 1000 * 1000 * 1000 + 2)
49
+ >>> t.schema
50
+ {'A': <class 'onetick.py.types.nsectime'>, 'B': <class 'onetick.py.types.nsectime'>}
51
+ >>> otp.run(t)
52
+ Time A B
53
+ 0 2003-12-01 1969-12-31 19:00:00 1970-01-01 19:00:00.000000002
54
+ """
55
+ def __str__(self):
56
+ return super().__repr__()
57
+
58
+ def __repr__(self):
59
+ return f'{self.__class__.__name__}({self})'
60
+
61
+
62
+ class _msectime(OTPBaseTimeStamp):
63
+ def __str__(cls):
64
+ return "msectime"
65
+
66
+
67
+ class msectime(int, metaclass=_msectime):
68
+ """
69
+ OneTick data type representing datetime with milliseconds precision.
70
+ Can be used to specify otp.Source column type when converting columns or creating new ones.
71
+ Note that this constructor creates datetime value in GMT timezone
72
+ and doesn't take into account the timezone with which the query is executed.
73
+
74
+ Examples
75
+ --------
76
+ >>> t = otp.Tick(A=1)
77
+ >>> t = t.table(A=otp.msectime)
78
+ >>> t['B'] = otp.msectime(2)
79
+ >>> t.schema
80
+ {'A': <class 'onetick.py.types.msectime'>, 'B': <class 'onetick.py.types.msectime'>}
81
+ >>> otp.run(t)
82
+ Time A B
83
+ 0 2003-12-01 1969-12-31 19:00:00.001 1969-12-31 19:00:00.002
84
+ """
85
+ def __str__(self):
86
+ return super().__repr__()
87
+
88
+ def __repr__(self):
89
+ return f'{self.__class__.__name__}({self})'
90
+
91
+
92
+ class OTPBaseTimeOffset:
93
+ datepart = "'invalid'" # that is just base class for other dateparts
94
+ n = 0
95
+ delta = pd.Timedelta(seconds=0)
96
+
97
+ def get_offset(self):
98
+ return self.n, self.datepart[1:-1]
99
+
100
+
101
+ class ExpressionDefinedTimeOffset(OTPBaseTimeOffset):
102
+ def __init__(self, datepart, n):
103
+ self.datepart = datepart
104
+ self.n = n
105
+
106
+ from onetick.py.core.column_operations.base import _Operation
107
+
108
+ def proxy_wrap(attr):
109
+ def f(self, *args, **kwargs):
110
+ return getattr(self.n, attr)(*args, **kwargs)
111
+ return f
112
+
113
+ for attr, value in inspect.getmembers(_Operation, callable):
114
+ if attr in {'__class__', '__init__', '__new__', '__init_subclass__', '__dir__',
115
+ '__getattribute__', '__getattr__', '__delattr__', '__setattr__',
116
+ '__subclasshook__', '__sizeof__', '__str__', '__repr__'}:
117
+ continue
118
+ setattr(ExpressionDefinedTimeOffset, attr, proxy_wrap(attr))
119
+
120
+
121
+ # ---------------------------- #
122
+ # Implement datepart units
123
+
124
+ def _construct_dpf(dp_class, str_repr=None, **dp_class_params):
125
+ """ construct a datepart factory """
126
+
127
+ if str_repr is None:
128
+ str_repr = dp_class.__name__.lower()
129
+
130
+ class _DatePartCls(dp_class, OTPBaseTimeOffset):
131
+ datepart = f"'{str_repr}'"
132
+
133
+ def _factory(n):
134
+ from onetick.py.core.column_operations._methods.methods import is_arithmetical
135
+ from onetick.py.core.column import _Column
136
+
137
+ if isinstance(n, int):
138
+ if dp_class_params:
139
+ return _DatePartCls(**dp_class_params) * n
140
+ return _DatePartCls(n)
141
+ if is_arithmetical(n):
142
+ n = _process_datediff(n)
143
+ return ExpressionDefinedTimeOffset(_DatePartCls.datepart, n)
144
+ if isinstance(n, _Column):
145
+ return ExpressionDefinedTimeOffset(_DatePartCls.datepart, n)
146
+ raise ValueError("Unknown type was passed as arg, integer constant or column or expression is expected here")
147
+
148
+ def _process_datediff(n):
149
+
150
+ n_time_operand = _get_n_time_operand(n)
151
+ if n_time_operand:
152
+ # check if otp.Hour(date1 - date2) is called, return a number of hours between two days in such ways
153
+ from onetick.py.core.column_operations._methods.methods import sub, _wrap_object
154
+ from onetick.py.core.column_operations.base import _Operation
155
+ from onetick.py.core.column import _LagOperator
156
+
157
+ available_types = (_Operation, _LagOperator)
158
+ if (getattr(n, "_op_func", sub) and len(n._op_params) == 2
159
+ and isinstance(n._op_params[0], available_types) and isinstance(n._op_params[1], available_types)):
160
+ def _datediff(*args):
161
+ args = ', '.join(map(_wrap_object, args))
162
+ return f'DATEDIFF({_DatePartCls.datepart}, {args}, _TIMEZONE)', int
163
+ return _Operation(_datediff, [n._op_params[1], n._op_params[0]])
164
+ else:
165
+ raise ValueError(
166
+ "Date arithmetic operations (except date2-date1, which calculate an amount of "
167
+ "periods between two dates) are not accepted in TimeOffset constructor"
168
+ )
169
+ return n
170
+
171
+ def _get_n_time_operand(n):
172
+ from onetick.py.core.column_operations._methods.op_types import are_time
173
+
174
+ result = 0
175
+ for op in every_operand(n):
176
+ if are_time(get_object_type(op)):
177
+ result += 1
178
+ return result
179
+
180
+ return _factory
181
+
182
+
183
+ def _construct_float_dpf(dp_class, dp_float_class, power, str_repr=None, float_str_repr=None, **dp_class_params):
184
+ def _factory(n):
185
+ if isinstance(n, float):
186
+ return _construct_dpf(dp_float_class, float_str_repr, **dp_class_params)(int(n * (10 ** power)))
187
+ else:
188
+ return _construct_dpf(dp_class, str_repr, **dp_class_params)(n)
189
+
190
+ return _factory
191
+
192
+
193
+ _add_examples_to_docs = functools.partial(
194
+ """
195
+ Object representing {0}'s datetime offset.
196
+
197
+ Can be added to or subtracted from:
198
+
199
+ * :py:class:`otp.datetime <onetick.py.datetime>` objects
200
+ * :py:class:`Source <onetick.py.Source>` columns of datetime type
201
+
202
+ Parameters
203
+ ----------
204
+ n: int, :class:`~onetick.py.Column`, :class:`~onetick.py.Operation`{additional_types}
205
+ Offset integer value or column of :class:`~onetick.py.Source`.
206
+ The only :class:`~onetick.py.Operation` supported is
207
+ subtracting one datetime column from another. See example below.{additional_notes}
208
+
209
+ Examples
210
+ --------
211
+ {1}
212
+ """.format, additional_types="", additional_notes="",
213
+ )
214
+
215
+ _float_dpf_types = ", float"
216
+ _float_dpf_notes = """
217
+ Offset could be ``float`` to pass a fractional time unit value."""
218
+
219
+
220
+ Year = _construct_dpf(offsets.DateOffset, "year", years=1)
221
+ Year.__doc__ = _add_examples_to_docs('year', """
222
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
223
+
224
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Year(1)
225
+ 2013-12-12 12:00:00
226
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Year(1)
227
+ 2011-12-12 12:00:00
228
+
229
+ Use offset in columns:
230
+
231
+ >>> t = otp.Tick(A=1)
232
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
233
+ >>> t['T'] += otp.Year(t['A'])
234
+ >>> otp.run(t)
235
+ Time T A
236
+ 0 2003-12-01 2013-12-12 12:00:00 1
237
+
238
+ Use it to calculate difference between two dates:
239
+
240
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1))
241
+ >>> t['DIFF'] = otp.Year(t['B'] - t['A'])
242
+ >>> otp.run(t)
243
+ Time A B DIFF
244
+ 0 2003-12-01 2022-01-01 2023-01-01 1
245
+ """)
246
+
247
+ Quarter = _construct_dpf(offsets.DateOffset, "quarter", months=3)
248
+ Quarter.__doc__ = _add_examples_to_docs('quarter', """
249
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
250
+
251
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Quarter(1)
252
+ 2013-03-12 12:00:00
253
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Quarter(1)
254
+ 2012-09-12 12:00:00
255
+
256
+ Use offset in columns:
257
+
258
+ >>> t = otp.Tick(A=1)
259
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12, tz='GMT')
260
+ >>> t['T'] += otp.Quarter(t['A'])
261
+ >>> otp.run(t, start=otp.datetime(2003, 12, 2), end=otp.datetime(2003, 12, 3), timezone='GMT')
262
+ Time T A
263
+ 0 2003-12-02 2013-03-12 12:00:00 1
264
+
265
+ Use it to calculate difference between two dates:
266
+
267
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1))
268
+ >>> t['DIFF'] = otp.Quarter(t['B'] - t['A'])
269
+ >>> otp.run(t)
270
+ Time A B DIFF
271
+ 0 2003-12-01 2022-01-01 2023-01-01 4
272
+ """)
273
+
274
+ Month = _construct_dpf(offsets.DateOffset, "month", months=1)
275
+ Month.__doc__ = _add_examples_to_docs('month', """
276
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
277
+
278
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Month(1)
279
+ 2013-01-12 12:00:00
280
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Month(1)
281
+ 2012-11-12 12:00:00
282
+
283
+ Use offset in columns:
284
+
285
+ >>> t = otp.Tick(A=1)
286
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
287
+ >>> t['T'] += otp.Month(t['A'])
288
+ >>> otp.run(t)
289
+ Time T A
290
+ 0 2003-12-01 2013-01-12 12:00:00 1
291
+
292
+ Use it to calculate difference between two dates:
293
+
294
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1))
295
+ >>> t['DIFF'] = otp.Month(t['B'] - t['A'])
296
+ >>> otp.run(t)
297
+ Time A B DIFF
298
+ 0 2003-12-01 2022-01-01 2023-01-01 12
299
+ """)
300
+
301
+ Week = _construct_dpf(offsets.Week)
302
+ Week.__doc__ = _add_examples_to_docs('week', """
303
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
304
+
305
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Week(1)
306
+ 2012-12-19 12:00:00
307
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Week(1)
308
+ 2012-12-05 12:00:00
309
+
310
+ Use offset in columns:
311
+
312
+ >>> t = otp.Tick(A=1)
313
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
314
+ >>> t['T'] += otp.Week(t['A'])
315
+ >>> otp.run(t)
316
+ Time T A
317
+ 0 2003-12-01 2012-12-19 12:00:00 1
318
+
319
+ Use it to calculate difference between two dates:
320
+
321
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1))
322
+ >>> t['DIFF'] = otp.Week(t['B'] - t['A'])
323
+ >>> otp.run(t)
324
+ Time A B DIFF
325
+ 0 2003-12-01 2022-01-01 2023-01-01 53
326
+ """)
327
+
328
+ Day = _construct_dpf(offsets.Day)
329
+ Day.__doc__ = _add_examples_to_docs('day', """
330
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
331
+
332
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Day(1)
333
+ 2012-12-13 12:00:00
334
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Day(1)
335
+ 2012-12-11 12:00:00
336
+
337
+ Use offset in columns:
338
+
339
+ >>> t = otp.Tick(A=1)
340
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
341
+ >>> t['T'] += otp.Day(t['A'])
342
+ >>> otp.run(t)
343
+ Time T A
344
+ 0 2003-12-01 2012-12-13 12:00:00 1
345
+
346
+ Use it to calculate difference between two dates:
347
+
348
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2023, 1, 1))
349
+ >>> t['DIFF'] = otp.Day(t['B'] - t['A'])
350
+ >>> otp.run(t)
351
+ Time A B DIFF
352
+ 0 2003-12-01 2022-01-01 2023-01-01 365
353
+ """)
354
+
355
+ Hour = _construct_dpf(offsets.Hour)
356
+ Hour.__doc__ = _add_examples_to_docs('hour', """
357
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
358
+
359
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Hour(1)
360
+ 2012-12-12 13:00:00
361
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Hour(1)
362
+ 2012-12-12 11:00:00
363
+
364
+ Use offset in columns:
365
+
366
+ >>> t = otp.Tick(A=1)
367
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
368
+ >>> t['T'] += otp.Hour(t['A'])
369
+ >>> otp.run(t)
370
+ Time T A
371
+ 0 2003-12-01 2012-12-12 13:00:00 1
372
+
373
+ Use it to calculate difference between two dates:
374
+
375
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 2))
376
+ >>> t['DIFF'] = otp.Hour(t['B'] - t['A'])
377
+ >>> otp.run(t)
378
+ Time A B DIFF
379
+ 0 2003-12-01 2022-01-01 2022-01-02 24
380
+ """)
381
+
382
+ Minute = _construct_dpf(offsets.Minute)
383
+ Minute.__doc__ = _add_examples_to_docs('minute', """
384
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
385
+
386
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Minute(1)
387
+ 2012-12-12 12:01:00
388
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Minute(1)
389
+ 2012-12-12 11:59:00
390
+
391
+ Use offset in columns:
392
+
393
+ >>> t = otp.Tick(A=1)
394
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
395
+ >>> t['T'] += otp.Minute(t['A'])
396
+ >>> otp.run(t)
397
+ Time T A
398
+ 0 2003-12-01 2012-12-12 12:01:00 1
399
+
400
+ Use it to calculate difference between two dates:
401
+
402
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 1, 1))
403
+ >>> t['DIFF'] = otp.Minute(t['B'] - t['A'])
404
+ >>> otp.run(t)
405
+ Time A B DIFF
406
+ 0 2003-12-01 2022-01-01 2022-01-01 01:00:00 60
407
+ """)
408
+
409
+ Second = _construct_float_dpf(offsets.Second, offsets.Nano, 9, None, "nanosecond")
410
+ Second.__doc__ = _add_examples_to_docs('second', """
411
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
412
+
413
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Second(1)
414
+ 2012-12-12 12:00:01
415
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Second(1)
416
+ 2012-12-12 11:59:59
417
+
418
+ Using ``float`` value to pass nanoseconds:
419
+
420
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Second(1.000000123)
421
+ 2012-12-12 12:00:01.000000123
422
+
423
+ Use offset in columns:
424
+
425
+ >>> t = otp.Tick(A=1)
426
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
427
+ >>> t['T'] += otp.Second(t['A'])
428
+ >>> otp.run(t)
429
+ Time T A
430
+ 0 2003-12-01 2012-12-12 12:00:01 1
431
+
432
+ Use it to calculate difference between two dates:
433
+
434
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 1, 0, 1))
435
+ >>> t['DIFF'] = otp.Second(t['B'] - t['A'])
436
+ >>> otp.run(t)
437
+ Time A B DIFF
438
+ 0 2003-12-01 2022-01-01 2022-01-01 00:01:00 60
439
+ """, additional_types=_float_dpf_types, additional_notes=_float_dpf_notes)
440
+
441
+ Milli = _construct_float_dpf(offsets.Milli, offsets.Nano, 6, "millisecond", "nanosecond")
442
+ Milli.__doc__ = _add_examples_to_docs('millisecond', """
443
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
444
+
445
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Milli(1)
446
+ 2012-12-12 12:00:00.001000
447
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Milli(1)
448
+ 2012-12-12 11:59:59.999000
449
+
450
+ Using ``float`` value to pass nanoseconds:
451
+
452
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Milli(1.000123)
453
+ 2012-12-12 12:00:00.001000123
454
+
455
+ Use offset in columns:
456
+
457
+ >>> t = otp.Tick(A=1)
458
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
459
+ >>> t['T'] += otp.Milli(t['A'])
460
+ >>> otp.run(t)
461
+ Time T A
462
+ 0 2003-12-01 2012-12-12 12:00:00.001 1
463
+
464
+ Use it to calculate difference between two dates:
465
+
466
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 1, 0, 0, 1))
467
+ >>> t['DIFF'] = otp.Milli(t['B'] - t['A'])
468
+ >>> otp.run(t)
469
+ Time A B DIFF
470
+ 0 2003-12-01 2022-01-01 2022-01-01 00:00:01 1000
471
+ """, additional_types=_float_dpf_types, additional_notes=_float_dpf_notes)
472
+
473
+ # microseconds are not supported yet
474
+
475
+ Nano = _construct_dpf(offsets.Nano, "nanosecond")
476
+ Nano.__doc__ = _add_examples_to_docs('nanosecond', """
477
+ Add to or subtract from :py:class:`otp.datetime <onetick.py.datetime>` object:
478
+
479
+ >>> otp.datetime(2012, 12, 12, 12) + otp.Nano(1)
480
+ 2012-12-12 12:00:00.000000001
481
+ >>> otp.datetime(2012, 12, 12, 12) - otp.Nano(1)
482
+ 2012-12-12 11:59:59.999999999
483
+
484
+ Use offset in columns:
485
+
486
+ >>> t = otp.Tick(A=1)
487
+ >>> t['T'] = otp.datetime(2012, 12, 12, 12)
488
+ >>> t['T'] += otp.Nano(t['A'])
489
+ >>> otp.run(t)
490
+ Time T A
491
+ 0 2003-12-01 2012-12-12 12:00:00.000000001 1
492
+
493
+ Use it to calculate difference between two dates:
494
+
495
+ >>> t = otp.Tick(A=otp.dt(2022, 1, 1), B=otp.dt(2022, 1, 1, 0, 0, 1))
496
+ >>> t['DIFF'] = otp.Nano(t['B'] - t['A'])
497
+ >>> otp.run(t)
498
+ Time A B DIFF
499
+ 0 2003-12-01 2022-01-01 2022-01-01 00:00:01 1000000000
500
+ """)
501
+
502
+ # ---------------------------- #
503
+
504
+
505
+ class _inner_string(type):
506
+ def __str__(cls):
507
+ if cls.length is Ellipsis:
508
+ return "varstring"
509
+ if cls.length:
510
+ return f"string[{cls.length}]"
511
+ else:
512
+ return "string"
513
+
514
+ def __repr__(cls):
515
+ return str(cls)
516
+
517
+ # We have ot use functools.cache, because 'class' in python is an object,
518
+ # and _inner_str for the same item is different for every call,
519
+ # but we want to make str[1024] be equal to another str[1024]
520
+ @functools.lru_cache(maxsize=None) # noqa: W1518
521
+ def __getitem__(cls, item):
522
+
523
+ if (not isinstance(item, int) or item < 1) and item is not Ellipsis:
524
+ raise TypeError("It is not allowed to have non numeric index")
525
+
526
+ class _inner_str(string): # type: ignore[misc]
527
+ length = item
528
+
529
+ def __len__(self):
530
+ return self.__class__.length
531
+
532
+ return _inner_str
533
+
534
+
535
+ class string(str, metaclass=_inner_string): # type: ignore[misc]
536
+ """
537
+ OneTick data type representing string with length and varstring.
538
+ To set string length use ``__getitem__``.
539
+ If the length is not set then the :py:attr:`~DEFAULT_LENGTH` length is used by default.
540
+ In this case using ``otp.string`` is the same as using ``str``.
541
+ If the length is set to Ellipse it represents varstring. Varstring is used for returning variably sized strings.
542
+
543
+ Note
544
+ ----
545
+ If you try to set value with length x to string[y] and x > y, value will be truncated to y length.
546
+
547
+ Attributes
548
+ ----------
549
+ DEFAULT_LENGTH: int
550
+ default length of the string when the length is not specified
551
+
552
+ Examples
553
+ --------
554
+
555
+ Adding new fields with :class:`otp.string <string>` type:
556
+
557
+ >>> t = otp.Tick(A=otp.string[32]('HELLO'))
558
+ >>> t['B'] = otp.string[128]('WORLD')
559
+ >>> t.schema
560
+ {'A': string[32], 'B': string[128]}
561
+ >>> otp.run(t)
562
+ Time A B
563
+ 0 2003-12-01 HELLO WORLD
564
+
565
+ Note that class :class:`otp.string <string>` is a child of python's ``str`` class,
566
+ so every object passed to constructor is converted to string.
567
+ So it may not work as expected in some cases, e.g. when passing :class:`~onetick.py.Operation` objects
568
+ (because their string representation is the name of the column or OneTick expression).
569
+ In this case it's better to use direct type conversion to get :class:`otp.string <string>` object:
570
+
571
+ >>> t = otp.Tick(X=otp.string[256](t['_SYMBOL_NAME']))
572
+ >>> t['Y'] = t['_SYMBOL_NAME'].astype(otp.string[256])
573
+ >>> t.schema
574
+ {'X': string[256], 'Y': string[256]}
575
+ >>> otp.run(t, symbols='AAAA')
576
+ Time X Y
577
+ 0 2003-12-01 _SYMBOL_NAME AAAA
578
+
579
+ Setting the type of the existing field:
580
+
581
+ >>> # OTdirective: skip-snippet:;
582
+ >>> t = otp.Tick(A='a')
583
+ >>> t = t.table(A=otp.string[10])
584
+ >>> t.schema
585
+ {'A': string[10]}
586
+
587
+ Example of truncation column value to set string length.
588
+
589
+ >>> # OTdirective: skip-snippet:;
590
+ >>> t['A'] *= 100
591
+ >>> t['B'] = t['A'].str.len()
592
+ >>> otp.run(t)
593
+ Time A B
594
+ 0 2003-12-01 aaaaaaaaaa 10
595
+
596
+ Example of string with default length.
597
+
598
+ >>> t = otp.Tick(A='a')
599
+ >>> t['A'] *= 100
600
+ >>> t['B'] = t['A'].str.len()
601
+ >>> otp.run(t)
602
+ Time A B
603
+ 0 2003-12-01 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 64
604
+
605
+ Setting Ellipsis as length represents varstring.
606
+
607
+ >>> t = otp.Tick(A='a')
608
+ >>> t = t.table(A=otp.string[...])
609
+ >>> t.schema
610
+ {'A': varstring}
611
+
612
+ Varstring length is multiplied.
613
+
614
+ >>> t['A'] *= 65
615
+ >>> t['B'] = t['A'].str.len()
616
+ >>> otp.run(t)
617
+ Time A B
618
+ 0 2003-12-01 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 65
619
+
620
+ `otp.varstring` is a shortcut:
621
+
622
+ >>> t = otp.Tick(A='a')
623
+ >>> t = t.table(A=otp.varstring)
624
+ >>> t.schema
625
+ {'A': varstring}
626
+ """
627
+
628
+ DEFAULT_LENGTH = 64
629
+ length = None
630
+
631
+ def __repr__(self):
632
+ return f'{self.__class__}({super().__repr__()})'
633
+
634
+
635
+ varstring = string[...] # type: ignore[type-arg,misc]
636
+
637
+
638
+ class _nan_base(type):
639
+ def __str__(cls):
640
+ return "double"
641
+
642
+
643
+ class _nan(float, metaclass=_nan_base):
644
+ """
645
+ Object that represents NaN (not a number) float value.
646
+ Can be used anywhere where float value is expected.
647
+
648
+ Examples
649
+ --------
650
+ >>> t = otp.Ticks({'A': [1.1, 2.2, otp.nan]})
651
+ >>> t['B'] = otp.nan
652
+ >>> t['C'] = t['A'] / 0
653
+ >>> t['D'] = t['A'] + otp.nan
654
+ >>> otp.run(t)
655
+ Time A B C D
656
+ 0 2003-12-01 00:00:00.000 1.1 NaN inf NaN
657
+ 1 2003-12-01 00:00:00.001 2.2 NaN inf NaN
658
+ 2 2003-12-01 00:00:00.002 NaN NaN NaN NaN
659
+ """
660
+
661
+ __name__ = 'nan'
662
+
663
+ def __str__(self):
664
+ return "NAN()"
665
+
666
+ def __repr__(self):
667
+ return 'nan'
668
+
669
+
670
+ nan = _nan()
671
+
672
+
673
+ class _inf(float, metaclass=_nan_base):
674
+ """
675
+ Object that represents infinity value.
676
+ Can be used anywhere where float value is expected.
677
+
678
+ Examples
679
+ --------
680
+ >>> t = otp.Ticks({'A': [1.1, 2.2, otp.inf]})
681
+ >>> t['B'] = otp.inf
682
+ >>> t['C'] = t['A'] / 0
683
+ >>> t['D'] = t['A'] - otp.inf
684
+ >>> otp.run(t)
685
+ Time A B C D
686
+ 0 2003-12-01 00:00:00.000 1.1 inf inf -inf
687
+ 1 2003-12-01 00:00:00.001 2.2 inf inf -inf
688
+ 2 2003-12-01 00:00:00.002 inf inf inf NaN
689
+ """
690
+
691
+ __name__ = 'inf'
692
+
693
+ def __init__(self):
694
+ self._sign = "" # empty string or '-' for negative infinity
695
+
696
+ def __str__(self):
697
+ return f"{self._sign}INFINITY()"
698
+
699
+ def __repr__(self):
700
+ return f'{self._sign}inf'
701
+
702
+ def __neg__(self):
703
+ result = _inf()
704
+ result._sign = "" if self._sign else "-"
705
+ return result
706
+
707
+
708
+ inf = _inf()
709
+
710
+
711
+ class decimal:
712
+ """
713
+ Object that represents decimal OneTick value.
714
+ Decimal is 128 bit base 10 floating point number.
715
+
716
+ Parameters
717
+ ----------
718
+ value: int, float, str
719
+ The value to initialize decimal from.
720
+ Note that float values may be converted with precision lost.
721
+
722
+ Examples
723
+ --------
724
+
725
+ :py:class:`~onetick.py.types.decimal` objects can be used in tick generators
726
+ and column operations as any other onetick-py type:
727
+
728
+ >>> t = otp.Ticks({'A': [otp.decimal(1), otp.decimal(2)]})
729
+ >>> t['B'] = otp.decimal(1.23456789)
730
+ >>> t['C'] = t['A'] / 0
731
+ >>> t['D'] = t['A'] + otp.nan
732
+ >>> otp.run(t)
733
+ Time A B C D
734
+ 0 2003-12-01 00:00:00.000 1.0 1.234568 inf NaN
735
+ 1 2003-12-01 00:00:00.001 2.0 1.234568 inf NaN
736
+
737
+ Additionally, any arithmetic operation with :py:class:`~onetick.py.types.decimal` object will return
738
+ an :py:class:`~onetick.py.Operation` object:
739
+
740
+ >>> t = otp.Tick(A=1)
741
+ >>> t['X'] = otp.decimal(1) / 0
742
+ >>> otp.run(t)
743
+ Time A X
744
+ 0 2003-12-01 1 inf
745
+
746
+ Note that converting from float (first row) may result in losing precision.
747
+ :py:class:`~onetick.py.types.decimal` objects are created from strings or integers, so they don't lose precision:
748
+
749
+ >>> t0 = otp.Tick(A=0.1)
750
+ >>> t1 = otp.Tick(A=otp.decimal(0.01))
751
+ >>> t2 = otp.Tick(A=otp.decimal('0.001'))
752
+ >>> t3 = otp.Tick(A=otp.decimal(1) / otp.decimal(10_000))
753
+ >>> t = otp.merge([t0, t1, t2, t3], enforce_order=True)
754
+ >>> t['STR_A'] = t['A'].decimal.str(34)
755
+ >>> otp.run(t)
756
+ Time A STR_A
757
+ 0 2003-12-01 0.1000 0.1000000000000000055511151231257827
758
+ 1 2003-12-01 0.0100 0.0100000000000000000000000000000000
759
+ 2 2003-12-01 0.0010 0.0010000000000000000000000000000000
760
+ 3 2003-12-01 0.0001 0.0001000000000000000000000000000000
761
+
762
+ Note that :py:class:`otp.Ticks <onetick.py.Ticks>` will convert everything from string under the hood,
763
+ so even the float values will not lose precision:
764
+
765
+ >>> t = otp.Ticks({'A': [0.1, otp.decimal(0.01), otp.decimal('0.001'), otp.decimal(1e-4)]})
766
+ >>> t['STR_A'] = t['A'].decimal.str(34)
767
+ >>> otp.run(t)
768
+ Time A STR_A
769
+ 0 2003-12-01 00:00:00.000 0.1000 0.1000000000000000000000000000000000
770
+ 1 2003-12-01 00:00:00.001 0.0100 0.0100000000000000000000000000000000
771
+ 2 2003-12-01 00:00:00.002 0.0010 0.0010000000000000000000000000000000
772
+ 3 2003-12-01 00:00:00.003 0.0001 0.0001000000000000000000000000000000
773
+ """
774
+ def __new__(cls, *args, **kwargs):
775
+ # this method dynamically adds properties and methods
776
+ # from otp.Operation class to this one
777
+
778
+ # otp.decimal class doesn't fit well in onetick-py type system,
779
+ # so this class is a mix of both type and Operation logic
780
+
781
+ # Basically it works like this:
782
+ # otp.decimal is a OneTick type
783
+ # otp.decimal(1) is a decimal type object
784
+ # Doing anything with this object returns an otp.Operation:
785
+ # otp.decimal(1) / 2
786
+
787
+ def proxy_wrap(attr, value):
788
+ if callable(value):
789
+ @functools.wraps(value)
790
+ def f(self, *args, **kwargs):
791
+ op = self.to_operation()
792
+ return getattr(op, attr)(*args, **kwargs)
793
+ return f
794
+ else:
795
+ @functools.wraps(value)
796
+ def f(self):
797
+ op = self.to_operation()
798
+ return getattr(op, attr)
799
+ return property(f)
800
+
801
+ for attr, value in inspect.getmembers(otp.Operation):
802
+ # comparison methods are defined by default for some reason,
803
+ # but we want to get them from otp.Operation
804
+ if not hasattr(cls, attr) or attr in ('__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__'):
805
+ setattr(cls, attr, proxy_wrap(attr, value))
806
+
807
+ return super().__new__(cls)
808
+
809
+ def __init__(self, value):
810
+ from onetick.py.core.column_operations.base import OnetickParameter
811
+ supported_types = (str, int, float, OnetickParameter)
812
+ if not isinstance(value, supported_types):
813
+ raise TypeError(f"Parameter 'value' must be one of these types: {supported_types}, got {type(value)}")
814
+ self.__value = value
815
+
816
+ @classmethod
817
+ def _to_onetick_type_string(cls):
818
+ # called by ott.type2str
819
+ return 'decimal'
820
+
821
+ def _to_onetick_string(self):
822
+ # called by ott.value2str
823
+ from onetick.py.core.column_operations.base import OnetickParameter
824
+ if isinstance(self.__value, OnetickParameter):
825
+ value = self.__value
826
+ else:
827
+ value = str(self.__value)
828
+ return f'STRING_TO_DECIMAL({value2str(value)})'
829
+
830
+ def to_operation(self):
831
+ return otp.Operation(op_str=self._to_onetick_string(), dtype=decimal)
832
+
833
+ def __str__(self):
834
+ # called by otp.CSV, we don't need to convert the value with OneTick functions in this case
835
+ return str(self.__value)
836
+
837
+ def __repr__(self):
838
+ return f"{self.__class__.__name__}({value2str(self.__value)})"
839
+
840
+ def __format__(self, __format_spec: str) -> str:
841
+ return _decimal.Decimal(self.__value).__format__(__format_spec)
842
+
843
+ # --------------------------------------------------------------- #
844
+ # AUXILIARY FUNCTIONS
845
+ # --------------------------------------------------------------- #
846
+
847
+
848
+ def is_type_basic(dtype):
849
+ return dtype in (
850
+ int,
851
+ float,
852
+ str,
853
+ byte,
854
+ short,
855
+ uint,
856
+ ulong,
857
+ _int,
858
+ long,
859
+ nsectime,
860
+ msectime,
861
+ decimal,
862
+ ) or issubclass(dtype, string)
863
+
864
+
865
+ # TODO: PY-632: unify these functions with others
866
+ def get_source_base_type(value):
867
+ if inspect.isclass(value):
868
+ value_type = value
869
+ if not is_type_basic(value_type):
870
+ warnings.warn('Setting schema with complex types is deprecated,'
871
+ ' use basic type instead', FutureWarning, stacklevel=2)
872
+ else:
873
+ warnings.warn('Setting schema with instance of the class is deprecated,'
874
+ ' use type instead', FutureWarning, stacklevel=2)
875
+ value_type = type(value)
876
+ # convert string to custom string if necessary
877
+ if value_type is str and len(value) > string.DEFAULT_LENGTH:
878
+ value_type = string[len(value)]
879
+
880
+ if issubclass(value_type, bool):
881
+ value_type = float
882
+
883
+ if is_time_type(value_type):
884
+ value_type = nsectime
885
+
886
+ # check valid value type
887
+ if get_base_type(value_type) not in [int, float, str, bool, decimal]:
888
+ raise TypeError(f'Type "{repr(value_type)}" is not supported.')
889
+
890
+ if not is_type_basic(value_type):
891
+ raise TypeError(f"Type {repr(value_type)} can't be set in schema.")
892
+ return value_type
893
+
894
+
895
+ def is_type_supported(dtype):
896
+ return get_base_type(dtype) in [int, float, str, bool, decimal] or issubclass(dtype, (datetime, date))
897
+
898
+
899
+ def get_base_type(obj):
900
+ if issubclass(obj, str):
901
+ return str
902
+ elif issubclass(obj, bool):
903
+ return bool
904
+ elif issubclass(obj, int):
905
+ return int
906
+ elif issubclass(obj, float):
907
+ return float
908
+ elif issubclass(obj, decimal):
909
+ return decimal
910
+
911
+ return type(None)
912
+
913
+
914
+ def get_object_type(obj):
915
+ if isinstance(obj, (_nan, _inf)):
916
+ return float
917
+ if isinstance(obj, Type):
918
+ return obj
919
+ if hasattr(obj, 'dtype'):
920
+ dtype = obj.dtype
921
+ if isinstance(dtype, np.dtype):
922
+ return dtype.type
923
+ return dtype
924
+ if is_time_type(obj):
925
+ return nsectime
926
+ # pylint: disable-next=unidiomatic-typecheck
927
+ if type(obj) is int and obj > long.MAX:
928
+ # by default we use python's int (onetick's long) for numbers
929
+ # in case the number is too big, let's use onetick's ulong
930
+ return ulong
931
+ return type(obj)
932
+
933
+
934
+ def get_type_by_objects(objs):
935
+ """
936
+ Helper that calculates the widest type of the list passed objects.
937
+ Used to determine type by returned values.
938
+ """
939
+
940
+ # collect types
941
+ types = set()
942
+ for v in objs:
943
+ t = get_object_type(v)
944
+ if issubclass(t, str):
945
+ t = str
946
+ types.add(t)
947
+
948
+ # does not allow to mix string and numeric types
949
+ dtype = None
950
+ if str in types and (float in types or int in types or bool in types or nsectime in types or msectime in types):
951
+ raise TypeError("It is not allowed to return values of string type and numeric type in one function.")
952
+
953
+ # if there is only one value there, then
954
+ # use it as is
955
+ if len(types) == 1:
956
+ dtype = next(iter(types))
957
+ if dtype is bool:
958
+ return dtype
959
+
960
+ # process numeric types: the most generic is float
961
+ if int in types:
962
+ dtype = int
963
+ if bool in types:
964
+ dtype = float
965
+ # None is equal to otp.nan
966
+ if float in types or type(None) in types:
967
+ dtype = float
968
+
969
+ # process string types, taking into account OneTick long strings
970
+ if str in types:
971
+ max_len = string.DEFAULT_LENGTH
972
+ for v in objs:
973
+ t = get_object_type(v)
974
+ if issubclass(t, string):
975
+ if t.length is Ellipsis or max_len is Ellipsis:
976
+ max_len = Ellipsis
977
+ else:
978
+ max_len = max(t.length, max_len)
979
+ elif isinstance(v, str):
980
+ max_len = max(len(v), max_len)
981
+
982
+ if max_len == string.DEFAULT_LENGTH:
983
+ dtype = str
984
+ else:
985
+ dtype = string[max_len] # pylint: disable=E1136
986
+
987
+ # process msectime and nsectime
988
+ if dtype is float and (msectime in types or nsectime in types):
989
+ raise TypeError("It is not allowed to return value of time type and float type in one function.")
990
+
991
+ if msectime in types:
992
+ dtype = msectime
993
+ if nsectime in types:
994
+ dtype = nsectime
995
+
996
+ # we assume the None value has float default value, ie NaN
997
+ # pylint: disable-next=unidiomatic-typecheck
998
+ if type(None) is dtype:
999
+ dtype = float
1000
+
1001
+ return dtype
1002
+
1003
+
1004
+ # ------------------- #
1005
+ # extend datetime
1006
+
1007
+
1008
+ class AbstractTime:
1009
+ def __init__(self):
1010
+ self.ts: pd.Timestamp
1011
+
1012
+ @property
1013
+ def year(self):
1014
+ return self.ts.year
1015
+
1016
+ @property
1017
+ def month(self):
1018
+ return self.ts.month
1019
+
1020
+ @property
1021
+ def day(self):
1022
+ return self.ts.day
1023
+
1024
+ def date(self):
1025
+ return _date(self.year, self.month, self.day)
1026
+
1027
+ @property
1028
+ def start(self):
1029
+ return pd.Timestamp(self.year, self.month, self.day)
1030
+
1031
+ @property
1032
+ def end(self):
1033
+ return pd.Timestamp(next_day(self.start))
1034
+
1035
+ def strftime(self, fmt):
1036
+ return self.ts.strftime(fmt)
1037
+
1038
+ @property
1039
+ def value(self):
1040
+ return self.ts.value
1041
+
1042
+ def timestamp(self):
1043
+ return self.ts.timestamp()
1044
+
1045
+ def __eq__(self, other):
1046
+ other = getattr(other, "ts", other)
1047
+ return self.ts == other
1048
+
1049
+ def __hash__(self):
1050
+ return hash(self.ts)
1051
+
1052
+ def __gt__(self, other):
1053
+ other = getattr(other, "ts", other)
1054
+ return self.ts > other
1055
+
1056
+ def __ge__(self, other):
1057
+ other = getattr(other, "ts", other)
1058
+ return self.ts >= other
1059
+
1060
+ def __lt__(self, other):
1061
+ other = getattr(other, "ts", other)
1062
+ return self.ts < other
1063
+
1064
+ def __le__(self, other):
1065
+ other = getattr(other, "ts", other)
1066
+ return self.ts <= other
1067
+
1068
+
1069
+ class datetime(AbstractTime):
1070
+ """
1071
+ Class :py:class:`otp.datetime <onetick.py.datetime>` is used for representing date with time in onetick-py.
1072
+ It can be used both when specifying start and end time for queries and
1073
+ in column operations with :py:class:`onetick.py.Source`.
1074
+ :ref:`Datetime offset objects <datetime_offsets>` (e.g. `otp.Nano`, `otp.Day`)
1075
+ can be added to or subtracted from `otp.datetime` object.
1076
+
1077
+ Note
1078
+ ----
1079
+ Class :py:class:`otp.datetime <onetick.py.datetime>` share many methods
1080
+ that classes :pandas:`pandas.Timestamp` and :py:class:`datetime.datetime` have,
1081
+ but these objects are not fully interchangeable.
1082
+ Class :py:class:`otp.datetime <onetick.py.datetime>` should work in all onetick-py methods and classes,
1083
+ other classes should work too if documented,
1084
+ and may even work when not documented, but the users should not count on it.
1085
+
1086
+ Parameters
1087
+ ----------
1088
+ first_arg: int, str, :py:class:`otp.datetime <onetick.py.datetime>`, :py:class:`otp.date <onetick.py.date>`\
1089
+ :pandas:`pandas.Timestamp`, :py:class:`datetime.datetime`
1090
+ If `month`, `day` and other parts of date are specified,
1091
+ first argument will be considered as year.
1092
+ Otherwise, first argument will be converted to :py:class:`otp.datetime <onetick.py.datetime>`.
1093
+ month: int
1094
+ Number between 1 and 12.
1095
+ day: int
1096
+ Number between 1 and 31.
1097
+ hour: int, default=0
1098
+ Number between 0 and 23.
1099
+ minute: int, default=0
1100
+ Number between 0 and 59.
1101
+ second: int, default=0
1102
+ Number between 0 and 59.
1103
+ microsecond: int, default=0
1104
+ Number between 0 and 999999.
1105
+ nanosecond: int, default=0
1106
+ Number between 0 and 999.
1107
+ tzinfo: :py:class:`datetime.tzinfo`
1108
+ Timezone object.
1109
+ tz: str
1110
+ Timezone name.
1111
+
1112
+ Examples
1113
+ --------
1114
+
1115
+ Initialization by :py:class:`datetime.datetime` class from standard library:
1116
+
1117
+ >>> otp.datetime(datetime.datetime(2019, 1, 1, 1))
1118
+ 2019-01-01 01:00:00
1119
+
1120
+ Initialization by :pandas:`pandas.Timestamp` class:
1121
+
1122
+ >>> otp.datetime(pd.Timestamp(2019, 1, 1, 1))
1123
+ 2019-01-01 01:00:00
1124
+
1125
+ Initialization by int timestamp:
1126
+
1127
+ >>> otp.datetime(1234567890)
1128
+ 1970-01-01 00:00:01.234567890
1129
+
1130
+ Initialization by params with nanoseconds:
1131
+
1132
+ >>> otp.datetime(2019, 1, 1, 1, 2, 3, 4, 5)
1133
+ 2019-01-01 01:02:03.000004005
1134
+
1135
+ Initialization by string:
1136
+
1137
+ >>> otp.datetime('2019/01/01 1:02')
1138
+ 2019-01-01 01:02:00
1139
+
1140
+ `otp.dt` is the alias for `otp.datetime`:
1141
+
1142
+ >>> otp.dt(2019, 1, 1)
1143
+ 2019-01-01 00:00:00
1144
+
1145
+ See also
1146
+ --------
1147
+ :ref:`Datetime offset objects <datetime_guide>`.
1148
+ """
1149
+ def __init__(
1150
+ self,
1151
+ first_arg,
1152
+ month=None,
1153
+ day=None,
1154
+ hour=None,
1155
+ minute=None,
1156
+ second=None,
1157
+ microsecond=None,
1158
+ nanosecond=None,
1159
+ *,
1160
+ tzinfo=None,
1161
+ tz=None,
1162
+ ): # TODO: python 3.8 change first_arg to positional only arg
1163
+ tz, tzinfo = self._process_timezones_args(tz, tzinfo)
1164
+
1165
+ if not any([month, day, hour, minute, second, microsecond, nanosecond]):
1166
+ result = self._create_from_one_arg(first_arg, tz, tzinfo)
1167
+ else:
1168
+ result = self._create_from_several_arg(first_arg, month, day, hour, minute, second, microsecond, nanosecond,
1169
+ tzinfo)
1170
+ self.ts = result
1171
+
1172
+ def _process_timezones_args(self, tz, tzinfo):
1173
+ if tz is not None:
1174
+ if tzinfo is None:
1175
+ # parameter tz in pandas.Timestamp is broken https://github.com/pandas-dev/pandas/issues/31929
1176
+ # it is fixed in pandas>=2.0.0, but we need to support older versions
1177
+ tzinfo = get_tzfile_by_name(tz)
1178
+ tz = None
1179
+ else:
1180
+ raise ValueError(
1181
+ "tzinfo and tz params are mutually exclusive parameters, "
1182
+ "they can't be specified both at the same time"
1183
+ )
1184
+ return tz, tzinfo
1185
+
1186
+ def _create_from_several_arg(self, first_arg, month, day, hour, minute, second, microsecond, nanosecond, tzinfo):
1187
+ if nanosecond is not None and not (0 <= nanosecond <= 999):
1188
+ raise ValueError(
1189
+ "Nanosecond parameter should be between 0 and 999. "
1190
+ "Please use microsecond parameter or otp.Nano object."
1191
+ )
1192
+ if parse_version(pd.__version__) >= parse_version("2.0.0"):
1193
+ result = pd.Timestamp(
1194
+ first_arg, month, day, hour or 0, minute or 0, second or 0, microsecond or 0,
1195
+ nanosecond=nanosecond or 0,
1196
+ ).replace(tzinfo=tzinfo)
1197
+ else:
1198
+ result = pd.Timestamp(
1199
+ first_arg, month, day, hour or 0, minute or 0, second or 0, microsecond or 0, nanosecond or 0,
1200
+ ).replace(tzinfo=tzinfo)
1201
+ return result
1202
+
1203
+ def _create_from_one_arg(self, first_arg, tz, tzinfo):
1204
+ arg_tz = getattr(first_arg, "tz", None)
1205
+ arg_tzinfo = getattr(first_arg, "tzinfo", None)
1206
+ if tz and arg_tz and arg_tz != tz or tzinfo and arg_tzinfo and arg_tzinfo != tzinfo:
1207
+ raise ValueError(
1208
+ "You've specified the timezone for the object, which already has it. "
1209
+ "It is recommended to swap the current timezone to desired by method of this object "
1210
+ "and then create otp.datetime object."
1211
+ )
1212
+ if isinstance(first_arg, (datetime, date)):
1213
+ first_arg = first_arg.ts
1214
+ result = pd.Timestamp(first_arg, tzinfo=tzinfo, tz=tz)
1215
+ return result
1216
+
1217
+ @property
1218
+ def start(self):
1219
+ return super().start.replace(tzinfo=self.tzinfo)
1220
+
1221
+ @property
1222
+ def end(self):
1223
+ return super().end.replace(tzinfo=self.tzinfo)
1224
+
1225
+ def replace(self, **kwargs):
1226
+ """
1227
+ Replace parts of `otp.datetime` object.
1228
+
1229
+ Parameters
1230
+ ----------
1231
+ year: int, optional
1232
+ month: int, optional
1233
+ day: int, optional
1234
+ hour: int, optional
1235
+ minute: int, optional
1236
+ second: int, optional
1237
+ microsecond: int, optional
1238
+ nanosecond: int, optional
1239
+ tzinfo: tz-convertible, optional
1240
+
1241
+ Returns
1242
+ -------
1243
+ result: :py:class:`otp.datetime <onetick.py.datetime>`
1244
+ Timestamp with fields replaced.
1245
+
1246
+ Examples
1247
+ --------
1248
+ >>> ts = otp.datetime(2022, 2, 24, 3, 15, 54, 999, 1)
1249
+ >>> ts
1250
+ 2022-02-24 03:15:54.000999001
1251
+ >>> ts.replace(year=2000, month=2, day=2, hour=2, minute=2, second=2, microsecond=2, nanosecond=2)
1252
+ 2000-02-02 02:02:02.000002002
1253
+ """
1254
+ return datetime(self.ts.replace(**kwargs))
1255
+
1256
+ @property
1257
+ def tz(self):
1258
+ return self.ts.tz
1259
+
1260
+ @property
1261
+ def tzinfo(self):
1262
+ return self.ts.tzinfo
1263
+
1264
+ @property
1265
+ def hour(self):
1266
+ return self.ts.hour
1267
+
1268
+ @property
1269
+ def minute(self):
1270
+ return self.ts.minute
1271
+
1272
+ @property
1273
+ def second(self):
1274
+ return self.ts.second
1275
+
1276
+ @property
1277
+ def microsecond(self):
1278
+ return self.ts.microsecond
1279
+
1280
+ @property
1281
+ def nanosecond(self):
1282
+ return self.ts.nanosecond
1283
+
1284
+ @staticmethod
1285
+ def now(tz=None):
1286
+ """
1287
+ Will return :py:class:`otp.datetime <onetick.py.datetime>` object
1288
+ with timestamp at the moment of calling this function.
1289
+ Not to be confused with function :func:`otp.now <onetick.py.now>` which can only add column
1290
+ with current timestamp to the :py:class:`otp.Source <onetick.py.Source>` when running the query.
1291
+
1292
+ Parameters
1293
+ ----------
1294
+ tz : str or timezone object, default None
1295
+ Timezone to localize to.
1296
+ """
1297
+ return datetime(pd.Timestamp.now(tz))
1298
+
1299
+ def __add__(self, other):
1300
+ """
1301
+ Add :ref:`datetime offset <datetime_offsets>` to otp.datetime.
1302
+
1303
+ Parameters
1304
+ ----------
1305
+ other: :ref:`datetime offset <datetime_offsets>`, :py:class:`otp.datetime <onetick.py.datetime>`
1306
+ object to add
1307
+
1308
+ Returns
1309
+ -------
1310
+ result: :py:class:`otp.datetime <onetick.py.datetime>`, :pandas:`pandas.Timedelta`
1311
+ return :py:class:`otp.datetime <onetick.py.datetime>`
1312
+ if otp.Nano or another :ref:`datetime offset <datetime_offsets>` object was passed as an argument,
1313
+ or :pandas:`pandas.Timedelta` object if :py:class:`otp.datetime <onetick.py.datetime>`
1314
+ was passed as an argument.
1315
+
1316
+ Examples
1317
+ --------
1318
+ >>> otp.datetime(2022, 2, 24) + otp.Nano(1)
1319
+ 2022-02-24 00:00:00.000000001
1320
+ """
1321
+ self._error_on_int_param(other, "+")
1322
+ return datetime(self.ts + other)
1323
+
1324
+ def __sub__(self, other):
1325
+ """
1326
+ Subtract :ref:`datetime offset <datetime_offsets>` from otp.datetime.
1327
+
1328
+ Parameters
1329
+ ----------
1330
+ other: :ref:`datetime offset <datetime_offsets>`, :py:class:`otp.datetime <onetick.py.datetime>`
1331
+ object to subtract
1332
+
1333
+ Returns
1334
+ -------
1335
+ result: :py:class:`otp.datetime <onetick.py.datetime>`, :pandas:`pandas.Timedelta`
1336
+ return datetime if otp.Nano or another :ref:`datetime offset <datetime_offsets>`
1337
+ object was passed as an argument,
1338
+ or :pandas:`pandas.Timedelta` object if :py:class:`otp.datetime <onetick.py.datetime>`
1339
+ was passed as an argument.
1340
+
1341
+ Examples
1342
+ --------
1343
+ >>> otp.datetime(2022, 2, 24) - otp.Nano(1)
1344
+ 2022-02-23 23:59:59.999999999
1345
+ """
1346
+ self._error_on_int_param(other, "-")
1347
+ other = getattr(other, "ts", other)
1348
+ result = self.ts - other
1349
+ # do not convert to datetime in case timedelta is returned (arg is date)
1350
+ result = datetime(result) if isinstance(result, pd.Timestamp) else result
1351
+ return result
1352
+
1353
+ def _error_on_int_param(self, other, op):
1354
+ if isinstance(other, int):
1355
+ raise TypeError(f"unsupported operand type(s) for {op}: 'otp.datetime' and 'int'")
1356
+
1357
+ def __str__(self):
1358
+ return str(self.ts)
1359
+
1360
+ def __repr__(self):
1361
+ return str(self.ts)
1362
+
1363
+ def tz_localize(self, tz):
1364
+ """
1365
+ Localize tz-naive datetime object to a given timezone
1366
+
1367
+ Parameters
1368
+ ----------
1369
+ tz: str or tzinfo
1370
+ timezone to localize datetime object into
1371
+
1372
+ Returns
1373
+ -------
1374
+ result: :py:class:`otp.datetime <onetick.py.datetime>`
1375
+ localized datetime object
1376
+
1377
+ Examples
1378
+ --------
1379
+ >>> d = otp.datetime(2021, 6, 3)
1380
+ >>> d.tz_localize("EST5EDT")
1381
+ 2021-06-03 00:00:00-04:00
1382
+ """
1383
+ return datetime(self.ts.tz_localize(tz))
1384
+
1385
+ def tz_convert(self, tz):
1386
+ """
1387
+ Convert tz-aware datetime object to another timezone
1388
+
1389
+ Parameters
1390
+ ----------
1391
+ tz: str or tzinfo
1392
+ timezone to convert datetime object into
1393
+
1394
+ Returns
1395
+ -------
1396
+ result: :py:class:`otp.datetime <onetick.py.datetime>`
1397
+ converted datetime object
1398
+
1399
+ Examples
1400
+ --------
1401
+ >>> d = otp.datetime(2021, 6, 3, tz="EST5EDT")
1402
+ >>> d.tz_convert("Europe/Moscow")
1403
+ 2021-06-03 07:00:00+03:00
1404
+ """
1405
+ return datetime(self.ts.tz_convert(tz))
1406
+
1407
+ def to_operation(self, timezone=None):
1408
+ """
1409
+ Convert :py:class:`otp.datetime <onetick.py.datetime>` object to
1410
+ :py:class:`otp.Operation <onetick.py.Operation>`
1411
+
1412
+ Parameters
1413
+ ----------
1414
+ timezone: Operation
1415
+ Can be used to specify timezone as an Operation.
1416
+
1417
+ Examples
1418
+ --------
1419
+ >>> t = otp.Ticks(TZ=['EST5EDT', 'GMT'])
1420
+ >>> t['DT'] = otp.dt(2022, 1, 1).to_operation(timezone=t['TZ'])
1421
+ >>> otp.run(t, timezone='GMT')[['TZ', 'DT']]
1422
+ TZ DT
1423
+ 0 EST5EDT 2022-01-01 05:00:00
1424
+ 1 GMT 2022-01-01 00:00:00
1425
+ """
1426
+ return otp.Operation(op_str=otp.types.datetime2expr(self, timezone=timezone), dtype=otp.nsectime)
1427
+
1428
+
1429
+ dt = datetime
1430
+
1431
+
1432
+ class date(datetime):
1433
+ """
1434
+ Class ``date`` is used for representing date in onetick-py.
1435
+ It can be used both when specifying start and end time for queries and
1436
+ in column operations with :py:class:`onetick.py.Source`.
1437
+
1438
+ Note
1439
+ ----
1440
+ Class :py:class:`otp.date <onetick.py.date>` share many methods
1441
+ that classes :pandas:`pandas.Timestamp` and :py:class:`datetime.date` have,
1442
+ but these objects are not fully interchangeable.
1443
+ Class :py:class:`otp.date <onetick.py.date>` should work in all onetick-py methods and classes,
1444
+ other classes should work too if documented,
1445
+ and may even work when not documented, but the users should not count on it.
1446
+
1447
+ Parameters
1448
+ ----------
1449
+ first_arg: int, str, :py:class:`otp.datetime <onetick.py.datetime>`, :py:class:`otp.date <onetick.py.date>`\
1450
+ :pandas:`pandas.Timestamp`, :py:class:`datetime.datetime`, :py:class:`datetime.date`
1451
+ If `month` and `day` arguments are specified, first argument will be considered as year.
1452
+ Otherwise, first argument will be converted to otp.date.
1453
+ month: int
1454
+ Number between 1 and 12.
1455
+ day: int
1456
+ Number between 1 and 31.
1457
+
1458
+ Examples
1459
+ --------
1460
+ :ref:`Datetime guide <datetime_guide>`.
1461
+ """
1462
+
1463
+ def __init__(self, first_arg: Union[int, str, _date, _datetime, pd.Timestamp, AbstractTime],
1464
+ month=None, day=None):
1465
+ if month is None and day is None:
1466
+ if isinstance(first_arg, AbstractTime):
1467
+ first_arg = first_arg.ts
1468
+ elif isinstance(first_arg, (int, str)):
1469
+ first_arg = pd.Timestamp(first_arg)
1470
+ if isinstance(first_arg, (_datetime, pd.Timestamp, datetime)):
1471
+ first_arg = first_arg.date()
1472
+ self.ts = pd.Timestamp(first_arg) # remove hour, minutes and so on
1473
+ elif all((month, day)):
1474
+ self.ts = pd.Timestamp(first_arg, month, day)
1475
+ else:
1476
+ raise ValueError("Please specify three integers (year, month, day) "
1477
+ "or object or create date from (string, int timestamp, "
1478
+ "pandas.Timestamp, otp.datetime, otp.date, "
1479
+ "datetime.datetime, datetime.date)")
1480
+
1481
+ def __str__(self):
1482
+ return self.ts.strftime("%Y-%m-%d")
1483
+
1484
+ def __repr__(self):
1485
+ return self.ts.strftime("%Y-%m-%d")
1486
+
1487
+ def to_str(self, format="%Y%m%d"):
1488
+ """
1489
+ Convert date to string, by default it will be in YYYYMMDD format.
1490
+
1491
+ Parameters
1492
+ ----------
1493
+ format: str
1494
+ strftime format of string to convert to.
1495
+ Returns
1496
+ -------
1497
+ result: str
1498
+ """
1499
+ return self.ts.strftime(format)
1500
+
1501
+
1502
+ class _integer_meta(type):
1503
+ def __str__(cls):
1504
+ return getattr(cls, '_NAME', cls.__name__)
1505
+
1506
+ @property
1507
+ def TYPE_SIZE(cls):
1508
+ return 8 * ctypes.sizeof(cls._CTYPE)
1509
+
1510
+ @property
1511
+ def MIN(cls):
1512
+ if cls._UNSIGNED:
1513
+ return 0
1514
+ else:
1515
+ return -(2 ** (cls.TYPE_SIZE - 1))
1516
+
1517
+ @property
1518
+ def MAX(cls):
1519
+ if cls._UNSIGNED:
1520
+ return (2 ** cls.TYPE_SIZE) - 1
1521
+ else:
1522
+ return (2 ** (cls.TYPE_SIZE - 1)) - 1
1523
+
1524
+
1525
+ class _integer(int, metaclass=_integer_meta):
1526
+ def __new__(cls, value, *args, **kwargs):
1527
+ if not cls.MIN <= value <= cls.MAX:
1528
+ raise ValueError(f"{cls.__name__} values must be between {cls.MIN} and {cls.MAX}")
1529
+ return super().__new__(cls, value, *args, **kwargs)
1530
+
1531
+ def __get_result(self, value):
1532
+ if isinstance(value, int):
1533
+ return self.__class__(self._CTYPE(value).value)
1534
+ return value
1535
+
1536
+ def __add__(self, other):
1537
+ return self.__get_result(
1538
+ super().__add__(other)
1539
+ )
1540
+
1541
+ def __radd__(self, other):
1542
+ return self.__get_result(
1543
+ super().__radd__(other)
1544
+ )
1545
+
1546
+ def __sub__(self, other):
1547
+ return self.__get_result(
1548
+ super().__sub__(other)
1549
+ )
1550
+
1551
+ def __rsub__(self, other):
1552
+ return self.__get_result(
1553
+ super().__rsub__(other)
1554
+ )
1555
+
1556
+ def __mul__(self, other):
1557
+ return self.__get_result(
1558
+ super().__mul__(other)
1559
+ )
1560
+
1561
+ def __rmul__(self, other):
1562
+ return self.__get_result(
1563
+ super().__rmul__(other)
1564
+ )
1565
+
1566
+ def __truediv__(self, other):
1567
+ return self.__get_result(
1568
+ super().__truediv__(other)
1569
+ )
1570
+
1571
+ def __rtruediv__(self, other):
1572
+ return self.__get_result(
1573
+ super().__rtruediv__(other)
1574
+ )
1575
+
1576
+ def __str__(self):
1577
+ return super().__repr__()
1578
+
1579
+ def __repr__(self):
1580
+ return f"{self.__class__.__name__}({self})"
1581
+
1582
+
1583
+ class long(_integer):
1584
+ """
1585
+ OneTick data type representing signed long integer.
1586
+
1587
+ The size of the type is not specified and may vary across different systems.
1588
+ Most commonly it's a 8-byte type with allowed values from -2**63 to 2**63 - 1.
1589
+
1590
+ Note that the value is checked to be valid in constructor,
1591
+ but no overflow checking is done when arithmetic operations are performed.
1592
+
1593
+ Examples
1594
+ --------
1595
+ >>> t = otp.Tick(A=otp.long(1))
1596
+ >>> t['B'] = otp.long(1) + 1
1597
+ >>> t.schema
1598
+ {'A': <class 'onetick.py.types.long'>, 'B': <class 'onetick.py.types.long'>}
1599
+ """
1600
+ _CTYPE = ctypes.c_long
1601
+ _UNSIGNED = False
1602
+
1603
+
1604
+ class ulong(_integer):
1605
+ """
1606
+ OneTick data type representing unsigned long integer.
1607
+
1608
+ The size of the type is not specified and may vary across different systems.
1609
+ Most commonly it's a 8-byte type with allowed values from 0 to 2**64 - 1.
1610
+
1611
+ Note that the value is checked to be valid in constructor,
1612
+ but no overflow checking is done when arithmetic operations are performed.
1613
+
1614
+ Examples
1615
+ --------
1616
+ >>> t = otp.Tick(A=otp.ulong(1))
1617
+ >>> t['B'] = otp.ulong(1) + 1
1618
+ >>> t.schema
1619
+ {'A': <class 'onetick.py.types.ulong'>, 'B': <class 'onetick.py.types.ulong'>}
1620
+
1621
+ Note that arithmetic operations may result in overflow.
1622
+ Here we get 2**64 - 1 instead of -1.
1623
+
1624
+ .. testcode::
1625
+ :skipif: is_windows()
1626
+
1627
+ t = otp.Tick(A=otp.ulong(0) - 1)
1628
+ df = otp.run(t)
1629
+ print(df)
1630
+
1631
+ .. testoutput::
1632
+
1633
+ Time A
1634
+ 0 2003-12-01 18446744073709551615
1635
+ """
1636
+ _CTYPE = ctypes.c_ulong
1637
+ _UNSIGNED = True
1638
+
1639
+
1640
+ class _int(_integer):
1641
+ """
1642
+ OneTick data type representing signed integer.
1643
+
1644
+ The size of the type is not specified and may vary across different systems.
1645
+ Most commonly it's a 4-byte type with allowed values from -2**31 to 2**31 - 1.
1646
+
1647
+ Note that the value is checked to be valid in constructor,
1648
+ but no overflow checking is done when arithmetic operations are performed.
1649
+
1650
+ Examples
1651
+ --------
1652
+ >>> t = otp.Tick(A=otp.int(1))
1653
+ >>> t['B'] = otp.int(1) + 1
1654
+ >>> t.schema
1655
+ {'A': <class 'onetick.py.types._int'>, 'B': <class 'onetick.py.types._int'>}
1656
+ """
1657
+ _CTYPE = ctypes.c_int
1658
+ _UNSIGNED = False
1659
+ _NAME = 'int'
1660
+
1661
+
1662
+ class uint(_integer):
1663
+ """
1664
+ OneTick data type representing unsigned integer.
1665
+
1666
+ The size of the type is not specified and may vary across different systems.
1667
+ Most commonly it's a 4-byte type with allowed values from 0 to 2**32 - 1.
1668
+
1669
+ Note that the value is checked to be valid in constructor,
1670
+ but no overflow checking is done when arithmetic operations are performed.
1671
+
1672
+ Examples
1673
+ --------
1674
+ >>> t = otp.Tick(A=otp.uint(1))
1675
+ >>> t['B'] = otp.uint(1) + 1
1676
+ >>> t.schema
1677
+ {'A': <class 'onetick.py.types.uint'>, 'B': <class 'onetick.py.types.uint'>}
1678
+
1679
+ Note that arithmetic operations may result in overflow.
1680
+ Here we get 2**32 - 1 instead of -1.
1681
+
1682
+ .. testcode::
1683
+ :skipif: is_windows()
1684
+
1685
+ t = otp.Tick(A=otp.uint(0) - 1)
1686
+ df = otp.run(t)
1687
+ print(df)
1688
+
1689
+ .. testoutput::
1690
+
1691
+ Time A
1692
+ 0 2003-12-01 4294967295
1693
+ """
1694
+ _CTYPE = ctypes.c_uint
1695
+ _UNSIGNED = True
1696
+
1697
+
1698
+ class byte(_integer):
1699
+ """
1700
+ OneTick data type representing byte integer.
1701
+
1702
+ The size of the type is not specified and may vary across different systems.
1703
+ Most commonly it's a 1-byte type with allowed values from -128 to 127.
1704
+
1705
+ Note that the value is checked to be valid in constructor,
1706
+ but no overflow checking is done when arithmetic operations are performed.
1707
+
1708
+ Examples
1709
+ --------
1710
+ >>> t = otp.Tick(A=otp.byte(1))
1711
+ >>> t['B'] = otp.byte(1) + 1
1712
+ >>> t.schema
1713
+ {'A': <class 'onetick.py.types.byte'>, 'B': <class 'onetick.py.types.byte'>}
1714
+
1715
+ Note that arithmetic operations may result in overflow.
1716
+ Here we get 127 instead of -129.
1717
+
1718
+ >>> t = otp.Tick(A=otp.byte(-128) - 1)
1719
+ >>> otp.run(t)
1720
+ Time A
1721
+ 0 2003-12-01 127
1722
+ """
1723
+ _CTYPE = ctypes.c_byte
1724
+ _UNSIGNED = False
1725
+
1726
+
1727
+ class short(_integer):
1728
+ """
1729
+ OneTick data type representing short integer.
1730
+
1731
+ The size of the type is not specified and may vary across different systems.
1732
+ Most commonly it's a 2-byte type with allowed values from -32768 to 32767.
1733
+
1734
+ Note that the value is checked to be valid in constructor,
1735
+ but no overflow checking is done when arithmetic operations are performed.
1736
+
1737
+ Examples
1738
+ --------
1739
+ >>> t = otp.Tick(A=otp.short(1))
1740
+ >>> t['B'] = otp.short(1) + 1
1741
+ >>> t.schema
1742
+ {'A': <class 'onetick.py.types.short'>, 'B': <class 'onetick.py.types.short'>}
1743
+
1744
+ Note that arithmetic operations may result in overflow.
1745
+ Here we get 32767 instead of -32769.
1746
+
1747
+ >>> t = otp.Tick(A=otp.short(-32768) - 1)
1748
+ >>> otp.run(t)
1749
+ Time A
1750
+ 0 2003-12-01 32767
1751
+ """
1752
+ _CTYPE = ctypes.c_short
1753
+ _UNSIGNED = False
1754
+
1755
+
1756
+ # ------------------- #
1757
+ def type2str(t):
1758
+ if t is int:
1759
+ return "long"
1760
+ if t is str:
1761
+ return "string"
1762
+ if t is float:
1763
+ return "double"
1764
+ if t is None:
1765
+ return ''
1766
+ if t is decimal:
1767
+ return t._to_onetick_type_string()
1768
+ return str(t)
1769
+
1770
+
1771
+ def str2type(type_name: str):
1772
+ """
1773
+ Converts OneTick type by its name into Python/OTP domain type.
1774
+
1775
+ Parameters
1776
+ ----------
1777
+ type_name: str
1778
+ name of type from CSV or OneTick DB
1779
+
1780
+ Returns
1781
+ -------
1782
+ class:
1783
+ Python/OTP type representing OneTick type
1784
+ """
1785
+ if type_name == "time32":
1786
+ return int
1787
+ if type_name == "long":
1788
+ # otp.long can be used too, but we use int for backward compatibility
1789
+ return int
1790
+ if type_name == "int":
1791
+ return _int
1792
+ if type_name == "byte":
1793
+ return byte
1794
+ if type_name == "short":
1795
+ return short
1796
+ if type_name == "uint":
1797
+ return uint
1798
+ if type_name == "ulong":
1799
+ return ulong
1800
+ elif type_name in ["double", "float"]:
1801
+ return float
1802
+ elif type_name == "decimal":
1803
+ return decimal
1804
+ elif type_name == "msectime":
1805
+ return msectime
1806
+ elif type_name == "nsectime":
1807
+ return nsectime
1808
+ elif type_name in ["string", "matrix", f"string[{string.DEFAULT_LENGTH}]"]:
1809
+ return str
1810
+ elif type_name == "varstring":
1811
+ return varstring
1812
+ elif type_name.find("string") != -1:
1813
+ length = int(type_name[type_name.find("[") + 1:type_name.find("]")])
1814
+ return string[length]
1815
+ return None
1816
+
1817
+
1818
+ def type2np(t):
1819
+ if issubclass(t, str):
1820
+ if issubclass(t, otp.string) and isinstance(t.length, int):
1821
+ return '<U' + str(t.length)
1822
+ return '<U64'
1823
+ elif issubclass(t, bool):
1824
+ return 'boolean'
1825
+ elif issubclass(t, nsectime):
1826
+ return 'datetime64[ns]'
1827
+ elif issubclass(t, msectime):
1828
+ return 'datetime64[ms]'
1829
+ elif issubclass(t, datetime):
1830
+ return 'datetime64[ns]'
1831
+ elif issubclass(t, byte):
1832
+ if otq.webapi:
1833
+ return 'int8'
1834
+ return 'int32'
1835
+ elif issubclass(t, short):
1836
+ if otq.webapi:
1837
+ return 'int16'
1838
+ return 'int32'
1839
+ elif issubclass(t, uint):
1840
+ return 'uint32'
1841
+ elif issubclass(t, ulong):
1842
+ return 'uint64'
1843
+ elif issubclass(t, _int):
1844
+ return 'int32'
1845
+ elif issubclass(t, long):
1846
+ return 'int64'
1847
+ elif issubclass(t, int):
1848
+ return 'int64'
1849
+ elif issubclass(t, float):
1850
+ return 'float64'
1851
+ elif issubclass(t, decimal):
1852
+ return 'float64'
1853
+ else:
1854
+ return np.dtype(t)
1855
+
1856
+
1857
+ # TODO: move this union of types to some common place
1858
+ def datetime2expr(
1859
+ dt_obj: Union[_datetime, _date, pd.Timestamp, date, datetime],
1860
+ timezone: Optional[str] = None,
1861
+ timezone_naive: Optional[str] = None,
1862
+ ) -> str:
1863
+ """
1864
+ Convert python datetime values to OneTick string representation.
1865
+ If ``dt_obj`` is timezone-aware then timezone will be taken from ``dt_obj`` value.
1866
+ If ``dt_obj`` is timezone-naive then timezone specified with otp.config['tz'] or otp.run() will be used.
1867
+
1868
+ Parameters
1869
+ ----------
1870
+ dt_obj
1871
+ date or datetime value
1872
+ timezone: str or Operation
1873
+ This timezone will be used unconditionally.
1874
+ timezone_naive: str or Operation
1875
+ This timezone will be used if ``dt_obj`` is timezone-naive.
1876
+ """
1877
+ dt_str = _format_datetime(dt_obj, '%Y-%m-%d %H:%M:%S.%f', add_nano_suffix=True)
1878
+ if timezone is None:
1879
+ timezone = get_timezone_from_datetime(dt_obj)
1880
+ if timezone is None:
1881
+ timezone = timezone_naive
1882
+ if not isinstance(timezone, otp.Operation):
1883
+ timezone = f'"{timezone}"' if timezone else '_TIMEZONE'
1884
+ return f'PARSE_NSECTIME("%Y-%m-%d %H:%M:%S.%J", "{dt_str}", {str(timezone)})'
1885
+
1886
+
1887
+ def datetime2timeval(dt_obj: Union[_datetime, _date, pd.Timestamp, date, datetime], timezone: str = 'GMT'):
1888
+ """
1889
+ Get nanosecond-precision pyomd.timeval_t
1890
+ from different datetime types supported by onetick-py.
1891
+
1892
+ If ``dt_obj`` is timezone-aware, then its timezone will be used.
1893
+ If ``dt_obj`` is timezone-naive , then it will be localized to ``timezone`` parameter (GMT by default).
1894
+ """
1895
+ dt_str = _format_datetime(dt_obj, '%Y-%m-%d %H:%M:%S.%f', add_nano_suffix=True)
1896
+ tz_str = get_timezone_from_datetime(dt_obj)
1897
+ if tz_str:
1898
+ # use timezone from the object
1899
+ return pyomd.TimeParser('%Y-%m-%d %H:%M:%S.%J', tz_str).parse_time(dt_str)
1900
+ else:
1901
+ # localize to the timezone specified in fucntion parameter
1902
+ return pyomd.TimeParser('%Y-%m-%d %H:%M:%S.%J', timezone).parse_time(dt_str)
1903
+
1904
+
1905
+ def _format_datetime(dt_obj, fmt, add_nano_suffix=True):
1906
+ dt_str = dt_obj.strftime(fmt)
1907
+ if add_nano_suffix:
1908
+ if isinstance(dt_obj, (pd.Timestamp, datetime)):
1909
+ dt_str += f'{dt_obj.nanosecond:03}'[-3:]
1910
+ else:
1911
+ dt_str += '000'
1912
+ return dt_str
1913
+
1914
+
1915
+ def value2str(v):
1916
+ """
1917
+ Converts a python value from the `v` parameter into OneTick format.
1918
+ """
1919
+ if issubclass(type(v), str):
1920
+ # there is no escape, so replacing double quotes with concatenation with it
1921
+ return '"' + str(v).replace('"', '''"+'"'+"''') + '"'
1922
+
1923
+ if isinstance(v, decimal):
1924
+ return v._to_onetick_string()
1925
+
1926
+ if isinstance(v, float) and not (isinstance(v, (_inf, _nan))):
1927
+ # PY-286: support science notation
1928
+ s = str(v)
1929
+ if "e" in s:
1930
+ return f'atof({value2str(s)})'
1931
+ if s == "nan":
1932
+ return str(nan)
1933
+ return s
1934
+
1935
+ if is_time_type(v):
1936
+ return datetime2expr(v)
1937
+
1938
+ if isinstance(v, nsectime):
1939
+ # we do not need the same for msectime because it works as is
1940
+ if int(v) == 0 or int(v) > 15e12: # it is 2445/5/1
1941
+ return f'NSECTIME({v})'
1942
+ # This branch is for backward compatibility. Originally here was a bug that
1943
+ # allowed to pass only milliseconds as a value into the otp.nsectime constructor.
1944
+ # Obviously we expect there only nanoseconds, and the built-in NSECTIME works only
1945
+ # with nanoseconds.
1946
+ warnings.warn('It seems that you are using number of milliseconds as nanoseconds. ', stacklevel=2)
1947
+
1948
+ return str(v)
1949
+
1950
+
1951
+ # TODO: maybe can be removed, it is used only in tests now
1952
+ def time2nsectime(time, timezone=None):
1953
+ """
1954
+ Converts complex time types to nsectime timestamp.
1955
+
1956
+ Parameters
1957
+ ----------
1958
+ time: :py:class:`datetime.datetime`, :py:class:`datetime.date`,\
1959
+ :py:class:`otp.datetime <onetick.py.datetime>`, :py:class:`otp.date <onetick.py.date>`, pandas.Timestamp
1960
+ time to convert
1961
+ timezone:
1962
+ convert timezone before nsectime calculation
1963
+
1964
+ Returns
1965
+ -------
1966
+ result: int
1967
+ number of nanoseconds since epoch
1968
+ """
1969
+ if isinstance(time, (_datetime, _date)):
1970
+ time = pd.Timestamp(time)
1971
+ elif isinstance(time, date):
1972
+ time = datetime(time)
1973
+ if timezone:
1974
+ if not has_timezone_parameter(): # accommodating legacy behavior prior to 20220327-3 weekly build
1975
+ time = time.replace(tzinfo=None)
1976
+ else:
1977
+ if time.tzinfo is None:
1978
+ time = time.tz_localize(timezone)
1979
+ else:
1980
+ # timezone conversion doesn't change the offset from epoch, only string representation,
1981
+ # so time.value (the number of nanoseconds) will stay the same
1982
+ # this line can be deleted
1983
+ time = time.tz_convert(timezone)
1984
+ return time.value
1985
+
1986
+
1987
+ def is_time_type(time):
1988
+ """ Returns true if argument is subclass of any time type
1989
+
1990
+ Checks if pass type is time type, currently checks for otp.date, otp.datetime,
1991
+ pd.Timestamp, datetime.date, datetime.datetime
1992
+
1993
+ Parameters
1994
+ ----------
1995
+ time:
1996
+ object or type of the object
1997
+
1998
+ Returns
1999
+ -------
2000
+ result: bool
2001
+ Return true if argument is time type
2002
+
2003
+ Examples
2004
+ --------
2005
+
2006
+ >>> is_time_type(datetime.datetime) # OTdirective: skip-example: ;
2007
+ True
2008
+ >>> is_time_type(type(5)) # OTdirective: skip-example: ;
2009
+ False
2010
+ >>> is_time_type(datetime.datetime(2019, 1, 1)) # OTdirective: snippet-name: types.is time;
2011
+ True
2012
+ """
2013
+ time = time if inspect.isclass(time) else type(time)
2014
+ # do not check for datetime.datetime and pd.Timestamp, because they are in the same hierarchy
2015
+ # datetime.date -> datetime.datetime -> pd.Timestamp, where `->` means base class
2016
+ return issubclass(time, (_date, datetime, date))
2017
+
2018
+
2019
+ def next_day(dt_obj: Union[_date, _datetime, date, datetime, pd.Timestamp]) -> _datetime:
2020
+ """
2021
+ Return the start of the next day of ``dt_obj`` as timezone-naive :py:class:`datetime.datetime`.
2022
+
2023
+ Next day in this case means simply incrementing day/month/year number,
2024
+ not adding 24 hours (which may return the same date on DST days).
2025
+ """
2026
+ return _datetime(dt_obj.year, dt_obj.month, dt_obj.day) + _timedelta(days=1)
2027
+
2028
+
2029
+ def default_by_type(dtype):
2030
+ """
2031
+ Get default value by OneTick type.
2032
+
2033
+ Parameters
2034
+ ----------
2035
+ dtype:
2036
+ one of onetick-py base types
2037
+
2038
+ Examples
2039
+ --------
2040
+ >>> otp.default_by_type(float)
2041
+ nan
2042
+ >>> otp.default_by_type(otp.decimal)
2043
+ decimal(0)
2044
+ >>> otp.default_by_type(int)
2045
+ 0
2046
+ >>> otp.default_by_type(otp.ulong)
2047
+ ulong(0)
2048
+ >>> otp.default_by_type(otp.uint)
2049
+ uint(0)
2050
+ >>> otp.default_by_type(otp.short)
2051
+ short(0)
2052
+ >>> otp.default_by_type(otp.byte)
2053
+ byte(0)
2054
+ >>> otp.default_by_type(otp.nsectime)
2055
+ nsectime(0)
2056
+ >>> otp.default_by_type(otp.msectime)
2057
+ msectime(0)
2058
+ >>> otp.default_by_type(str)
2059
+ ''
2060
+ >>> otp.default_by_type(otp.string)
2061
+ string('')
2062
+ >>> otp.default_by_type(otp.string[123])
2063
+ string[123]('')
2064
+ >>> otp.default_by_type(otp.varstring)
2065
+ varstring('')
2066
+ """
2067
+ # TODO: think if we want to treat bool as basic onetick type
2068
+ if dtype is bool:
2069
+ return 0
2070
+ if not is_type_basic(dtype):
2071
+ raise TypeError(f"Can't get default value for type: {dtype}")
2072
+ if issubclass(dtype, int):
2073
+ return dtype(0)
2074
+ if dtype is otp.decimal:
2075
+ return otp.decimal(0)
2076
+ if issubclass(dtype, float):
2077
+ return nan
2078
+ if issubclass(dtype, str):
2079
+ return dtype('')
2080
+ if issubclass(dtype, nsectime) or issubclass(dtype, msectime):
2081
+ return dtype(0)
2082
+ raise TypeError(f"Can't get default value for type: {dtype}")
2083
+
2084
+
2085
+ class timedelta(pd.Timedelta):
2086
+ """
2087
+ The object representing the delta between timestamps.
2088
+
2089
+ Parameters
2090
+ ----------
2091
+ value: :py:class:`otp.timedelta <onetick.py.timedelta>`, :pandas:`pandas.Timedelta`,\
2092
+ :py:class:`datetime.timedelta`, str, or int
2093
+ Initialize this object from other types of objects.
2094
+ kwargs:
2095
+ Dictionary of offset names and their values.
2096
+ Available offset names:
2097
+ *weeks*, *days*, *hours*, *minutes*, *seconds*,
2098
+ *milliseconds*, *microseconds*, *nanoseconds*.
2099
+
2100
+ Examples
2101
+ --------
2102
+
2103
+ Create :py:class:`otp.timedelta <onetick.py.timedelta>` from key-value arguments:
2104
+
2105
+ >>> otp.timedelta(weeks=1, days=1, hours=1, minutes=1, seconds=1, milliseconds=1, microseconds=1, nanoseconds=1)
2106
+ timedelta('8 days 01:01:01.001001001')
2107
+
2108
+ Create :py:class:`otp.timedelta <onetick.py.timedelta>` from different types of objects:
2109
+
2110
+ >>> otp.timedelta(datetime.timedelta(days=2, hours=3))
2111
+ timedelta('2 days 03:00:00')
2112
+
2113
+ >>> otp.timedelta('20 days 13:02:01.999777666')
2114
+ timedelta('20 days 13:02:01.999777666')
2115
+
2116
+ Adding :py:class:`otp.timedelta <onetick.py.timedelta>` object to :py:class:`otp.datetime <onetick.py.datetime>`:
2117
+
2118
+ >>> otp.datetime(2022, 1, 1, 1, 2, 3) + otp.timedelta(days=1, hours=1, minutes=1, seconds=1)
2119
+ 2022-01-02 02:03:04
2120
+
2121
+ Adding :py:class:`otp.timedelta <onetick.py.timedelta>` object to :py:class:`otp.date <onetick.py.date>`:
2122
+
2123
+ >>> otp.date(2022, 1, 1) + otp.timedelta(weeks=1, nanoseconds=1)
2124
+ 2022-01-08 00:00:00.000000001
2125
+ """
2126
+
2127
+ def __repr__(self):
2128
+ return super().__repr__().lower()
2129
+
2130
+ def _get_offset(self):
2131
+ return self.value, 'nanosecond'