Qubx 0.1.5__cp311-cp311-manylinux_2_35_x86_64.whl → 0.1.7__cp311-cp311-manylinux_2_35_x86_64.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.

Potentially problematic release.


This version of Qubx might be problematic. Click here for more details.

qubx/data/readers.py CHANGED
@@ -29,6 +29,13 @@ def _recognize_t(t: Union[int, str], defaultvalue, timeunit) -> int:
29
29
  return defaultvalue
30
30
 
31
31
 
32
+ def _time(t, timestamp_units: str) -> int:
33
+ t = int(t) if isinstance(t, float) else t
34
+ if timestamp_units == 'ns':
35
+ return np.datetime64(t, 'ns').item()
36
+ return np.datetime64(t, timestamp_units).astype('datetime64[ns]').item()
37
+
38
+
32
39
  def _find_column_index_in_list(xs, *args):
33
40
  xs = [x.lower() for x in xs]
34
41
  for a in args:
@@ -38,6 +45,9 @@ def _find_column_index_in_list(xs, *args):
38
45
  raise IndexError(f"Can't find any from {args} in list: {xs}")
39
46
 
40
47
 
48
+ _FIND_TIME_COL_IDX = lambda column_names: _find_column_index_in_list(column_names, 'time', 'timestamp', 'datetime', 'date', 'open_time')
49
+
50
+
41
51
  class DataTransformer:
42
52
 
43
53
  def __init__(self) -> None:
@@ -121,9 +131,9 @@ class CsvStorageDataReader(DataReader):
121
131
  # - try to find range to load
122
132
  start_idx, stop_idx = 0, table.num_rows
123
133
  try:
124
- _time_field_idx = _find_column_index_in_list(fieldnames, 'time', 'timestamp', 'datetime', 'date')
134
+ _time_field_idx = _FIND_TIME_COL_IDX(fieldnames)
125
135
  _time_type = table.field(_time_field_idx).type
126
- _time_unit = _time_type.unit if hasattr(_time_type, 'unit') else 's'
136
+ _time_unit = _time_type.unit if hasattr(_time_type, 'unit') else 'ms'
127
137
  _time_data = table[_time_field_idx]
128
138
 
129
139
  # - check if need convert time to primitive types (i.e. Date32 -> timestamp[x])
@@ -181,9 +191,11 @@ class AsPandasFrame(DataTransformer):
181
191
  """
182
192
  List of records to pandas dataframe transformer
183
193
  """
194
+ def __init__(self, timestamp_units=None) -> None:
195
+ self.timestamp_units = timestamp_units
184
196
 
185
197
  def start_transform(self, name: str, column_names: List[str]):
186
- self._time_idx = _find_column_index_in_list(column_names, 'time', 'timestamp', 'datetime', 'date')
198
+ self._time_idx = _FIND_TIME_COL_IDX(column_names)
187
199
  self._column_names = column_names
188
200
  self._frame = pd.DataFrame()
189
201
 
@@ -191,6 +203,8 @@ class AsPandasFrame(DataTransformer):
191
203
  self._frame
192
204
  p = pd.DataFrame.from_records(rows_data, columns=self._column_names)
193
205
  p.set_index(self._column_names[self._time_idx], drop=True, inplace=True)
206
+ p.index = pd.to_datetime(p.index, unit=self.timestamp_units) if self.timestamp_units else p.index
207
+ p.index.rename('timestamp', inplace=True)
194
208
  p.sort_index(inplace=True)
195
209
  self._frame = pd.concat((self._frame, p), axis=0, sort=True)
196
210
  return p
@@ -200,6 +214,17 @@ class AsPandasFrame(DataTransformer):
200
214
 
201
215
 
202
216
  class AsOhlcvSeries(DataTransformer):
217
+ """
218
+ Convert incoming data into OHLCV series.
219
+
220
+ Incoming data may have one of the following structures:
221
+
222
+ ```
223
+ ohlcv: time,open,high,low,close,volume|quote_volume,(buy_volume)
224
+ quotes: time,bid,ask,bidsize,asksize
225
+ trades (TAS): time,price,size,(is_taker)
226
+ ```
227
+ """
203
228
 
204
229
  def __init__(self, timeframe: str | None = None, timestamp_units='ns') -> None:
205
230
  super().__init__()
@@ -209,7 +234,7 @@ class AsOhlcvSeries(DataTransformer):
209
234
  self.timestamp_units = timestamp_units
210
235
 
211
236
  def start_transform(self, name: str, column_names: List[str]):
212
- self._time_idx = _find_column_index_in_list(column_names, 'time', 'timestamp', 'datetime', 'date')
237
+ self._time_idx = _FIND_TIME_COL_IDX(column_names)
213
238
  self._volume_idx = None
214
239
  self._b_volume_idx = None
215
240
  try:
@@ -251,15 +276,10 @@ class AsOhlcvSeries(DataTransformer):
251
276
  if self.timeframe:
252
277
  self._series = OHLCV(self._name, self.timeframe)
253
278
 
254
- def _time(self, t) -> int:
255
- if self.timestamp_units == 'ns':
256
- return np.datetime64(t, 'ns').item()
257
- return np.datetime64(t, self.timestamp_units).astype('datetime64[ns]').item()
258
-
259
279
  def _proc_ohlc(self, rows_data: List[List]):
260
280
  for d in rows_data:
261
281
  self._series.update_by_bar(
262
- self._time(d[self._time_idx]),
282
+ _time(d[self._time_idx], self.timestamp_units),
263
283
  d[self._open_idx], d[self._high_idx], d[self._low_idx], d[self._close_idx],
264
284
  d[self._volume_idx] if self._volume_idx else 0,
265
285
  d[self._b_volume_idx] if self._b_volume_idx else 0
@@ -268,7 +288,7 @@ class AsOhlcvSeries(DataTransformer):
268
288
  def _proc_quotes(self, rows_data: List[List]):
269
289
  for d in rows_data:
270
290
  self._series.update(
271
- self._time(d[self._time_idx]),
291
+ _time(d[self._time_idx], self.timestamp_units),
272
292
  (d[self._ask_idx] + d[self._bid_idx])/2
273
293
  )
274
294
 
@@ -277,7 +297,7 @@ class AsOhlcvSeries(DataTransformer):
277
297
  a = d[self._taker_idx] if self._taker_idx else 0
278
298
  s = d[self._size_idx]
279
299
  b = s if a else 0
280
- self._series.update(self._time(d[self._time_idx]), d[self._price_idx], s, b)
300
+ self._series.update(_time(d[self._time_idx], self.timestamp_units), d[self._price_idx], s, b)
281
301
 
282
302
  def process_data(self, rows_data: List[List]) -> Any:
283
303
  if self._series is None:
@@ -302,10 +322,14 @@ class AsOhlcvSeries(DataTransformer):
302
322
 
303
323
 
304
324
  class AsQuotes(DataTransformer):
325
+ """
326
+ Tries to convert incoming data to list of Quote's
327
+ Data must have appropriate structure: bid, ask, bidsize, asksize and time
328
+ """
305
329
 
306
330
  def start_transform(self, name: str, column_names: List[str]):
307
331
  self.buffer = list()
308
- self._time_idx = _find_column_index_in_list(column_names, 'time', 'timestamp', 'datetime')
332
+ self._time_idx = _FIND_TIME_COL_IDX(column_names)
309
333
  self._bid_idx = _find_column_index_in_list(column_names, 'bid')
310
334
  self._ask_idx = _find_column_index_in_list(column_names, 'ask')
311
335
  self._bidvol_idx = _find_column_index_in_list(column_names, 'bidvol', 'bid_vol', 'bidsize', 'bid_size')
@@ -320,6 +344,48 @@ class AsQuotes(DataTransformer):
320
344
  bv = d[self._bidvol_idx]
321
345
  av = d[self._askvol_idx]
322
346
  self.buffer.append(Quote(t.as_unit('ns').asm8.item(), b, a, bv, av))
347
+
348
+
349
+ class AsTimestampedRecords(DataTransformer):
350
+ """
351
+ Convert incoming data to list or dictionaries with preprocessed timestamps ('timestamp_ns' and 'timestamp')
352
+ ```
353
+ [
354
+ {
355
+ 'open_time': 1711944240000.0,
356
+ 'open': 203.219,
357
+ 'high': 203.33,
358
+ 'low': 203.134,
359
+ 'close': 203.175,
360
+ 'volume': 10060.0,
361
+ ....
362
+ 'timestamp_ns': 1711944240000000000,
363
+ 'timestamp': Timestamp('2024-04-01 04:04:00')
364
+ },
365
+ ...
366
+ ] ```
367
+ """
368
+
369
+ def __init__(self, timestamp_units: str | None=None) -> None:
370
+ self.timestamp_units = timestamp_units
371
+
372
+ def start_transform(self, name: str, column_names: List[str]):
373
+ self.buffer = list()
374
+ self._time_idx = _FIND_TIME_COL_IDX(column_names)
375
+ self._column_names = column_names
376
+
377
+ def process_data(self, rows_data: Iterable) -> Any:
378
+ self.buffer.extend(rows_data)
379
+
380
+ def collect(self) -> Any:
381
+ res = []
382
+ for r in self.buffer:
383
+ t = r[self._time_idx]
384
+ if self.timestamp_units:
385
+ t = _time(t, self.timestamp_units)
386
+ di = dict(zip(self._column_names, r)) | { 'timestamp_ns': t, 'timestamp': pd.Timestamp(t) }
387
+ res.append(di)
388
+ return res
323
389
 
324
390
 
325
391
  class RestoreTicksFromOHLC(DataTransformer):
@@ -344,7 +410,7 @@ class RestoreTicksFromOHLC(DataTransformer):
344
410
  def start_transform(self, name: str, column_names: List[str]):
345
411
  self.buffer = []
346
412
  # - it will fail if receive data doesn't look as ohlcv
347
- self._time_idx = _find_column_index_in_list(column_names, 'time', 'timestamp', 'datetime', 'date')
413
+ self._time_idx = _FIND_TIME_COL_IDX(column_names)
348
414
  self._open_idx = _find_column_index_in_list(column_names, 'open')
349
415
  self._high_idx = _find_column_index_in_list(column_names, 'high')
350
416
  self._low_idx = _find_column_index_in_list(column_names, 'low')
@@ -465,15 +531,26 @@ class QuestDBConnector(DataReader):
465
531
  chunksize=0, # TODO: use self._cursor.fetchmany in this case !!!!
466
532
  timeframe: str='1m') -> Any:
467
533
  start, end = handle_start_stop(start, stop)
468
- w0 = f"timestamp >= '{start}'" if start else ''
469
- w1 = f"timestamp <= '{end}'" if end else ''
470
- where = f'where {w0} and {w1}' if (w0 and w1) else f"where {(w0 or w1)}"
534
+ _req = self._prepare_data_sql(data_id, start, end, timeframe)
535
+
536
+ self._cursor.execute(_req) # type: ignore
537
+ records = self._cursor.fetchall() # TODO: for chunksize > 0 use fetchmany etc
538
+
539
+ names = [d.name for d in self._cursor.description] # type: ignore
540
+ transform.start_transform(data_id, names)
541
+
542
+ transform.process_data(records)
543
+ return transform.collect()
471
544
 
545
+ def _prepare_data_sql(self, data_id: str, start: str|None, end: str|None, resample: str) -> str:
472
546
  # just a temp hack - actually we need to discuss symbology etc
473
547
  symbol = data_id#.split('.')[-1]
474
548
 
475
- self._cursor.execute(
476
- f"""
549
+ w0 = f"timestamp >= '{start}'" if start else ''
550
+ w1 = f"timestamp <= '{end}'" if end else ''
551
+ where = f'where {w0} and {w1}' if (w0 and w1) else f"where {(w0 or w1)}"
552
+
553
+ return f"""
477
554
  select timestamp,
478
555
  first(open) as open,
479
556
  max(high) as high,
@@ -485,21 +562,15 @@ class QuestDBConnector(DataReader):
485
562
  sum(taker_buy_volume) as taker_buy_volume,
486
563
  sum(taker_buy_quote_volume) as taker_buy_quote_volume
487
564
  from "{symbol.upper()}" {where}
488
- SAMPLE by {timeframe};
489
- """ # type: ignore
490
- )
491
- records = self._cursor.fetchall() # TODO: for chunksize > 0 use fetchmany etc
492
- names = [d.name for d in self._cursor.description]
565
+ SAMPLE by {resample};
566
+ """
493
567
 
494
- transform.start_transform(data_id, names)
495
-
496
- # d = np.array(records)
497
- transform.process_data(records)
498
- return transform.collect()
568
+ def _prepare_names_sql(self) -> str:
569
+ return "select table_name from tables()"
499
570
 
500
571
  @_retry
501
572
  def get_names(self) -> List[str] :
502
- self._cursor.execute("select table_name from tables()")
573
+ self._cursor.execute(self._prepare_names_sql()) # type: ignore
503
574
  records = self._cursor.fetchall()
504
575
  return [r[0] for r in records]
505
576
 
@@ -511,3 +582,48 @@ class QuestDBConnector(DataReader):
511
582
  except:
512
583
  pass
513
584
 
585
+
586
+ class SnapshotsBuilder(DataTransformer):
587
+ """
588
+ Snapshots assembler from OB updates
589
+ """
590
+ def __init__(self,
591
+ levels: int=-1, # how many levels restore, 1 - TOB, -1 - all
592
+ as_frame=False # result is dataframe
593
+ ):
594
+ self.buffer = []
595
+ self.levels = levels
596
+ self.as_frame = as_frame
597
+
598
+ def start_transform(self, name: str, column_names: List[str]):
599
+ # initialize buffer / series etc
600
+ # let's keep restored snapshots into some buffer etc
601
+ self.buffer = []
602
+
603
+ # do additional init stuff here
604
+
605
+ def process_data(self, rows_data:List[List]) -> Any:
606
+ for r in rows_data:
607
+ # restore snapshots and put into buffer or series
608
+ pass
609
+
610
+ def collect(self) -> Any:
611
+ # - may be convert it to pandas DataFrame ?
612
+ if self.as_frame:
613
+ return pd.DataFrame.from_records(self.buffer) # or custom transform
614
+
615
+ # - or just returns as plain list
616
+ return self.buffer
617
+
618
+
619
+ class QuestDBOrderBookConnector(QuestDBConnector):
620
+ """
621
+ Example of custom OrderBook data connector
622
+ """
623
+
624
+ def _prepare_data_sql(self, data_id: str, start: str|None, end: str|None, resample: str|None) -> str:
625
+ raise NotImplemented("TODO")
626
+
627
+ def _prepare_names_sql(self) -> str:
628
+ # return "select table_name from tables() where ..."
629
+ raise NotImplemented("TODO")
@@ -5,10 +5,6 @@ from importlib.util import spec_from_file_location
5
5
  from importlib.machinery import ExtensionFileLoader, SourceFileLoader
6
6
  from typing import List
7
7
 
8
- # - disable warn about deprecation of imp module: after dev stage _pyxreloader will be removed
9
- import warnings
10
- warnings.filterwarnings("ignore", category=DeprecationWarning)
11
- import imp
12
8
 
13
9
  PYX_EXT = ".pyx"
14
10
  PYXDEP_EXT = ".pyxdep"
@@ -49,6 +45,10 @@ def handle_dependencies(pyxfilename):
49
45
 
50
46
 
51
47
  def handle_special_build(modname, pyxfilename):
48
+ try:
49
+ import imp
50
+ except:
51
+ return None, None
52
52
  special_build = os.path.splitext(pyxfilename)[0] + PYXBLD_EXT
53
53
  ext = None
54
54
  setup_args={}
@@ -135,6 +135,10 @@ def build_module(name, pyxfilename, user_setup_args, pyxbuild_dir=None, inplace=
135
135
 
136
136
 
137
137
  def load_module(name, pyxfilename, pyxbuild_dir=None, is_package=False, build_inplace=False, language_level=None, so_path=None):
138
+ try:
139
+ import imp
140
+ except:
141
+ return None
138
142
  try:
139
143
  if so_path is None:
140
144
  if is_package:
qubx/utils/time.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime
2
- from typing import List, Optional, Union
2
+ from typing import List, Optional, Tuple, Union
3
3
  import numpy as np
4
4
  import re
5
5
 
@@ -115,7 +115,7 @@ def infer_series_frequency(series: Union[List, pd.DataFrame, pd.Series, pd.Datet
115
115
  return np.timedelta64(max(freqs, key=freqs.get))
116
116
 
117
117
 
118
- def handle_start_stop(s: Optional[str], e: Optional[str], convert=str) -> tuple:
118
+ def handle_start_stop(s: Optional[str], e: Optional[str], convert=str) -> Tuple[str|None, str|None]:
119
119
  """
120
120
  Process start/stop times
121
121
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Qubx
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Qubx - quantitative trading framework
5
5
  Home-page: https://github.com/dmarienko/Qubx
6
6
  Author: Dmitry Marienko
@@ -15,17 +15,25 @@ Requires-Dist: croniter (>=2.0.5,<3.0.0)
15
15
  Requires-Dist: cython (==3.0.8)
16
16
  Requires-Dist: importlib-metadata
17
17
  Requires-Dist: loguru (>=0.7.2,<0.8.0)
18
+ Requires-Dist: matplotlib (>=3.8.4,<4.0.0)
18
19
  Requires-Dist: ntplib (>=0.4.0,<0.5.0)
20
+ Requires-Dist: numba (>=0.59.1,<0.60.0)
19
21
  Requires-Dist: numpy (>=1.26.3,<2.0.0)
22
+ Requires-Dist: pandas (>=2.2.2,<3.0.0)
23
+ Requires-Dist: plotly (>=5.22.0,<6.0.0)
20
24
  Requires-Dist: psycopg (>=3.1.18,<4.0.0)
25
+ Requires-Dist: psycopg-binary (>=3.1.19,<4.0.0)
26
+ Requires-Dist: psycopg-pool (>=3.2.2,<4.0.0)
21
27
  Requires-Dist: pyarrow (>=15.0.0,<16.0.0)
22
28
  Requires-Dist: pydantic (>=1.10.2,<2.0.0)
23
29
  Requires-Dist: pymongo (>=4.6.1,<5.0.0)
24
30
  Requires-Dist: pytest[lazyfixture] (>=7.2.0,<8.0.0)
25
31
  Requires-Dist: python-binance (>=1.0.19,<2.0.0)
26
32
  Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
33
+ Requires-Dist: scikit-learn (>=1.4.2,<2.0.0)
27
34
  Requires-Dist: scipy (>=1.12.0,<2.0.0)
28
35
  Requires-Dist: stackprinter (>=0.2.10,<0.3.0)
36
+ Requires-Dist: statsmodels (>=0.14.2,<0.15.0)
29
37
  Requires-Dist: tqdm
30
38
  Project-URL: Repository, https://github.com/dmarienko/Qubx
31
39
  Description-Content-Type: text/markdown
@@ -6,13 +6,13 @@ qubx/core/basics.py,sha256=2u7WV5KX-RbTmzoKfi1yT4HNLDPfQcFMCUZ1pVsM_VE,14777
6
6
  qubx/core/helpers.py,sha256=gPE78dO718NBY0-JbfqNGCzIvr4BVatFntNIy2RUrEY,11559
7
7
  qubx/core/loggers.py,sha256=HpgavBZegoDv9ssihtqX0pitXKULVAPHUpoE_volJw0,11910
8
8
  qubx/core/lookups.py,sha256=4aEC7b2AyEXFqHHGDenex3Z1FZGrpDSb8IwzBZrSqIA,13688
9
- qubx/core/series.cpython-311-x86_64-linux-gnu.so,sha256=6Thl2grZwLGSVJSe27Y4HgwIZ1HRvrLttx2QDY-bhag,698952
9
+ qubx/core/series.cpython-311-x86_64-linux-gnu.so,sha256=Wpec9yDlhedIs11mCYl8pVf30bghhwL4UHBxgMesiak,698952
10
10
  qubx/core/series.pxd,sha256=IS89NQ5FYp3T0YIHe1lELKZIAKrNvX8K6WlLyac44I4,2847
11
11
  qubx/core/series.pyx,sha256=WEAjn4j3zn540Cxx68X5gRXilvwa7NGdbki6myzZbIM,28108
12
12
  qubx/core/strategy.py,sha256=Fs4fFyHaEGYuz7mBeQHBWFu3Ipg0yFzcxXhskgsPxJE,30330
13
- qubx/core/utils.cpython-311-x86_64-linux-gnu.so,sha256=zjjop5Q7v5O7NTg4YJoweL662Z0oez2KAYJA_-WhPMw,74216
13
+ qubx/core/utils.cpython-311-x86_64-linux-gnu.so,sha256=nLncQM5F9XMpCda8IWifgSYWtoL0MCrQg27fPxK9Z2I,74216
14
14
  qubx/core/utils.pyx,sha256=6dQ8R02bl8V3f-W3Wk9-e86D9OvDz-5-4NA_dlF_xwc,1368
15
- qubx/data/readers.py,sha256=r5_DhzyaTMNGHr9sDjbIgK2kMcSC8fHYeDrb2ep1NLU,19648
15
+ qubx/data/readers.py,sha256=wQc3tsLoNcvO9c2YFDxrN8jyXtPMbR3XrA_NBA38zp8,23350
16
16
  qubx/impl/ccxt_connector.py,sha256=NqF-tgxfTATnmVqKUonNXCAzECrDU8YrgqM3Nq06fw8,9150
17
17
  qubx/impl/ccxt_customizations.py,sha256=kK_4KmOyKvDVgd4MTkVg4CyqdjE-6r41siZIvLj-A-Q,3488
18
18
  qubx/impl/ccxt_trading.py,sha256=cmg4P-zd78w-V8j3-IGS2LFxikGhxFPgmCvz3sr065Q,9097
@@ -23,17 +23,17 @@ qubx/pandaz/__init__.py,sha256=Iw5uzicYGSC3FEKZ-W1O5-7cXq_P0kH11-EcXV0zZhs,175
23
23
  qubx/pandaz/ta.py,sha256=TUvjrvmk4EQvDcXoRp6Os08-HUap-ZvpSDGawhViOgg,85271
24
24
  qubx/pandaz/utils.py,sha256=FyLKQy8spkqxhBij_nPFC_ZzI_L3-IgB9O53MqWKmq0,19109
25
25
  qubx/ta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- qubx/ta/indicators.cpython-311-x86_64-linux-gnu.so,sha256=1d34LKbFiANJPGpRtPuOluaLjhEqzv-Xr9GfSXzJ24g,284552
26
+ qubx/ta/indicators.cpython-311-x86_64-linux-gnu.so,sha256=HFcrtnfX2BiiqymRwZ5lftBsqrF-TEmZKchPVzhbUbM,284552
27
27
  qubx/ta/indicators.pyx,sha256=P-GEYUks2lSHo6hbtUFAB7TWE1AunjLR4jIjwqPHrwU,7708
28
28
  qubx/trackers/__init__.py,sha256=1y_yvIy0OQwBqfhAW_EY33NxFzFSWvI0qNAPU6zchYc,60
29
29
  qubx/trackers/rebalancers.py,sha256=QCzANCooZBi2VMCBjjCPMq_Dt1h1zrBelATnfmVve74,5522
30
30
  qubx/utils/__init__.py,sha256=XJFje4jP69pnPTp7fpTUmqwXz9PKzGYtJf8-kBofum0,273
31
- qubx/utils/_pyxreloader.py,sha256=arPR_JQsxgSju0iqI9EG_u8TfeS3BgGMBGVRXaBj-UY,12076
31
+ qubx/utils/_pyxreloader.py,sha256=FyqGzfSpZGYziB8JYS5AP3cLRAvJSIPAKgwQn0E4YQ0,12017
32
32
  qubx/utils/charting/mpl_helpers.py,sha256=ZaBrF0yOBOoVEk4TCBFi9KPyL3O5GPoteDglIzL8uSs,35158
33
33
  qubx/utils/marketdata/binance.py,sha256=36dl4rxOAGTeY3uoONmiPanj8BkP0oBdDiH-URJJo9A,10993
34
34
  qubx/utils/misc.py,sha256=z5rdz5hbRu9-F2QgF47OCkMvhfIkRKs-PHR8L5DWkBM,9831
35
35
  qubx/utils/runner.py,sha256=OY7SoRfxHwzn0rKTGB_lbg5zNASEL_49hQXWqs-LiMk,9306
36
- qubx/utils/time.py,sha256=mdQ02PGoUBm9iH_wvFIhAhOkBoJOpO24ZanWcGU8oms,4884
37
- qubx-0.1.5.dist-info/METADATA,sha256=UtgLrgWsZ3bVQiJo3Xe6sfOhPQubitebJwekMpuXLY0,2144
38
- qubx-0.1.5.dist-info/WHEEL,sha256=MLOa6LysROdjgj4FVxsHitAnIh8Be2D_c9ZSBHKrz2M,110
39
- qubx-0.1.5.dist-info/RECORD,,
36
+ qubx/utils/time.py,sha256=_DjCdQditzZwMy_8rvPdWyw5tjw__2p24LMPgXdZ8i0,4911
37
+ qubx-0.1.7.dist-info/METADATA,sha256=gjq_-njDfPiu_li6ID5hClbIvZL3NsVARvM6NRNZd9U,2490
38
+ qubx-0.1.7.dist-info/WHEEL,sha256=MLOa6LysROdjgj4FVxsHitAnIh8Be2D_c9ZSBHKrz2M,110
39
+ qubx-0.1.7.dist-info/RECORD,,
File without changes