Qubx 0.6.95__tar.gz → 0.7.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Qubx might be problematic. Click here for more details.
- {qubx-0.6.95 → qubx-0.7.4}/PKG-INFO +2 -2
- {qubx-0.6.95 → qubx-0.7.4}/pyproject.toml +2 -2
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/utils.py +1 -1
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/data.py +32 -29
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/account.py +101 -34
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/broker.py +117 -273
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/client.py +53 -3
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/constants.py +2 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/data.py +50 -11
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/factory.py +34 -3
- qubx-0.7.4/src/qubx/connectors/xlighter/handlers/orderbook.py +323 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/handlers/trades.py +4 -4
- qubx-0.7.4/src/qubx/connectors/xlighter/nonce.py +21 -0
- qubx-0.7.4/src/qubx/connectors/xlighter/rate_limits.py +96 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/websocket.py +31 -1
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/basics.py +23 -1
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/context.py +169 -47
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/exceptions.py +4 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/interfaces.py +61 -8
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/mixins/processing.py +17 -3
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/mixins/trading.py +30 -5
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/series.pyi +15 -1
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/series.pyx +110 -2
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/readers.py +116 -2
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/storages/questdb.py +1 -1
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/emitters/composite.py +17 -1
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/emitters/questdb.py +81 -15
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/formatters/slack.py +6 -4
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/slack.py +35 -74
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/gathering/simplest.py +1 -1
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/notifications/__init__.py +5 -5
- qubx-0.7.4/src/qubx/notifications/composite.py +83 -0
- qubx-0.7.4/src/qubx/notifications/slack.py +190 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/pandaz/ta.py +32 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/ta/indicators.pyi +11 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/ta/indicators.pyx +253 -1
- qubx-0.7.4/src/qubx/utils/hft/__init__.py +5 -0
- qubx-0.7.4/src/qubx/utils/hft/numba_utils.py +12 -0
- qubx-0.7.4/src/qubx/utils/hft/orderbook.pyi +177 -0
- qubx-0.7.4/src/qubx/utils/hft/orderbook.pyx +416 -0
- qubx-0.7.4/src/qubx/utils/nonce.py +53 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/orderbook.py +1 -276
- qubx-0.7.4/src/qubx/utils/rate_limiter.py +225 -0
- qubx-0.7.4/src/qubx/utils/ringbuffer.pxd +17 -0
- qubx-0.7.4/src/qubx/utils/ringbuffer.pyi +197 -0
- qubx-0.7.4/src/qubx/utils/ringbuffer.pyx +253 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/factory.py +11 -14
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/runner.py +3 -3
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/app.py +41 -33
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/handlers.py +16 -5
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/init_code.py +21 -24
- qubx-0.7.4/src/qubx/utils/runner/textual/widgets/orders_table.py +242 -0
- qubx-0.7.4/src/qubx/utils/runner/textual/widgets/positions_table.py +330 -0
- qubx-0.7.4/src/qubx/utils/runner/textual/widgets/quotes_table.py +225 -0
- qubx-0.7.4/src/qubx/utils/runner/textual/widgets/repl_output.py +102 -0
- qubx-0.7.4/src/qubx/utils/slack.py +177 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/time.py +97 -1
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/websocket_manager.py +10 -5
- qubx-0.6.95/src/qubx/connectors/xlighter/handlers/orderbook.py +0 -128
- qubx-0.6.95/src/qubx/notifications/composite.py +0 -71
- qubx-0.6.95/src/qubx/notifications/slack.py +0 -213
- qubx-0.6.95/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -61
- qubx-0.6.95/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -91
- qubx-0.6.95/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -90
- qubx-0.6.95/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -97
- {qubx-0.6.95 → qubx-0.7.4}/LICENSE +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/README.md +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/build.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/account.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/data.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/management.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/runner.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/backtester/transfers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/cli/commands.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/cli/release.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/cli/tui.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/reader.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/extensions.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/handlers/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/handlers/base.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/handlers/quote.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/handlers/stats.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/instruments.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/parsers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/reader.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/connectors/xlighter/utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/account.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/deque.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/detectors/delisting.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/detectors/stale.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/helpers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/initializer.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/metrics.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/series.pxd +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/composite.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/containers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/registry.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/storage.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/storages/csv.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/storages/utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/data/transformers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/emitters/base.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/emitters/csv.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/emitters/indicator.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/features/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/features/core.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/features/orderbook.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/features/price.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/features/trades.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/features/utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/health/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/health/base.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/loggers/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/loggers/csv.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/loggers/factory.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/loggers/mongo.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/notifications/throttler.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restorers/position.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/base.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/project/template.yml +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/questdb.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-0.6.95 → qubx-0.7.4}/src/qubx/utils/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Qubx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Qubx - Quantitative Trading Framework
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Dmitry Marienko
|
|
@@ -24,7 +24,6 @@ Requires-Dist: ipywidgets (>=8.1.5,<9.0.0)
|
|
|
24
24
|
Requires-Dist: jinja2 (>=3.1.0,<4.0.0)
|
|
25
25
|
Requires-Dist: jupyter (>=1.1.1,<2.0.0)
|
|
26
26
|
Requires-Dist: jupyter-console (>=6.6.3,<7.0.0)
|
|
27
|
-
Requires-Dist: lighter-sdk (>=0.1.4,<0.2.0)
|
|
28
27
|
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
29
28
|
Requires-Dist: matplotlib (>=3.8.4,<4.0.0)
|
|
30
29
|
Requires-Dist: msgspec (>=0.18.6,<0.19.0)
|
|
@@ -44,6 +43,7 @@ Requires-Dist: python-binance (>=1.0.19,<2.0.0)
|
|
|
44
43
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
45
44
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
46
45
|
Requires-Dist: qubx-bitfinex-api (>=3.0.7,<4.0.0)
|
|
46
|
+
Requires-Dist: qubx-lighter-api (>=0.1.4,<0.2.0)
|
|
47
47
|
Requires-Dist: questdb (>=2.0.3,<3.0.0)
|
|
48
48
|
Requires-Dist: redis (>=5.2.1,<6.0.0)
|
|
49
49
|
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "Qubx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.4"
|
|
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"
|
|
@@ -78,7 +78,7 @@ 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
|
-
lighter-
|
|
81
|
+
qubx-lighter-api = "^0.1.4"
|
|
82
82
|
|
|
83
83
|
[tool.ruff.lint]
|
|
84
84
|
extend-select = [ "I",]
|
|
@@ -155,7 +155,7 @@ class SimulatedLogFormatter:
|
|
|
155
155
|
now = self.time_provider.time().astype("datetime64[us]").item().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
156
156
|
|
|
157
157
|
# prefix = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> [ <level>%s</level> ] " % record["level"].icon
|
|
158
|
-
prefix = f"<lc>{now}</lc> [<level>{record['level'].icon}</level>] "
|
|
158
|
+
prefix = f"<lc>{now}</lc> [<level>{record['level'].icon}</level>] <cyan>({{module}})</cyan> "
|
|
159
159
|
|
|
160
160
|
if record["exception"] is not None:
|
|
161
161
|
record["extra"]["stack"] = stackprinter.format(record["exception"], style="darkbg3")
|
|
@@ -43,18 +43,15 @@ class CcxtDataProvider(IDataProvider):
|
|
|
43
43
|
):
|
|
44
44
|
# Store the exchange manager (always ExchangeManager now)
|
|
45
45
|
self._exchange_manager = exchange_manager
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
self.time_provider = time_provider
|
|
48
48
|
self.channel = channel
|
|
49
49
|
self.max_ws_retries = max_ws_retries
|
|
50
50
|
self._warmup_timeout = warmup_timeout
|
|
51
51
|
self._health_monitor = health_monitor or DummyHealthMonitor()
|
|
52
52
|
|
|
53
|
-
self._data_arrival_listeners: List[IDataArrivalListener] = [
|
|
54
|
-
|
|
55
|
-
self._exchange_manager
|
|
56
|
-
]
|
|
57
|
-
|
|
53
|
+
self._data_arrival_listeners: List[IDataArrivalListener] = [self._health_monitor, self._exchange_manager]
|
|
54
|
+
|
|
58
55
|
logger.debug(f"Registered {len(self._data_arrival_listeners)} data arrival listeners")
|
|
59
56
|
|
|
60
57
|
# Core components - access exchange directly via exchange_manager.exchange
|
|
@@ -106,10 +103,10 @@ class CcxtDataProvider(IDataProvider):
|
|
|
106
103
|
def _loop(self) -> AsyncThreadLoop:
|
|
107
104
|
"""Get current AsyncThreadLoop for the exchange."""
|
|
108
105
|
return AsyncThreadLoop(self._exchange_manager.exchange.asyncio_loop)
|
|
109
|
-
|
|
106
|
+
|
|
110
107
|
def notify_data_arrival(self, event_type: str, event_time: dt_64) -> None:
|
|
111
108
|
"""Notify all registered listeners about data arrival.
|
|
112
|
-
|
|
109
|
+
|
|
113
110
|
Args:
|
|
114
111
|
event_type: Type of data event (e.g., "ohlcv:BTC/USDT:1m")
|
|
115
112
|
event_time: Timestamp of the data event
|
|
@@ -120,10 +117,6 @@ class CcxtDataProvider(IDataProvider):
|
|
|
120
117
|
except Exception as e:
|
|
121
118
|
logger.error(f"Error notifying data arrival listener {type(listener).__name__}: {e}")
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
120
|
@property
|
|
128
121
|
def is_simulation(self) -> bool:
|
|
129
122
|
return False
|
|
@@ -219,7 +212,7 @@ class CcxtDataProvider(IDataProvider):
|
|
|
219
212
|
self._warmup_service.execute_warmup(warmups)
|
|
220
213
|
|
|
221
214
|
def get_quote(self, instrument: Instrument) -> Quote | None:
|
|
222
|
-
return self._last_quotes
|
|
215
|
+
return self._last_quotes.get(instrument, None)
|
|
223
216
|
|
|
224
217
|
def get_ohlc(self, instrument: Instrument, timeframe: str, nbarsback: int) -> List[Bar]:
|
|
225
218
|
"""Get historical OHLC data (delegated to OhlcDataHandler)."""
|
|
@@ -259,9 +252,9 @@ class CcxtDataProvider(IDataProvider):
|
|
|
259
252
|
except Exception as e:
|
|
260
253
|
logger.error(f"Error stopping subscription {subscription_type}: {e}")
|
|
261
254
|
|
|
262
|
-
# Stop ExchangeManager monitoring
|
|
255
|
+
# Stop ExchangeManager monitoring
|
|
263
256
|
self._exchange_manager.stop_monitoring()
|
|
264
|
-
|
|
257
|
+
|
|
265
258
|
# Close exchange connection
|
|
266
259
|
if hasattr(self._exchange_manager.exchange, "close"):
|
|
267
260
|
future = self._loop.submit(self._exchange_manager.exchange.close()) # type: ignore
|
|
@@ -277,47 +270,57 @@ class CcxtDataProvider(IDataProvider):
|
|
|
277
270
|
|
|
278
271
|
def _handle_exchange_recreation(self) -> None:
|
|
279
272
|
"""Handle exchange recreation by resubscribing to all active subscriptions."""
|
|
280
|
-
logger.info(
|
|
281
|
-
|
|
273
|
+
logger.info(
|
|
274
|
+
f"<yellow>{self._exchange_id}</yellow> Handling exchange recreation - resubscribing to active subscriptions"
|
|
275
|
+
)
|
|
276
|
+
|
|
282
277
|
# Get snapshot of current subscriptions before cleanup
|
|
283
278
|
active_subscriptions = self._subscription_manager.get_subscriptions()
|
|
284
|
-
|
|
279
|
+
|
|
285
280
|
resubscription_data = []
|
|
286
281
|
for subscription_type in active_subscriptions:
|
|
287
282
|
instruments = self._subscription_manager.get_subscribed_instruments(subscription_type)
|
|
288
283
|
if instruments:
|
|
289
284
|
resubscription_data.append((subscription_type, instruments))
|
|
290
|
-
|
|
291
|
-
logger.info(
|
|
292
|
-
|
|
285
|
+
|
|
286
|
+
logger.info(
|
|
287
|
+
f"<yellow>{self._exchange_id}</yellow> Found {len(resubscription_data)} active subscriptions to recreate"
|
|
288
|
+
)
|
|
289
|
+
|
|
293
290
|
# Track success/failure counts for reporting
|
|
294
291
|
successful_resubscriptions = 0
|
|
295
292
|
failed_resubscriptions = 0
|
|
296
|
-
|
|
293
|
+
|
|
297
294
|
# Clean resubscription: unsubscribe then subscribe for each subscription type
|
|
298
295
|
for subscription_type, instruments in resubscription_data:
|
|
299
296
|
try:
|
|
300
|
-
logger.info(
|
|
301
|
-
|
|
297
|
+
logger.info(
|
|
298
|
+
f"<yellow>{self._exchange_id}</yellow> Resubscribing to {subscription_type} with {len(instruments)} instruments"
|
|
299
|
+
)
|
|
300
|
+
|
|
302
301
|
self.unsubscribe(subscription_type, instruments)
|
|
303
302
|
|
|
304
303
|
# Resubscribe with reset=True to ensure clean state
|
|
305
304
|
self.subscribe(subscription_type, instruments, reset=True)
|
|
306
|
-
|
|
305
|
+
|
|
307
306
|
successful_resubscriptions += 1
|
|
308
307
|
logger.debug(f"<yellow>{self._exchange_id}</yellow> Successfully resubscribed to {subscription_type}")
|
|
309
|
-
|
|
308
|
+
|
|
310
309
|
except Exception as e:
|
|
311
310
|
failed_resubscriptions += 1
|
|
312
311
|
logger.error(f"<yellow>{self._exchange_id}</yellow> Failed to resubscribe to {subscription_type}: {e}")
|
|
313
312
|
# Continue with other subscriptions even if one fails
|
|
314
|
-
|
|
313
|
+
|
|
315
314
|
# Report final status
|
|
316
315
|
total_subscriptions = len(resubscription_data)
|
|
317
316
|
if failed_resubscriptions == 0:
|
|
318
|
-
logger.info(
|
|
317
|
+
logger.info(
|
|
318
|
+
f"<yellow>{self._exchange_id}</yellow> Exchange recreation resubscription completed successfully ({total_subscriptions}/{total_subscriptions})"
|
|
319
|
+
)
|
|
319
320
|
else:
|
|
320
|
-
logger.warning(
|
|
321
|
+
logger.warning(
|
|
322
|
+
f"<yellow>{self._exchange_id}</yellow> Exchange recreation resubscription completed with errors ({successful_resubscriptions}/{total_subscriptions} successful)"
|
|
323
|
+
)
|
|
321
324
|
|
|
322
325
|
@property
|
|
323
326
|
def subscribed_instruments(self) -> Set[Instrument]:
|
|
@@ -12,16 +12,16 @@ through channel for strategy notification but do not update positions.
|
|
|
12
12
|
|
|
13
13
|
import asyncio
|
|
14
14
|
import time
|
|
15
|
-
from typing import Optional
|
|
15
|
+
from typing import Awaitable, Callable, Optional
|
|
16
16
|
|
|
17
17
|
import numpy as np
|
|
18
|
+
import pandas as pd
|
|
18
19
|
|
|
19
20
|
from qubx import logger
|
|
20
21
|
from qubx.core.account import BasicAccountProcessor
|
|
21
22
|
from qubx.core.basics import (
|
|
22
23
|
ZERO_COSTS,
|
|
23
24
|
CtrlChannel,
|
|
24
|
-
DataType,
|
|
25
25
|
Deal,
|
|
26
26
|
Instrument,
|
|
27
27
|
ITimeProvider,
|
|
@@ -29,6 +29,7 @@ from qubx.core.basics import (
|
|
|
29
29
|
TransactionCostsCalculator,
|
|
30
30
|
)
|
|
31
31
|
from qubx.core.interfaces import ISubscriptionManager
|
|
32
|
+
from qubx.core.utils import recognize_timeframe
|
|
32
33
|
from qubx.utils.misc import AsyncThreadLoop
|
|
33
34
|
|
|
34
35
|
from .client import LighterClient
|
|
@@ -121,8 +122,9 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
121
122
|
self._processed_tx_hashes: set[str] = set() # Track processed transaction hashes
|
|
122
123
|
|
|
123
124
|
self._account_stats_initialized = False
|
|
125
|
+
self._account_positions_initialized = False
|
|
124
126
|
|
|
125
|
-
|
|
127
|
+
self.__info(f"Initialized LighterAccountProcessor for account {account_id}")
|
|
126
128
|
|
|
127
129
|
def set_subscription_manager(self, manager: ISubscriptionManager) -> None:
|
|
128
130
|
"""Set the subscription manager (required by interface)"""
|
|
@@ -131,37 +133,37 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
131
133
|
def get_total_capital(self, exchange: str | None = None) -> float:
|
|
132
134
|
if not self._account_stats_initialized:
|
|
133
135
|
self._async_loop.submit(self._start_subscriptions())
|
|
134
|
-
self.
|
|
136
|
+
self._wait_for_account_initialized()
|
|
135
137
|
return super().get_total_capital(exchange)
|
|
136
138
|
|
|
137
139
|
def start(self):
|
|
138
140
|
"""Start WebSocket subscriptions for account data"""
|
|
139
141
|
if self._is_running:
|
|
140
|
-
|
|
142
|
+
self.__debug("Account processor is already running")
|
|
141
143
|
return
|
|
142
144
|
|
|
143
145
|
if not self.channel or not self.channel.control.is_set():
|
|
144
|
-
|
|
146
|
+
self.__warning("Channel not set or control not active, cannot start")
|
|
145
147
|
return
|
|
146
148
|
|
|
147
149
|
self._is_running = True
|
|
148
150
|
|
|
149
151
|
# Start subscription tasks using AsyncThreadLoop
|
|
150
|
-
|
|
152
|
+
self.__info("Starting Lighter account subscriptions")
|
|
151
153
|
|
|
152
154
|
if not self._account_stats_initialized:
|
|
153
155
|
# Submit connection and subscription tasks to the event loop
|
|
154
156
|
self._async_loop.submit(self._start_subscriptions())
|
|
155
|
-
self.
|
|
157
|
+
self._wait_for_account_initialized()
|
|
156
158
|
|
|
157
|
-
|
|
159
|
+
self.__info("Lighter account subscriptions started")
|
|
158
160
|
|
|
159
161
|
def stop(self):
|
|
160
162
|
"""Stop all WebSocket subscriptions"""
|
|
161
163
|
if not self._is_running:
|
|
162
164
|
return
|
|
163
165
|
|
|
164
|
-
|
|
166
|
+
self.__info("Stopping Lighter account subscriptions")
|
|
165
167
|
|
|
166
168
|
# Cancel all subscription tasks
|
|
167
169
|
for task in self._subscription_tasks:
|
|
@@ -170,7 +172,7 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
170
172
|
|
|
171
173
|
self._subscription_tasks.clear()
|
|
172
174
|
self._is_running = False
|
|
173
|
-
|
|
175
|
+
self.__info("Lighter account subscriptions stopped")
|
|
174
176
|
|
|
175
177
|
def process_deals(self, instrument: Instrument, deals: list[Deal], is_snapshot: bool = False) -> None:
|
|
176
178
|
"""
|
|
@@ -200,12 +202,12 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
200
202
|
# Add fee to position's commission tracking
|
|
201
203
|
position.commissions += deal.fee_amount
|
|
202
204
|
|
|
203
|
-
|
|
205
|
+
self.__debug(
|
|
204
206
|
f"Tracked fee for {instrument.symbol}: {deal.fee_amount:.6f} {deal.fee_currency} "
|
|
205
207
|
f"(total commissions: {position.commissions:.6f})"
|
|
206
208
|
)
|
|
207
209
|
|
|
208
|
-
|
|
210
|
+
self.__debug(
|
|
209
211
|
f"Processed {len(deals)} deal(s) for {instrument.symbol} - fees tracked, positions synced from account_all"
|
|
210
212
|
)
|
|
211
213
|
|
|
@@ -227,7 +229,7 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
227
229
|
# Get the existing order stored under client_id
|
|
228
230
|
existing_order = self._active_orders[order.client_id]
|
|
229
231
|
|
|
230
|
-
|
|
232
|
+
self.__debug(f"Migrating order: client_id={order.client_id} → server_id={order.id}")
|
|
231
233
|
|
|
232
234
|
# Remove from old location
|
|
233
235
|
self._active_orders.pop(order.client_id)
|
|
@@ -245,13 +247,13 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
245
247
|
# The base class will now find the existing order under order.id and merge in place
|
|
246
248
|
super().process_order(order, update_locked_value)
|
|
247
249
|
|
|
248
|
-
def
|
|
250
|
+
def _wait_for_account_initialized(self):
|
|
249
251
|
max_wait_time = 20.0 # seconds
|
|
250
252
|
elapsed = 0.0
|
|
251
253
|
interval = 0.1
|
|
252
|
-
while not self._account_stats_initialized:
|
|
254
|
+
while not self._account_stats_initialized or not self._account_positions_initialized:
|
|
253
255
|
if elapsed >= max_wait_time:
|
|
254
|
-
raise TimeoutError(f"Account
|
|
256
|
+
raise TimeoutError(f"Account was not initialized within {max_wait_time} seconds")
|
|
255
257
|
time.sleep(interval)
|
|
256
258
|
elapsed += interval
|
|
257
259
|
|
|
@@ -260,26 +262,27 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
260
262
|
try:
|
|
261
263
|
# Ensure WebSocket is connected
|
|
262
264
|
if not self.ws_manager.is_connected:
|
|
263
|
-
|
|
265
|
+
self.__info("Connecting to Lighter WebSocket...")
|
|
264
266
|
await self.ws_manager.connect()
|
|
265
|
-
|
|
267
|
+
self.__info("Connected to Lighter WebSocket")
|
|
266
268
|
|
|
267
269
|
# Start all subscriptions
|
|
268
270
|
await self._subscribe_account_all()
|
|
269
271
|
await self._subscribe_account_all_orders()
|
|
270
272
|
await self._subscribe_user_stats()
|
|
273
|
+
await self._poller(name="sync_orders", coroutine=self._sync_orders, interval="1min")
|
|
271
274
|
|
|
272
275
|
except Exception as e:
|
|
273
|
-
|
|
276
|
+
self.__error(f"Failed to start subscriptions: {e}")
|
|
274
277
|
self._is_running = False
|
|
275
278
|
raise
|
|
276
279
|
|
|
277
280
|
async def _subscribe_account_all(self):
|
|
278
281
|
try:
|
|
279
282
|
await self.ws_manager.subscribe_account_all(self._lighter_account_index, self._handle_account_all_message)
|
|
280
|
-
|
|
283
|
+
self.__info(f"Subscribed to account_all for account {self._lighter_account_index}")
|
|
281
284
|
except Exception as e:
|
|
282
|
-
|
|
285
|
+
self.__error(f"Failed to subscribe to account_all for account {self._lighter_account_index}: {e}")
|
|
283
286
|
raise
|
|
284
287
|
|
|
285
288
|
async def _subscribe_account_all_orders(self):
|
|
@@ -287,19 +290,64 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
287
290
|
await self.ws_manager.subscribe_account_all_orders(
|
|
288
291
|
self._lighter_account_index, self._handle_account_all_orders_message
|
|
289
292
|
)
|
|
290
|
-
|
|
293
|
+
self.__info(f"Subscribed to account_all_orders for account {self._lighter_account_index}")
|
|
291
294
|
except Exception as e:
|
|
292
|
-
|
|
295
|
+
self.__error(f"Failed to subscribe to account_all_orders for account {self._lighter_account_index}: {e}")
|
|
293
296
|
raise
|
|
294
297
|
|
|
295
298
|
async def _subscribe_user_stats(self):
|
|
296
299
|
try:
|
|
297
300
|
await self.ws_manager.subscribe_user_stats(self._lighter_account_index, self._handle_user_stats_message)
|
|
298
|
-
|
|
301
|
+
self.__info(f"Subscribed to user_stats for account {self._lighter_account_index}")
|
|
299
302
|
except Exception as e:
|
|
300
|
-
|
|
303
|
+
self.__error(f"Failed to subscribe to user_stats for account {self._lighter_account_index}: {e}")
|
|
301
304
|
raise
|
|
302
305
|
|
|
306
|
+
async def _sync_orders(self):
|
|
307
|
+
now = self.time_provider.time()
|
|
308
|
+
orders = self.get_orders()
|
|
309
|
+
remove_orders = []
|
|
310
|
+
for order_id, order in orders.items():
|
|
311
|
+
if order.status == "NEW" and order.time < now - recognize_timeframe("1min"):
|
|
312
|
+
remove_orders.append(order_id)
|
|
313
|
+
for order_id in remove_orders:
|
|
314
|
+
self.remove_order(order_id)
|
|
315
|
+
|
|
316
|
+
async def _poller(
|
|
317
|
+
self,
|
|
318
|
+
name: str,
|
|
319
|
+
coroutine: Callable[[], Awaitable],
|
|
320
|
+
interval: str,
|
|
321
|
+
backoff: str | None = None,
|
|
322
|
+
):
|
|
323
|
+
sleep_time = pd.Timedelta(interval).total_seconds()
|
|
324
|
+
retries = 0
|
|
325
|
+
|
|
326
|
+
if backoff is not None:
|
|
327
|
+
sleep_time = pd.Timedelta(backoff).total_seconds()
|
|
328
|
+
await asyncio.sleep(sleep_time)
|
|
329
|
+
|
|
330
|
+
while self.channel.control.is_set():
|
|
331
|
+
try:
|
|
332
|
+
await coroutine()
|
|
333
|
+
retries = 0 # Reset retry counter on success
|
|
334
|
+
except Exception as e:
|
|
335
|
+
if not self.channel.control.is_set():
|
|
336
|
+
# If the channel is closed, then ignore all exceptions and exit
|
|
337
|
+
break
|
|
338
|
+
logger.error(f"Unexpected error during account polling: {e}")
|
|
339
|
+
logger.exception(e)
|
|
340
|
+
retries += 1
|
|
341
|
+
if retries >= self.max_retries:
|
|
342
|
+
logger.error(f"Max retries ({self.max_retries}) reached. Stopping poller.")
|
|
343
|
+
break
|
|
344
|
+
finally:
|
|
345
|
+
if not self.channel.control.is_set():
|
|
346
|
+
break
|
|
347
|
+
await asyncio.sleep(min(sleep_time * (2 ** (retries)), 60)) # Exponential backoff capped at 60s
|
|
348
|
+
|
|
349
|
+
logger.debug(f"{name} polling task has been stopped")
|
|
350
|
+
|
|
303
351
|
async def _handle_account_all_message(self, message: dict):
|
|
304
352
|
"""
|
|
305
353
|
Handle account_all WebSocket messages (primary channel).
|
|
@@ -335,11 +383,15 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
335
383
|
if position.last_update_price == 0 or np.isnan(position.last_update_price):
|
|
336
384
|
position.last_update_price = pos_state.avg_entry_price
|
|
337
385
|
|
|
338
|
-
|
|
386
|
+
self.__debug(
|
|
339
387
|
f"Synced position: {instrument.symbol} = {pos_state.quantity:+.4f} "
|
|
340
388
|
f"@ avg_price={pos_state.avg_entry_price:.4f}"
|
|
341
389
|
)
|
|
342
390
|
|
|
391
|
+
if not self._account_positions_initialized:
|
|
392
|
+
self._account_positions_initialized = True
|
|
393
|
+
self.__info("Account positions initialized")
|
|
394
|
+
|
|
343
395
|
# Send deals through channel for strategy notification
|
|
344
396
|
# Note: process_deals is overridden to track fees without updating positions
|
|
345
397
|
for instrument, deal in deals:
|
|
@@ -347,7 +399,7 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
347
399
|
# False means not a snapshot
|
|
348
400
|
self.channel.send((instrument, "deals", [deal], False))
|
|
349
401
|
|
|
350
|
-
|
|
402
|
+
self.__debug(
|
|
351
403
|
f"Sent deal: {instrument.symbol} {deal.amount:+.4f} @ {deal.price:.4f} "
|
|
352
404
|
f"fee={deal.fee_amount:.6f} (id={deal.id})"
|
|
353
405
|
)
|
|
@@ -363,7 +415,7 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
363
415
|
# )
|
|
364
416
|
|
|
365
417
|
except Exception as e:
|
|
366
|
-
|
|
418
|
+
self.__error(f"Error handling account_all message: {e}")
|
|
367
419
|
logger.exception(e)
|
|
368
420
|
|
|
369
421
|
async def _handle_account_all_orders_message(self, message: dict):
|
|
@@ -384,13 +436,13 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
384
436
|
# False means not a snapshot
|
|
385
437
|
self.channel.send((instrument, "order", order, False))
|
|
386
438
|
|
|
387
|
-
|
|
439
|
+
self.__debug(
|
|
388
440
|
f"Sent order: {instrument.symbol} {order.side} {order.quantity:+.4f} @ {order.price:.4f} "
|
|
389
441
|
f"[{order.status}] (order_id={order.id})"
|
|
390
442
|
)
|
|
391
443
|
|
|
392
444
|
except Exception as e:
|
|
393
|
-
|
|
445
|
+
self.__error(f"Error handling account_all_orders message: {e}")
|
|
394
446
|
logger.exception(e)
|
|
395
447
|
|
|
396
448
|
async def _handle_user_stats_message(self, message: dict):
|
|
@@ -407,15 +459,30 @@ class LighterAccountProcessor(BasicAccountProcessor):
|
|
|
407
459
|
for currency, balance in balances.items():
|
|
408
460
|
self.update_balance(currency, balance.total, balance.locked)
|
|
409
461
|
|
|
410
|
-
|
|
462
|
+
self.__debug(
|
|
411
463
|
f"Updated balance - {currency}: total={balance.total:.2f}, "
|
|
412
464
|
f"free={balance.free:.2f}, locked={balance.locked:.2f}"
|
|
413
465
|
)
|
|
414
466
|
|
|
415
467
|
if not self._account_stats_initialized:
|
|
416
468
|
self._account_stats_initialized = True
|
|
417
|
-
|
|
469
|
+
self.__info("Account stats initialized")
|
|
418
470
|
|
|
419
471
|
except Exception as e:
|
|
420
|
-
|
|
472
|
+
self.__error(f"Error handling user_stats message: {e}")
|
|
421
473
|
logger.exception(e)
|
|
474
|
+
|
|
475
|
+
def __info(self, msg: str):
|
|
476
|
+
logger.info(self.__format(msg))
|
|
477
|
+
|
|
478
|
+
def __debug(self, msg: str):
|
|
479
|
+
logger.debug(self.__format(msg))
|
|
480
|
+
|
|
481
|
+
def __warning(self, msg: str):
|
|
482
|
+
logger.warning(self.__format(msg))
|
|
483
|
+
|
|
484
|
+
def __error(self, msg: str):
|
|
485
|
+
logger.error(self.__format(msg))
|
|
486
|
+
|
|
487
|
+
def __format(self, msg: str):
|
|
488
|
+
return f"<green>[Lighter]</green> {msg}"
|