Qubx 0.6.64__tar.gz → 0.7.25__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.6.64 → qubx-0.7.25}/PKG-INFO +58 -6
- {qubx-0.6.64 → qubx-0.7.25}/README.md +48 -3
- {qubx-0.6.64 → qubx-0.7.25}/pyproject.toml +12 -3
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/__init__.py +1 -1
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/account.py +9 -2
- qubx-0.7.25/src/qubx/backtester/broker.py +128 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/data.py +64 -11
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/management.py +18 -5
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/ome.py +5 -2
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/runner.py +164 -33
- qubx-0.7.25/src/qubx/backtester/sentinels.py +23 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/simulated_data.py +23 -9
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/simulated_exchange.py +29 -3
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/simulator.py +54 -7
- qubx-0.7.25/src/qubx/backtester/transfers.py +151 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/utils.py +58 -18
- qubx-0.7.25/src/qubx/cli/commands.py +607 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/cli/release.py +351 -26
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/account.py +62 -29
- qubx-0.7.25/src/qubx/connectors/ccxt/adapters/__init__.py +7 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/adapters/polling_adapter.py +247 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/broker.py +223 -52
- qubx-0.7.25/src/qubx/connectors/ccxt/connection_manager.py +319 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/data.py +337 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/exchange_manager.py +265 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/exchanges/__init__.py +36 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/exchanges/base.py +63 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +359 -127
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +3 -1
- qubx-0.7.25/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +7 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +75 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +306 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +602 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +4 -2
- qubx-0.7.25/src/qubx/connectors/ccxt/factory.py +177 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/__init__.py +29 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/base.py +93 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/factory.py +123 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/funding_rate.py +213 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/liquidation.py +93 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/ohlc.py +377 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/open_interest.py +201 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/orderbook.py +219 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/quote.py +99 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/handlers/trade.py +203 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/reader.py +869 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/subscription_config.py +87 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/subscription_manager.py +337 -0
- qubx-0.7.25/src/qubx/connectors/ccxt/subscription_orchestrator.py +370 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/utils.py +129 -23
- qubx-0.7.25/src/qubx/connectors/ccxt/warmup_service.py +122 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/tardis/data.py +15 -9
- qubx-0.7.25/src/qubx/connectors/xlighter/__init__.py +82 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/account.py +557 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/broker.py +803 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/client.py +414 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/constants.py +129 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/data.py +725 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/extensions.py +248 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/factory.py +239 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/handlers/__init__.py +13 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/handlers/base.py +116 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/handlers/orderbook.py +374 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/handlers/stats.py +352 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/handlers/trades.py +146 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/instruments.py +60 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/nonce.py +21 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/parsers.py +657 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/rate_limits.py +96 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/reader.py +453 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/utils.py +303 -0
- qubx-0.7.25/src/qubx/connectors/xlighter/websocket.py +490 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/account.py +310 -55
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/basics.py +302 -45
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/context.py +330 -85
- qubx-0.7.25/src/qubx/core/detectors/__init__.py +4 -0
- qubx-0.7.25/src/qubx/core/detectors/delisting.py +92 -0
- qubx-0.7.25/src/qubx/core/detectors/stale.py +419 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/exceptions.py +4 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/helpers.py +188 -16
- qubx-0.7.25/src/qubx/core/initializer.py +249 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/interfaces.py +799 -139
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/loggers.py +16 -10
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/lookups.py +1 -1
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/metrics.py +668 -46
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/mixins/market.py +30 -4
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/mixins/processing.py +281 -62
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/mixins/subscription.py +150 -43
- qubx-0.7.25/src/qubx/core/mixins/trading.py +547 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/mixins/universe.py +26 -12
- qubx-0.7.25/src/qubx/core/mixins/utils.py +4 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/series.pxd +43 -8
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/series.pyi +95 -3
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/series.pyx +460 -68
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/utils.pyx +21 -2
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/data/__init__.py +12 -1
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/data/composite.py +322 -19
- qubx-0.7.25/src/qubx/data/containers.py +234 -0
- qubx-0.7.25/src/qubx/data/helpers.py +2080 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/data/readers.py +230 -51
- qubx-0.7.25/src/qubx/data/registry.py +242 -0
- qubx-0.7.25/src/qubx/data/storage.py +74 -0
- qubx-0.7.25/src/qubx/data/storages/csv.py +273 -0
- qubx-0.7.25/src/qubx/data/storages/questdb.py +596 -0
- qubx-0.7.25/src/qubx/data/storages/utils.py +115 -0
- qubx-0.7.25/src/qubx/data/transformers.py +494 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/emitters/base.py +32 -16
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/emitters/composite.py +30 -1
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/emitters/csv.py +2 -1
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/emitters/indicator.py +16 -17
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/emitters/inmemory.py +5 -4
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/emitters/prometheus.py +2 -2
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/emitters/questdb.py +136 -35
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/exporters/formatters/__init__.py +8 -1
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/exporters/formatters/base.py +0 -1
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/exporters/formatters/incremental.py +8 -8
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/exporters/formatters/slack.py +6 -4
- qubx-0.7.25/src/qubx/exporters/formatters/target_position.py +76 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/exporters/redis_streams.py +27 -7
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/exporters/slack.py +59 -74
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/gathering/simplest.py +5 -3
- qubx-0.7.25/src/qubx/health/__init__.py +4 -0
- qubx-0.7.25/src/qubx/health/base.py +543 -0
- qubx-0.7.25/src/qubx/health/dummy.py +99 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/loggers/csv.py +4 -4
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/notifications/__init__.py +5 -5
- qubx-0.7.25/src/qubx/notifications/composite.py +83 -0
- qubx-0.7.25/src/qubx/notifications/slack.py +189 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/pandaz/ta.py +235 -34
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/pandaz/utils.py +1 -1
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/crypto-fees.ini +8 -1
- qubx-0.7.25/src/qubx/resources/instruments/hyperliquid-spot.json +4204 -0
- qubx-0.7.25/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +4424 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restarts/state_resolvers.py +10 -5
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restorers/balance.py +36 -38
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restorers/interfaces.py +2 -2
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restorers/position.py +25 -24
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restorers/signal.py +2 -2
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/ta/indicators.pxd +123 -3
- qubx-0.7.25/src/qubx/ta/indicators.pyi +97 -0
- qubx-0.7.25/src/qubx/ta/indicators.pyx +1866 -0
- qubx-0.7.25/src/qubx/templates/__init__.py +5 -0
- qubx-0.7.25/src/qubx/templates/base.py +166 -0
- qubx-0.7.25/src/qubx/templates/project/accounts.toml.j2 +22 -0
- qubx-0.7.25/src/qubx/templates/project/config.yml.j2 +33 -0
- qubx-0.7.25/src/qubx/templates/project/jlive.sh.j2 +43 -0
- qubx-0.7.25/src/qubx/templates/project/jpaper.sh.j2 +6 -0
- qubx-0.7.25/src/qubx/templates/project/pyproject.toml.j2 +18 -0
- qubx-0.7.25/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +5 -0
- qubx-0.7.25/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +170 -0
- qubx-0.7.25/src/qubx/templates/project/template.yml +20 -0
- qubx-0.7.25/src/qubx/templates/simple/__init__.py.j2 +5 -0
- qubx-0.7.25/src/qubx/templates/simple/accounts.toml.j2 +22 -0
- qubx-0.7.25/src/qubx/templates/simple/config.yml.j2 +30 -0
- qubx-0.7.25/src/qubx/templates/simple/jlive.sh.j2 +43 -0
- qubx-0.7.25/src/qubx/templates/simple/jpaper.sh.j2 +6 -0
- qubx-0.7.25/src/qubx/templates/simple/strategy.py.j2 +95 -0
- qubx-0.7.25/src/qubx/templates/simple/template.yml +20 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/trackers/__init__.py +2 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/trackers/advanced.py +41 -4
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/trackers/riskctrl.py +494 -4
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/trackers/sizers.py +65 -2
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/charting/lookinglass.py +145 -15
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/charting/mpl_helpers.py +128 -0
- qubx-0.7.25/src/qubx/utils/charting/orderbook.py +314 -0
- qubx-0.7.25/src/qubx/utils/hft/__init__.py +5 -0
- qubx-0.7.25/src/qubx/utils/hft/numba_utils.py +12 -0
- qubx-0.7.25/src/qubx/utils/hft/orderbook.pyi +177 -0
- qubx-0.7.25/src/qubx/utils/hft/orderbook.pyx +416 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/marketdata/ccxt.py +44 -7
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/misc.py +52 -8
- qubx-0.7.25/src/qubx/utils/nonce.py +53 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/orderbook.py +1 -1
- qubx-0.7.25/src/qubx/utils/questdb.py +141 -0
- qubx-0.7.25/src/qubx/utils/rate_limiter.py +222 -0
- qubx-0.7.25/src/qubx/utils/ringbuffer.pxd +17 -0
- qubx-0.7.25/src/qubx/utils/ringbuffer.pyi +197 -0
- qubx-0.7.25/src/qubx/utils/ringbuffer.pyx +253 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/runner/_jupyter_runner.pyt +23 -12
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/runner/accounts.py +30 -1
- qubx-0.7.25/src/qubx/utils/runner/configs.py +318 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/runner/factory.py +46 -14
- qubx-0.7.25/src/qubx/utils/runner/kernel_service.py +195 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/runner/runner.py +327 -39
- qubx-0.7.25/src/qubx/utils/runner/textual/__init__.py +177 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/app.py +412 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/handlers.py +101 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/init_code.py +301 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/kernel.py +269 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/styles.tcss +134 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/widgets/__init__.py +10 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/widgets/command_input.py +105 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/widgets/debug_log.py +97 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/widgets/orders_table.py +242 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/widgets/positions_table.py +330 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/widgets/quotes_table.py +225 -0
- qubx-0.7.25/src/qubx/utils/runner/textual/widgets/repl_output.py +111 -0
- qubx-0.7.25/src/qubx/utils/slack.py +339 -0
- qubx-0.7.25/src/qubx/utils/throttler.py +136 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/time.py +168 -1
- qubx-0.7.25/src/qubx/utils/websocket_manager.py +450 -0
- qubx-0.6.64/src/qubx/backtester/broker.py +0 -84
- qubx-0.6.64/src/qubx/cli/commands.py +0 -279
- qubx-0.6.64/src/qubx/connectors/ccxt/data.py +0 -823
- qubx-0.6.64/src/qubx/connectors/ccxt/factory.py +0 -104
- qubx-0.6.64/src/qubx/connectors/ccxt/reader.py +0 -237
- qubx-0.6.64/src/qubx/core/deque.py +0 -182
- qubx-0.6.64/src/qubx/core/initializer.py +0 -115
- qubx-0.6.64/src/qubx/core/mixins/trading.py +0 -201
- qubx-0.6.64/src/qubx/data/helpers.py +0 -443
- qubx-0.6.64/src/qubx/data/registry.py +0 -124
- qubx-0.6.64/src/qubx/features/__init__.py +0 -14
- qubx-0.6.64/src/qubx/features/core.py +0 -254
- qubx-0.6.64/src/qubx/features/orderbook.py +0 -42
- qubx-0.6.64/src/qubx/features/price.py +0 -20
- qubx-0.6.64/src/qubx/features/trades.py +0 -105
- qubx-0.6.64/src/qubx/features/utils.py +0 -10
- qubx-0.6.64/src/qubx/health/__init__.py +0 -3
- qubx-0.6.64/src/qubx/health/base.py +0 -668
- qubx-0.6.64/src/qubx/notifications/composite.py +0 -71
- qubx-0.6.64/src/qubx/notifications/slack.py +0 -213
- qubx-0.6.64/src/qubx/resources/instruments/symbols-hyperliquid-spot.json +0 -1
- qubx-0.6.64/src/qubx/resources/instruments/symbols-hyperliquid.f-perpetual.json +0 -1
- qubx-0.6.64/src/qubx/ta/indicators.pyi +0 -41
- qubx-0.6.64/src/qubx/ta/indicators.pyx +0 -784
- qubx-0.6.64/src/qubx/utils/questdb.py +0 -79
- qubx-0.6.64/src/qubx/utils/runner/configs.py +0 -137
- {qubx-0.6.64 → qubx-0.7.25}/LICENSE +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/build.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/cli/tui.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/loggers/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/loggers/factory.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/loggers/mongo.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/notifications/throttler.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.64 → qubx-0.7.25}/src/qubx/utils/version.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: Qubx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.25
|
|
4
4
|
Summary: Qubx - Quantitative Trading Framework
|
|
5
|
+
License-File: LICENSE
|
|
5
6
|
Author: Dmitry Marienko
|
|
6
7
|
Author-email: dmitry.marienko@xlydian.com
|
|
7
8
|
Requires-Python: >=3.10,<4.0
|
|
@@ -10,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
15
|
Requires-Dist: aiohttp (>=3.10.11,<3.11.0)
|
|
14
16
|
Requires-Dist: ccxt (>=4.2.68,<5.0.0)
|
|
15
17
|
Requires-Dist: croniter (>=2.0.5,<3.0.0)
|
|
@@ -19,6 +21,7 @@ Requires-Dist: dash-bootstrap-components (>=1.6.0,<2.0.0)
|
|
|
19
21
|
Requires-Dist: gitpython (>=3.1.44,<4.0.0)
|
|
20
22
|
Requires-Dist: importlib-metadata
|
|
21
23
|
Requires-Dist: ipywidgets (>=8.1.5,<9.0.0)
|
|
24
|
+
Requires-Dist: jinja2 (>=3.1.0,<4.0.0)
|
|
22
25
|
Requires-Dist: jupyter (>=1.1.1,<2.0.0)
|
|
23
26
|
Requires-Dist: jupyter-console (>=6.6.3,<7.0.0)
|
|
24
27
|
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
@@ -40,6 +43,7 @@ Requires-Dist: python-binance (>=1.0.19,<2.0.0)
|
|
|
40
43
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
41
44
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
42
45
|
Requires-Dist: qubx-bitfinex-api (>=3.0.7,<4.0.0)
|
|
46
|
+
Requires-Dist: qubx-lighter-api (>=0.1.4,<0.2.0)
|
|
43
47
|
Requires-Dist: questdb (>=2.0.3,<3.0.0)
|
|
44
48
|
Requires-Dist: redis (>=5.2.1,<6.0.0)
|
|
45
49
|
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
|
@@ -49,9 +53,12 @@ Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
|
|
|
49
53
|
Requires-Dist: stackprinter (>=0.2.10,<0.3.0)
|
|
50
54
|
Requires-Dist: statsmodels (>=0.14.2,<0.15.0)
|
|
51
55
|
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
52
|
-
Requires-Dist: textual (>=0.
|
|
56
|
+
Requires-Dist: textual-autocomplete (>=4.0.0,<5.0.0)
|
|
57
|
+
Requires-Dist: textual-serve (>=1.0.0,<2.0.0)
|
|
58
|
+
Requires-Dist: textual[syntax] (>=6.0.0,<7.0.0)
|
|
53
59
|
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
54
60
|
Requires-Dist: tqdm
|
|
61
|
+
Requires-Dist: uvloop (>=0.22.1,<0.23.0)
|
|
55
62
|
Requires-Dist: websockets (==15.0.1)
|
|
56
63
|
Project-URL: Repository, https://github.com/xLydianSoftware/Qubx
|
|
57
64
|
Description-Content-Type: text/markdown
|
|
@@ -69,6 +76,49 @@ Description-Content-Type: text/markdown
|
|
|
69
76
|
|
|
70
77
|
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.
|
|
71
78
|
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
### 1. Install Dependencies
|
|
82
|
+
```bash
|
|
83
|
+
poetry install
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 2. Create a Strategy
|
|
87
|
+
```bash
|
|
88
|
+
# Create a simple strategy template (default)
|
|
89
|
+
poetry run qubx init
|
|
90
|
+
|
|
91
|
+
# Or specify a name and symbols
|
|
92
|
+
poetry run qubx init --name my_strategy --symbols BTCUSDT,ETHUSDT
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3. Run Your Strategy
|
|
96
|
+
```bash
|
|
97
|
+
cd my_strategy
|
|
98
|
+
|
|
99
|
+
# Run in paper trading mode
|
|
100
|
+
poetry run qubx run config.yml --paper
|
|
101
|
+
|
|
102
|
+
# Or run in Jupyter mode for interactive development
|
|
103
|
+
./jpaper.sh
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Available Templates
|
|
107
|
+
```bash
|
|
108
|
+
# List available strategy templates
|
|
109
|
+
poetry run qubx init --list-templates
|
|
110
|
+
|
|
111
|
+
# Create strategy with full project structure and MACD example
|
|
112
|
+
poetry run qubx init --template project --name my_project
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Strategy Development Workflow
|
|
116
|
+
1. **Initialize**: `poetry run qubx init` - Create strategy from template
|
|
117
|
+
2. **Develop**: Edit `strategy.py` to implement your trading logic
|
|
118
|
+
3. **Test**: `poetry run qubx run config.yml --paper` - Run in paper mode
|
|
119
|
+
4. **Debug**: `./jpaper.sh` - Use Jupyter for interactive development
|
|
120
|
+
5. **Deploy**: Configure for live trading when ready
|
|
121
|
+
|
|
72
122
|
## Features
|
|
73
123
|
|
|
74
124
|
- 🚀 High-performance backtesting engine
|
|
@@ -119,11 +169,13 @@ qubx --help # Show all available commands
|
|
|
119
169
|
|
|
120
170
|
Available commands:
|
|
121
171
|
|
|
122
|
-
- `qubx
|
|
123
|
-
- `qubx ls` - List all strategies in a directory
|
|
124
|
-
- `qubx release` - Package a strategy into a zip file
|
|
172
|
+
- `qubx init` - Create a new strategy from template
|
|
125
173
|
- `qubx run` - Start a strategy with given configuration
|
|
126
174
|
- `qubx simulate` - Run strategy simulation
|
|
175
|
+
- `qubx ls` - List all strategies in a directory
|
|
176
|
+
- `qubx release` - Package a strategy into a zip file
|
|
177
|
+
- `qubx deploy` - Deploy a strategy from a zip file
|
|
178
|
+
- `qubx browse` - Browse backtest results using interactive TUI
|
|
127
179
|
|
|
128
180
|
## Development
|
|
129
181
|
|
|
@@ -11,6 +11,49 @@
|
|
|
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
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### 1. Install Dependencies
|
|
17
|
+
```bash
|
|
18
|
+
poetry install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Create a Strategy
|
|
22
|
+
```bash
|
|
23
|
+
# Create a simple strategy template (default)
|
|
24
|
+
poetry run qubx init
|
|
25
|
+
|
|
26
|
+
# Or specify a name and symbols
|
|
27
|
+
poetry run qubx init --name my_strategy --symbols BTCUSDT,ETHUSDT
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 3. Run Your Strategy
|
|
31
|
+
```bash
|
|
32
|
+
cd my_strategy
|
|
33
|
+
|
|
34
|
+
# Run in paper trading mode
|
|
35
|
+
poetry run qubx run config.yml --paper
|
|
36
|
+
|
|
37
|
+
# Or run in Jupyter mode for interactive development
|
|
38
|
+
./jpaper.sh
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Available Templates
|
|
42
|
+
```bash
|
|
43
|
+
# List available strategy templates
|
|
44
|
+
poetry run qubx init --list-templates
|
|
45
|
+
|
|
46
|
+
# Create strategy with full project structure and MACD example
|
|
47
|
+
poetry run qubx init --template project --name my_project
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Strategy Development Workflow
|
|
51
|
+
1. **Initialize**: `poetry run qubx init` - Create strategy from template
|
|
52
|
+
2. **Develop**: Edit `strategy.py` to implement your trading logic
|
|
53
|
+
3. **Test**: `poetry run qubx run config.yml --paper` - Run in paper mode
|
|
54
|
+
4. **Debug**: `./jpaper.sh` - Use Jupyter for interactive development
|
|
55
|
+
5. **Deploy**: Configure for live trading when ready
|
|
56
|
+
|
|
14
57
|
## Features
|
|
15
58
|
|
|
16
59
|
- 🚀 High-performance backtesting engine
|
|
@@ -61,11 +104,13 @@ qubx --help # Show all available commands
|
|
|
61
104
|
|
|
62
105
|
Available commands:
|
|
63
106
|
|
|
64
|
-
- `qubx
|
|
65
|
-
- `qubx ls` - List all strategies in a directory
|
|
66
|
-
- `qubx release` - Package a strategy into a zip file
|
|
107
|
+
- `qubx init` - Create a new strategy from template
|
|
67
108
|
- `qubx run` - Start a strategy with given configuration
|
|
68
109
|
- `qubx simulate` - Run strategy simulation
|
|
110
|
+
- `qubx ls` - List all strategies in a directory
|
|
111
|
+
- `qubx release` - Package a strategy into a zip file
|
|
112
|
+
- `qubx deploy` - Deploy a strategy from a zip file
|
|
113
|
+
- `qubx browse` - Browse backtest results using interactive TUI
|
|
69
114
|
|
|
70
115
|
## Development
|
|
71
116
|
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "Qubx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.25"
|
|
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"
|
|
@@ -74,8 +74,12 @@ orjson = "^3.10.15"
|
|
|
74
74
|
aiohttp = "~3.10.11"
|
|
75
75
|
websockets = "15.0.1"
|
|
76
76
|
qubx-bitfinex-api = "^3.0.7"
|
|
77
|
-
textual = "^0.
|
|
77
|
+
textual-autocomplete = "^4.0.0"
|
|
78
|
+
textual-serve = "^1.0.0"
|
|
78
79
|
rich = "^13.9.4"
|
|
80
|
+
jinja2 = "^3.1.0"
|
|
81
|
+
qubx-lighter-api = "^0.1.4"
|
|
82
|
+
uvloop = "^0.22.1"
|
|
79
83
|
|
|
80
84
|
[tool.ruff.lint]
|
|
81
85
|
extend-select = [ "I",]
|
|
@@ -87,7 +91,11 @@ asyncio_default_fixture_loop_scope = "function"
|
|
|
87
91
|
pythonpath = [ "src",]
|
|
88
92
|
markers = [ "integration: mark test as requiring external services like Redis", "e2e: mark test as requiring external exchange connections and API credentials",]
|
|
89
93
|
addopts = "--disable-warnings"
|
|
90
|
-
filterwarnings = [ "ignore:.*Jupyter is migrating.*:DeprecationWarning",]
|
|
94
|
+
filterwarnings = [ "ignore:.*Jupyter is migrating.*:DeprecationWarning", "ignore:coroutine.*AsyncMockMixin._execute_mock_call.*was never awaited:RuntimeWarning",]
|
|
95
|
+
|
|
96
|
+
[tool.poetry.dependencies.textual]
|
|
97
|
+
extras = [ "syntax",]
|
|
98
|
+
version = "^6.0.0"
|
|
91
99
|
|
|
92
100
|
[tool.ruff.lint.extend-per-file-ignores]
|
|
93
101
|
"*.ipynb" = [ "F405", "F401", "E701", "E402", "F403", "E401", "E702", "I001",]
|
|
@@ -119,6 +127,7 @@ pytest-mock = "^3.12.0"
|
|
|
119
127
|
pytest-lazy-fixture = "^0.6.3"
|
|
120
128
|
pytest-cov = "^4.1.0"
|
|
121
129
|
mongomock = "^4.3.0"
|
|
130
|
+
pytest-textual-snapshot = "^1.1.0"
|
|
122
131
|
|
|
123
132
|
[tool.poetry.group.k8.dependencies]
|
|
124
133
|
prometheus-client = "^0.21.1"
|
|
@@ -186,7 +186,7 @@ if runtime_env() in ["notebook", "shell"]:
|
|
|
186
186
|
return
|
|
187
187
|
|
|
188
188
|
ipy = get_ipython()
|
|
189
|
-
for a in [x for x in re.split(r"[\
|
|
189
|
+
for a in [x for x in re.split(r"[\s,;]", line.strip()) if x]:
|
|
190
190
|
ipy.push({a: self._get_manager().Value(None, None)})
|
|
191
191
|
|
|
192
192
|
# code to run
|
|
@@ -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,12 +42,14 @@ 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)
|
|
44
51
|
|
|
45
|
-
def get_orders(self, instrument: Instrument | None = None) -> dict[str, Order]:
|
|
52
|
+
def get_orders(self, instrument: Instrument | None = None, exchange: str | None = None) -> dict[str, Order]:
|
|
46
53
|
return self._exchange.get_open_orders(instrument)
|
|
47
54
|
|
|
48
55
|
def get_position(self, instrument: Instrument) -> Position:
|
|
@@ -0,0 +1,128 @@
|
|
|
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) -> None:
|
|
55
|
+
"""Submit order asynchronously (same as sync in simulation)."""
|
|
56
|
+
self.send_order(request)
|
|
57
|
+
|
|
58
|
+
def cancel_order(self, order_id: str) -> bool:
|
|
59
|
+
"""Cancel an order synchronously and return success status."""
|
|
60
|
+
try:
|
|
61
|
+
self._send_execution_report(order_update := self._exchange.cancel_order(order_id))
|
|
62
|
+
return order_update is not None
|
|
63
|
+
except OrderNotFound:
|
|
64
|
+
# Order was already cancelled or doesn't exist
|
|
65
|
+
logger.debug(f"Order {order_id} not found")
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
def cancel_order_async(self, order_id: str) -> None:
|
|
69
|
+
"""Cancel an order asynchronously (fire-and-forget)."""
|
|
70
|
+
# For simulation, async is same as sync since it's fast
|
|
71
|
+
self.cancel_order(order_id)
|
|
72
|
+
|
|
73
|
+
def cancel_orders(self, instrument: Instrument) -> None:
|
|
74
|
+
raise NotImplementedError("Not implemented yet")
|
|
75
|
+
|
|
76
|
+
def update_order(self, order_id: str, price: float, amount: float) -> Order:
|
|
77
|
+
"""Update an existing limit order using cancel+recreate strategy.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
order_id: The ID of the order to update
|
|
81
|
+
price: New price for the order
|
|
82
|
+
amount: New amount for the order
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Order: The updated (newly created) order object
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
OrderNotFound: If the order is not found
|
|
89
|
+
BadRequest: If the order is not a limit order
|
|
90
|
+
"""
|
|
91
|
+
# Get the existing order from account
|
|
92
|
+
active_orders = self._account.get_orders()
|
|
93
|
+
existing_order = active_orders.get(order_id)
|
|
94
|
+
if not existing_order:
|
|
95
|
+
raise OrderNotFound(f"Order {order_id} not found")
|
|
96
|
+
|
|
97
|
+
# Validate that it's a limit order
|
|
98
|
+
if existing_order.type != "LIMIT":
|
|
99
|
+
raise BadRequest(
|
|
100
|
+
f"Order {order_id} is not a limit order (type: {existing_order.type}). Only limit orders can be updated."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
self.cancel_order(order_id)
|
|
104
|
+
|
|
105
|
+
request = OrderRequest(
|
|
106
|
+
instrument=existing_order.instrument,
|
|
107
|
+
quantity=abs(amount),
|
|
108
|
+
price=price,
|
|
109
|
+
order_type="LIMIT",
|
|
110
|
+
side=existing_order.side,
|
|
111
|
+
time_in_force=existing_order.time_in_force or "gtc",
|
|
112
|
+
options={},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
updated_order = self.send_order(request)
|
|
116
|
+
|
|
117
|
+
return updated_order
|
|
118
|
+
|
|
119
|
+
def _send_execution_report(self, report: SimulatedExecutionReport | None):
|
|
120
|
+
if report is None:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
self.channel.send((report.instrument, "order", report.order, False))
|
|
124
|
+
if report.exec is not None:
|
|
125
|
+
self.channel.send((report.instrument, "deals", [report.exec], False))
|
|
126
|
+
|
|
127
|
+
def exchange(self) -> str:
|
|
128
|
+
return self._exchange.exchange_id
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
+
from typing import TypeVar
|
|
2
3
|
|
|
3
4
|
import pandas as pd
|
|
4
5
|
|
|
@@ -19,6 +20,17 @@ from qubx.utils.time import infer_series_frequency
|
|
|
19
20
|
from .account import SimulatedAccountProcessor
|
|
20
21
|
from .utils import SimulatedTimeProvider
|
|
21
22
|
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_first_existing(data: dict, keys: list, default: T = None) -> T:
|
|
27
|
+
data_get = data.get # Cache method lookup
|
|
28
|
+
sentinel = object()
|
|
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
|
|
33
|
+
|
|
22
34
|
|
|
23
35
|
class SimulatedDataProvider(IDataProvider):
|
|
24
36
|
time_provider: SimulatedTimeProvider
|
|
@@ -62,16 +74,35 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
62
74
|
def is_simulation(self) -> bool:
|
|
63
75
|
return True
|
|
64
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
|
+
|
|
65
88
|
def subscribe(self, subscription_type: str, instruments: set[Instrument], reset: bool) -> None:
|
|
66
89
|
_new_instr = [i for i in instruments if not self.has_subscription(i, subscription_type)]
|
|
90
|
+
|
|
67
91
|
self._data_source.add_instruments_for_subscription(subscription_type, list(instruments))
|
|
68
92
|
|
|
69
93
|
# - provide historical data and last quote for subscribed instruments
|
|
70
94
|
for i in _new_instr:
|
|
71
|
-
#
|
|
95
|
+
# - check if the instrument was actually subscribed (not filtered out)
|
|
72
96
|
if not self.has_subscription(i, subscription_type):
|
|
73
97
|
continue
|
|
74
|
-
|
|
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
|
|
75
106
|
h_data = self._data_source.peek_historical_data(i, subscription_type)
|
|
76
107
|
if h_data:
|
|
77
108
|
# _s_type = DataType.from_str(subscription_type)[0]
|
|
@@ -91,9 +122,16 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
91
122
|
def unsubscribe(self, subscription_type: str, instruments: set[Instrument] | Instrument | None = None) -> None:
|
|
92
123
|
# logger.debug(f" | unsubscribe: {subscription_type} -> {instruments}")
|
|
93
124
|
if instruments is not None:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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)
|
|
97
135
|
|
|
98
136
|
def has_subscription(self, instrument: Instrument, subscription_type: str) -> bool:
|
|
99
137
|
return self._data_source.has_subscription(instrument, subscription_type)
|
|
@@ -157,15 +195,30 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
157
195
|
if _b_ts_0 <= cut_time_ns and cut_time_ns < _b_ts_1:
|
|
158
196
|
break
|
|
159
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
|
+
|
|
160
208
|
bars.append(
|
|
161
209
|
Bar(
|
|
162
210
|
_b_ts_0,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
r.data.get("volume", 0),
|
|
168
|
-
r.data
|
|
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),
|
|
169
222
|
)
|
|
170
223
|
)
|
|
171
224
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import zipfile
|
|
3
3
|
from collections import defaultdict
|
|
4
|
+
from os.path import expanduser
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
import pandas as pd
|
|
8
9
|
import yaml
|
|
10
|
+
from tqdm.auto import tqdm
|
|
9
11
|
|
|
10
12
|
from qubx.core.metrics import TradingSessionResult
|
|
11
13
|
from qubx.utils.misc import blue, cyan, green, magenta, red, yellow
|
|
@@ -38,7 +40,7 @@ class BacktestsResultsManager:
|
|
|
38
40
|
"""
|
|
39
41
|
|
|
40
42
|
def __init__(self, path: str):
|
|
41
|
-
self.path = path
|
|
43
|
+
self.path = expanduser(path)
|
|
42
44
|
self.reload()
|
|
43
45
|
|
|
44
46
|
def reload(self) -> "BacktestsResultsManager":
|
|
@@ -326,10 +328,11 @@ class BacktestsResultsManager:
|
|
|
326
328
|
if not as_table:
|
|
327
329
|
print(_s)
|
|
328
330
|
|
|
331
|
+
dd_column = "max_dd_pct" if "max_dd_pct" in metrics else "mdd_pct"
|
|
329
332
|
if with_metrics:
|
|
330
333
|
_m_repr = (
|
|
331
334
|
pd.DataFrame.from_dict(metrics, orient="index")
|
|
332
|
-
.T[["gain", "cagr", "sharpe", "qr",
|
|
335
|
+
.T[["gain", "cagr", "sharpe", "qr", dd_column, "mdd_usd", "fees", "execs"]]
|
|
333
336
|
.astype(float)
|
|
334
337
|
)
|
|
335
338
|
_m_repr = _m_repr.round(3).to_string(index=False)
|
|
@@ -344,7 +347,7 @@ class BacktestsResultsManager:
|
|
|
344
347
|
metrics = {
|
|
345
348
|
m: round(v, 3)
|
|
346
349
|
for m, v in metrics.items()
|
|
347
|
-
if m in ["gain", "cagr", "sharpe", "qr",
|
|
350
|
+
if m in ["gain", "cagr", "sharpe", "qr", dd_column, "mdd_usd", "fees", "execs"]
|
|
348
351
|
}
|
|
349
352
|
_t_rep.append(
|
|
350
353
|
{"Index": info.get("idx", ""), "Strategy": name}
|
|
@@ -379,7 +382,7 @@ class BacktestsResultsManager:
|
|
|
379
382
|
_mtrx[_nm] = v.get("performance", {})
|
|
380
383
|
|
|
381
384
|
_m_repr = pd.DataFrame.from_dict(_mtrx, orient="index")[
|
|
382
|
-
["gain", "cagr", "sharpe", "qr", "
|
|
385
|
+
["gain", "cagr", "sharpe", "qr", "mdd_pct", "mdd_usd", "fees", "execs"]
|
|
383
386
|
].astype(float)
|
|
384
387
|
_m_repr = _m_repr.round(3)
|
|
385
388
|
_m_repr = _m_repr.sort_values(by=sort_by, ascending=ascending) if sort_by else _m_repr
|
|
@@ -420,8 +423,10 @@ class BacktestsResultsManager:
|
|
|
420
423
|
Returns:
|
|
421
424
|
plotly.graph_objects.Figure: The plot of the variation.
|
|
422
425
|
"""
|
|
423
|
-
import plotly.express as px
|
|
424
426
|
from itertools import cycle
|
|
427
|
+
|
|
428
|
+
import plotly.express as px
|
|
429
|
+
|
|
425
430
|
from qubx.utils.misc import string_shortener
|
|
426
431
|
|
|
427
432
|
_vars = self.variations.get(variation_idx)
|
|
@@ -507,3 +512,11 @@ class BacktestsResultsManager:
|
|
|
507
512
|
)
|
|
508
513
|
)
|
|
509
514
|
return figure
|
|
515
|
+
|
|
516
|
+
def export_backtests_to_markdown(self, path: str, tags: tuple[str] | None = None):
|
|
517
|
+
"""
|
|
518
|
+
Export backtests to markdown format
|
|
519
|
+
"""
|
|
520
|
+
for n, v in tqdm(self.results.items()):
|
|
521
|
+
r = TradingSessionResult.from_file(v.get("path"))
|
|
522
|
+
r.to_markdown(path, list(tags) if tags else None)
|
|
@@ -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")
|