Qubx 3.2.2.dev1__tar.gz → 3.3.0__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.
Files changed (328) hide show
  1. {qubx-3.2.2.dev1 → qubx-3.3.0}/PKG-INFO +1 -2
  2. {qubx-3.2.2.dev1 → qubx-3.3.0}/pyproject.toml +0 -1
  3. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/_version.py +2 -2
  4. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/connector.py +6 -2
  5. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/runner.py +5 -14
  6. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/simulator.py +1 -1
  7. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/transfers.py +16 -5
  8. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/utils.py +73 -8
  9. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/connection_manager.py +2 -1
  10. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/connector.py +309 -26
  11. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchange_manager.py +18 -61
  12. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/base.py +8 -2
  13. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +15 -0
  14. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +13 -1
  15. qubx-3.3.0/src/qubx/connectors/ccxt/rate_limits.py +424 -0
  16. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/warmup_service.py +25 -3
  17. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/control/builtin.py +27 -3
  18. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/account_manager/manager.py +26 -4
  19. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/account_manager/reconciler.py +6 -2
  20. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/account_manager/reducer.py +12 -1
  21. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/account_manager/state.py +41 -8
  22. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/basics.py +15 -6
  23. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/connector.py +12 -1
  24. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/context.py +101 -6
  25. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/errors.py +20 -0
  26. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/events.py +1 -0
  27. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/exceptions.py +20 -0
  28. qubx-3.3.0/src/qubx/core/fit_context.py +481 -0
  29. qubx-3.3.0/src/qubx/core/fit_executor.py +157 -0
  30. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/initializer.py +23 -0
  31. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/interfaces.py +151 -15
  32. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/metrics.py +31 -28
  33. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/mixins/instrument_service.py +18 -8
  34. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/mixins/market.py +193 -26
  35. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/mixins/processing.py +205 -13
  36. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/mixins/subscription.py +177 -40
  37. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/mixins/trading.py +102 -22
  38. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/series.pyi +4 -0
  39. qubx-3.3.0/src/qubx/core/status.py +108 -0
  40. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/storages/ccxt.py +15 -56
  41. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/storages/multi.py +23 -16
  42. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/emitters/csv.py +2 -2
  43. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/emitters/inmemory.py +2 -2
  44. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/emitters/questdb.py +22 -15
  45. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/redis_streams.py +22 -11
  46. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/health/base.py +176 -11
  47. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/health/dummy.py +7 -0
  48. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/rate_limiting/__init__.py +3 -1
  49. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/rate_limiting/backend.py +13 -6
  50. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/rate_limiting/config.py +9 -3
  51. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/rate_limiting/engine.py +14 -1
  52. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/rate_limiting/ip_resolver.py +6 -6
  53. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/rate_limiting/manager.py +18 -3
  54. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/rate_limiting/pools.py +63 -51
  55. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/rate_limiting/redis_backend.py +28 -4
  56. qubx-3.3.0/src/qubx/rate_limiting/resilient.py +176 -0
  57. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/state/redis.py +19 -1
  58. qubx-3.3.0/src/qubx/state/safe.py +226 -0
  59. qubx-3.3.0/src/qubx/utils/clock.py +167 -0
  60. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/rate_limiter.py +53 -35
  61. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/results.py +23 -1
  62. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/configs.py +43 -1
  63. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/factory.py +16 -3
  64. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/runner.py +10 -1
  65. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/handlers.py +1 -0
  66. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/init_code.py +14 -0
  67. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/styles.tcss +7 -0
  68. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/widgets/account_summary.py +26 -0
  69. qubx-3.3.0/src/qubx/utils/threading.py +84 -0
  70. qubx-3.2.2.dev1/src/qubx/connectors/ccxt/rate_limits.py +0 -295
  71. qubx-3.2.2.dev1/src/qubx/utils/ntp.py +0 -63
  72. {qubx-3.2.2.dev1 → qubx-3.3.0}/.gitignore +0 -0
  73. {qubx-3.2.2.dev1 → qubx-3.3.0}/LICENSE +0 -0
  74. {qubx-3.2.2.dev1 → qubx-3.3.0}/README.md +0 -0
  75. {qubx-3.2.2.dev1 → qubx-3.3.0}/scripts/build.py +0 -0
  76. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/__init__.py +0 -0
  77. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/_nb_magic.py +0 -0
  78. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/__init__.py +0 -0
  79. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/data.py +0 -0
  80. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/iteratedstream.py +0 -0
  81. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/management.py +0 -0
  82. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/ome.py +0 -0
  83. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/optimization.py +0 -0
  84. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/sentinels.py +0 -0
  85. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/simulated_data.py +0 -0
  86. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/backtester/simulated_exchange.py +0 -0
  87. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/__init__.py +0 -0
  88. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/commands.py +0 -0
  89. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/deploy.py +0 -0
  90. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/misc.py +0 -0
  91. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/release.py +0 -0
  92. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/resolver.py +0 -0
  93. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/s3.py +0 -0
  94. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/theme.py +0 -0
  95. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/tui.py +0 -0
  96. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/cli/user_config.py +0 -0
  97. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/config.py +0 -0
  98. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/__init__.py +0 -0
  99. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/__init__.py +0 -0
  100. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  101. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  102. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/data.py +0 -0
  103. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  104. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  105. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/_two_stream.py +0 -0
  106. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/binance/connector.py +0 -0
  107. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  108. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  109. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/bitfinex/connector.py +0 -0
  110. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
  111. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  112. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  113. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
  114. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/okx/connector.py +0 -0
  115. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
  116. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/factory.py +0 -0
  117. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  118. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  119. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  120. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  121. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  122. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  123. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  124. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  125. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  126. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  127. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/plugin.py +0 -0
  128. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  129. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  130. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  131. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/ccxt/utils.py +0 -0
  132. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/plugin.py +0 -0
  133. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/registry.py +0 -0
  134. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/tardis/data.py +0 -0
  135. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/tardis/plugin.py +0 -0
  136. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/connectors/tardis/utils.py +0 -0
  137. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/control/__init__.py +0 -0
  138. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/control/decorator.py +0 -0
  139. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/control/executor.py +0 -0
  140. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/control/interfaces.py +0 -0
  141. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/control/server.py +0 -0
  142. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/control/types.py +0 -0
  143. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/__init__.py +0 -0
  144. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/account_manager/__init__.py +0 -0
  145. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/account_manager/config.py +0 -0
  146. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/account_manager/diffs.py +0 -0
  147. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/account_manager/state_machine.py +0 -0
  148. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/detectors/__init__.py +0 -0
  149. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/detectors/delisting.py +0 -0
  150. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/detectors/stale.py +0 -0
  151. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/helpers.py +0 -0
  152. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/instrument_service.py +0 -0
  153. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/loggers.py +0 -0
  154. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/lookups.py +0 -0
  155. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/mixins/__init__.py +0 -0
  156. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/mixins/universe.py +0 -0
  157. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/mixins/utils.py +0 -0
  158. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/series.pxd +0 -0
  159. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/series.pyx +0 -0
  160. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/utils.pyi +0 -0
  161. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/core/utils.pyx +0 -0
  162. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/__init__.py +0 -0
  163. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/cache.py +0 -0
  164. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/containers.py +0 -0
  165. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/guards.py +0 -0
  166. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/registry.py +0 -0
  167. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/storage.py +0 -0
  168. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/storages/csv.py +0 -0
  169. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/storages/handy.py +0 -0
  170. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/storages/questdb.py +0 -0
  171. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/storages/stub.py +0 -0
  172. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/storages/utils.py +0 -0
  173. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/data/transformers.py +0 -0
  174. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/emitters/__init__.py +0 -0
  175. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/emitters/base.py +0 -0
  176. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/emitters/composite.py +0 -0
  177. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/emitters/indicator.py +0 -0
  178. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/emitters/prometheus.py +0 -0
  179. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/__init__.py +0 -0
  180. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/composite.py +0 -0
  181. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/formatters/__init__.py +0 -0
  182. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/formatters/base.py +0 -0
  183. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/formatters/incremental.py +0 -0
  184. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/formatters/slack.py +0 -0
  185. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/formatters/target_position.py +0 -0
  186. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/exporters/slack.py +0 -0
  187. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/gathering/simplest.py +0 -0
  188. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/health/__init__.py +0 -0
  189. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/loggers/__init__.py +0 -0
  190. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/loggers/csv.py +0 -0
  191. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/loggers/factory.py +0 -0
  192. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/loggers/inmemory.py +0 -0
  193. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/loggers/mongo.py +0 -0
  194. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/loggers/postgres.py +0 -0
  195. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/notifications/__init__.py +0 -0
  196. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/notifications/composite.py +0 -0
  197. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/notifications/slack.py +0 -0
  198. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/notifications/throttler.py +0 -0
  199. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/pandaz/__init__.py +0 -0
  200. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/pandaz/stats.py +0 -0
  201. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/pandaz/ta.py +0 -0
  202. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/pandaz/utils.py +0 -0
  203. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/plugins/__init__.py +0 -0
  204. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/plugins/loader.py +0 -0
  205. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/_build.py +0 -0
  206. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/crypto-fees.ini +0 -0
  207. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  208. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  209. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  210. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  211. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  212. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  213. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  214. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  215. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  216. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  217. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  218. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restarts/__init__.py +0 -0
  219. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restarts/state_resolvers.py +0 -0
  220. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restarts/time_finders.py +0 -0
  221. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restorers/__init__.py +0 -0
  222. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restorers/balance.py +0 -0
  223. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restorers/factory.py +0 -0
  224. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restorers/interfaces.py +0 -0
  225. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restorers/position.py +0 -0
  226. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restorers/signal.py +0 -0
  227. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restorers/state.py +0 -0
  228. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/restorers/utils.py +0 -0
  229. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/state/__init__.py +0 -0
  230. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/state/dummy.py +0 -0
  231. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/ta/__init__.py +0 -0
  232. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/ta/indicators.pxd +0 -0
  233. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/ta/indicators.pyi +0 -0
  234. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/ta/indicators.pyx +0 -0
  235. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/__init__.py +0 -0
  236. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/base.py +0 -0
  237. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  238. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/project/config.yml.j2 +0 -0
  239. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  240. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  241. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  242. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  243. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  244. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/project/template.yml +0 -0
  245. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
  246. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
  247. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
  248. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
  249. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
  250. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.gitignore.j2 +0 -0
  251. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.python-version +0 -0
  252. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.vscode/launch.json +0 -0
  253. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.vscode/settings.json +0 -0
  254. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
  255. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
  256. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/README.md.j2 +0 -0
  257. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/configs/.gitkeep +0 -0
  258. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/justfile.j2 +0 -0
  259. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
  260. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/research/.gitkeep +0 -0
  261. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  262. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
  263. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  264. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/template.yml +0 -0
  265. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
  266. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  267. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  268. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/simple/config.yml.j2 +0 -0
  269. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  270. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  271. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  272. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/templates/simple/template.yml +0 -0
  273. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/trackers/__init__.py +0 -0
  274. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/trackers/advanced.py +0 -0
  275. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/trackers/composite.py +0 -0
  276. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/trackers/rebalancers.py +0 -0
  277. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/trackers/riskctrl.py +0 -0
  278. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/trackers/sizers.py +0 -0
  279. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/__init__.py +0 -0
  280. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/_pyxreloader.py +0 -0
  281. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/charting/lookinglass.py +0 -0
  282. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  283. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/charting/orderbook.py +0 -0
  284. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/collections.py +0 -0
  285. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/hft/__init__.py +0 -0
  286. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/hft/numba_utils.py +0 -0
  287. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/hft/orderbook.pyi +0 -0
  288. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/hft/orderbook.pyx +0 -0
  289. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/marketdata/binance.py +0 -0
  290. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/marketdata/ccxt.py +0 -0
  291. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/marketdata/dukas.py +0 -0
  292. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/misc.py +0 -0
  293. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/nonce.py +0 -0
  294. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/numbers_utils.py +0 -0
  295. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/orderbook.py +0 -0
  296. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/plotting/__init__.py +0 -0
  297. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/plotting/dashboard.py +0 -0
  298. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/plotting/data.py +0 -0
  299. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/plotting/interfaces.py +0 -0
  300. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  301. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  302. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/questdb.py +0 -0
  303. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/ringbuffer.pxd +0 -0
  304. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/ringbuffer.pyi +0 -0
  305. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/ringbuffer.pyx +0 -0
  306. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/__init__.py +0 -0
  307. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  308. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/accounts.py +0 -0
  309. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/kernel_service.py +0 -0
  310. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/__init__.py +0 -0
  311. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/app.py +0 -0
  312. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/kernel.py +0 -0
  313. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  314. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  315. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  316. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  317. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  318. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  319. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  320. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/s3.py +0 -0
  321. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/slack.py +0 -0
  322. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/throttler.py +0 -0
  323. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/time.py +0 -0
  324. {qubx-3.2.2.dev1 → qubx-3.3.0}/src/qubx/utils/websocket_manager.py +0 -0
  325. {qubx-3.2.2.dev1 → qubx-3.3.0}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  326. {qubx-3.2.2.dev1 → qubx-3.3.0}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
  327. {qubx-3.2.2.dev1 → qubx-3.3.0}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  328. {qubx-3.2.2.dev1 → qubx-3.3.0}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: Qubx
3
- Version: 3.2.2.dev1
3
+ Version: 3.3.0
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Project-URL: homepage, https://xlydian.com
6
6
  Project-URL: repository, https://github.com/xLydianSoftware/Qubx
@@ -23,7 +23,6 @@ Requires-Dist: joblib<2,>=1.3.0
23
23
  Requires-Dist: jupyter-client<9,>=8.0.0
24
24
  Requires-Dist: loguru<1,>=0.7.2
25
25
  Requires-Dist: msgspec<1,>=0.18.6
26
- Requires-Dist: ntplib<1,>=0.4.0
27
26
  Requires-Dist: numba<1,>=0.59.1
28
27
  Requires-Dist: numpy>=2.0.0
29
28
  Requires-Dist: orjson<4,>=3.10.15
@@ -53,7 +53,6 @@ dependencies = [
53
53
  "croniter>=2.0.5,<3",
54
54
  "tabulate>=0.9.0,<1",
55
55
  "gitpython>=3.1.44,<4",
56
- "ntplib>=0.4.0,<1",
57
56
  "jinja2>=3.1.0,<4",
58
57
  "rich>=13.9.4,<14",
59
58
  "click>=8.1.7,<9",
@@ -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 = '3.2.2.dev1'
22
- __version_tuple__ = version_tuple = (3, 2, 2, 'dev1')
21
+ __version__ = version = '3.3.0'
22
+ __version_tuple__ = version_tuple = (3, 3, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -298,13 +298,17 @@ class SimulatedConnector(ChannelEmitter):
298
298
  def is_simulated_trading(self) -> bool:
299
299
  return True
300
300
 
301
- def set_max_instrument_leverage(self, instrument: Instrument, leverage: float) -> bool:
302
- return True
301
+ def set_instrument_leverage(self, instrument: Instrument, leverage: float) -> None:
302
+ # - simulation does not model venue leverage settings; accept and forget
303
+ return None
303
304
 
304
305
  def set_margin_mode(self, instrument: Instrument, mode: str) -> bool:
305
306
  return True
306
307
 
307
308
  # Simulation does not model venue margin: the config getters read neutral.
309
+ def get_instrument_leverage(self, instrument: Instrument) -> float | None:
310
+ return None
311
+
308
312
  def get_max_instrument_leverage(self, instrument: Instrument) -> float | None:
309
313
  return None
310
314
 
@@ -11,7 +11,7 @@ from qubx.backtester.sentinels import NoDataContinue
11
11
  from qubx.backtester.simulated_data import SimulatedDataIterator
12
12
  from qubx.backtester.transfers import SimulationTransferManager
13
13
  from qubx.core.account_manager import AccountManagerConfig, SimulatedAccountManager
14
- from qubx.core.basics import SW, Balance, DataType, Instrument, TransactionCostsCalculator
14
+ from qubx.core.basics import SW, DataType, Instrument, TransactionCostsCalculator
15
15
  from qubx.core.context import StrategyContext
16
16
  from qubx.core.exceptions import SimulationConfigError, SimulationError
17
17
  from qubx.core.helpers import extract_parameters_from_object, full_qualified_class_name
@@ -49,6 +49,7 @@ from .utils import (
49
49
  SimulationSetup,
50
50
  _get_default_warmup_period,
51
51
  find_open_close_time_indent_secs_from_subscription,
52
+ initial_balances,
52
53
  )
53
54
 
54
55
 
@@ -608,7 +609,7 @@ class SimulationRunner:
608
609
 
609
610
  am = SimulatedAccountManager(
610
611
  connectors=self._connectors,
611
- base_currencies={exchange: self.setup.base_currency for exchange in self.setup.exchanges},
612
+ base_currencies=self.setup.base_currencies,
612
613
  time=time_provider,
613
614
  cfg=AccountManagerConfig(),
614
615
  account_id=self.account_id,
@@ -616,17 +617,7 @@ class SimulationRunner:
616
617
  )
617
618
 
618
619
  # - seed initial capital per exchange into the account state
619
- assert isinstance(self.setup.capital, dict)
620
- for exchange, capital in self.setup.capital.items():
621
- am.seed_balance(
622
- exchange,
623
- Balance(
624
- exchange=exchange,
625
- currency=self.setup.base_currency,
626
- total=capital,
627
- free=capital,
628
- locked=0.0,
629
- ),
630
- )
620
+ for exchange, balance in initial_balances(self.setup).items():
621
+ am.seed_balance(exchange, balance)
631
622
 
632
623
  return am
@@ -38,7 +38,7 @@ def simulate(
38
38
  instruments: list[str] | list[Instrument] | dict[ExchangeName_t, list[SymbolOrInstrument_t]] | None = None,
39
39
  commissions: str | dict[str, str | None] | None = None,
40
40
  exchange: ExchangeName_t | list[ExchangeName_t] | None = None,
41
- base_currency: str = "USDT",
41
+ base_currency: str | dict[str, str] = "USDT",
42
42
  n_jobs: int = 1,
43
43
  silent: bool = False,
44
44
  aux_data: IStorage | None = None,
@@ -21,6 +21,15 @@ class SimulationTransferManager(ITransferManager):
21
21
  def transfer_funds(self, from_exchange: str, to_exchange: str, currency: str, amount: float) -> str:
22
22
  if amount <= 0:
23
23
  raise ValueError(f"Transfer amount must be positive, got {amount}")
24
+ to_currency = self._account.get_base_currency(to_exchange)
25
+ # Venues settle in their own currency: a withdrawal leaves in `currency` and arrives as
26
+ # `to_currency`. Both legs must be priced — an unpriced asset has no transferable value.
27
+ from_rate = self._account.get_state(from_exchange).conversion_rate_to_base(currency)
28
+ to_rate = self._account.get_state(to_exchange).conversion_rate_to_base(to_currency)
29
+ if from_rate is None or to_rate is None:
30
+ raise ValueError(f"Cannot transfer {currency} to {to_exchange} {to_currency}: no conversion rate")
31
+ to_amount = amount * from_rate / to_rate
32
+
24
33
  from_balance = self._account.get_balance(currency, exchange=from_exchange)
25
34
  if from_balance.free < amount:
26
35
  raise ValueError(
@@ -28,11 +37,8 @@ class SimulationTransferManager(ITransferManager):
28
37
  f"{from_balance.free:.8f} {currency} available, {amount:.8f} requested"
29
38
  )
30
39
 
31
- # Instant transfer: debit the source and credit the destination. adjust_balance
32
- # mutates the held Balance in place (holders keep a live reference) and creates
33
- # the destination Balance if missing.
34
40
  self._account.adjust_balance(from_exchange, currency, -amount)
35
- self._account.adjust_balance(to_exchange, currency, amount)
41
+ self._account.adjust_balance(to_exchange, to_currency, to_amount)
36
42
 
37
43
  transaction_id = f"sim_{uuid.uuid4().hex[:12]}"
38
44
  self._transfers[transaction_id] = Transfer(
@@ -43,8 +49,13 @@ class SimulationTransferManager(ITransferManager):
43
49
  amount=amount,
44
50
  status=TransferStatus.COMPLETED, # transfers are instant in simulation
45
51
  timestamp=self._time.time(),
52
+ to_currency=to_currency,
53
+ to_amount=to_amount,
54
+ )
55
+ logger.debug(
56
+ f"[SimTransfer] {amount:.8f} {currency} {from_exchange} -> "
57
+ f"{to_amount:.8f} {to_currency} {to_exchange} ({transaction_id})"
46
58
  )
47
- logger.debug(f"[SimTransfer] {amount:.8f} {currency} {from_exchange} -> {to_exchange} ({transaction_id})")
48
59
  return transaction_id
49
60
 
50
61
  def get_transfer_status(self, transaction_id: str) -> Transfer:
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Mapping, Sequence
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
3
  from enum import Enum
4
4
  from typing import Any, Callable, TypeAlias
5
5
 
@@ -8,6 +8,8 @@ import pandas as pd
8
8
 
9
9
  from qubx import logger
10
10
  from qubx.core.basics import (
11
+ STABLE_CURRENCIES,
12
+ Balance,
11
13
  CtrlChannel,
12
14
  DataType,
13
15
  Instrument,
@@ -80,6 +82,20 @@ def _type(obj: Any) -> SetupTypes:
80
82
  return t
81
83
 
82
84
 
85
+ def _settle_currencies(instruments: list[Instrument], exchange: str) -> set[str]:
86
+ return {i.settle.upper() for i in instruments if i.exchange == exchange and i.settle}
87
+
88
+
89
+ def _derive_settle_currency(instruments: list[Instrument], exchange: str) -> str | None:
90
+ """The one settle currency a venue's instruments agree on, or None when absent, mixed, or
91
+ not a recognized stable — BINANCE.UM:ETHBTC settles in BTC, and BTC is not 1.0 of capital."""
92
+ settles = _settle_currencies(instruments, exchange)
93
+ if len(settles) != 1:
94
+ return None
95
+ settle = settles.pop()
96
+ return settle if settle in STABLE_CURRENCIES else None
97
+
98
+
83
99
  @dataclass
84
100
  class SimulationSetup:
85
101
  """
@@ -93,16 +109,37 @@ class SimulationSetup:
93
109
  instruments: list[Instrument]
94
110
  exchanges: list[str]
95
111
  capital: float | dict[str, float]
96
- base_currency: str
112
+ base_currency: str | dict[str, str]
97
113
  commissions: str | dict[str, str | None] | None = None
98
114
  signal_timeframe: str = "1Min"
99
115
  accurate_stop_orders_execution: bool = False
100
116
  enable_funding: bool = False
117
+ base_currencies: dict[str, str] = field(init=False, default_factory=dict)
101
118
 
102
119
  def __post_init__(self) -> None:
103
- # Normalize once at the config boundary: AccountState stores base_currency upper-cased,
104
- # so a lowercase value here would seed capital under a key total_capital never reads.
105
- self.base_currency = self.base_currency.upper()
120
+ # Per-exchange base currency: explicit mapping wins, else the venue's shared settle
121
+ # currency, else the scalar. AccountState upper-cases, so normalize here or the seeded
122
+ # Balance lands under a key total_capital never reads.
123
+ explicit = (
124
+ {ex: ccy.upper() for ex, ccy in self.base_currency.items()}
125
+ if isinstance(self.base_currency, dict)
126
+ else {}
127
+ )
128
+ default_ccy = (
129
+ self.base_currency.upper()
130
+ if isinstance(self.base_currency, str)
131
+ else (explicit.get(self.exchanges[0], "USDT") if self.exchanges else "USDT")
132
+ )
133
+ self.base_currencies = {}
134
+ for ex in self.exchanges:
135
+ resolved = explicit.get(ex) or _derive_settle_currency(self.instruments, ex)
136
+ if resolved is None and (settles := _settle_currencies(self.instruments, ex)):
137
+ logger.debug(
138
+ f"No single stable settle currency for {ex} (saw {sorted(settles)}) "
139
+ f"- using {default_ccy} as its base currency"
140
+ )
141
+ self.base_currencies[ex] = resolved or default_ccy
142
+ self.base_currency = self.base_currencies[self.exchanges[0]] if self.exchanges else default_ccy
106
143
  # Normalize capital to a per-exchange dict: split evenly when a single float is given
107
144
  if isinstance(self.capital, (int, float)):
108
145
  n = len(self.exchanges)
@@ -113,6 +150,21 @@ class SimulationSetup:
113
150
  return f"{self.name} {self.setup_type} capital {self.capital} {self.base_currency} for [{','.join(map(lambda x: x.symbol, self.instruments))}] @ {self.exchanges}[{self.commissions}]"
114
151
 
115
152
 
153
+ def initial_balances(setup: SimulationSetup) -> dict[str, Balance]:
154
+ """Startup balance per exchange, each in that venue's own base currency."""
155
+ assert isinstance(setup.capital, dict)
156
+ return {
157
+ exchange: Balance(
158
+ exchange=exchange,
159
+ currency=setup.base_currencies[exchange],
160
+ total=capital,
161
+ free=capital,
162
+ locked=0.0,
163
+ )
164
+ for exchange, capital in setup.capital.items()
165
+ }
166
+
167
+
116
168
  # fmt: off
117
169
  @dataclass
118
170
  class SimulationDataConfig:
@@ -287,7 +339,9 @@ def find_instruments_and_exchanges(
287
339
  _instrs.append(instrument)
288
340
  _exchanges.append(resolved_exchange)
289
341
 
290
- return _instrs, list(set(_exchanges))
342
+ # sorted, not list(set(...)): string hashing is per-process randomized, and exchanges[0]
343
+ # decides the persisted scalar base_currency of a multi-venue run.
344
+ return _instrs, sorted(set(_exchanges))
291
345
 
292
346
 
293
347
  class _StructureSniffer:
@@ -345,7 +399,7 @@ def recognize_simulation_configuration(
345
399
  instruments: list[Instrument],
346
400
  exchanges: list[str],
347
401
  capital: float | dict[str, float],
348
- basic_currency: str,
402
+ basic_currency: str | dict[str, str],
349
403
  commissions: str | dict[str, str | None] | None,
350
404
  signal_timeframe: str,
351
405
  accurate_stop_orders_execution: bool,
@@ -640,7 +694,16 @@ def recognize_simulation_data_config(
640
694
  )
641
695
 
642
696
 
643
- _TRANSFERS_LOG_COLUMNS = ["transaction_id", "from_exchange", "to_exchange", "currency", "amount", "status"]
697
+ _TRANSFERS_LOG_COLUMNS = [
698
+ "transaction_id",
699
+ "from_exchange",
700
+ "to_exchange",
701
+ "currency",
702
+ "amount",
703
+ "status",
704
+ "to_currency",
705
+ "to_amount",
706
+ ]
644
707
 
645
708
 
646
709
  def collect_transfers_log(transfer_manager: ITransferManager | None) -> pd.DataFrame | None:
@@ -667,6 +730,8 @@ def collect_transfers_log(transfer_manager: ITransferManager | None) -> pd.DataF
667
730
  "currency": t.currency,
668
731
  "amount": t.amount,
669
732
  "status": str(t.status),
733
+ "to_currency": t.to_currency or t.currency,
734
+ "to_amount": t.to_amount if t.to_amount is not None else t.amount,
670
735
  }
671
736
  for t in transfers
672
737
  ]
@@ -155,7 +155,8 @@ class ConnectionManager:
155
155
  await asyncio.sleep(1)
156
156
  continue
157
157
  except (RateLimitExceeded, DDoSProtection) as e:
158
- # Rate limit hit — report to rate limiter if available and backoff
158
+ # WS 429/418 are IP actions that ban REST too, and a gate close is not a token debit,
159
+ # so the next header sync does not erase it — hence the REST pool.
159
160
  logger.warning(
160
161
  f"<yellow>{self._exchange_id}</yellow> Rate limited in {stream_name}: {e}"
161
162
  )