Qubx 0.7.4__tar.gz → 0.7.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Qubx might be problematic. Click here for more details.

Files changed (273) hide show
  1. {qubx-0.7.4 → qubx-0.7.6}/PKG-INFO +1 -1
  2. {qubx-0.7.4 → qubx-0.7.6}/pyproject.toml +1 -1
  3. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/runner.py +9 -4
  4. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/account.py +28 -40
  5. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/broker.py +8 -14
  6. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/parsers.py +7 -31
  7. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/websocket.py +0 -2
  8. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/account.py +4 -3
  9. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/context.py +23 -13
  10. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/interfaces.py +12 -2
  11. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/mixins/processing.py +4 -0
  12. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/mixins/trading.py +6 -3
  13. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/series.pyi +1 -0
  14. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/series.pyx +43 -27
  15. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/slack.py +27 -3
  16. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/notifications/slack.py +20 -21
  17. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restarts/state_resolvers.py +1 -0
  18. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/ta/indicators.pxd +59 -1
  19. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/ta/indicators.pyi +13 -0
  20. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/ta/indicators.pyx +214 -18
  21. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/runner.py +3 -2
  22. qubx-0.7.6/src/qubx/utils/slack.py +339 -0
  23. qubx-0.7.4/src/qubx/utils/slack.py +0 -177
  24. {qubx-0.7.4 → qubx-0.7.6}/LICENSE +0 -0
  25. {qubx-0.7.4 → qubx-0.7.6}/README.md +0 -0
  26. {qubx-0.7.4 → qubx-0.7.6}/build.py +0 -0
  27. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/__init__.py +0 -0
  28. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/_nb_magic.py +0 -0
  29. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/__init__.py +0 -0
  30. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/account.py +0 -0
  31. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/broker.py +0 -0
  32. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/data.py +0 -0
  33. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/management.py +0 -0
  34. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/ome.py +0 -0
  35. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/optimization.py +0 -0
  36. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/sentinels.py +0 -0
  37. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/simulated_data.py +0 -0
  38. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/simulated_exchange.py +0 -0
  39. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/simulator.py +0 -0
  40. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/transfers.py +0 -0
  41. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/backtester/utils.py +0 -0
  42. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/cli/__init__.py +0 -0
  43. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/cli/commands.py +0 -0
  44. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/cli/deploy.py +0 -0
  45. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/cli/misc.py +0 -0
  46. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/cli/release.py +0 -0
  47. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/cli/tui.py +0 -0
  48. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/__init__.py +0 -0
  49. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/account.py +0 -0
  50. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  51. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  52. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/broker.py +0 -0
  53. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  54. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/data.py +0 -0
  55. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  56. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  57. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  58. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  59. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  60. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  61. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  62. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  63. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  64. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  65. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  66. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  67. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  68. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/factory.py +0 -0
  69. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  70. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  71. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  72. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  73. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  74. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  75. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  76. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  77. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  78. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  79. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/reader.py +0 -0
  80. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  81. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  82. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  83. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/utils.py +0 -0
  84. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  85. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/tardis/data.py +0 -0
  86. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/tardis/utils.py +0 -0
  87. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/__init__.py +0 -0
  88. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/client.py +0 -0
  89. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/constants.py +0 -0
  90. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/data.py +0 -0
  91. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/extensions.py +0 -0
  92. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/factory.py +0 -0
  93. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/handlers/__init__.py +0 -0
  94. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/handlers/base.py +0 -0
  95. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/handlers/orderbook.py +0 -0
  96. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/handlers/quote.py +0 -0
  97. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/handlers/stats.py +0 -0
  98. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/handlers/trades.py +0 -0
  99. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/instruments.py +0 -0
  100. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/nonce.py +0 -0
  101. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/rate_limits.py +0 -0
  102. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/reader.py +0 -0
  103. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/connectors/xlighter/utils.py +0 -0
  104. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/__init__.py +0 -0
  105. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/basics.py +0 -0
  106. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/deque.py +0 -0
  107. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/detectors/__init__.py +0 -0
  108. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/detectors/delisting.py +0 -0
  109. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/detectors/stale.py +0 -0
  110. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/errors.py +0 -0
  111. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/exceptions.py +0 -0
  112. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/helpers.py +0 -0
  113. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/initializer.py +0 -0
  114. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/loggers.py +0 -0
  115. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/lookups.py +0 -0
  116. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/metrics.py +0 -0
  117. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/mixins/__init__.py +0 -0
  118. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/mixins/market.py +0 -0
  119. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/mixins/subscription.py +0 -0
  120. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/mixins/universe.py +0 -0
  121. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/mixins/utils.py +0 -0
  122. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/series.pxd +0 -0
  123. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/utils.pyi +0 -0
  124. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/core/utils.pyx +0 -0
  125. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/__init__.py +0 -0
  126. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/composite.py +0 -0
  127. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/containers.py +0 -0
  128. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/helpers.py +0 -0
  129. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/hft.py +0 -0
  130. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/readers.py +0 -0
  131. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/registry.py +0 -0
  132. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/storage.py +0 -0
  133. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/storages/csv.py +0 -0
  134. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/storages/questdb.py +0 -0
  135. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/storages/utils.py +0 -0
  136. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/tardis.py +0 -0
  137. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/data/transformers.py +0 -0
  138. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/emitters/__init__.py +0 -0
  139. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/emitters/base.py +0 -0
  140. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/emitters/composite.py +0 -0
  141. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/emitters/csv.py +0 -0
  142. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/emitters/indicator.py +0 -0
  143. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/emitters/inmemory.py +0 -0
  144. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/emitters/prometheus.py +0 -0
  145. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/emitters/questdb.py +0 -0
  146. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/__init__.py +0 -0
  147. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/composite.py +0 -0
  148. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/formatters/__init__.py +0 -0
  149. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/formatters/base.py +0 -0
  150. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/formatters/incremental.py +0 -0
  151. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/formatters/slack.py +0 -0
  152. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/formatters/target_position.py +0 -0
  153. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/exporters/redis_streams.py +0 -0
  154. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/features/__init__.py +0 -0
  155. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/features/core.py +0 -0
  156. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/features/orderbook.py +0 -0
  157. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/features/price.py +0 -0
  158. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/features/trades.py +0 -0
  159. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/features/utils.py +0 -0
  160. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/gathering/simplest.py +0 -0
  161. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/health/__init__.py +0 -0
  162. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/health/base.py +0 -0
  163. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/loggers/__init__.py +0 -0
  164. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/loggers/csv.py +0 -0
  165. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/loggers/factory.py +0 -0
  166. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/loggers/inmemory.py +0 -0
  167. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/loggers/mongo.py +0 -0
  168. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/math/__init__.py +0 -0
  169. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/math/stats.py +0 -0
  170. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/notifications/__init__.py +0 -0
  171. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/notifications/composite.py +0 -0
  172. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/notifications/throttler.py +0 -0
  173. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/pandaz/__init__.py +0 -0
  174. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/pandaz/ta.py +0 -0
  175. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/pandaz/utils.py +0 -0
  176. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/_build.py +0 -0
  177. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/crypto-fees.ini +0 -0
  178. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  179. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  180. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  181. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  182. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  183. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  184. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  185. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  186. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  187. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  188. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  189. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restarts/__init__.py +0 -0
  190. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restarts/time_finders.py +0 -0
  191. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restorers/__init__.py +0 -0
  192. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restorers/balance.py +0 -0
  193. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restorers/factory.py +0 -0
  194. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restorers/interfaces.py +0 -0
  195. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restorers/position.py +0 -0
  196. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restorers/signal.py +0 -0
  197. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restorers/state.py +0 -0
  198. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/restorers/utils.py +0 -0
  199. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/ta/__init__.py +0 -0
  200. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/__init__.py +0 -0
  201. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/base.py +0 -0
  202. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  203. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/project/config.yml.j2 +0 -0
  204. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  205. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  206. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  207. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  208. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  209. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/project/template.yml +0 -0
  210. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  211. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  212. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/simple/config.yml.j2 +0 -0
  213. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  214. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  215. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  216. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/templates/simple/template.yml +0 -0
  217. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/trackers/__init__.py +0 -0
  218. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/trackers/advanced.py +0 -0
  219. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/trackers/composite.py +0 -0
  220. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/trackers/rebalancers.py +0 -0
  221. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/trackers/riskctrl.py +0 -0
  222. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/trackers/sizers.py +0 -0
  223. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/__init__.py +0 -0
  224. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/_pyxreloader.py +0 -0
  225. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/charting/lookinglass.py +0 -0
  226. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  227. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/charting/orderbook.py +0 -0
  228. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/collections.py +0 -0
  229. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/hft/__init__.py +0 -0
  230. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/hft/numba_utils.py +0 -0
  231. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/hft/orderbook.pyi +0 -0
  232. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/hft/orderbook.pyx +0 -0
  233. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/marketdata/binance.py +0 -0
  234. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/marketdata/ccxt.py +0 -0
  235. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/marketdata/dukas.py +0 -0
  236. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/misc.py +0 -0
  237. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/nonce.py +0 -0
  238. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/ntp.py +0 -0
  239. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/numbers_utils.py +0 -0
  240. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/orderbook.py +0 -0
  241. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/plotting/__init__.py +0 -0
  242. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/plotting/dashboard.py +0 -0
  243. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/plotting/data.py +0 -0
  244. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/plotting/interfaces.py +0 -0
  245. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  246. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  247. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/questdb.py +0 -0
  248. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/rate_limiter.py +0 -0
  249. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/ringbuffer.pxd +0 -0
  250. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/ringbuffer.pyi +0 -0
  251. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/ringbuffer.pyx +0 -0
  252. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/__init__.py +0 -0
  253. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  254. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/accounts.py +0 -0
  255. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/configs.py +0 -0
  256. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/factory.py +0 -0
  257. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/kernel_service.py +0 -0
  258. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/__init__.py +0 -0
  259. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/app.py +0 -0
  260. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/handlers.py +0 -0
  261. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/init_code.py +0 -0
  262. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/kernel.py +0 -0
  263. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  264. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  265. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  266. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  267. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  268. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  269. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  270. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  271. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/time.py +0 -0
  272. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/version.py +0 -0
  273. {qubx-0.7.4 → qubx-0.7.6}/src/qubx/utils/websocket_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Qubx
3
- Version: 0.7.4
3
+ Version: 0.7.6
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  License-File: LICENSE
6
6
  Author: Dmitry Marienko
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "Qubx"
7
- version = "0.7.4"
7
+ version = "0.7.6"
8
8
  description = "Qubx - Quantitative Trading Framework"
9
9
  authors = [ "Dmitry Marienko <dmitry.marienko@xlydian.com>", "Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",]
10
10
  readme = "README.md"
@@ -1,4 +1,4 @@
1
- from typing import Any
1
+ from typing import Any, cast
2
2
 
3
3
  import numpy as np
4
4
  import pandas as pd
@@ -19,6 +19,7 @@ from qubx.core.interfaces import (
19
19
  IMetricEmitter,
20
20
  IStrategy,
21
21
  IStrategyContext,
22
+ IStrategyNotifier,
22
23
  ITimeProvider,
23
24
  StrategyState,
24
25
  )
@@ -59,6 +60,7 @@ class SimulationRunner:
59
60
  portfolio_log_freq: str
60
61
  ctx: IStrategyContext
61
62
  logs_writer: InMemoryLogsWriter
63
+ notifier: IStrategyNotifier | None
62
64
 
63
65
  account: CompositeAccountProcessor
64
66
  channel: CtrlChannel
@@ -85,6 +87,7 @@ class SimulationRunner:
85
87
  emitter: IMetricEmitter | None = None,
86
88
  strategy_state: StrategyState | None = None,
87
89
  initializer: BasicStrategyInitializer | None = None,
90
+ notifier: IStrategyNotifier | None = None,
88
91
  warmup_mode: bool = False,
89
92
  ):
90
93
  """
@@ -101,11 +104,12 @@ class SimulationRunner:
101
104
  """
102
105
  self.setup = setup
103
106
  self.data_config = data_config
104
- self.start = pd.Timestamp(start)
105
- self.stop = pd.Timestamp(stop)
107
+ self.start = cast(pd.Timestamp, pd.Timestamp(start))
108
+ self.stop = cast(pd.Timestamp, pd.Timestamp(stop))
106
109
  self.account_id = account_id
107
110
  self.portfolio_log_freq = portfolio_log_freq
108
111
  self.emitter = emitter
112
+ self.notifier = notifier
109
113
  self.strategy_state = strategy_state if strategy_state is not None else StrategyState()
110
114
  self.initializer = initializer
111
115
  self.warmup_mode = warmup_mode
@@ -282,7 +286,7 @@ class SimulationRunner:
282
286
  else:
283
287
  _run = self._process_strategy
284
288
 
285
- start, stop = pd.Timestamp(start), pd.Timestamp(stop)
289
+ start, stop = cast(pd.Timestamp, pd.Timestamp(start)), cast(pd.Timestamp, pd.Timestamp(stop))
286
290
  total_duration = stop - start
287
291
  update_delta = total_duration / 100
288
292
  prev_dt = np.datetime64(start)
@@ -474,6 +478,7 @@ class SimulationRunner:
474
478
  aux_data_provider=self._aux_data_reader,
475
479
  emitter=self.emitter,
476
480
  strategy_state=self.strategy_state,
481
+ notifier=self.notifier,
477
482
  initializer=self.initializer,
478
483
  )
479
484
 
@@ -202,15 +202,6 @@ class LighterAccountProcessor(BasicAccountProcessor):
202
202
  # Add fee to position's commission tracking
203
203
  position.commissions += deal.fee_amount
204
204
 
205
- self.__debug(
206
- f"Tracked fee for {instrument.symbol}: {deal.fee_amount:.6f} {deal.fee_currency} "
207
- f"(total commissions: {position.commissions:.6f})"
208
- )
209
-
210
- self.__debug(
211
- f"Processed {len(deals)} deal(s) for {instrument.symbol} - fees tracked, positions synced from account_all"
212
- )
213
-
214
205
  def process_order(self, order: Order, update_locked_value: bool = True) -> None:
215
206
  """
216
207
  Override process_order to handle Lighter's server-assigned order IDs.
@@ -228,8 +219,7 @@ class LighterAccountProcessor(BasicAccountProcessor):
228
219
  if order.client_id and order.client_id in self._active_orders:
229
220
  # Get the existing order stored under client_id
230
221
  existing_order = self._active_orders[order.client_id]
231
-
232
- self.__debug(f"Migrating order: client_id={order.client_id} → server_id={order.id}")
222
+ # self.__debug(f"Migrating order: client_id={order.client_id} → server_id={order.id}")
233
223
 
234
224
  # Remove from old location
235
225
  self._active_orders.pop(order.client_id)
@@ -383,10 +373,13 @@ class LighterAccountProcessor(BasicAccountProcessor):
383
373
  if position.last_update_price == 0 or np.isnan(position.last_update_price):
384
374
  position.last_update_price = pos_state.avg_entry_price
385
375
 
386
- self.__debug(
387
- f"Synced position: {instrument.symbol} = {pos_state.quantity:+.4f} "
388
- f"@ avg_price={pos_state.avg_entry_price:.4f}"
389
- )
376
+ updated_positions_str = "\n\t".join(
377
+ [
378
+ f"{instrument.symbol} --> {pos_state.quantity:+.4f} @ {pos_state.avg_entry_price:.4f}"
379
+ for instrument, pos_state in position_states.items()
380
+ ]
381
+ )
382
+ self.__debug(f"Updated positions:\n\t{updated_positions_str}")
390
383
 
391
384
  if not self._account_positions_initialized:
392
385
  self._account_positions_initialized = True
@@ -399,10 +392,10 @@ class LighterAccountProcessor(BasicAccountProcessor):
399
392
  # False means not a snapshot
400
393
  self.channel.send((instrument, "deals", [deal], False))
401
394
 
402
- self.__debug(
403
- f"Sent deal: {instrument.symbol} {deal.amount:+.4f} @ {deal.price:.4f} "
404
- f"fee={deal.fee_amount:.6f} (id={deal.id})"
405
- )
395
+ # self.__debug(
396
+ # f"Sent deal: {instrument.symbol} {deal.amount:+.4f} @ {deal.price:.4f} "
397
+ # f"fee={deal.fee_amount:.6f} (id={deal.id})"
398
+ # )
406
399
 
407
400
  # Funding payments are handled by the data provider, so I commented them here to avoid double sending
408
401
  # Send funding payments through channel
@@ -419,27 +412,19 @@ class LighterAccountProcessor(BasicAccountProcessor):
419
412
  logger.exception(e)
420
413
 
421
414
  async def _handle_account_all_orders_message(self, message: dict):
422
- """
423
- Handle account_all_orders WebSocket messages.
424
-
425
- Parses order updates and sends Order objects through channel.
426
-
427
- Follows CCXT pattern: parse → send through channel → framework handles rest.
428
- """
429
415
  try:
430
- # Parse message into list of (Instrument, Order) tuples
431
416
  orders = parse_account_all_orders_message(message, self.instrument_loader)
432
417
 
433
- # Send each order through channel (CCXT pattern)
434
- for instrument, order in orders:
435
- # Send: (instrument, "order", order, False)
436
- # False means not a snapshot
437
- self.channel.send((instrument, "order", order, False))
418
+ _d_msg = "\n\t".join([f"{order.id} ({order.instrument.symbol})" for order in orders])
419
+ logger.debug(f"Received {len(orders)} orders: \n\t{_d_msg}")
420
+
421
+ for order in orders:
422
+ self.channel.send((order.instrument, "order", order, False))
438
423
 
439
- self.__debug(
440
- f"Sent order: {instrument.symbol} {order.side} {order.quantity:+.4f} @ {order.price:.4f} "
441
- f"[{order.status}] (order_id={order.id})"
442
- )
424
+ # self.__debug(
425
+ # f"Sent order: {instrument.symbol} {order.side} {order.quantity:+.4f} @ {order.price:.4f} "
426
+ # f"[{order.status}] (order_id={order.id})"
427
+ # )
443
428
 
444
429
  except Exception as e:
445
430
  self.__error(f"Error handling account_all_orders message: {e}")
@@ -459,10 +444,13 @@ class LighterAccountProcessor(BasicAccountProcessor):
459
444
  for currency, balance in balances.items():
460
445
  self.update_balance(currency, balance.total, balance.locked)
461
446
 
462
- self.__debug(
463
- f"Updated balance - {currency}: total={balance.total:.2f}, "
464
- f"free={balance.free:.2f}, locked={balance.locked:.2f}"
465
- )
447
+ updated_balances_str = "\n\t".join(
448
+ [
449
+ f"{currency} --> {balance.total:.2f} (free={balance.free:.2f}, locked={balance.locked:.2f})"
450
+ for currency, balance in balances.items()
451
+ ]
452
+ )
453
+ self.__debug(f"Updated balances:\n\t{updated_balances_str}")
466
454
 
467
455
  if not self._account_stats_initialized:
468
456
  self._account_stats_initialized = True
@@ -255,11 +255,6 @@ class LighterBroker(IBroker):
255
255
  else:
256
256
  price = mid_price * (1 - max_slippage)
257
257
 
258
- logger.debug(
259
- f"Market order slippage protection: mid={mid_price:.4f}, "
260
- f"slippage={max_slippage * 100:.1f}%, protected_price={price:.4f}"
261
- )
262
-
263
258
  except Exception as e:
264
259
  raise InvalidOrderParameters(
265
260
  f"Failed to calculate market order price for {instrument.symbol}: {e}"
@@ -329,7 +324,6 @@ class LighterBroker(IBroker):
329
324
  # This makes it available for cancellation before WebSocket updates arrive
330
325
  self.account.process_order(order)
331
326
 
332
- logger.info(f"Order submitted via WebSocket: {order_id} ({client_id})")
333
327
  return order
334
328
 
335
329
  except Exception as e:
@@ -370,7 +364,7 @@ class LighterBroker(IBroker):
370
364
  return abs(hash(order.id)) % (2**56)
371
365
 
372
366
  async def _cancel_order(self, order: Order) -> bool:
373
- logger.info(f"Canceling order: {order.id}")
367
+ logger.info(f"[{order.instrument}] Canceling order @ {order.price} {order.side} {order.quantity} [{order.id}]")
374
368
 
375
369
  try:
376
370
  market_id = self.instrument_loader.get_market_id(order.instrument.symbol)
@@ -388,7 +382,6 @@ class LighterBroker(IBroker):
388
382
  return False
389
383
 
390
384
  await self.ws_manager.send_tx(tx_type=TX_TYPE_CANCEL_ORDER, tx_info=tx_info, tx_id=f"cancel_{order.id}")
391
- logger.info(f"Order cancellation submitted via WebSocket: {order.id}")
392
385
  return True
393
386
 
394
387
  except Exception as e:
@@ -408,7 +401,9 @@ class LighterBroker(IBroker):
408
401
  base_amount_int = int(amount * (10**instrument.size_precision))
409
402
  price_int = int(price * (10**instrument.price_precision))
410
403
 
411
- logger.debug(f"Modify order {order.id}: amount={order.quantity} → {amount}, price={order.price} → {price}")
404
+ logger.debug(
405
+ f"[{order.instrument.symbol}] :: Modifying order {order.id}: amount={order.quantity} → {amount}, price={order.price} → {price}"
406
+ )
412
407
 
413
408
  # Step 1: Sign modification transaction locally
414
409
  signer = self.client.signer_client
@@ -442,7 +437,6 @@ class LighterBroker(IBroker):
442
437
  options=order.options,
443
438
  )
444
439
 
445
- logger.info(f"Order modification submitted via WebSocket: {order.id}")
446
440
  return updated_order
447
441
 
448
442
  except Exception as e:
@@ -572,10 +566,10 @@ class LighterBroker(IBroker):
572
566
  else:
573
567
  price = mid_price * (1 - max_slippage)
574
568
 
575
- logger.debug(
576
- f"Market order slippage protection (batch): mid={mid_price:.4f}, "
577
- f"slippage={max_slippage * 100:.1f}%, protected_price={price:.4f}"
578
- )
569
+ # logger.debug(
570
+ # f"Market order slippage protection (batch): mid={mid_price:.4f}, "
571
+ # f"slippage={max_slippage * 100:.1f}%, protected_price={price:.4f}"
572
+ # )
579
573
 
580
574
  except Exception as e:
581
575
  raise InvalidOrderParameters(
@@ -185,7 +185,7 @@ def parse_account_tx_message(
185
185
 
186
186
  def parse_account_all_orders_message(
187
187
  message: dict[str, Any], instrument_loader: LighterInstrumentLoader
188
- ) -> list[tuple[Instrument, Order]]:
188
+ ) -> list[Order]:
189
189
  """
190
190
  Parse account_all_orders WebSocket message into list of Orders.
191
191
 
@@ -220,24 +220,8 @@ def parse_account_all_orders_message(
220
220
  }
221
221
  }
222
222
  ```
223
-
224
- Args:
225
- message: Raw WebSocket message from account_all_orders channel
226
- instrument_loader: Loader with market_id -> Instrument mapping
227
-
228
- Returns:
229
- List of (Instrument, Order) tuples. Empty list if:
230
- - Message is subscription confirmation (type="subscribed/account_all_orders")
231
- - No orders in message
232
- - Orders dict is empty
233
-
234
- Note:
235
- - Order.quantity is signed: positive for BUY, negative for SELL
236
- - Order.quantity represents the original order size (initial_base_amount)
237
- - Filled/remaining amounts are stored in Order.options dict
238
- - Lighter order statuses map to Qubx OrderStatus literals
239
223
  """
240
- orders: list[tuple[Instrument, Order]] = []
224
+ orders = []
241
225
 
242
226
  # Get orders dict (keyed by market_index)
243
227
  orders_by_market = message.get("orders", {})
@@ -262,7 +246,7 @@ def parse_account_all_orders_message(
262
246
  for order_data in market_orders:
263
247
  try:
264
248
  order = _parse_lighter_order(order_data, instrument)
265
- orders.append((instrument, order))
249
+ orders.append(order)
266
250
  except Exception as e:
267
251
  logger.error(f"Failed to parse order: {e}")
268
252
  continue
@@ -291,9 +275,9 @@ def _parse_lighter_order(order_data: dict[str, Any], instrument: Instrument) ->
291
275
  client_order_id = str(client_order_id)
292
276
 
293
277
  # Amounts
294
- initial_amount = float(order_data.get("initial_base_amount", "0"))
295
- remaining_amount = float(order_data.get("remaining_base_amount", "0"))
296
- filled_amount = float(order_data.get("filled_base_amount", "0"))
278
+ initial_amount = abs(float(order_data.get("initial_base_amount", "0")))
279
+ remaining_amount = abs(float(order_data.get("remaining_base_amount", "0")))
280
+ filled_amount = abs(float(order_data.get("filled_base_amount", "0")))
297
281
 
298
282
  # Price (may be "0.0000" for market orders)
299
283
  price_str = order_data.get("price", "0")
@@ -343,10 +327,6 @@ def _parse_lighter_order(order_data: dict[str, Any], instrument: Instrument) ->
343
327
  # Use updated_at as order time (most recent)
344
328
  timestamp = pd.Timestamp(updated_at, unit="s").asm8
345
329
 
346
- # Calculate cost (filled_amount * avg_price)
347
- # For simplicity, use order price as avg price
348
- cost = filled_amount * price if price > 0 else 0
349
-
350
330
  # Build options dict with additional info
351
331
  options = {
352
332
  "initial_amount": initial_amount,
@@ -363,21 +343,17 @@ def _parse_lighter_order(order_data: dict[str, Any], instrument: Instrument) ->
363
343
  if trigger_price_str and trigger_price_str != "0.0000":
364
344
  options["trigger_price"] = float(trigger_price_str)
365
345
 
366
- # Signed quantity: positive for BUY, negative for SELL
367
- quantity = initial_amount if side == "BUY" else -initial_amount
368
-
369
346
  return Order(
370
347
  id=order_id,
371
348
  type=order_type,
372
349
  instrument=instrument,
373
350
  time=timestamp,
374
- quantity=quantity,
351
+ quantity=remaining_amount,
375
352
  price=price,
376
353
  side=side,
377
354
  status=status,
378
355
  time_in_force=time_in_force,
379
356
  client_id=client_order_id,
380
- cost=cost,
381
357
  options=options,
382
358
  )
383
359
 
@@ -148,7 +148,6 @@ class LighterWebSocketManager(BaseWebSocketManager):
148
148
  "data": {"id": tx_id, "tx_type": tx_type, "tx_info": tx_info_dict},
149
149
  }
150
150
 
151
- logger.debug(f"Sending transaction via WebSocket: type={tx_type}, id={tx_id}")
152
151
  await self.send(message)
153
152
 
154
153
  # Wait for response (next message should be the tx response)
@@ -202,7 +201,6 @@ class LighterWebSocketManager(BaseWebSocketManager):
202
201
  "data": {"id": batch_id, "tx_types": tx_types, "tx_infos": tx_infos_dicts},
203
202
  }
204
203
 
205
- logger.info(f"Sending transaction batch via WebSocket: count={len(tx_types)}, id={batch_id}")
206
204
  await self.send(message)
207
205
 
208
206
  # Return confirmation
@@ -246,9 +246,10 @@ class BasicAccountProcessor(IAccountProcessor):
246
246
  else:
247
247
  self._active_orders[order.id] = order
248
248
 
249
- # - calculate amount locked by this order
250
- if update_locked_value and order.type == "LIMIT":
251
- self._lock_limit_order_value(self._active_orders[order.id])
249
+ if order.id in self._active_orders:
250
+ # - calculate amount locked by this order
251
+ if update_locked_value and order.type == "LIMIT":
252
+ self._lock_limit_order_value(self._active_orders[order.id])
252
253
 
253
254
  if _closed or _cancel:
254
255
  # TODO: (LIVE) WE NEED TO THINK HOW TO CLEANUP THIS COLLECTION !!!! -> @DM
@@ -314,6 +314,10 @@ class StrategyContext(IStrategyContext):
314
314
  logger.info(f"[StrategyContext] :: Received {sig_name} signal - initiating graceful shutdown")
315
315
  self.stop()
316
316
 
317
+ @property
318
+ def strategy_name(self) -> str:
319
+ return self._strategy_name or self.strategy.__class__.__name__
320
+
317
321
  def start(self, blocking: bool = False):
318
322
  if self._is_initialized:
319
323
  raise ValueError("Strategy is already started !")
@@ -333,17 +337,6 @@ class StrategyContext(IStrategyContext):
333
337
  self._atexit_registered = True
334
338
  logger.debug("[StrategyContext] :: Registered atexit handler")
335
339
 
336
- # Notify strategy start
337
- if self._notifier:
338
- try:
339
- self._notifier.notify_start(
340
- {
341
- "Instruments": [str(i) for i in self._initial_instruments],
342
- },
343
- )
344
- except Exception as e:
345
- logger.error(f"[StrategyContext] :: Failed to notify strategy start: {e}")
346
-
347
340
  # - run cron scheduler
348
341
  self._scheduler.run()
349
342
 
@@ -365,6 +358,21 @@ class StrategyContext(IStrategyContext):
365
358
  open_positions = {k: p for k, p in self.get_positions().items() if p.is_open()}
366
359
  self._initial_instruments = list(set(open_positions.keys()) | set(self._initial_instruments))
367
360
 
361
+ # Notify strategy start
362
+ if self._notifier and not self.is_simulation:
363
+ try:
364
+ self._notifier.notify_start(
365
+ {
366
+ "Exchanges": "|".join(self.exchanges),
367
+ "Total Capital": f"${self.get_total_capital():,.0f}",
368
+ "Net Leverage": f"{self.get_net_leverage():.1%}",
369
+ "Open Positions": len(open_positions),
370
+ "Instruments": len(self._initial_instruments),
371
+ },
372
+ )
373
+ except Exception as e:
374
+ logger.error(f"[StrategyContext] :: Failed to notify strategy start: {e}")
375
+
368
376
  # - update universe with initial instruments after the strategy is initialized
369
377
  self.set_universe(self._initial_instruments, skip_callback=True)
370
378
 
@@ -402,7 +410,7 @@ class StrategyContext(IStrategyContext):
402
410
  # These are the most important callbacks that must always run
403
411
 
404
412
  # Notify strategy stop
405
- if self._notifier:
413
+ if self._notifier and not self.is_simulation:
406
414
  try:
407
415
  self._notifier.notify_stop(
408
416
  {
@@ -444,7 +452,9 @@ class StrategyContext(IStrategyContext):
444
452
  data_provider.close()
445
453
  logger.debug(f"[StrategyContext] :: Closed data provider: {type(data_provider).__name__}")
446
454
  except Exception as e:
447
- logger.error(f"[StrategyContext] :: Failed to close data provider {type(data_provider).__name__}: {e}")
455
+ logger.error(
456
+ f"[StrategyContext] :: Failed to close data provider {type(data_provider).__name__}: {e}"
457
+ )
448
458
  except Exception as e:
449
459
  logger.error(f"[StrategyContext] :: Error iterating data providers: {e}")
450
460
 
@@ -942,8 +942,7 @@ class ITradingManager:
942
942
 
943
943
  Args:
944
944
  instrument: The instrument to get the minimum size for
945
- amount: The amount to get the minimum size for
946
- price: The price to get the minimum size for
945
+ amount: The amount to be traded to determine if it's position reducing or not
947
946
  """
948
947
  ...
949
948
 
@@ -1453,6 +1452,7 @@ class IStrategyContext(
1453
1452
  emitter: "IMetricEmitter"
1454
1453
  health: "IHealthReader"
1455
1454
  notifier: "IStrategyNotifier"
1455
+ strategy_name: str
1456
1456
 
1457
1457
  _strategy_state: StrategyState
1458
1458
 
@@ -2352,6 +2352,16 @@ class IStrategy(metaclass=Mixable):
2352
2352
  """
2353
2353
  return None
2354
2354
 
2355
+ def on_deals(self, ctx: IStrategyContext, deals: list[Deal]) -> None:
2356
+ """
2357
+ Called when deals are received.
2358
+
2359
+ Args:
2360
+ ctx: Strategy context.
2361
+ deals: The deals.
2362
+ """
2363
+ return None
2364
+
2355
2365
  def on_error(self, ctx: IStrategyContext, error: BaseErrorEvent) -> None:
2356
2366
  """
2357
2367
  Called when an error occurs.
@@ -940,6 +940,10 @@ class ProcessingManager(IProcessingManager):
940
940
  account=self._account,
941
941
  )
942
942
 
943
+ # - notify strategy about deals
944
+ if deals:
945
+ self._strategy.on_deals(self._context, deals)
946
+
943
947
  # - process active targets: if we got 0 position after executions remove current position from active
944
948
  if not self._context.get_position(instrument).is_open():
945
949
  self._active_targets.pop(instrument, None)
@@ -464,9 +464,12 @@ class TradingManager(ITradingManager):
464
464
  size_adj = instrument.round_size_down(abs(amount))
465
465
  min_size = self._get_min_size(instrument, amount)
466
466
  if abs(size_adj) < min_size:
467
- raise InvalidOrderSize(
468
- f"[{instrument.symbol}] Attempt to trade size {abs(amount)} less than minimal allowed {min_size} !"
469
- )
467
+ # Try just in case to round up to avoid too small orders
468
+ size_adj = instrument.round_size_up(abs(amount))
469
+ if abs(size_adj) < min_size:
470
+ raise InvalidOrderSize(
471
+ f"[{instrument.symbol}] Attempt to trade size {abs(amount)} less than minimal allowed {min_size} !"
472
+ )
470
473
  return size_adj
471
474
 
472
475
  def _adjust_price(self, instrument: Instrument, price: float | None, amount: float) -> float | None:
@@ -169,6 +169,7 @@ class OHLCV(TimeSeries):
169
169
  def to_records(self) -> dict: ...
170
170
  def to_series(self, length: int | None = None) -> pd.DataFrame: ...
171
171
  def pd(self, length: int | None = None) -> pd.DataFrame: ...
172
+ def resample(self, timeframe: str, max_series_length=np.inf) -> "OHLCV": ...
172
173
  @staticmethod
173
174
  def from_dataframe(df_p: pd.DataFrame, name: str = "ohlc") -> "OHLCV": ...
174
175