Qubx 0.6.68__tar.gz → 0.6.71__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 (216) hide show
  1. {qubx-0.6.68 → qubx-0.6.71}/PKG-INFO +1 -1
  2. {qubx-0.6.68 → qubx-0.6.71}/pyproject.toml +2 -2
  3. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/__init__.py +1 -1
  4. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/cli/commands.py +4 -0
  5. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/cli/release.py +333 -25
  6. qubx-0.6.71/src/qubx/connectors/ccxt/adapters/__init__.py +7 -0
  7. qubx-0.6.71/src/qubx/connectors/ccxt/adapters/polling_adapter.py +439 -0
  8. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/exchanges/__init__.py +7 -0
  9. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +148 -1
  10. qubx-0.6.71/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +1 -0
  11. qubx-0.6.71/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +161 -0
  12. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/handlers/factory.py +1 -0
  13. qubx-0.6.71/src/qubx/connectors/ccxt/handlers/funding_rate.py +219 -0
  14. qubx-0.6.71/src/qubx/connectors/ccxt/handlers/ohlc.py +346 -0
  15. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/handlers/open_interest.py +1 -3
  16. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/handlers/orderbook.py +43 -36
  17. qubx-0.6.71/src/qubx/connectors/ccxt/reader.py +560 -0
  18. qubx-0.6.71/src/qubx/connectors/ccxt/subscription_config.py +82 -0
  19. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/subscription_orchestrator.py +120 -7
  20. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/warmup_service.py +27 -24
  21. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/basics.py +7 -5
  22. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/helpers.py +6 -6
  23. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/lookups.py +1 -1
  24. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/data/composite.py +252 -9
  25. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/data/readers.py +2 -2
  26. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/gathering/simplest.py +1 -1
  27. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/pandaz/ta.py +105 -0
  28. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/crypto-fees.ini +8 -1
  29. qubx-0.6.71/src/qubx/resources/instruments/hyperliquid-spot.json +4204 -0
  30. qubx-0.6.71/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +4424 -0
  31. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/marketdata/ccxt.py +6 -1
  32. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/runner/_jupyter_runner.pyt +1 -1
  33. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/runner/configs.py +41 -1
  34. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/runner/factory.py +35 -0
  35. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/runner/runner.py +22 -7
  36. qubx-0.6.68/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -93
  37. qubx-0.6.68/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -202
  38. qubx-0.6.68/src/qubx/connectors/ccxt/reader.py +0 -237
  39. qubx-0.6.68/src/qubx/connectors/ccxt/subscription_config.py +0 -40
  40. qubx-0.6.68/src/qubx/resources/instruments/symbols-hyperliquid-spot.json +0 -1
  41. qubx-0.6.68/src/qubx/resources/instruments/symbols-hyperliquid.f-perpetual.json +0 -1
  42. {qubx-0.6.68 → qubx-0.6.71}/LICENSE +0 -0
  43. {qubx-0.6.68 → qubx-0.6.71}/README.md +0 -0
  44. {qubx-0.6.68 → qubx-0.6.71}/build.py +0 -0
  45. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/_nb_magic.py +0 -0
  46. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/__init__.py +0 -0
  47. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/account.py +0 -0
  48. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/broker.py +0 -0
  49. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/data.py +0 -0
  50. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/management.py +0 -0
  51. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/ome.py +0 -0
  52. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/optimization.py +0 -0
  53. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/runner.py +0 -0
  54. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/sentinels.py +0 -0
  55. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/simulated_data.py +0 -0
  56. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/simulated_exchange.py +0 -0
  57. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/simulator.py +0 -0
  58. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/backtester/utils.py +0 -0
  59. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/cli/__init__.py +0 -0
  60. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/cli/deploy.py +0 -0
  61. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/cli/misc.py +0 -0
  62. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/cli/tui.py +0 -0
  63. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/__init__.py +0 -0
  64. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/account.py +0 -0
  65. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/broker.py +0 -0
  66. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  67. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/data.py +0 -0
  68. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  69. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  70. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  71. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  72. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  73. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/factory.py +0 -0
  74. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  75. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  76. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  77. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/handlers/quote.py +1 -1
  78. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  79. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  80. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/ccxt/utils.py +0 -0
  81. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/tardis/data.py +0 -0
  82. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/connectors/tardis/utils.py +0 -0
  83. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/__init__.py +0 -0
  84. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/account.py +0 -0
  85. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/context.py +0 -0
  86. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/deque.py +0 -0
  87. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/errors.py +0 -0
  88. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/exceptions.py +0 -0
  89. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/initializer.py +0 -0
  90. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/interfaces.py +0 -0
  91. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/loggers.py +0 -0
  92. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/metrics.py +0 -0
  93. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/mixins/__init__.py +0 -0
  94. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/mixins/market.py +0 -0
  95. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/mixins/processing.py +0 -0
  96. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/mixins/subscription.py +0 -0
  97. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/mixins/trading.py +0 -0
  98. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/mixins/universe.py +0 -0
  99. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/series.pxd +0 -0
  100. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/series.pyi +0 -0
  101. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/series.pyx +0 -0
  102. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/stale_data_detector.py +0 -0
  103. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/utils.pyi +0 -0
  104. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/core/utils.pyx +0 -0
  105. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/data/__init__.py +0 -0
  106. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/data/helpers.py +0 -0
  107. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/data/hft.py +0 -0
  108. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/data/registry.py +0 -0
  109. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/data/tardis.py +0 -0
  110. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/emitters/__init__.py +0 -0
  111. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/emitters/base.py +0 -0
  112. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/emitters/composite.py +0 -0
  113. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/emitters/csv.py +0 -0
  114. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/emitters/indicator.py +0 -0
  115. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/emitters/inmemory.py +0 -0
  116. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/emitters/prometheus.py +0 -0
  117. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/emitters/questdb.py +0 -0
  118. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/exporters/__init__.py +0 -0
  119. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/exporters/composite.py +0 -0
  120. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/exporters/formatters/__init__.py +0 -0
  121. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/exporters/formatters/base.py +0 -0
  122. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/exporters/formatters/incremental.py +0 -0
  123. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/exporters/formatters/slack.py +0 -0
  124. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/exporters/redis_streams.py +0 -0
  125. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/exporters/slack.py +0 -0
  126. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/features/__init__.py +0 -0
  127. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/features/core.py +0 -0
  128. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/features/orderbook.py +0 -0
  129. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/features/price.py +0 -0
  130. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/features/trades.py +0 -0
  131. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/features/utils.py +0 -0
  132. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/health/__init__.py +0 -0
  133. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/health/base.py +0 -0
  134. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/loggers/__init__.py +0 -0
  135. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/loggers/csv.py +0 -0
  136. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/loggers/factory.py +0 -0
  137. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/loggers/inmemory.py +0 -0
  138. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/loggers/mongo.py +0 -0
  139. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/math/__init__.py +0 -0
  140. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/math/stats.py +0 -0
  141. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/notifications/__init__.py +0 -0
  142. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/notifications/composite.py +0 -0
  143. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/notifications/slack.py +0 -0
  144. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/notifications/throttler.py +0 -0
  145. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/pandaz/__init__.py +0 -0
  146. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/pandaz/utils.py +0 -0
  147. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/_build.py +0 -0
  148. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  149. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  150. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  151. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  152. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  153. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  154. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  155. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  156. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  157. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restarts/__init__.py +0 -0
  158. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restarts/state_resolvers.py +0 -0
  159. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restarts/time_finders.py +0 -0
  160. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restorers/__init__.py +0 -0
  161. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restorers/balance.py +0 -0
  162. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restorers/factory.py +0 -0
  163. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restorers/interfaces.py +0 -0
  164. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restorers/position.py +0 -0
  165. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restorers/signal.py +0 -0
  166. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restorers/state.py +0 -0
  167. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/restorers/utils.py +0 -0
  168. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/ta/__init__.py +0 -0
  169. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/ta/indicators.pxd +0 -0
  170. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/ta/indicators.pyi +0 -0
  171. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/ta/indicators.pyx +0 -0
  172. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/__init__.py +0 -0
  173. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/base.py +0 -0
  174. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  175. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/project/config.yml.j2 +0 -0
  176. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  177. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  178. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  179. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  180. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  181. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/project/template.yml +0 -0
  182. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  183. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  184. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/simple/config.yml.j2 +0 -0
  185. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  186. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  187. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  188. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/templates/simple/template.yml +0 -0
  189. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/trackers/__init__.py +0 -0
  190. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/trackers/advanced.py +0 -0
  191. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/trackers/composite.py +0 -0
  192. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/trackers/rebalancers.py +0 -0
  193. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/trackers/riskctrl.py +0 -0
  194. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/trackers/sizers.py +0 -0
  195. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/__init__.py +0 -0
  196. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/_pyxreloader.py +0 -0
  197. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/charting/lookinglass.py +0 -0
  198. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  199. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/collections.py +0 -0
  200. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/marketdata/binance.py +0 -0
  201. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/marketdata/dukas.py +0 -0
  202. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/misc.py +0 -0
  203. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/ntp.py +0 -0
  204. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/numbers_utils.py +0 -0
  205. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/orderbook.py +0 -0
  206. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/plotting/__init__.py +0 -0
  207. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/plotting/dashboard.py +0 -0
  208. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/plotting/data.py +0 -0
  209. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/plotting/interfaces.py +0 -0
  210. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  211. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  212. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/questdb.py +0 -0
  213. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/runner/__init__.py +0 -0
  214. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/runner/accounts.py +0 -0
  215. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/time.py +0 -0
  216. {qubx-0.6.68 → qubx-0.6.71}/src/qubx/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Qubx
3
- Version: 0.6.68
3
+ Version: 0.6.71
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Author: Dmitry Marienko
6
6
  Author-email: dmitry.marienko@xlydian.com
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "Qubx"
7
- version = "0.6.68"
7
+ version = "0.6.71"
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"
@@ -88,7 +88,7 @@ asyncio_default_fixture_loop_scope = "function"
88
88
  pythonpath = [ "src",]
89
89
  markers = [ "integration: mark test as requiring external services like Redis", "e2e: mark test as requiring external exchange connections and API credentials",]
90
90
  addopts = "--disable-warnings"
91
- filterwarnings = [ "ignore:.*Jupyter is migrating.*:DeprecationWarning",]
91
+ filterwarnings = [ "ignore:.*Jupyter is migrating.*:DeprecationWarning", "ignore:coroutine.*AsyncMockMixin._execute_mock_call.*was never awaited:RuntimeWarning",]
92
92
 
93
93
  [tool.ruff.lint.extend-per-file-ignores]
94
94
  "*.ipynb" = [ "F405", "F401", "E701", "E402", "F403", "E401", "E702", "I001",]
@@ -186,7 +186,7 @@ if runtime_env() in ["notebook", "shell"]:
186
186
  return
187
187
 
188
188
  ipy = get_ipython()
189
- for a in [x for x in re.split(r"[\ ,;]", line.strip()) if x]:
189
+ for a in [x for x in re.split(r"[\s,;]", line.strip()) if x]:
190
190
  ipy.push({a: self._get_manager().Value(None, None)})
191
191
 
192
192
  # code to run
@@ -32,6 +32,10 @@ def main(debug: bool, debug_port: int, log_level: str):
32
32
  """
33
33
  Qubx CLI.
34
34
  """
35
+ # Suppress syntax warnings from AST parsing during import resolution
36
+ import warnings
37
+ warnings.filterwarnings("ignore", category=SyntaxWarning)
38
+
35
39
  os.environ["PYDEVD_DISABLE_FILE_VALIDATION"] = "1"
36
40
  log_level = log_level.upper() if not debug else "DEBUG"
37
41
 
@@ -37,6 +37,16 @@ Import = namedtuple("Import", ["module", "name", "alias"])
37
37
  DEFAULT_CFG_NAME = "config.yml"
38
38
 
39
39
 
40
+ class ImportResolutionError(Exception):
41
+ """Raised when import resolution fails."""
42
+ pass
43
+
44
+
45
+ class DependencyResolutionError(Exception):
46
+ """Raised when dependency file resolution fails."""
47
+ pass
48
+
49
+
40
50
  @dataclass
41
51
  class ReleaseInfo:
42
52
  tag: str
@@ -53,23 +63,139 @@ class StrategyInfo:
53
63
  config: StrategyConfig
54
64
 
55
65
 
56
- def get_imports(path: str, what_to_look: list[str] = ["xincubator"]) -> Generator[Import, None, None]:
66
+ def resolve_relative_import(relative_module: str, file_path: str, project_root: str) -> str:
67
+ """
68
+ Resolve a relative import to an absolute module path.
69
+
70
+ Args:
71
+ relative_module: The relative module string (e.g., "..utils", ".helper")
72
+ file_path: Absolute path to the file containing the relative import
73
+ project_root: Root directory of the project
74
+
75
+ Returns:
76
+ Absolute module path string
77
+
78
+ Raises:
79
+ ImportResolutionError: If the relative import cannot be resolved
80
+ """
81
+ # Get the relative path from project root to the file
82
+ rel_file_path = os.path.relpath(file_path, project_root)
83
+
84
+ # Get the directory containing the file (remove filename)
85
+ file_dir = os.path.dirname(rel_file_path)
86
+
87
+ # Convert file directory path to module path
88
+ if file_dir:
89
+ current_module_parts = file_dir.replace(os.sep, ".").split(".")
90
+ else:
91
+ current_module_parts = []
92
+
93
+ # Parse the relative import
94
+ level = 0
95
+ module_name = relative_module
96
+
97
+ # Count leading dots to determine level
98
+ while module_name.startswith("."):
99
+ level += 1
100
+ module_name = module_name[1:]
101
+
102
+ # Calculate the target module parts
103
+ if level == 0:
104
+ # Not actually a relative import
105
+ return module_name
106
+
107
+ # For relative imports, we need to go up from the current package
108
+ # level-1 because level=1 means "same package", level=2 means "parent package"
109
+ if level == 1:
110
+ # from .module -> current package + module
111
+ parent_parts = current_module_parts
112
+ else:
113
+ # from ..module -> parent package + module
114
+ levels_up = level - 1
115
+ if levels_up > len(current_module_parts):
116
+ raise ImportResolutionError(
117
+ f"Relative import '{relative_module}' goes beyond project root in {file_path}"
118
+ )
119
+ parent_parts = current_module_parts[:-levels_up] if levels_up > 0 else current_module_parts
120
+
121
+ # Combine parent with the remaining module name
122
+ if module_name:
123
+ resolved_parts = parent_parts + module_name.split(".")
124
+ else:
125
+ resolved_parts = parent_parts
126
+
127
+ return ".".join(resolved_parts) if resolved_parts else ""
128
+
129
+
130
+ def get_imports(
131
+ path: str,
132
+ what_to_look: list[str] = ["xincubator"],
133
+ project_root: str | None = None
134
+ ) -> Generator[Import, None, None]:
57
135
  """
58
136
  Get imports from the given file.
137
+
138
+ Args:
139
+ path: Path to Python file to analyze
140
+ what_to_look: List of module prefixes to filter for (empty list = no filter)
141
+ project_root: Root directory for resolving relative imports (optional)
142
+
143
+ Yields:
144
+ Import namedtuples for each matching import statement
145
+
146
+ Raises:
147
+ SyntaxError: If the Python file has syntax errors
148
+ FileNotFoundError: If the file doesn't exist
149
+ ImportResolutionError: If relative imports cannot be resolved
59
150
  """
60
151
  with open(path) as fh:
61
152
  root = ast.parse(fh.read(), path)
62
153
 
63
154
  for node in ast.iter_child_nodes(root):
64
155
  if isinstance(node, ast.Import):
65
- module = []
156
+ # Handle direct imports like: import module, import module.submodule
157
+ for n in node.names:
158
+ module_parts = n.name.split(".")
159
+ # Apply filter if provided
160
+ if not what_to_look or module_parts[0] in what_to_look:
161
+ yield Import(module_parts, module_parts[-1:], n.asname)
162
+
66
163
  elif isinstance(node, ast.ImportFrom):
67
- module = node.module.split(".") if node.module else []
68
- else:
69
- continue
70
- for n in node.names:
71
- if module and what_to_look and module[0] in what_to_look:
72
- yield Import(module, n.name.split("."), n.asname)
164
+ # Handle from imports like: from module import name
165
+ level = getattr(node, 'level', 0)
166
+
167
+ if level > 0:
168
+ # This is a relative import (has dots)
169
+ if project_root is None:
170
+ # Skip relative imports if no project root provided
171
+ continue
172
+
173
+ # Build the relative module string
174
+ relative_module = "." * level
175
+ if node.module:
176
+ relative_module += node.module
177
+
178
+ try:
179
+ # Resolve relative import to absolute module path
180
+ resolved_module = resolve_relative_import(relative_module, path, project_root)
181
+ if resolved_module:
182
+ module_parts = resolved_module.split(".")
183
+ # Apply filter if provided
184
+ if not what_to_look or (module_parts and module_parts[0] in what_to_look):
185
+ for n in node.names:
186
+ yield Import(module_parts, n.name.split("."), n.asname)
187
+ except ImportResolutionError as e:
188
+ # Log the error but don't fail completely
189
+ logger.warning(f"Failed to resolve relative import: {e}")
190
+ continue
191
+ else:
192
+ # Regular from import (no dots)
193
+ if node.module:
194
+ module_parts = node.module.split(".")
195
+ # Apply filter if provided
196
+ if not what_to_look or module_parts[0] in what_to_look:
197
+ for n in node.names:
198
+ yield Import(module_parts, n.name.split("."), n.asname)
73
199
 
74
200
 
75
201
  def ls_strats(path: str) -> None:
@@ -409,6 +535,12 @@ def _copy_strategy_file(strategy_path: str, pyproject_root: str, release_dir: st
409
535
  def _try_copy_file(src_file: str, dest_dir: str, pyproject_root: str) -> None:
410
536
  """Try to copy the file to the release directory."""
411
537
  if os.path.exists(src_file):
538
+ # Skip unwanted files
539
+ file_name = os.path.basename(src_file)
540
+ if file_name.endswith('.pyc') or file_name.startswith('.'):
541
+ logger.debug(f"Skipping unwanted file: {src_file}")
542
+ return
543
+
412
544
  # Get the relative path from pyproject_root
413
545
  _rel_import_path = os.path.relpath(src_file, pyproject_root)
414
546
  _dest_import_path = os.path.join(dest_dir, _rel_import_path)
@@ -421,35 +553,168 @@ def _try_copy_file(src_file: str, dest_dir: str, pyproject_root: str) -> None:
421
553
  shutil.copy2(src_file, _dest_import_path)
422
554
 
423
555
 
556
+ def _copy_package_directory(src_package_dir: str, dest_dir: str, pyproject_root: str) -> None:
557
+ """
558
+ Copy an entire package directory recursively to the release directory.
559
+
560
+ Args:
561
+ src_package_dir: Source package directory path
562
+ dest_dir: Destination release directory
563
+ pyproject_root: Project root for calculating relative paths
564
+ """
565
+ if not os.path.exists(src_package_dir):
566
+ logger.warning(f"Package directory not found: {src_package_dir}")
567
+ return
568
+
569
+ # Get the relative path from pyproject_root
570
+ rel_package_path = os.path.relpath(src_package_dir, pyproject_root)
571
+ dest_package_path = os.path.join(dest_dir, rel_package_path)
572
+
573
+ # Create destination directory structure
574
+ os.makedirs(dest_package_path, exist_ok=True)
575
+
576
+ # Copy all files in the package directory recursively
577
+ for root, dirs, files in os.walk(src_package_dir):
578
+ # Filter out unwanted directories (modify dirs in-place to skip them)
579
+ dirs[:] = [d for d in dirs if not d.startswith('__pycache__') and not d.startswith('.')]
580
+
581
+ # Calculate relative path within the package
582
+ rel_root = os.path.relpath(root, src_package_dir)
583
+
584
+ # Create subdirectories in destination
585
+ if rel_root != ".":
586
+ dest_subdir = os.path.join(dest_package_path, rel_root)
587
+ os.makedirs(dest_subdir, exist_ok=True)
588
+ else:
589
+ dest_subdir = dest_package_path
590
+
591
+ # Copy all files (filter out unwanted files)
592
+ for file_name in files:
593
+ # Skip unwanted files
594
+ if file_name.endswith('.pyc') or file_name.startswith('.'):
595
+ continue
596
+
597
+ src_file = os.path.join(root, file_name)
598
+ dest_file = os.path.join(dest_subdir, file_name)
599
+
600
+ logger.debug(f"Copying package file from {src_file} to {dest_file}")
601
+ shutil.copy2(src_file, dest_file)
602
+
603
+
604
+ def _validate_dependencies(imports: list[Import], src_root: str, src_dir: str) -> tuple[list[Import], list[str]]:
605
+ """
606
+ Validate that all discovered dependencies can be resolved to actual files.
607
+
608
+ Args:
609
+ imports: List of Import objects to validate
610
+ src_root: Root directory containing the source packages
611
+ src_dir: Name of the source directory/package
612
+
613
+ Returns:
614
+ Tuple of (valid_imports, missing_dependencies)
615
+ """
616
+ valid_imports = []
617
+ missing_dependencies = []
618
+
619
+ for imp in imports:
620
+ # Construct expected path for this import
621
+ module_path_parts = [s for s in imp.module if s != src_dir]
622
+ base_path = os.path.join(src_root, *module_path_parts)
623
+
624
+ # Check if the import can be resolved to an actual file or package
625
+ found = False
626
+
627
+ # Try different file extensions and package structures
628
+ possible_paths = [
629
+ base_path + ".py",
630
+ base_path + ".pyx",
631
+ base_path + ".pyi",
632
+ base_path + ".pxd",
633
+ os.path.join(base_path, "__init__.py")
634
+ ]
635
+
636
+ for path in possible_paths:
637
+ if os.path.exists(path):
638
+ valid_imports.append(imp)
639
+ found = True
640
+ break
641
+
642
+ if not found:
643
+ missing_dependencies.append(f"{'.'.join(imp.module)} -> searched: {', '.join(possible_paths)}")
644
+
645
+ return valid_imports, missing_dependencies
646
+
647
+
424
648
  def _copy_dependencies(strategy_path: str, pyproject_root: str, release_dir: str) -> None:
425
- """Copy all dependencies required by the strategy."""
649
+ """
650
+ Copy all dependencies required by the strategy with validation.
651
+
652
+ Args:
653
+ strategy_path: Path to the main strategy file
654
+ pyproject_root: Root directory of the project
655
+ release_dir: Destination directory for the release
656
+
657
+ Raises:
658
+ DependencyResolutionError: If critical dependencies cannot be resolved
659
+ """
426
660
  _src_dir = os.path.basename(pyproject_root)
427
- _imports = _get_imports(strategy_path, pyproject_root, [_src_dir])
661
+
428
662
  # find inside of the pyproject_root a folder with the same name as the _src_dir
429
663
  # for instance it could be like macd_crossover/src/macd_crossover
430
664
  # or macd_crossover/macd_crossover
431
665
  # and assign this folder to _src_root
432
666
  _src_root = None
667
+ potential_roots = []
433
668
  for root, dirs, files in os.walk(pyproject_root):
434
669
  if _src_dir in dirs:
435
- _src_root = os.path.join(root, _src_dir)
670
+ potential_root = os.path.join(root, _src_dir)
671
+ potential_roots.append(potential_root)
672
+
673
+ # Prefer source directories (src/package_name) over root directories (package_name)
674
+ # This handles cases where both /project/package_name AND /project/src/package_name exist
675
+ for root in potential_roots:
676
+ if os.path.sep + "src" + os.path.sep in root:
677
+ _src_root = root
436
678
  break
679
+
680
+ # If no src-based root found, use the first one (for simpler structures)
681
+ if _src_root is None and potential_roots:
682
+ _src_root = potential_roots[0]
437
683
 
438
684
  if _src_root is None:
439
- raise ValueError(f"Could not find the source root for {_src_dir} in {pyproject_root}")
440
-
441
- for _imp in _imports:
685
+ raise DependencyResolutionError(f"Could not find the source root for {_src_dir} in {pyproject_root}")
686
+
687
+ # Now call _get_imports with the correct source root directory
688
+ _imports = _get_imports(strategy_path, _src_root, [_src_dir])
689
+
690
+ # Validate all dependencies before copying
691
+ valid_imports, missing_dependencies = _validate_dependencies(_imports, _src_root, _src_dir)
692
+
693
+ if missing_dependencies:
694
+ logger.warning(f"Found {len(missing_dependencies)} missing dependencies:")
695
+ for missing in missing_dependencies:
696
+ logger.warning(f" - {missing}")
697
+ logger.warning("Release package may be incomplete. Consider fixing missing dependencies.")
698
+
699
+ logger.info(f"Copying {len(valid_imports)} validated dependencies...")
700
+
701
+ # Copy only the valid dependencies
702
+ for _imp in valid_imports:
442
703
  # Construct source path
443
704
  _base = os.path.join(_src_root, *[s for s in _imp.module if s != _src_dir])
444
705
 
445
706
  # - try to copy all available files for satisfying the import
446
707
  if os.path.isdir(_base):
447
- _try_copy_file(os.path.join(_base, "__init__.py"), release_dir, pyproject_root)
708
+ # This is a package directory - copy all files recursively
709
+ _copy_package_directory(_base, release_dir, pyproject_root)
448
710
  else:
711
+ # This is a single module - copy all variants
449
712
  _try_copy_file(_base + ".py", release_dir, pyproject_root)
450
713
  _try_copy_file(_base + ".pyx", release_dir, pyproject_root)
451
714
  _try_copy_file(_base + ".pyi", release_dir, pyproject_root)
452
715
  _try_copy_file(_base + ".pxd", release_dir, pyproject_root)
716
+
717
+ logger.info(f"Successfully copied {len(valid_imports)} dependencies to release package")
453
718
 
454
719
 
455
720
  def _create_metadata(stg_name: str, git_info: ReleaseInfo, release_dir: str) -> None:
@@ -656,19 +921,62 @@ def _create_zip_archive(output_dir: str, release_dir: str, tag: str) -> None:
656
921
 
657
922
 
658
923
  def _get_imports(file_name: str, current_directory: str, what_to_look: list[str]) -> list[Import]:
659
- imports = list(get_imports(file_name, what_to_look))
924
+ """
925
+ Recursively get all imports from a file and its dependencies.
926
+
927
+ Args:
928
+ file_name: Path to the Python file to analyze
929
+ current_directory: Root directory for resolving imports
930
+ what_to_look: List of module prefixes to filter for
931
+
932
+ Returns:
933
+ List of Import objects for all discovered dependencies
934
+
935
+ Raises:
936
+ DependencyResolutionError: If a required dependency cannot be found or processed
937
+ """
938
+ try:
939
+ imports = list(get_imports(file_name, what_to_look, project_root=current_directory))
940
+ except (SyntaxError, FileNotFoundError) as e:
941
+ raise DependencyResolutionError(f"Failed to parse imports from {file_name}: {e}")
942
+
660
943
  current_dirname = os.path.basename(current_directory)
944
+ missing_dependencies = []
945
+
661
946
  for i in imports:
662
947
  try:
663
- base = os.path.join(*[current_directory, *[s for s in i.module if s != current_dirname]])
664
-
665
- # - first try to find a .py file
666
- if not os.path.exists(f1_py := base + ".py"):
667
- f1_py = os.path.join(base, "__init__") + ".py"
668
-
669
- imports.extend(_get_imports(f1_py, current_directory, what_to_look))
670
- except Exception:
671
- pass
948
+ # Build path to the imported module
949
+ module_path_parts = [s for s in i.module if s != current_dirname]
950
+ base = os.path.join(current_directory, *module_path_parts)
951
+
952
+ # Try to find the dependency file
953
+ dependency_file = None
954
+ if os.path.exists(base + ".py"):
955
+ dependency_file = base + ".py"
956
+ elif os.path.exists(os.path.join(base, "__init__.py")):
957
+ dependency_file = os.path.join(base, "__init__.py")
958
+
959
+ if dependency_file:
960
+ # Recursively process the dependency
961
+ try:
962
+ imports.extend(_get_imports(dependency_file, current_directory, what_to_look))
963
+ except DependencyResolutionError as e:
964
+ # Log nested dependency errors but continue processing
965
+ logger.warning(f"Failed to resolve nested dependency: {e}")
966
+ else:
967
+ # Track missing dependencies
968
+ missing_dependencies.append(f"{'.'.join(i.module)} (searched: {base}.py, {base}/__init__.py)")
969
+
970
+ except Exception as e:
971
+ # Convert unexpected errors to DependencyResolutionError
972
+ raise DependencyResolutionError(f"Unexpected error processing import {'.'.join(i.module)}: {e}")
973
+
974
+ # Warn about missing dependencies but don't fail completely
975
+ if missing_dependencies:
976
+ logger.warning(f"Could not resolve {len(missing_dependencies)} dependencies from {file_name}:")
977
+ for dep in missing_dependencies:
978
+ logger.warning(f" - {dep}")
979
+
672
980
  return imports
673
981
 
674
982
 
@@ -0,0 +1,7 @@
1
+ """
2
+ CCXT polling adapters for converting fetch_* methods to watch_* behavior.
3
+ """
4
+
5
+ from .polling_adapter import PollingToWebSocketAdapter
6
+
7
+ __all__ = ["PollingToWebSocketAdapter"]