Qubx 1.2.3.dev1__tar.gz → 1.2.3.dev3__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-1.2.3.dev1 → qubx-1.2.3.dev3}/PKG-INFO +1 -1
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/_version.py +2 -2
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchange_manager.py +38 -3
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/rate_limits.py +18 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/storages/ccxt.py +212 -10
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/transformers.py +1 -1
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/pandaz/ta.py +1 -1
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/trackers/riskctrl.py +4 -2
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/trackers/sizers.py +18 -2
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/runner.py +26 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/.gitignore +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/LICENSE +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/README.md +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/pyproject.toml +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/scripts/build.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/_nb_magic.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/account.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/broker.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/data.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/iteratedstream.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/management.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/ome.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/optimization.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/runner.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/simulator.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/transfers.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/backtester/utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/commands.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/deploy.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/misc.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/release.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/resolver.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/s3.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/theme.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/tui.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/cli/user_config.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/config.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/registry.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/control/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/control/builtin.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/control/decorator.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/control/executor.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/control/interfaces.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/control/server.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/control/types.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/account.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/basics.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/context.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/detectors/delisting.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/detectors/stale.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/errors.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/exceptions.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/helpers.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/initializer.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/interfaces.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/loggers.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/lookups.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/metrics.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/mixins/market.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/series.pxd +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/series.pyi +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/series.pyx +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/utils.pyi +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/core/utils.pyx +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/cache.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/containers.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/guards.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/registry.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/storage.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/storages/csv.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/storages/handy.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/storages/multi.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/storages/questdb.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/storages/stub.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/data/storages/utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/emitters/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/emitters/base.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/emitters/composite.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/emitters/csv.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/emitters/indicator.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/emitters/questdb.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/composite.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/exporters/slack.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/gathering/simplest.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/health/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/health/base.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/health/dummy.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/loggers/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/loggers/csv.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/loggers/factory.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/loggers/mongo.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/loggers/postgres.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/notifications/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/notifications/composite.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/notifications/slack.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/notifications/throttler.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/pandaz/stats.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/pandaz/utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/plugins/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/plugins/loader.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/rate_limiting/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/rate_limiting/backend.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/rate_limiting/config.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/rate_limiting/engine.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/rate_limiting/ip_resolver.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/rate_limiting/manager.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/rate_limiting/pools.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/rate_limiting/redis_backend.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/_build.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restarts/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restorers/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restorers/balance.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restorers/factory.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restorers/position.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restorers/signal.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restorers/state.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/restorers/utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/state/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/state/dummy.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/state/redis.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/ta/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/base.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/project/template.yml +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.gitignore.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.python-version +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.vscode/launch.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.vscode/settings.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/README.md.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/configs/.gitkeep +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/justfile.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/research/.gitkeep +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/template.yml +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/trackers/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/trackers/advanced.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/trackers/composite.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/collections.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/misc.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/nonce.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/ntp.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/orderbook.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/questdb.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/rate_limiter.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/results.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/app.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/handlers.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/init_code.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/s3.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/slack.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/throttler.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/time.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/utils/websocket_manager.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
- {qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.2.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 2, 3, '
|
|
21
|
+
__version__ = version = '1.2.3.dev3'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 2, 3, 'dev3')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -271,14 +271,32 @@ class ExchangeManager:
|
|
|
271
271
|
logger.info(f"Rate limiter attached to {self._exchange_name}")
|
|
272
272
|
|
|
273
273
|
def _apply_rate_limiter_to_exchange(self, exchange: cxp.Exchange) -> None:
|
|
274
|
-
"""
|
|
274
|
+
"""Install our throttle and response-header-sync hooks on *exchange*.
|
|
275
|
+
|
|
276
|
+
Two hooks are installed per exchange:
|
|
277
|
+
|
|
278
|
+
1. **Throttle (pre-request)** — replaces CCXT's built-in throttle so every
|
|
279
|
+
REST call blocks on our token bucket first. ``acquire`` forwards the
|
|
280
|
+
CCXT-provided cost to the limiter as ``weight_override``.
|
|
281
|
+
|
|
282
|
+
2. **Response hook (post-request)** — wraps CCXT's ``on_rest_response`` to
|
|
283
|
+
invoke the exchange-specific header parser (OKX ``x-ratelimit-*``,
|
|
284
|
+
Binance ``X-MBX-USED-WEIGHT-1M``, etc.). The parser calls
|
|
285
|
+
``rate_limiter.sync_from_exchange`` so our modeled budget tracks the
|
|
286
|
+
exchange's reported usage. Unlike ``CcxtStorage``'s best-effort sync,
|
|
287
|
+
this path runs atomically inside CCXT's fetch pipeline — no
|
|
288
|
+
concurrent-fetch race on ``last_response_headers``.
|
|
289
|
+
|
|
290
|
+
Both hooks are reapplied automatically after exchange recreation via
|
|
291
|
+
``register_recreation_callback`` in :meth:`attach_rate_limiter`.
|
|
292
|
+
"""
|
|
275
293
|
rate_limiter = getattr(self, "_rate_limiter", None)
|
|
276
294
|
if rate_limiter is None:
|
|
277
295
|
return
|
|
278
296
|
|
|
279
|
-
#
|
|
280
|
-
# We override the throttle method to use our rate limiter instead of CCXT's built-in
|
|
297
|
+
# 1) Throttle override — pre-request acquisition.
|
|
281
298
|
exchange.enableRateLimit = True
|
|
299
|
+
|
|
282
300
|
async def _rate_limit_throttle(cost=None):
|
|
283
301
|
if cost is None:
|
|
284
302
|
cost = 1.0
|
|
@@ -286,6 +304,23 @@ class ExchangeManager:
|
|
|
286
304
|
|
|
287
305
|
exchange.throttle = _rate_limit_throttle
|
|
288
306
|
|
|
307
|
+
# 2) Response hook — post-response state sync.
|
|
308
|
+
from qubx.connectors.ccxt.rate_limits import get_header_parser
|
|
309
|
+
|
|
310
|
+
parser = get_header_parser(getattr(exchange, "id", ""))
|
|
311
|
+
if parser is not None:
|
|
312
|
+
_orig_on_rest = exchange.on_rest_response
|
|
313
|
+
|
|
314
|
+
def _header_sync_hook(code, reason, url, method, headers, body, req_headers, req_body):
|
|
315
|
+
try:
|
|
316
|
+
if headers:
|
|
317
|
+
parser(headers, rate_limiter)
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.debug(f"[{self._exchange_name}] header sync failed (non-fatal): {e}")
|
|
320
|
+
return _orig_on_rest(code, reason, url, method, headers, body, req_headers, req_body)
|
|
321
|
+
|
|
322
|
+
exchange.on_rest_response = _header_sync_hook
|
|
323
|
+
|
|
289
324
|
@property
|
|
290
325
|
def rate_limiter(self):
|
|
291
326
|
"""Access the attached rate limiter (if any)."""
|
|
@@ -277,3 +277,21 @@ def get_header_parser(exchange_name: str):
|
|
|
277
277
|
"""Get the response header parser for an exchange."""
|
|
278
278
|
name = exchange_name.lower().split(".")[0]
|
|
279
279
|
return HEADER_PARSERS.get(name)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# NOTE: As of #264 these parsers are invoked from two fetch paths:
|
|
283
|
+
#
|
|
284
|
+
# * ``qubx.data.storages.ccxt._sync_rate_limiter_from_response_headers`` after
|
|
285
|
+
# each successful OHLCV page fetch. Best-effort — ``last_response_headers`` is
|
|
286
|
+
# an exchange-wide attribute so under concurrent fetches the read may be from a
|
|
287
|
+
# neighboring request. Since all concurrent requests drain the same IP-scoped
|
|
288
|
+
# budget, an approximate reading is still strictly better than no sync.
|
|
289
|
+
#
|
|
290
|
+
# * ``qubx.connectors.ccxt.exchange_manager.ExchangeManager._apply_rate_limiter_to_exchange``
|
|
291
|
+
# via CCXT's ``on_rest_response`` hook. This one is atomic per-request (the
|
|
292
|
+
# hook runs inside CCXT's fetch pipeline with the actual response headers), so
|
|
293
|
+
# the live path gets exact sync rather than best-effort.
|
|
294
|
+
#
|
|
295
|
+
# Both code paths call the parser, which in turn calls
|
|
296
|
+
# ``rate_limiter.sync_from_exchange(...)`` to pin the modeled budget to the
|
|
297
|
+
# server-reported remaining.
|
|
@@ -35,9 +35,11 @@ Supported data types
|
|
|
35
35
|
from __future__ import annotations
|
|
36
36
|
|
|
37
37
|
import asyncio
|
|
38
|
-
|
|
38
|
+
import random
|
|
39
|
+
from collections.abc import Awaitable, Callable, Iterator
|
|
39
40
|
from typing import Any, cast
|
|
40
41
|
|
|
42
|
+
import ccxt
|
|
41
43
|
import numpy as np
|
|
42
44
|
import pandas as pd
|
|
43
45
|
import pyarrow as pa
|
|
@@ -272,6 +274,149 @@ class CcxtReader(IReader):
|
|
|
272
274
|
pass
|
|
273
275
|
|
|
274
276
|
|
|
277
|
+
_T = Any # explicit alias keeps the retry helper signature readable
|
|
278
|
+
|
|
279
|
+
# retry defaults — conservative. tuned for warmup OHLCV bursts where transient
|
|
280
|
+
# RateLimitExceeded / NetworkError are the dominant failure modes (see #264).
|
|
281
|
+
_RETRY_MAX_ATTEMPTS = 5
|
|
282
|
+
_RETRY_BASE_DELAY_S = 1.0
|
|
283
|
+
_RETRY_MAX_DELAY_S = 30.0
|
|
284
|
+
_RETRY_JITTER_S = 1.0
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _sync_rate_limiter_from_response_headers(ccxt_ex: Any, rate_limiter: Any) -> None:
|
|
288
|
+
"""
|
|
289
|
+
Best-effort sync of the rate limiter's modeled state from the exchange's
|
|
290
|
+
last HTTP response headers.
|
|
291
|
+
|
|
292
|
+
Without this, the client-side token bucket flies blind to:
|
|
293
|
+
* cross-process / cross-bot contention on the same IP or account,
|
|
294
|
+
* drift between our ``PoolConfig`` and the exchange's real limits,
|
|
295
|
+
* pre-existing budget debt at process start.
|
|
296
|
+
|
|
297
|
+
This is a *best-effort* correction because ``ccxt_ex.last_response_headers``
|
|
298
|
+
is exchange-wide, not per-request — under concurrent fetches the attribute
|
|
299
|
+
may have been overwritten between the awaited response and this read.
|
|
300
|
+
That is acceptable in practice: every concurrent request drains the same
|
|
301
|
+
IP-scoped budget, so whatever remaining value we read is approximately
|
|
302
|
+
correct, and the limiter converges on subsequent syncs.
|
|
303
|
+
|
|
304
|
+
Any exception (missing attr, malformed header, parser bug) is swallowed
|
|
305
|
+
at DEBUG — header sync must never break a fetch.
|
|
306
|
+
"""
|
|
307
|
+
if rate_limiter is None:
|
|
308
|
+
return
|
|
309
|
+
try:
|
|
310
|
+
from qubx.connectors.ccxt.rate_limits import get_header_parser
|
|
311
|
+
|
|
312
|
+
parser = get_header_parser(getattr(ccxt_ex, "id", ""))
|
|
313
|
+
if parser is None:
|
|
314
|
+
return
|
|
315
|
+
headers = getattr(ccxt_ex, "last_response_headers", None)
|
|
316
|
+
if not headers:
|
|
317
|
+
return
|
|
318
|
+
parser(headers, rate_limiter)
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.debug(f"[CCXT] header sync failed (non-fatal): {e}")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class CcxtFetchExhausted(RuntimeError):
|
|
324
|
+
"""
|
|
325
|
+
Raised when one or more symbols exhaust all retry attempts during a
|
|
326
|
+
multi-symbol fetch. The caller cannot silently proceed with partial data,
|
|
327
|
+
because downstream indicators may compute nonsense on zero bars and
|
|
328
|
+
propagate into corrupt live state (see #264).
|
|
329
|
+
|
|
330
|
+
The ``failures`` attribute maps ``qubx_sym -> last_exception`` so callers
|
|
331
|
+
can log or dispatch based on specific failures.
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
def __init__(self, failures: dict[str, BaseException], total_requested: int) -> None:
|
|
335
|
+
self.failures = failures
|
|
336
|
+
self.total_requested = total_requested
|
|
337
|
+
failed_symbols = sorted(failures.keys())
|
|
338
|
+
preview = ", ".join(
|
|
339
|
+
f"{s}={type(failures[s]).__name__}" for s in failed_symbols[:5]
|
|
340
|
+
)
|
|
341
|
+
more = f" (+{len(failed_symbols) - 5} more)" if len(failed_symbols) > 5 else ""
|
|
342
|
+
super().__init__(
|
|
343
|
+
f"OHLCV fetch exhausted retries for {len(failures)}/{total_requested} "
|
|
344
|
+
f"symbols: {preview}{more}"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
async def _retryable_fetch(
|
|
349
|
+
call: Callable[[], Awaitable[_T]],
|
|
350
|
+
*,
|
|
351
|
+
rate_limiter: Any = None,
|
|
352
|
+
rate_limiter_endpoint: str = "ccxt_rest",
|
|
353
|
+
max_attempts: int = _RETRY_MAX_ATTEMPTS,
|
|
354
|
+
base_delay: float = _RETRY_BASE_DELAY_S,
|
|
355
|
+
max_delay: float = _RETRY_MAX_DELAY_S,
|
|
356
|
+
jitter: float = _RETRY_JITTER_S,
|
|
357
|
+
context: str = "",
|
|
358
|
+
sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
|
|
359
|
+
) -> _T:
|
|
360
|
+
"""
|
|
361
|
+
Invoke ``call()`` with exponential backoff + jitter on transient CCXT errors.
|
|
362
|
+
|
|
363
|
+
Retryable errors:
|
|
364
|
+
* ``ccxt.RateLimitExceeded`` — exchange-level 429 / OKX 50011.
|
|
365
|
+
* ``ccxt.NetworkError`` (parent; also covers ``ExchangeNotAvailable``
|
|
366
|
+
and ``OnMaintenance``).
|
|
367
|
+
* ``asyncio.TimeoutError``.
|
|
368
|
+
|
|
369
|
+
Everything else (``ccxt.ExchangeError`` and subclasses like ``BadSymbol``,
|
|
370
|
+
``AuthenticationError``, plus any non-CCXT exception) is re-raised
|
|
371
|
+
immediately — permanent errors should not consume retry budget.
|
|
372
|
+
|
|
373
|
+
The rate-limiter interaction is handled here in two stages per attempt:
|
|
374
|
+
1. ``acquire(endpoint)`` before each call to block while the gate is closed.
|
|
375
|
+
2. ``report_limit_hit(endpoint=...)`` on ``RateLimitExceeded`` so the
|
|
376
|
+
gate closes for ``cooldown`` seconds, pausing all concurrent callers.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
call: Zero-arg callable returning the coroutine to invoke.
|
|
380
|
+
rate_limiter: Optional ``ExchangeRateLimiter``; if provided, calls are
|
|
381
|
+
gated and rate-limit hits are reported back to it.
|
|
382
|
+
rate_limiter_endpoint: Endpoint name used for ``acquire`` /
|
|
383
|
+
``report_limit_hit`` lookups.
|
|
384
|
+
max_attempts: Total attempts including the initial one.
|
|
385
|
+
base_delay: First retry delay in seconds; doubles each attempt.
|
|
386
|
+
max_delay: Cap on the exponential delay.
|
|
387
|
+
jitter: Uniform jitter (0..jitter) added to each delay.
|
|
388
|
+
context: Short human label included in log lines (e.g. ``"OKX BTCUSDT"``).
|
|
389
|
+
sleep: Async sleep function (injectable for tests).
|
|
390
|
+
|
|
391
|
+
Raises:
|
|
392
|
+
The last retryable exception once ``max_attempts`` is exhausted.
|
|
393
|
+
"""
|
|
394
|
+
last_exc: BaseException | None = None
|
|
395
|
+
for attempt in range(1, max_attempts + 1):
|
|
396
|
+
if rate_limiter is not None:
|
|
397
|
+
await rate_limiter.acquire(rate_limiter_endpoint)
|
|
398
|
+
try:
|
|
399
|
+
return await call()
|
|
400
|
+
except (ccxt.NetworkError, asyncio.TimeoutError) as e:
|
|
401
|
+
last_exc = e
|
|
402
|
+
if isinstance(e, ccxt.RateLimitExceeded) and rate_limiter is not None:
|
|
403
|
+
rate_limiter.report_limit_hit(
|
|
404
|
+
endpoint=rate_limiter_endpoint,
|
|
405
|
+
reason=(f"{context}: " if context else "") + str(e)[:120],
|
|
406
|
+
)
|
|
407
|
+
if attempt >= max_attempts:
|
|
408
|
+
break
|
|
409
|
+
delay = min(base_delay * (2 ** (attempt - 1)), max_delay) + random.uniform(0, jitter)
|
|
410
|
+
logger.warning(
|
|
411
|
+
f"[CCXT] transient error on {context or 'fetch'} "
|
|
412
|
+
f"(attempt {attempt}/{max_attempts}): {type(e).__name__}: {e}; "
|
|
413
|
+
f"retrying in {delay:.2f}s"
|
|
414
|
+
)
|
|
415
|
+
await sleep(delay)
|
|
416
|
+
assert last_exc is not None # unreachable: the loop always raises or returns
|
|
417
|
+
raise last_exc
|
|
418
|
+
|
|
419
|
+
|
|
275
420
|
@storage("ccxt")
|
|
276
421
|
class CcxtStorage(IStorage):
|
|
277
422
|
"""
|
|
@@ -315,10 +460,20 @@ class CcxtStorage(IStorage):
|
|
|
315
460
|
max_bars: int = 10_000,
|
|
316
461
|
max_history: str = "3650d",
|
|
317
462
|
rate_limiters: dict | None = None,
|
|
463
|
+
retry_max_attempts: int = _RETRY_MAX_ATTEMPTS,
|
|
464
|
+
retry_base_delay: float = _RETRY_BASE_DELAY_S,
|
|
465
|
+
retry_max_delay: float = _RETRY_MAX_DELAY_S,
|
|
466
|
+
retry_jitter: float = _RETRY_JITTER_S,
|
|
467
|
+
strict_fetch: bool = True,
|
|
318
468
|
) -> None:
|
|
319
469
|
self._max_bars = max_bars
|
|
320
470
|
self._max_history = to_timedelta(max_history)
|
|
321
471
|
self._rate_limiters = rate_limiters or {}
|
|
472
|
+
self._retry_max_attempts = retry_max_attempts
|
|
473
|
+
self._retry_base_delay = retry_base_delay
|
|
474
|
+
self._retry_max_delay = retry_max_delay
|
|
475
|
+
self._retry_jitter = retry_jitter
|
|
476
|
+
self._strict_fetch = strict_fetch
|
|
322
477
|
# - shared async loop (created from first exchange connection)
|
|
323
478
|
self._loop = None
|
|
324
479
|
self._capabilities = None
|
|
@@ -484,13 +639,31 @@ class CcxtStorage(IStorage):
|
|
|
484
639
|
all_items: list = []
|
|
485
640
|
current_since = since
|
|
486
641
|
|
|
487
|
-
# Get rate limiter for this exchange (if available)
|
|
642
|
+
# Get rate limiter for this exchange (if available). Retry + backoff + rate-limit
|
|
643
|
+
# acquisition are all encapsulated inside ``_retryable_fetch`` so this loop stays
|
|
644
|
+
# focused on pagination.
|
|
488
645
|
_rl = self._get_rate_limiter(ccxt_ex)
|
|
646
|
+
_ex_id = getattr(ccxt_ex, "id", "ccxt")
|
|
489
647
|
|
|
490
648
|
for _ in range(max_pages):
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
649
|
+
_since = current_since # capture for the closure below
|
|
650
|
+
|
|
651
|
+
async def _do_page() -> list:
|
|
652
|
+
batch = await method(since=_since, limit=limit, **method_kwargs)
|
|
653
|
+
# Best-effort: keep our modeled budget in sync with what the exchange
|
|
654
|
+
# actually reports — see ``_sync_rate_limiter_from_response_headers``.
|
|
655
|
+
_sync_rate_limiter_from_response_headers(ccxt_ex, _rl)
|
|
656
|
+
return batch
|
|
657
|
+
|
|
658
|
+
batch = await _retryable_fetch(
|
|
659
|
+
_do_page,
|
|
660
|
+
rate_limiter=_rl,
|
|
661
|
+
max_attempts=self._retry_max_attempts,
|
|
662
|
+
base_delay=self._retry_base_delay,
|
|
663
|
+
max_delay=self._retry_max_delay,
|
|
664
|
+
jitter=self._retry_jitter,
|
|
665
|
+
context=f"{_ex_id} {method_kwargs.get('symbol', '?')}",
|
|
666
|
+
)
|
|
494
667
|
if not batch:
|
|
495
668
|
break
|
|
496
669
|
for item in batch:
|
|
@@ -543,7 +716,24 @@ class CcxtStorage(IStorage):
|
|
|
543
716
|
) -> dict[str, list]:
|
|
544
717
|
"""
|
|
545
718
|
Concurrent OHLCV fetch for multiple instruments with bounded concurrency.
|
|
546
|
-
|
|
719
|
+
|
|
720
|
+
Per-symbol results go through ``_async_paginated_fetch`` which already
|
|
721
|
+
retries transient errors with backoff. If retries are exhausted, behavior
|
|
722
|
+
depends on ``self._strict_fetch``:
|
|
723
|
+
|
|
724
|
+
* ``strict_fetch=True`` (default) — logs ERROR per symbol and raises
|
|
725
|
+
:class:`CcxtFetchExhausted` listing all failures. This prevents
|
|
726
|
+
downstream indicators from computing on zero bars (see #264).
|
|
727
|
+
* ``strict_fetch=False`` — logs WARNING per symbol and returns ``[]``
|
|
728
|
+
for that symbol. Kept for backwards compatibility and use cases
|
|
729
|
+
where partial data is acceptable.
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
``{qubx_sym: raw_candles}`` on full or partial success.
|
|
733
|
+
|
|
734
|
+
Raises:
|
|
735
|
+
CcxtFetchExhausted: when any symbol exhausts retries and
|
|
736
|
+
``strict_fetch`` is True.
|
|
547
737
|
"""
|
|
548
738
|
sem = asyncio.Semaphore(5) # max 5 concurrent fetches
|
|
549
739
|
|
|
@@ -553,14 +743,26 @@ class CcxtStorage(IStorage):
|
|
|
553
743
|
|
|
554
744
|
coros = [_fetch_with_sem(ccxt_sym, exc_tf, since, until) for ccxt_sym, _ in instruments_info]
|
|
555
745
|
results = await asyncio.gather(*coros, return_exceptions=True)
|
|
746
|
+
|
|
556
747
|
out: dict[str, list] = {}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if isinstance(result,
|
|
560
|
-
|
|
748
|
+
failures: dict[str, BaseException] = {}
|
|
749
|
+
for (_, qubx_sym), result in zip(instruments_info, results):
|
|
750
|
+
if isinstance(result, BaseException):
|
|
751
|
+
failures[qubx_sym] = result
|
|
561
752
|
out[qubx_sym] = []
|
|
562
753
|
else:
|
|
563
754
|
out[qubx_sym] = cast(list, result)
|
|
755
|
+
|
|
756
|
+
if failures:
|
|
757
|
+
_ex_id = getattr(ccxt_ex, "id", "ccxt")
|
|
758
|
+
for qubx_sym, exc in failures.items():
|
|
759
|
+
logger.error(
|
|
760
|
+
f"[CCXT] {_ex_id}: OHLCV fetch for {qubx_sym} exhausted retries — "
|
|
761
|
+
f"{type(exc).__name__}: {exc}"
|
|
762
|
+
)
|
|
763
|
+
if self._strict_fetch:
|
|
764
|
+
raise CcxtFetchExhausted(failures, total_requested=len(instruments_info))
|
|
765
|
+
|
|
564
766
|
return out
|
|
565
767
|
|
|
566
768
|
def _ccxt_sym_to_qubx(self, ccxt_symbol: str) -> str:
|
|
@@ -310,7 +310,7 @@ class TypedRecords(IDataTransformer):
|
|
|
310
310
|
return FundingPayment(
|
|
311
311
|
time=int(row["time"]),
|
|
312
312
|
funding_rate=float(row["funding_rate"]),
|
|
313
|
-
funding_interval_hours=
|
|
313
|
+
funding_interval_hours=round(float(row["funding_interval_hours"])),
|
|
314
314
|
)
|
|
315
315
|
|
|
316
316
|
case DataType.OPEN_INTEREST:
|
|
@@ -251,7 +251,7 @@ def running_view(arr, window, axis=-1):
|
|
|
251
251
|
"""
|
|
252
252
|
shape = list(arr.shape)
|
|
253
253
|
shape[axis] -= window - 1
|
|
254
|
-
return np.lib.
|
|
254
|
+
return np.lib.stride_tricks.as_strided(arr, shape + [window], arr.strides + (arr.strides[axis],))
|
|
255
255
|
|
|
256
256
|
|
|
257
257
|
def detrend(y, order):
|
|
@@ -794,9 +794,11 @@ class AtrRiskTracker(GenericRiskControllerDecorator):
|
|
|
794
794
|
def calculate_risks(self, ctx: IStrategyContext, quote: Quote | None, signal: Signal) -> Signal | None:
|
|
795
795
|
volatility = self._get_volatility(ctx, signal.instrument)
|
|
796
796
|
|
|
797
|
-
if len(volatility)
|
|
797
|
+
last_volatility = volatility[1] if len(volatility) >= 2 else None
|
|
798
|
+
if last_volatility is None or not np.isfinite(last_volatility) or last_volatility <= 0:
|
|
798
799
|
logger.debug(
|
|
799
|
-
f"[<y>{self._full_name}</y>(<g>{signal.instrument}</g>)] ::
|
|
800
|
+
f"[<y>{self._full_name}</y>(<g>{signal.instrument}</g>)] :: "
|
|
801
|
+
f"ATR unusable (value={last_volatility!r}), skipping risk calculation"
|
|
800
802
|
)
|
|
801
803
|
return None
|
|
802
804
|
|
|
@@ -113,6 +113,14 @@ class FixedRiskSizer(IPositionSizer):
|
|
|
113
113
|
if (_entry := self.get_signal_entry_price(ctx, signal)) is None:
|
|
114
114
|
continue
|
|
115
115
|
|
|
116
|
+
_stop_distance = abs(signal.stop / _entry - 1)
|
|
117
|
+
if _stop_distance <= 0 or not np.isfinite(_stop_distance):
|
|
118
|
+
logger.warning(
|
|
119
|
+
f" >>> {self.__class__.__name__}: degenerate stop/entry for "
|
|
120
|
+
f"{signal.instrument.symbol} (entry={_entry}, stop={signal.stop}) - skipping"
|
|
121
|
+
)
|
|
122
|
+
continue
|
|
123
|
+
|
|
116
124
|
# - hey, we can't trade using negative balance ;)
|
|
117
125
|
_cap = max(ctx.get_total_capital() if self.reinvest_profit else ctx.get_capital(), 0)
|
|
118
126
|
_scale = abs(signal.signal) if self.scale_by_signal else 1
|
|
@@ -122,7 +130,7 @@ class FixedRiskSizer(IPositionSizer):
|
|
|
122
130
|
_notional_per_contract = _entry * signal.instrument.quantity_multiplier
|
|
123
131
|
target_position_size = (
|
|
124
132
|
_direction
|
|
125
|
-
*min((_cap * self.max_cap_in_risk) /
|
|
133
|
+
*min((_cap * self.max_cap_in_risk) / _stop_distance, self.max_allowed_position_quoted) / _notional_per_contract
|
|
126
134
|
/ (len(ctx.instruments) if self.divide_by_symbols else 1)
|
|
127
135
|
* _scale
|
|
128
136
|
)
|
|
@@ -237,6 +245,14 @@ class FixedRiskSizerWithConstantCapital(IPositionSizer):
|
|
|
237
245
|
if (_entry := self.get_signal_entry_price(ctx, signal)) is None:
|
|
238
246
|
continue
|
|
239
247
|
|
|
248
|
+
_stop_distance = abs(signal.stop / _entry - 1)
|
|
249
|
+
if _stop_distance <= 0 or not np.isfinite(_stop_distance):
|
|
250
|
+
logger.warning(
|
|
251
|
+
f" >>> {self.__class__.__name__}: degenerate stop/entry for {signal.instrument.symbol} "
|
|
252
|
+
f"(entry={_entry}, stop={signal.stop}) - skipping"
|
|
253
|
+
)
|
|
254
|
+
continue
|
|
255
|
+
|
|
240
256
|
# - just use same fixed capital
|
|
241
257
|
_cap = self.capital / (len(ctx.instruments) if self.divide_by_symbols else 1)
|
|
242
258
|
|
|
@@ -245,7 +261,7 @@ class FixedRiskSizerWithConstantCapital(IPositionSizer):
|
|
|
245
261
|
_notional_per_contract = _entry * signal.instrument.quantity_multiplier
|
|
246
262
|
target_position_size = (
|
|
247
263
|
_direction * min(
|
|
248
|
-
(_cap * self.max_cap_in_risk) /
|
|
264
|
+
(_cap * self.max_cap_in_risk) / _stop_distance,
|
|
249
265
|
self.max_allowed_position_quoted
|
|
250
266
|
) / _notional_per_contract
|
|
251
267
|
)
|
|
@@ -80,6 +80,27 @@ from .accounts import AccountConfigurationManager
|
|
|
80
80
|
INVERSE_EXCHANGE_MAPPINGS = {mapping: exchange for exchange, mapping in EXCHANGE_MAPPINGS.items()}
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def _inject_warmup_rate_limiters(warmup: WarmupConfig | None, rate_limiters: dict | None) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Inject rate limiters into warmup storage configs.
|
|
86
|
+
|
|
87
|
+
Mirrors the aux_configs injection done in ``create_strategy_context`` — without this,
|
|
88
|
+
warmup data fetches (e.g. via ``CcxtStorage``) bypass the per-exchange rate limiter
|
|
89
|
+
and can be rate-limited by the exchange with no retry, producing empty bars and
|
|
90
|
+
corrupting downstream indicators (see xLydianSoftware/Qubx#264).
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
warmup: Warmup config. If None or no rate_limiters, this is a no-op.
|
|
94
|
+
rate_limiters: Dict of rate limiters keyed by exchange name (from ``RateLimitManager``).
|
|
95
|
+
"""
|
|
96
|
+
if warmup is None or not rate_limiters:
|
|
97
|
+
return
|
|
98
|
+
warmup.data.args["rate_limiters"] = rate_limiters
|
|
99
|
+
for typed_cfg in warmup.custom_data or []:
|
|
100
|
+
for sc in typed_cfg.storages:
|
|
101
|
+
sc.args["rate_limiters"] = rate_limiters
|
|
102
|
+
|
|
103
|
+
|
|
83
104
|
def _cleanup_event_loop(loop: asyncio.AbstractEventLoop | None) -> None:
|
|
84
105
|
"""
|
|
85
106
|
Cleanup the shared event loop.
|
|
@@ -1028,6 +1049,11 @@ def _run_warmup(
|
|
|
1028
1049
|
|
|
1029
1050
|
logger.info(f"<yellow>Warmup start time: {warmup_start_time}</yellow>")
|
|
1030
1051
|
|
|
1052
|
+
# - inject rate limiters into warmup storage configs (for CcxtStorage, LighterStorage, etc.)
|
|
1053
|
+
# mirrors the aux_configs injection done in create_strategy_context.
|
|
1054
|
+
# without this, warmup OHLCV fetches bypass the configured per-exchange rate limiter.
|
|
1055
|
+
_inject_warmup_rate_limiters(warmup, getattr(ctx, "_rate_limiters", None))
|
|
1056
|
+
|
|
1031
1057
|
# - resolve warmup data sources (mirrors SimulationConfig layout)
|
|
1032
1058
|
# - warmup.data is required (StorageConfig, not None) so construct_storage always returns IStorage
|
|
1033
1059
|
_warmup_data_storage = construct_storage(warmup.data)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py
RENAMED
|
File without changes
|
{qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py
RENAMED
|
File without changes
|
{qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py
RENAMED
|
File without changes
|
{qubx-1.2.3.dev1 → qubx-1.2.3.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|