lumibot 4.2.1__tar.gz → 4.2.4__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 (279) hide show
  1. {lumibot-4.2.1/lumibot.egg-info → lumibot-4.2.4}/PKG-INFO +1 -1
  2. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/thetadata_backtesting_pandas.py +2 -1
  3. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/strategies/_strategy.py +14 -11
  4. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/thetadata_helper.py +19 -4
  5. {lumibot-4.2.1 → lumibot-4.2.4/lumibot.egg-info}/PKG-INFO +1 -1
  6. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot.egg-info/SOURCES.txt +1 -0
  7. {lumibot-4.2.1 → lumibot-4.2.4}/setup.py +1 -1
  8. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtest_cache_manager.py +7 -7
  9. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtesting_data_source_env.py +3 -0
  10. lumibot-4.2.4/tests/test_backtesting_datetime_normalization.py +90 -0
  11. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_thetadata_helper.py +50 -9
  12. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_thetadata_pandas_verification.py +1 -1
  13. {lumibot-4.2.1 → lumibot-4.2.4}/LICENSE +0 -0
  14. {lumibot-4.2.1 → lumibot-4.2.4}/MANIFEST.in +0 -0
  15. {lumibot-4.2.1 → lumibot-4.2.4}/README.md +0 -0
  16. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/__init__.py +0 -0
  17. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/__init__.py +0 -0
  18. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/alpaca_backtesting.py +0 -0
  19. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/alpha_vantage_backtesting.py +0 -0
  20. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/backtesting_broker.py +0 -0
  21. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/ccxt_backtesting.py +0 -0
  22. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/databento_backtesting.py +0 -0
  23. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/databento_backtesting_pandas.py +0 -0
  24. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/databento_backtesting_polars.py +0 -0
  25. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/fix_debug.py +0 -0
  26. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/interactive_brokers_rest_backtesting.py +0 -0
  27. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/pandas_backtesting.py +0 -0
  28. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/polygon_backtesting.py +0 -0
  29. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/thetadata_backtesting.py +0 -0
  30. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/backtesting/yahoo_backtesting.py +0 -0
  31. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/__init__.py +0 -0
  32. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/alpaca.py +0 -0
  33. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/bitunix.py +0 -0
  34. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/broker.py +0 -0
  35. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/ccxt.py +0 -0
  36. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/example_broker.py +0 -0
  37. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/interactive_brokers.py +0 -0
  38. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/interactive_brokers_rest.py +0 -0
  39. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/projectx.py +0 -0
  40. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/schwab.py +0 -0
  41. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/tradier.py +0 -0
  42. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/brokers/tradovate.py +0 -0
  43. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/components/__init__.py +0 -0
  44. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/components/configs_helper.py +0 -0
  45. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/components/drift_rebalancer_logic.py +0 -0
  46. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/components/grok_helper.py +0 -0
  47. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/components/options_helper.py +0 -0
  48. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/components/perplexity_helper.py +0 -0
  49. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/components/quiver_helper.py +0 -0
  50. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/components/vix_helper.py +0 -0
  51. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/constants.py +0 -0
  52. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/credentials.py +0 -0
  53. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/__init__.py +0 -0
  54. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/alpaca_data.py +0 -0
  55. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/alpha_vantage_data.py +0 -0
  56. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/bitunix_data.py +0 -0
  57. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/ccxt_backtesting_data.py +0 -0
  58. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/ccxt_data.py +0 -0
  59. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/data_source.py +0 -0
  60. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/data_source_backtesting.py +0 -0
  61. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/databento_data.py +0 -0
  62. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/databento_data_pandas.py +0 -0
  63. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/databento_data_polars.py +0 -0
  64. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/example_broker_data.py +0 -0
  65. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/exceptions.py +0 -0
  66. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/interactive_brokers_data.py +0 -0
  67. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/interactive_brokers_rest_data.py +0 -0
  68. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/pandas_data.py +0 -0
  69. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/polars_data.py +0 -0
  70. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/polars_mixin.py +0 -0
  71. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/polygon_data_polars.py +0 -0
  72. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/projectx_data.py +0 -0
  73. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/schwab_data.py +0 -0
  74. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/tradier_data.py +0 -0
  75. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/tradovate_data.py +0 -0
  76. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/yahoo_data.py +0 -0
  77. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/data_sources/yahoo_data_polars.py +0 -0
  78. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/__init__.py +0 -0
  79. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/asset.py +0 -0
  80. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/bar.py +0 -0
  81. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/bars.py +0 -0
  82. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/chains.py +0 -0
  83. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/data.py +0 -0
  84. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/data_polars.py +0 -0
  85. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/dataline.py +0 -0
  86. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/order.py +0 -0
  87. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/position.py +0 -0
  88. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/quote.py +0 -0
  89. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/entities/trading_fee.py +0 -0
  90. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/__init__.py +0 -0
  91. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/bitunix_futures_example.py +0 -0
  92. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/ccxt_backtesting_example.py +0 -0
  93. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/classic_60_40.py +0 -0
  94. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/classic_60_40_config.py +0 -0
  95. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/crypto_50_50.py +0 -0
  96. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/crypto_50_50_config.py +0 -0
  97. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/crypto_important_functions.py +0 -0
  98. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/drift_rebalancer.py +0 -0
  99. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/forex_hold_to_expiry.py +0 -0
  100. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/futures_hold_to_expiry.py +0 -0
  101. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/lifecycle_logger.py +0 -0
  102. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/options_hold_to_expiry.py +0 -0
  103. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/schedule_function.py +0 -0
  104. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/simple_start_single_file.py +0 -0
  105. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/stock_bracket.py +0 -0
  106. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/stock_buy_and_hold.py +0 -0
  107. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/stock_diversified_leverage.py +0 -0
  108. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/stock_limit_and_trailing_stops.py +0 -0
  109. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/stock_momentum.py +0 -0
  110. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/stock_oco.py +0 -0
  111. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/strangle.py +0 -0
  112. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/example_strategies/test_broker_functions.py +0 -0
  113. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/resources/ThetaTerminal.jar +0 -0
  114. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/resources/conf.yaml +0 -0
  115. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/strategies/__init__.py +0 -0
  116. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/strategies/session_manager.py +0 -0
  117. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/strategies/strategy.py +0 -0
  118. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/strategies/strategy_executor.py +0 -0
  119. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/__init__.py +0 -0
  120. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/alpaca_helpers.py +0 -0
  121. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/backtest_cache.py +0 -0
  122. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/bitunix_helpers.py +0 -0
  123. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/black_scholes.py +0 -0
  124. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/ccxt_data_store.py +0 -0
  125. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/databento_helper.py +0 -0
  126. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/databento_helper_polars.py +0 -0
  127. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/debugers.py +0 -0
  128. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/decorators.py +0 -0
  129. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/futures_roll.py +0 -0
  130. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/futures_symbols.py +0 -0
  131. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/helpers.py +0 -0
  132. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/indicators.py +0 -0
  133. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/lumibot_logger.py +0 -0
  134. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/lumibot_time.py +0 -0
  135. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/pandas.py +0 -0
  136. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/polars_utils.py +0 -0
  137. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/polygon_helper.py +0 -0
  138. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/polygon_helper_async.py +0 -0
  139. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/polygon_helper_polars_optimized.py +0 -0
  140. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/projectx_helpers.py +0 -0
  141. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/schwab_helper.py +0 -0
  142. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/types.py +0 -0
  143. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/yahoo_helper.py +0 -0
  144. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/tools/yahoo_helper_polars_optimized.py +0 -0
  145. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/traders/__init__.py +0 -0
  146. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/traders/debug_log_trader.py +0 -0
  147. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/traders/trader.py +0 -0
  148. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/trading_builtins/__init__.py +0 -0
  149. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/trading_builtins/custom_stream.py +0 -0
  150. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot/trading_builtins/safe_list.py +0 -0
  151. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot.egg-info/dependency_links.txt +0 -0
  152. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot.egg-info/requires.txt +0 -0
  153. {lumibot-4.2.1 → lumibot-4.2.4}/lumibot.egg-info/top_level.txt +0 -0
  154. {lumibot-4.2.1 → lumibot-4.2.4}/pyproject.toml +0 -0
  155. {lumibot-4.2.1 → lumibot-4.2.4}/setup.cfg +0 -0
  156. {lumibot-4.2.1 → lumibot-4.2.4}/tests/__init__.py +0 -0
  157. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/__init__.py +0 -0
  158. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/conftest.py +0 -0
  159. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/performance_tracker.py +0 -0
  160. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/profile_thetadata_vs_polygon.py +0 -0
  161. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_accuracy_verification.py +0 -0
  162. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_backtesting_broker_processing.py +0 -0
  163. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_buy_hold_quiet_logs_full_run.py +0 -0
  164. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_crypto_cash_regressions.py +0 -0
  165. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_daily_data_timestamp_comparison.py +0 -0
  166. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_databento.py +0 -0
  167. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_databento_comprehensive_trading.py +0 -0
  168. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_databento_parity.py +0 -0
  169. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_debug_avg_fill_price.py +0 -0
  170. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_dividends.py +0 -0
  171. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_example_strategies.py +0 -0
  172. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_failing_backtest.py +0 -0
  173. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_futures_edge_cases.py +0 -0
  174. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_futures_single_trade.py +0 -0
  175. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_futures_ultra_simple.py +0 -0
  176. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_index_data_verification.py +0 -0
  177. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_multileg_backtest.py +0 -0
  178. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_pandas_backtest.py +0 -0
  179. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_passing_trader_into_backtest.py +0 -0
  180. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_polars_lru_eviction.py +0 -0
  181. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_polygon.py +0 -0
  182. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_strategy_executor.py +0 -0
  183. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_thetadata.py +0 -0
  184. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_thetadata_comprehensive.py +0 -0
  185. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_thetadata_vs_polygon.py +0 -0
  186. {lumibot-4.2.1 → lumibot-4.2.4}/tests/backtest/test_yahoo.py +0 -0
  187. {lumibot-4.2.1 → lumibot-4.2.4}/tests/conftest.py +0 -0
  188. {lumibot-4.2.1 → lumibot-4.2.4}/tests/fixtures.py +0 -0
  189. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_alpaca.py +0 -0
  190. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_alpaca_auth_fix.py +0 -0
  191. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_alpaca_backtesting.py +0 -0
  192. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_alpaca_data.py +0 -0
  193. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_alpaca_helpers.py +0 -0
  194. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_alpaca_multileg_fix.py +0 -0
  195. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_alpaca_oauth.py +0 -0
  196. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_apscheduler_warnings.py +0 -0
  197. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_asset.py +0 -0
  198. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_asset_auto_expiry.py +0 -0
  199. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_auto_market_inference.py +0 -0
  200. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtesting_broker.py +0 -0
  201. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtesting_broker_await_close.py +0 -0
  202. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtesting_broker_time_advance.py +0 -0
  203. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtesting_crypto_cash_unit.py +0 -0
  204. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtesting_flow_control.py +0 -0
  205. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtesting_multileg_unit.py +0 -0
  206. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_backtesting_quiet_logs_complete.py +0 -0
  207. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_bars_aggregate_frequency_normalization.py +0 -0
  208. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_bars_aggregation_timeunits.py +0 -0
  209. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_bars_frequency_flex.py +0 -0
  210. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_botspot_handler.py +0 -0
  211. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_botspot_logger.py +0 -0
  212. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_broker_bitunix.py +0 -0
  213. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_broker_cleanup.py +0 -0
  214. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_broker_initialization.py +0 -0
  215. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_brokers_handle_crypto.py +0 -0
  216. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_cash.py +0 -0
  217. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_ccxt.py +0 -0
  218. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_ccxt_store.py +0 -0
  219. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_configs_helper.py +0 -0
  220. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_continuous_futures.py +0 -0
  221. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_continuous_futures_integration.py +0 -0
  222. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_continuous_futures_resolution.py +0 -0
  223. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_data_polars_parity.py +0 -0
  224. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_data_source.py +0 -0
  225. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_databento_asset_validation.py +0 -0
  226. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_databento_auto_expiry_integration.py +0 -0
  227. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_databento_backtesting.py +0 -0
  228. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_databento_backtesting_polars.py +0 -0
  229. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_databento_data.py +0 -0
  230. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_databento_helper.py +0 -0
  231. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_databento_live.py +0 -0
  232. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_databento_timezone_fixes.py +0 -0
  233. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_drift_rebalancer.py +0 -0
  234. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_futures_integration.py +0 -0
  235. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_futures_roll.py +0 -0
  236. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_get_historical_prices.py +0 -0
  237. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_helpers.py +0 -0
  238. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_indicator_subplots.py +0 -0
  239. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_integration_tests.py +0 -0
  240. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_interactive_brokers.py +0 -0
  241. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_live_trading_resilience.py +0 -0
  242. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_logger_env_vars.py +0 -0
  243. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_logging.py +0 -0
  244. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_lumibot_logger.py +0 -0
  245. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_market_infinite_loop_bug.py +0 -0
  246. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_mes_symbols.py +0 -0
  247. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_momentum.py +0 -0
  248. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_options_helper.py +0 -0
  249. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_order.py +0 -0
  250. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_order_serialization.py +0 -0
  251. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_pandas_data.py +0 -0
  252. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_polars_resample.py +0 -0
  253. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_polygon_helper.py +0 -0
  254. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_position_serialization.py +0 -0
  255. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx.py +0 -0
  256. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_bracket_helpers.py +0 -0
  257. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_bracket_lifecycle_unit.py +0 -0
  258. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_data.py +0 -0
  259. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_datetime_columns.py +0 -0
  260. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_datetime_index.py +0 -0
  261. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_helpers.py +0 -0
  262. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_lifecycle.py +0 -0
  263. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_lifecycle_unit.py +0 -0
  264. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_live_flow.py +0 -0
  265. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_timestep_alias.py +0 -0
  266. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_projectx_url_mappings.py +0 -0
  267. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_quiet_logs_buy_and_hold.py +0 -0
  268. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_quiet_logs_comprehensive.py +0 -0
  269. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_quiet_logs_functionality.py +0 -0
  270. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_quiet_logs_requirements.py +0 -0
  271. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_session_manager.py +0 -0
  272. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_strategy_methods.py +0 -0
  273. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_thetadata_backwards_compat.py +0 -0
  274. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_tradier.py +0 -0
  275. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_tradier_data.py +0 -0
  276. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_tradingfee.py +0 -0
  277. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_tradovate.py +0 -0
  278. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_unified_logger.py +0 -0
  279. {lumibot-4.2.1 → lumibot-4.2.4}/tests/test_vix_helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumibot
3
- Version: 4.2.1
3
+ Version: 4.2.4
4
4
  Summary: Backtesting and Trading Library, Made by Lumiwealth
5
5
  Home-page: https://github.com/Lumiwealth/lumibot
6
6
  Author: Robert Grzesik
@@ -288,7 +288,8 @@ class ThetaDataBacktestingPandas(PandasData):
288
288
  )
289
289
 
290
290
  expected_last_dt = self.to_default_timezone(current_dt).replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=1)
291
- target_index = pd.date_range(end=expected_last_dt, periods=requested_length, freq="D", tz=self.tzinfo)
291
+ expected_last_dt_utc = expected_last_dt.astimezone(pytz.UTC)
292
+ target_index = pd.date_range(end=expected_last_dt_utc, periods=requested_length, freq="D", tz=pytz.UTC).tz_convert(self.tzinfo)
292
293
 
293
294
  # DEBUG-LOG: Target index details
294
295
  logger.debug(
@@ -1388,12 +1388,6 @@ class _Strategy:
1388
1388
  if use_other_option_source and not isinstance(optionsource_class, type):
1389
1389
  raise ValueError(f"`optionsource_class` must be a class. You passed in {optionsource_class}")
1390
1390
 
1391
- self.verify_backtest_inputs(backtesting_start, backtesting_end)
1392
-
1393
- if not self.IS_BACKTESTABLE:
1394
- get_logger(__name__).warning(f"Strategy {name + ' ' if name is not None else ''}cannot be " f"backtested at the moment")
1395
- return None
1396
-
1397
1391
  try:
1398
1392
  backtesting_start = to_datetime_aware(backtesting_start)
1399
1393
  backtesting_end = to_datetime_aware(backtesting_end)
@@ -1405,6 +1399,12 @@ class _Strategy:
1405
1399
  )
1406
1400
  return None
1407
1401
 
1402
+ self.verify_backtest_inputs(backtesting_start, backtesting_end)
1403
+
1404
+ if not self.IS_BACKTESTABLE:
1405
+ get_logger(__name__).warning(f"Strategy {name + ' ' if name is not None else ''}cannot be " f"backtested at the moment")
1406
+ return None
1407
+
1408
1408
  if BACKTESTING_QUIET_LOGS is not None:
1409
1409
  quiet_logs = BACKTESTING_QUIET_LOGS
1410
1410
 
@@ -1628,18 +1628,21 @@ class _Strategy:
1628
1628
  if not isinstance(backtesting_end, datetime.datetime):
1629
1629
  raise ValueError(f"`backtesting_end` must be a datetime object. You passed in {backtesting_end}")
1630
1630
 
1631
+ start_dt = to_datetime_aware(backtesting_start)
1632
+ end_dt = to_datetime_aware(backtesting_end)
1633
+
1631
1634
  # Check that backtesting end is after backtesting start
1632
- if backtesting_end <= backtesting_start:
1635
+ if end_dt <= start_dt:
1633
1636
  raise ValueError(
1634
1637
  f"`backtesting_end` must be after `backtesting_start`. You passed in "
1635
- f"{backtesting_end} and {backtesting_start}"
1638
+ f"{end_dt} and {start_dt}"
1636
1639
  )
1637
1640
 
1638
1641
  # Check that backtesting_end is not in the future
1639
- now = datetime.datetime.now(backtesting_end.tzinfo) if backtesting_end.tzinfo else datetime.datetime.now()
1640
- if backtesting_end > now:
1642
+ now = datetime.datetime.now(end_dt.tzinfo) if end_dt.tzinfo else datetime.datetime.now()
1643
+ if end_dt > now:
1641
1644
  raise ValueError(
1642
- f"`backtesting_end` cannot be in the future. You passed in {backtesting_end}, now is {now}"
1645
+ f"`backtesting_end` cannot be in the future. You passed in {end_dt}, now is {now}"
1643
1646
  )
1644
1647
 
1645
1648
  def send_update_to_cloud(self):
@@ -26,6 +26,17 @@ CONNECTION_MAX_RETRIES = 60
26
26
  BOOT_GRACE_PERIOD = 5.0
27
27
  MAX_RESTART_ATTEMPTS = 3
28
28
 
29
+
30
+ def _resolve_asset_folder(asset_obj: Asset) -> str:
31
+ asset_type = getattr(asset_obj, "asset_type", None) or "stock"
32
+ asset_key = str(asset_type).strip().lower()
33
+ return asset_key
34
+
35
+
36
+ def _normalize_folder_component(value: str, fallback: str) -> str:
37
+ normalized = str(value or "").strip().lower().replace(" ", "_")
38
+ return normalized or fallback
39
+
29
40
  # Global process tracking for ThetaTerminal
30
41
  THETA_DATA_PROCESS = None
31
42
  THETA_DATA_PID = None
@@ -785,7 +796,11 @@ def get_trading_dates(asset: Asset, start: datetime, end: datetime):
785
796
  def build_cache_filename(asset: Asset, timespan: str, datastyle: str = "ohlc"):
786
797
  """Helper function to create the cache filename for a given asset and timespan"""
787
798
 
788
- lumibot_cache_folder = Path(LUMIBOT_CACHE_FOLDER) / CACHE_SUBFOLDER
799
+ provider_root = Path(LUMIBOT_CACHE_FOLDER) / CACHE_SUBFOLDER
800
+ asset_folder = _resolve_asset_folder(asset)
801
+ timespan_folder = _normalize_folder_component(timespan, "unknown")
802
+ datastyle_folder = _normalize_folder_component(datastyle, "default")
803
+ base_folder = provider_root / asset_folder / timespan_folder / datastyle_folder
789
804
 
790
805
  # If It's an option then also add the expiration date, strike price and right to the filename
791
806
  if asset.asset_type == "option":
@@ -799,7 +814,7 @@ def build_cache_filename(asset: Asset, timespan: str, datastyle: str = "ohlc"):
799
814
  uniq_str = asset.symbol
800
815
 
801
816
  cache_filename = f"{asset.asset_type}_{uniq_str}_{timespan}_{datastyle}.parquet"
802
- cache_file = lumibot_cache_folder / cache_filename
817
+ cache_file = base_folder / cache_filename
803
818
  return cache_file
804
819
 
805
820
 
@@ -1969,7 +1984,7 @@ def get_chains_cached(
1969
1984
  Retrieve option chain with caching (MATCHES POLYGON PATTERN).
1970
1985
 
1971
1986
  This function follows the EXACT same caching strategy as Polygon:
1972
- 1. Check cache: LUMIBOT_CACHE_FOLDER/thetadata/option_chains/{symbol}_{date}.parquet
1987
+ 1. Check cache: LUMIBOT_CACHE_FOLDER/thetadata/<asset-type>/option_chains/{symbol}_{date}.parquet
1973
1988
  2. Reuse files within RECENT_FILE_TOLERANCE_DAYS (default 7 days)
1974
1989
  3. If not found, fetch from ThetaData and save to cache
1975
1990
  4. Use pyarrow engine with snappy compression
@@ -2006,7 +2021,7 @@ def get_chains_cached(
2006
2021
  return None
2007
2022
 
2008
2023
  # 2) Build cache folder path
2009
- chain_folder = Path(LUMIBOT_CACHE_FOLDER) / "thetadata" / "option_chains"
2024
+ chain_folder = Path(LUMIBOT_CACHE_FOLDER) / "thetadata" / _resolve_asset_folder(asset) / "option_chains"
2010
2025
  chain_folder.mkdir(parents=True, exist_ok=True)
2011
2026
 
2012
2027
  # 3) Check for recent cached file (within RECENT_FILE_TOLERANCE_DAYS)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumibot
3
- Version: 4.2.1
3
+ Version: 4.2.4
4
4
  Summary: Backtesting and Trading Library, Made by Lumiwealth
5
5
  Home-page: https://github.com/Lumiwealth/lumibot
6
6
  Author: Robert Grzesik
@@ -167,6 +167,7 @@ tests/test_backtesting_broker_await_close.py
167
167
  tests/test_backtesting_broker_time_advance.py
168
168
  tests/test_backtesting_crypto_cash_unit.py
169
169
  tests/test_backtesting_data_source_env.py
170
+ tests/test_backtesting_datetime_normalization.py
170
171
  tests/test_backtesting_flow_control.py
171
172
  tests/test_backtesting_multileg_unit.py
172
173
  tests/test_backtesting_quiet_logs_complete.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.2.1",
8
+ version="4.2.4",
9
9
  author="Robert Grzesik",
10
10
  author_email="rob@lumiwealth.com",
11
11
  description="Backtesting and Trading Library, Made by Lumiwealth",
@@ -67,7 +67,7 @@ def _build_settings(prefix: str = "prod/cache") -> BacktestCacheSettings:
67
67
  def test_remote_key_uses_relative_cache_path(tmp_path, monkeypatch):
68
68
  cache_root = tmp_path / "cache"
69
69
  cache_root.mkdir()
70
- local_file = cache_root / "thetadata" / "bars" / "spy.parquet"
70
+ local_file = cache_root / "thetadata" / "stock" / "minute" / "ohlc" / "stock_SPY_minute_ohlc.parquet"
71
71
  local_file.parent.mkdir(parents=True, exist_ok=True)
72
72
 
73
73
  monkeypatch.setattr(backtest_cache, "LUMIBOT_CACHE_FOLDER", cache_root)
@@ -76,17 +76,17 @@ def test_remote_key_uses_relative_cache_path(tmp_path, monkeypatch):
76
76
  manager = BacktestCacheManager(settings, client_factory=lambda settings: StubS3Client())
77
77
 
78
78
  remote_key = manager.remote_key_for(local_file)
79
- assert remote_key == "stage/cache/v3/thetadata/bars/spy.parquet"
79
+ assert remote_key == "stage/cache/v3/thetadata/stock/minute/ohlc/stock_SPY_minute_ohlc.parquet"
80
80
 
81
81
 
82
82
  def test_ensure_local_file_downloads_from_s3(tmp_path, monkeypatch):
83
83
  cache_root = tmp_path / "cache"
84
84
  cache_root.mkdir()
85
- local_file = cache_root / "thetadata" / "bars" / "spy.parquet"
85
+ local_file = cache_root / "thetadata" / "stock" / "minute" / "ohlc" / "stock_SPY_minute_ohlc.parquet"
86
86
 
87
87
  monkeypatch.setattr(backtest_cache, "LUMIBOT_CACHE_FOLDER", cache_root)
88
88
 
89
- remote_key = "stage/cache/v3/thetadata/bars/spy.parquet"
89
+ remote_key = "stage/cache/v3/thetadata/stock/minute/ohlc/stock_SPY_minute_ohlc.parquet"
90
90
  objects = {("test-bucket", remote_key): b"cached-data"}
91
91
 
92
92
  stub = StubS3Client(objects)
@@ -101,7 +101,7 @@ def test_ensure_local_file_downloads_from_s3(tmp_path, monkeypatch):
101
101
  def test_ensure_local_file_handles_missing_remote(tmp_path, monkeypatch):
102
102
  cache_root = tmp_path / "cache"
103
103
  cache_root.mkdir()
104
- local_file = cache_root / "thetadata" / "bars" / "spy.parquet"
104
+ local_file = cache_root / "thetadata" / "stock" / "minute" / "ohlc" / "stock_SPY_minute_ohlc.parquet"
105
105
 
106
106
  monkeypatch.setattr(backtest_cache, "LUMIBOT_CACHE_FOLDER", cache_root)
107
107
 
@@ -116,13 +116,13 @@ def test_ensure_local_file_handles_missing_remote(tmp_path, monkeypatch):
116
116
  def test_on_local_update_uploads_file(tmp_path, monkeypatch):
117
117
  cache_root = tmp_path / "cache"
118
118
  cache_root.mkdir()
119
- local_file = cache_root / "thetadata" / "bars" / "spy.parquet"
119
+ local_file = cache_root / "thetadata" / "stock" / "minute" / "ohlc" / "stock_SPY_minute_ohlc.parquet"
120
120
  local_file.parent.mkdir(parents=True, exist_ok=True)
121
121
  local_file.write_bytes(b"new-data")
122
122
 
123
123
  monkeypatch.setattr(backtest_cache, "LUMIBOT_CACHE_FOLDER", cache_root)
124
124
 
125
- remote_key = "stage/cache/v3/thetadata/bars/spy.parquet"
125
+ remote_key = "stage/cache/v3/thetadata/stock/minute/ohlc/stock_SPY_minute_ohlc.parquet"
126
126
  stub = StubS3Client({("test-bucket", remote_key): b"old"})
127
127
  manager = BacktestCacheManager(_build_settings(prefix="stage/cache"), client_factory=lambda s: stub)
128
128
 
@@ -75,6 +75,9 @@ class TestBacktestingDataSourceEnv:
75
75
  # Configure caplog to capture INFO level logs from lumibot.strategies._strategy
76
76
  import logging
77
77
  caplog.set_level(logging.INFO, logger='lumibot.strategies._strategy')
78
+ polygon_key = os.environ.get("POLYGON_API_KEY")
79
+ if not polygon_key:
80
+ pytest.skip("Polygon API key not configured")
78
81
 
79
82
  with patch.dict(os.environ, {'BACKTESTING_DATA_SOURCE': 'polygon'}):
80
83
  # Re-import credentials to pick up env change
@@ -0,0 +1,90 @@
1
+ import datetime
2
+ from unittest.mock import patch
3
+
4
+ import pytest
5
+
6
+ from lumibot.strategies import Strategy
7
+ from lumibot.strategies._strategy import _Strategy
8
+
9
+
10
+ class MinimalStrategy(Strategy):
11
+ """No-op strategy used for backtest scaffolding."""
12
+
13
+ def initialize(self):
14
+ self.sleeptime = "1D"
15
+
16
+ def on_trading_iteration(self):
17
+ pass
18
+
19
+
20
+ class DummyDataSource:
21
+ """Lightweight datasource stub capturing the start/end datetimes."""
22
+
23
+ SOURCE = "dummy"
24
+
25
+ def __init__(self, datetime_start=None, datetime_end=None, **kwargs):
26
+ self.datetime_start = datetime_start
27
+ self.datetime_end = datetime_end
28
+ self._data_store = {}
29
+
30
+
31
+ class DummyTrader:
32
+ """Trader stub that records strategies and returns canned results."""
33
+
34
+ def __init__(self, *args, **kwargs):
35
+ self._strategies = []
36
+
37
+ def add_strategy(self, strategy):
38
+ self._strategies.append(strategy)
39
+
40
+ def run_all(self, **_kwargs):
41
+ return {strategy.name: {"dummy": True} for strategy in self._strategies}
42
+
43
+
44
+ class _EarlyExit(Exception):
45
+ """Signal to stop run_backtest after the datasource is constructed."""
46
+
47
+
48
+ def test_verify_backtest_inputs_accepts_mixed_timezones():
49
+ """Regression: verify_backtest_inputs must not crash on naive vs aware inputs."""
50
+ naive_start = datetime.datetime(2025, 1, 1)
51
+ aware_end = datetime.datetime(2025, 9, 30, tzinfo=datetime.timezone.utc)
52
+
53
+ # Should not raise
54
+ _Strategy.verify_backtest_inputs(naive_start, aware_end)
55
+
56
+
57
+ def test_run_backtest_normalizes_mixed_timezones():
58
+ """Strategy.run_backtest should normalize naive/aware datetimes before validation."""
59
+ naive_start = datetime.datetime(2025, 1, 1)
60
+ aware_end = datetime.datetime(2025, 9, 30, tzinfo=datetime.timezone.utc)
61
+
62
+ captured = {}
63
+
64
+ class CapturingDataSource(DummyDataSource):
65
+ def __init__(self, datetime_start=None, datetime_end=None, **kwargs):
66
+ super().__init__(datetime_start=datetime_start, datetime_end=datetime_end, **kwargs)
67
+ captured["start"] = self.datetime_start
68
+ captured["end"] = self.datetime_end
69
+
70
+ def broker_factory(data_source, *args, **kwargs):
71
+ captured["data_source"] = data_source
72
+ raise _EarlyExit
73
+
74
+ with patch("lumibot.strategies._strategy.BacktestingBroker", side_effect=broker_factory), \
75
+ patch("lumibot.strategies._strategy.Trader", DummyTrader):
76
+ with pytest.raises(_EarlyExit):
77
+ MinimalStrategy.run_backtest(
78
+ CapturingDataSource,
79
+ backtesting_start=naive_start,
80
+ backtesting_end=aware_end,
81
+ show_plot=False,
82
+ show_tearsheet=False,
83
+ show_indicators=False,
84
+ show_progress_bar=False,
85
+ save_logfile=False,
86
+ save_stats_file=False,
87
+ )
88
+
89
+ assert "start" in captured and captured["start"].tzinfo is not None
90
+ assert "end" in captured and captured["end"].tzinfo is not None
@@ -324,13 +324,13 @@ def test_get_trading_dates():
324
324
  def test_build_cache_filename(mocker, tmpdir, datastyle):
325
325
  asset = Asset("SPY")
326
326
  timespan = "1D"
327
- mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", tmpdir)
328
- expected = tmpdir / "thetadata" / f"stock_SPY_1D_{datastyle}.parquet"
327
+ mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", str(tmpdir))
328
+ expected = tmpdir / "thetadata" / "stock" / "1d" / datastyle / f"stock_SPY_1D_{datastyle}.parquet"
329
329
  assert thetadata_helper.build_cache_filename(asset, timespan, datastyle) == expected
330
330
 
331
331
  expire_date = datetime.date(2023, 8, 1)
332
332
  option_asset = Asset("SPY", asset_type="option", expiration=expire_date, strike=100, right="CALL")
333
- expected = tmpdir / "thetadata" / f"option_SPY_230801_100_CALL_1D_{datastyle}.parquet"
333
+ expected = tmpdir / "thetadata" / "option" / "1d" / datastyle / f"option_SPY_230801_100_CALL_1D_{datastyle}.parquet"
334
334
  assert thetadata_helper.build_cache_filename(option_asset, timespan, datastyle) == expected
335
335
 
336
336
  # Bad option asset with no expiration
@@ -427,8 +427,8 @@ def test_missing_dates():
427
427
  ],
428
428
  )
429
429
  def test_update_cache(mocker, tmpdir, df_all, df_cached, datastyle):
430
- mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", tmpdir)
431
- cache_file = Path(tmpdir / "thetadata" / f"stock_SPY_1D_{datastyle}.parquet")
430
+ mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", str(tmpdir))
431
+ cache_file = thetadata_helper.build_cache_filename(Asset("SPY"), "1D", datastyle)
432
432
 
433
433
  # Empty DataFrame of df_all, don't write cache file
434
434
  thetadata_helper.update_cache(cache_file, df_all, df_cached)
@@ -550,8 +550,9 @@ def test_get_price_data_invokes_remote_cache_manager(tmp_path, monkeypatch):
550
550
  )
551
551
  def test_load_data_from_cache(mocker, tmpdir, df_cached, datastyle):
552
552
  # Setup some basics
553
- mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", tmpdir)
554
- cache_file = Path(tmpdir / "thetadata" / f"stock_SPY_1D_{datastyle}.parquet")
553
+ mocker.patch.object(thetadata_helper, "LUMIBOT_CACHE_FOLDER", str(tmpdir))
554
+ asset = Asset("SPY")
555
+ cache_file = thetadata_helper.build_cache_filename(asset, "1D", datastyle)
555
556
 
556
557
  # No cache file should return None (not raise)
557
558
  assert thetadata_helper.load_cache(cache_file) is None
@@ -1371,8 +1372,8 @@ class TestThetaDataChainsCaching:
1371
1372
 
1372
1373
  # CLEAR CACHE to ensure first call downloads fresh data
1373
1374
  # This prevents cache pollution from previous tests in the suite
1374
- # Chains are stored in: LUMIBOT_CACHE_FOLDER / "thetadata" / "option_chains"
1375
- chain_folder = Path(LUMIBOT_CACHE_FOLDER) / "thetadata" / "option_chains"
1375
+ # Chains are stored in: LUMIBOT_CACHE_FOLDER / "thetadata" / "option" / "option_chains"
1376
+ chain_folder = Path(LUMIBOT_CACHE_FOLDER) / "thetadata" / "option" / "option_chains"
1376
1377
  if chain_folder.exists():
1377
1378
  # Delete all AAPL chain cache files
1378
1379
  for cache_file in chain_folder.glob("AAPL_*.parquet"):
@@ -1407,6 +1408,46 @@ class TestThetaDataChainsCaching:
1407
1408
  assert time2 < time1 * 0.1, f"Cache not working: time1={time1:.2f}s, time2={time2:.2f}s (should be 10x faster)"
1408
1409
  print(f"✓ Cache speedup: {time1/time2:.1f}x faster ({time1:.2f}s -> {time2:.4f}s)")
1409
1410
 
1411
+
1412
+ def test_finalize_day_frame_handles_dst_fallback():
1413
+ tz = pytz.timezone("America/New_York")
1414
+ utc = pytz.UTC
1415
+ frame_index = pd.date_range(
1416
+ end=tz.localize(datetime.datetime(2024, 10, 31, 16, 0)),
1417
+ periods=5,
1418
+ freq="D",
1419
+ )
1420
+ frame = pd.DataFrame(
1421
+ {
1422
+ "open": [100 + i for i in range(len(frame_index))],
1423
+ "high": [101 + i for i in range(len(frame_index))],
1424
+ "low": [99 + i for i in range(len(frame_index))],
1425
+ "close": [100.5 + i for i in range(len(frame_index))],
1426
+ "volume": [1000 + i for i in range(len(frame_index))],
1427
+ },
1428
+ index=frame_index,
1429
+ )
1430
+
1431
+ data_source = ThetaDataBacktestingPandas(
1432
+ datetime_start=utc.localize(datetime.datetime(2024, 10, 1)),
1433
+ datetime_end=utc.localize(datetime.datetime(2024, 11, 5)),
1434
+ username="user",
1435
+ password="pass",
1436
+ use_quote_data=False,
1437
+ )
1438
+
1439
+ current_dt = utc.localize(datetime.datetime(2024, 11, 4, 13, 30))
1440
+ result = data_source._finalize_day_frame(
1441
+ frame,
1442
+ current_dt,
1443
+ requested_length=len(frame_index),
1444
+ timeshift=None,
1445
+ asset=Asset("TSLA"),
1446
+ )
1447
+
1448
+ assert result is not None
1449
+ assert len(result) == len(frame_index)
1450
+
1410
1451
  def test_chains_strike_format(self):
1411
1452
  """Test strikes are floats (not integers) and properly converted."""
1412
1453
  username = os.environ.get("THETADATA_USERNAME")
@@ -44,7 +44,7 @@ def count_cache_files():
44
44
  cache_dir = get_cache_dir()
45
45
  if not cache_dir.exists():
46
46
  return 0
47
- return len(list(cache_dir.glob("*.parquet")))
47
+ return sum(1 for _ in cache_dir.rglob("*.parquet"))
48
48
 
49
49
 
50
50
  class WeeklyMomentumOptionsStrategy(Strategy):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes