onetick-py 1.170.0__py3-none-any.whl → 1.172.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 (30) hide show
  1. onetick/py/__init__.py +8 -2
  2. onetick/py/_version.py +1 -1
  3. onetick/py/cache.py +3 -3
  4. onetick/py/callback/callbacks.py +1 -1
  5. onetick/py/configuration.py +35 -9
  6. onetick/py/core/_source/source_methods/misc.py +1 -1
  7. onetick/py/core/_source/source_methods/writes.py +48 -33
  8. onetick/py/core/column_operations/_methods/methods.py +1 -4
  9. onetick/py/core/column_operations/accessors/_accessor.py +4 -6
  10. onetick/py/core/column_operations/accessors/decimal_accessor.py +20 -8
  11. onetick/py/core/column_operations/accessors/dt_accessor.py +137 -68
  12. onetick/py/core/column_operations/accessors/float_accessor.py +35 -15
  13. onetick/py/core/column_operations/accessors/str_accessor.py +0 -7
  14. onetick/py/core/column_operations/base.py +72 -12
  15. onetick/py/core/multi_output_source.py +41 -2
  16. onetick/py/core/per_tick_script.py +2 -0
  17. onetick/py/db/_inspection.py +82 -49
  18. onetick/py/db/db.py +2 -2
  19. onetick/py/functions.py +7 -1
  20. onetick/py/math.py +374 -386
  21. onetick/py/misc.py +70 -38
  22. onetick/py/otq.py +18 -12
  23. onetick/py/run.py +75 -5
  24. onetick/py/sources/ticks.py +1 -1
  25. {onetick_py-1.170.0.dist-info → onetick_py-1.172.0.dist-info}/METADATA +4 -2
  26. {onetick_py-1.170.0.dist-info → onetick_py-1.172.0.dist-info}/RECORD +30 -30
  27. {onetick_py-1.170.0.dist-info → onetick_py-1.172.0.dist-info}/WHEEL +0 -0
  28. {onetick_py-1.170.0.dist-info → onetick_py-1.172.0.dist-info}/entry_points.txt +0 -0
  29. {onetick_py-1.170.0.dist-info → onetick_py-1.172.0.dist-info}/licenses/LICENSE +0 -0
  30. {onetick_py-1.170.0.dist-info → onetick_py-1.172.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  from typing import Union
2
2
 
3
3
  from onetick.py import configuration, utils
4
+ from onetick.py import types as ott
4
5
  from onetick.py.core.column_operations.accessors._accessor import _Accessor
5
6
  from onetick.py.backports import Literal
6
7
  from onetick.py.types import datetime, value2str
@@ -76,14 +77,17 @@ class _DtAccessor(_Accessor):
76
77
  """
77
78
  if timezone is utils.default:
78
79
  timezone = configuration.config.tz
79
- timezone, format_str = self._preprocess_tz_and_format(timezone, format)
80
80
 
81
- def formatter(x):
82
- return f'nsectime_format({format_str},{x},{timezone})'
81
+ def formatter(column, _format, _timezone):
82
+ column = ott.value2str(column)
83
+ _timezone, _format = self._preprocess_tz_and_format(_timezone, _format)
84
+ return f'nsectime_format({_format},{column},{_timezone})'
83
85
 
84
- return _DtAccessor.Formatter(self._base_column,
85
- str,
86
- formatter=formatter)
86
+ return _DtAccessor.Formatter(
87
+ op_params=[self._base_column, format, timezone],
88
+ dtype=str,
89
+ formatter=formatter,
90
+ )
87
91
 
88
92
  def date(self):
89
93
  """
@@ -103,7 +107,9 @@ class _DtAccessor(_Accessor):
103
107
  return self.strftime(format_str, None).str.to_datetime(format_str, None)
104
108
 
105
109
  @docstring(parameters=[_timezone_doc], add_self=True)
106
- def day_of_week(self, start_index: int = 1, start_day: Literal['monday', 'sunday'] = 'monday', timezone=None):
110
+ def day_of_week(
111
+ self, start_index: Union[int, _Operation] = 1, start_day: Literal['monday', 'sunday'] = 'monday', timezone=None,
112
+ ):
107
113
  """
108
114
  Return the day of the week.
109
115
 
@@ -112,7 +118,7 @@ class _DtAccessor(_Accessor):
112
118
 
113
119
  Parameters
114
120
  ----------
115
- start_index: int
121
+ start_index: int or Operation
116
122
  Sunday index.
117
123
  start_day: 'monday' or 'sunday'
118
124
  Day that will be denoted with ``start_index``
@@ -135,19 +141,22 @@ class _DtAccessor(_Accessor):
135
141
  if start_day not in ['monday', 'sunday']:
136
142
  raise ValueError(f"'start_day' parameter ({start_day}) not in ['monday', 'sunday']")
137
143
 
138
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
139
-
140
- def formatter(x):
141
- format_ = f'day_of_week({x},{timezone})'
142
- if start_day == 'monday':
144
+ def formatter(column, _start_index, _start_day, _timezone):
145
+ column = ott.value2str(column)
146
+ _start_index = ott.value2str(_start_index)
147
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
148
+ format_ = f'day_of_week({column},{_timezone})'
149
+ if _start_day == 'monday':
143
150
  # CASE should be uppercased because it can be used in per-tick script
144
151
  format_ = f'CASE({format_}, 0, 7, {format_})-1'
145
- format_ += f'+{start_index}'
152
+ format_ += f'+{_start_index}'
146
153
  return format_
147
154
 
148
- return _DtAccessor.Formatter(self._base_column,
149
- int,
150
- formatter=formatter)
155
+ return _DtAccessor.Formatter(
156
+ op_params=[self._base_column, start_index, start_day, timezone],
157
+ dtype=int,
158
+ formatter=formatter,
159
+ )
151
160
 
152
161
  @docstring(parameters=[_timezone_doc], add_self=True)
153
162
  def day_name(self, timezone=None):
@@ -168,11 +177,16 @@ class _DtAccessor(_Accessor):
168
177
  5 2022-05-15 Sunday
169
178
  6 2022-05-16 Monday
170
179
  """
171
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
180
+ def formatter(column, _timezone):
181
+ column = ott.value2str(column)
182
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
183
+ return f'DAYNAME({column},{_timezone})'
172
184
 
173
- return _DtAccessor.Formatter(self._base_column,
174
- str,
175
- formatter=lambda x: f'DAYNAME({x},{timezone})')
185
+ return _DtAccessor.Formatter(
186
+ op_params=[self._base_column, timezone],
187
+ dtype=str,
188
+ formatter=formatter,
189
+ )
176
190
 
177
191
  @docstring(parameters=[_timezone_doc], add_self=True)
178
192
  def day_of_month(self, timezone=None):
@@ -193,11 +207,16 @@ class _DtAccessor(_Accessor):
193
207
  5 2022-05-15 15
194
208
  6 2022-05-16 16
195
209
  """
196
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
210
+ def formatter(column, _timezone):
211
+ column = ott.value2str(column)
212
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
213
+ return f'DAYOFMONTH({column},{_timezone})'
197
214
 
198
- return _DtAccessor.Formatter(self._base_column,
199
- int,
200
- formatter=lambda x: f'DAYOFMONTH({x},{timezone})')
215
+ return _DtAccessor.Formatter(
216
+ op_params=[self._base_column, timezone],
217
+ dtype=int,
218
+ formatter=formatter,
219
+ )
201
220
 
202
221
  @docstring(parameters=[_timezone_doc], add_self=True)
203
222
  def day_of_year(self, timezone=None):
@@ -218,11 +237,16 @@ class _DtAccessor(_Accessor):
218
237
  5 2022-05-15 135
219
238
  6 2022-05-16 136
220
239
  """
221
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
240
+ def formatter(column, _timezone):
241
+ column = ott.value2str(column)
242
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
243
+ return f'DAYOFYEAR({column},{_timezone})'
222
244
 
223
- return _DtAccessor.Formatter(self._base_column,
224
- int,
225
- formatter=lambda x: f'DAYOFYEAR({x},{timezone})')
245
+ return _DtAccessor.Formatter(
246
+ op_params=[self._base_column, timezone],
247
+ dtype=int,
248
+ formatter=formatter,
249
+ )
226
250
 
227
251
  @docstring(parameters=[_timezone_doc], add_self=True)
228
252
  def hour(self, timezone=None):
@@ -243,11 +267,16 @@ class _DtAccessor(_Accessor):
243
267
  5 2022-05-01 15:00:06 15
244
268
  6 2022-05-01 16:00:06 16
245
269
  """
246
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
270
+ def formatter(column, _timezone):
271
+ column = ott.value2str(column)
272
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
273
+ return f'HOUR({column},{_timezone})'
247
274
 
248
- return _DtAccessor.Formatter(self._base_column,
249
- int,
250
- formatter=lambda x: f'HOUR({x},{timezone})')
275
+ return _DtAccessor.Formatter(
276
+ op_params=[self._base_column, timezone],
277
+ dtype=int,
278
+ formatter=formatter,
279
+ )
251
280
 
252
281
  @docstring(parameters=[_timezone_doc], add_self=True)
253
282
  def minute(self, timezone=None):
@@ -269,11 +298,16 @@ class _DtAccessor(_Accessor):
269
298
  5 2022-05-01 15:15:06 15
270
299
  6 2022-05-01 15:16:06 16
271
300
  """
272
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
301
+ def formatter(column, _timezone):
302
+ column = ott.value2str(column)
303
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
304
+ return f'MINUTE({column},{_timezone})'
273
305
 
274
- return _DtAccessor.Formatter(self._base_column,
275
- int,
276
- formatter=lambda x: f'MINUTE({x},{timezone})')
306
+ return _DtAccessor.Formatter(
307
+ op_params=[self._base_column, timezone],
308
+ dtype=int,
309
+ formatter=formatter,
310
+ )
277
311
 
278
312
  @docstring(parameters=[_timezone_doc], add_self=True)
279
313
  def second(self, timezone=None):
@@ -294,11 +328,16 @@ class _DtAccessor(_Accessor):
294
328
  5 2022-05-01 15:11:15 15
295
329
  6 2022-05-01 15:11:16 16
296
330
  """
297
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
331
+ def formatter(column, _timezone):
332
+ column = ott.value2str(column)
333
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
334
+ return f'SECOND({column},{_timezone})'
298
335
 
299
- return _DtAccessor.Formatter(self._base_column,
300
- int,
301
- formatter=lambda x: f'SECOND({x},{timezone})')
336
+ return _DtAccessor.Formatter(
337
+ op_params=[self._base_column, timezone],
338
+ dtype=int,
339
+ formatter=formatter,
340
+ )
302
341
 
303
342
  @docstring(parameters=[_timezone_doc], add_self=True)
304
343
  def month(self, timezone=None):
@@ -320,11 +359,16 @@ class _DtAccessor(_Accessor):
320
359
  6 2022-09-01 9
321
360
  7 2022-10-01 10
322
361
  """
323
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
362
+ def formatter(column, _timezone):
363
+ column = ott.value2str(column)
364
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
365
+ return f'MONTH({column},{_timezone})'
324
366
 
325
- return _DtAccessor.Formatter(self._base_column,
326
- int,
327
- formatter=lambda x: f'MONTH({x},{timezone})')
367
+ return _DtAccessor.Formatter(
368
+ op_params=[self._base_column, timezone],
369
+ dtype=int,
370
+ formatter=formatter,
371
+ )
328
372
 
329
373
  @docstring(parameters=[_timezone_doc], add_self=True)
330
374
  def month_name(self, timezone=None):
@@ -346,11 +390,16 @@ class _DtAccessor(_Accessor):
346
390
  6 2022-09-01 Sep
347
391
  7 2022-10-01 Oct
348
392
  """
349
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
393
+ def formatter(column, _timezone):
394
+ column = ott.value2str(column)
395
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
396
+ return f'MONTHNAME({column},{_timezone})'
350
397
 
351
- return _DtAccessor.Formatter(self._base_column,
352
- str,
353
- formatter=lambda x: f'MONTHNAME({x},{timezone})')
398
+ return _DtAccessor.Formatter(
399
+ op_params=[self._base_column, timezone],
400
+ dtype=str,
401
+ formatter=formatter,
402
+ )
354
403
 
355
404
  @docstring(parameters=[_timezone_doc], add_self=True)
356
405
  def quarter(self, timezone=None):
@@ -372,11 +421,16 @@ class _DtAccessor(_Accessor):
372
421
  6 2022-09-01 3
373
422
  7 2022-10-01 4
374
423
  """
375
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
424
+ def formatter(column, _timezone):
425
+ column = ott.value2str(column)
426
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
427
+ return f'QUARTER({column},{_timezone})'
376
428
 
377
- return _DtAccessor.Formatter(self._base_column,
378
- int,
379
- formatter=lambda x: f'QUARTER({x},{timezone})')
429
+ return _DtAccessor.Formatter(
430
+ op_params=[self._base_column, timezone],
431
+ dtype=int,
432
+ formatter=formatter,
433
+ )
380
434
 
381
435
  @docstring(parameters=[_timezone_doc], add_self=True)
382
436
  def year(self, timezone=None):
@@ -398,11 +452,16 @@ class _DtAccessor(_Accessor):
398
452
  6 2029-03-01 2029
399
453
  7 2030-03-01 2030
400
454
  """
401
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
455
+ def formatter(column, _timezone):
456
+ column = ott.value2str(column)
457
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
458
+ return f'YEAR({column},{_timezone})'
402
459
 
403
- return _DtAccessor.Formatter(self._base_column,
404
- int,
405
- formatter=lambda x: f'YEAR({x},{timezone})')
460
+ return _DtAccessor.Formatter(
461
+ op_params=[self._base_column, timezone],
462
+ dtype=int,
463
+ formatter=formatter,
464
+ )
406
465
 
407
466
  @docstring(parameters=[_timezone_doc], add_self=True)
408
467
  def date_trunc(self,
@@ -434,12 +493,17 @@ class _DtAccessor(_Accessor):
434
493
  5 2020-11-11 05:04:13.101737879 2020-11-11 05:04:13.101000000 millisecond
435
494
  6 2020-11-11 05:04:13.101737879 2020-11-11 05:04:13.101737879 nanosecond
436
495
  """
437
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
438
- date_part = value2str(date_part)
439
-
440
- return _DtAccessor.Formatter(self._base_column,
441
- datetime,
442
- formatter=lambda x: f'DATE_TRUNC({date_part},{x},{timezone})')
496
+ def formatter(column, _date_part, _timezone):
497
+ column = ott.value2str(column)
498
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
499
+ _date_part = value2str(_date_part)
500
+ return f'DATE_TRUNC({_date_part},{column},{_timezone})'
501
+
502
+ return _DtAccessor.Formatter(
503
+ op_params=[self._base_column, date_part, timezone],
504
+ dtype=datetime,
505
+ formatter=formatter,
506
+ )
443
507
 
444
508
  @docstring(parameters=[_timezone_doc], add_self=True)
445
509
  def week(self, timezone=None):
@@ -461,8 +525,13 @@ class _DtAccessor(_Accessor):
461
525
  6 2020-09-01 36
462
526
  7 2020-10-01 40
463
527
  """
464
- timezone, _ = self._preprocess_tz_and_format(timezone, '')
465
-
466
- return _DtAccessor.Formatter(self._base_column,
467
- int,
468
- formatter=lambda x: f'WEEK({x},{timezone})')
528
+ def formatter(column, _timezone):
529
+ column = ott.value2str(column)
530
+ _timezone, _ = self._preprocess_tz_and_format(_timezone, '')
531
+ return f'WEEK({column},{_timezone})'
532
+
533
+ return _DtAccessor.Formatter(
534
+ op_params=[self._base_column, timezone],
535
+ dtype=int,
536
+ formatter=formatter,
537
+ )
@@ -1,5 +1,6 @@
1
1
  from onetick.py import types as ott
2
2
  from onetick.py.core.column_operations.accessors._accessor import _Accessor
3
+ from onetick.py.core.column_operations.base import _Operation
3
4
 
4
5
 
5
6
  class _FloatAccessor(_Accessor):
@@ -61,11 +62,18 @@ class _FloatAccessor(_Accessor):
61
62
  Name: X, dtype: object
62
63
  """
63
64
  dtype = ott.string[length] if isinstance(length, int) else str
64
- length = ott.value2str(length)
65
- precision = ott.value2str(precision)
66
- return _FloatAccessor.Formatter(self._base_column,
67
- dtype,
68
- formatter=lambda x: f"str({x}, {length}, {precision})")
65
+
66
+ def formatter(column, _length, _precision):
67
+ column = ott.value2str(column)
68
+ _length = ott.value2str(_length)
69
+ _precision = ott.value2str(_precision)
70
+ return f"str({column}, {_length}, {_precision})"
71
+
72
+ return _FloatAccessor.Formatter(
73
+ op_params=[self._base_column, length, precision],
74
+ dtype=dtype,
75
+ formatter=formatter,
76
+ )
69
77
 
70
78
  def cmp(self, other, eps):
71
79
  """
@@ -110,11 +118,17 @@ class _FloatAccessor(_Accessor):
110
118
  4 1.0
111
119
  Name: X, dtype: float64
112
120
  """
113
- other = ott.value2str(other)
114
- eps = ott.value2str(eps)
115
- return _FloatAccessor.Formatter(self._base_column,
116
- float,
117
- formatter=lambda x: f"double_compare({x}, {other}, {eps})")
121
+ def formatter(column, _other, _eps):
122
+ column = ott.value2str(column)
123
+ _other = ott.value2str(_other)
124
+ _eps = ott.value2str(_eps)
125
+ return f"double_compare({column}, {_other}, {_eps})"
126
+
127
+ return _FloatAccessor.Formatter(
128
+ op_params=[self._base_column, other, eps],
129
+ dtype=float,
130
+ formatter=formatter,
131
+ )
118
132
 
119
133
  def eq(self, other, delta):
120
134
  """
@@ -153,8 +167,14 @@ class _FloatAccessor(_Accessor):
153
167
  4 0.0
154
168
  Name: X, dtype: float64
155
169
  """
156
- other = ott.value2str(other)
157
- delta = ott.value2str(delta)
158
- return _FloatAccessor.Formatter(self._base_column,
159
- bool,
160
- formatter=lambda x: f"abs({x} - {other}) <= {delta}")
170
+ def formatter(column, _other, _delta):
171
+ column = ott.value2str(column)
172
+ _other = ott.value2str(_other)
173
+ _delta = ott.value2str(_delta)
174
+ return f"abs({column} - {_other}) <= {_delta}"
175
+
176
+ return _FloatAccessor.Formatter(
177
+ op_params=[self._base_column, other, delta],
178
+ dtype=bool,
179
+ formatter=formatter,
180
+ )
@@ -21,13 +21,6 @@ class _StrAccessor(_Accessor):
21
21
  >>> data = otp.Ticks(X=['some string'])
22
22
  >>> data["Y"] = data["X"].str.<function_name>() # doctest: +SKIP
23
23
  """
24
- class Formatter(_Operation):
25
- def __init__(self, dtype, formatter, op_params):
26
-
27
- def op_func(*args, **kwargs):
28
- return formatter(*args, **kwargs), dtype
29
-
30
- super().__init__(op_func=op_func, op_params=op_params, dtype=dtype)
31
24
 
32
25
  def to_datetime(self,
33
26
  format='%Y/%m/%d %H:%M:%S.%J',
@@ -139,9 +139,16 @@ class Operation:
139
139
  """
140
140
  return Expr(self)
141
141
 
142
- def round(self, precision=None):
142
+ def round(self, precision=0):
143
143
  """
144
- Rounds input column with specified `precision`.
144
+ Rounds input column with specified ``precision``.
145
+
146
+ Rounding :class:`otp.nan <onetick.py.nan>` returns NaN
147
+ and rounding :class:`otp.inf <onetick.py.inf>` returns Infinity.
148
+
149
+ For values that are exactly half-way between two integers (when the fraction part of value is exactly 0.5),
150
+ the rounding method used here is *upwards*, which returns the bigger number.
151
+ For other rounding methods see :func:`otp.math.round <onetick.py.math.round>` function.
145
152
 
146
153
  Parameters
147
154
  ----------
@@ -161,8 +168,8 @@ class Operation:
161
168
  >>> t['C'] = t['A'].round(2)
162
169
  >>> t['D'] = t['A'].round(-2)
163
170
  >>> otp.run(t)
164
- Time A B C D
165
- 0 2003-12-01 1234.5678 1235 1234.57 1200.0
171
+ Time A B C D
172
+ 0 2003-12-01 1234.5678 1235.0 1234.57 1200.0
166
173
 
167
174
  Returns
168
175
  -------
@@ -505,31 +512,84 @@ class Operation:
505
512
  """
506
513
  return _Operation(_methods.abs, [self])
507
514
 
508
- def __round__(self, precision=None):
515
+ def __round__(self, precision=0):
509
516
  """
510
517
  Rounds value with specified ``precision``.
511
518
 
519
+ Rounding :class:`otp.nan <onetick.py.nan>` returns NaN
520
+ and rounding :class:`otp.inf <onetick.py.inf>` returns Infinity.
521
+
522
+ For values that are exactly half-way between two integers (when the fraction part of value is exactly 0.5),
523
+ the rounding method used here is *upwards*, which returns the bigger number.
524
+ For other rounding methods see :func:`otp.math.round <onetick.py.math.round>` function.
525
+
526
+ See also
527
+ --------
528
+ :func:`otp.math.round <onetick.py.math.round>`
529
+ :func:`otp.math.floor <onetick.py.math.floor>`
530
+ :func:`otp.math.ceil <onetick.py.math.ceil>`
531
+
512
532
  Parameters
513
533
  ----------
514
534
  precision: int
515
535
  Number from -12 to 12.
516
536
  Positive precision is precision after the floating point.
517
- Negative precision is precision before the floating point.
537
+ Negative precision is precision before the floating point (and the fraction part is dropped in this case).
518
538
 
519
539
  Examples
520
540
  --------
521
- >>> t = otp.Tick(A=1234.5678)
522
- >>> t['B'] = round(t['A'])
523
- >>> t['C'] = round(t['A'], 2)
524
- >>> t['D'] = round(t['A'], -2)
541
+
542
+ By default the ``precision`` is zero and the number is rounded to the closest integer number
543
+ (and to the bigger number when the fraction part of value is exactly 0.5):
544
+
545
+ >>> t = otp.Ticks(A=[-123.45, 123.45, 123.5])
546
+ >>> t['ROUND'] = round(t['A'])
547
+ >>> otp.run(t)
548
+ Time A ROUND
549
+ 0 2003-12-01 00:00:00.000 -123.45 -123.0
550
+ 1 2003-12-01 00:00:00.001 123.45 123.0
551
+ 2 2003-12-01 00:00:00.002 123.50 124.0
552
+
553
+ Positive precision truncates to the number of digits *after* floating point:
554
+
555
+ >>> t = otp.Ticks(A=[-123.45, 123.45])
556
+ >>> t['ROUND1'] = round(t['A'], 1)
557
+ >>> t['ROUND2'] = round(t['A'], 2)
558
+ >>> otp.run(t)
559
+ Time A ROUND1 ROUND2
560
+ 0 2003-12-01 00:00:00.000 -123.45 -123.4 -123.45
561
+ 1 2003-12-01 00:00:00.001 123.45 123.5 123.45
562
+
563
+ Negative precision truncates to the number of digits *before* floating point
564
+ (and the fraction part is dropped in this case):
565
+
566
+ >>> t = otp.Ticks(A=[-123.45, 123.45])
567
+ >>> t['ROUND_M1'] = round(t['A'], -1)
568
+ >>> t['ROUND_M2'] = round(t['A'], -2)
569
+ >>> otp.run(t)
570
+ Time A ROUND_M1 ROUND_M2
571
+ 0 2003-12-01 00:00:00.000 -123.45 -120.0 -100.0
572
+ 1 2003-12-01 00:00:00.001 123.45 120.0 100.0
573
+
574
+ Rounding :class:`otp.nan <onetick.py.nan>` returns NaN
575
+ and rounding :class:`otp.inf <onetick.py.inf>` returns Infinity in all cases:
576
+
577
+ >>> t = otp.Ticks(A=[otp.inf, -otp.inf, otp.nan])
578
+ >>> t['ROUND_0'] = round(t['A'])
579
+ >>> t['ROUND_P2'] = round(t['A'], 2)
580
+ >>> t['ROUND_M2'] = round(t['A'], -2)
525
581
  >>> otp.run(t)
526
- Time A B C D
527
- 0 2003-12-01 1234.5678 1235 1234.57 1200.0
582
+ Time A ROUND_0 ROUND_P2 ROUND_M2
583
+ 0 2003-12-01 00:00:00.000 inf inf inf inf
584
+ 1 2003-12-01 00:00:00.001 -inf -inf -inf -inf
585
+ 2 2003-12-01 00:00:00.002 NaN NaN NaN NaN
528
586
 
529
587
  Returns
530
588
  -------
531
589
  Operation
532
590
  """
591
+ if precision is None:
592
+ precision = 0
533
593
  return _Operation(_methods.round, [self, precision])
534
594
 
535
595
  def __neg__(self):
@@ -1,3 +1,4 @@
1
+ from onetick import py as otp
1
2
  from onetick.py import configuration
2
3
  from onetick.py.otq import otq
3
4
 
@@ -94,8 +95,8 @@ class MultiOutputSource:
94
95
  # we create a set of keys for all outputs and see if all sets are connected;
95
96
  # two sets are connected if they have any key in common
96
97
 
97
- if len(outputs) <= 1:
98
- raise ValueError('At least two branches should be passed to a MultiOutputSource object')
98
+ if len(outputs) < 1:
99
+ raise ValueError('At least one branch should be passed to a MultiOutputSource object')
99
100
 
100
101
  def get_history_key_set(hist):
101
102
  keys = set()
@@ -155,6 +156,41 @@ class MultiOutputSource:
155
156
  def _side_branch_list(self):
156
157
  return list(self.__side_branches.values())
157
158
 
159
+ def get_branch(self, branch_name: str) -> otp.Source:
160
+ """
161
+ Retrieve a branch by its name.
162
+
163
+ Parameters
164
+ ----------
165
+ branch_name : str
166
+ The name of the branch to retrieve.
167
+
168
+ Returns
169
+ -------
170
+ otp.Source
171
+ The branch corresponding to the given name.
172
+ """
173
+ if branch_name == self.__main_branch_name:
174
+ return self.__main_branch
175
+
176
+ branch = self.__side_branches.get(branch_name)
177
+ if branch is None:
178
+ raise ValueError(f'Branch name "{branch_name}" not found among the outputs!')
179
+
180
+ return branch
181
+
182
+ @property
183
+ def main_branch(self) -> otp.Source:
184
+ """
185
+ Get the main branch.
186
+
187
+ Returns
188
+ -------
189
+ otp.Source
190
+ The main branch.
191
+ """
192
+ return self.__main_branch
193
+
158
194
  def _prepare_for_execution(self, symbols=None, start=None, end=None, start_time_expression=None,
159
195
  end_time_expression=None, timezone=None,
160
196
  has_output=None, # NOSONAR
@@ -191,3 +227,6 @@ class MultiOutputSource:
191
227
  end=end,
192
228
  timezone=timezone,
193
229
  add_passthrough=False)
230
+
231
+ def _store_in_tmp_otq(self, *args, **kwargs):
232
+ return self.main_branch._store_in_tmp_otq(*args, **kwargs)
@@ -1095,6 +1095,8 @@ class CaseExpressionParser(ExpressionParser):
1095
1095
  try:
1096
1096
  return super().call(expr)
1097
1097
  except Exception:
1098
+ if orig_err is not None:
1099
+ raise err from orig_err
1098
1100
  raise ValueError(
1099
1101
  f"Can't convert function '{astunparse(expr)}' to CASE() expression."
1100
1102
  ) from err