onetick-py 1.162.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. locator_parser/__init__.py +0 -0
  2. locator_parser/acl.py +73 -0
  3. locator_parser/actions.py +266 -0
  4. locator_parser/common.py +365 -0
  5. locator_parser/io.py +41 -0
  6. locator_parser/locator.py +150 -0
  7. onetick/__init__.py +101 -0
  8. onetick/doc_utilities/__init__.py +3 -0
  9. onetick/doc_utilities/napoleon.py +40 -0
  10. onetick/doc_utilities/ot_doctest.py +140 -0
  11. onetick/doc_utilities/snippets.py +280 -0
  12. onetick/lib/__init__.py +4 -0
  13. onetick/lib/instance.py +138 -0
  14. onetick/py/__init__.py +290 -0
  15. onetick/py/_stack_info.py +89 -0
  16. onetick/py/_version.py +2 -0
  17. onetick/py/aggregations/__init__.py +11 -0
  18. onetick/py/aggregations/_base.py +645 -0
  19. onetick/py/aggregations/_docs.py +912 -0
  20. onetick/py/aggregations/compute.py +286 -0
  21. onetick/py/aggregations/functions.py +2216 -0
  22. onetick/py/aggregations/generic.py +104 -0
  23. onetick/py/aggregations/high_low.py +80 -0
  24. onetick/py/aggregations/num_distinct.py +83 -0
  25. onetick/py/aggregations/order_book.py +427 -0
  26. onetick/py/aggregations/other.py +1014 -0
  27. onetick/py/backports.py +26 -0
  28. onetick/py/cache.py +373 -0
  29. onetick/py/callback/__init__.py +5 -0
  30. onetick/py/callback/callback.py +275 -0
  31. onetick/py/callback/callbacks.py +131 -0
  32. onetick/py/compatibility.py +752 -0
  33. onetick/py/configuration.py +736 -0
  34. onetick/py/core/__init__.py +0 -0
  35. onetick/py/core/_csv_inspector.py +93 -0
  36. onetick/py/core/_internal/__init__.py +0 -0
  37. onetick/py/core/_internal/_manually_bound_value.py +6 -0
  38. onetick/py/core/_internal/_nodes_history.py +250 -0
  39. onetick/py/core/_internal/_op_utils/__init__.py +0 -0
  40. onetick/py/core/_internal/_op_utils/every_operand.py +9 -0
  41. onetick/py/core/_internal/_op_utils/is_const.py +10 -0
  42. onetick/py/core/_internal/_per_tick_scripts/tick_list_sort_template.script +121 -0
  43. onetick/py/core/_internal/_proxy_node.py +140 -0
  44. onetick/py/core/_internal/_state_objects.py +2307 -0
  45. onetick/py/core/_internal/_state_vars.py +87 -0
  46. onetick/py/core/_source/__init__.py +0 -0
  47. onetick/py/core/_source/_symbol_param.py +95 -0
  48. onetick/py/core/_source/schema.py +97 -0
  49. onetick/py/core/_source/source_methods/__init__.py +0 -0
  50. onetick/py/core/_source/source_methods/aggregations.py +810 -0
  51. onetick/py/core/_source/source_methods/applyers.py +296 -0
  52. onetick/py/core/_source/source_methods/columns.py +141 -0
  53. onetick/py/core/_source/source_methods/data_quality.py +301 -0
  54. onetick/py/core/_source/source_methods/debugs.py +270 -0
  55. onetick/py/core/_source/source_methods/drops.py +120 -0
  56. onetick/py/core/_source/source_methods/fields.py +619 -0
  57. onetick/py/core/_source/source_methods/filters.py +1001 -0
  58. onetick/py/core/_source/source_methods/joins.py +1393 -0
  59. onetick/py/core/_source/source_methods/merges.py +566 -0
  60. onetick/py/core/_source/source_methods/misc.py +1325 -0
  61. onetick/py/core/_source/source_methods/pandases.py +155 -0
  62. onetick/py/core/_source/source_methods/renames.py +356 -0
  63. onetick/py/core/_source/source_methods/sorts.py +183 -0
  64. onetick/py/core/_source/source_methods/switches.py +142 -0
  65. onetick/py/core/_source/source_methods/symbols.py +117 -0
  66. onetick/py/core/_source/source_methods/times.py +627 -0
  67. onetick/py/core/_source/source_methods/writes.py +702 -0
  68. onetick/py/core/_source/symbol.py +202 -0
  69. onetick/py/core/_source/tmp_otq.py +222 -0
  70. onetick/py/core/column.py +209 -0
  71. onetick/py/core/column_operations/__init__.py +0 -0
  72. onetick/py/core/column_operations/_methods/__init__.py +4 -0
  73. onetick/py/core/column_operations/_methods/_internal.py +28 -0
  74. onetick/py/core/column_operations/_methods/conversions.py +215 -0
  75. onetick/py/core/column_operations/_methods/methods.py +294 -0
  76. onetick/py/core/column_operations/_methods/op_types.py +150 -0
  77. onetick/py/core/column_operations/accessors/__init__.py +0 -0
  78. onetick/py/core/column_operations/accessors/_accessor.py +30 -0
  79. onetick/py/core/column_operations/accessors/decimal_accessor.py +92 -0
  80. onetick/py/core/column_operations/accessors/dt_accessor.py +464 -0
  81. onetick/py/core/column_operations/accessors/float_accessor.py +160 -0
  82. onetick/py/core/column_operations/accessors/str_accessor.py +1374 -0
  83. onetick/py/core/column_operations/base.py +1061 -0
  84. onetick/py/core/cut_builder.py +149 -0
  85. onetick/py/core/db_constants.py +20 -0
  86. onetick/py/core/eval_query.py +244 -0
  87. onetick/py/core/lambda_object.py +442 -0
  88. onetick/py/core/multi_output_source.py +193 -0
  89. onetick/py/core/per_tick_script.py +2253 -0
  90. onetick/py/core/query_inspector.py +465 -0
  91. onetick/py/core/source.py +1663 -0
  92. onetick/py/db/__init__.py +2 -0
  93. onetick/py/db/_inspection.py +1042 -0
  94. onetick/py/db/db.py +1423 -0
  95. onetick/py/db/utils.py +64 -0
  96. onetick/py/docs/__init__.py +0 -0
  97. onetick/py/docs/docstring_parser.py +112 -0
  98. onetick/py/docs/utils.py +81 -0
  99. onetick/py/functions.py +2354 -0
  100. onetick/py/license.py +188 -0
  101. onetick/py/log.py +88 -0
  102. onetick/py/math.py +947 -0
  103. onetick/py/misc.py +437 -0
  104. onetick/py/oqd/__init__.py +22 -0
  105. onetick/py/oqd/eps.py +1195 -0
  106. onetick/py/oqd/sources.py +325 -0
  107. onetick/py/otq.py +211 -0
  108. onetick/py/pyomd_mock.py +47 -0
  109. onetick/py/run.py +841 -0
  110. onetick/py/servers.py +173 -0
  111. onetick/py/session.py +1342 -0
  112. onetick/py/sources/__init__.py +19 -0
  113. onetick/py/sources/cache.py +167 -0
  114. onetick/py/sources/common.py +126 -0
  115. onetick/py/sources/csv.py +642 -0
  116. onetick/py/sources/custom.py +85 -0
  117. onetick/py/sources/data_file.py +305 -0
  118. onetick/py/sources/data_source.py +1049 -0
  119. onetick/py/sources/empty.py +94 -0
  120. onetick/py/sources/odbc.py +337 -0
  121. onetick/py/sources/order_book.py +238 -0
  122. onetick/py/sources/parquet.py +168 -0
  123. onetick/py/sources/pit.py +191 -0
  124. onetick/py/sources/query.py +495 -0
  125. onetick/py/sources/snapshots.py +419 -0
  126. onetick/py/sources/split_query_output_by_symbol.py +198 -0
  127. onetick/py/sources/symbology_mapping.py +123 -0
  128. onetick/py/sources/symbols.py +357 -0
  129. onetick/py/sources/ticks.py +825 -0
  130. onetick/py/sql.py +70 -0
  131. onetick/py/state.py +256 -0
  132. onetick/py/types.py +2056 -0
  133. onetick/py/utils/__init__.py +70 -0
  134. onetick/py/utils/acl.py +93 -0
  135. onetick/py/utils/config.py +186 -0
  136. onetick/py/utils/default.py +49 -0
  137. onetick/py/utils/file.py +38 -0
  138. onetick/py/utils/helpers.py +76 -0
  139. onetick/py/utils/locator.py +94 -0
  140. onetick/py/utils/perf.py +499 -0
  141. onetick/py/utils/query.py +49 -0
  142. onetick/py/utils/render.py +1139 -0
  143. onetick/py/utils/script.py +244 -0
  144. onetick/py/utils/temp.py +471 -0
  145. onetick/py/utils/types.py +118 -0
  146. onetick/py/utils/tz.py +82 -0
  147. onetick_py-1.162.2.dist-info/METADATA +148 -0
  148. onetick_py-1.162.2.dist-info/RECORD +152 -0
  149. onetick_py-1.162.2.dist-info/WHEEL +5 -0
  150. onetick_py-1.162.2.dist-info/entry_points.txt +2 -0
  151. onetick_py-1.162.2.dist-info/licenses/LICENSE +21 -0
  152. onetick_py-1.162.2.dist-info/top_level.txt +2 -0
onetick/py/types.py ADDED
@@ -0,0 +1,2056 @@
1
+ import ctypes
2
+ import functools
3
+ import inspect
4
+ import os
5
+ import warnings
6
+ from typing import Optional, Type, Union
7
+ from packaging.version import parse as parse_version
8
+
9
+ import pandas as pd
10
+ import numpy as np
11
+ from datetime import date as _date
12
+ from datetime import datetime as _datetime
13
+
14
+ from pandas.tseries import offsets
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 (type(item) is not 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_str(type):
712
+ def __str__(cls):
713
+ return 'decimal'
714
+
715
+
716
+ class decimal(float, metaclass=_decimal_str):
717
+ """
718
+ Object that represents decimal OneTick value.
719
+ Decimal is 128 bit base 10 floating point number.
720
+
721
+ Examples
722
+ --------
723
+ >>> t = otp.Ticks({'A': [otp.decimal(1), otp.decimal(2)]})
724
+ >>> t['B'] = otp.decimal(1.23456789)
725
+ >>> t['C'] = t['A'] / 0
726
+ >>> t['D'] = t['A'] + otp.nan
727
+ >>> otp.run(t)
728
+ Time A B C D
729
+ 0 2003-12-01 00:00:00.000 1.0 1.234568 inf NaN
730
+ 1 2003-12-01 00:00:00.001 2.0 1.234568 inf NaN
731
+ """
732
+ def __wrap(self, res):
733
+ # if parent class doesn't support some operation, it returns NotImplemented and so do we
734
+ # In other case we wrap float result with our decimal class
735
+ if isinstance(res, type(NotImplemented)):
736
+ return NotImplemented
737
+ return self.__class__(res)
738
+
739
+ def __add__(self, other):
740
+ return self.__wrap(super().__add__(other))
741
+
742
+ def __radd__(self, other):
743
+ return self.__wrap(super().__radd__(other))
744
+
745
+ def __sub__(self, other):
746
+ return self.__wrap(super().__sub__(other))
747
+
748
+ def __rsub__(self, other):
749
+ return self.__wrap(super().__rsub__(other))
750
+
751
+ def __mul__(self, other):
752
+ return self.__wrap(super().__mul__(other))
753
+
754
+ def __rmul__(self, other):
755
+ return self.__wrap(super().__rmul__(other))
756
+
757
+ def __truediv__(self, other):
758
+ return self.__wrap(super().__truediv__(other))
759
+
760
+ def __rtruediv__(self, other):
761
+ return self.__wrap(super().__rtruediv__(other))
762
+
763
+ def __str__(self):
764
+ return super().__repr__()
765
+
766
+ def __repr__(self):
767
+ return f"{self.__class__.__name__}({self})"
768
+
769
+ # --------------------------------------------------------------- #
770
+ # AUXILIARY FUNCTIONS
771
+ # --------------------------------------------------------------- #
772
+
773
+
774
+ def is_type_basic(dtype):
775
+ return dtype in (
776
+ int,
777
+ float,
778
+ str,
779
+ byte,
780
+ short,
781
+ uint,
782
+ ulong,
783
+ _int,
784
+ long,
785
+ nsectime,
786
+ msectime,
787
+ decimal,
788
+ ) or issubclass(dtype, string)
789
+
790
+
791
+ # TODO: PY-632: unify these functions with others
792
+ def get_source_base_type(value):
793
+ if inspect.isclass(value):
794
+ value_type = value
795
+ if not is_type_basic(value_type):
796
+ warnings.warn('Setting schema with complex types is deprecated,'
797
+ ' use basic type instead', FutureWarning, stacklevel=2)
798
+ else:
799
+ warnings.warn('Setting schema with instance of the class is deprecated,'
800
+ ' use type instead', FutureWarning, stacklevel=2)
801
+ value_type = type(value)
802
+ # convert string to custom string if necessary
803
+ if value_type is str and len(value) > string.DEFAULT_LENGTH:
804
+ value_type = string[len(value)]
805
+
806
+ if issubclass(value_type, bool):
807
+ value_type = float
808
+
809
+ if is_time_type(value_type):
810
+ value_type = nsectime
811
+
812
+ # check valid value type
813
+ if get_base_type(value_type) not in [int, float, str, bool]:
814
+ raise TypeError(f'Type "{repr(value_type)}" is not supported.')
815
+
816
+ if not is_type_basic(value_type):
817
+ raise TypeError(f"Type {repr(value_type)} can't be set in schema.")
818
+ return value_type
819
+
820
+
821
+ def is_type_supported(dtype):
822
+ return get_base_type(dtype) in [int, float, str, bool] or issubclass(dtype, (datetime, date))
823
+
824
+
825
+ def get_base_type(obj):
826
+ if issubclass(obj, str):
827
+ return str
828
+ elif issubclass(obj, bool):
829
+ return bool
830
+ elif issubclass(obj, int):
831
+ return int
832
+ elif issubclass(obj, float):
833
+ return float
834
+
835
+ return type(None)
836
+
837
+
838
+ def get_object_type(obj):
839
+ if isinstance(obj, (_nan, _inf)):
840
+ return float
841
+ if isinstance(obj, Type):
842
+ return obj
843
+ if hasattr(obj, 'dtype'):
844
+ dtype = obj.dtype
845
+ if isinstance(dtype, np.dtype):
846
+ return dtype.type
847
+ return dtype
848
+ if is_time_type(obj):
849
+ return nsectime
850
+ if type(obj) is int and obj > long.MAX:
851
+ # by default we use python's int (onetick's long) for numbers
852
+ # in case the number is too big, let's use onetick's ulong
853
+ return ulong
854
+ return type(obj)
855
+
856
+
857
+ def get_type_by_objects(objs):
858
+ """
859
+ Helper that calculates the widest type of the list passed objects.
860
+ Used to determine type by returned values.
861
+ """
862
+
863
+ # collect types
864
+ types = set()
865
+ for v in objs:
866
+ t = get_object_type(v)
867
+ if issubclass(t, str):
868
+ t = str
869
+ types.add(t)
870
+
871
+ # does not allow to mix string and numeric types
872
+ dtype = None
873
+ if str in types and (float in types or int in types or bool in types or nsectime in types or msectime in types):
874
+ raise TypeError("It is not allowed to return values of string type and numeric type in one function.")
875
+
876
+ # if there is only one value there, then
877
+ # use it as is
878
+ if len(types) == 1:
879
+ dtype = next(iter(types))
880
+ if dtype is bool:
881
+ return dtype
882
+
883
+ # process numeric types: the most generic is float
884
+ if int in types:
885
+ dtype = int
886
+ if bool in types:
887
+ dtype = float
888
+ # None is equal to otp.nan
889
+ if float in types or type(None) in types:
890
+ dtype = float
891
+
892
+ # process string types, taking into account OneTick long strings
893
+ if str in types:
894
+ max_len = string.DEFAULT_LENGTH
895
+ for v in objs:
896
+ t = get_object_type(v)
897
+ if issubclass(t, string):
898
+ if t.length is Ellipsis or max_len is Ellipsis:
899
+ max_len = Ellipsis
900
+ else:
901
+ max_len = max(t.length, max_len)
902
+ elif isinstance(v, str):
903
+ max_len = max(len(v), max_len)
904
+
905
+ if max_len == string.DEFAULT_LENGTH:
906
+ dtype = str
907
+ else:
908
+ dtype = string[max_len] # pylint: disable=E1136
909
+
910
+ # process msectime and nsectime
911
+ if dtype is float and (msectime in types or nsectime in types):
912
+ raise TypeError("It is not allowed to return value of time type and float type in one function.")
913
+
914
+ if msectime in types:
915
+ dtype = msectime
916
+ if nsectime in types:
917
+ dtype = nsectime
918
+
919
+ # we assume the None value has float default value, ie NaN
920
+ if type(None) is dtype:
921
+ dtype = float
922
+
923
+ return dtype
924
+
925
+
926
+ # ------------------- #
927
+ # extend datetime
928
+
929
+
930
+ class AbstractTime:
931
+ def __init__(self):
932
+ self.ts: pd.Timestamp
933
+
934
+ @property
935
+ def year(self):
936
+ return self.ts.year
937
+
938
+ @property
939
+ def month(self):
940
+ return self.ts.month
941
+
942
+ @property
943
+ def day(self):
944
+ return self.ts.day
945
+
946
+ def date(self):
947
+ return _date(self.year, self.month, self.day)
948
+
949
+ @property
950
+ def start(self):
951
+ return pd.Timestamp(self.year, self.month, self.day)
952
+
953
+ @property
954
+ def end(self):
955
+ return pd.Timestamp(next_day(self.start))
956
+
957
+ def strftime(self, fmt):
958
+ return self.ts.strftime(fmt)
959
+
960
+ @property
961
+ def value(self):
962
+ return self.ts.value
963
+
964
+ def timestamp(self):
965
+ return self.ts.timestamp()
966
+
967
+ def __eq__(self, other):
968
+ other = getattr(other, "ts", other)
969
+ return self.ts == other
970
+
971
+ def __hash__(self):
972
+ return hash(self.ts)
973
+
974
+ def __gt__(self, other):
975
+ other = getattr(other, "ts", other)
976
+ return self.ts > other
977
+
978
+ def __ge__(self, other):
979
+ other = getattr(other, "ts", other)
980
+ return self.ts >= other
981
+
982
+ def __lt__(self, other):
983
+ other = getattr(other, "ts", other)
984
+ return self.ts < other
985
+
986
+ def __le__(self, other):
987
+ other = getattr(other, "ts", other)
988
+ return self.ts <= other
989
+
990
+
991
+ class datetime(AbstractTime):
992
+ """
993
+ Class :py:class:`otp.datetime <onetick.py.datetime>` is used for representing date with time in onetick-py.
994
+ It can be used both when specifying start and end time for queries and
995
+ in column operations with :py:class:`onetick.py.Source`.
996
+ :ref:`Datetime offset objects <datetime_offsets>` (e.g. `otp.Nano`, `otp.Day`)
997
+ can be added to or subtracted from `otp.datetime` object.
998
+
999
+ Note
1000
+ ----
1001
+ Class :py:class:`otp.datetime <onetick.py.datetime>` share many methods
1002
+ that classes :pandas:`pandas.Timestamp` and :py:class:`datetime.datetime` have,
1003
+ but these objects are not fully interchangeable.
1004
+ Class :py:class:`otp.datetime <onetick.py.datetime>` should work in all onetick-py methods and classes,
1005
+ other classes should work too if documented,
1006
+ and may even work when not documented, but the users should not count on it.
1007
+
1008
+ Parameters
1009
+ ----------
1010
+ first_arg: int, str, :py:class:`otp.datetime <onetick.py.datetime>`, :py:class:`otp.date <onetick.py.date>`\
1011
+ :pandas:`pandas.Timestamp`, :py:class:`datetime.datetime`
1012
+ If `month`, `day` and other parts of date are specified,
1013
+ first argument will be considered as year.
1014
+ Otherwise, first argument will be converted to :py:class:`otp.datetime <onetick.py.datetime>`.
1015
+ month: int
1016
+ Number between 1 and 12.
1017
+ day: int
1018
+ Number between 1 and 31.
1019
+ hour: int, default=0
1020
+ Number between 0 and 23.
1021
+ minute: int, default=0
1022
+ Number between 0 and 59.
1023
+ second: int, default=0
1024
+ Number between 0 and 59.
1025
+ microsecond: int, default=0
1026
+ Number between 0 and 999999.
1027
+ nanosecond: int, default=0
1028
+ Number between 0 and 999.
1029
+ tzinfo: :py:class:`datetime.tzinfo`
1030
+ Timezone object.
1031
+ tz: str
1032
+ Timezone name.
1033
+
1034
+ Examples
1035
+ --------
1036
+
1037
+ Initialization by :py:class:`datetime.datetime` class from standard library:
1038
+
1039
+ >>> otp.datetime(datetime.datetime(2019, 1, 1, 1))
1040
+ 2019-01-01 01:00:00
1041
+
1042
+ Initialization by :pandas:`pandas.Timestamp` class:
1043
+
1044
+ >>> otp.datetime(pd.Timestamp(2019, 1, 1, 1))
1045
+ 2019-01-01 01:00:00
1046
+
1047
+ Initialization by int timestamp:
1048
+
1049
+ >>> otp.datetime(1234567890)
1050
+ 1970-01-01 00:00:01.234567890
1051
+
1052
+ Initialization by params with nanoseconds:
1053
+
1054
+ >>> otp.datetime(2019, 1, 1, 1, 2, 3, 4, 5)
1055
+ 2019-01-01 01:02:03.000004005
1056
+
1057
+ Initialization by string:
1058
+
1059
+ >>> otp.datetime('2019/01/01 1:02')
1060
+ 2019-01-01 01:02:00
1061
+
1062
+ `otp.dt` is the alias for `otp.datetime`:
1063
+
1064
+ >>> otp.dt(2019, 1, 1)
1065
+ 2019-01-01 00:00:00
1066
+
1067
+ See also
1068
+ --------
1069
+ :ref:`Datetime offset objects <datetime_guide>`.
1070
+ """
1071
+ def __init__(
1072
+ self,
1073
+ first_arg,
1074
+ month=None,
1075
+ day=None,
1076
+ hour=None,
1077
+ minute=None,
1078
+ second=None,
1079
+ microsecond=None,
1080
+ nanosecond=None,
1081
+ *,
1082
+ tzinfo=None,
1083
+ tz=None,
1084
+ ): # TODO: python 3.8 change first_arg to positional only arg
1085
+ tz, tzinfo = self._process_timezones_args(tz, tzinfo)
1086
+
1087
+ if not any([month, day, hour, minute, second, microsecond, nanosecond]):
1088
+ result = self._create_from_one_arg(first_arg, tz, tzinfo)
1089
+ else:
1090
+ result = self._create_from_several_arg(first_arg, month, day, hour, minute, second, microsecond, nanosecond,
1091
+ tzinfo)
1092
+ self.ts = result
1093
+
1094
+ def _process_timezones_args(self, tz, tzinfo):
1095
+ if tz is not None:
1096
+ if tzinfo is None:
1097
+ tzinfo = get_tzfile_by_name(tz) # pandas is broken https://github.com/pandas-dev/pandas/issues/31929
1098
+ tz = None
1099
+ else:
1100
+ raise ValueError(
1101
+ "tzinfo and tz params are mutually exclusive parameters, "
1102
+ "they can't be specified both at the same time"
1103
+ )
1104
+ return tz, tzinfo
1105
+
1106
+ def _create_from_several_arg(self, first_arg, month, day, hour, minute, second, microsecond, nanosecond, tzinfo):
1107
+ if nanosecond is not None and not (0 <= nanosecond <= 999):
1108
+ raise ValueError(
1109
+ "Nanosecond parameter should be between 0 and 999. "
1110
+ "Please use microsecond parameter or otp.Nano object."
1111
+ )
1112
+ if parse_version(pd.__version__) >= parse_version("2.0.0"):
1113
+ result = pd.Timestamp(
1114
+ first_arg, month, day, hour or 0, minute or 0, second or 0, microsecond or 0,
1115
+ nanosecond=nanosecond or 0,
1116
+ ).replace(tzinfo=tzinfo)
1117
+ else:
1118
+ result = pd.Timestamp(
1119
+ first_arg, month, day, hour or 0, minute or 0, second or 0, microsecond or 0, nanosecond or 0,
1120
+ ).replace(tzinfo=tzinfo)
1121
+ return result
1122
+
1123
+ def _create_from_one_arg(self, first_arg, tz, tzinfo):
1124
+ arg_tz = getattr(first_arg, "tz", None)
1125
+ arg_tzinfo = getattr(first_arg, "tzinfo", None)
1126
+ if tz and arg_tz and arg_tz != tz or tzinfo and arg_tzinfo and arg_tzinfo != tzinfo:
1127
+ raise ValueError(
1128
+ "You've specified the timezone for the object, which already has it. "
1129
+ "It is recommended to swap the current timezone to desired by method of this object "
1130
+ "and then create otp.datetime object."
1131
+ )
1132
+ if isinstance(first_arg, (datetime, date)):
1133
+ first_arg = first_arg.ts
1134
+ result = pd.Timestamp(first_arg, tzinfo=tzinfo, tz=tz)
1135
+ return result
1136
+
1137
+ @property
1138
+ def start(self):
1139
+ return super().start.replace(tzinfo=self.tzinfo)
1140
+
1141
+ @property
1142
+ def end(self):
1143
+ return super().end.replace(tzinfo=self.tzinfo)
1144
+
1145
+ def replace(self, **kwargs):
1146
+ """
1147
+ Replace parts of `otp.datetime` object.
1148
+
1149
+ Parameters
1150
+ ----------
1151
+ year: int, optional
1152
+ month: int, optional
1153
+ day: int, optional
1154
+ hour: int, optional
1155
+ minute: int, optional
1156
+ second: int, optional
1157
+ microsecond: int, optional
1158
+ nanosecond: int, optional
1159
+ tzinfo: tz-convertible, optional
1160
+
1161
+ Returns
1162
+ -------
1163
+ result: :py:class:`otp.datetime <onetick.py.datetime>`
1164
+ Timestamp with fields replaced.
1165
+
1166
+ Examples
1167
+ --------
1168
+ >>> ts = otp.datetime(2022, 2, 24, 3, 15, 54, 999, 1)
1169
+ >>> ts
1170
+ 2022-02-24 03:15:54.000999001
1171
+ >>> ts.replace(year=2000, month=2, day=2, hour=2, minute=2, second=2, microsecond=2, nanosecond=2)
1172
+ 2000-02-02 02:02:02.000002002
1173
+ """
1174
+ return datetime(self.ts.replace(**kwargs))
1175
+
1176
+ @property
1177
+ def tz(self):
1178
+ return self.ts.tz
1179
+
1180
+ @property
1181
+ def tzinfo(self):
1182
+ return self.ts.tzinfo
1183
+
1184
+ @property
1185
+ def hour(self):
1186
+ return self.ts.hour
1187
+
1188
+ @property
1189
+ def minute(self):
1190
+ return self.ts.minute
1191
+
1192
+ @property
1193
+ def second(self):
1194
+ return self.ts.second
1195
+
1196
+ @property
1197
+ def microsecond(self):
1198
+ return self.ts.microsecond
1199
+
1200
+ @property
1201
+ def nanosecond(self):
1202
+ return self.ts.nanosecond
1203
+
1204
+ @staticmethod
1205
+ def now(tz=None):
1206
+ """
1207
+ Will return :py:class:`otp.datetime <onetick.py.datetime>` object
1208
+ with timestamp at the moment of calling this function.
1209
+ Not to be confused with function :func:`otp.now <onetick.py.now>` which can only add column
1210
+ with current timestamp to the :py:class:`otp.Source <onetick.py.Source>` when running the query.
1211
+
1212
+ Parameters
1213
+ ----------
1214
+ tz : str or timezone object, default None
1215
+ Timezone to localize to.
1216
+ """
1217
+ return datetime(pd.Timestamp.now(tz))
1218
+
1219
+ def __add__(self, other):
1220
+ """
1221
+ Add :ref:`datetime offset <datetime_offsets>` to otp.datetime.
1222
+
1223
+ Parameters
1224
+ ----------
1225
+ other: :ref:`datetime offset <datetime_offsets>`, :py:class:`otp.datetime <onetick.py.datetime>`
1226
+ object to add
1227
+
1228
+ Returns
1229
+ -------
1230
+ result: :py:class:`otp.datetime <onetick.py.datetime>`, :pandas:`pandas.Timedelta`
1231
+ return :py:class:`otp.datetime <onetick.py.datetime>`
1232
+ if otp.Nano or another :ref:`datetime offset <datetime_offsets>` object was passed as an argument,
1233
+ or :pandas:`pandas.Timedelta` object if :py:class:`otp.datetime <onetick.py.datetime>`
1234
+ was passed as an argument.
1235
+
1236
+ Examples
1237
+ --------
1238
+ >>> otp.datetime(2022, 2, 24) + otp.Nano(1)
1239
+ 2022-02-24 00:00:00.000000001
1240
+ """
1241
+ self._error_on_int_param(other, "+")
1242
+ return datetime(self.ts + other)
1243
+
1244
+ def __sub__(self, other):
1245
+ """
1246
+ Subtract :ref:`datetime offset <datetime_offsets>` from otp.datetime.
1247
+
1248
+ Parameters
1249
+ ----------
1250
+ other: :ref:`datetime offset <datetime_offsets>`, :py:class:`otp.datetime <onetick.py.datetime>`
1251
+ object to subtract
1252
+
1253
+ Returns
1254
+ -------
1255
+ result: :py:class:`otp.datetime <onetick.py.datetime>`, :pandas:`pandas.Timedelta`
1256
+ return datetime if otp.Nano or another :ref:`datetime offset <datetime_offsets>`
1257
+ object was passed as an argument,
1258
+ or :pandas:`pandas.Timedelta` object if :py:class:`otp.datetime <onetick.py.datetime>`
1259
+ was passed as an argument.
1260
+
1261
+ Examples
1262
+ --------
1263
+ >>> otp.datetime(2022, 2, 24) - otp.Nano(1)
1264
+ 2022-02-23 23:59:59.999999999
1265
+ """
1266
+ self._error_on_int_param(other, "-")
1267
+ other = getattr(other, "ts", other)
1268
+ result = self.ts - other
1269
+ # do not convert to datetime in case timedelta is returned (arg is date)
1270
+ result = datetime(result) if isinstance(result, pd.Timestamp) else result
1271
+ return result
1272
+
1273
+ def _error_on_int_param(self, other, op):
1274
+ if type(other) is int:
1275
+ raise TypeError(f"unsupported operand type(s) for {op}: 'otp.datetime' and 'int'")
1276
+
1277
+ def __str__(self):
1278
+ return str(self.ts)
1279
+
1280
+ def __repr__(self):
1281
+ return str(self.ts)
1282
+
1283
+ def tz_localize(self, tz):
1284
+ """
1285
+ Localize tz-naive datetime object to a given timezone
1286
+
1287
+ Parameters
1288
+ ----------
1289
+ tz: str or tzinfo
1290
+ timezone to localize datetime object into
1291
+
1292
+ Returns
1293
+ -------
1294
+ result: :py:class:`otp.datetime <onetick.py.datetime>`
1295
+ localized datetime object
1296
+
1297
+ Examples
1298
+ --------
1299
+ >>> d = otp.datetime(2021, 6, 3)
1300
+ >>> d.tz_localize("EST5EDT")
1301
+ 2021-06-03 00:00:00-04:00
1302
+ """
1303
+ return datetime(self.ts.tz_localize(tz))
1304
+
1305
+ def tz_convert(self, tz):
1306
+ """
1307
+ Convert tz-aware datetime object to another timezone
1308
+
1309
+ Parameters
1310
+ ----------
1311
+ tz: str or tzinfo
1312
+ timezone to convert datetime object into
1313
+
1314
+ Returns
1315
+ -------
1316
+ result: :py:class:`otp.datetime <onetick.py.datetime>`
1317
+ converted datetime object
1318
+
1319
+ Examples
1320
+ --------
1321
+ >>> d = otp.datetime(2021, 6, 3, tz="EST5EDT")
1322
+ >>> d.tz_convert("Europe/Moscow")
1323
+ 2021-06-03 07:00:00+03:00
1324
+ """
1325
+ return datetime(self.ts.tz_convert(tz))
1326
+
1327
+ def to_operation(self, timezone=None):
1328
+ """
1329
+ Convert :py:class:`otp.datetime <onetick.py.datetime>` object to
1330
+ :py:class:`otp.Operation <onetick.py.Operation>`
1331
+
1332
+ Parameters
1333
+ ----------
1334
+ timezone: Operation
1335
+ Can be used to specify timezone as an Operation.
1336
+
1337
+ Examples
1338
+ --------
1339
+ >>> t = otp.Ticks(TZ=['EST5EDT', 'GMT'])
1340
+ >>> t['DT'] = otp.dt(2022, 1, 1).to_operation(timezone=t['TZ'])
1341
+ >>> otp.run(t, timezone='GMT')[['TZ', 'DT']]
1342
+ TZ DT
1343
+ 0 EST5EDT 2022-01-01 05:00:00
1344
+ 1 GMT 2022-01-01 00:00:00
1345
+ """
1346
+ return otp.Operation(op_str=otp.types.datetime2expr(self, timezone=timezone), dtype=otp.nsectime)
1347
+
1348
+
1349
+ dt = datetime
1350
+
1351
+
1352
+ class date(datetime):
1353
+ """
1354
+ Class ``date`` is used for representing date in onetick-py.
1355
+ It can be used both when specifying start and end time for queries and
1356
+ in column operations with :py:class:`onetick.py.Source`.
1357
+
1358
+ Note
1359
+ ----
1360
+ Class :py:class:`otp.date <onetick.py.date>` share many methods
1361
+ that classes :pandas:`pandas.Timestamp` and :py:class:`datetime.date` have,
1362
+ but these objects are not fully interchangeable.
1363
+ Class :py:class:`otp.date <onetick.py.date>` should work in all onetick-py methods and classes,
1364
+ other classes should work too if documented,
1365
+ and may even work when not documented, but the users should not count on it.
1366
+
1367
+ Parameters
1368
+ ----------
1369
+ first_arg: int, str, :py:class:`otp.datetime <onetick.py.datetime>`, :py:class:`otp.date <onetick.py.date>`\
1370
+ :pandas:`pandas.Timestamp`, :py:class:`datetime.datetime`, :py:class:`datetime.date`
1371
+ If `month` and `day` arguments are specified, first argument will be considered as year.
1372
+ Otherwise, first argument will be converted to otp.date.
1373
+ month: int
1374
+ Number between 1 and 12.
1375
+ day: int
1376
+ Number between 1 and 31.
1377
+
1378
+ Examples
1379
+ --------
1380
+ :ref:`Datetime guide <datetime_guide>`.
1381
+ """
1382
+
1383
+ def __init__(self, first_arg: Union[int, str, _date, _datetime, pd.Timestamp, AbstractTime],
1384
+ month=None, day=None):
1385
+ if month is None and day is None:
1386
+ if isinstance(first_arg, AbstractTime):
1387
+ first_arg = first_arg.ts
1388
+ elif isinstance(first_arg, (int, str)):
1389
+ first_arg = pd.Timestamp(first_arg)
1390
+ if isinstance(first_arg, (_datetime, pd.Timestamp, datetime)):
1391
+ first_arg = first_arg.date()
1392
+ self.ts = pd.Timestamp(first_arg) # remove hour, minutes and so on
1393
+ elif all((month, day)):
1394
+ self.ts = pd.Timestamp(first_arg, month, day)
1395
+ else:
1396
+ raise ValueError("Please specify three integers (year, month, day) "
1397
+ "or object or create date from (string, int timestamp, "
1398
+ "pandas.Timestamp, otp.datetime, otp.date, "
1399
+ "datetime.datetime, datetime.date)")
1400
+
1401
+ def __str__(self):
1402
+ return self.ts.strftime("%Y-%m-%d")
1403
+
1404
+ def __repr__(self):
1405
+ return self.ts.strftime("%Y-%m-%d")
1406
+
1407
+ def to_str(self, format="%Y%m%d"):
1408
+ """
1409
+ Convert date to string, by default it will be in YYYYMMDD format.
1410
+
1411
+ Parameters
1412
+ ----------
1413
+ format: str
1414
+ strftime format of string to convert to.
1415
+ Returns
1416
+ -------
1417
+ result: str
1418
+ """
1419
+ return self.ts.strftime(format)
1420
+
1421
+
1422
+ class _integer_meta(type):
1423
+ def __str__(cls):
1424
+ return getattr(cls, '_NAME', cls.__name__)
1425
+
1426
+ @property
1427
+ def TYPE_SIZE(cls):
1428
+ return 8 * ctypes.sizeof(cls._CTYPE)
1429
+
1430
+ @property
1431
+ def MIN(cls):
1432
+ if cls._UNSIGNED:
1433
+ return 0
1434
+ else:
1435
+ return -(2 ** (cls.TYPE_SIZE - 1))
1436
+
1437
+ @property
1438
+ def MAX(cls):
1439
+ if cls._UNSIGNED:
1440
+ return (2 ** cls.TYPE_SIZE) - 1
1441
+ else:
1442
+ return (2 ** (cls.TYPE_SIZE - 1)) - 1
1443
+
1444
+
1445
+ class _integer(int, metaclass=_integer_meta):
1446
+ def __new__(cls, value, *args, **kwargs):
1447
+ if not cls.MIN <= value <= cls.MAX:
1448
+ raise ValueError(f"{cls.__name__} values must be between {cls.MIN} and {cls.MAX}")
1449
+ return super().__new__(cls, value, *args, **kwargs)
1450
+
1451
+ def __get_result(self, value):
1452
+ if isinstance(value, int):
1453
+ return self.__class__(self._CTYPE(value).value)
1454
+ return value
1455
+
1456
+ def __add__(self, other):
1457
+ return self.__get_result(
1458
+ super().__add__(other)
1459
+ )
1460
+
1461
+ def __radd__(self, other):
1462
+ return self.__get_result(
1463
+ super().__radd__(other)
1464
+ )
1465
+
1466
+ def __sub__(self, other):
1467
+ return self.__get_result(
1468
+ super().__sub__(other)
1469
+ )
1470
+
1471
+ def __rsub__(self, other):
1472
+ return self.__get_result(
1473
+ super().__rsub__(other)
1474
+ )
1475
+
1476
+ def __mul__(self, other):
1477
+ return self.__get_result(
1478
+ super().__mul__(other)
1479
+ )
1480
+
1481
+ def __rmul__(self, other):
1482
+ return self.__get_result(
1483
+ super().__rmul__(other)
1484
+ )
1485
+
1486
+ def __truediv__(self, other):
1487
+ return self.__get_result(
1488
+ super().__truediv__(other)
1489
+ )
1490
+
1491
+ def __rtruediv__(self, other):
1492
+ return self.__get_result(
1493
+ super().__rtruediv__(other)
1494
+ )
1495
+
1496
+ def __str__(self):
1497
+ return super().__repr__()
1498
+
1499
+ def __repr__(self):
1500
+ return f"{self.__class__.__name__}({self})"
1501
+
1502
+
1503
+ class long(_integer):
1504
+ """
1505
+ OneTick data type representing signed long integer.
1506
+
1507
+ The size of the type is not specified and may vary across different systems.
1508
+ Most commonly it's a 8-byte type with allowed values from -2**63 to 2**63 - 1.
1509
+
1510
+ Note that the value is checked to be valid in constructor,
1511
+ but no overflow checking is done when arithmetic operations are performed.
1512
+
1513
+ Examples
1514
+ --------
1515
+ >>> t = otp.Tick(A=otp.long(1))
1516
+ >>> t['B'] = otp.long(1) + 1
1517
+ >>> t.schema
1518
+ {'A': <class 'onetick.py.types.long'>, 'B': <class 'onetick.py.types.long'>}
1519
+ """
1520
+ _CTYPE = ctypes.c_long
1521
+ _UNSIGNED = False
1522
+
1523
+
1524
+ class ulong(_integer):
1525
+ """
1526
+ OneTick data type representing unsigned long integer.
1527
+
1528
+ The size of the type is not specified and may vary across different systems.
1529
+ Most commonly it's a 8-byte type with allowed values from 0 to 2**64 - 1.
1530
+
1531
+ Note that the value is checked to be valid in constructor,
1532
+ but no overflow checking is done when arithmetic operations are performed.
1533
+
1534
+ Examples
1535
+ --------
1536
+ >>> t = otp.Tick(A=otp.ulong(1))
1537
+ >>> t['B'] = otp.ulong(1) + 1
1538
+ >>> t.schema
1539
+ {'A': <class 'onetick.py.types.ulong'>, 'B': <class 'onetick.py.types.ulong'>}
1540
+
1541
+ Note that arithmetic operations may result in overflow.
1542
+ Here we get 2**64 - 1 instead of -1.
1543
+
1544
+ .. testcode::
1545
+ :skipif: is_windows()
1546
+
1547
+ t = otp.Tick(A=otp.ulong(0) - 1)
1548
+ df = otp.run(t)
1549
+ print(df)
1550
+
1551
+ .. testoutput::
1552
+
1553
+ Time A
1554
+ 0 2003-12-01 18446744073709551615
1555
+ """
1556
+ _CTYPE = ctypes.c_ulong
1557
+ _UNSIGNED = True
1558
+
1559
+
1560
+ class _int(_integer):
1561
+ """
1562
+ OneTick data type representing signed integer.
1563
+
1564
+ The size of the type is not specified and may vary across different systems.
1565
+ Most commonly it's a 4-byte type with allowed values from -2**31 to 2**31 - 1.
1566
+
1567
+ Note that the value is checked to be valid in constructor,
1568
+ but no overflow checking is done when arithmetic operations are performed.
1569
+
1570
+ Examples
1571
+ --------
1572
+ >>> t = otp.Tick(A=otp.int(1))
1573
+ >>> t['B'] = otp.int(1) + 1
1574
+ >>> t.schema
1575
+ {'A': <class 'onetick.py.types._int'>, 'B': <class 'onetick.py.types._int'>}
1576
+ """
1577
+ _CTYPE = ctypes.c_int
1578
+ _UNSIGNED = False
1579
+ _NAME = 'int'
1580
+
1581
+
1582
+ class uint(_integer):
1583
+ """
1584
+ OneTick data type representing unsigned integer.
1585
+
1586
+ The size of the type is not specified and may vary across different systems.
1587
+ Most commonly it's a 4-byte type with allowed values from 0 to 2**32 - 1.
1588
+
1589
+ Note that the value is checked to be valid in constructor,
1590
+ but no overflow checking is done when arithmetic operations are performed.
1591
+
1592
+ Examples
1593
+ --------
1594
+ >>> t = otp.Tick(A=otp.uint(1))
1595
+ >>> t['B'] = otp.uint(1) + 1
1596
+ >>> t.schema
1597
+ {'A': <class 'onetick.py.types.uint'>, 'B': <class 'onetick.py.types.uint'>}
1598
+
1599
+ Note that arithmetic operations may result in overflow.
1600
+ Here we get 2**32 - 1 instead of -1.
1601
+
1602
+ .. testcode::
1603
+ :skipif: is_windows()
1604
+
1605
+ t = otp.Tick(A=otp.uint(0) - 1)
1606
+ df = otp.run(t)
1607
+ print(df)
1608
+
1609
+ .. testoutput::
1610
+
1611
+ Time A
1612
+ 0 2003-12-01 4294967295
1613
+ """
1614
+ _CTYPE = ctypes.c_uint
1615
+ _UNSIGNED = True
1616
+
1617
+
1618
+ class byte(_integer):
1619
+ """
1620
+ OneTick data type representing byte integer.
1621
+
1622
+ The size of the type is not specified and may vary across different systems.
1623
+ Most commonly it's a 1-byte type with allowed values from -128 to 127.
1624
+
1625
+ Note that the value is checked to be valid in constructor,
1626
+ but no overflow checking is done when arithmetic operations are performed.
1627
+
1628
+ Examples
1629
+ --------
1630
+ >>> t = otp.Tick(A=otp.byte(1))
1631
+ >>> t['B'] = otp.byte(1) + 1
1632
+ >>> t.schema
1633
+ {'A': <class 'onetick.py.types.byte'>, 'B': <class 'onetick.py.types.byte'>}
1634
+
1635
+ Note that arithmetic operations may result in overflow.
1636
+ Here we get 127 instead of -129.
1637
+
1638
+ >>> t = otp.Tick(A=otp.byte(-128) - 1)
1639
+ >>> otp.run(t)
1640
+ Time A
1641
+ 0 2003-12-01 127
1642
+ """
1643
+ _CTYPE = ctypes.c_byte
1644
+ _UNSIGNED = False
1645
+
1646
+
1647
+ class short(_integer):
1648
+ """
1649
+ OneTick data type representing short integer.
1650
+
1651
+ The size of the type is not specified and may vary across different systems.
1652
+ Most commonly it's a 2-byte type with allowed values from -32768 to 32767.
1653
+
1654
+ Note that the value is checked to be valid in constructor,
1655
+ but no overflow checking is done when arithmetic operations are performed.
1656
+
1657
+ Examples
1658
+ --------
1659
+ >>> t = otp.Tick(A=otp.short(1))
1660
+ >>> t['B'] = otp.short(1) + 1
1661
+ >>> t.schema
1662
+ {'A': <class 'onetick.py.types.short'>, 'B': <class 'onetick.py.types.short'>}
1663
+
1664
+ Note that arithmetic operations may result in overflow.
1665
+ Here we get 32767 instead of -32769.
1666
+
1667
+ >>> t = otp.Tick(A=otp.short(-32768) - 1)
1668
+ >>> otp.run(t)
1669
+ Time A
1670
+ 0 2003-12-01 32767
1671
+ """
1672
+ _CTYPE = ctypes.c_short
1673
+ _UNSIGNED = False
1674
+
1675
+
1676
+ # ------------------- #
1677
+ def type2str(t):
1678
+ if t is int:
1679
+ return "long"
1680
+ if t is str:
1681
+ return "string"
1682
+ if t is float:
1683
+ return "double"
1684
+ if t is None:
1685
+ return ''
1686
+ return str(t)
1687
+
1688
+
1689
+ def str2type(type_name: str):
1690
+ """
1691
+ Converts OneTick type by its name into Python/OTP domain type.
1692
+
1693
+ Parameters
1694
+ ----------
1695
+ type_name: str
1696
+ name of type from CSV or OneTick DB
1697
+
1698
+ Returns
1699
+ -------
1700
+ class:
1701
+ Python/OTP type representing OneTick type
1702
+ """
1703
+ if type_name == "time32":
1704
+ return int
1705
+ if type_name == "long":
1706
+ # otp.long can be used too, but we use int for backward compatibility
1707
+ return int
1708
+ if type_name == "int":
1709
+ return _int
1710
+ if type_name == "byte":
1711
+ return byte
1712
+ if type_name == "short":
1713
+ return short
1714
+ if type_name == "uint":
1715
+ return uint
1716
+ if type_name == "ulong":
1717
+ return ulong
1718
+ elif type_name in ["double", "float"]:
1719
+ return float
1720
+ elif type_name == "decimal":
1721
+ return decimal
1722
+ elif type_name == "msectime":
1723
+ return msectime
1724
+ elif type_name == "nsectime":
1725
+ return nsectime
1726
+ elif type_name in ["string", "matrix", f"string[{string.DEFAULT_LENGTH}]"]:
1727
+ return str
1728
+ elif type_name == "varstring":
1729
+ return varstring
1730
+ elif type_name.find("string") != -1:
1731
+ length = int(type_name[type_name.find("[") + 1:type_name.find("]")])
1732
+ return string[length]
1733
+ return None
1734
+
1735
+
1736
+ def type2np(t):
1737
+ if issubclass(t, str):
1738
+ if issubclass(t, otp.string) and type(t.length) is int:
1739
+ return '<U' + str(t.length)
1740
+ return '<U64'
1741
+ elif issubclass(t, bool):
1742
+ return 'boolean'
1743
+ elif issubclass(t, nsectime):
1744
+ return 'datetime64[ns]'
1745
+ elif issubclass(t, msectime):
1746
+ return 'datetime64[ms]'
1747
+ elif issubclass(t, datetime):
1748
+ return 'datetime64[ns]'
1749
+ elif issubclass(t, byte):
1750
+ if otq.webapi:
1751
+ return 'int8'
1752
+ return 'int32'
1753
+ elif issubclass(t, short):
1754
+ if otq.webapi:
1755
+ return 'int16'
1756
+ return 'int32'
1757
+ elif issubclass(t, uint):
1758
+ return 'uint32'
1759
+ elif issubclass(t, ulong):
1760
+ return 'uint64'
1761
+ elif issubclass(t, _int):
1762
+ return 'int32'
1763
+ elif issubclass(t, long):
1764
+ return 'int64'
1765
+ elif issubclass(t, int):
1766
+ return 'int64'
1767
+ elif issubclass(t, float):
1768
+ return 'float64'
1769
+ elif issubclass(t, decimal):
1770
+ return 'float64'
1771
+ else:
1772
+ return np.dtype(t)
1773
+
1774
+
1775
+ # TODO: move this union of types to some common place
1776
+ def datetime2expr(
1777
+ dt: Union[_datetime, _date, pd.Timestamp, date, datetime],
1778
+ timezone: Optional[str] = None,
1779
+ timezone_naive: Optional[str] = None,
1780
+ ) -> str:
1781
+ """
1782
+ Convert python datetime values to OneTick string representation.
1783
+ If `dt` is timezone-aware then timezone will be taken from `dt` value.
1784
+ If `dt` is timezone-naive then timezone specified with otp.config['tz'] or otp.run() will be used.
1785
+
1786
+ Parameters
1787
+ ----------
1788
+ dt
1789
+ date or datetime value
1790
+ timezone: str or Operation
1791
+ This timezone will be used unconditionally.
1792
+ timezone_naive: str or Operation
1793
+ This timezone will be used if `dt` is timezone-naive.
1794
+ """
1795
+ dt_str = _format_datetime(dt, '%Y-%m-%d %H:%M:%S.%f', add_nano_suffix=True)
1796
+ if timezone is None:
1797
+ timezone = get_timezone_from_datetime(dt)
1798
+ if timezone is None:
1799
+ timezone = timezone_naive
1800
+ if not isinstance(timezone, otp.Operation):
1801
+ timezone = f'"{timezone}"' if timezone else '_TIMEZONE'
1802
+ return f'PARSE_NSECTIME("%Y-%m-%d %H:%M:%S.%J", "{dt_str}", {str(timezone)})'
1803
+
1804
+
1805
+ def datetime2timeval(dt: Union[_datetime, _date, pd.Timestamp, date, datetime], timezone: str = 'GMT'):
1806
+ """
1807
+ Get nanosecond-precision pyomd.timeval_t
1808
+ from different datetime types supported by onetick-py.
1809
+
1810
+ If ``dt`` is timezone-aware, then its timezone will be used.
1811
+ If ``dt`` is timezone-naive , then it will be localized to ``timezone`` parameter (GMT by default).
1812
+ """
1813
+ dt_str = _format_datetime(dt, '%Y-%m-%d %H:%M:%S.%f', add_nano_suffix=True)
1814
+ tz_str = get_timezone_from_datetime(dt)
1815
+ if tz_str:
1816
+ # use timezone from the object
1817
+ return pyomd.TimeParser('%Y-%m-%d %H:%M:%S.%J', tz_str).parse_time(dt_str)
1818
+ else:
1819
+ # localize to the timezone specified in fucntion parameter
1820
+ return pyomd.TimeParser('%Y-%m-%d %H:%M:%S.%J', timezone).parse_time(dt_str)
1821
+
1822
+
1823
+ def _format_datetime(dt, fmt, add_nano_suffix=True):
1824
+ dt_str = dt.strftime(fmt)
1825
+ if add_nano_suffix:
1826
+ if isinstance(dt, (pd.Timestamp, datetime)):
1827
+ dt_str += f'{dt.nanosecond:03}'[-3:]
1828
+ else:
1829
+ dt_str += '000'
1830
+ return dt_str
1831
+
1832
+
1833
+ def value2str(v):
1834
+ """
1835
+ Converts a python value from the `v` parameter into OneTick format.
1836
+ """
1837
+ if issubclass(type(v), str):
1838
+ # there is no escape, so replacing double quotes with concatenation with it
1839
+ return '"' + str(v).replace('"', '''"+'"'+"''') + '"'
1840
+
1841
+ if isinstance(v, (float, decimal)) and not (isinstance(v, (_inf, _nan))):
1842
+ # PY-286: support science notation
1843
+ s = str(v)
1844
+ if "e" in s:
1845
+ s = f"{v:.20f}".rstrip("0")
1846
+ if s == "nan":
1847
+ return str(nan)
1848
+ if isinstance(v, decimal):
1849
+ return f'DECIMAL({s})'
1850
+ return s
1851
+
1852
+ if is_time_type(v):
1853
+ return datetime2expr(v)
1854
+
1855
+ if isinstance(v, nsectime):
1856
+ # we do not need the same for msectime because it works as is
1857
+ if int(v) == 0 or int(v) > 15e12: # it is 2445/5/1
1858
+ return f'NSECTIME({v})'
1859
+ # This branch is for backward compatibility. Originally here was a bug that
1860
+ # allowed to pass only milliseconds as a value into the otp.nsectime constructor.
1861
+ # Obviously we expect there only nanoseconds, and the built-in NSECTIME works only
1862
+ # with nanoseconds.
1863
+ warnings.warn('It seems that you are using number of milliseconds as nanoseconds. ', stacklevel=2)
1864
+
1865
+ return str(v)
1866
+
1867
+
1868
+ # TODO: maybe can be removed, it is used only in tests now
1869
+ def time2nsectime(time, timezone=None):
1870
+ """
1871
+ Converts complex time types to nsectime timestamp.
1872
+
1873
+ Parameters
1874
+ ----------
1875
+ time: :py:class:`datetime.datetime`, :py:class:`datetime.date`,\
1876
+ :py:class:`otp.datetime <onetick.py.datetime>`, :py:class:`otp.date <onetick.py.date>`, pandas.Timestamp
1877
+ time to convert
1878
+ timezone:
1879
+ convert timezone before nsectime calculation
1880
+
1881
+ Returns
1882
+ -------
1883
+ result: int
1884
+ number of nanoseconds since epoch
1885
+ """
1886
+ if isinstance(time, (_datetime, _date)):
1887
+ time = pd.Timestamp(time)
1888
+ elif isinstance(time, date):
1889
+ time = datetime(time)
1890
+ if timezone:
1891
+ if not has_timezone_parameter(): # accommodating legacy behavior prior to 20220327-3 weekly build
1892
+ time = time.replace(tzinfo=None)
1893
+ else:
1894
+ if time.tzinfo is None:
1895
+ time = time.tz_localize(timezone)
1896
+ else:
1897
+ # timezone conversion doesn't change the offset from epoch, only string representation,
1898
+ # so time.value (the number of nanoseconds) will stay the same
1899
+ # this line can be deleted
1900
+ time = time.tz_convert(timezone)
1901
+ return time.value
1902
+
1903
+
1904
+ def is_time_type(time):
1905
+ """ Returns true if argument is subclass of any time type
1906
+
1907
+ Checks if pass type is time type, currently checks for otp.date, otp.datetime,
1908
+ pd.Timestamp, datetime.date, datetime.datetime
1909
+
1910
+ Parameters
1911
+ ----------
1912
+ time:
1913
+ object or type of the object
1914
+
1915
+ Returns
1916
+ -------
1917
+ result: bool
1918
+ Return true if argument is time type
1919
+
1920
+ Examples
1921
+ --------
1922
+
1923
+ >>> is_time_type(datetime.datetime) # OTdirective: skip-example: ;
1924
+ True
1925
+ >>> is_time_type(type(5)) # OTdirective: skip-example: ;
1926
+ False
1927
+ >>> is_time_type(datetime.datetime(2019, 1, 1)) # OTdirective: snippet-name: types.is time;
1928
+ True
1929
+ """
1930
+ time = time if inspect.isclass(time) else type(time)
1931
+ # do not check for datetime.datetime and pd.Timestamp, because they are in the same hierarchy
1932
+ # datetime.date -> datetime.datetime -> pd.Timestamp, where `->` means base class
1933
+ return issubclass(time, (_date, datetime, date))
1934
+
1935
+
1936
+ def next_day(dt: Union[_date, _datetime, date, datetime, pd.Timestamp]) -> _datetime:
1937
+ """
1938
+ Return next day of `dt` as datetime.datetime.
1939
+ """
1940
+ updated_dt = dt + Day(1)
1941
+
1942
+ # If we switch ts on timezone transition time, ts changes its timezone offset
1943
+ if hasattr(dt, "tzinfo") and updated_dt.tzinfo != dt.tzinfo:
1944
+ dt_offset = dt.tzinfo.utcoffset(dt)
1945
+ updated_dt_offset = updated_dt.tzinfo.utcoffset(updated_dt)
1946
+
1947
+ tz_diff = dt_offset - updated_dt_offset
1948
+ updated_dt += tz_diff
1949
+
1950
+ updated_date = updated_dt.date()
1951
+
1952
+ return _datetime(updated_date.year, updated_date.month, updated_date.day)
1953
+
1954
+
1955
+ def default_by_type(dtype):
1956
+ """
1957
+ Get default value by OneTick type.
1958
+
1959
+ Parameters
1960
+ ----------
1961
+ dtype:
1962
+ one of onetick-py base types
1963
+
1964
+ Examples
1965
+ --------
1966
+ >>> otp.default_by_type(float)
1967
+ nan
1968
+ >>> otp.default_by_type(otp.decimal)
1969
+ decimal(0.0)
1970
+ >>> otp.default_by_type(int)
1971
+ 0
1972
+ >>> otp.default_by_type(otp.ulong)
1973
+ ulong(0)
1974
+ >>> otp.default_by_type(otp.uint)
1975
+ uint(0)
1976
+ >>> otp.default_by_type(otp.short)
1977
+ short(0)
1978
+ >>> otp.default_by_type(otp.byte)
1979
+ byte(0)
1980
+ >>> otp.default_by_type(otp.nsectime)
1981
+ nsectime(0)
1982
+ >>> otp.default_by_type(otp.msectime)
1983
+ msectime(0)
1984
+ >>> otp.default_by_type(str)
1985
+ ''
1986
+ >>> otp.default_by_type(otp.string)
1987
+ string('')
1988
+ >>> otp.default_by_type(otp.string[123])
1989
+ string[123]('')
1990
+ >>> otp.default_by_type(otp.varstring)
1991
+ varstring('')
1992
+ """
1993
+ # TODO: think if we want to treat bool as basic onetick type
1994
+ if dtype is bool:
1995
+ return 0
1996
+ if not is_type_basic(dtype):
1997
+ raise TypeError(f"Can't get default value for type: {dtype}")
1998
+ if issubclass(dtype, int):
1999
+ return dtype(0)
2000
+ if dtype is otp.decimal:
2001
+ return otp.decimal(0)
2002
+ if issubclass(dtype, float):
2003
+ return nan
2004
+ if issubclass(dtype, str):
2005
+ return dtype('')
2006
+ if issubclass(dtype, nsectime) or issubclass(dtype, msectime):
2007
+ return dtype(0)
2008
+
2009
+
2010
+ class timedelta(pd.Timedelta):
2011
+ """
2012
+ The object representing the delta between timestamps.
2013
+
2014
+ Parameters
2015
+ ----------
2016
+ value: :py:class:`otp.timedelta <onetick.py.timedelta>`, :pandas:`pandas.Timedelta`,\
2017
+ :py:class:`datetime.timedelta`, str, or int
2018
+ Initialize this object from other types of objects.
2019
+ kwargs:
2020
+ Dictionary of offset names and their values.
2021
+ Available offset names:
2022
+ *weeks*, *days*, *hours*, *minutes*, *seconds*,
2023
+ *milliseconds*, *microseconds*, *nanoseconds*.
2024
+
2025
+ Examples
2026
+ --------
2027
+
2028
+ Create :py:class:`otp.timedelta <onetick.py.timedelta>` from key-value arguments:
2029
+
2030
+ >>> otp.timedelta(weeks=1, days=1, hours=1, minutes=1, seconds=1, milliseconds=1, microseconds=1, nanoseconds=1)
2031
+ timedelta('8 days 01:01:01.001001001')
2032
+
2033
+ Create :py:class:`otp.timedelta <onetick.py.timedelta>` from different types of objects:
2034
+
2035
+ >>> otp.timedelta(datetime.timedelta(days=2, hours=3))
2036
+ timedelta('2 days 03:00:00')
2037
+
2038
+ >>> otp.timedelta('20 days 13:02:01.999777666')
2039
+ timedelta('20 days 13:02:01.999777666')
2040
+
2041
+ Adding :py:class:`otp.timedelta <onetick.py.timedelta>` object to :py:class:`otp.datetime <onetick.py.datetime>`:
2042
+
2043
+ >>> otp.datetime(2022, 1, 1, 1, 2, 3) + otp.timedelta(days=1, hours=1, minutes=1, seconds=1)
2044
+ 2022-01-02 02:03:04
2045
+
2046
+ Adding :py:class:`otp.timedelta <onetick.py.timedelta>` object to :py:class:`otp.date <onetick.py.date>`:
2047
+
2048
+ >>> otp.date(2022, 1, 1) + otp.timedelta(weeks=1, nanoseconds=1)
2049
+ 2022-01-08 00:00:00.000000001
2050
+ """
2051
+
2052
+ def __repr__(self):
2053
+ return super().__repr__().lower()
2054
+
2055
+ def _get_offset(self):
2056
+ return self.value, 'nanosecond'