Qubx 1.1.3.dev5__tar.gz → 1.1.3.dev6__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 (303) hide show
  1. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/PKG-INFO +3 -1
  2. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/pyproject.toml +4 -0
  3. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/_version.py +2 -2
  4. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/config.py +2 -1
  5. qubx-1.1.3.dev6/src/qubx/control/__init__.py +15 -0
  6. qubx-1.1.3.dev6/src/qubx/control/builtin.py +566 -0
  7. qubx-1.1.3.dev6/src/qubx/control/decorator.py +114 -0
  8. qubx-1.1.3.dev6/src/qubx/control/executor.py +132 -0
  9. qubx-1.1.3.dev6/src/qubx/control/interfaces.py +27 -0
  10. qubx-1.1.3.dev6/src/qubx/control/server.py +152 -0
  11. qubx-1.1.3.dev6/src/qubx/control/types.py +38 -0
  12. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/context.py +42 -3
  13. qubx-1.1.3.dev6/src/qubx/health/__init__.py +4 -0
  14. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/runner.py +13 -9
  15. qubx-1.1.3.dev5/src/qubx/health/__init__.py +0 -5
  16. qubx-1.1.3.dev5/src/qubx/health/server.py +0 -67
  17. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/.gitignore +0 -0
  18. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/LICENSE +0 -0
  19. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/README.md +0 -0
  20. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/scripts/build.py +0 -0
  21. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/__init__.py +0 -0
  22. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/_nb_magic.py +0 -0
  23. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/__init__.py +0 -0
  24. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/account.py +0 -0
  25. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/broker.py +0 -0
  26. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/data.py +0 -0
  27. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/iteratedstream.py +0 -0
  28. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/management.py +0 -0
  29. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/ome.py +0 -0
  30. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/optimization.py +0 -0
  31. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/runner.py +0 -0
  32. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/sentinels.py +0 -0
  33. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/simulated_data.py +0 -0
  34. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/simulated_exchange.py +0 -0
  35. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/simulator.py +0 -0
  36. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/transfers.py +0 -0
  37. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/backtester/utils.py +0 -0
  38. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/__init__.py +0 -0
  39. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/commands.py +0 -0
  40. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/deploy.py +0 -0
  41. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/misc.py +0 -0
  42. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/release.py +0 -0
  43. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/resolver.py +0 -0
  44. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/s3.py +0 -0
  45. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/theme.py +0 -0
  46. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/tui.py +0 -0
  47. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/cli/user_config.py +0 -0
  48. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/__init__.py +0 -0
  49. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/__init__.py +0 -0
  50. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/account.py +0 -0
  51. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  52. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  53. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/broker.py +0 -0
  54. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  55. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/data.py +0 -0
  56. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  57. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  58. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  59. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  60. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  61. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  62. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  63. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  64. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
  65. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
  66. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  67. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  68. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  69. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  70. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  71. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
  72. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
  73. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
  74. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
  75. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/factory.py +0 -0
  76. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  77. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  78. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  79. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  80. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  81. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  82. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  83. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  84. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  85. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  86. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  87. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  88. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  89. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/utils.py +0 -0
  90. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  91. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/registry.py +0 -0
  92. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/tardis/data.py +0 -0
  93. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/connectors/tardis/utils.py +0 -0
  94. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/__init__.py +0 -0
  95. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/account.py +0 -0
  96. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/basics.py +0 -0
  97. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/detectors/__init__.py +0 -0
  98. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/detectors/delisting.py +0 -0
  99. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/detectors/stale.py +0 -0
  100. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/errors.py +0 -0
  101. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/exceptions.py +0 -0
  102. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/helpers.py +0 -0
  103. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/initializer.py +0 -0
  104. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/interfaces.py +0 -0
  105. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/loggers.py +0 -0
  106. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/lookups.py +0 -0
  107. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/metrics.py +0 -0
  108. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/mixins/__init__.py +0 -0
  109. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/mixins/market.py +0 -0
  110. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/mixins/processing.py +0 -0
  111. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/mixins/subscription.py +0 -0
  112. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/mixins/trading.py +0 -0
  113. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/mixins/universe.py +0 -0
  114. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/mixins/utils.py +0 -0
  115. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/series.pxd +0 -0
  116. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/series.pyi +0 -0
  117. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/series.pyx +0 -0
  118. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/utils.pyi +0 -0
  119. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/core/utils.pyx +0 -0
  120. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/__init__.py +0 -0
  121. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/cache.py +0 -0
  122. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/containers.py +0 -0
  123. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/guards.py +0 -0
  124. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/registry.py +0 -0
  125. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/storage.py +0 -0
  126. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/storages/ccxt.py +0 -0
  127. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/storages/csv.py +0 -0
  128. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/storages/handy.py +0 -0
  129. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/storages/multi.py +0 -0
  130. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/storages/questdb.py +0 -0
  131. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/storages/stub.py +0 -0
  132. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/storages/utils.py +0 -0
  133. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/data/transformers.py +0 -0
  134. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/emitters/__init__.py +0 -0
  135. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/emitters/base.py +0 -0
  136. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/emitters/composite.py +0 -0
  137. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/emitters/csv.py +0 -0
  138. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/emitters/indicator.py +0 -0
  139. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/emitters/inmemory.py +0 -0
  140. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/emitters/prometheus.py +0 -0
  141. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/emitters/questdb.py +0 -0
  142. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/__init__.py +0 -0
  143. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/composite.py +0 -0
  144. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/formatters/__init__.py +0 -0
  145. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/formatters/base.py +0 -0
  146. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/formatters/incremental.py +0 -0
  147. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/formatters/slack.py +0 -0
  148. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/formatters/target_position.py +0 -0
  149. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/redis_streams.py +0 -0
  150. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/exporters/slack.py +0 -0
  151. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/gathering/simplest.py +0 -0
  152. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/health/base.py +0 -0
  153. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/health/dummy.py +0 -0
  154. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/loggers/__init__.py +0 -0
  155. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/loggers/csv.py +0 -0
  156. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/loggers/factory.py +0 -0
  157. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/loggers/inmemory.py +0 -0
  158. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/loggers/mongo.py +0 -0
  159. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/loggers/postgres.py +0 -0
  160. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/notifications/__init__.py +0 -0
  161. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/notifications/composite.py +0 -0
  162. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/notifications/slack.py +0 -0
  163. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/notifications/throttler.py +0 -0
  164. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/pandaz/__init__.py +0 -0
  165. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/pandaz/stats.py +0 -0
  166. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/pandaz/ta.py +0 -0
  167. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/pandaz/utils.py +0 -0
  168. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/plugins/__init__.py +0 -0
  169. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/plugins/loader.py +0 -0
  170. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/_build.py +0 -0
  171. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/crypto-fees.ini +0 -0
  172. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  173. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  174. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  175. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  176. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  177. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  178. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  179. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  180. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  181. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  182. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  183. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restarts/__init__.py +0 -0
  184. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restarts/state_resolvers.py +0 -0
  185. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restarts/time_finders.py +0 -0
  186. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restorers/__init__.py +0 -0
  187. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restorers/balance.py +0 -0
  188. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restorers/factory.py +0 -0
  189. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restorers/interfaces.py +0 -0
  190. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restorers/position.py +0 -0
  191. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restorers/signal.py +0 -0
  192. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restorers/state.py +0 -0
  193. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/restorers/utils.py +0 -0
  194. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/state/__init__.py +0 -0
  195. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/state/dummy.py +0 -0
  196. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/state/redis.py +0 -0
  197. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/ta/__init__.py +0 -0
  198. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/ta/indicators.pxd +0 -0
  199. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/ta/indicators.pyi +0 -0
  200. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/ta/indicators.pyx +0 -0
  201. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/__init__.py +0 -0
  202. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/base.py +0 -0
  203. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  204. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/project/config.yml.j2 +0 -0
  205. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  206. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  207. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  208. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  209. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  210. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/project/template.yml +0 -0
  211. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
  212. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
  213. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
  214. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
  215. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
  216. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.gitignore.j2 +0 -0
  217. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.python-version +0 -0
  218. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.vscode/launch.json +0 -0
  219. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.vscode/settings.json +0 -0
  220. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
  221. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
  222. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/README.md.j2 +0 -0
  223. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/configs/.gitkeep +0 -0
  224. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/justfile.j2 +0 -0
  225. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
  226. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/research/.gitkeep +0 -0
  227. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  228. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
  229. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  230. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/template.yml +0 -0
  231. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
  232. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  233. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  234. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/simple/config.yml.j2 +0 -0
  235. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  236. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  237. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  238. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/templates/simple/template.yml +0 -0
  239. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/trackers/__init__.py +0 -0
  240. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/trackers/advanced.py +0 -0
  241. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/trackers/composite.py +0 -0
  242. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/trackers/rebalancers.py +0 -0
  243. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/trackers/riskctrl.py +0 -0
  244. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/trackers/sizers.py +0 -0
  245. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/__init__.py +0 -0
  246. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/_pyxreloader.py +0 -0
  247. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/charting/lookinglass.py +0 -0
  248. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  249. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/charting/orderbook.py +0 -0
  250. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/collections.py +0 -0
  251. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/hft/__init__.py +0 -0
  252. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/hft/numba_utils.py +0 -0
  253. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/hft/orderbook.pyi +0 -0
  254. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/hft/orderbook.pyx +0 -0
  255. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/marketdata/binance.py +0 -0
  256. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/marketdata/ccxt.py +0 -0
  257. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/marketdata/dukas.py +0 -0
  258. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/misc.py +0 -0
  259. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/nonce.py +0 -0
  260. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/ntp.py +0 -0
  261. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/numbers_utils.py +0 -0
  262. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/orderbook.py +0 -0
  263. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/plotting/__init__.py +0 -0
  264. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/plotting/dashboard.py +0 -0
  265. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/plotting/data.py +0 -0
  266. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/plotting/interfaces.py +0 -0
  267. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  268. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  269. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/questdb.py +0 -0
  270. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/rate_limiter.py +0 -0
  271. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/results.py +0 -0
  272. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/ringbuffer.pxd +0 -0
  273. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/ringbuffer.pyi +0 -0
  274. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/ringbuffer.pyx +0 -0
  275. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/__init__.py +0 -0
  276. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  277. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/accounts.py +0 -0
  278. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/configs.py +0 -0
  279. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/factory.py +0 -0
  280. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/kernel_service.py +0 -0
  281. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/__init__.py +0 -0
  282. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/app.py +0 -0
  283. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/handlers.py +0 -0
  284. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/init_code.py +0 -0
  285. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/kernel.py +0 -0
  286. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  287. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  288. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
  289. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  290. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  291. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  292. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  293. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  294. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  295. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/s3.py +0 -0
  296. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/slack.py +0 -0
  297. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/throttler.py +0 -0
  298. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/time.py +0 -0
  299. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/src/qubx/utils/websocket_manager.py +0 -0
  300. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  301. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
  302. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  303. {qubx-1.1.3.dev5 → qubx-1.1.3.dev6}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Qubx
3
- Version: 1.1.3.dev5
3
+ Version: 1.1.3.dev6
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
@@ -12,6 +12,7 @@ Requires-Dist: aiohttp<3.11,>=3.10.11
12
12
  Requires-Dist: click<9,>=8.1.7
13
13
  Requires-Dist: croniter<3,>=2.0.5
14
14
  Requires-Dist: duckdb>=1.0.0
15
+ Requires-Dist: fastapi<1,>=0.115.0
15
16
  Requires-Dist: gitpython<4,>=3.1.44
16
17
  Requires-Dist: importlib-metadata
17
18
  Requires-Dist: ipykernel<7,>=6.29.0
@@ -49,6 +50,7 @@ Requires-Dist: textual-serve<2,>=1.0.0
49
50
  Requires-Dist: textual[syntax]<7,>=6.0.0
50
51
  Requires-Dist: toml<1,>=0.10.2
51
52
  Requires-Dist: tqdm<5,>=4.66.0
53
+ Requires-Dist: uvicorn<1,>=0.34.0
52
54
  Requires-Dist: uvloop<1,>=0.22.1; sys_platform != 'win32'
53
55
  Requires-Dist: websockets==15.0.1
54
56
  Provides-Extra: binance
@@ -36,6 +36,9 @@ dependencies = [
36
36
  "aiohttp>=3.10.11,<3.11",
37
37
  "websockets==15.0.1",
38
38
  "uvloop>=0.22.1,<1; sys_platform != 'win32'",
39
+ # Control server
40
+ "fastapi>=0.115.0,<1",
41
+ "uvicorn>=0.34.0,<1",
39
42
  # Utilities
40
43
  "importlib-metadata",
41
44
  "croniter>=2.0.5,<3",
@@ -145,6 +148,7 @@ dev = [
145
148
  # Testing
146
149
  "pytest>=8.2.0,<9",
147
150
  "pytest-asyncio>=0.24.0,<1",
151
+ "httpx>=0.27.0,<1",
148
152
  "pytest-mock>=3.12.0,<4",
149
153
  "pytest-lazy-fixture>=0.6.3,<1",
150
154
  "pytest-cov>=4.1.0,<5",
@@ -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.1.3.dev5'
22
- __version_tuple__ = version_tuple = (1, 1, 3, 'dev5')
21
+ __version__ = version = '1.1.3.dev6'
22
+ __version_tuple__ = version_tuple = (1, 1, 3, 'dev6')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -91,7 +91,8 @@ class QubxSettings(BaseSettings):
91
91
  bot_id: str | None = None
92
92
  instance_id: str | None = None
93
93
  metrics_port: int | None = None
94
- health_port: int | None = None
94
+ control_port: int | None = None
95
+ health_port: int | None = None # deprecated: use control_port
95
96
  log_format: str = "text" # "text" or "json"
96
97
  default_browse_path: str | None = None
97
98
 
@@ -0,0 +1,15 @@
1
+ from .decorator import action, collect_actions, execute_decorated_action
2
+ from .interfaces import IControllable
3
+ from .server import ControlServer
4
+ from .types import ActionDef, ActionParam, ActionResult
5
+
6
+ __all__ = [
7
+ "IControllable",
8
+ "ActionDef",
9
+ "ActionParam",
10
+ "ActionResult",
11
+ "ControlServer",
12
+ "action",
13
+ "collect_actions",
14
+ "execute_decorated_action",
15
+ ]
@@ -0,0 +1,566 @@
1
+ """Built-in control actions available on every bot."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Callable
6
+
7
+ from qubx.core.basics import Signal
8
+
9
+ from .types import ActionDef, ActionParam, ActionResult
10
+
11
+ if TYPE_CHECKING:
12
+ from qubx.core.interfaces import IStrategyContext
13
+
14
+
15
+ # --- Rounding helpers ---
16
+
17
+
18
+ def _rm(v: float) -> float:
19
+ """Round money/dollar amounts to 2 decimals."""
20
+ return round(v, 2)
21
+
22
+
23
+ def _rl(v: float) -> float:
24
+ """Round leverage to 4 decimals."""
25
+ return round(v, 4)
26
+
27
+
28
+ def _rms(v: float) -> float:
29
+ """Round latency (ms) to 1 decimal."""
30
+ return round(v, 1)
31
+
32
+
33
+ def _get_universe(ctx: IStrategyContext, **kwargs) -> ActionResult:
34
+ instruments = ctx.instruments
35
+ return ActionResult(
36
+ status="ok",
37
+ data={"instruments": [str(i) for i in instruments], "count": len(instruments)},
38
+ )
39
+
40
+
41
+ def _add_instruments(ctx: IStrategyContext, symbols: list[str], **kwargs) -> ActionResult:
42
+ instruments = []
43
+ errors = []
44
+ for s in symbols:
45
+ instr = ctx.query_instrument(s)
46
+ if instr is not None:
47
+ instruments.append(instr)
48
+ else:
49
+ errors.append(s)
50
+
51
+ if errors:
52
+ return ActionResult(status="error", error=f"Unknown symbols: {errors}")
53
+
54
+ ctx.add_instruments(instruments)
55
+ return ActionResult(
56
+ status="ok",
57
+ data={"added": [str(i) for i in instruments], "universe_size": len(ctx.instruments)},
58
+ )
59
+
60
+
61
+ def _remove_instruments(ctx: IStrategyContext, symbols: list[str], if_has_position: str = "close", **kwargs) -> ActionResult:
62
+ instruments = []
63
+ errors = []
64
+ for s in symbols:
65
+ instr = ctx.query_instrument(s)
66
+ if instr is not None:
67
+ instruments.append(instr)
68
+ else:
69
+ errors.append(s)
70
+
71
+ if errors:
72
+ return ActionResult(status="error", error=f"Unknown symbols: {errors}")
73
+
74
+ ctx.remove_instruments(instruments, if_has_position_then=if_has_position)
75
+ return ActionResult(
76
+ status="ok",
77
+ data={"removed": [str(i) for i in instruments], "universe_size": len(ctx.instruments)},
78
+ )
79
+
80
+
81
+ def _get_positions(ctx: IStrategyContext, **kwargs) -> ActionResult:
82
+ positions = {}
83
+ for instr, pos in ctx.get_positions().items():
84
+ positions[str(instr)] = {
85
+ "quantity": pos.quantity,
86
+ "avg_price": pos.position_avg_price,
87
+ "market_price": pos.last_update_price,
88
+ "pnl": _rm(pos.pnl),
89
+ "realized_pnl": _rm(pos.r_pnl),
90
+ "market_value": _rm(pos.market_value_funds),
91
+ }
92
+ return ActionResult(status="ok", data={"positions": positions})
93
+
94
+
95
+ def _get_balances(ctx: IStrategyContext, **kwargs) -> ActionResult:
96
+ balances = {}
97
+ for bal in ctx.get_balances():
98
+ key = f"{bal.exchange}:{bal.currency}"
99
+ balances[key] = {"total": _rm(bal.total), "free": _rm(bal.free), "locked": _rm(bal.locked)}
100
+ return ActionResult(status="ok", data={"balances": balances})
101
+
102
+
103
+ def _get_orders(ctx: IStrategyContext, symbol: str | None = None, **kwargs) -> ActionResult:
104
+ orders_data = []
105
+ if symbol:
106
+ instr = ctx.query_instrument(symbol)
107
+ if instr is None:
108
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
109
+ orders = ctx.get_orders(instrument=instr)
110
+ else:
111
+ orders = ctx.get_orders()
112
+ for order_id, order in orders.items():
113
+ orders_data.append(
114
+ {
115
+ "id": order.id,
116
+ "instrument": str(order.instrument),
117
+ "type": order.type,
118
+ "side": order.side,
119
+ "quantity": order.quantity,
120
+ "price": order.price,
121
+ "status": order.status,
122
+ "client_id": order.client_id,
123
+ }
124
+ )
125
+ return ActionResult(status="ok", data={"orders": orders_data})
126
+
127
+
128
+ def _get_quote(ctx: IStrategyContext, symbol: str, **kwargs) -> ActionResult:
129
+ instr = ctx.query_instrument(symbol)
130
+ if instr is None:
131
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
132
+
133
+ quote = ctx.quote(instr)
134
+ if quote is None:
135
+ return ActionResult(status="error", error=f"No quote available for {symbol}")
136
+
137
+ import pandas as pd
138
+
139
+ return ActionResult(
140
+ status="ok",
141
+ data={
142
+ "bid": quote.bid,
143
+ "ask": quote.ask,
144
+ "bid_size": quote.bid_size,
145
+ "ask_size": quote.ask_size,
146
+ "time": str(pd.Timestamp(quote.time, unit="ns")),
147
+ },
148
+ )
149
+
150
+
151
+ def _get_ohlc(ctx: IStrategyContext, symbol: str, timeframe: str = "1h", length: int = 20, **kwargs) -> ActionResult:
152
+ instr = ctx.query_instrument(symbol)
153
+ if instr is None:
154
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
155
+
156
+ try:
157
+ df = ctx.ohlc_pd(instr, timeframe=timeframe, length=length)
158
+ if df is None or df.empty:
159
+ return ActionResult(status="ok", data={"bars": [], "count": 0})
160
+
161
+ bars = []
162
+ for ts, row in df.iterrows():
163
+ bars.append(
164
+ {
165
+ "time": str(ts),
166
+ "open": float(row.get("open", 0)),
167
+ "high": float(row.get("high", 0)),
168
+ "low": float(row.get("low", 0)),
169
+ "close": float(row.get("close", 0)),
170
+ "volume": float(row.get("volume", 0)),
171
+ }
172
+ )
173
+ return ActionResult(status="ok", data={"bars": bars, "count": len(bars)})
174
+ except Exception as e:
175
+ return ActionResult(status="error", error=str(e))
176
+
177
+
178
+ def _trade(ctx: IStrategyContext, symbol: str, amount: float, price: float | None = None, time_in_force: str = "gtc", **kwargs) -> ActionResult:
179
+ instr = ctx.query_instrument(symbol)
180
+ if instr is None:
181
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
182
+
183
+ try:
184
+ order = ctx.trade(instr, amount, price=price, time_in_force=time_in_force)
185
+ return ActionResult(
186
+ status="ok",
187
+ data={"order_id": order.id if order else None, "instrument": str(instr), "amount": amount},
188
+ )
189
+ except Exception as e:
190
+ return ActionResult(status="error", error=str(e))
191
+
192
+
193
+ def _set_target_position(ctx: IStrategyContext, symbol: str, target: float, price: float | None = None, **kwargs) -> ActionResult:
194
+ instr = ctx.query_instrument(symbol)
195
+ if instr is None:
196
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
197
+
198
+ positions = ctx.get_positions()
199
+ previous = positions[instr].quantity if instr in positions else 0.0
200
+
201
+ try:
202
+ ctx.set_target_position(instr, target, price=price)
203
+ return ActionResult(status="ok", data={"previous": previous, "new": target, "instrument": str(instr)})
204
+ except Exception as e:
205
+ return ActionResult(status="error", error=str(e))
206
+
207
+
208
+ def _close_position(ctx: IStrategyContext, symbol: str, **kwargs) -> ActionResult:
209
+ instr = ctx.query_instrument(symbol)
210
+ if instr is None:
211
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
212
+
213
+ positions = ctx.get_positions()
214
+ amount = positions[instr].quantity if instr in positions else 0.0
215
+
216
+ try:
217
+ ctx.close_position(instr)
218
+ return ActionResult(status="ok", data={"closed": True, "amount": amount, "instrument": str(instr)})
219
+ except Exception as e:
220
+ return ActionResult(status="error", error=str(e))
221
+
222
+
223
+ def _cancel_orders(ctx: IStrategyContext, symbol: str | None = None, **kwargs) -> ActionResult:
224
+ try:
225
+ if symbol:
226
+ instr = ctx.query_instrument(symbol)
227
+ if instr is None:
228
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
229
+ ctx.cancel_orders(instr)
230
+ else:
231
+ for instr in ctx.instruments:
232
+ ctx.cancel_orders(instr)
233
+ return ActionResult(status="ok", data={"cancelled": True})
234
+ except Exception as e:
235
+ return ActionResult(status="error", error=str(e))
236
+
237
+
238
+ def _emit_signal(ctx: IStrategyContext, symbol: str, signal_value: float, price: float | None = None, group: str = "", **kwargs) -> ActionResult:
239
+ instr = ctx.query_instrument(symbol)
240
+ if instr is None:
241
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
242
+
243
+ try:
244
+ sig = Signal(
245
+ time=ctx.time(),
246
+ instrument=instr,
247
+ signal=signal_value,
248
+ price=price,
249
+ group=group,
250
+ )
251
+ ctx.emit_signal(sig)
252
+ return ActionResult(status="ok", data={"emitted": True, "instrument": str(instr), "signal": signal_value})
253
+ except Exception as e:
254
+ return ActionResult(status="error", error=str(e))
255
+
256
+
257
+ def _get_state(ctx: IStrategyContext, **kwargs) -> ActionResult:
258
+ positions = {}
259
+ for instr, pos in ctx.get_positions().items():
260
+ positions[str(instr)] = {
261
+ "quantity": pos.quantity,
262
+ "avg_price": pos.position_avg_price,
263
+ "market_price": pos.last_update_price,
264
+ "pnl": _rm(pos.pnl),
265
+ }
266
+
267
+ return ActionResult(
268
+ status="ok",
269
+ data={
270
+ "total_capital": _rm(ctx.get_total_capital()),
271
+ "net_leverage": _rl(ctx.get_net_leverage()),
272
+ "gross_leverage": _rl(ctx.get_gross_leverage()),
273
+ "positions": positions,
274
+ "instruments": [str(i) for i in ctx.instruments],
275
+ "is_warmup": ctx.is_warmup_in_progress,
276
+ "is_simulation": ctx.is_simulation,
277
+ },
278
+ )
279
+
280
+
281
+ def _get_total_capital(ctx: IStrategyContext, **kwargs) -> ActionResult:
282
+ return ActionResult(
283
+ status="ok",
284
+ data={"total_capital": _rm(ctx.get_total_capital())},
285
+ )
286
+
287
+
288
+ def _get_leverages(ctx: IStrategyContext, **kwargs) -> ActionResult:
289
+ leverages = {}
290
+ for instr, lev in ctx.get_leverages().items():
291
+ leverages[str(instr)] = _rl(lev)
292
+ return ActionResult(
293
+ status="ok",
294
+ data={
295
+ "leverages": leverages,
296
+ "net": _rl(ctx.get_net_leverage()),
297
+ "gross": _rl(ctx.get_gross_leverage()),
298
+ },
299
+ )
300
+
301
+
302
+ def _get_subscriptions(ctx: IStrategyContext, symbol: str | None = None, **kwargs) -> ActionResult:
303
+ if symbol:
304
+ instr = ctx.query_instrument(symbol)
305
+ if instr is None:
306
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
307
+ subs = ctx.get_subscriptions(instr)
308
+ return ActionResult(status="ok", data={"subscriptions": {str(instr): subs}})
309
+
310
+ result = {}
311
+ for instr in ctx.instruments:
312
+ result[str(instr)] = ctx.get_subscriptions(instr)
313
+ return ActionResult(status="ok", data={"subscriptions": result})
314
+
315
+
316
+ def _set_universe(ctx: IStrategyContext, symbols: list[str], if_has_position: str = "close", **kwargs) -> ActionResult:
317
+ instruments = []
318
+ errors = []
319
+ for s in symbols:
320
+ instr = ctx.query_instrument(s)
321
+ if instr is not None:
322
+ instruments.append(instr)
323
+ else:
324
+ errors.append(s)
325
+
326
+ if errors:
327
+ return ActionResult(status="error", error=f"Unknown symbols: {errors}")
328
+
329
+ ctx.set_universe(instruments, if_has_position_then=if_has_position)
330
+ return ActionResult(
331
+ status="ok",
332
+ data={"instruments": [str(i) for i in instruments], "count": len(instruments)},
333
+ )
334
+
335
+
336
+ def _set_target_leverage(ctx: IStrategyContext, symbol: str, leverage: float, price: float | None = None, **kwargs) -> ActionResult:
337
+ instr = ctx.query_instrument(symbol)
338
+ if instr is None:
339
+ return ActionResult(status="error", error=f"Unknown symbol: {symbol}")
340
+
341
+ try:
342
+ ctx.set_target_leverage(instr, leverage, price=price)
343
+ return ActionResult(status="ok", data={"instrument": str(instr), "leverage": leverage})
344
+ except Exception as e:
345
+ return ActionResult(status="error", error=str(e))
346
+
347
+
348
+ def _close_positions(ctx: IStrategyContext, **kwargs) -> ActionResult:
349
+ try:
350
+ positions = {str(i): p.quantity for i, p in ctx.get_positions().items() if p.quantity != 0}
351
+ ctx.close_positions()
352
+ return ActionResult(status="ok", data={"closed": positions})
353
+ except Exception as e:
354
+ return ActionResult(status="error", error=str(e))
355
+
356
+
357
+ def _get_health(ctx: IStrategyContext, **kwargs) -> ActionResult:
358
+ health = ctx.health
359
+ exchanges = ctx.exchanges
360
+ connected = {}
361
+ latencies = {}
362
+ for exch in exchanges:
363
+ connected[exch] = health.is_connected(exch)
364
+ latencies[exch] = {k: _rms(v) for k, v in health.get_data_latencies(exch).items()}
365
+ return ActionResult(
366
+ status="ok",
367
+ data={
368
+ "connected": connected,
369
+ "queue_size": health.get_queue_size(),
370
+ "data_latencies_ms": latencies,
371
+ },
372
+ )
373
+
374
+
375
+ # --- Action registry ---
376
+
377
+ BUILTIN_ACTIONS: dict[str, tuple[ActionDef, Callable]] = {
378
+ "get_universe": (
379
+ ActionDef(name="get_universe", description="Get current trading universe", category="universe", read_only=True),
380
+ _get_universe,
381
+ ),
382
+ "add_instruments": (
383
+ ActionDef(
384
+ name="add_instruments",
385
+ description="Add instruments to the trading universe",
386
+ category="universe",
387
+ params=[ActionParam(name="symbols", type="array", description="List of symbol strings to add", items_type="string")],
388
+ ),
389
+ _add_instruments,
390
+ ),
391
+ "remove_instruments": (
392
+ ActionDef(
393
+ name="remove_instruments",
394
+ description="Remove instruments from the universe",
395
+ category="universe",
396
+ dangerous=True,
397
+ params=[
398
+ ActionParam(name="symbols", type="array", description="List of symbol strings to remove", items_type="string"),
399
+ ActionParam(name="if_has_position", type="string", description="Policy if instrument has position: close, wait_for_close, wait_for_change", required=False, default="close"),
400
+ ],
401
+ ),
402
+ _remove_instruments,
403
+ ),
404
+ "get_positions": (
405
+ ActionDef(name="get_positions", description="Get all current positions with PnL", category="diagnostics", read_only=True),
406
+ _get_positions,
407
+ ),
408
+ "get_balances": (
409
+ ActionDef(name="get_balances", description="Get account balances", category="diagnostics", read_only=True),
410
+ _get_balances,
411
+ ),
412
+ "get_orders": (
413
+ ActionDef(
414
+ name="get_orders",
415
+ description="Get open orders",
416
+ category="diagnostics",
417
+ read_only=True,
418
+ params=[ActionParam(name="symbol", type="string", description="Filter by symbol", required=False, default=None)],
419
+ ),
420
+ _get_orders,
421
+ ),
422
+ "get_quote": (
423
+ ActionDef(
424
+ name="get_quote",
425
+ description="Get latest quote for an instrument",
426
+ category="diagnostics",
427
+ read_only=True,
428
+ params=[ActionParam(name="symbol", type="string", description="Trading instrument symbol")],
429
+ ),
430
+ _get_quote,
431
+ ),
432
+ "get_ohlc": (
433
+ ActionDef(
434
+ name="get_ohlc",
435
+ description="Get recent OHLC bars",
436
+ category="diagnostics",
437
+ read_only=True,
438
+ params=[
439
+ ActionParam(name="symbol", type="string", description="Trading instrument symbol"),
440
+ ActionParam(name="timeframe", type="string", description="Bar timeframe", required=False, default="1h"),
441
+ ActionParam(name="length", type="integer", description="Number of bars", required=False, default=20),
442
+ ],
443
+ ),
444
+ _get_ohlc,
445
+ ),
446
+ "trade": (
447
+ ActionDef(
448
+ name="trade",
449
+ description="Place an order",
450
+ category="trading",
451
+ dangerous=True,
452
+ params=[
453
+ ActionParam(name="symbol", type="string", description="Trading instrument symbol"),
454
+ ActionParam(name="amount", type="number", description="Order amount (positive=buy, negative=sell)"),
455
+ ActionParam(name="price", type="number", description="Limit price", required=False, default=None),
456
+ ActionParam(name="time_in_force", type="string", description="Time in force", required=False, default="gtc"),
457
+ ],
458
+ ),
459
+ _trade,
460
+ ),
461
+ "set_target_position": (
462
+ ActionDef(
463
+ name="set_target_position",
464
+ description="Set target position for an instrument",
465
+ category="trading",
466
+ dangerous=True,
467
+ params=[
468
+ ActionParam(name="symbol", type="string", description="Trading instrument symbol"),
469
+ ActionParam(name="target", type="number", description="Target position size"),
470
+ ActionParam(name="price", type="number", description="Limit price", required=False, default=None),
471
+ ],
472
+ ),
473
+ _set_target_position,
474
+ ),
475
+ "close_position": (
476
+ ActionDef(
477
+ name="close_position",
478
+ description="Close position for an instrument",
479
+ category="trading",
480
+ dangerous=True,
481
+ params=[ActionParam(name="symbol", type="string", description="Trading instrument symbol")],
482
+ ),
483
+ _close_position,
484
+ ),
485
+ "cancel_orders": (
486
+ ActionDef(
487
+ name="cancel_orders",
488
+ description="Cancel open orders",
489
+ category="trading",
490
+ params=[ActionParam(name="symbol", type="string", description="Filter by symbol", required=False, default=None)],
491
+ ),
492
+ _cancel_orders,
493
+ ),
494
+ "emit_signal": (
495
+ ActionDef(
496
+ name="emit_signal",
497
+ description="Emit a trading signal",
498
+ category="trading",
499
+ dangerous=True,
500
+ params=[
501
+ ActionParam(name="symbol", type="string", description="Trading instrument symbol"),
502
+ ActionParam(name="signal_value", type="number", description="Signal value (target position)"),
503
+ ActionParam(name="price", type="number", description="Limit price", required=False, default=None),
504
+ ActionParam(name="group", type="string", description="Signal group", required=False, default=""),
505
+ ],
506
+ ),
507
+ _emit_signal,
508
+ ),
509
+ "get_state": (
510
+ ActionDef(name="get_state", description="Get full bot state dump", category="diagnostics", read_only=True),
511
+ _get_state,
512
+ ),
513
+ "get_health": (
514
+ ActionDef(name="get_health", description="Get health metrics", category="diagnostics", read_only=True),
515
+ _get_health,
516
+ ),
517
+ "get_total_capital": (
518
+ ActionDef(name="get_total_capital", description="Get total capital across all exchanges", category="diagnostics", read_only=True),
519
+ _get_total_capital,
520
+ ),
521
+ "get_leverages": (
522
+ ActionDef(name="get_leverages", description="Get per-instrument and portfolio leverage", category="diagnostics", read_only=True),
523
+ _get_leverages,
524
+ ),
525
+ "get_subscriptions": (
526
+ ActionDef(
527
+ name="get_subscriptions",
528
+ description="Get active data subscriptions",
529
+ category="diagnostics",
530
+ read_only=True,
531
+ params=[ActionParam(name="symbol", type="string", description="Filter by symbol", required=False, default=None)],
532
+ ),
533
+ _get_subscriptions,
534
+ ),
535
+ "set_universe": (
536
+ ActionDef(
537
+ name="set_universe",
538
+ description="Replace the entire trading universe",
539
+ category="universe",
540
+ dangerous=True,
541
+ params=[
542
+ ActionParam(name="symbols", type="array", description="List of symbol strings for the new universe", items_type="string"),
543
+ ActionParam(name="if_has_position", type="string", description="Policy for removed instruments with positions: close, wait_for_close, wait_for_change", required=False, default="close"),
544
+ ],
545
+ ),
546
+ _set_universe,
547
+ ),
548
+ "set_target_leverage": (
549
+ ActionDef(
550
+ name="set_target_leverage",
551
+ description="Set target leverage for an instrument",
552
+ category="trading",
553
+ dangerous=True,
554
+ params=[
555
+ ActionParam(name="symbol", type="string", description="Trading instrument symbol"),
556
+ ActionParam(name="leverage", type="number", description="Target leverage"),
557
+ ActionParam(name="price", type="number", description="Limit price", required=False, default=None),
558
+ ],
559
+ ),
560
+ _set_target_leverage,
561
+ ),
562
+ "close_positions": (
563
+ ActionDef(name="close_positions", description="Close all open positions", category="trading", dangerous=True),
564
+ _close_positions,
565
+ ),
566
+ }