Qubx 0.7.40.dev12__tar.gz → 1.0.0.dev2__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.
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/PKG-INFO +1 -3
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/pyproject.toml +1 -2
- qubx-1.0.0.dev2/src/qubx/_nb_magic.py +99 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/_version.py +2 -2
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/data.py +25 -90
- qubx-1.0.0.dev2/src/qubx/backtester/iteratedstream.py +194 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/runner.py +148 -171
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/sentinels.py +11 -15
- qubx-1.0.0.dev2/src/qubx/backtester/simulated_data.py +1202 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/simulator.py +20 -30
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/utils.py +135 -434
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/data.py +0 -3
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/tardis/data.py +7 -8
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/basics.py +42 -9
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/context.py +32 -36
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/detectors/stale.py +5 -5
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/helpers.py +3 -266
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/interfaces.py +57 -15
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/lookups.py +12 -5
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/metrics.py +14 -7
- qubx-1.0.0.dev2/src/qubx/core/mixins/market.py +455 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/processing.py +7 -15
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/universe.py +7 -7
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/series.pxd +23 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/series.pyi +71 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/series.pyx +324 -0
- qubx-1.0.0.dev2/src/qubx/data/__init__.py +24 -0
- qubx-1.0.0.dev2/src/qubx/data/cache.py +574 -0
- qubx-1.0.0.dev2/src/qubx/data/containers.py +304 -0
- qubx-1.0.0.dev2/src/qubx/data/guards.py +129 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/data/registry.py +38 -155
- qubx-1.0.0.dev2/src/qubx/data/storage.py +116 -0
- qubx-1.0.0.dev2/src/qubx/data/storages/ccxt.py +769 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/data/storages/csv.py +42 -11
- qubx-1.0.0.dev2/src/qubx/data/storages/handy.py +311 -0
- qubx-1.0.0.dev2/src/qubx/data/storages/multi.py +454 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/data/storages/questdb.py +370 -23
- qubx-1.0.0.dev2/src/qubx/data/storages/stub.py +23 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/data/storages/utils.py +50 -23
- qubx-1.0.0.dev2/src/qubx/data/transformers.py +594 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/ta/indicators.pyi +7 -2
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/marketdata/ccxt.py +7 -6
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/marketdata/dukas.py +8 -15
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/misc.py +22 -1
- qubx-1.0.0.dev2/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/configs.py +20 -16
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/factory.py +72 -79
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/runner.py +70 -39
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/time.py +108 -7
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +1 -0
- qubx-0.7.40.dev12/src/qubx/_nb_magic.py +0 -101
- qubx-0.7.40.dev12/src/qubx/backtester/simulated_data.py +0 -473
- qubx-0.7.40.dev12/src/qubx/connectors/ccxt/__init__.py +0 -3
- qubx-0.7.40.dev12/src/qubx/connectors/ccxt/reader.py +0 -874
- qubx-0.7.40.dev12/src/qubx/core/mixins/market.py +0 -167
- qubx-0.7.40.dev12/src/qubx/data/__init__.py +0 -38
- qubx-0.7.40.dev12/src/qubx/data/composite.py +0 -824
- qubx-0.7.40.dev12/src/qubx/data/containers.py +0 -234
- qubx-0.7.40.dev12/src/qubx/data/helpers.py +0 -2080
- qubx-0.7.40.dev12/src/qubx/data/hft.py +0 -855
- qubx-0.7.40.dev12/src/qubx/data/readers.py +0 -2245
- qubx-0.7.40.dev12/src/qubx/data/storage.py +0 -74
- qubx-0.7.40.dev12/src/qubx/data/tardis.py +0 -892
- qubx-0.7.40.dev12/src/qubx/data/transformers.py +0 -527
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/.gitignore +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/LICENSE +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/README.md +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/hatch_build.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/account.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/management.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/transfers.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/commands.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/misc.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/release.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/tui.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/__init__.py +0 -0
- {qubx-0.7.40.dev12/src/qubx/core → qubx-1.0.0.dev2/src/qubx/connectors/ccxt}/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/registry.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.7.40.dev12/src/qubx/restarts → qubx-1.0.0.dev2/src/qubx/core}/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/account.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/detectors/delisting.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/errors.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/initializer.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/loggers.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/base.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/composite.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/csv.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/indicator.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/questdb.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/health/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/health/base.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/health/dummy.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/csv.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/factory.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/mongo.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/notifications/throttler.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/pandaz/stats.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/plugins/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/plugins/loader.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/_build.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-0.7.40.dev12/src/qubx/ta → qubx-1.0.0.dev2/src/qubx/restarts}/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/position.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/state.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/state/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/state/dummy.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/state/redis.py +0 -0
- {qubx-0.7.40.dev12/src/qubx/utils/plotting → qubx-1.0.0.dev2/src/qubx/ta}/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/base.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/template.yml +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/collections.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/nonce.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.7.40.dev12/src/qubx/utils/plotting/renderers → qubx-1.0.0.dev2/src/qubx/utils/plotting}/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.7.40.dev12/src/qubx/utils/runner → qubx-1.0.0.dev2/src/qubx/utils/plotting/renderers}/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/questdb.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/rate_limiter.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/app.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/handlers.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/init_code.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/slack.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/throttler.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/websocket_manager.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
- {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Qubx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0.dev2
|
|
4
4
|
Summary: Qubx - Quantitative Trading Framework
|
|
5
5
|
Project-URL: homepage, https://xlydian.com
|
|
6
6
|
Project-URL: repository, https://github.com/xLydianSoftware/Qubx
|
|
@@ -55,8 +55,6 @@ Requires-Dist: toml<1,>=0.10.2
|
|
|
55
55
|
Requires-Dist: tqdm
|
|
56
56
|
Requires-Dist: uvloop<1,>=0.22.1; sys_platform != 'win32'
|
|
57
57
|
Requires-Dist: websockets==15.0.1
|
|
58
|
-
Provides-Extra: hft
|
|
59
|
-
Requires-Dist: hftbacktest<3,>=2.2.0; extra == 'hft'
|
|
60
58
|
Provides-Extra: k8
|
|
61
59
|
Requires-Dist: prometheus-client<1,>=0.21.1; extra == 'k8'
|
|
62
60
|
Description-Content-Type: text/markdown
|
|
@@ -66,7 +66,6 @@ docs = "https://xlydiansoftware.github.io/Qubx"
|
|
|
66
66
|
[project.optional-dependencies]
|
|
67
67
|
# Runtime optional features only (shipped with package)
|
|
68
68
|
k8 = ["prometheus-client>=0.21.1,<1"]
|
|
69
|
-
hft = ["hftbacktest>=2.2.0,<3"]
|
|
70
69
|
|
|
71
70
|
[project.scripts]
|
|
72
71
|
qubx = "qubx.cli.commands:main"
|
|
@@ -131,7 +130,7 @@ extend-select = ["I"]
|
|
|
131
130
|
ignore = ["E731", "E722", "E741"]
|
|
132
131
|
|
|
133
132
|
[tool.ruff.lint.extend-per-file-ignores]
|
|
134
|
-
"*.ipynb" = ["F405", "F401", "E701", "E402", "F403", "E401", "E702", "
|
|
133
|
+
"*.ipynb" = [ "F405", "F401", "E701", "E402", "F403", "E401", "E702", "F821", "E731", ]
|
|
135
134
|
|
|
136
135
|
[tool.pytest.ini_options]
|
|
137
136
|
asyncio_mode = "auto"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
""" "
|
|
2
|
+
Here stuff we want to have in every Jupyter notebook after calling %qubx magic
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import qubx
|
|
6
|
+
from qubx import runtime_env
|
|
7
|
+
from qubx.utils.misc import add_project_to_system_path, logo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def np_fmt_short():
|
|
11
|
+
# default np output is 75 columns so extend it a bit and suppress scientific fmt for small floats
|
|
12
|
+
np.set_printoptions(linewidth=240, suppress=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def np_fmt_reset():
|
|
16
|
+
# reset default np printing options
|
|
17
|
+
np.set_printoptions(
|
|
18
|
+
edgeitems=3,
|
|
19
|
+
infstr="inf",
|
|
20
|
+
linewidth=75,
|
|
21
|
+
nanstr="nan",
|
|
22
|
+
precision=8,
|
|
23
|
+
suppress=False,
|
|
24
|
+
threshold=1000,
|
|
25
|
+
formatter=None,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if runtime_env() in ["notebook", "shell"]:
|
|
30
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
31
|
+
# -- all imports below will appear in notebook after calling %%qubx magic ---
|
|
32
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
33
|
+
|
|
34
|
+
# - - - - Common stuff - - - -
|
|
35
|
+
import numpy as np # type: ignore # noqa: F401
|
|
36
|
+
import pandas as pd # type: ignore # noqa: F401
|
|
37
|
+
|
|
38
|
+
# - - - - Charting stuff - - - -
|
|
39
|
+
from matplotlib import pyplot as plt # type: ignore # noqa: F401
|
|
40
|
+
from tqdm.auto import tqdm # type: ignore # noqa: F401
|
|
41
|
+
|
|
42
|
+
# - - - - TA stuff and indicators - - - -
|
|
43
|
+
import qubx.pandaz.ta as pta # type: ignore # noqa: F401
|
|
44
|
+
import qubx.ta.indicators as ta # type: ignore # noqa: F401
|
|
45
|
+
from qubx.backtester.optimization import variate # type: ignore # noqa: F401
|
|
46
|
+
|
|
47
|
+
# - - - - Simulator stuff - - - -
|
|
48
|
+
from qubx.backtester.simulator import simulate # type: ignore # noqa: F401
|
|
49
|
+
|
|
50
|
+
# - - - - Portfolio analysis - - - -
|
|
51
|
+
from qubx.core.metrics import ( # type: ignore # noqa: F401
|
|
52
|
+
calculate_leverage_per_symbol, # type: ignore
|
|
53
|
+
calculate_pnl_per_symbol, # type: ignore
|
|
54
|
+
chart_signals, # type: ignore
|
|
55
|
+
combine_sessions, # type: ignore
|
|
56
|
+
drop_symbols, # type: ignore
|
|
57
|
+
extend_trading_results, # type: ignore
|
|
58
|
+
find_session, # type: ignore
|
|
59
|
+
find_sessions, # type: ignore
|
|
60
|
+
get_symbol_pnls, # type: ignore
|
|
61
|
+
pick_symbols, # type: ignore
|
|
62
|
+
pnl, # type: ignore
|
|
63
|
+
portfolio_metrics, # type: ignore
|
|
64
|
+
tearsheet, # type: ignore
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# - - - - Data storages - - - -
|
|
68
|
+
from qubx.data import ( # noqa: F401
|
|
69
|
+
CachedStorage, # type: ignore
|
|
70
|
+
CsvStorage, # type: ignore
|
|
71
|
+
QuestDBStorage, # type: ignore
|
|
72
|
+
)
|
|
73
|
+
from qubx.data.registry import StorageRegistry # noqa: F401 # type: ignore
|
|
74
|
+
|
|
75
|
+
# - - - - Utils - - - -
|
|
76
|
+
from qubx.pandaz.utils import ( # noqa: F401
|
|
77
|
+
continuous_periods, # type: ignore
|
|
78
|
+
drop_duplicated_indexes, # type: ignore
|
|
79
|
+
generate_equal_date_ranges, # type: ignore
|
|
80
|
+
ohlc_resample, # type: ignore
|
|
81
|
+
retain_columns_and_join, # type: ignore
|
|
82
|
+
rolling_forward_test_split, # type: ignore
|
|
83
|
+
scols, # type: ignore
|
|
84
|
+
srows, # type: ignore
|
|
85
|
+
)
|
|
86
|
+
from qubx.utils.charting.lookinglass import LookingGlass # type: ignore # noqa: F401
|
|
87
|
+
from qubx.utils.charting.mpl_helpers import fig, ohlc_plot, plot_trends, sbp, subplot # type: ignore # noqa: F401
|
|
88
|
+
from qubx.utils.misc import this_project_root # type: ignore # noqa: F401
|
|
89
|
+
|
|
90
|
+
# - setup short numpy output format
|
|
91
|
+
np_fmt_short()
|
|
92
|
+
|
|
93
|
+
# - add project home to system path
|
|
94
|
+
add_project_to_system_path()
|
|
95
|
+
|
|
96
|
+
# show logo first time
|
|
97
|
+
if not hasattr(qubx.QubxMagics, "__already_initialized__"):
|
|
98
|
+
setattr(qubx.QubxMagics, "__already_initialized__", True)
|
|
99
|
+
logo()
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (
|
|
31
|
+
__version__ = version = '1.0.0.dev2'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 0, 0, 'dev2')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -4,18 +4,14 @@ from typing import TypeVar
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
6
6
|
from qubx import logger
|
|
7
|
-
from qubx.backtester.simulated_data import
|
|
7
|
+
from qubx.backtester.simulated_data import SimulatedDataIterator
|
|
8
8
|
from qubx.core.basics import (
|
|
9
9
|
CtrlChannel,
|
|
10
10
|
DataType,
|
|
11
11
|
Instrument,
|
|
12
|
-
TimestampedDict,
|
|
13
12
|
)
|
|
14
|
-
from qubx.core.helpers import BasicScheduler
|
|
15
13
|
from qubx.core.interfaces import IDataProvider
|
|
16
|
-
from qubx.core.series import Bar, Quote
|
|
17
|
-
from qubx.data.readers import AsDict, DataReader
|
|
18
|
-
from qubx.utils.time import infer_series_frequency
|
|
14
|
+
from qubx.core.series import Bar, Quote
|
|
19
15
|
|
|
20
16
|
from .account import SimulatedAccountProcessor
|
|
21
17
|
from .utils import SimulatedTimeProvider
|
|
@@ -23,49 +19,50 @@ from .utils import SimulatedTimeProvider
|
|
|
23
19
|
T = TypeVar("T")
|
|
24
20
|
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
for key in keys:
|
|
30
|
-
if (value := data_get(key, sentinel)) is not sentinel and value is not None:
|
|
31
|
-
return value
|
|
32
|
-
return default
|
|
22
|
+
class SimulatedDataProvider(IDataProvider):
|
|
23
|
+
"""
|
|
24
|
+
Per-exchange data provider for backtesting simulation.
|
|
33
25
|
|
|
26
|
+
Thin wrapper around SimulatedDataIterator (shared across all exchanges in a simulation).
|
|
27
|
+
Handles exchange-specific concerns: subscribe/unsubscribe lifecycle, last-quote tracking,
|
|
28
|
+
and account notifications (OME). One instance per exchange in the simulation.
|
|
29
|
+
|
|
30
|
+
Data flow for market data subscriptions:
|
|
31
|
+
subscribe() → SimulatedDataIterator.add_instruments_for_subscription()
|
|
32
|
+
→ peek historical data → emulate last quote → notify account OME
|
|
33
|
+
|
|
34
|
+
Data flow for historical OHLC lookback (ctx.ohlc()):
|
|
35
|
+
get_ohlc(instrument, timeframe, nbarsback)
|
|
36
|
+
→ uses time_provider.time() as current simulated time
|
|
37
|
+
→ delegates to SimulatedDataIterator.get_ohlc() which reads from IStorage,
|
|
38
|
+
transforms via TypedRecords, and applies open_close_time_indent cut
|
|
39
|
+
to exclude bars that haven't "closed" yet at the simulated time
|
|
40
|
+
"""
|
|
34
41
|
|
|
35
|
-
class SimulatedDataProvider(IDataProvider):
|
|
36
42
|
time_provider: SimulatedTimeProvider
|
|
37
43
|
channel: CtrlChannel
|
|
38
44
|
|
|
39
|
-
_scheduler: BasicScheduler
|
|
40
45
|
_account: SimulatedAccountProcessor
|
|
41
46
|
_last_quotes: dict[Instrument, Quote | None]
|
|
42
|
-
|
|
43
|
-
_data_source: IterableSimulationData
|
|
44
|
-
_open_close_time_indent_ns: int
|
|
47
|
+
_data_source: SimulatedDataIterator
|
|
45
48
|
|
|
46
49
|
def __init__(
|
|
47
50
|
self,
|
|
48
51
|
exchange_id: str,
|
|
49
52
|
channel: CtrlChannel,
|
|
50
|
-
scheduler: BasicScheduler,
|
|
51
53
|
time_provider: SimulatedTimeProvider,
|
|
52
54
|
account: SimulatedAccountProcessor,
|
|
53
|
-
|
|
54
|
-
data_source: IterableSimulationData,
|
|
55
|
-
open_close_time_indent_secs=1,
|
|
55
|
+
data_source: SimulatedDataIterator,
|
|
56
56
|
):
|
|
57
57
|
self.channel = channel
|
|
58
58
|
self.time_provider = time_provider
|
|
59
59
|
self._exchange_id = exchange_id
|
|
60
|
-
self._scheduler = scheduler
|
|
61
60
|
self._account = account
|
|
62
|
-
self._readers = readers
|
|
63
61
|
|
|
64
62
|
# - simulation data source
|
|
65
63
|
self._data_source = data_source
|
|
66
|
-
self._open_close_time_indent_ns = open_close_time_indent_secs * 1_000_000_000 # convert seconds to nanoseconds
|
|
67
64
|
|
|
68
|
-
# - create
|
|
65
|
+
# - create last quote holder
|
|
69
66
|
self._last_quotes = defaultdict(lambda: None)
|
|
70
67
|
|
|
71
68
|
logger.info(f"{self.__class__.__name__}.{exchange_id} is initialized")
|
|
@@ -152,19 +149,9 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
152
149
|
self._data_source.set_warmup_period(si[0], warm_period)
|
|
153
150
|
|
|
154
151
|
def get_ohlc(self, instrument: Instrument, timeframe: str, nbarsback: int) -> list[Bar]:
|
|
155
|
-
_reader = self._readers.get(DataType.OHLC)
|
|
156
|
-
if _reader is None:
|
|
157
|
-
logger.error(f"Reader for {DataType.OHLC} data not configured")
|
|
158
|
-
return []
|
|
159
|
-
|
|
160
152
|
start = pd.Timestamp(self.time_provider.time())
|
|
161
|
-
end = start - nbarsback *
|
|
162
|
-
|
|
163
|
-
return self._convert_records_to_bars(
|
|
164
|
-
_reader.read(data_id=_spec, start=start, stop=end, timeframe=timeframe, transform=AsDict()), # type: ignore
|
|
165
|
-
time_as_nsec(self.time_provider.time()),
|
|
166
|
-
_timeframe.asm8.item(),
|
|
167
|
-
)
|
|
153
|
+
end = start - nbarsback * pd.Timedelta(timeframe)
|
|
154
|
+
return self._data_source.get_ohlc(instrument, timeframe, start, end)
|
|
168
155
|
|
|
169
156
|
def get_quote(self, instrument: Instrument) -> Quote | None:
|
|
170
157
|
return self._last_quotes[instrument]
|
|
@@ -172,57 +159,5 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
172
159
|
def close(self):
|
|
173
160
|
pass
|
|
174
161
|
|
|
175
|
-
def _convert_records_to_bars(
|
|
176
|
-
self, records: list[TimestampedDict], cut_time_ns: int, timeframe_ns: int
|
|
177
|
-
) -> list[Bar]:
|
|
178
|
-
"""
|
|
179
|
-
Convert records to bars and we need to cut last bar up to the cut_time_ns
|
|
180
|
-
"""
|
|
181
|
-
bars = []
|
|
182
|
-
|
|
183
|
-
# - if no records, return empty list to avoid exception from infer_series_frequency
|
|
184
|
-
if not records or records is None:
|
|
185
|
-
return bars
|
|
186
|
-
|
|
187
|
-
if len(records) > 1:
|
|
188
|
-
_data_tf = infer_series_frequency([r.time for r in records[:50]])
|
|
189
|
-
timeframe_ns = _data_tf.item()
|
|
190
|
-
|
|
191
|
-
for r in records:
|
|
192
|
-
_b_ts_0 = r.time
|
|
193
|
-
_b_ts_1 = _b_ts_0 + timeframe_ns - self._open_close_time_indent_ns
|
|
194
|
-
|
|
195
|
-
if _b_ts_0 <= cut_time_ns and cut_time_ns < _b_ts_1:
|
|
196
|
-
break
|
|
197
|
-
|
|
198
|
-
# Handle None values in OHLC data
|
|
199
|
-
open_price = r.data["open"]
|
|
200
|
-
high_price = r.data["high"]
|
|
201
|
-
low_price = r.data["low"]
|
|
202
|
-
close_price = r.data["close"]
|
|
203
|
-
|
|
204
|
-
# Skip this record if any OHLC value is None
|
|
205
|
-
if open_price is None or high_price is None or low_price is None or close_price is None:
|
|
206
|
-
continue
|
|
207
|
-
|
|
208
|
-
bars.append(
|
|
209
|
-
Bar(
|
|
210
|
-
_b_ts_0,
|
|
211
|
-
open_price,
|
|
212
|
-
high_price,
|
|
213
|
-
low_price,
|
|
214
|
-
close_price,
|
|
215
|
-
volume=r.data.get("volume", 0) or 0, # Handle None volume
|
|
216
|
-
bought_volume=_get_first_existing(r.data, ["taker_buy_volume", "bought_volume"], 0),
|
|
217
|
-
volume_quote=_get_first_existing(r.data, ["quote_volume", "volume_quote"], 0),
|
|
218
|
-
bought_volume_quote=_get_first_existing(
|
|
219
|
-
r.data, ["taker_buy_quote_volume", "bought_volume_quote"], 0
|
|
220
|
-
),
|
|
221
|
-
trade_count=_get_first_existing(r.data, ["count", "trade_count"], 0),
|
|
222
|
-
)
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
return bars
|
|
226
|
-
|
|
227
162
|
def exchange(self) -> str:
|
|
228
163
|
return self._exchange_id.upper()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from collections import defaultdict, deque
|
|
3
|
+
from collections.abc import Callable, Iterator, Mapping
|
|
4
|
+
from typing import Any, TypeAlias
|
|
5
|
+
|
|
6
|
+
from qubx.core.basics import Timestamped
|
|
7
|
+
|
|
8
|
+
SlicerOutData: TypeAlias = tuple[str, int, Timestamped] | tuple
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class IteratedDataStreamsSlicer(Iterator[SlicerOutData]):
|
|
12
|
+
"""
|
|
13
|
+
This class manages seamless iteration over multiple time-series data streams,
|
|
14
|
+
ensuring that events are processed in the correct chronological order regardless of their source.
|
|
15
|
+
It supports adding / removing new data streams to the slicer on the fly (during the itration).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_iterators: dict[str, Iterator[list[Timestamped]]]
|
|
19
|
+
_buffers: dict[str, list[Timestamped]]
|
|
20
|
+
_keys: deque[str]
|
|
21
|
+
_iterating: bool
|
|
22
|
+
|
|
23
|
+
def __init__(self, time_func: Callable[[Timestamped], Any] = lambda x: x.time):
|
|
24
|
+
self._buffers = defaultdict(list)
|
|
25
|
+
self._iterators = {}
|
|
26
|
+
self._keys = deque()
|
|
27
|
+
self._iterating = False
|
|
28
|
+
self._time_func = time_func
|
|
29
|
+
|
|
30
|
+
def put(self, data: Mapping[str, Iterator[list[Timestamped]]]):
|
|
31
|
+
_rebuild = False
|
|
32
|
+
for k, vi in data.items():
|
|
33
|
+
if k not in self._keys:
|
|
34
|
+
self._iterators[k] = vi
|
|
35
|
+
self._buffers[k] = self._load_next_chunk_to_buffer(k) # do initial chunk fetching
|
|
36
|
+
self._keys.append(k)
|
|
37
|
+
_rebuild = True
|
|
38
|
+
|
|
39
|
+
# - rebuild strategy
|
|
40
|
+
if _rebuild and self._iterating:
|
|
41
|
+
self._build_initial_iteration_seq()
|
|
42
|
+
|
|
43
|
+
def __add__(self, data: Mapping[str, Iterator]) -> "IteratedDataStreamsSlicer":
|
|
44
|
+
self.put(data)
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def remove(self, keys: list[str] | str):
|
|
48
|
+
"""
|
|
49
|
+
Remove data iterator and associated keys from the queue.
|
|
50
|
+
If the key is not found, it does nothing.
|
|
51
|
+
"""
|
|
52
|
+
_keys = keys if isinstance(keys, list) else [keys]
|
|
53
|
+
_rebuild = False
|
|
54
|
+
for i in _keys:
|
|
55
|
+
# Check and remove from each data structure independently
|
|
56
|
+
removed_any = False
|
|
57
|
+
|
|
58
|
+
if i in self._buffers:
|
|
59
|
+
self._buffers.pop(i)
|
|
60
|
+
removed_any = True
|
|
61
|
+
|
|
62
|
+
if i in self._iterators:
|
|
63
|
+
self._iterators.pop(i)
|
|
64
|
+
removed_any = True
|
|
65
|
+
|
|
66
|
+
if i in self._keys:
|
|
67
|
+
self._keys.remove(i)
|
|
68
|
+
removed_any = True
|
|
69
|
+
|
|
70
|
+
if removed_any:
|
|
71
|
+
_rebuild = True
|
|
72
|
+
|
|
73
|
+
# - rebuild strategy
|
|
74
|
+
if _rebuild and self._iterating:
|
|
75
|
+
self._build_initial_iteration_seq()
|
|
76
|
+
|
|
77
|
+
def __iter__(self) -> Iterator:
|
|
78
|
+
self._build_initial_iteration_seq()
|
|
79
|
+
self._iterating = True
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def _build_initial_iteration_seq(self):
|
|
83
|
+
_init_seq = {k: self._time_func(self._buffers[k][-1]) for k in self._keys if self._buffers[k]}
|
|
84
|
+
_init_seq = dict(sorted(_init_seq.items(), key=lambda item: item[1]))
|
|
85
|
+
self._keys = deque(_init_seq.keys())
|
|
86
|
+
|
|
87
|
+
def _load_next_chunk_to_buffer(self, index: str) -> list[Timestamped]:
|
|
88
|
+
try:
|
|
89
|
+
return list(reversed(next(self._iterators[index])))
|
|
90
|
+
except (StopIteration, IndexError, RuntimeError):
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
def _remove_iterator(self, key: str):
|
|
94
|
+
self._buffers.pop(key)
|
|
95
|
+
self._iterators.pop(key)
|
|
96
|
+
self._keys.remove(key)
|
|
97
|
+
|
|
98
|
+
def _pop_top(self, k: str) -> Timestamped:
|
|
99
|
+
"""
|
|
100
|
+
Removes and returns the most recent timestamped data element from the buffer associated with the given key.
|
|
101
|
+
If the buffer is empty after popping, it attempts to load the next chunk of data into the buffer.
|
|
102
|
+
If no more data is available, the iterator associated with the key is removed.
|
|
103
|
+
|
|
104
|
+
Parameters:
|
|
105
|
+
k (str): The key identifying the data stream buffer to pop from.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Timestamped: The most recent timestamped data element from the buffer.
|
|
109
|
+
"""
|
|
110
|
+
if not self._buffers[k]:
|
|
111
|
+
raise StopIteration
|
|
112
|
+
|
|
113
|
+
v = (data := self._buffers[k]).pop()
|
|
114
|
+
if not data:
|
|
115
|
+
try:
|
|
116
|
+
data.extend(self._load_next_chunk_to_buffer(k)) # - get next chunk of data
|
|
117
|
+
except StopIteration:
|
|
118
|
+
self._remove_iterator(k) # - remove iterable data
|
|
119
|
+
return v
|
|
120
|
+
|
|
121
|
+
def fetch_before_time(self, key: str, time_ns: int) -> list[Timestamped]:
|
|
122
|
+
"""
|
|
123
|
+
Fetches and returns all timestamped data elements from the buffer associated with the given key
|
|
124
|
+
that have a timestamp earlier than the specified time.
|
|
125
|
+
|
|
126
|
+
Parameters:
|
|
127
|
+
- key (str): The key identifying the data stream buffer to fetch from.
|
|
128
|
+
- time_ns (int): The timestamp in nanoseconds. All returned elements will have a timestamp less than this value.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
- list[Timestamped]: A list of timestamped data elements that occur before the specified time.
|
|
132
|
+
"""
|
|
133
|
+
values = []
|
|
134
|
+
data = self._buffers[key]
|
|
135
|
+
if not data:
|
|
136
|
+
try:
|
|
137
|
+
data.extend(self._load_next_chunk_to_buffer(key)) # - get next chunk of data
|
|
138
|
+
except StopIteration:
|
|
139
|
+
self._remove_iterator(key)
|
|
140
|
+
# Return empty list if no data is available
|
|
141
|
+
return values
|
|
142
|
+
|
|
143
|
+
# Check if data is still empty after attempting to load
|
|
144
|
+
if not data:
|
|
145
|
+
return values
|
|
146
|
+
|
|
147
|
+
# pull most past elements
|
|
148
|
+
v = data[-1]
|
|
149
|
+
while self._time_func(v) < time_ns:
|
|
150
|
+
values.append(data.pop())
|
|
151
|
+
if not data:
|
|
152
|
+
try:
|
|
153
|
+
data.extend(self._load_next_chunk_to_buffer(key)) # - get next chunk of data
|
|
154
|
+
except StopIteration:
|
|
155
|
+
self._remove_iterator(key)
|
|
156
|
+
break
|
|
157
|
+
# Check if data is still empty after loading attempt
|
|
158
|
+
if not data:
|
|
159
|
+
break
|
|
160
|
+
v = data[-1]
|
|
161
|
+
|
|
162
|
+
return values
|
|
163
|
+
|
|
164
|
+
def __next__(self) -> SlicerOutData:
|
|
165
|
+
"""
|
|
166
|
+
Advances the iterator to the next available timestamped data element across all data streams.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
- SlicerOutData: A tuple containing the key of the data stream, the timestamp of the data element, and the data element itself.
|
|
170
|
+
- NoDataContinue: If there are no data streams but scheduler has pending events.
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
- StopIteration: If there are no more data elements to iterate over and no scheduled events.
|
|
174
|
+
"""
|
|
175
|
+
if not self._keys:
|
|
176
|
+
# DON'T set _iterating = False here! We're still iterating, just temporarily out of data
|
|
177
|
+
# Return sentinel indicating no data streams but iteration could continue
|
|
178
|
+
from qubx.backtester.sentinels import NoDataContinue
|
|
179
|
+
|
|
180
|
+
return ("", 0, NoDataContinue())
|
|
181
|
+
|
|
182
|
+
_min_t = math.inf
|
|
183
|
+
_min_k = self._keys[0]
|
|
184
|
+
for i in self._keys:
|
|
185
|
+
if not self._buffers[i]:
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
_x = self._buffers[i][-1]
|
|
189
|
+
if self._time_func(_x) < _min_t:
|
|
190
|
+
_min_t = self._time_func(_x)
|
|
191
|
+
_min_k = i
|
|
192
|
+
|
|
193
|
+
_v = self._pop_top(_min_k)
|
|
194
|
+
return (_min_k, self._time_func(_v), _v)
|