Qubx 0.6.71__tar.gz → 0.6.73__tar.gz
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-0.6.71 → qubx-0.6.73}/PKG-INFO +1 -1
- {qubx-0.6.71 → qubx-0.6.73}/pyproject.toml +1 -1
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/data.py +16 -6
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/runner.py +7 -4
- qubx-0.6.73/src/qubx/connectors/ccxt/adapters/polling_adapter.py +247 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/connection_manager.py +122 -133
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/data.py +108 -43
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/__init__.py +26 -0
- qubx-0.6.73/src/qubx/connectors/ccxt/exchanges/base.py +63 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +186 -166
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +3 -1
- qubx-0.6.73/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +6 -0
- qubx-0.6.73/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +69 -0
- qubx-0.6.73/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +519 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +3 -1
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/factory.py +4 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/base.py +2 -1
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/funding_rate.py +88 -88
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/liquidation.py +1 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/ohlc.py +63 -45
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/open_interest.py +12 -13
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/orderbook.py +65 -39
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/quote.py +3 -1
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/trade.py +15 -1
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/reader.py +179 -72
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/subscription_config.py +39 -34
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/subscription_manager.py +103 -118
- qubx-0.6.73/src/qubx/connectors/ccxt/subscription_orchestrator.py +365 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/utils.py +50 -26
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/account.py +5 -5
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/basics.py +24 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/initializer.py +7 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/interfaces.py +21 -5
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/subscription.py +6 -1
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/readers.py +11 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/advanced.py +41 -4
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/charting/mpl_helpers.py +134 -0
- qubx-0.6.73/src/qubx/utils/charting/orderbook.py +314 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/runner.py +9 -0
- qubx-0.6.71/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -439
- qubx-0.6.71/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -1
- qubx-0.6.71/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -161
- qubx-0.6.71/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -328
- {qubx-0.6.71 → qubx-0.6.73}/LICENSE +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/README.md +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/build.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/account.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/management.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/commands.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/release.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/tui.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/context.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/deque.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/helpers.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/metrics.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/series.pxd +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/series.pyi +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/series.pyx +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/stale_data_detector.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/composite.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/registry.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/base.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/composite.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/csv.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/indicator.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/questdb.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/core.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/orderbook.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/price.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/trades.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/utils.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/health/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/health/base.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/csv.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/factory.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/mongo.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/notifications/throttler.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/position.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/base.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/template.yml +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/questdb.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/time.py +0 -0
- {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/version.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "Qubx"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.73"
|
|
8
8
|
description = "Qubx - Quantitative Trading Framework"
|
|
9
9
|
authors = [ "Dmitry Marienko <dmitry.marienko@xlydian.com>", "Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",]
|
|
10
10
|
readme = "README.md"
|
|
@@ -27,7 +27,7 @@ def _get_first_existing(data: dict, keys: list, default: T = None) -> T:
|
|
|
27
27
|
data_get = data.get # Cache method lookup
|
|
28
28
|
sentinel = object()
|
|
29
29
|
for key in keys:
|
|
30
|
-
if (value := data_get(key, sentinel)) is not sentinel:
|
|
30
|
+
if (value := data_get(key, sentinel)) is not sentinel and value is not None:
|
|
31
31
|
return value
|
|
32
32
|
return default
|
|
33
33
|
|
|
@@ -169,14 +169,24 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
169
169
|
if _b_ts_0 <= cut_time_ns and cut_time_ns < _b_ts_1:
|
|
170
170
|
break
|
|
171
171
|
|
|
172
|
+
# Handle None values in OHLC data
|
|
173
|
+
open_price = r.data["open"]
|
|
174
|
+
high_price = r.data["high"]
|
|
175
|
+
low_price = r.data["low"]
|
|
176
|
+
close_price = r.data["close"]
|
|
177
|
+
|
|
178
|
+
# Skip this record if any OHLC value is None
|
|
179
|
+
if open_price is None or high_price is None or low_price is None or close_price is None:
|
|
180
|
+
continue
|
|
181
|
+
|
|
172
182
|
bars.append(
|
|
173
183
|
Bar(
|
|
174
184
|
_b_ts_0,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
volume=r.data.get("volume", 0),
|
|
185
|
+
open_price,
|
|
186
|
+
high_price,
|
|
187
|
+
low_price,
|
|
188
|
+
close_price,
|
|
189
|
+
volume=r.data.get("volume", 0) or 0, # Handle None volume
|
|
180
190
|
bought_volume=_get_first_existing(r.data, ["taker_buy_volume", "bought_volume"], 0),
|
|
181
191
|
volume_quote=_get_first_existing(r.data, ["quote_volume", "volume_quote"], 0),
|
|
182
192
|
bought_volume_quote=_get_first_existing(
|
|
@@ -4,10 +4,9 @@ import numpy as np
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from tqdm.auto import tqdm
|
|
6
6
|
|
|
7
|
-
from qubx import logger
|
|
7
|
+
from qubx import QubxLogConfig, logger
|
|
8
8
|
from qubx.backtester.sentinels import NoDataContinue
|
|
9
9
|
from qubx.backtester.simulated_data import IterableSimulationData
|
|
10
|
-
from qubx.backtester.utils import SimulationDataConfig, TimeGuardedWrapper
|
|
11
10
|
from qubx.core.account import CompositeAccountProcessor
|
|
12
11
|
from qubx.core.basics import SW, DataType, Instrument, TransactionCostsCalculator
|
|
13
12
|
from qubx.core.context import StrategyContext
|
|
@@ -41,6 +40,7 @@ from .utils import (
|
|
|
41
40
|
SimulatedTimeProvider,
|
|
42
41
|
SimulationDataConfig,
|
|
43
42
|
SimulationSetup,
|
|
43
|
+
TimeGuardedWrapper,
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
|
|
@@ -328,6 +328,7 @@ class SimulationRunner:
|
|
|
328
328
|
|
|
329
329
|
if not _run(instrument, data_type, event, is_hist):
|
|
330
330
|
return False
|
|
331
|
+
|
|
331
332
|
return True
|
|
332
333
|
|
|
333
334
|
def _handle_no_data_scenario(self, stop_time):
|
|
@@ -356,8 +357,9 @@ class SimulationRunner:
|
|
|
356
357
|
return False # No scheduled events, stop simulation
|
|
357
358
|
|
|
358
359
|
def print_latency_report(self) -> None:
|
|
359
|
-
_l_r
|
|
360
|
-
|
|
360
|
+
if (_l_r := SW.latency_report()) is not None:
|
|
361
|
+
_llvl = QubxLogConfig.get_log_level()
|
|
362
|
+
QubxLogConfig.set_log_level("INFO")
|
|
361
363
|
logger.info(
|
|
362
364
|
"<BLUE> Time spent in simulation report </BLUE>\n<r>"
|
|
363
365
|
+ _frame_to_str(
|
|
@@ -365,6 +367,7 @@ class SimulationRunner:
|
|
|
365
367
|
)
|
|
366
368
|
+ "</r>"
|
|
367
369
|
)
|
|
370
|
+
QubxLogConfig.set_log_level(_llvl)
|
|
368
371
|
|
|
369
372
|
def _create_backtest_context(self) -> IStrategyContext:
|
|
370
373
|
logger.debug(
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simplified polling adapter to convert CCXT fetch_* methods into watch_* behavior.
|
|
3
|
+
|
|
4
|
+
This adapter provides a much simpler approach:
|
|
5
|
+
- No background tasks or queues
|
|
6
|
+
- get_next_data() waits until it's time to poll, then polls synchronously
|
|
7
|
+
- Time-aligned polling (e.g., 11:30, 11:35, 11:40 for 5-minute intervals)
|
|
8
|
+
- Immediate polling when symbols change
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import math
|
|
13
|
+
import time
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any, Callable, Dict, List, Optional, Set
|
|
16
|
+
|
|
17
|
+
from qubx import logger
|
|
18
|
+
|
|
19
|
+
# Constants
|
|
20
|
+
DEFAULT_POLL_INTERVAL = 300 # 5 minutes
|
|
21
|
+
MIN_POLL_INTERVAL = 1 # 1 second minimum
|
|
22
|
+
MAX_POLL_INTERVAL = 3600 # 1 hour maximum
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class PollingConfig:
|
|
27
|
+
"""Configuration for polling adapter."""
|
|
28
|
+
|
|
29
|
+
poll_interval_seconds: float = DEFAULT_POLL_INTERVAL
|
|
30
|
+
|
|
31
|
+
def __post_init__(self):
|
|
32
|
+
"""Validate configuration after initialization."""
|
|
33
|
+
if not MIN_POLL_INTERVAL <= self.poll_interval_seconds <= MAX_POLL_INTERVAL:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"poll_interval_seconds must be between {MIN_POLL_INTERVAL} and {MAX_POLL_INTERVAL}, "
|
|
36
|
+
f"got {self.poll_interval_seconds}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PollingToWebSocketAdapter:
|
|
41
|
+
"""
|
|
42
|
+
Simplified polling adapter that polls synchronously when data is requested.
|
|
43
|
+
|
|
44
|
+
Key features:
|
|
45
|
+
- No background tasks or queues
|
|
46
|
+
- Time-aligned polling (respects clock boundaries)
|
|
47
|
+
- Immediate polling when symbols change
|
|
48
|
+
- Thread-safe symbol management
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
fetch_method: Callable,
|
|
54
|
+
symbols: Optional[List[str]] = None,
|
|
55
|
+
params: Optional[Dict[str, Any]] = None,
|
|
56
|
+
config: Optional[PollingConfig] = None,
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Initialize the simplified polling adapter.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
fetch_method: The CCXT fetch_* method to call
|
|
63
|
+
symbols: Initial list of symbols to watch
|
|
64
|
+
params: Additional parameters for fetch_method
|
|
65
|
+
config: PollingConfig instance (uses default if None)
|
|
66
|
+
"""
|
|
67
|
+
self.config = config if config is not None else PollingConfig()
|
|
68
|
+
self.fetch_method = fetch_method
|
|
69
|
+
self.params = params or {}
|
|
70
|
+
self.adapter_id = f"polling_adapter_{id(self)}"
|
|
71
|
+
|
|
72
|
+
# Thread-safe symbol management
|
|
73
|
+
self._symbols_lock = asyncio.Lock()
|
|
74
|
+
self._symbols: Set[str] = set(symbols or [])
|
|
75
|
+
|
|
76
|
+
# Polling state
|
|
77
|
+
self._last_poll_time: Optional[float] = None
|
|
78
|
+
self._symbols_changed = False # Flag to trigger immediate poll
|
|
79
|
+
|
|
80
|
+
# Statistics
|
|
81
|
+
self._poll_count = 0
|
|
82
|
+
self._error_count = 0
|
|
83
|
+
|
|
84
|
+
async def get_next_data(self) -> Dict[str, Any]:
|
|
85
|
+
"""
|
|
86
|
+
Get the next available data by waiting until it's time to poll, then polling.
|
|
87
|
+
|
|
88
|
+
This method:
|
|
89
|
+
1. Checks if symbols changed (immediate poll)
|
|
90
|
+
2. Calculates when next poll should happen based on time alignment
|
|
91
|
+
3. Waits until that time
|
|
92
|
+
4. Polls and returns fresh data
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Dictionary containing fetched data for symbols
|
|
96
|
+
"""
|
|
97
|
+
async with self._symbols_lock:
|
|
98
|
+
current_symbols = list(self._symbols)
|
|
99
|
+
symbols_changed = self._symbols_changed
|
|
100
|
+
|
|
101
|
+
# If symbols changed, poll immediately
|
|
102
|
+
if symbols_changed:
|
|
103
|
+
logger.debug(f"Symbols changed, polling immediately for adapter {self.adapter_id}")
|
|
104
|
+
async with self._symbols_lock:
|
|
105
|
+
self._symbols_changed = False
|
|
106
|
+
return await self._poll_now(current_symbols)
|
|
107
|
+
|
|
108
|
+
# Calculate wait time for next aligned poll
|
|
109
|
+
wait_time = self._calculate_wait_time()
|
|
110
|
+
|
|
111
|
+
if wait_time > 0:
|
|
112
|
+
logger.debug(f"Waiting {wait_time:.1f}s for next poll cycle for adapter {self.adapter_id}")
|
|
113
|
+
await asyncio.sleep(wait_time)
|
|
114
|
+
|
|
115
|
+
# Time to poll
|
|
116
|
+
logger.debug(f"Polling now for adapter {self.adapter_id}")
|
|
117
|
+
return await self._poll_now(current_symbols)
|
|
118
|
+
|
|
119
|
+
def _calculate_wait_time(self) -> float:
|
|
120
|
+
"""
|
|
121
|
+
Calculate how long to wait until the next aligned poll time.
|
|
122
|
+
|
|
123
|
+
For intervals >= 1 minute: aligns to clock boundaries (11:30, 11:35, 11:40)
|
|
124
|
+
For intervals < 1 minute: uses simple interval-based timing
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Number of seconds to wait (0 if should poll now)
|
|
128
|
+
"""
|
|
129
|
+
current_time = time.time()
|
|
130
|
+
interval_seconds = self.config.poll_interval_seconds
|
|
131
|
+
|
|
132
|
+
# First poll is always immediate
|
|
133
|
+
if self._last_poll_time is None:
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
if interval_seconds >= 60:
|
|
137
|
+
# Time-aligned polling for intervals >= 1 minute using UTC
|
|
138
|
+
# Calculate next boundary based on seconds since epoch
|
|
139
|
+
next_boundary = math.ceil(current_time / interval_seconds) * interval_seconds
|
|
140
|
+
wait_time = next_boundary - current_time
|
|
141
|
+
return max(0, wait_time)
|
|
142
|
+
else:
|
|
143
|
+
# Simple interval-based polling for sub-minute intervals
|
|
144
|
+
next_poll_time = self._last_poll_time + interval_seconds
|
|
145
|
+
wait_time = next_poll_time - current_time
|
|
146
|
+
return max(0, wait_time)
|
|
147
|
+
|
|
148
|
+
async def _poll_now(self, symbols: List[str]) -> Dict[str, Any]:
|
|
149
|
+
"""
|
|
150
|
+
Perform a poll operation immediately.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
symbols: List of symbols to poll for
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Dictionary containing fetched data for symbols
|
|
157
|
+
"""
|
|
158
|
+
self._poll_count += 1
|
|
159
|
+
self._last_poll_time = time.time()
|
|
160
|
+
|
|
161
|
+
logger.debug(f"Polling {len(symbols) if symbols else 'all'} symbols for adapter {self.adapter_id}")
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
# Filter out adapter-specific parameters
|
|
165
|
+
adapter_params = {"pollInterval", "interval", "updateInterval", "poll_interval_minutes"}
|
|
166
|
+
fetch_params = {k: v for k, v in self.params.items() if k not in adapter_params}
|
|
167
|
+
|
|
168
|
+
# Call the fetch method
|
|
169
|
+
result = await self.fetch_method(symbols, **fetch_params)
|
|
170
|
+
|
|
171
|
+
logger.debug(f"Poll completed successfully for adapter {self.adapter_id}")
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
self._error_count += 1
|
|
176
|
+
logger.error(f"Poll failed for adapter {self.adapter_id}: {e}")
|
|
177
|
+
raise
|
|
178
|
+
|
|
179
|
+
async def update_symbols(self, new_symbols: List[str]) -> None:
|
|
180
|
+
"""
|
|
181
|
+
Update the symbol list.
|
|
182
|
+
|
|
183
|
+
If symbols changed, the next call to get_next_data() will poll immediately.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
new_symbols: New complete list of symbols to watch
|
|
187
|
+
"""
|
|
188
|
+
async with self._symbols_lock:
|
|
189
|
+
old_symbols = self._symbols.copy()
|
|
190
|
+
self._symbols = set(new_symbols or [])
|
|
191
|
+
symbols_changed = old_symbols != self._symbols
|
|
192
|
+
|
|
193
|
+
if symbols_changed:
|
|
194
|
+
self._symbols_changed = True
|
|
195
|
+
logger.debug(
|
|
196
|
+
f"Symbols updated for adapter {self.adapter_id}: {len(old_symbols)} -> {len(self._symbols)}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
async def add_symbols(self, new_symbols: List[str]) -> None:
|
|
200
|
+
"""Add symbols to the existing watch list."""
|
|
201
|
+
if not new_symbols:
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
async with self._symbols_lock:
|
|
205
|
+
before_count = len(self._symbols)
|
|
206
|
+
self._symbols.update(new_symbols)
|
|
207
|
+
after_count = len(self._symbols)
|
|
208
|
+
|
|
209
|
+
if after_count > before_count:
|
|
210
|
+
self._symbols_changed = True
|
|
211
|
+
logger.debug(f"Added {after_count - before_count} symbols to adapter {self.adapter_id}")
|
|
212
|
+
|
|
213
|
+
async def remove_symbols(self, symbols_to_remove: List[str]) -> None:
|
|
214
|
+
"""Remove symbols from the watch list."""
|
|
215
|
+
if not symbols_to_remove:
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
async with self._symbols_lock:
|
|
219
|
+
before_count = len(self._symbols)
|
|
220
|
+
self._symbols.difference_update(symbols_to_remove)
|
|
221
|
+
after_count = len(self._symbols)
|
|
222
|
+
|
|
223
|
+
if after_count < before_count:
|
|
224
|
+
self._symbols_changed = True
|
|
225
|
+
logger.debug(f"Removed {before_count - after_count} symbols from adapter {self.adapter_id}")
|
|
226
|
+
|
|
227
|
+
def is_watching(self, symbol: Optional[str] = None) -> bool:
|
|
228
|
+
"""Check if adapter has symbols configured to watch."""
|
|
229
|
+
if symbol is None:
|
|
230
|
+
return len(self._symbols) > 0
|
|
231
|
+
else:
|
|
232
|
+
return symbol in self._symbols
|
|
233
|
+
|
|
234
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
235
|
+
"""Get adapter statistics for monitoring."""
|
|
236
|
+
return {
|
|
237
|
+
"adapter_id": self.adapter_id,
|
|
238
|
+
"symbol_count": len(self._symbols),
|
|
239
|
+
"poll_count": self._poll_count,
|
|
240
|
+
"error_count": self._error_count,
|
|
241
|
+
"last_poll_time": self._last_poll_time,
|
|
242
|
+
"poll_interval_seconds": self.config.poll_interval_seconds,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async def stop(self) -> None:
|
|
246
|
+
"""Stop the adapter (cleanup method for compatibility)."""
|
|
247
|
+
logger.debug(f"Adapter {self.adapter_id} stopped (polled {self._poll_count} times, {self._error_count} errors)")
|