lumibot 4.0.22__tar.gz → 4.0.23__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 lumibot might be problematic. Click here for more details.

Files changed (249) hide show
  1. {lumibot-4.0.22/lumibot.egg-info → lumibot-4.0.23}/PKG-INFO +1 -1
  2. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/__init__.py +2 -1
  3. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/databento_data.py +5 -5
  4. lumibot-4.0.23/lumibot/data_sources/databento_data_polars_backtesting.py +490 -0
  5. lumibot-4.0.22/lumibot/data_sources/databento_data_polars.py → lumibot-4.0.23/lumibot/data_sources/databento_data_polars_live.py +2 -2
  6. {lumibot-4.0.22 → lumibot-4.0.23/lumibot.egg-info}/PKG-INFO +1 -1
  7. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot.egg-info/SOURCES.txt +2 -1
  8. {lumibot-4.0.22 → lumibot-4.0.23}/setup.py +1 -1
  9. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_databento.py +53 -0
  10. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_databento_live.py +10 -10
  11. {lumibot-4.0.22 → lumibot-4.0.23}/LICENSE +0 -0
  12. {lumibot-4.0.22 → lumibot-4.0.23}/MANIFEST.in +0 -0
  13. {lumibot-4.0.22 → lumibot-4.0.23}/README.md +0 -0
  14. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/__init__.py +0 -0
  15. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/__init__.py +0 -0
  16. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/alpaca_backtesting.py +0 -0
  17. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/alpha_vantage_backtesting.py +0 -0
  18. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/backtesting_broker.py +0 -0
  19. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/ccxt_backtesting.py +0 -0
  20. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/databento_backtesting.py +0 -0
  21. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/databento_backtesting_polars.py +0 -0
  22. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/interactive_brokers_rest_backtesting.py +0 -0
  23. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/pandas_backtesting.py +0 -0
  24. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/polygon_backtesting.py +0 -0
  25. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/thetadata_backtesting.py +0 -0
  26. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/backtesting/yahoo_backtesting.py +0 -0
  27. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/__init__.py +0 -0
  28. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/alpaca.py +0 -0
  29. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/bitunix.py +0 -0
  30. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/broker.py +0 -0
  31. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/ccxt.py +0 -0
  32. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/example_broker.py +0 -0
  33. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/interactive_brokers.py +0 -0
  34. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/interactive_brokers_rest.py +0 -0
  35. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/projectx.py +0 -0
  36. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/schwab.py +0 -0
  37. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/tradier.py +0 -0
  38. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/brokers/tradovate.py +0 -0
  39. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/components/__init__.py +0 -0
  40. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/components/configs_helper.py +0 -0
  41. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/components/drift_rebalancer_logic.py +0 -0
  42. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/components/grok_helper.py +0 -0
  43. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/components/options_helper.py +0 -0
  44. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/components/perplexity_helper.py +0 -0
  45. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/components/quiver_helper.py +0 -0
  46. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/components/vix_helper.py +0 -0
  47. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/constants.py +0 -0
  48. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/credentials.py +0 -0
  49. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/alpaca_data.py +0 -0
  50. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/alpha_vantage_data.py +0 -0
  51. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/bitunix_data.py +0 -0
  52. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/ccxt_backtesting_data.py +0 -0
  53. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/ccxt_data.py +0 -0
  54. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/data_source.py +0 -0
  55. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/data_source_backtesting.py +0 -0
  56. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/example_broker_data.py +0 -0
  57. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/exceptions.py +0 -0
  58. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/interactive_brokers_data.py +0 -0
  59. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/interactive_brokers_rest_data.py +0 -0
  60. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/pandas_data.py +0 -0
  61. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/polars_mixin.py +0 -0
  62. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/polygon_data_polars.py +0 -0
  63. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/projectx_data.py +0 -0
  64. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/schwab_data.py +0 -0
  65. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/tradier_data.py +0 -0
  66. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/tradovate_data.py +0 -0
  67. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/yahoo_data.py +0 -0
  68. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/data_sources/yahoo_data_polars.py +0 -0
  69. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/__init__.py +0 -0
  70. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/asset.py +0 -0
  71. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/bar.py +0 -0
  72. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/bars.py +0 -0
  73. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/chains.py +0 -0
  74. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/data.py +0 -0
  75. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/dataline.py +0 -0
  76. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/order.py +0 -0
  77. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/position.py +0 -0
  78. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/quote.py +0 -0
  79. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/entities/trading_fee.py +0 -0
  80. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/__init__.py +0 -0
  81. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/bitunix_futures_example.py +0 -0
  82. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/ccxt_backtesting_example.py +0 -0
  83. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/classic_60_40.py +0 -0
  84. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/classic_60_40_config.py +0 -0
  85. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/crypto_50_50.py +0 -0
  86. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/crypto_50_50_config.py +0 -0
  87. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/crypto_important_functions.py +0 -0
  88. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/drift_rebalancer.py +0 -0
  89. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/forex_hold_to_expiry.py +0 -0
  90. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/futures_hold_to_expiry.py +0 -0
  91. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/lifecycle_logger.py +0 -0
  92. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/options_hold_to_expiry.py +0 -0
  93. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/schedule_function.py +0 -0
  94. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/simple_start_single_file.py +0 -0
  95. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/stock_bracket.py +0 -0
  96. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/stock_buy_and_hold.py +0 -0
  97. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/stock_diversified_leverage.py +0 -0
  98. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/stock_limit_and_trailing_stops.py +0 -0
  99. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/stock_momentum.py +0 -0
  100. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/stock_oco.py +0 -0
  101. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/strangle.py +0 -0
  102. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/example_strategies/test_broker_functions.py +0 -0
  103. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/resources/conf.yaml +0 -0
  104. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/strategies/__init__.py +0 -0
  105. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/strategies/_strategy.py +0 -0
  106. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/strategies/session_manager.py +0 -0
  107. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/strategies/strategy.py +0 -0
  108. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/strategies/strategy_executor.py +0 -0
  109. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/__init__.py +0 -0
  110. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/alpaca_helpers.py +0 -0
  111. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/bitunix_helpers.py +0 -0
  112. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/black_scholes.py +0 -0
  113. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/ccxt_data_store.py +0 -0
  114. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/databento_helper.py +0 -0
  115. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/databento_helper_polars.py +0 -0
  116. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/debugers.py +0 -0
  117. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/decorators.py +0 -0
  118. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/futures_symbols.py +0 -0
  119. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/helpers.py +0 -0
  120. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/indicators.py +0 -0
  121. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/lumibot_logger.py +0 -0
  122. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/lumibot_time.py +0 -0
  123. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/pandas.py +0 -0
  124. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/polygon_helper.py +0 -0
  125. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/polygon_helper_async.py +0 -0
  126. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/polygon_helper_polars_optimized.py +0 -0
  127. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/projectx_helpers.py +0 -0
  128. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/schwab_helper.py +0 -0
  129. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/thetadata_helper.py +0 -0
  130. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/types.py +0 -0
  131. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/yahoo_helper.py +0 -0
  132. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/tools/yahoo_helper_polars_optimized.py +0 -0
  133. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/traders/__init__.py +0 -0
  134. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/traders/debug_log_trader.py +0 -0
  135. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/traders/trader.py +0 -0
  136. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/trading_builtins/__init__.py +0 -0
  137. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/trading_builtins/custom_stream.py +0 -0
  138. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot/trading_builtins/safe_list.py +0 -0
  139. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot.egg-info/dependency_links.txt +0 -0
  140. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot.egg-info/requires.txt +0 -0
  141. {lumibot-4.0.22 → lumibot-4.0.23}/lumibot.egg-info/top_level.txt +0 -0
  142. {lumibot-4.0.22 → lumibot-4.0.23}/pyproject.toml +0 -0
  143. {lumibot-4.0.22 → lumibot-4.0.23}/setup.cfg +0 -0
  144. {lumibot-4.0.22 → lumibot-4.0.23}/tests/__init__.py +0 -0
  145. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/__init__.py +0 -0
  146. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/conftest.py +0 -0
  147. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/performance_tracker.py +0 -0
  148. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_backtesting_broker_processing.py +0 -0
  149. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_buy_hold_quiet_logs_full_run.py +0 -0
  150. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_crypto_cash_regressions.py +0 -0
  151. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_dividends.py +0 -0
  152. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_example_strategies.py +0 -0
  153. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_failing_backtest.py +0 -0
  154. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_multileg_backtest.py +0 -0
  155. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_pandas_backtest.py +0 -0
  156. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_passing_trader_into_backtest.py +0 -0
  157. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_polygon.py +0 -0
  158. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_strategy_executor.py +0 -0
  159. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_thetadata.py +0 -0
  160. {lumibot-4.0.22 → lumibot-4.0.23}/tests/backtest/test_yahoo.py +0 -0
  161. {lumibot-4.0.22 → lumibot-4.0.23}/tests/conftest.py +0 -0
  162. {lumibot-4.0.22 → lumibot-4.0.23}/tests/fixtures.py +0 -0
  163. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_alpaca.py +0 -0
  164. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_alpaca_auth_fix.py +0 -0
  165. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_alpaca_backtesting.py +0 -0
  166. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_alpaca_data.py +0 -0
  167. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_alpaca_helpers.py +0 -0
  168. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_alpaca_multileg_fix.py +0 -0
  169. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_alpaca_oauth.py +0 -0
  170. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_apscheduler_warnings.py +0 -0
  171. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_asset.py +0 -0
  172. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_asset_auto_expiry.py +0 -0
  173. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_auto_market_inference.py +0 -0
  174. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_backtesting_broker.py +0 -0
  175. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_backtesting_broker_await_close.py +0 -0
  176. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_backtesting_broker_time_advance.py +0 -0
  177. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_backtesting_crypto_cash_unit.py +0 -0
  178. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_backtesting_flow_control.py +0 -0
  179. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_backtesting_multileg_unit.py +0 -0
  180. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_backtesting_quiet_logs_complete.py +0 -0
  181. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_bars_aggregate_frequency_normalization.py +0 -0
  182. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_bars_aggregation_timeunits.py +0 -0
  183. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_bars_frequency_flex.py +0 -0
  184. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_botspot_handler.py +0 -0
  185. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_botspot_logger.py +0 -0
  186. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_broker_bitunix.py +0 -0
  187. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_broker_cleanup.py +0 -0
  188. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_broker_initialization.py +0 -0
  189. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_brokers_handle_crypto.py +0 -0
  190. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_cash.py +0 -0
  191. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_ccxt.py +0 -0
  192. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_ccxt_store.py +0 -0
  193. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_configs_helper.py +0 -0
  194. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_continuous_futures.py +0 -0
  195. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_continuous_futures_integration.py +0 -0
  196. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_continuous_futures_resolution.py +0 -0
  197. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_data_source.py +0 -0
  198. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_databento_asset_validation.py +0 -0
  199. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_databento_auto_expiry_integration.py +0 -0
  200. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_databento_backtesting.py +0 -0
  201. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_databento_backtesting_polars.py +0 -0
  202. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_databento_data.py +0 -0
  203. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_databento_helper.py +0 -0
  204. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_databento_timezone_fixes.py +0 -0
  205. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_drift_rebalancer.py +0 -0
  206. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_futures_integration.py +0 -0
  207. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_get_historical_prices.py +0 -0
  208. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_helpers.py +0 -0
  209. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_indicator_subplots.py +0 -0
  210. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_integration_tests.py +0 -0
  211. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_interactive_brokers.py +0 -0
  212. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_live_trading_resilience.py +0 -0
  213. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_logger_env_vars.py +0 -0
  214. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_logging.py +0 -0
  215. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_lumibot_logger.py +0 -0
  216. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_market_infinite_loop_bug.py +0 -0
  217. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_mes_symbols.py +0 -0
  218. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_momentum.py +0 -0
  219. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_options_helper.py +0 -0
  220. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_order.py +0 -0
  221. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_order_serialization.py +0 -0
  222. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_pandas_data.py +0 -0
  223. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_polygon_helper.py +0 -0
  224. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_position_serialization.py +0 -0
  225. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx.py +0 -0
  226. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_bracket_helpers.py +0 -0
  227. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_bracket_lifecycle_unit.py +0 -0
  228. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_data.py +0 -0
  229. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_datetime_columns.py +0 -0
  230. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_datetime_index.py +0 -0
  231. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_helpers.py +0 -0
  232. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_lifecycle.py +0 -0
  233. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_lifecycle_unit.py +0 -0
  234. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_live_flow.py +0 -0
  235. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_timestep_alias.py +0 -0
  236. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_projectx_url_mappings.py +0 -0
  237. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_quiet_logs_buy_and_hold.py +0 -0
  238. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_quiet_logs_comprehensive.py +0 -0
  239. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_quiet_logs_functionality.py +0 -0
  240. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_quiet_logs_requirements.py +0 -0
  241. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_session_manager.py +0 -0
  242. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_strategy_methods.py +0 -0
  243. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_thetadata_helper.py +0 -0
  244. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_tradier.py +0 -0
  245. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_tradier_data.py +0 -0
  246. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_tradingfee.py +0 -0
  247. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_tradovate.py +0 -0
  248. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_unified_logger.py +0 -0
  249. {lumibot-4.0.22 → lumibot-4.0.23}/tests/test_vix_helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lumibot
3
- Version: 4.0.22
3
+ Version: 4.0.23
4
4
  Summary: Backtesting and Trading Library, Made by Lumiwealth
5
5
  Home-page: https://github.com/Lumiwealth/lumibot
6
6
  Author: Robert Grzesik
@@ -20,5 +20,6 @@ from .interactive_brokers_rest_data import InteractiveBrokersRESTData
20
20
  from .schwab_data import SchwabData
21
21
  from .tradovate_data import TradovateData
22
22
 
23
- from .databento_data_polars import DataBentoDataPolars as DataBentoData
23
+ from .databento_data_polars_live import DataBentoDataPolarsLive as DataBentoData
24
+ from .databento_data_polars_backtesting import DataBentoDataPolarsBacktesting as DataBentoDataBacktesting
24
25
  from .projectx_data import ProjectXData
@@ -8,9 +8,9 @@ from lumibot.tools import databento_helper
8
8
  from lumibot.tools.lumibot_logger import get_logger
9
9
 
10
10
  try:
11
- from .databento_data_polars import DataBentoDataPolars
11
+ from .databento_data_polars_live import DataBentoDataPolarsLive
12
12
  except Exception: # pragma: no cover - optional dependency path
13
- DataBentoDataPolars = None
13
+ DataBentoDataPolarsLive = None
14
14
 
15
15
  logger = get_logger(__name__)
16
16
 
@@ -330,13 +330,13 @@ class DataBentoData(DataSource):
330
330
  price = self.get_last_price(asset, quote=quote)
331
331
  return Quote(asset=asset, price=price)
332
332
 
333
- def _ensure_live_delegate(self) -> Optional['DataBentoDataPolars']:
334
- if DataBentoDataPolars is None or self.is_backtesting_mode:
333
+ def _ensure_live_delegate(self) -> Optional['DataBentoDataPolarsLive']:
334
+ if DataBentoDataPolarsLive is None or self.is_backtesting_mode:
335
335
  return None
336
336
 
337
337
  if self._live_delegate is None:
338
338
  try:
339
- self._live_delegate = DataBentoDataPolars(
339
+ self._live_delegate = DataBentoDataPolarsLive(
340
340
  api_key=self._api_key,
341
341
  has_paid_subscription=True,
342
342
  enable_cache=False,
@@ -0,0 +1,490 @@
1
+ """Ultra-optimized DataBento backtesting using pure polars with zero pandas conversions.
2
+
3
+ This implementation:
4
+ 1. Uses polars columnar storage directly
5
+ 2. Lazy evaluation for maximum performance
6
+ 3. Efficient caching with parquet files
7
+ 4. Vectorized operations only
8
+ 5. Inherits from DataSourceBacktesting (proper architecture)
9
+ """
10
+
11
+ import os
12
+ import traceback
13
+ from datetime import datetime, timedelta
14
+ from decimal import Decimal
15
+ from typing import Dict, Optional, Union
16
+
17
+ import numpy as np
18
+ import polars as pl
19
+
20
+ from lumibot.data_sources import DataSourceBacktesting
21
+ from lumibot.data_sources.polars_mixin import PolarsMixin
22
+ from lumibot.entities import Asset, Bars
23
+ from lumibot.tools import databento_helper_polars
24
+ from lumibot.tools.lumibot_logger import get_logger
25
+
26
+ logger = get_logger(__name__)
27
+ START_BUFFER = timedelta(days=5)
28
+
29
+
30
+ class DataBentoDataPolarsBacktesting(PolarsMixin, DataSourceBacktesting):
31
+ """Ultra-optimized DataBento backtesting data source with pure polars."""
32
+
33
+ SOURCE = "DATABENTO"
34
+ MIN_TIMESTEP = "minute"
35
+ TIMESTEP_MAPPING = [
36
+ {"timestep": "minute", "representations": ["1m", "minute", "1 minute"]},
37
+ {"timestep": "hour", "representations": ["1h", "hour", "1 hour"]},
38
+ {"timestep": "day", "representations": ["1d", "day", "1 day"]},
39
+ ]
40
+
41
+ def __init__(
42
+ self,
43
+ datetime_start,
44
+ datetime_end,
45
+ api_key=None,
46
+ max_memory=None,
47
+ timeout=30,
48
+ max_retries=3,
49
+ **kwargs,
50
+ ):
51
+ super().__init__(
52
+ datetime_start=datetime_start,
53
+ datetime_end=datetime_end,
54
+ api_key=api_key,
55
+ **kwargs
56
+ )
57
+
58
+ self.name = "databento"
59
+ self._api_key = api_key or os.environ.get("DATABENTO_API_KEY")
60
+ self._timeout = timeout
61
+ self._max_retries = max_retries
62
+ self.MAX_STORAGE_BYTES = max_memory
63
+
64
+ # Initialize polars storage from mixin
65
+ self._init_polars_storage()
66
+
67
+ # DataBento-specific caches
68
+ self._eager_cache: Dict[Asset, pl.DataFrame] = {}
69
+
70
+ # Prefetch tracking - CRITICAL for performance
71
+ self._prefetch_cache: Dict[tuple, bool] = {}
72
+ self._prefetched_assets = set() # Track which assets have been fully loaded
73
+
74
+ logger.info(f"DataBento backtesting initialized for period: {datetime_start} to {datetime_end}")
75
+
76
+ def _enforce_storage_limit(self, data_store: Dict[Asset, pl.LazyFrame]):
77
+ """Enforce storage limit by removing least recently used data."""
78
+ # Use mixin's enforce method
79
+ self._enforce_storage_limit_polars(self.MAX_STORAGE_BYTES)
80
+
81
+ # Clean up DataBento-specific caches
82
+ if self.MAX_STORAGE_BYTES and len(self._eager_cache) > 0:
83
+ # Remove from eager cache too
84
+ assets_to_remove = [a for a in self._eager_cache.keys() if a not in data_store]
85
+ for asset in assets_to_remove:
86
+ del self._eager_cache[asset]
87
+
88
+ def _store_data(self, asset: Asset, data: pl.DataFrame) -> pl.LazyFrame:
89
+ """Store data efficiently using lazy frames.
90
+
91
+ Returns lazy frame for efficient subsequent operations.
92
+ """
93
+ # Use mixin's store method first
94
+ lazy_data = self._store_data_polars(asset, data)
95
+
96
+ if lazy_data is None:
97
+ return None
98
+
99
+ # Update the stored data
100
+ self._data_store[asset] = lazy_data
101
+
102
+ # Enforce storage limit
103
+ if self.MAX_STORAGE_BYTES:
104
+ self._enforce_storage_limit(self._data_store)
105
+
106
+ return lazy_data
107
+
108
+ def get_start_datetime_and_ts_unit(self, length, timestep, start_dt=None, start_buffer=timedelta(days=5)):
109
+ """
110
+ Get the start datetime for the data.
111
+
112
+ Parameters
113
+ ----------
114
+ length : int
115
+ The number of data points to get.
116
+ timestep : str
117
+ The timestep to use. For example, "minute" or "hour" or "day".
118
+ start_dt : datetime
119
+ The start datetime to use. If None, the current self.datetime_start will be used.
120
+ start_buffer : timedelta
121
+ The buffer to add to the start datetime.
122
+
123
+ Returns
124
+ -------
125
+ datetime
126
+ The start datetime.
127
+ str
128
+ The timestep unit.
129
+ """
130
+ # Convert timestep string to timedelta and get start datetime
131
+ td, ts_unit = self.convert_timestep_str_to_timedelta(timestep)
132
+ if ts_unit == "day":
133
+ weeks_requested = length // 5 # Full trading week is 5 days
134
+ extra_padding_days = weeks_requested * 3 # to account for 3day weekends
135
+ td = timedelta(days=length + extra_padding_days)
136
+ else:
137
+ td *= length
138
+ if start_dt is not None:
139
+ start_datetime = start_dt - td
140
+ else:
141
+ start_datetime = self.datetime_start - td
142
+ start_datetime = start_datetime - start_buffer
143
+ return start_datetime, ts_unit
144
+
145
+ def is_data_cached(self, asset: Asset, start_dt, end_dt, timestep: str) -> bool:
146
+ """
147
+ Check if data is already cached for the given parameters.
148
+
149
+ Parameters
150
+ ----------
151
+ asset : Asset
152
+ The asset to check
153
+ start_dt : datetime
154
+ Start datetime
155
+ end_dt : datetime
156
+ End datetime
157
+ timestep : str
158
+ Time granularity
159
+
160
+ Returns
161
+ -------
162
+ bool
163
+ True if data is cached, False otherwise
164
+ """
165
+ search_asset = asset
166
+ if isinstance(asset, tuple):
167
+ search_asset = asset
168
+
169
+ # Check if in data store
170
+ if search_asset not in self._data_store:
171
+ return False
172
+
173
+ # Check if in filtered cache for daily data
174
+ if timestep == "day":
175
+ cache_key = (search_asset, start_dt.date(), timestep)
176
+ if cache_key in self._filtered_data_cache:
177
+ return True
178
+
179
+ # Check prefetch cache
180
+ cache_key = (search_asset, start_dt.date(), end_dt.date(), timestep)
181
+ return cache_key in self._prefetch_cache
182
+
183
+ def _update_data(self, asset: Asset, quote: Asset, length: int, timestep: str, start_dt=None):
184
+ """
185
+ Get asset data and update the self._data_store dictionary.
186
+
187
+ Parameters
188
+ ----------
189
+ asset : Asset
190
+ The asset to get data for.
191
+ quote : Asset
192
+ The quote asset to use. For example, if asset is "SPY" and quote is "USD", the data will be for "SPY/USD".
193
+ length : int
194
+ The number of data points to get.
195
+ timestep : str
196
+ The timestep to use. For example, "minute" or "hour" or "day".
197
+ start_dt : datetime
198
+ The start datetime to use. If None, the current self.start_datetime will be used.
199
+ """
200
+ search_asset = asset
201
+ asset_separated = asset
202
+ quote_asset = quote if quote is not None else Asset("USD", "forex")
203
+
204
+ if isinstance(search_asset, tuple):
205
+ asset_separated, quote_asset = search_asset
206
+ else:
207
+ search_asset = (search_asset, quote_asset)
208
+
209
+ # CRITICAL: If asset was prefetched, don't fetch again!
210
+ if search_asset in self._prefetched_assets:
211
+ return
212
+
213
+ # Check if we already have data in the store
214
+ if search_asset in self._data_store:
215
+ # Data already loaded, mark as prefetched and return
216
+ self._prefetched_assets.add(search_asset)
217
+ return
218
+
219
+ # Get the start datetime and timestep unit
220
+ start_datetime, ts_unit = self.get_start_datetime_and_ts_unit(
221
+ length, timestep, start_dt, start_buffer=START_BUFFER
222
+ )
223
+
224
+ # Fetch data for ENTIRE backtest period (like pandas does)
225
+ start_datetime = self.datetime_start - START_BUFFER
226
+ end_datetime = self.datetime_end + timedelta(days=1)
227
+
228
+ logger.info(f"Prefetching {asset_separated.symbol} data from {start_datetime.date()} to {end_datetime.date()}")
229
+
230
+ # Check if we have data for this asset
231
+ if search_asset in self._data_store:
232
+ # For daily timestep, use optimized caching strategy
233
+ if ts_unit == "day":
234
+ # Check if we need to clear cache for new date
235
+ current_date = self._datetime.date()
236
+
237
+ # Try to get from filtered cache first
238
+ cache_key = (search_asset, current_date, ts_unit)
239
+ if cache_key in self._filtered_data_cache:
240
+ result = self._filtered_data_cache[cache_key]
241
+ if len(result) >= length:
242
+ # Cache hit!
243
+ return
244
+
245
+ # Download data from DataBento using polars helper
246
+ try:
247
+ df = databento_helper_polars.get_price_data_from_databento_polars(
248
+ api_key=self._api_key,
249
+ asset=asset_separated,
250
+ start=start_datetime,
251
+ end=end_datetime,
252
+ timestep=timestep,
253
+ venue=None,
254
+ force_cache_update=False
255
+ )
256
+ except Exception as e:
257
+ # Handle all exceptions
258
+ logger.error(f"Error getting data from DataBento: {e}")
259
+ logger.error(traceback.format_exc())
260
+ # Mark as prefetched even on error to avoid retry loops
261
+ self._prefetched_assets.add(search_asset)
262
+ raise Exception("Error getting data from DataBento") from e
263
+
264
+ if (df is None) or len(df) == 0:
265
+ logger.warning(
266
+ f"DataBento returned no data: asset={getattr(asset_separated, 'symbol', asset_separated)} "
267
+ f"quote={getattr(quote_asset, 'symbol', quote_asset)} "
268
+ f"timestep={timestep} start={start_datetime.strftime('%Y-%m-%d %H:%M:%S')} "
269
+ f"end={end_datetime.strftime('%Y-%m-%d %H:%M:%S')} len=0"
270
+ )
271
+ # Mark as prefetched to avoid retry
272
+ self._prefetched_assets.add(search_asset)
273
+ return
274
+
275
+ # Store data
276
+ self._store_data(search_asset, df)
277
+ logger.info(f"Cached {len(df)} rows for {asset_separated.symbol}")
278
+
279
+ # Mark as prefetched
280
+ self._prefetched_assets.add(search_asset)
281
+
282
+ def _pull_source_symbol_bars(
283
+ self,
284
+ asset: Asset,
285
+ length: int,
286
+ timestep: str = "day",
287
+ timeshift: int = None,
288
+ quote: Asset = None,
289
+ exchange: str = None,
290
+ include_after_hours: bool = True,
291
+ ) -> Optional[pl.DataFrame]:
292
+ """Pull bars with maximum efficiency using pre-filtered cache."""
293
+
294
+ # Build search key
295
+ search_asset = asset if not isinstance(asset, tuple) else asset
296
+ if quote:
297
+ search_asset = (asset, quote)
298
+
299
+ # For daily timestep, use optimized caching strategy
300
+ if timestep == "day":
301
+ current_date = self._datetime.date()
302
+ cache_key = (search_asset, current_date, timestep)
303
+
304
+ # Try cache first
305
+ if cache_key in self._filtered_data_cache:
306
+ result = self._filtered_data_cache[cache_key]
307
+ if len(result) >= length:
308
+ return result.tail(length)
309
+
310
+ # Get the current datetime and calculate the start datetime
311
+ current_dt = self.get_datetime()
312
+ # Get data from DataBento
313
+ self._update_data(asset, quote, length, timestep, current_dt)
314
+
315
+ # Get lazy data
316
+ search_asset = asset if not isinstance(asset, tuple) else asset
317
+ if quote:
318
+ search_asset = (asset, quote)
319
+
320
+ lazy_data = self._get_data_lazy(search_asset)
321
+
322
+ if lazy_data is None:
323
+ return None
324
+
325
+ # Use lazy evaluation and collect only when needed
326
+ # Check if we have cached filtered data first
327
+ if timestep == "day":
328
+ current_date = self._datetime.date()
329
+ cache_key = (search_asset, current_date, timestep)
330
+ if cache_key in self._filtered_data_cache:
331
+ data = self._filtered_data_cache[cache_key]
332
+ else:
333
+ # Collect with filtering for efficiency
334
+ data = lazy_data.collect()
335
+ else:
336
+ # For minute data, collect on demand
337
+ data = lazy_data.collect()
338
+
339
+ # OPTIMIZATION: Direct filtering on eager DataFrame
340
+ current_dt = self.to_default_timezone(self._datetime)
341
+
342
+ # Determine end filter
343
+ if timestep == "day":
344
+ dt = self._datetime.replace(hour=23, minute=59, second=59, microsecond=999999)
345
+ end_filter = dt - timedelta(days=1)
346
+ else:
347
+ end_filter = current_dt
348
+
349
+ if timeshift:
350
+ if isinstance(timeshift, int):
351
+ timeshift = timedelta(days=timeshift)
352
+ end_filter = end_filter - timeshift
353
+
354
+ logger.debug(f"Filtering {asset.symbol} data: current_dt={current_dt}, end_filter={end_filter}, timestep={timestep}, timeshift={timeshift}")
355
+
356
+ # Convert to lazy frame for filtering
357
+ lazy_data = data.lazy() if not hasattr(data, 'collect') else data
358
+
359
+ # Use mixin's filter method
360
+ result = self._filter_data_polars(search_asset, lazy_data, end_filter, length, timestep)
361
+
362
+ if result is None:
363
+ return None
364
+
365
+ if len(result) < length:
366
+ logger.debug(
367
+ f"Requested {length} bars but only {len(result)} available "
368
+ f"for {asset.symbol} before {end_filter}"
369
+ )
370
+
371
+ logger.debug(f"Returning {len(result)} bars for {asset.symbol}")
372
+
373
+ return result
374
+
375
+ def _parse_source_symbol_bars(
376
+ self,
377
+ response: pl.DataFrame,
378
+ asset: Asset,
379
+ quote: Optional[Asset] = None,
380
+ length: Optional[int] = None,
381
+ return_polars: bool = False,
382
+ ) -> Bars:
383
+ """Parse bars from polars DataFrame."""
384
+ if quote is not None:
385
+ logger.warning(f"quote is not implemented for DataBentoData, but {quote} was passed as the quote")
386
+
387
+ # Use mixin's parse method
388
+ return self._parse_source_symbol_bars_polars(
389
+ response, asset, self.SOURCE, quote, length, return_polars=return_polars
390
+ )
391
+
392
+ def get_last_price(
393
+ self,
394
+ asset: Asset,
395
+ timestep: str = "minute",
396
+ quote: Optional[Asset] = None,
397
+ exchange: Optional[str] = None,
398
+ **kwargs
399
+ ) -> Union[float, Decimal, None]:
400
+ """Get last price with aggressive caching."""
401
+
402
+ if timestep is None:
403
+ timestep = self.get_timestep()
404
+
405
+ # Use mixin's cache check
406
+ current_datetime = self._datetime
407
+ cached_price = self._get_cached_last_price_polars(asset, current_datetime, timestep)
408
+ if cached_price is not None:
409
+ return cached_price
410
+
411
+ try:
412
+ dt = self.get_datetime()
413
+ self._update_data(asset, quote, 1, timestep, dt)
414
+ except Exception as e:
415
+ logger.error(f"Error get_last_price from DataBento: {e}")
416
+ logger.error(f"Error get_last_price from DataBento: {asset=} {quote=} {timestep=} {dt=} {e}")
417
+ self._cache_last_price_polars(asset, None, current_datetime, timestep)
418
+ return None
419
+
420
+ # Get price efficiently
421
+ # For daily data, don't apply additional timeshift since _pull_source_symbol_bars
422
+ # already handles getting the previous day's data
423
+ # Only request 1 bar for efficiency (matching pandas implementation)
424
+ timeshift = None if timestep == "day" else timedelta(days=-1)
425
+ length = 1
426
+
427
+ bars_data = self._pull_source_symbol_bars(
428
+ asset, length, timestep=timestep, timeshift=timeshift, quote=quote
429
+ )
430
+
431
+ if bars_data is None or len(bars_data) == 0:
432
+ logger.warning(f"No bars data for {asset.symbol} at {current_datetime}")
433
+ self._cache_last_price_polars(asset, None, current_datetime, timestep)
434
+ return None
435
+
436
+ # Direct column access - since we only request 1 bar, take the first (and only) element
437
+ open_price = bars_data["open"][0]
438
+
439
+ # Convert if needed
440
+ if isinstance(open_price, (np.int64, np.integer)):
441
+ open_price = Decimal(int(open_price))
442
+ elif isinstance(open_price, (np.float64, np.floating)):
443
+ open_price = float(open_price)
444
+
445
+ # Use mixin's cache method
446
+ self._cache_last_price_polars(asset, open_price, current_datetime, timestep)
447
+ return open_price
448
+
449
+ def get_historical_prices(
450
+ self,
451
+ asset: Asset,
452
+ length: int,
453
+ timestep: str = None,
454
+ timeshift: Optional[timedelta] = None,
455
+ quote: Optional[Asset] = None,
456
+ exchange: Optional[str] = None,
457
+ include_after_hours: bool = False,
458
+ return_polars: bool = False,
459
+ ) -> Optional[Bars]:
460
+ """Get historical prices using polars."""
461
+ logger.debug(f"get_historical_prices called for {asset.symbol}")
462
+ if timestep is None:
463
+ timestep = self.get_timestep()
464
+
465
+ # Get bars data
466
+ bars_data = self._pull_source_symbol_bars(
467
+ asset,
468
+ length,
469
+ timestep=timestep,
470
+ timeshift=timeshift,
471
+ quote=quote,
472
+ include_after_hours=include_after_hours
473
+ )
474
+
475
+ if bars_data is None:
476
+ return None
477
+
478
+ # Create and return Bars object
479
+ return self._parse_source_symbol_bars(
480
+ bars_data, asset, quote=quote, length=length, return_polars=return_polars
481
+ )
482
+
483
+ def get_chains(self, asset: Asset, quote: Asset = None, exchange: str = None):
484
+ """Get option chains - not implemented for DataBento."""
485
+ logger.warning("get_chains is not implemented for DataBentoData")
486
+ return None
487
+
488
+ def get_quote(self, asset: Asset) -> None:
489
+ """Get quote - not implemented for DataBento backtesting."""
490
+ return None
@@ -28,10 +28,10 @@ from lumibot.tools.lumibot_logger import get_logger
28
28
  logger = get_logger(__name__)
29
29
 
30
30
 
31
- class DataBentoDataPolars(PolarsMixin, DataSource):
31
+ class DataBentoDataPolarsLive(PolarsMixin, DataSource):
32
32
  """
33
33
  DataBento data source optimized with Polars and proper Live API usage.
34
-
34
+
35
35
  Uses Live API for real-time trade streaming to achieve <1 minute lag.
36
36
  Falls back to Historical API for older data.
37
37
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lumibot
3
- Version: 4.0.22
3
+ Version: 4.0.23
4
4
  Summary: Backtesting and Trading Library, Made by Lumiwealth
5
5
  Home-page: https://github.com/Lumiwealth/lumibot
6
6
  Author: Robert Grzesik
@@ -53,7 +53,8 @@ lumibot/data_sources/ccxt_data.py
53
53
  lumibot/data_sources/data_source.py
54
54
  lumibot/data_sources/data_source_backtesting.py
55
55
  lumibot/data_sources/databento_data.py
56
- lumibot/data_sources/databento_data_polars.py
56
+ lumibot/data_sources/databento_data_polars_backtesting.py
57
+ lumibot/data_sources/databento_data_polars_live.py
57
58
  lumibot/data_sources/example_broker_data.py
58
59
  lumibot/data_sources/exceptions.py
59
60
  lumibot/data_sources/interactive_brokers_data.py
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="lumibot",
8
- version="4.0.22",
8
+ version="4.0.23",
9
9
  author="Robert Grzesik",
10
10
  author_email="rob@lumiwealth.com",
11
11
  description="Backtesting and Trading Library, Made by Lumiwealth",
@@ -3,6 +3,7 @@ import pytest
3
3
  import pytz
4
4
 
5
5
  from lumibot.backtesting import BacktestingBroker, DataBentoDataBacktesting
6
+ from lumibot.data_sources import DataBentoDataBacktesting as DataBentoDataBacktestingPolars
6
7
  from lumibot.entities import Asset
7
8
  from lumibot.strategies import Strategy
8
9
  from lumibot.traders import Trader
@@ -97,6 +98,58 @@ class TestDatabentoBacktestFull:
97
98
  for price in strat_obj.prices:
98
99
  assert price is not None and price > 0, f"Expected valid price, got {price}"
99
100
 
101
+ @pytest.mark.apitest
102
+ @pytest.mark.skipif(
103
+ not DATABENTO_API_KEY,
104
+ reason="This test requires a Databento API key"
105
+ )
106
+ @pytest.mark.skipif(
107
+ DATABENTO_API_KEY == '<your key here>',
108
+ reason="This test requires a Databento API key"
109
+ )
110
+ def test_databento_continuous_futures_minute_data_polars(self):
111
+ """
112
+ Test Databento with Polars implementation - minute-level data.
113
+ Should be significantly faster than pandas version.
114
+ """
115
+ # Use timezone-aware datetimes for futures trading
116
+ tzinfo = pytz.timezone("America/New_York")
117
+ backtesting_start = tzinfo.localize(datetime.datetime(2025, 1, 2, 9, 30))
118
+ backtesting_end = tzinfo.localize(datetime.datetime(2025, 1, 3, 16, 0))
119
+
120
+ data_source = DataBentoDataBacktestingPolars(
121
+ datetime_start=backtesting_start,
122
+ datetime_end=backtesting_end,
123
+ api_key=DATABENTO_API_KEY,
124
+ )
125
+
126
+ broker = BacktestingBroker(data_source=data_source)
127
+
128
+ strat_obj = SimpleContinuousFutures(
129
+ broker=broker,
130
+ )
131
+
132
+ trader = Trader(logfile="", backtest=True)
133
+ trader.add_strategy(strat_obj)
134
+ results = trader.run_all(
135
+ show_plot=False,
136
+ show_tearsheet=False,
137
+ show_indicators=False,
138
+ save_tearsheet=False
139
+ )
140
+
141
+ # Verify results
142
+ assert results is not None
143
+ assert len(strat_obj.prices) > 0, "Expected to collect some prices"
144
+ assert len(strat_obj.times) > 0, "Expected to collect some timestamps"
145
+
146
+ # Verify minute-level cadence
147
+ assert len(strat_obj.prices) > 100, f"Expected many minute-level data points, got {len(strat_obj.prices)}"
148
+
149
+ # Verify all prices are valid numbers
150
+ for price in strat_obj.prices:
151
+ assert price is not None and price > 0, f"Expected valid price, got {price}"
152
+
100
153
  @pytest.mark.apitest
101
154
  @pytest.mark.skipif(
102
155
  not DATABENTO_API_KEY,