lumibot 4.1.3__tar.gz → 4.2.0__tar.gz

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

Potentially problematic release.


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

Files changed (293) hide show
  1. {lumibot-4.1.3/lumibot.egg-info → lumibot-4.2.0}/PKG-INFO +9 -1
  2. {lumibot-4.1.3 → lumibot-4.2.0}/README.md +7 -0
  3. lumibot-4.2.0/lumibot/backtesting/__init__.py +30 -0
  4. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/backtesting/backtesting_broker.py +98 -18
  5. lumibot-4.2.0/lumibot/backtesting/databento_backtesting.py +7 -0
  6. lumibot-4.1.3/lumibot/backtesting/databento_backtesting.py → lumibot-4.2.0/lumibot/backtesting/databento_backtesting_pandas.py +59 -9
  7. lumibot-4.2.0/lumibot/backtesting/databento_backtesting_polars.py +991 -0
  8. lumibot-4.2.0/lumibot/backtesting/fix_debug.py +37 -0
  9. lumibot-4.2.0/lumibot/backtesting/thetadata_backtesting.py +12 -0
  10. lumibot-4.2.0/lumibot/backtesting/thetadata_backtesting_pandas.py +1178 -0
  11. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/alpaca.py +8 -1
  12. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/schwab.py +12 -2
  13. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/credentials.py +13 -0
  14. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/__init__.py +5 -8
  15. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/data_source.py +6 -2
  16. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/data_source_backtesting.py +30 -0
  17. lumibot-4.2.0/lumibot/data_sources/databento_data.py +7 -0
  18. lumibot-4.1.3/lumibot/data_sources/databento_data.py → lumibot-4.2.0/lumibot/data_sources/databento_data_pandas.py +77 -29
  19. lumibot-4.1.3/lumibot/data_sources/databento_data_polars_live.py → lumibot-4.2.0/lumibot/data_sources/databento_data_polars.py +15 -9
  20. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/pandas_data.py +30 -17
  21. lumibot-4.2.0/lumibot/data_sources/polars_data.py +986 -0
  22. lumibot-4.2.0/lumibot/data_sources/polars_mixin.py +853 -0
  23. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/polygon_data_polars.py +5 -0
  24. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/yahoo_data.py +9 -2
  25. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/yahoo_data_polars.py +5 -0
  26. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/__init__.py +15 -0
  27. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/asset.py +5 -28
  28. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/bars.py +89 -20
  29. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/data.py +29 -6
  30. lumibot-4.2.0/lumibot/entities/data_polars.py +668 -0
  31. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/position.py +38 -4
  32. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/strategies/_strategy.py +2 -1
  33. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/strategies/strategy.py +61 -49
  34. lumibot-4.2.0/lumibot/tools/backtest_cache.py +284 -0
  35. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/databento_helper.py +35 -35
  36. lumibot-4.2.0/lumibot/tools/databento_helper_polars.py +1257 -0
  37. lumibot-4.2.0/lumibot/tools/futures_roll.py +251 -0
  38. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/indicators.py +135 -104
  39. lumibot-4.2.0/lumibot/tools/polars_utils.py +142 -0
  40. lumibot-4.2.0/lumibot/tools/thetadata_helper.py +2074 -0
  41. {lumibot-4.1.3 → lumibot-4.2.0/lumibot.egg-info}/PKG-INFO +9 -1
  42. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot.egg-info/SOURCES.txt +19 -10
  43. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot.egg-info/requires.txt +1 -0
  44. {lumibot-4.1.3 → lumibot-4.2.0}/setup.py +2 -1
  45. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_databento.py +37 -6
  46. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_databento_comprehensive_trading.py +8 -4
  47. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_databento_parity.py +4 -2
  48. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_debug_avg_fill_price.py +1 -1
  49. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_example_strategies.py +11 -1
  50. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_futures_edge_cases.py +3 -3
  51. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_futures_single_trade.py +2 -2
  52. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_futures_ultra_simple.py +2 -2
  53. lumibot-4.2.0/tests/backtest/test_polars_lru_eviction.py +470 -0
  54. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_yahoo.py +42 -0
  55. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_asset.py +4 -4
  56. lumibot-4.2.0/tests/test_backtest_cache_manager.py +149 -0
  57. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_backtesting_data_source_env.py +6 -0
  58. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_continuous_futures_resolution.py +60 -48
  59. lumibot-4.2.0/tests/test_data_polars_parity.py +160 -0
  60. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_databento_asset_validation.py +23 -5
  61. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_databento_backtesting.py +1 -1
  62. lumibot-4.2.0/tests/test_databento_backtesting_polars.py +321 -0
  63. lumibot-4.2.0/tests/test_databento_data.py +250 -0
  64. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_databento_live.py +10 -10
  65. lumibot-4.2.0/tests/test_futures_roll.py +38 -0
  66. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_indicator_subplots.py +101 -0
  67. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_market_infinite_loop_bug.py +77 -3
  68. lumibot-4.2.0/tests/test_polars_resample.py +67 -0
  69. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_polygon_helper.py +46 -0
  70. lumibot-4.2.0/tests/test_thetadata_backwards_compat.py +97 -0
  71. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_thetadata_helper.py +222 -23
  72. lumibot-4.2.0/tests/test_thetadata_pandas_verification.py +186 -0
  73. lumibot-4.1.3/lumibot/backtesting/__init__.py +0 -16
  74. lumibot-4.1.3/lumibot/backtesting/thetadata_backtesting.py +0 -358
  75. lumibot-4.1.3/lumibot/data_sources/databento_data_polars_backtesting.py +0 -636
  76. lumibot-4.1.3/lumibot/data_sources/polars_mixin.py +0 -477
  77. lumibot-4.1.3/lumibot/tools/databento_helper_polars.py +0 -1294
  78. lumibot-4.1.3/lumibot/tools/databento_roll.py +0 -216
  79. lumibot-4.1.3/lumibot/tools/thetadata_helper.py +0 -1140
  80. lumibot-4.1.3/tests/backtest/check_timing_offset.py +0 -198
  81. lumibot-4.1.3/tests/backtest/check_volume_spike.py +0 -112
  82. lumibot-4.1.3/tests/backtest/comprehensive_comparison.py +0 -166
  83. lumibot-4.1.3/tests/backtest/debug_comparison.py +0 -91
  84. lumibot-4.1.3/tests/backtest/diagnose_price_difference.py +0 -97
  85. lumibot-4.1.3/tests/backtest/direct_api_comparison.py +0 -203
  86. lumibot-4.1.3/tests/backtest/root_cause_analysis.py +0 -109
  87. lumibot-4.1.3/tests/test_databento_data.py +0 -493
  88. {lumibot-4.1.3 → lumibot-4.2.0}/LICENSE +0 -0
  89. {lumibot-4.1.3 → lumibot-4.2.0}/MANIFEST.in +0 -0
  90. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/__init__.py +0 -0
  91. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/backtesting/alpaca_backtesting.py +0 -0
  92. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/backtesting/alpha_vantage_backtesting.py +0 -0
  93. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/backtesting/ccxt_backtesting.py +0 -0
  94. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/backtesting/interactive_brokers_rest_backtesting.py +0 -0
  95. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/backtesting/pandas_backtesting.py +0 -0
  96. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/backtesting/polygon_backtesting.py +0 -0
  97. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/backtesting/yahoo_backtesting.py +0 -0
  98. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/__init__.py +0 -0
  99. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/bitunix.py +0 -0
  100. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/broker.py +0 -0
  101. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/ccxt.py +0 -0
  102. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/example_broker.py +0 -0
  103. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/interactive_brokers.py +0 -0
  104. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/interactive_brokers_rest.py +0 -0
  105. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/projectx.py +0 -0
  106. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/tradier.py +0 -0
  107. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/brokers/tradovate.py +0 -0
  108. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/components/__init__.py +0 -0
  109. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/components/configs_helper.py +0 -0
  110. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/components/drift_rebalancer_logic.py +0 -0
  111. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/components/grok_helper.py +0 -0
  112. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/components/options_helper.py +0 -0
  113. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/components/perplexity_helper.py +0 -0
  114. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/components/quiver_helper.py +0 -0
  115. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/components/vix_helper.py +0 -0
  116. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/constants.py +0 -0
  117. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/alpaca_data.py +0 -0
  118. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/alpha_vantage_data.py +0 -0
  119. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/bitunix_data.py +0 -0
  120. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/ccxt_backtesting_data.py +0 -0
  121. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/ccxt_data.py +0 -0
  122. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/example_broker_data.py +0 -0
  123. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/exceptions.py +0 -0
  124. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/interactive_brokers_data.py +0 -0
  125. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/interactive_brokers_rest_data.py +0 -0
  126. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/projectx_data.py +0 -0
  127. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/schwab_data.py +0 -0
  128. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/tradier_data.py +0 -0
  129. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/data_sources/tradovate_data.py +0 -0
  130. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/bar.py +0 -0
  131. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/chains.py +0 -0
  132. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/dataline.py +0 -0
  133. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/order.py +0 -0
  134. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/quote.py +0 -0
  135. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/entities/trading_fee.py +0 -0
  136. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/__init__.py +0 -0
  137. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/bitunix_futures_example.py +0 -0
  138. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/ccxt_backtesting_example.py +0 -0
  139. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/classic_60_40.py +0 -0
  140. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/classic_60_40_config.py +0 -0
  141. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/crypto_50_50.py +0 -0
  142. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/crypto_50_50_config.py +0 -0
  143. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/crypto_important_functions.py +0 -0
  144. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/drift_rebalancer.py +0 -0
  145. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/forex_hold_to_expiry.py +0 -0
  146. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/futures_hold_to_expiry.py +0 -0
  147. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/lifecycle_logger.py +0 -0
  148. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/options_hold_to_expiry.py +0 -0
  149. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/schedule_function.py +0 -0
  150. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/simple_start_single_file.py +0 -0
  151. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/stock_bracket.py +0 -0
  152. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/stock_buy_and_hold.py +0 -0
  153. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/stock_diversified_leverage.py +0 -0
  154. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/stock_limit_and_trailing_stops.py +0 -0
  155. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/stock_momentum.py +0 -0
  156. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/stock_oco.py +0 -0
  157. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/strangle.py +0 -0
  158. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/example_strategies/test_broker_functions.py +0 -0
  159. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/resources/ThetaTerminal.jar +0 -0
  160. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/resources/conf.yaml +0 -0
  161. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/strategies/__init__.py +0 -0
  162. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/strategies/session_manager.py +0 -0
  163. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/strategies/strategy_executor.py +0 -0
  164. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/__init__.py +0 -0
  165. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/alpaca_helpers.py +0 -0
  166. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/bitunix_helpers.py +0 -0
  167. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/black_scholes.py +0 -0
  168. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/ccxt_data_store.py +0 -0
  169. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/debugers.py +0 -0
  170. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/decorators.py +0 -0
  171. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/futures_symbols.py +0 -0
  172. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/helpers.py +0 -0
  173. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/lumibot_logger.py +0 -0
  174. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/lumibot_time.py +0 -0
  175. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/pandas.py +0 -0
  176. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/polygon_helper.py +0 -0
  177. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/polygon_helper_async.py +0 -0
  178. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/polygon_helper_polars_optimized.py +0 -0
  179. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/projectx_helpers.py +0 -0
  180. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/schwab_helper.py +0 -0
  181. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/types.py +0 -0
  182. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/yahoo_helper.py +0 -0
  183. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/tools/yahoo_helper_polars_optimized.py +0 -0
  184. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/traders/__init__.py +0 -0
  185. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/traders/debug_log_trader.py +0 -0
  186. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/traders/trader.py +0 -0
  187. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/trading_builtins/__init__.py +0 -0
  188. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/trading_builtins/custom_stream.py +0 -0
  189. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot/trading_builtins/safe_list.py +0 -0
  190. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot.egg-info/dependency_links.txt +0 -0
  191. {lumibot-4.1.3 → lumibot-4.2.0}/lumibot.egg-info/top_level.txt +0 -0
  192. {lumibot-4.1.3 → lumibot-4.2.0}/pyproject.toml +0 -0
  193. {lumibot-4.1.3 → lumibot-4.2.0}/setup.cfg +0 -0
  194. {lumibot-4.1.3 → lumibot-4.2.0}/tests/__init__.py +0 -0
  195. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/__init__.py +0 -0
  196. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/conftest.py +0 -0
  197. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/performance_tracker.py +0 -0
  198. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/profile_thetadata_vs_polygon.py +0 -0
  199. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_accuracy_verification.py +0 -0
  200. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_backtesting_broker_processing.py +0 -0
  201. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_buy_hold_quiet_logs_full_run.py +0 -0
  202. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_crypto_cash_regressions.py +0 -0
  203. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_daily_data_timestamp_comparison.py +0 -0
  204. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_dividends.py +0 -0
  205. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_failing_backtest.py +0 -0
  206. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_index_data_verification.py +0 -0
  207. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_multileg_backtest.py +0 -0
  208. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_pandas_backtest.py +0 -0
  209. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_passing_trader_into_backtest.py +0 -0
  210. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_polygon.py +0 -0
  211. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_strategy_executor.py +0 -0
  212. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_thetadata.py +0 -0
  213. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_thetadata_comprehensive.py +0 -0
  214. {lumibot-4.1.3 → lumibot-4.2.0}/tests/backtest/test_thetadata_vs_polygon.py +0 -0
  215. {lumibot-4.1.3 → lumibot-4.2.0}/tests/conftest.py +0 -0
  216. {lumibot-4.1.3 → lumibot-4.2.0}/tests/fixtures.py +0 -0
  217. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_alpaca.py +0 -0
  218. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_alpaca_auth_fix.py +0 -0
  219. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_alpaca_backtesting.py +0 -0
  220. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_alpaca_data.py +0 -0
  221. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_alpaca_helpers.py +0 -0
  222. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_alpaca_multileg_fix.py +0 -0
  223. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_alpaca_oauth.py +0 -0
  224. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_apscheduler_warnings.py +0 -0
  225. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_asset_auto_expiry.py +0 -0
  226. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_auto_market_inference.py +0 -0
  227. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_backtesting_broker.py +0 -0
  228. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_backtesting_broker_await_close.py +0 -0
  229. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_backtesting_broker_time_advance.py +0 -0
  230. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_backtesting_crypto_cash_unit.py +0 -0
  231. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_backtesting_flow_control.py +0 -0
  232. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_backtesting_multileg_unit.py +0 -0
  233. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_backtesting_quiet_logs_complete.py +0 -0
  234. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_bars_aggregate_frequency_normalization.py +0 -0
  235. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_bars_aggregation_timeunits.py +0 -0
  236. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_bars_frequency_flex.py +0 -0
  237. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_botspot_handler.py +0 -0
  238. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_botspot_logger.py +0 -0
  239. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_broker_bitunix.py +0 -0
  240. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_broker_cleanup.py +0 -0
  241. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_broker_initialization.py +0 -0
  242. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_brokers_handle_crypto.py +0 -0
  243. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_cash.py +0 -0
  244. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_ccxt.py +0 -0
  245. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_ccxt_store.py +0 -0
  246. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_configs_helper.py +0 -0
  247. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_continuous_futures.py +0 -0
  248. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_continuous_futures_integration.py +0 -0
  249. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_data_source.py +0 -0
  250. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_databento_auto_expiry_integration.py +0 -0
  251. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_databento_helper.py +0 -0
  252. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_databento_timezone_fixes.py +0 -0
  253. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_drift_rebalancer.py +0 -0
  254. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_futures_integration.py +0 -0
  255. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_get_historical_prices.py +0 -0
  256. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_helpers.py +0 -0
  257. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_integration_tests.py +0 -0
  258. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_interactive_brokers.py +0 -0
  259. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_live_trading_resilience.py +0 -0
  260. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_logger_env_vars.py +0 -0
  261. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_logging.py +0 -0
  262. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_lumibot_logger.py +0 -0
  263. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_mes_symbols.py +0 -0
  264. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_momentum.py +0 -0
  265. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_options_helper.py +0 -0
  266. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_order.py +0 -0
  267. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_order_serialization.py +0 -0
  268. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_pandas_data.py +0 -0
  269. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_position_serialization.py +0 -0
  270. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx.py +0 -0
  271. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_bracket_helpers.py +0 -0
  272. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_bracket_lifecycle_unit.py +0 -0
  273. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_data.py +0 -0
  274. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_datetime_columns.py +0 -0
  275. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_datetime_index.py +0 -0
  276. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_helpers.py +0 -0
  277. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_lifecycle.py +0 -0
  278. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_lifecycle_unit.py +0 -0
  279. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_live_flow.py +0 -0
  280. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_timestep_alias.py +0 -0
  281. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_projectx_url_mappings.py +0 -0
  282. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_quiet_logs_buy_and_hold.py +0 -0
  283. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_quiet_logs_comprehensive.py +0 -0
  284. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_quiet_logs_functionality.py +0 -0
  285. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_quiet_logs_requirements.py +0 -0
  286. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_session_manager.py +0 -0
  287. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_strategy_methods.py +0 -0
  288. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_tradier.py +0 -0
  289. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_tradier_data.py +0 -0
  290. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_tradingfee.py +0 -0
  291. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_tradovate.py +0 -0
  292. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_unified_logger.py +0 -0
  293. {lumibot-4.1.3 → lumibot-4.2.0}/tests/test_vix_helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumibot
3
- Version: 4.1.3
3
+ Version: 4.2.0
4
4
  Summary: Backtesting and Trading Library, Made by Lumiwealth
5
5
  Home-page: https://github.com/Lumiwealth/lumibot
6
6
  Author: Robert Grzesik
@@ -51,6 +51,7 @@ Requires-Dist: schwab-py>=1.5.0
51
51
  Requires-Dist: Flask>=2.3
52
52
  Requires-Dist: free-proxy
53
53
  Requires-Dist: requests-oauthlib
54
+ Requires-Dist: boto3>=1.28.0
54
55
  Dynamic: author
55
56
  Dynamic: author-email
56
57
  Dynamic: classifier
@@ -146,6 +147,13 @@ To run an individual test file, you can run the following command:
146
147
  pytest tests/test_asset.py
147
148
  ```
148
149
 
150
+ ## Remote Cache Configuration
151
+
152
+ Lumibot can mirror its local parquet caches to AWS S3 when you enable the new
153
+ backtest cache manager. The feature is optional and defaults to local storage.
154
+ To configure the environment variables, understand the key naming convention,
155
+ and follow the manual validation checklist, review `docs/remote_cache.md`.
156
+
149
157
  ### Showing Code Coverage
150
158
 
151
159
  To show code coverage, you can run the following command:
@@ -81,6 +81,13 @@ To run an individual test file, you can run the following command:
81
81
  pytest tests/test_asset.py
82
82
  ```
83
83
 
84
+ ## Remote Cache Configuration
85
+
86
+ Lumibot can mirror its local parquet caches to AWS S3 when you enable the new
87
+ backtest cache manager. The feature is optional and defaults to local storage.
88
+ To configure the environment variables, understand the key naming convention,
89
+ and follow the manual validation checklist, review `docs/remote_cache.md`.
90
+
84
91
  ### Showing Code Coverage
85
92
 
86
93
  To show code coverage, you can run the following command:
@@ -0,0 +1,30 @@
1
+ from .alpaca_backtesting import AlpacaBacktesting
2
+ from .alpha_vantage_backtesting import AlphaVantageBacktesting
3
+ from .backtesting_broker import BacktestingBroker
4
+ from .ccxt_backtesting import CcxtBacktesting
5
+ from .interactive_brokers_rest_backtesting import InteractiveBrokersRESTBacktesting
6
+ from .pandas_backtesting import PandasDataBacktesting
7
+ from .polygon_backtesting import PolygonDataBacktesting
8
+ from .thetadata_backtesting import ThetaDataBacktesting
9
+ from .thetadata_backtesting_pandas import ThetaDataBacktestingPandas
10
+ from .yahoo_backtesting import YahooDataBacktesting
11
+
12
+ from .databento_backtesting import DataBentoDataBacktesting
13
+ from .databento_backtesting_pandas import DataBentoDataBacktestingPandas
14
+ from .databento_backtesting_polars import DataBentoDataBacktestingPolars
15
+
16
+ __all__ = [
17
+ "AlpacaBacktesting",
18
+ "AlphaVantageBacktesting",
19
+ "BacktestingBroker",
20
+ "CcxtBacktesting",
21
+ "InteractiveBrokersRESTBacktesting",
22
+ "PandasDataBacktesting",
23
+ "PolygonDataBacktesting",
24
+ "ThetaDataBacktesting",
25
+ "ThetaDataBacktestingPandas",
26
+ "YahooDataBacktesting",
27
+ "DataBentoDataBacktesting",
28
+ "DataBentoDataBacktestingPandas",
29
+ "DataBentoDataBacktestingPolars",
30
+ ]
@@ -1,3 +1,4 @@
1
+ import math
1
2
  import traceback
2
3
  import threading
3
4
  from collections import OrderedDict
@@ -1353,17 +1354,21 @@ class BacktestingBroker(Broker):
1353
1354
 
1354
1355
  # Get the OHLCV data for the asset if we're using the YAHOO, CCXT data source
1355
1356
  data_source_name = self.data_source.SOURCE.upper()
1356
- if data_source_name in ["CCXT", "YAHOO", "ALPACA", "DATABENTO"]:
1357
- # Default to backing up one minute so fills use the next bar, consistent with other sources.
1357
+ if data_source_name in ["CCXT", "YAHOO", "ALPACA", "DATABENTO", "DATABENTO_POLARS"]:
1358
+ # Negative deltas here are intentional: _pull_source_symbol_bars subtracts the offset, so
1359
+ # passing -1 minute yields an effective +1 minute guard that keeps us on the previously
1360
+ # completed bar. See tests/*_lookahead for regression coverage.
1358
1361
  timeshift = timedelta(minutes=-1)
1359
- if data_source_name == "DATABENTO":
1360
- # DataBento mimics Polygon by requesting two bars to guard against gaps.
1362
+ if data_source_name in {"DATABENTO", "DATABENTO_POLARS"}:
1363
+ # DataBento feeds can skip minutes around maintenance windows. Giving it a two-minute
1364
+ # cushion mirrors the legacy Polygon behaviour and avoids falling through gaps.
1361
1365
  timeshift = timedelta(minutes=-2)
1362
1366
  elif data_source_name == "YAHOO":
1363
- # Yahoo uses day bars; shift one day instead to mirror legacy behavior.
1367
+ # Yahoo daily bars are stamped at the close (16:00). A one-day backstep keeps fills on
1368
+ # the previous session so we never peek at the in-progress bar.
1364
1369
  timeshift = timedelta(days=-1)
1365
1370
  elif data_source_name == "ALPACA":
1366
- # Alpaca minute bars are aligned to the current iteration already.
1371
+ # Alpaca minute bars line up with our clock already; no offset needed.
1367
1372
  timeshift = None
1368
1373
 
1369
1374
  ohlc = self.data_source.get_historical_prices(
@@ -1373,6 +1378,23 @@ class BacktestingBroker(Broker):
1373
1378
  timeshift=timeshift,
1374
1379
  )
1375
1380
 
1381
+ if (
1382
+ ohlc is None
1383
+ or getattr(ohlc, "df", None) is None
1384
+ or (hasattr(ohlc.df, "empty") and ohlc.df.empty)
1385
+ ):
1386
+ if strategy is not None:
1387
+ display_symbol = getattr(order.asset, "symbol", order.asset)
1388
+ order_identifier = getattr(order, "identifier", None)
1389
+ if order_identifier is None:
1390
+ order_identifier = getattr(order, "id", "<unknown>")
1391
+ strategy.log_message(
1392
+ f"[DIAG] No historical bars returned for {display_symbol} at {self.datetime}; "
1393
+ f"pending {order.order_type} id={order_identifier}",
1394
+ color="yellow",
1395
+ )
1396
+ continue
1397
+
1376
1398
  # Handle both pandas and polars DataFrames
1377
1399
  if hasattr(ohlc.df, 'index'): # pandas
1378
1400
  dt = ohlc.df.index[-1]
@@ -1406,6 +1428,16 @@ class BacktestingBroker(Broker):
1406
1428
  )
1407
1429
  # Check if we got any ohlc data
1408
1430
  if ohlc is None or ohlc.empty:
1431
+ if strategy is not None:
1432
+ display_symbol = getattr(order.asset, "symbol", order.asset)
1433
+ order_identifier = getattr(order, "identifier", None)
1434
+ if order_identifier is None:
1435
+ order_identifier = getattr(order, "id", "<unknown>")
1436
+ strategy.log_message(
1437
+ f"[DIAG] No pandas bars for {display_symbol} at {self.datetime}; "
1438
+ f"canceling {order.order_type} id={order_identifier}",
1439
+ color="yellow",
1440
+ )
1409
1441
  self.cancel_order(order)
1410
1442
  continue
1411
1443
 
@@ -1502,41 +1534,89 @@ class BacktestingBroker(Broker):
1502
1534
  strategy=strategy,
1503
1535
  )
1504
1536
  else:
1537
+ if strategy is not None:
1538
+ display_symbol = getattr(order.asset, "symbol", order.asset)
1539
+ order_identifier = getattr(order, "identifier", None)
1540
+ if order_identifier is None:
1541
+ order_identifier = getattr(order, "id", "<unknown>")
1542
+ detail = (
1543
+ f"limit={order.limit_price}, high={high}, low={low}"
1544
+ if order.order_type == Order.OrderType.LIMIT
1545
+ else f"type={order.order_type}, high={high}, low={low}, stop={getattr(order, 'stop_price', None)}"
1546
+ )
1547
+ strategy.log_message(
1548
+ f"[DIAG] Order remained open for {display_symbol} ({detail}) "
1549
+ f"id={order_identifier} at {self.datetime}",
1550
+ color="yellow",
1551
+ )
1505
1552
  continue
1506
1553
 
1507
1554
  # After handling all pending orders, cash settle any residual expired contracts.
1508
1555
  self.process_expired_option_contracts(strategy)
1509
1556
 
1557
+ def _coerce_price(self, value):
1558
+ """Convert numeric inputs to float when possible for safe comparisons."""
1559
+ if value is None:
1560
+ return None
1561
+ try:
1562
+ return float(value)
1563
+ except (TypeError, ValueError):
1564
+ return value
1565
+
1566
+ def _is_invalid_price(self, value):
1567
+ """Determine whether a price is unusable (None or NaN)."""
1568
+ if value is None:
1569
+ return True
1570
+ if isinstance(value, float) and math.isnan(value):
1571
+ return True
1572
+ return False
1573
+
1510
1574
  def limit_order(self, limit_price, side, open_, high, low):
1511
1575
  """Limit order logic."""
1576
+ open_val = self._coerce_price(open_)
1577
+ high_val = self._coerce_price(high)
1578
+ low_val = self._coerce_price(low)
1579
+ limit_val = self._coerce_price(limit_price)
1580
+
1581
+ if any(self._is_invalid_price(val) for val in (open_val, high_val, low_val, limit_val)):
1582
+ return None
1583
+
1512
1584
  # Gap Up case: Limit wasn't triggered by previous candle but current candle opens higher, fill it now
1513
- if side == "sell" and limit_price <= open_:
1514
- return open_
1585
+ if side == "sell" and limit_val <= open_val:
1586
+ return open_val
1515
1587
 
1516
1588
  # Gap Down case: Limit wasn't triggered by previous candle but current candle opens lower, fill it now
1517
- if side == "buy" and limit_price >= open_:
1518
- return open_
1589
+ if side == "buy" and limit_val >= open_val:
1590
+ return open_val
1519
1591
 
1520
1592
  # Current candle triggered limit normally
1521
- if low <= limit_price <= high:
1522
- return limit_price
1593
+ if low_val <= limit_val <= high_val:
1594
+ return limit_val
1523
1595
 
1524
1596
  # Limit has not been met
1525
1597
  return None
1526
1598
 
1527
1599
  def stop_order(self, stop_price, side, open_, high, low):
1528
1600
  """Stop order logic."""
1601
+ open_val = self._coerce_price(open_)
1602
+ high_val = self._coerce_price(high)
1603
+ low_val = self._coerce_price(low)
1604
+ stop_val = self._coerce_price(stop_price)
1605
+
1606
+ if any(self._is_invalid_price(val) for val in (open_val, high_val, low_val, stop_val)):
1607
+ return None
1608
+
1529
1609
  # Gap Down case: Stop wasn't triggered by previous candle but current candle opens lower, fill it now
1530
- if side == "sell" and stop_price >= open_:
1531
- return open_
1610
+ if side == "sell" and stop_val >= open_val:
1611
+ return open_val
1532
1612
 
1533
1613
  # Gap Up case: Stop wasn't triggered by previous candle but current candle opens higher, fill it now
1534
- if side == "buy" and stop_price <= open_:
1535
- return open_
1614
+ if side == "buy" and stop_val <= open_val:
1615
+ return open_val
1536
1616
 
1537
1617
  # Current candle triggered stop normally
1538
- if low <= stop_price <= high:
1539
- return stop_price
1618
+ if low_val <= stop_val <= high_val:
1619
+ return stop_val
1540
1620
 
1541
1621
  # Stop has not been met
1542
1622
  return None
@@ -0,0 +1,7 @@
1
+ """Canonical DataBento backtesting aliasing the Polars implementation."""
2
+
3
+ from .databento_backtesting_polars import DataBentoDataBacktestingPolars as DataBentoDataBacktesting
4
+ from .databento_backtesting_pandas import DataBentoDataBacktestingPandas
5
+ from .databento_backtesting_polars import DataBentoDataBacktestingPolars
6
+
7
+ __all__ = ["DataBentoDataBacktesting", "DataBentoDataBacktestingPandas", "DataBentoDataBacktestingPolars"]
@@ -7,7 +7,9 @@ from lumibot import LUMIBOT_DEFAULT_PYTZ
7
7
  from lumibot.data_sources import PandasData
8
8
  from lumibot.entities import Asset, Data
9
9
  from lumibot.tools import databento_helper
10
+ from lumibot.tools.databento_helper import DataBentoAuthenticationError
10
11
  from lumibot.tools.helpers import to_datetime_aware
12
+ from termcolor import colored
11
13
 
12
14
  from lumibot.tools.lumibot_logger import get_logger
13
15
  logger = get_logger(__name__)
@@ -15,7 +17,7 @@ logger = get_logger(__name__)
15
17
  START_BUFFER = timedelta(days=5)
16
18
 
17
19
 
18
- class DataBentoDataBacktesting(PandasData):
20
+ class DataBentoDataBacktestingPandas(PandasData):
19
21
  """
20
22
  Backtesting implementation of DataBento data source
21
23
 
@@ -85,7 +87,7 @@ class DataBentoDataBacktesting(PandasData):
85
87
  logger.error("DataBento package not available. Please install with: pip install databento")
86
88
  raise ImportError("DataBento package not available")
87
89
 
88
- logger.info(f"DataBento backtesting initialized for period: {datetime_start} to {datetime_end}")
90
+ logger.debug(f"DataBento backtesting initialized for period: {datetime_start} to {datetime_end}")
89
91
 
90
92
  def _check_and_clear_cache(self):
91
93
  """
@@ -157,8 +159,11 @@ class DataBentoDataBacktesting(PandasData):
157
159
  reference_date=self.datetime_start
158
160
  )
159
161
 
160
- logger.info(f"Successfully set multiplier for {asset.symbol}: {asset.multiplier}")
162
+ logger.debug(f"Successfully set multiplier for {asset.symbol}: {asset.multiplier}")
161
163
 
164
+ except DataBentoAuthenticationError as e:
165
+ logger.error(colored(f"DataBento authentication failed while fetching multiplier for {asset.symbol}: {e}", "red"))
166
+ raise
162
167
  except Exception as e:
163
168
  logger.warning(f"Could not fetch multiplier for {asset.symbol}: {e}")
164
169
 
@@ -177,7 +182,7 @@ class DataBentoDataBacktesting(PandasData):
177
182
  if not assets:
178
183
  return
179
184
 
180
- logger.info(f"Prefetching DataBento data for {len(assets)} assets...")
185
+ logger.debug(f"Prefetching DataBento data for {len(assets)} assets...")
181
186
 
182
187
  for asset in assets:
183
188
  # Create search key for the asset
@@ -193,7 +198,7 @@ class DataBentoDataBacktesting(PandasData):
193
198
  start_datetime = self.datetime_start - START_BUFFER
194
199
  end_datetime = self.datetime_end + timedelta(days=1)
195
200
 
196
- logger.info(f"Fetching {asset.symbol} data from {start_datetime.date()} to {end_datetime.date()}")
201
+ logger.debug(f"Fetching {asset.symbol} data from {start_datetime.date()} to {end_datetime.date()}")
197
202
 
198
203
  # Get data from DataBento for entire period
199
204
  df = databento_helper.get_price_data_from_databento(
@@ -231,11 +236,14 @@ class DataBentoDataBacktesting(PandasData):
231
236
  quote=quote_asset,
232
237
  )
233
238
  self.pandas_data[search_asset] = data_obj
234
- logger.info(f"Cached {len(df)} rows for {asset.symbol}")
239
+ logger.debug(f"Cached {len(df)} rows for {asset.symbol}")
235
240
 
236
241
  # Mark as prefetched
237
242
  self._prefetched_assets.add(search_asset)
238
243
 
244
+ except DataBentoAuthenticationError as e:
245
+ logger.error(colored(f"DataBento authentication failed while prefetching {asset.symbol}: {e}", "red"))
246
+ raise
239
247
  except Exception as e:
240
248
  logger.error(f"Error prefetching data for {asset.symbol}: {str(e)}")
241
249
  logger.error(traceback.format_exc())
@@ -317,7 +325,7 @@ class DataBentoDataBacktesting(PandasData):
317
325
  try:
318
326
  # Only log fetch message once per asset/timestep combination
319
327
  if log_key not in self._logged_requests:
320
- logger.info(f"Fetching {timestep} data for {asset_separated.symbol}")
328
+ logger.debug(f"Fetching {timestep} data for {asset_separated.symbol}")
321
329
  self._logged_requests.add(log_key)
322
330
 
323
331
  # Get the start datetime and timestep unit
@@ -373,6 +381,9 @@ class DataBentoDataBacktesting(PandasData):
373
381
 
374
382
  self.pandas_data[search_asset] = data_obj
375
383
 
384
+ except DataBentoAuthenticationError as e:
385
+ logger.error(colored(f"DataBento authentication failed for {asset_separated.symbol}: {e}", "red"))
386
+ raise
376
387
  except Exception as e:
377
388
  logger.error(f"Error updating pandas data for {asset_separated.symbol}: {str(e)}")
378
389
  logger.error(traceback.format_exc())
@@ -458,6 +469,9 @@ class DataBentoDataBacktesting(PandasData):
458
469
  venue=exchange
459
470
  )
460
471
 
472
+ except DataBentoAuthenticationError as e:
473
+ logger.error(colored(f"DataBento authentication failed while getting last price for {asset.symbol}: {e}", "red"))
474
+ raise
461
475
  except Exception as e:
462
476
  logger.error(f"Error getting last price for {asset.symbol}: {e}")
463
477
  return None
@@ -555,6 +569,9 @@ class DataBentoDataBacktesting(PandasData):
555
569
  logger.warning(f"No data found for {asset.symbol}")
556
570
  result[asset] = None
557
571
 
572
+ except DataBentoAuthenticationError as e:
573
+ logger.error(colored(f"DataBento authentication failed while getting bars for {asset}: {e}", "red"))
574
+ raise
558
575
  except Exception as e:
559
576
  logger.error(f"Error getting bars for {asset}: {e}")
560
577
  result[asset] = None
@@ -616,7 +633,28 @@ class DataBentoDataBacktesting(PandasData):
616
633
  df = asset_data.df
617
634
 
618
635
  if not df.empty:
619
- # Apply timeshift if specified
636
+ # ========================================================================
637
+ # CRITICAL: NEGATIVE TIMESHIFT ARITHMETIC FOR LOOKAHEAD
638
+ # ========================================================================
639
+ # Negative timeshift allows broker to "peek ahead" for realistic fills.
640
+ #
641
+ # Example with timeshift=-2 at broker_dt=09:30:
642
+ # - Arithmetic: current_dt - timeshift
643
+ # = 09:30 - timedelta(minutes=-2)
644
+ # = 09:30 - (-2 minutes)
645
+ # = 09:30 + 2 minutes
646
+ # = 09:32
647
+ # - Data source returns bars up to 09:32: [..., 09:29, 09:30, 09:31, 09:32]
648
+ # - Broker filters to future bars (>= 09:30): [09:30, 09:31, 09:32]
649
+ # - Broker uses FIRST future bar (09:31) and its OPEN price for fills
650
+ #
651
+ # Why this is necessary:
652
+ # - Real world: Order placed at 09:30:30 fills at 09:31:00 open
653
+ # - Backtesting: Broker at 09:30 needs to see 09:31 bar for realistic fills
654
+ #
655
+ # DO NOT change this arithmetic! "current_dt - timeshift" with negative
656
+ # timeshift is CORRECT and INTENTIONAL.
657
+ # ========================================================================
620
658
  shift_seconds = 0
621
659
  if timeshift:
622
660
  if isinstance(timeshift, int):
@@ -638,9 +676,21 @@ class DataBentoDataBacktesting(PandasData):
638
676
 
639
677
  cutoff_dt = current_dt_aware - bar_delta
640
678
 
679
+ # INSTRUMENTATION: Log timeshift application and filtering
680
+ broker_dt_orig = self.get_datetime()
681
+ filter_branch = "shift_seconds > 0" if shift_seconds > 0 else "shift_seconds <= 0"
682
+
641
683
  # Filter data up to current backtest time (exclude current bar unless broker overrides)
642
684
  filtered_df = df[df.index <= cutoff_dt] if shift_seconds > 0 else df[df.index < current_dt_aware]
643
685
 
686
+ # Log what bar we're returning
687
+ if not filtered_df.empty:
688
+ returned_bar_dt = filtered_df.index[-1]
689
+ logger.debug(f"[TIMESHIFT_PANDAS] asset={asset_separated.symbol} broker_dt={broker_dt_orig} "
690
+ f"timeshift={timeshift} shift_seconds={shift_seconds} "
691
+ f"shifted_dt={current_dt_aware} cutoff_dt={cutoff_dt} "
692
+ f"filter={filter_branch} returned_bar={returned_bar_dt}")
693
+
644
694
  # Take the last 'length' bars
645
695
  result_df = filtered_df.tail(length)
646
696
 
@@ -685,4 +735,4 @@ class DataBentoDataBacktesting(PandasData):
685
735
  # Prefetch data for all assets
686
736
  self.prefetch_data(assets, timestep)
687
737
 
688
- logger.info(f"Initialized DataBento backtesting with prefetched data for {len(assets)} assets")
738
+ logger.debug(f"Initialized DataBento backtesting with prefetched data for {len(assets)} assets")