Qubx 0.6.84__tar.gz → 0.6.87__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.84 → qubx-0.6.87}/PKG-INFO +1 -1
- {qubx-0.6.84 → qubx-0.6.87}/pyproject.toml +1 -1
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/management.py +3 -2
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/runner.py +1 -1
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/commands.py +46 -1
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/account.py +1 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +17 -9
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +1 -1
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/funding_rate.py +3 -3
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/orderbook.py +8 -6
- qubx-0.6.87/src/qubx/connectors/ccxt/handlers/trade.py +207 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/reader.py +3 -2
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/helpers.py +9 -3
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/interfaces.py +7 -6
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/metrics.py +74 -14
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/subscription.py +7 -1
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/series.pxd +3 -2
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/series.pyi +3 -2
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/series.pyx +30 -5
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/base.py +23 -14
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/composite.py +13 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/csv.py +2 -1
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/indicator.py +4 -2
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/inmemory.py +5 -4
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/prometheus.py +2 -2
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/questdb.py +16 -10
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/formatters/__init__.py +8 -1
- qubx-0.6.87/src/qubx/exporters/formatters/target_position.py +78 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/health/base.py +7 -10
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/configs.py +120 -17
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/runner.py +6 -6
- qubx-0.6.84/src/qubx/connectors/ccxt/handlers/trade.py +0 -111
- {qubx-0.6.84 → qubx-0.6.87}/LICENSE +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/README.md +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/build.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/account.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/data.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/release.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/tui.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/account.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/basics.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/context.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/deque.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/initializer.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/stale_data_detector.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/composite.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/readers.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/registry.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/core.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/orderbook.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/price.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/trades.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/utils.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/health/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/csv.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/factory.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/mongo.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/notifications/throttler.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/position.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/base.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/template.yml +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/questdb.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/time.py +0 -0
- {qubx-0.6.84 → qubx-0.6.87}/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.87"
|
|
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"
|
|
@@ -327,10 +327,11 @@ class BacktestsResultsManager:
|
|
|
327
327
|
if not as_table:
|
|
328
328
|
print(_s)
|
|
329
329
|
|
|
330
|
+
dd_column = "max_dd_pct" if "max_dd_pct" in metrics else "mdd_pct"
|
|
330
331
|
if with_metrics:
|
|
331
332
|
_m_repr = (
|
|
332
333
|
pd.DataFrame.from_dict(metrics, orient="index")
|
|
333
|
-
.T[["gain", "cagr", "sharpe", "qr",
|
|
334
|
+
.T[["gain", "cagr", "sharpe", "qr", dd_column, "mdd_usd", "fees", "execs"]]
|
|
334
335
|
.astype(float)
|
|
335
336
|
)
|
|
336
337
|
_m_repr = _m_repr.round(3).to_string(index=False)
|
|
@@ -345,7 +346,7 @@ class BacktestsResultsManager:
|
|
|
345
346
|
metrics = {
|
|
346
347
|
m: round(v, 3)
|
|
347
348
|
for m, v in metrics.items()
|
|
348
|
-
if m in ["gain", "cagr", "sharpe", "qr",
|
|
349
|
+
if m in ["gain", "cagr", "sharpe", "qr", dd_column, "mdd_usd", "fees", "execs"]
|
|
349
350
|
}
|
|
350
351
|
_t_rep.append(
|
|
351
352
|
{"Index": info.get("idx", ""), "Strategy": name}
|
|
@@ -137,6 +137,51 @@ def ls(directory: str):
|
|
|
137
137
|
ls_strats(directory)
|
|
138
138
|
|
|
139
139
|
|
|
140
|
+
@main.command()
|
|
141
|
+
@click.argument("config-file", type=Path, required=True)
|
|
142
|
+
@click.option(
|
|
143
|
+
"--no-check-imports",
|
|
144
|
+
is_flag=True,
|
|
145
|
+
default=False,
|
|
146
|
+
help="Skip checking if strategy class can be imported",
|
|
147
|
+
show_default=True,
|
|
148
|
+
)
|
|
149
|
+
def validate(config_file: Path, no_check_imports: bool):
|
|
150
|
+
"""
|
|
151
|
+
Validates a strategy configuration file without running it.
|
|
152
|
+
|
|
153
|
+
Checks for:
|
|
154
|
+
- Valid YAML syntax
|
|
155
|
+
- Required configuration fields
|
|
156
|
+
- Strategy class exists and can be imported (unless --no-check-imports)
|
|
157
|
+
- Exchange configurations are valid
|
|
158
|
+
- Simulation parameters are valid (if present)
|
|
159
|
+
|
|
160
|
+
Returns exit code 0 if valid, 1 if invalid.
|
|
161
|
+
"""
|
|
162
|
+
from qubx.utils.runner.configs import validate_strategy_config
|
|
163
|
+
|
|
164
|
+
result = validate_strategy_config(config_file, check_imports=not no_check_imports)
|
|
165
|
+
|
|
166
|
+
if result.valid:
|
|
167
|
+
click.echo(click.style("✓ Configuration is valid", fg="green", bold=True))
|
|
168
|
+
if result.warnings:
|
|
169
|
+
click.echo(click.style("\nWarnings:", fg="yellow", bold=True))
|
|
170
|
+
for warning in result.warnings:
|
|
171
|
+
click.echo(click.style(f" - {warning}", fg="yellow"))
|
|
172
|
+
raise SystemExit(0)
|
|
173
|
+
else:
|
|
174
|
+
click.echo(click.style("✗ Configuration is invalid", fg="red", bold=True))
|
|
175
|
+
click.echo(click.style("\nErrors:", fg="red", bold=True))
|
|
176
|
+
for error in result.errors:
|
|
177
|
+
click.echo(click.style(f" - {error}", fg="red"))
|
|
178
|
+
if result.warnings:
|
|
179
|
+
click.echo(click.style("\nWarnings:", fg="yellow", bold=True))
|
|
180
|
+
for warning in result.warnings:
|
|
181
|
+
click.echo(click.style(f" - {warning}", fg="yellow"))
|
|
182
|
+
raise SystemExit(1)
|
|
183
|
+
|
|
184
|
+
|
|
140
185
|
@main.command()
|
|
141
186
|
@click.argument(
|
|
142
187
|
"directory",
|
|
@@ -358,7 +403,7 @@ def init(
|
|
|
358
403
|
The generated strategy can be run immediately with:
|
|
359
404
|
poetry run qubx run --config config.yml --paper
|
|
360
405
|
"""
|
|
361
|
-
from qubx.templates import
|
|
406
|
+
from qubx.templates import TemplateError, TemplateManager
|
|
362
407
|
|
|
363
408
|
try:
|
|
364
409
|
manager = TemplateManager()
|
|
@@ -130,6 +130,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
|
|
|
130
130
|
|
|
131
131
|
if not self.exchange_manager.exchange.isSandboxModeEnabled:
|
|
132
132
|
# - start polling tasks
|
|
133
|
+
self._loop.submit(self.exchange_manager.exchange.load_markets()).result()
|
|
133
134
|
self._polling_tasks["balance"] = self._loop.submit(
|
|
134
135
|
self._poller("balance", self._update_balance, self.balance_interval)
|
|
135
136
|
)
|
|
@@ -9,7 +9,7 @@ from qubx.core.exceptions import BadRequest
|
|
|
9
9
|
class HyperliquidCcxtBroker(CcxtBroker):
|
|
10
10
|
"""
|
|
11
11
|
HyperLiquid-specific broker that handles market order slippage requirements.
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
HyperLiquid requires a price even for market orders to calculate max slippage.
|
|
14
14
|
This broker automatically calculates slippage-protected prices for market orders.
|
|
15
15
|
"""
|
|
@@ -17,7 +17,7 @@ class HyperliquidCcxtBroker(CcxtBroker):
|
|
|
17
17
|
def __init__(
|
|
18
18
|
self,
|
|
19
19
|
*args,
|
|
20
|
-
market_order_slippage: float = 0.
|
|
20
|
+
market_order_slippage: float = 0.01, # 5% default slippage
|
|
21
21
|
**kwargs,
|
|
22
22
|
):
|
|
23
23
|
super().__init__(*args, **kwargs)
|
|
@@ -38,21 +38,29 @@ class HyperliquidCcxtBroker(CcxtBroker):
|
|
|
38
38
|
if order_type.lower() == "market" and price is None:
|
|
39
39
|
quote = self.data_provider.get_quote(instrument)
|
|
40
40
|
if quote is None:
|
|
41
|
-
logger.warning(
|
|
42
|
-
|
|
41
|
+
logger.warning(
|
|
42
|
+
f"[<y>{instrument.symbol}</y>] :: Quote is not available for market order slippage calculation."
|
|
43
|
+
)
|
|
44
|
+
raise BadRequest(
|
|
45
|
+
f"Quote is not available for market order slippage calculation for {instrument.symbol}"
|
|
46
|
+
)
|
|
43
47
|
|
|
44
48
|
# Get slippage from options or use default
|
|
45
49
|
slippage = options.get("slippage", self.market_order_slippage)
|
|
46
|
-
|
|
50
|
+
|
|
47
51
|
# Calculate slippage-protected price
|
|
48
52
|
if order_side.upper() == "BUY":
|
|
49
53
|
# For buy orders, add slippage to ask price to ensure execution
|
|
50
54
|
price = quote.ask * (1 + slippage)
|
|
51
|
-
logger.debug(
|
|
55
|
+
logger.debug(
|
|
56
|
+
f"[<y>{instrument.symbol}</y>] :: Market BUY order: using slippage-protected price {price:.6f} (ask: {quote.ask:.6f}, slippage: {slippage:.1%})"
|
|
57
|
+
)
|
|
52
58
|
else: # SELL
|
|
53
59
|
# For sell orders, subtract slippage from bid price to ensure execution
|
|
54
60
|
price = quote.bid * (1 - slippage)
|
|
55
|
-
logger.debug(
|
|
61
|
+
logger.debug(
|
|
62
|
+
f"[<y>{instrument.symbol}</y>] :: Market SELL order: using slippage-protected price {price:.6f} (bid: {quote.bid:.6f}, slippage: {slippage:.1%})"
|
|
63
|
+
)
|
|
56
64
|
|
|
57
65
|
# Call parent implementation with calculated price
|
|
58
66
|
payload = super()._prepare_order_payload(
|
|
@@ -64,6 +72,6 @@ class HyperliquidCcxtBroker(CcxtBroker):
|
|
|
64
72
|
if "slippage" in options:
|
|
65
73
|
# HyperLiquid accepts slippage as a percentage (e.g., 0.05 for 5%)
|
|
66
74
|
params["px"] = price # Explicit price for slippage calculation
|
|
67
|
-
|
|
75
|
+
|
|
68
76
|
payload["params"] = params
|
|
69
|
-
return payload
|
|
77
|
+
return payload
|
|
@@ -8,7 +8,7 @@ from ...adapters.polling_adapter import PollingConfig, PollingToWebSocketAdapter
|
|
|
8
8
|
from ..base import CcxtFuturePatchMixin
|
|
9
9
|
|
|
10
10
|
# Constants
|
|
11
|
-
FUNDING_RATE_DEFAULT_POLL_MINUTES =
|
|
11
|
+
FUNDING_RATE_DEFAULT_POLL_MINUTES = 1
|
|
12
12
|
FUNDING_RATE_HOUR_MS = 60 * 60 * 1000 # 1 hour in milliseconds
|
|
13
13
|
|
|
14
14
|
|
|
@@ -71,7 +71,7 @@ class FundingRateDataHandler(BaseDataTypeHandler):
|
|
|
71
71
|
channel.send((instrument, DataType.FUNDING_RATE, funding_rate, False))
|
|
72
72
|
|
|
73
73
|
# Emit payment if funding interval changed
|
|
74
|
-
if self._should_emit_payment(instrument, funding_rate):
|
|
74
|
+
if self._should_emit_payment(instrument, funding_rate, current_time):
|
|
75
75
|
payment = self._create_funding_payment(instrument)
|
|
76
76
|
channel.send((instrument, DataType.FUNDING_PAYMENT, payment, False))
|
|
77
77
|
|
|
@@ -101,7 +101,7 @@ class FundingRateDataHandler(BaseDataTypeHandler):
|
|
|
101
101
|
stream_name=name,
|
|
102
102
|
)
|
|
103
103
|
|
|
104
|
-
def _should_emit_payment(self, instrument: Instrument, rate: FundingRate) -> bool:
|
|
104
|
+
def _should_emit_payment(self, instrument: Instrument, rate: FundingRate, current_time: dt_64) -> bool:
|
|
105
105
|
"""
|
|
106
106
|
Determine if a funding payment should be emitted.
|
|
107
107
|
|
|
@@ -132,7 +132,7 @@ class FundingRateDataHandler(BaseDataTypeHandler):
|
|
|
132
132
|
return False
|
|
133
133
|
|
|
134
134
|
# Emit if next_funding_time has advanced (new funding period started)
|
|
135
|
-
if rate.next_funding_time > last_info["payment_time"]:
|
|
135
|
+
if rate.next_funding_time > last_info["payment_time"] and current_time > last_info["payment_time"]:
|
|
136
136
|
# Store payment info for _create_funding_payment
|
|
137
137
|
self._pending_funding_rates[f"{key}_payment"] = {
|
|
138
138
|
"rate": last_info["rate"].rate,
|
|
@@ -65,7 +65,7 @@ class OrderBookDataHandler(BaseDataTypeHandler):
|
|
|
65
65
|
|
|
66
66
|
# Notify all listeners
|
|
67
67
|
self._data_provider.notify_data_arrival(sub_type, dt_64(ob.time, "ns"))
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
channel.send((instrument, sub_type, ob, False))
|
|
70
70
|
return True
|
|
71
71
|
|
|
@@ -150,7 +150,7 @@ class OrderBookDataHandler(BaseDataTypeHandler):
|
|
|
150
150
|
) -> SubscriptionConfiguration:
|
|
151
151
|
"""
|
|
152
152
|
Prepare subscription configuration for individual instruments.
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
Creates separate subscriber functions for each instrument to enable independent
|
|
155
155
|
WebSocket streams without waiting for all instruments. This follows the same
|
|
156
156
|
pattern as the OHLC handler for proper individual stream management.
|
|
@@ -169,10 +169,10 @@ class OrderBookDataHandler(BaseDataTypeHandler):
|
|
|
169
169
|
try:
|
|
170
170
|
# Watch orderbook for single instrument
|
|
171
171
|
ccxt_ob = await self._exchange_manager.exchange.watch_order_book(symbol)
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
# Use private processing method to avoid duplication
|
|
174
174
|
self._process_orderbook(ccxt_ob, inst, sub_type, channel, depth, tick_size_pct)
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
except Exception as e:
|
|
177
177
|
logger.error(
|
|
178
178
|
f"<yellow>{exchange_id}</yellow> Error in individual orderbook subscription for {inst.symbol}: {e}"
|
|
@@ -186,13 +186,15 @@ class OrderBookDataHandler(BaseDataTypeHandler):
|
|
|
186
186
|
# Create individual unsubscriber if exchange supports it
|
|
187
187
|
un_watch_method = getattr(self._exchange_manager.exchange, "un_watch_order_book", None)
|
|
188
188
|
if un_watch_method is not None and callable(un_watch_method):
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
def create_individual_unsubscriber(symbol=ccxt_symbol, exchange_id=self._exchange_id):
|
|
191
191
|
async def individual_unsubscriber():
|
|
192
192
|
try:
|
|
193
193
|
await self._exchange_manager.exchange.un_watch_order_book(symbol)
|
|
194
194
|
except Exception as e:
|
|
195
|
-
logger.error(
|
|
195
|
+
logger.error(
|
|
196
|
+
f"<yellow>{exchange_id}</yellow> Error unsubscribing orderbook for {symbol}: {e}"
|
|
197
|
+
)
|
|
196
198
|
|
|
197
199
|
return individual_unsubscriber
|
|
198
200
|
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trade data type handler for CCXT data provider.
|
|
3
|
+
|
|
4
|
+
Handles subscription and warmup for trade data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Set
|
|
8
|
+
|
|
9
|
+
from qubx import logger
|
|
10
|
+
from qubx.core.basics import CtrlChannel, DataType, Instrument, dt_64
|
|
11
|
+
from qubx.core.series import Quote
|
|
12
|
+
|
|
13
|
+
from ..subscription_config import SubscriptionConfiguration
|
|
14
|
+
from ..utils import (
|
|
15
|
+
ccxt_convert_trade,
|
|
16
|
+
ccxt_find_instrument,
|
|
17
|
+
create_market_type_batched_subscriber,
|
|
18
|
+
instrument_to_ccxt_symbol,
|
|
19
|
+
)
|
|
20
|
+
from .base import BaseDataTypeHandler
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TradeDataHandler(BaseDataTypeHandler):
|
|
24
|
+
"""Handler for trade data subscription and processing."""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def data_type(self) -> str:
|
|
28
|
+
return "trade"
|
|
29
|
+
|
|
30
|
+
def _process_trade(self, trades: list, instrument: Instrument, sub_type: str, channel: CtrlChannel):
|
|
31
|
+
"""
|
|
32
|
+
Process trades with synthetic quote generation.
|
|
33
|
+
|
|
34
|
+
This method handles the common logic for processing trade data that's shared between
|
|
35
|
+
bulk and individual subscription approaches.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
trades: List of CCXT trade dictionaries
|
|
39
|
+
instrument: Instrument these trades belong to
|
|
40
|
+
sub_type: Subscription type string
|
|
41
|
+
channel: Control channel to send data through
|
|
42
|
+
"""
|
|
43
|
+
for trade in trades:
|
|
44
|
+
converted_trade = ccxt_convert_trade(trade)
|
|
45
|
+
|
|
46
|
+
# Notify all listeners
|
|
47
|
+
self._data_provider.notify_data_arrival(sub_type, dt_64(converted_trade.time, "ns"))
|
|
48
|
+
|
|
49
|
+
channel.send((instrument, sub_type, converted_trade, False))
|
|
50
|
+
|
|
51
|
+
# Generate synthetic quote if no quote/orderbook subscription exists
|
|
52
|
+
if len(trades) > 0 and not (
|
|
53
|
+
self._data_provider.has_subscription(instrument, DataType.ORDERBOOK)
|
|
54
|
+
or self._data_provider.has_subscription(instrument, DataType.QUOTE)
|
|
55
|
+
):
|
|
56
|
+
last_trade = trades[-1]
|
|
57
|
+
converted_trade = ccxt_convert_trade(last_trade)
|
|
58
|
+
_price = converted_trade.price
|
|
59
|
+
_time = converted_trade.time
|
|
60
|
+
_s2 = instrument.tick_size / 2.0
|
|
61
|
+
_bid, _ask = _price - _s2, _price + _s2
|
|
62
|
+
self._data_provider._last_quotes[instrument] = Quote(_time, _bid, _ask, 0.0, 0.0)
|
|
63
|
+
|
|
64
|
+
def prepare_subscription(
|
|
65
|
+
self, name: str, sub_type: str, channel: CtrlChannel, instruments: Set[Instrument], **params
|
|
66
|
+
) -> SubscriptionConfiguration:
|
|
67
|
+
"""
|
|
68
|
+
Prepare trade subscription configuration.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
name: Stream name for this subscription
|
|
72
|
+
sub_type: Parsed subscription type ("trade")
|
|
73
|
+
channel: Control channel for managing subscription lifecycle
|
|
74
|
+
instruments: Set of instruments to subscribe to
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
SubscriptionConfiguration with subscriber and unsubscriber functions
|
|
78
|
+
"""
|
|
79
|
+
# Use exchange-specific approach based on capabilities
|
|
80
|
+
if self._exchange_manager.exchange.has.get("watchTradesForSymbols", False):
|
|
81
|
+
return self._prepare_subscription_for_instruments(name, sub_type, channel, instruments)
|
|
82
|
+
else:
|
|
83
|
+
# Fall back to individual instrument subscriptions
|
|
84
|
+
return self._prepare_subscription_for_individual_instruments(name, sub_type, channel, instruments)
|
|
85
|
+
|
|
86
|
+
def _prepare_subscription_for_instruments(
|
|
87
|
+
self,
|
|
88
|
+
name: str,
|
|
89
|
+
sub_type: str,
|
|
90
|
+
channel: CtrlChannel,
|
|
91
|
+
instruments: Set[Instrument],
|
|
92
|
+
) -> SubscriptionConfiguration:
|
|
93
|
+
"""Prepare subscription configuration for multiple instruments using bulk API."""
|
|
94
|
+
_instr_to_ccxt_symbol = {i: instrument_to_ccxt_symbol(i) for i in instruments}
|
|
95
|
+
_symbol_to_instrument = {_instr_to_ccxt_symbol[i]: i for i in instruments}
|
|
96
|
+
|
|
97
|
+
async def watch_trades(instruments_batch: list[Instrument]):
|
|
98
|
+
symbols = [_instr_to_ccxt_symbol[i] for i in instruments_batch]
|
|
99
|
+
trades = await self._exchange_manager.exchange.watch_trades_for_symbols(symbols)
|
|
100
|
+
|
|
101
|
+
exch_symbol = trades[0]["symbol"]
|
|
102
|
+
instrument = ccxt_find_instrument(exch_symbol, self._exchange_manager.exchange, _symbol_to_instrument)
|
|
103
|
+
|
|
104
|
+
# Use private processing method to avoid duplication
|
|
105
|
+
self._process_trade(trades, instrument, sub_type, channel)
|
|
106
|
+
|
|
107
|
+
async def un_watch_trades(instruments_batch: list[Instrument]):
|
|
108
|
+
symbols = [_instr_to_ccxt_symbol[i] for i in instruments_batch]
|
|
109
|
+
await self._exchange_manager.exchange.un_watch_trades_for_symbols(symbols)
|
|
110
|
+
|
|
111
|
+
return SubscriptionConfiguration(
|
|
112
|
+
subscription_type=sub_type,
|
|
113
|
+
subscriber_func=create_market_type_batched_subscriber(watch_trades, instruments),
|
|
114
|
+
unsubscriber_func=create_market_type_batched_subscriber(un_watch_trades, instruments),
|
|
115
|
+
stream_name=name,
|
|
116
|
+
requires_market_type_batching=True,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def _prepare_subscription_for_individual_instruments(
|
|
120
|
+
self,
|
|
121
|
+
name: str,
|
|
122
|
+
sub_type: str,
|
|
123
|
+
channel: CtrlChannel,
|
|
124
|
+
instruments: Set[Instrument],
|
|
125
|
+
) -> SubscriptionConfiguration:
|
|
126
|
+
"""
|
|
127
|
+
Prepare subscription configuration for individual instruments.
|
|
128
|
+
|
|
129
|
+
Creates separate subscriber functions for each instrument to enable independent
|
|
130
|
+
WebSocket streams without waiting for all instruments. This follows the same
|
|
131
|
+
pattern as the orderbook handler for proper individual stream management.
|
|
132
|
+
"""
|
|
133
|
+
_instr_to_ccxt_symbol = {i: instrument_to_ccxt_symbol(i) for i in instruments}
|
|
134
|
+
|
|
135
|
+
individual_subscribers = {}
|
|
136
|
+
individual_unsubscribers = {}
|
|
137
|
+
|
|
138
|
+
for instrument in instruments:
|
|
139
|
+
ccxt_symbol = _instr_to_ccxt_symbol[instrument]
|
|
140
|
+
|
|
141
|
+
# Create individual subscriber for this instrument using closure
|
|
142
|
+
def create_individual_subscriber(inst=instrument, symbol=ccxt_symbol, exchange_id=self._exchange_id):
|
|
143
|
+
async def individual_subscriber():
|
|
144
|
+
try:
|
|
145
|
+
# Watch trades for single instrument
|
|
146
|
+
trades = await self._exchange_manager.exchange.watch_trades(symbol)
|
|
147
|
+
|
|
148
|
+
# Use private processing method to avoid duplication
|
|
149
|
+
self._process_trade(trades, inst, sub_type, channel)
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.error(
|
|
153
|
+
f"<yellow>{exchange_id}</yellow> Error in individual trade subscription for {inst.symbol}: {e}"
|
|
154
|
+
)
|
|
155
|
+
raise # Let connection manager handle retries
|
|
156
|
+
|
|
157
|
+
return individual_subscriber
|
|
158
|
+
|
|
159
|
+
individual_subscribers[instrument] = create_individual_subscriber()
|
|
160
|
+
|
|
161
|
+
# Create individual unsubscriber if exchange supports it
|
|
162
|
+
un_watch_method = getattr(self._exchange_manager.exchange, "un_watch_trades", None)
|
|
163
|
+
if un_watch_method is not None and callable(un_watch_method):
|
|
164
|
+
|
|
165
|
+
def create_individual_unsubscriber(symbol=ccxt_symbol, exchange_id=self._exchange_id):
|
|
166
|
+
async def individual_unsubscriber():
|
|
167
|
+
try:
|
|
168
|
+
await self._exchange_manager.exchange.un_watch_trades(symbol)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"<yellow>{exchange_id}</yellow> Error unsubscribing trades for {symbol}: {e}")
|
|
171
|
+
|
|
172
|
+
return individual_unsubscriber
|
|
173
|
+
|
|
174
|
+
individual_unsubscribers[instrument] = create_individual_unsubscriber()
|
|
175
|
+
|
|
176
|
+
return SubscriptionConfiguration(
|
|
177
|
+
subscription_type=sub_type,
|
|
178
|
+
instrument_subscribers=individual_subscribers,
|
|
179
|
+
instrument_unsubscribers=individual_unsubscribers if individual_unsubscribers else None,
|
|
180
|
+
stream_name=name,
|
|
181
|
+
requires_market_type_batching=False,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
async def warmup(self, instruments: Set[Instrument], channel: CtrlChannel, warmup_period: str, **params) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Fetch historical trade data for warmup during backtesting.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
instruments: Set of instruments to warm up
|
|
190
|
+
channel: Control channel for sending warmup data
|
|
191
|
+
warmup_period: Period to warm up (e.g., "30d", "1000h")
|
|
192
|
+
"""
|
|
193
|
+
for instrument in instruments:
|
|
194
|
+
trades = await self._exchange_manager.exchange.fetch_trades(
|
|
195
|
+
instrument.symbol, since=self._data_provider._time_msec_nbars_back(warmup_period)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
logger.debug(f"<yellow>{self._exchange_id}</yellow> Loaded {len(trades)} trades for {instrument}")
|
|
199
|
+
|
|
200
|
+
channel.send(
|
|
201
|
+
(
|
|
202
|
+
instrument,
|
|
203
|
+
DataType.TRADE,
|
|
204
|
+
[ccxt_convert_trade(trade) for trade in trades],
|
|
205
|
+
True, # historical data
|
|
206
|
+
)
|
|
207
|
+
)
|
|
@@ -20,7 +20,7 @@ from .utils import ccxt_find_instrument, instrument_to_ccxt_symbol
|
|
|
20
20
|
|
|
21
21
|
@reader("ccxt")
|
|
22
22
|
class CcxtDataReader(DataReader):
|
|
23
|
-
SUPPORTED_DATA_TYPES = {"ohlc"
|
|
23
|
+
SUPPORTED_DATA_TYPES = {"ohlc"}
|
|
24
24
|
|
|
25
25
|
_exchanges: dict[str, Exchange]
|
|
26
26
|
_loop: AsyncThreadLoop
|
|
@@ -74,7 +74,8 @@ class CcxtDataReader(DataReader):
|
|
|
74
74
|
if instrument is None:
|
|
75
75
|
return []
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
timeframe = timeframe or "1m"
|
|
78
|
+
_timeframe = pd.Timedelta(timeframe)
|
|
78
79
|
_start, _stop = self._get_start_stop(start, stop, _timeframe)
|
|
79
80
|
|
|
80
81
|
if _start > _stop:
|
|
@@ -233,10 +233,16 @@ class CachedMarketDataHolder:
|
|
|
233
233
|
if series:
|
|
234
234
|
total_vol = trade.size
|
|
235
235
|
bought_vol = total_vol if trade.side == 1 else 0.0
|
|
236
|
+
volume_quote = trade.price * trade.size
|
|
237
|
+
bought_volume_quote = volume_quote if trade.side == 1 else 0.0
|
|
236
238
|
for ser in series.values():
|
|
237
|
-
if len(ser) > 0
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
if len(ser) > 0:
|
|
240
|
+
current_bar_start = floor_t64(np.datetime64(ser[0].time, 'ns'), np.timedelta64(ser.timeframe, 'ns'))
|
|
241
|
+
trade_bar_start = floor_t64(np.datetime64(trade.time, 'ns'), np.timedelta64(ser.timeframe, 'ns'))
|
|
242
|
+
if trade_bar_start < current_bar_start:
|
|
243
|
+
# Trade belongs to a previous bar - skip it
|
|
244
|
+
continue
|
|
245
|
+
ser.update(trade.time, trade.price, total_vol, bought_vol, volume_quote, bought_volume_quote, 1)
|
|
240
246
|
|
|
241
247
|
def finalize_ohlc_for_instruments(self, time: dt_64, instruments: list[Instrument]):
|
|
242
248
|
"""
|
|
@@ -2050,7 +2050,7 @@ class IMetricEmitter:
|
|
|
2050
2050
|
self,
|
|
2051
2051
|
name: str,
|
|
2052
2052
|
value: float,
|
|
2053
|
-
tags: dict[str,
|
|
2053
|
+
tags: dict[str, Any] | None = None,
|
|
2054
2054
|
timestamp: dt_64 | None = None,
|
|
2055
2055
|
instrument: Instrument | None = None,
|
|
2056
2056
|
) -> None:
|
|
@@ -2092,15 +2092,16 @@ class IMetricEmitter:
|
|
|
2092
2092
|
"""
|
|
2093
2093
|
pass
|
|
2094
2094
|
|
|
2095
|
-
def
|
|
2095
|
+
def set_context(self, context: "IStrategyContext") -> None:
|
|
2096
2096
|
"""
|
|
2097
|
-
Set the
|
|
2097
|
+
Set the strategy context for the metric emitter.
|
|
2098
2098
|
|
|
2099
|
-
This method is used to set the
|
|
2100
|
-
|
|
2099
|
+
This method is used to set the context that provides access to time and simulation state.
|
|
2100
|
+
The context is used to automatically add is_live tag and get timestamps when no explicit
|
|
2101
|
+
timestamp is provided in the emit method.
|
|
2101
2102
|
|
|
2102
2103
|
Args:
|
|
2103
|
-
|
|
2104
|
+
context: The strategy context to use
|
|
2104
2105
|
"""
|
|
2105
2106
|
pass
|
|
2106
2107
|
|