Qubx 0.6.54__cp312-cp312-manylinux_2_39_x86_64.whl → 0.6.56__cp312-cp312-manylinux_2_39_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.

Files changed (36) hide show
  1. qubx/backtester/runner.py +1 -1
  2. qubx/core/basics.py +111 -5
  3. qubx/core/helpers.py +18 -7
  4. qubx/core/lookups.py +226 -304
  5. qubx/core/mixins/processing.py +23 -3
  6. qubx/core/series.cpython-312-x86_64-linux-gnu.so +0 -0
  7. qubx/core/utils.cpython-312-x86_64-linux-gnu.so +0 -0
  8. qubx/resources/crypto-fees.ini +98 -0
  9. qubx/resources/instruments/symbols-binance-spot.json +1 -0
  10. qubx/resources/instruments/symbols-binance.cm-future.json +1 -0
  11. qubx/resources/instruments/symbols-binance.cm-perpetual.json +1 -0
  12. qubx/resources/instruments/symbols-binance.um-future.json +1 -0
  13. qubx/resources/instruments/symbols-binance.um-perpetual.json +1 -0
  14. qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +1 -0
  15. qubx/resources/instruments/symbols-hyperliquid-spot.json +1 -0
  16. qubx/resources/instruments/symbols-hyperliquid.f-perpetual.json +1 -0
  17. qubx/resources/instruments/symbols-kraken-spot.json +1 -0
  18. qubx/resources/instruments/symbols-kraken.f-future.json +1 -0
  19. qubx/resources/instruments/symbols-kraken.f-perpetual.json +1 -0
  20. qubx/restorers/factory.py +3 -3
  21. qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so +0 -0
  22. qubx/utils/marketdata/ccxt.py +51 -6
  23. qubx/utils/marketdata/dukas.py +1 -1
  24. qubx/utils/runner/runner.py +1 -1
  25. {qubx-0.6.54.dist-info → qubx-0.6.56.dist-info}/METADATA +1 -1
  26. {qubx-0.6.54.dist-info → qubx-0.6.56.dist-info}/RECORD +29 -24
  27. qubx/resources/instruments/symbols-binance.cm.json +0 -1
  28. qubx/resources/instruments/symbols-binance.json +0 -1
  29. qubx/resources/instruments/symbols-binance.um.json +0 -1
  30. qubx/resources/instruments/symbols-bitfinex.f.json +0 -1
  31. qubx/resources/instruments/symbols-bitfinex.json +0 -1
  32. qubx/resources/instruments/symbols-kraken.f.json +0 -1
  33. qubx/resources/instruments/symbols-kraken.json +0 -1
  34. {qubx-0.6.54.dist-info → qubx-0.6.56.dist-info}/LICENSE +0 -0
  35. {qubx-0.6.54.dist-info → qubx-0.6.56.dist-info}/WHEEL +0 -0
  36. {qubx-0.6.54.dist-info → qubx-0.6.56.dist-info}/entry_points.txt +0 -0
qubx/backtester/runner.py CHANGED
@@ -439,7 +439,7 @@ class SimulationRunner:
439
439
  if isinstance(commissions, (str, type(None))):
440
440
  commissions = {e: commissions for e in exchanges}
441
441
  for exchange in exchanges:
442
- _exchange_to_tcc[exchange] = lookup.fees.find(exchange.lower(), commissions.get(exchange))
442
+ _exchange_to_tcc[exchange] = lookup.find_fees(exchange.lower(), commissions.get(exchange))
443
443
  return _exchange_to_tcc
444
444
 
445
445
  def _construct_account_processor(
qubx/core/basics.py CHANGED
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from dataclasses import dataclass, field
2
3
  from datetime import datetime
3
4
  from enum import StrEnum
@@ -176,6 +177,14 @@ class MarketType(StrEnum):
176
177
 
177
178
  @dataclass
178
179
  class Instrument:
180
+ """
181
+ Instrument class.
182
+
183
+ - 2025-06-11: Important change for FUTURE type: now instrument's symbol contains delivery date in format YYYYMMDD.
184
+ So now for let's say september's BTCUSDT future, symbol would be BTCUSD.20250914
185
+ and full id is `BINANCE.UM:FUTURE:BTCUSD.20250914`
186
+ """
187
+
179
188
  symbol: str
180
189
  asset_type: AssetType
181
190
  market_type: MarketType
@@ -192,8 +201,10 @@ class Instrument:
192
201
  maint_margin: float = 0.0 # maintenance margin
193
202
  liquidation_fee: float = 0.0 # liquidation fee
194
203
  contract_size: float = 1.0 # contract size
195
- onboard_date: datetime | None = None
196
- delivery_date: datetime | None = None
204
+ onboard_date: datetime | None = None # date when instrument was listed on the exchange
205
+ delivery_date: datetime | None = None # date when instrument is delivered
206
+ delist_date: datetime | None = None # date when instrument is delisted
207
+ inverse: bool = False # if true, then the future is inverse
197
208
 
198
209
  @property
199
210
  def price_precision(self):
@@ -437,9 +448,6 @@ class AssetBalance:
437
448
  return self
438
449
 
439
450
 
440
- MARKET_TYPE = Literal["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
441
-
442
-
443
451
  class Position:
444
452
  instrument: Instrument # instrument for this position
445
453
  quantity: float = 0.0 # quantity positive for long and negative for short
@@ -878,3 +886,101 @@ class RestoredState:
878
886
  balances: dict[str, AssetBalance]
879
887
  instrument_to_target_positions: dict[Instrument, list[TargetPosition]]
880
888
  positions: dict[Instrument, Position]
889
+
890
+
891
+ class InstrumentsLookup:
892
+ def get_lookup(self) -> dict[str, Instrument]: ...
893
+
894
+ def find(
895
+ self,
896
+ exchange: str,
897
+ base: str,
898
+ quote: str,
899
+ settle: str | None = None,
900
+ market_type: MarketType | None = None,
901
+ ) -> Instrument | None:
902
+ for i in self.get_lookup().values():
903
+ if (
904
+ i.exchange == exchange
905
+ and ((i.base == base and i.quote == quote) or (i.base == quote and i.quote == base))
906
+ and (market_type is None or i.market_type == market_type)
907
+ ):
908
+ if settle is not None and i.settle is not None:
909
+ if i.settle == settle:
910
+ return i
911
+ else:
912
+ return i
913
+ return None
914
+
915
+ def find_symbol(self, exchange: str, symbol: str, market_type: MarketType | None = None) -> Instrument | None:
916
+ for i in self.get_lookup().values():
917
+ if (
918
+ (i.exchange == exchange)
919
+ and (i.symbol == symbol)
920
+ and (market_type is None or i.market_type == market_type)
921
+ ):
922
+ return i
923
+
924
+ return None
925
+
926
+ def find_instruments(
927
+ self,
928
+ exchange: str,
929
+ quote: str | None = None,
930
+ market_type: MarketType | None = None,
931
+ as_of: str | pd.Timestamp | None = None,
932
+ ) -> list[Instrument]:
933
+ """
934
+ Find instruments by exchange, quote, market type and as of date.
935
+ If as_of is not None, then only instruments that are not delisted after as_of date will be returned.
936
+ - exchange: str - exchange name
937
+ - quote: str | None - quote currency
938
+ - market_type: MarketType | None - market type
939
+ - as_of is a string in format YYYY-MM-DD or pd.Timestamp or None
940
+ """
941
+ _limit_time = pd.Timestamp(as_of) if as_of else None
942
+ return [
943
+ i
944
+ for i in self.get_lookup().values()
945
+ if i.exchange == exchange
946
+ and (quote is None or i.quote == quote)
947
+ and (market_type is None or i.market_type == market_type)
948
+ and (
949
+ _limit_time is None
950
+ or (i.delist_date is None)
951
+ or (pd.Timestamp(i.delist_date).tz_localize(None) >= _limit_time)
952
+ )
953
+ ]
954
+
955
+ def find_aux_instrument_for(
956
+ self, instrument: Instrument, base_currency: str, market_type: MarketType | None = None
957
+ ) -> Instrument | None:
958
+ """
959
+ Tries to find aux instrument (for conversions to funded currency)
960
+ for example:
961
+ ETHBTC -> BTCUSDT for base_currency USDT
962
+ EURGBP -> GBPUSD for base_currency USD
963
+ ...
964
+ """
965
+ if market_type is None:
966
+ market_type = instrument.market_type
967
+ base_currency = base_currency.upper()
968
+ if instrument.quote != base_currency:
969
+ return self.find(instrument.exchange, instrument.quote, base_currency, market_type=market_type)
970
+ return None
971
+
972
+ def __getitem__(self, spath: str) -> list[Instrument]:
973
+ """
974
+ Helper method for finding instruments by pattern.
975
+ It's convenient to use in research mode.
976
+ """
977
+ res = []
978
+ c = re.compile(spath)
979
+ for k, v in self.get_lookup().items():
980
+ if re.match(c, k):
981
+ res.append(v)
982
+ return res
983
+
984
+
985
+ class FeesLookup:
986
+ def find_fees(self, exchange: str, spec: str | None) -> TransactionCostsCalculator: ...
qubx/core/helpers.py CHANGED
@@ -6,15 +6,15 @@ import time
6
6
  from collections import defaultdict, deque
7
7
  from inspect import isbuiltin, isclass, isfunction, ismethod, ismethoddescriptor
8
8
  from threading import Thread
9
- from typing import Any, Callable, Dict, List
9
+ from typing import Any, Callable
10
10
 
11
11
  import numpy as np
12
12
  import pandas as pd
13
13
  from croniter import croniter
14
14
 
15
15
  from qubx import logger
16
- from qubx.core.basics import SW, CtrlChannel, DataType, Instrument, Timestamped
17
- from qubx.core.series import OHLCV, Bar, OrderBook, Quote, Trade
16
+ from qubx.core.basics import SW, CtrlChannel, DataType, Instrument, Timestamped, dt_64
17
+ from qubx.core.series import OHLCV, Bar, OrderBook, Quote, Trade, time_as_nsec
18
18
  from qubx.utils.time import convert_seconds_to_str, convert_tf_str_td64, interval_to_cron
19
19
 
20
20
 
@@ -26,9 +26,9 @@ class CachedMarketDataHolder:
26
26
  default_timeframe: np.timedelta64
27
27
  _last_bar: dict[Instrument, Bar | None]
28
28
  _ohlcvs: dict[Instrument, dict[np.timedelta64, OHLCV]]
29
- _updates: dict[Instrument, Any]
29
+ _updates: dict[Instrument, Bar | Quote | Trade]
30
30
 
31
- _instr_to_sub_to_buffer: Dict[Instrument, Dict[str, deque]]
31
+ _instr_to_sub_to_buffer: dict[Instrument, dict[str, deque]]
32
32
 
33
33
  def __init__(self, default_timeframe: str | None = None, max_buffer_size: int = 10_000) -> None:
34
34
  self._ohlcvs = dict()
@@ -93,7 +93,7 @@ class CachedMarketDataHolder:
93
93
 
94
94
  return self._ohlcvs[instrument][tf]
95
95
 
96
- def get_data(self, instrument: Instrument, event_type: str) -> List[Any]:
96
+ def get_data(self, instrument: Instrument, event_type: str) -> list[Any]:
97
97
  return list(self._instr_to_sub_to_buffer[instrument][event_type])
98
98
 
99
99
  def update(
@@ -126,7 +126,7 @@ class CachedMarketDataHolder:
126
126
  pass
127
127
 
128
128
  @SW.watch("CachedMarketDataHolder")
129
- def update_by_bars(self, instrument: Instrument, timeframe: str | np.timedelta64, bars: List[Bar]) -> OHLCV:
129
+ def update_by_bars(self, instrument: Instrument, timeframe: str | np.timedelta64, bars: list[Bar]) -> OHLCV:
130
130
  """
131
131
  Update or create OHLCV series with the provided historical bars.
132
132
 
@@ -203,6 +203,17 @@ class CachedMarketDataHolder:
203
203
  continue
204
204
  ser.update(trade.time, trade.price, total_vol, bought_vol)
205
205
 
206
+ def finalize_all_ohlc(self, time: dt_64):
207
+ """
208
+ Finalize all OHLCV series at the given time.
209
+ FIXME: (2025-06-17) This is part of urgent live fix and must be removed in future !!!.
210
+ """
211
+ for instrument in self._ohlcvs.keys():
212
+ # - use most recent update
213
+ if (_u := self._updates.get(instrument)) is not None:
214
+ _px = extract_price(_u)
215
+ self.update_by_bar(instrument, Bar(time_as_nsec(time), _px, _px, _px, _px, 0, 0))
216
+
206
217
 
207
218
  SPEC_REGEX = re.compile(
208
219
  r"((?P<type>[A-Za-z]+)(\.?(?P<timeframe>[0-9A-Za-z]+))?\ *:)?"