Qubx 0.7.8__tar.gz → 0.7.35__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.8 → qubx-0.7.35}/PKG-INFO +35 -7
- {qubx-0.7.8 → qubx-0.7.35}/README.md +29 -1
- {qubx-0.7.8 → qubx-0.7.35}/pyproject.toml +7 -5
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/account.py +8 -1
- qubx-0.7.35/src/qubx/backtester/broker.py +168 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/data.py +33 -7
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/management.py +1 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/ome.py +5 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/runner.py +23 -11
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/simulated_exchange.py +23 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/simulator.py +3 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/transfers.py +13 -8
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/utils.py +9 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/commands.py +71 -33
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/release.py +9 -10
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/tui.py +0 -3
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/account.py +22 -10
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/broker.py +225 -69
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/data.py +19 -38
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchange_manager.py +33 -79
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +121 -108
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +1 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/factory.py +6 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -4
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/liquidation.py +7 -6
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/ohlc.py +25 -24
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -6
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/orderbook.py +1 -3
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/quote.py +1 -5
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/trade.py +0 -4
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/reader.py +367 -166
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/utils.py +45 -41
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/tardis/data.py +9 -3
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/__init__.py +3 -4
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/account.py +236 -105
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/broker.py +151 -109
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/client.py +43 -52
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/constants.py +10 -22
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/data.py +268 -333
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/extensions.py +7 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/factory.py +13 -89
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/__init__.py +0 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/orderbook.py +111 -47
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/stats.py +6 -5
- qubx-0.7.35/src/qubx/connectors/xlighter/instruments.py +60 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/nonce.py +9 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/parsers.py +10 -7
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/rate_limits.py +2 -2
- qubx-0.7.35/src/qubx/connectors/xlighter/reader.py +453 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/utils.py +22 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/websocket.py +18 -5
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/account.py +280 -41
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/basics.py +116 -12
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/context.py +80 -48
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/detectors/delisting.py +16 -5
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/errors.py +3 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/helpers.py +67 -4
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/initializer.py +2 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/interfaces.py +418 -158
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/loggers.py +14 -10
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/metrics.py +48 -19
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/market.py +14 -11
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/processing.py +139 -52
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/subscription.py +125 -38
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/trading.py +167 -65
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/universe.py +18 -22
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/series.pyx +27 -6
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/utils.pyi +2 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/utils.pyx +21 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/composite.py +45 -15
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/helpers.py +9 -5
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/readers.py +31 -10
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/storages/questdb.py +18 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/transformers.py +43 -42
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/base.py +8 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/indicator.py +2 -10
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/questdb.py +42 -13
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/base.py +6 -5
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/incremental.py +24 -20
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/target_position.py +7 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/gathering/simplest.py +21 -7
- qubx-0.7.35/src/qubx/health/__init__.py +4 -0
- qubx-0.7.35/src/qubx/health/base.py +543 -0
- qubx-0.7.35/src/qubx/health/dummy.py +99 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/pandaz/__init__.py +18 -16
- {qubx-0.7.8/src/qubx/math → qubx-0.7.35/src/qubx/pandaz}/stats.py +41 -16
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/pandaz/ta.py +2 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restarts/state_resolvers.py +1 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/balance.py +36 -38
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/interfaces.py +2 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/position.py +25 -24
- qubx-0.7.35/src/qubx/state/__init__.py +14 -0
- qubx-0.7.35/src/qubx/state/dummy.py +35 -0
- qubx-0.7.35/src/qubx/state/redis.py +170 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/ta/indicators.pxd +58 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/ta/indicators.pyi +11 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/ta/indicators.pyx +436 -25
- qubx-0.7.35/src/qubx/templates/__init__.py +5 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/base.py +41 -42
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/riskctrl.py +2 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/misc.py +24 -4
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/rate_limiter.py +3 -6
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/_jupyter_runner.pyt +3 -3
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/configs.py +83 -4
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/factory.py +60 -25
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/runner.py +163 -27
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/__init__.py +3 -1
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/app.py +18 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/init_code.py +6 -5
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/repl_output.py +9 -0
- qubx-0.7.35/src/qubx/utils/throttler.py +136 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/time.py +10 -2
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/websocket_manager.py +5 -2
- qubx-0.7.8/src/qubx/backtester/broker.py +0 -137
- qubx-0.7.8/src/qubx/connectors/xlighter/handlers/quote.py +0 -158
- qubx-0.7.8/src/qubx/connectors/xlighter/instruments.py +0 -253
- qubx-0.7.8/src/qubx/connectors/xlighter/reader.py +0 -490
- qubx-0.7.8/src/qubx/core/deque.py +0 -182
- qubx-0.7.8/src/qubx/features/__init__.py +0 -14
- qubx-0.7.8/src/qubx/features/core.py +0 -254
- qubx-0.7.8/src/qubx/features/orderbook.py +0 -42
- qubx-0.7.8/src/qubx/features/price.py +0 -20
- qubx-0.7.8/src/qubx/features/trades.py +0 -105
- qubx-0.7.8/src/qubx/features/utils.py +0 -10
- qubx-0.7.8/src/qubx/health/__init__.py +0 -3
- qubx-0.7.8/src/qubx/health/base.py +0 -672
- qubx-0.7.8/src/qubx/math/__init__.py +0 -3
- qubx-0.7.8/src/qubx/templates/__init__.py +0 -5
- {qubx-0.7.8 → qubx-0.7.35}/LICENSE +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/build.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/misc.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/base.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/trades.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/detectors/stale.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/lookups.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/series.pxd +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/series.pyi +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/containers.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/hft.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/registry.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/storage.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/storages/csv.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/storages/utils.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/tardis.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/composite.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/csv.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/csv.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/factory.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/mongo.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/notifications/throttler.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/_build.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/state.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/template.yml +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/collections.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/nonce.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/questdb.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/handlers.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/slack.py +0 -0
- {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/version.py +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Qubx
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.35
|
|
4
4
|
Summary: Qubx - Quantitative Trading Framework
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Dmitry Marienko
|
|
7
7
|
Author-email: dmitry.marienko@xlydian.com
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -43,21 +42,22 @@ Requires-Dist: python-binance (>=1.0.19,<2.0.0)
|
|
|
43
42
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
44
43
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
45
44
|
Requires-Dist: qubx-bitfinex-api (>=3.0.7,<4.0.0)
|
|
46
|
-
Requires-Dist: qubx-lighter-api (>=0.1.
|
|
45
|
+
Requires-Dist: qubx-lighter-api (>=0.1.5,<0.2.0)
|
|
47
46
|
Requires-Dist: questdb (>=2.0.3,<3.0.0)
|
|
48
47
|
Requires-Dist: redis (>=5.2.1,<6.0.0)
|
|
49
48
|
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
|
50
|
-
Requires-Dist: scikit-learn (>=1.
|
|
49
|
+
Requires-Dist: scikit-learn (>=1.8.0)
|
|
51
50
|
Requires-Dist: scipy (>=1.12.0,<2.0.0)
|
|
52
51
|
Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
|
|
53
52
|
Requires-Dist: stackprinter (>=0.2.10,<0.3.0)
|
|
54
|
-
Requires-Dist: statsmodels (>=0.14.
|
|
53
|
+
Requires-Dist: statsmodels (>=0.14.6,<0.15.0)
|
|
55
54
|
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
56
55
|
Requires-Dist: textual-autocomplete (>=4.0.0,<5.0.0)
|
|
57
56
|
Requires-Dist: textual-serve (>=1.0.0,<2.0.0)
|
|
58
57
|
Requires-Dist: textual[syntax] (>=6.0.0,<7.0.0)
|
|
59
58
|
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
60
59
|
Requires-Dist: tqdm
|
|
60
|
+
Requires-Dist: uvloop (>=0.22.1,<0.23.0)
|
|
61
61
|
Requires-Dist: websockets (==15.0.1)
|
|
62
62
|
Project-URL: Repository, https://github.com/xLydianSoftware/Qubx
|
|
63
63
|
Description-Content-Type: text/markdown
|
|
@@ -69,20 +69,33 @@ Description-Content-Type: text/markdown
|
|
|
69
69
|
```
|
|
70
70
|
⠀⠀⡰⡖⠒⠒⢒⢦⠀⠀
|
|
71
71
|
⠀⢠⠃⠈⢆⣀⣎⣀⣱⡀ QUBX | Quantitative Backtesting Environment
|
|
72
|
-
⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c)
|
|
72
|
+
⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c) 2026, by xLydian
|
|
73
73
|
⠀⠀⠱⣜⣀⣀⣈⣦⠃⠀⠀⠀
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
Qubx is a next-generation quantitative trading framework designed for efficient backtesting and live trading. Built with Python, it offers a robust environment for developing, testing, and deploying trading strategies.
|
|
77
77
|
|
|
78
|
+
**Qubx is under active development.** We are continuously improving the framework and will update our documentation in the coming days/weeks. This will include comprehensive end-to-end examples for running simulations and live trading.
|
|
79
|
+
|
|
80
|
+
### Supported Data Types
|
|
81
|
+
|
|
82
|
+
Qubx supports a wide range of market data:
|
|
83
|
+
- OHLC (candlestick data)
|
|
84
|
+
- L2 Orderbook
|
|
85
|
+
- Liquidations
|
|
86
|
+
- Funding rates
|
|
87
|
+
- And more...
|
|
88
|
+
|
|
78
89
|
## Quick Start
|
|
79
90
|
|
|
80
91
|
### 1. Install Dependencies
|
|
92
|
+
|
|
81
93
|
```bash
|
|
82
94
|
poetry install
|
|
83
95
|
```
|
|
84
96
|
|
|
85
97
|
### 2. Create a Strategy
|
|
98
|
+
|
|
86
99
|
```bash
|
|
87
100
|
# Create a simple strategy template (default)
|
|
88
101
|
poetry run qubx init
|
|
@@ -92,6 +105,7 @@ poetry run qubx init --name my_strategy --symbols BTCUSDT,ETHUSDT
|
|
|
92
105
|
```
|
|
93
106
|
|
|
94
107
|
### 3. Run Your Strategy
|
|
108
|
+
|
|
95
109
|
```bash
|
|
96
110
|
cd my_strategy
|
|
97
111
|
|
|
@@ -103,6 +117,7 @@ poetry run qubx run config.yml --paper
|
|
|
103
117
|
```
|
|
104
118
|
|
|
105
119
|
### Available Templates
|
|
120
|
+
|
|
106
121
|
```bash
|
|
107
122
|
# List available strategy templates
|
|
108
123
|
poetry run qubx init --list-templates
|
|
@@ -112,6 +127,7 @@ poetry run qubx init --template project --name my_project
|
|
|
112
127
|
```
|
|
113
128
|
|
|
114
129
|
### Strategy Development Workflow
|
|
130
|
+
|
|
115
131
|
1. **Initialize**: `poetry run qubx init` - Create strategy from template
|
|
116
132
|
2. **Develop**: Edit `strategy.py` to implement your trading logic
|
|
117
133
|
3. **Test**: `poetry run qubx run config.yml --paper` - Run in paper mode
|
|
@@ -192,6 +208,18 @@ just test
|
|
|
192
208
|
- Build package: `just build`
|
|
193
209
|
- Run verbose tests: `just test-verbose`
|
|
194
210
|
|
|
211
|
+
## In Production
|
|
212
|
+
|
|
213
|
+
Qubx powers the [AllegedAlpha](https://app.lighter.xyz/public-pools/281474976625478) public pool on Lighter. Public pools allow users to deposit funds from their blockchain wallet into a smart contract. The pool operator manages the trading strategy, and a performance fee is taken from profits (X: [@allegedalpha](https://x.com/allegedalpha)).
|
|
214
|
+
|
|
215
|
+
## About xLydian
|
|
216
|
+
|
|
217
|
+
Qubx is developed by [xLydian](https://xlydian.com/).
|
|
218
|
+
|
|
219
|
+
- Website: [xlydian.com](https://xlydian.com/)
|
|
220
|
+
- X: [@xLydian_xyz](https://x.com/xLydian_xyz)
|
|
221
|
+
- Contact: [info@xlydian.com](mailto:info@xlydian.com)
|
|
222
|
+
|
|
195
223
|
## Contributing
|
|
196
224
|
|
|
197
225
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -5,20 +5,33 @@
|
|
|
5
5
|
```
|
|
6
6
|
⠀⠀⡰⡖⠒⠒⢒⢦⠀⠀
|
|
7
7
|
⠀⢠⠃⠈⢆⣀⣎⣀⣱⡀ QUBX | Quantitative Backtesting Environment
|
|
8
|
-
⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c)
|
|
8
|
+
⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c) 2026, by xLydian
|
|
9
9
|
⠀⠀⠱⣜⣀⣀⣈⣦⠃⠀⠀⠀
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Qubx is a next-generation quantitative trading framework designed for efficient backtesting and live trading. Built with Python, it offers a robust environment for developing, testing, and deploying trading strategies.
|
|
13
13
|
|
|
14
|
+
**Qubx is under active development.** We are continuously improving the framework and will update our documentation in the coming days/weeks. This will include comprehensive end-to-end examples for running simulations and live trading.
|
|
15
|
+
|
|
16
|
+
### Supported Data Types
|
|
17
|
+
|
|
18
|
+
Qubx supports a wide range of market data:
|
|
19
|
+
- OHLC (candlestick data)
|
|
20
|
+
- L2 Orderbook
|
|
21
|
+
- Liquidations
|
|
22
|
+
- Funding rates
|
|
23
|
+
- And more...
|
|
24
|
+
|
|
14
25
|
## Quick Start
|
|
15
26
|
|
|
16
27
|
### 1. Install Dependencies
|
|
28
|
+
|
|
17
29
|
```bash
|
|
18
30
|
poetry install
|
|
19
31
|
```
|
|
20
32
|
|
|
21
33
|
### 2. Create a Strategy
|
|
34
|
+
|
|
22
35
|
```bash
|
|
23
36
|
# Create a simple strategy template (default)
|
|
24
37
|
poetry run qubx init
|
|
@@ -28,6 +41,7 @@ poetry run qubx init --name my_strategy --symbols BTCUSDT,ETHUSDT
|
|
|
28
41
|
```
|
|
29
42
|
|
|
30
43
|
### 3. Run Your Strategy
|
|
44
|
+
|
|
31
45
|
```bash
|
|
32
46
|
cd my_strategy
|
|
33
47
|
|
|
@@ -39,6 +53,7 @@ poetry run qubx run config.yml --paper
|
|
|
39
53
|
```
|
|
40
54
|
|
|
41
55
|
### Available Templates
|
|
56
|
+
|
|
42
57
|
```bash
|
|
43
58
|
# List available strategy templates
|
|
44
59
|
poetry run qubx init --list-templates
|
|
@@ -48,6 +63,7 @@ poetry run qubx init --template project --name my_project
|
|
|
48
63
|
```
|
|
49
64
|
|
|
50
65
|
### Strategy Development Workflow
|
|
66
|
+
|
|
51
67
|
1. **Initialize**: `poetry run qubx init` - Create strategy from template
|
|
52
68
|
2. **Develop**: Edit `strategy.py` to implement your trading logic
|
|
53
69
|
3. **Test**: `poetry run qubx run config.yml --paper` - Run in paper mode
|
|
@@ -128,6 +144,18 @@ just test
|
|
|
128
144
|
- Build package: `just build`
|
|
129
145
|
- Run verbose tests: `just test-verbose`
|
|
130
146
|
|
|
147
|
+
## In Production
|
|
148
|
+
|
|
149
|
+
Qubx powers the [AllegedAlpha](https://app.lighter.xyz/public-pools/281474976625478) public pool on Lighter. Public pools allow users to deposit funds from their blockchain wallet into a smart contract. The pool operator manages the trading strategy, and a performance fee is taken from profits (X: [@allegedalpha](https://x.com/allegedalpha)).
|
|
150
|
+
|
|
151
|
+
## About xLydian
|
|
152
|
+
|
|
153
|
+
Qubx is developed by [xLydian](https://xlydian.com/).
|
|
154
|
+
|
|
155
|
+
- Website: [xlydian.com](https://xlydian.com/)
|
|
156
|
+
- X: [@xLydian_xyz](https://x.com/xLydian_xyz)
|
|
157
|
+
- Contact: [info@xlydian.com](mailto:info@xlydian.com)
|
|
158
|
+
|
|
131
159
|
## Contributing
|
|
132
160
|
|
|
133
161
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "Qubx"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.35"
|
|
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"
|
|
@@ -32,13 +32,13 @@ generate-setup-file = false
|
|
|
32
32
|
qubx = "qubx.cli.commands:main"
|
|
33
33
|
|
|
34
34
|
[tool.poetry.dependencies]
|
|
35
|
-
python = ">=3.
|
|
35
|
+
python = ">=3.11,<4.0"
|
|
36
36
|
numpy = "^1.26.3"
|
|
37
37
|
pandas = "^2.2.2"
|
|
38
38
|
pyarrow = "^15.0.0"
|
|
39
39
|
scipy = "^1.12.0"
|
|
40
|
-
scikit-learn = "
|
|
41
|
-
statsmodels = "^0.14.
|
|
40
|
+
scikit-learn = ">=1.8.0"
|
|
41
|
+
statsmodels = "^0.14.6"
|
|
42
42
|
numba = "^0.59.1"
|
|
43
43
|
sortedcontainers = "^2.4.0"
|
|
44
44
|
ntplib = "^0.4.0"
|
|
@@ -78,7 +78,8 @@ textual-autocomplete = "^4.0.0"
|
|
|
78
78
|
textual-serve = "^1.0.0"
|
|
79
79
|
rich = "^13.9.4"
|
|
80
80
|
jinja2 = "^3.1.0"
|
|
81
|
-
qubx-lighter-api = "^0.1.
|
|
81
|
+
qubx-lighter-api = "^0.1.5"
|
|
82
|
+
uvloop = "^0.22.1"
|
|
82
83
|
|
|
83
84
|
[tool.ruff.lint]
|
|
84
85
|
extend-select = [ "I",]
|
|
@@ -119,6 +120,7 @@ jinja2 = "3.1.5"
|
|
|
119
120
|
mike = "2.1.3"
|
|
120
121
|
mkdocs-jupyter = "0.25.1"
|
|
121
122
|
debugpy = "^1.8.12"
|
|
123
|
+
py-spy = "^0.4.1"
|
|
122
124
|
|
|
123
125
|
[tool.poetry.group.test.dependencies]
|
|
124
126
|
pytest-asyncio = "^0.24.0"
|
|
@@ -8,6 +8,7 @@ from qubx.core.basics import (
|
|
|
8
8
|
Timestamped,
|
|
9
9
|
dt_64,
|
|
10
10
|
)
|
|
11
|
+
from qubx.core.interfaces import IHealthMonitor
|
|
11
12
|
from qubx.core.series import OrderBook, Quote, Trade, TradeArray
|
|
12
13
|
from qubx.restorers import RestoredState
|
|
13
14
|
|
|
@@ -21,7 +22,9 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
|
|
|
21
22
|
account_id: str,
|
|
22
23
|
exchange: ISimulatedExchange,
|
|
23
24
|
channel: CtrlChannel,
|
|
25
|
+
health_monitor: IHealthMonitor,
|
|
24
26
|
base_currency: str,
|
|
27
|
+
exchange_name: str,
|
|
25
28
|
initial_capital: float,
|
|
26
29
|
restored_state: RestoredState | None = None,
|
|
27
30
|
) -> None:
|
|
@@ -29,6 +32,8 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
|
|
|
29
32
|
account_id=account_id,
|
|
30
33
|
time_provider=exchange.get_time_provider(),
|
|
31
34
|
base_currency=base_currency,
|
|
35
|
+
health_monitor=health_monitor,
|
|
36
|
+
exchange=exchange_name,
|
|
32
37
|
tcc=exchange.get_transaction_costs_calculator(),
|
|
33
38
|
initial_capital=initial_capital,
|
|
34
39
|
)
|
|
@@ -37,7 +42,9 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
|
|
|
37
42
|
self._channel = channel
|
|
38
43
|
|
|
39
44
|
if restored_state is not None:
|
|
40
|
-
|
|
45
|
+
# Convert list of AssetBalance to dict for internal storage
|
|
46
|
+
for balance in restored_state.balances:
|
|
47
|
+
self._balances[balance.currency] = balance
|
|
41
48
|
for instrument, position in restored_state.positions.items():
|
|
42
49
|
_pos = self.get_position(instrument)
|
|
43
50
|
_pos.reset_by_position(position)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from qubx import logger
|
|
2
|
+
from qubx.backtester.ome import SimulatedExecutionReport
|
|
3
|
+
from qubx.backtester.simulated_exchange import ISimulatedExchange
|
|
4
|
+
from qubx.core.basics import (
|
|
5
|
+
CtrlChannel,
|
|
6
|
+
Instrument,
|
|
7
|
+
Order,
|
|
8
|
+
OrderRequest,
|
|
9
|
+
)
|
|
10
|
+
from qubx.core.exceptions import BadRequest, OrderNotFound
|
|
11
|
+
from qubx.core.interfaces import IBroker
|
|
12
|
+
|
|
13
|
+
from .account import SimulatedAccountProcessor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SimulatedBroker(IBroker):
|
|
17
|
+
channel: CtrlChannel
|
|
18
|
+
|
|
19
|
+
_account: SimulatedAccountProcessor
|
|
20
|
+
_exchange: ISimulatedExchange
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
channel: CtrlChannel,
|
|
25
|
+
account: SimulatedAccountProcessor,
|
|
26
|
+
simulated_exchange: ISimulatedExchange,
|
|
27
|
+
) -> None:
|
|
28
|
+
self.channel = channel
|
|
29
|
+
self._account = account
|
|
30
|
+
self._exchange = simulated_exchange
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def is_simulated_trading(self) -> bool:
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
def send_order(self, request: OrderRequest) -> Order:
|
|
37
|
+
"""Submit order synchronously in simulation."""
|
|
38
|
+
instrument = request.instrument
|
|
39
|
+
order_side = request.side
|
|
40
|
+
order_type = request.order_type
|
|
41
|
+
amount = request.quantity
|
|
42
|
+
price = request.price
|
|
43
|
+
client_id = request.client_id
|
|
44
|
+
time_in_force = request.time_in_force
|
|
45
|
+
options = request.options
|
|
46
|
+
|
|
47
|
+
self._send_execution_report(
|
|
48
|
+
report := self._exchange.place_order(
|
|
49
|
+
instrument, order_side, order_type, amount, price, client_id, time_in_force, **options
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
return report.order
|
|
53
|
+
|
|
54
|
+
def send_order_async(self, request: OrderRequest) -> str | None:
|
|
55
|
+
"""Submit order asynchronously (same as sync in simulation)."""
|
|
56
|
+
self.send_order(request)
|
|
57
|
+
return request.client_id
|
|
58
|
+
|
|
59
|
+
def _validate_order_ids(self, order_id: str | None, client_order_id: str | None) -> None:
|
|
60
|
+
if (order_id is None and client_order_id is None) or (order_id is not None and client_order_id is not None):
|
|
61
|
+
raise ValueError("Exactly one of order_id or client_order_id must be provided")
|
|
62
|
+
|
|
63
|
+
def _resolve_order_id(self, order_id: str | None, client_order_id: str | None) -> str | None:
|
|
64
|
+
if order_id is not None:
|
|
65
|
+
return order_id
|
|
66
|
+
if client_order_id is not None:
|
|
67
|
+
order = self._account.find_order_by_client_id(client_order_id)
|
|
68
|
+
return order.id if order is not None else None
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def cancel_order(self, order_id: str | None = None, client_order_id: str | None = None) -> bool:
|
|
72
|
+
"""Cancel an order synchronously and return success status."""
|
|
73
|
+
self._validate_order_ids(order_id, client_order_id)
|
|
74
|
+
resolved_id = self._resolve_order_id(order_id, client_order_id)
|
|
75
|
+
if resolved_id is None:
|
|
76
|
+
return False
|
|
77
|
+
try:
|
|
78
|
+
self._send_execution_report(order_update := self._exchange.cancel_order(resolved_id))
|
|
79
|
+
return order_update is not None
|
|
80
|
+
except OrderNotFound:
|
|
81
|
+
# Order was already cancelled or doesn't exist
|
|
82
|
+
logger.debug(f"Order {resolved_id} not found")
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
def cancel_order_async(self, order_id: str | None = None, client_order_id: str | None = None) -> None:
|
|
86
|
+
"""Cancel an order asynchronously (fire-and-forget)."""
|
|
87
|
+
self._validate_order_ids(order_id, client_order_id)
|
|
88
|
+
resolved_id = self._resolve_order_id(order_id, client_order_id)
|
|
89
|
+
if resolved_id is None:
|
|
90
|
+
return
|
|
91
|
+
self.cancel_order(order_id=resolved_id)
|
|
92
|
+
|
|
93
|
+
def cancel_orders(self, instrument: Instrument) -> None:
|
|
94
|
+
raise NotImplementedError("Not implemented yet")
|
|
95
|
+
|
|
96
|
+
def update_order(
|
|
97
|
+
self, price: float, amount: float, order_id: str | None = None, client_order_id: str | None = None
|
|
98
|
+
) -> Order:
|
|
99
|
+
"""Update an existing limit order using cancel+recreate strategy.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
order_id: The ID of the order to update
|
|
103
|
+
price: New price for the order
|
|
104
|
+
amount: New amount for the order
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Order: The updated (newly created) order object
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
OrderNotFound: If the order is not found
|
|
111
|
+
BadRequest: If the order is not a limit order
|
|
112
|
+
"""
|
|
113
|
+
self._validate_order_ids(order_id, client_order_id)
|
|
114
|
+
resolved_id = self._resolve_order_id(order_id, client_order_id)
|
|
115
|
+
if resolved_id is None:
|
|
116
|
+
raise OrderNotFound(f"Order {order_id or client_order_id} not found")
|
|
117
|
+
|
|
118
|
+
# Get the existing order from account
|
|
119
|
+
active_orders = self._account.get_orders()
|
|
120
|
+
existing_order = active_orders.get(resolved_id)
|
|
121
|
+
if not existing_order:
|
|
122
|
+
raise OrderNotFound(f"Order {resolved_id} not found")
|
|
123
|
+
|
|
124
|
+
# Validate that it's a limit order
|
|
125
|
+
if existing_order.type != "LIMIT":
|
|
126
|
+
raise BadRequest(
|
|
127
|
+
f"Order {resolved_id} is not a limit order (type: {existing_order.type}). "
|
|
128
|
+
"Only limit orders can be updated."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
self.cancel_order(order_id=resolved_id)
|
|
132
|
+
|
|
133
|
+
request = OrderRequest(
|
|
134
|
+
instrument=existing_order.instrument,
|
|
135
|
+
quantity=abs(amount),
|
|
136
|
+
price=price,
|
|
137
|
+
order_type="LIMIT",
|
|
138
|
+
side=existing_order.side,
|
|
139
|
+
time_in_force=existing_order.time_in_force or "gtc",
|
|
140
|
+
client_id=existing_order.client_id,
|
|
141
|
+
options={},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
updated_order = self.send_order(request)
|
|
145
|
+
|
|
146
|
+
return updated_order
|
|
147
|
+
|
|
148
|
+
def update_order_async(
|
|
149
|
+
self, price: float, amount: float, order_id: str | None = None, client_order_id: str | None = None
|
|
150
|
+
) -> str | None:
|
|
151
|
+
"""Update order asynchronously (same as sync in simulation)."""
|
|
152
|
+
self._validate_order_ids(order_id, client_order_id)
|
|
153
|
+
resolved_id = self._resolve_order_id(order_id, client_order_id)
|
|
154
|
+
if resolved_id is None:
|
|
155
|
+
return None
|
|
156
|
+
updated_order = self.update_order(order_id=resolved_id, price=price, amount=amount)
|
|
157
|
+
return updated_order.client_id
|
|
158
|
+
|
|
159
|
+
def _send_execution_report(self, report: SimulatedExecutionReport | None):
|
|
160
|
+
if report is None:
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
self.channel.send((report.instrument, "order", report.order, False))
|
|
164
|
+
if report.exec is not None:
|
|
165
|
+
self.channel.send((report.instrument, "deals", [report.exec], False))
|
|
166
|
+
|
|
167
|
+
def exchange(self) -> str:
|
|
168
|
+
return self._exchange.exchange_id
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import TypeVar
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
@@ -74,16 +74,35 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
74
74
|
def is_simulation(self) -> bool:
|
|
75
75
|
return True
|
|
76
76
|
|
|
77
|
+
def is_connected(self) -> bool:
|
|
78
|
+
"""
|
|
79
|
+
Check if the data provider is currently connected to the exchange.
|
|
80
|
+
|
|
81
|
+
For simulated data provider, always returns True since data is loaded from files.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
bool: Always True for simulated data
|
|
85
|
+
"""
|
|
86
|
+
return True
|
|
87
|
+
|
|
77
88
|
def subscribe(self, subscription_type: str, instruments: set[Instrument], reset: bool) -> None:
|
|
78
89
|
_new_instr = [i for i in instruments if not self.has_subscription(i, subscription_type)]
|
|
90
|
+
|
|
79
91
|
self._data_source.add_instruments_for_subscription(subscription_type, list(instruments))
|
|
80
92
|
|
|
81
93
|
# - provide historical data and last quote for subscribed instruments
|
|
82
94
|
for i in _new_instr:
|
|
83
|
-
#
|
|
95
|
+
# - check if the instrument was actually subscribed (not filtered out)
|
|
84
96
|
if not self.has_subscription(i, subscription_type):
|
|
85
97
|
continue
|
|
86
98
|
|
|
99
|
+
# - notify simulating exchange that instrument is subscribed
|
|
100
|
+
self._account._exchange.on_subscribe(i)
|
|
101
|
+
|
|
102
|
+
# - we need to clear last quote as it can be staled
|
|
103
|
+
self._last_quotes.pop(i, None)
|
|
104
|
+
|
|
105
|
+
# - try to peek most recent market data
|
|
87
106
|
h_data = self._data_source.peek_historical_data(i, subscription_type)
|
|
88
107
|
if h_data:
|
|
89
108
|
# _s_type = DataType.from_str(subscription_type)[0]
|
|
@@ -103,9 +122,16 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
103
122
|
def unsubscribe(self, subscription_type: str, instruments: set[Instrument] | Instrument | None = None) -> None:
|
|
104
123
|
# logger.debug(f" | unsubscribe: {subscription_type} -> {instruments}")
|
|
105
124
|
if instruments is not None:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
_instruments = [instruments] if isinstance(instruments, Instrument) else list(instruments)
|
|
126
|
+
self._data_source.remove_instruments_from_subscription(subscription_type, _instruments)
|
|
127
|
+
|
|
128
|
+
# - Clear last quotes for unsubscribed instruments
|
|
129
|
+
for instr in _instruments:
|
|
130
|
+
# - clear last quote
|
|
131
|
+
self._last_quotes.pop(instr, None)
|
|
132
|
+
|
|
133
|
+
# - Notify simulating exchange that instrument is unsubscribed
|
|
134
|
+
self._account._exchange.on_unsubscribe(instr)
|
|
109
135
|
|
|
110
136
|
def has_subscription(self, instrument: Instrument, subscription_type: str) -> bool:
|
|
111
137
|
return self._data_source.has_subscription(instrument, subscription_type)
|
|
@@ -174,11 +200,11 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
174
200
|
high_price = r.data["high"]
|
|
175
201
|
low_price = r.data["low"]
|
|
176
202
|
close_price = r.data["close"]
|
|
177
|
-
|
|
203
|
+
|
|
178
204
|
# Skip this record if any OHLC value is None
|
|
179
205
|
if open_price is None or high_price is None or low_price is None or close_price is None:
|
|
180
206
|
continue
|
|
181
|
-
|
|
207
|
+
|
|
182
208
|
bars.append(
|
|
183
209
|
Bar(
|
|
184
210
|
_b_ts_0,
|
|
@@ -382,7 +382,7 @@ class BacktestsResultsManager:
|
|
|
382
382
|
_mtrx[_nm] = v.get("performance", {})
|
|
383
383
|
|
|
384
384
|
_m_repr = pd.DataFrame.from_dict(_mtrx, orient="index")[
|
|
385
|
-
["gain", "cagr", "sharpe", "qr", "
|
|
385
|
+
["gain", "cagr", "sharpe", "qr", "mdd_pct", "mdd_usd", "fees", "execs"]
|
|
386
386
|
].astype(float)
|
|
387
387
|
_m_repr = _m_repr.round(3)
|
|
388
388
|
_m_repr = _m_repr.sort_values(by=sort_by, ascending=ascending) if sort_by else _m_repr
|
|
@@ -79,8 +79,11 @@ class OrdersManagementEngine:
|
|
|
79
79
|
self.active_orders = dict()
|
|
80
80
|
self.stop_orders = dict()
|
|
81
81
|
self.bbo = None
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
# - 2025-12-09: OME always started with fixed order_id / trade_id
|
|
83
|
+
# executions stopped to be accounted in portfolio when OME re-created
|
|
84
|
+
_start_id = int(time_provider.time().view("i8")) + 1000
|
|
85
|
+
self.__order_id = _start_id
|
|
86
|
+
self.__trade_id = _start_id
|
|
84
87
|
self._fill_stops_at_price = fill_stop_order_at_price
|
|
85
88
|
self._tick_size = instrument.tick_size
|
|
86
89
|
self._last_update_time = np.datetime64(0, "ns")
|
|
@@ -16,6 +16,7 @@ from qubx.core.initializer import BasicStrategyInitializer
|
|
|
16
16
|
from qubx.core.interfaces import (
|
|
17
17
|
CtrlChannel,
|
|
18
18
|
IDataProvider,
|
|
19
|
+
IHealthMonitor,
|
|
19
20
|
IMetricEmitter,
|
|
20
21
|
IStrategy,
|
|
21
22
|
IStrategyContext,
|
|
@@ -26,6 +27,7 @@ from qubx.core.interfaces import (
|
|
|
26
27
|
from qubx.core.loggers import StrategyLogging
|
|
27
28
|
from qubx.core.lookups import lookup
|
|
28
29
|
from qubx.data.helpers import CachedPrefetchReader
|
|
30
|
+
from qubx.health import DummyHealthMonitor
|
|
29
31
|
from qubx.loggers.inmemory import InMemoryLogsWriter
|
|
30
32
|
from qubx.pandaz.utils import _frame_to_str
|
|
31
33
|
from qubx.utils.time import now_ns
|
|
@@ -140,18 +142,20 @@ class SimulationRunner:
|
|
|
140
142
|
|
|
141
143
|
self._prefetch_aux_data()
|
|
142
144
|
|
|
143
|
-
#
|
|
145
|
+
# - Apply warmup periods before the start
|
|
146
|
+
# - merge default warmups with strategy warmups (strategy warmups take precedence)
|
|
147
|
+
_merged_warmups = {
|
|
148
|
+
**(self.data_config.default_warmups or {}),
|
|
149
|
+
**self.ctx.initializer.get_subscription_warmup(),
|
|
150
|
+
}
|
|
151
|
+
if _merged_warmups:
|
|
152
|
+
logger.debug(f"[<y>SimulationRunner</y>] :: Setting warmups: {_merged_warmups}")
|
|
153
|
+
self.ctx.set_warmup(_merged_warmups)
|
|
154
|
+
|
|
155
|
+
# - Start the context
|
|
144
156
|
self.ctx.start()
|
|
145
157
|
|
|
146
|
-
#
|
|
147
|
-
for s in self.ctx.get_subscriptions():
|
|
148
|
-
if not self.ctx.get_warmup(s) and (_d_wt := self.data_config.default_warmups.get(s)):
|
|
149
|
-
logger.debug(
|
|
150
|
-
f"[<y>SimulationRunner</y>] :: Strategy didn't set warmup period for <c>{s}</c> so default <c>{_d_wt}</c> will be used"
|
|
151
|
-
)
|
|
152
|
-
self.ctx.set_warmup({s: _d_wt})
|
|
153
|
-
|
|
154
|
-
# Subscribe to any custom data types if needed
|
|
158
|
+
# - Subscribe to any custom data types if needed
|
|
155
159
|
def _is_known_type(t: str):
|
|
156
160
|
try:
|
|
157
161
|
DataType(t)
|
|
@@ -387,9 +391,14 @@ class SimulationRunner:
|
|
|
387
391
|
|
|
388
392
|
channel = SimulatedCtrlChannel("databus", sentinel=(None, None, None, None))
|
|
389
393
|
simulated_clock = SimulatedTimeProvider(np.datetime64(self.start, "ns"))
|
|
394
|
+
health_monitor = DummyHealthMonitor()
|
|
390
395
|
|
|
391
396
|
account = self._construct_account_processor(
|
|
392
|
-
self.setup.exchanges,
|
|
397
|
+
self.setup.exchanges,
|
|
398
|
+
self.setup.commissions,
|
|
399
|
+
simulated_clock,
|
|
400
|
+
channel,
|
|
401
|
+
health_monitor,
|
|
393
402
|
)
|
|
394
403
|
|
|
395
404
|
scheduler = SimulatedScheduler(channel, lambda: simulated_clock.time().item())
|
|
@@ -528,6 +537,7 @@ class SimulationRunner:
|
|
|
528
537
|
commissions: str | dict[str, str | None] | None,
|
|
529
538
|
time_provider: ITimeProvider,
|
|
530
539
|
channel: CtrlChannel,
|
|
540
|
+
health_monitor: IHealthMonitor,
|
|
531
541
|
) -> CompositeAccountProcessor:
|
|
532
542
|
_exchange_to_tcc = self._construct_tcc(exchanges, commissions)
|
|
533
543
|
for tcc in _exchange_to_tcc.values():
|
|
@@ -555,7 +565,9 @@ class SimulationRunner:
|
|
|
555
565
|
account_id=self.account_id,
|
|
556
566
|
exchange=_exchange_to_simulated_exchange[exchange],
|
|
557
567
|
channel=channel,
|
|
568
|
+
health_monitor=health_monitor,
|
|
558
569
|
base_currency=self.setup.base_currency,
|
|
570
|
+
exchange_name=exchange,
|
|
559
571
|
initial_capital=_initial_capital,
|
|
560
572
|
)
|
|
561
573
|
|
|
@@ -47,6 +47,10 @@ class ISimulatedExchange:
|
|
|
47
47
|
|
|
48
48
|
def get_open_orders(self, instrument: Instrument | None = None) -> dict[str, Order]: ...
|
|
49
49
|
|
|
50
|
+
def on_unsubscribe(self, instrument: Instrument) -> None: ...
|
|
51
|
+
|
|
52
|
+
def on_subscribe(self, instrument: Instrument) -> None: ...
|
|
53
|
+
|
|
50
54
|
def process_market_data(
|
|
51
55
|
self, instrument: Instrument, data: Quote | OrderBook | Trade | TradeArray
|
|
52
56
|
) -> Generator[SimulatedExecutionReport]: ...
|
|
@@ -93,6 +97,7 @@ class BasicSimulatedExchange(ISimulatedExchange):
|
|
|
93
97
|
"""
|
|
94
98
|
|
|
95
99
|
_ome: dict[Instrument, OrdersManagementEngine]
|
|
100
|
+
_half_tick_size: dict[Instrument, float]
|
|
96
101
|
_order_to_instrument: dict[str, Instrument]
|
|
97
102
|
_fill_stop_order_at_price: bool
|
|
98
103
|
_time_provider: ITimeProvider
|
|
@@ -112,6 +117,7 @@ class BasicSimulatedExchange(ISimulatedExchange):
|
|
|
112
117
|
self._fill_stop_order_at_price = accurate_stop_orders_execution
|
|
113
118
|
self._time_provider = time_provider
|
|
114
119
|
self._tcc = tcc
|
|
120
|
+
|
|
115
121
|
if self._fill_stop_order_at_price:
|
|
116
122
|
logger.info(
|
|
117
123
|
f"[<y>{self.__class__.__name__}</y>] :: emulation of stop orders executions at exact price is ON"
|
|
@@ -193,6 +199,23 @@ class BasicSimulatedExchange(ISimulatedExchange):
|
|
|
193
199
|
|
|
194
200
|
return {o.id: o for ome in self._ome.values() for o in ome.get_open_orders()}
|
|
195
201
|
|
|
202
|
+
def on_unsubscribe(self, instrument: Instrument) -> None:
|
|
203
|
+
"""
|
|
204
|
+
Called when an instrument is unsubscribed.
|
|
205
|
+
"""
|
|
206
|
+
# - clears the OME to remove stale BBO data.
|
|
207
|
+
self._ome.pop(instrument, None)
|
|
208
|
+
self._half_tick_size.pop(instrument, None)
|
|
209
|
+
|
|
210
|
+
def on_subscribe(self, instrument: Instrument) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Called when new instrument is subscribed.
|
|
213
|
+
"""
|
|
214
|
+
# - just for sanity: remove OME for this instrument if it wasn't removed in on_unsubscribe call
|
|
215
|
+
if instrument in self._ome:
|
|
216
|
+
self._ome.pop(instrument, None)
|
|
217
|
+
self._half_tick_size.pop(instrument, None)
|
|
218
|
+
|
|
196
219
|
def _get_ome(self, instrument: Instrument) -> OrdersManagementEngine:
|
|
197
220
|
if (ome := self._ome.get(instrument)) is None:
|
|
198
221
|
self._half_tick_size[instrument] = instrument.tick_size / 2 # type: ignore
|