lumibot 4.1.2__py3-none-any.whl → 4.2.0__py3-none-any.whl

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 (164) hide show
  1. lumibot/backtesting/__init__.py +19 -5
  2. lumibot/backtesting/backtesting_broker.py +98 -18
  3. lumibot/backtesting/databento_backtesting.py +5 -686
  4. lumibot/backtesting/databento_backtesting_pandas.py +738 -0
  5. lumibot/backtesting/databento_backtesting_polars.py +860 -546
  6. lumibot/backtesting/fix_debug.py +37 -0
  7. lumibot/backtesting/thetadata_backtesting.py +9 -355
  8. lumibot/backtesting/thetadata_backtesting_pandas.py +1178 -0
  9. lumibot/brokers/alpaca.py +8 -1
  10. lumibot/brokers/schwab.py +12 -2
  11. lumibot/credentials.py +13 -0
  12. lumibot/data_sources/__init__.py +5 -8
  13. lumibot/data_sources/data_source.py +6 -2
  14. lumibot/data_sources/data_source_backtesting.py +30 -0
  15. lumibot/data_sources/databento_data.py +5 -390
  16. lumibot/data_sources/databento_data_pandas.py +440 -0
  17. lumibot/data_sources/databento_data_polars.py +15 -9
  18. lumibot/data_sources/pandas_data.py +30 -17
  19. lumibot/data_sources/polars_data.py +986 -0
  20. lumibot/data_sources/polars_mixin.py +472 -96
  21. lumibot/data_sources/polygon_data_polars.py +5 -0
  22. lumibot/data_sources/yahoo_data.py +9 -2
  23. lumibot/data_sources/yahoo_data_polars.py +5 -0
  24. lumibot/entities/__init__.py +15 -0
  25. lumibot/entities/asset.py +5 -28
  26. lumibot/entities/bars.py +89 -20
  27. lumibot/entities/data.py +29 -6
  28. lumibot/entities/data_polars.py +668 -0
  29. lumibot/entities/position.py +38 -4
  30. lumibot/strategies/_strategy.py +31 -9
  31. lumibot/strategies/strategy.py +61 -49
  32. lumibot/tools/backtest_cache.py +284 -0
  33. lumibot/tools/databento_helper.py +65 -42
  34. lumibot/tools/databento_helper_polars.py +748 -778
  35. lumibot/tools/futures_roll.py +251 -0
  36. lumibot/tools/indicators.py +135 -104
  37. lumibot/tools/polars_utils.py +142 -0
  38. lumibot/tools/thetadata_helper.py +1068 -134
  39. {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/METADATA +9 -1
  40. {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/RECORD +72 -148
  41. tests/backtest/test_databento.py +37 -6
  42. tests/backtest/test_databento_comprehensive_trading.py +70 -87
  43. tests/backtest/test_databento_parity.py +31 -7
  44. tests/backtest/test_debug_avg_fill_price.py +1 -1
  45. tests/backtest/test_example_strategies.py +11 -1
  46. tests/backtest/test_futures_edge_cases.py +96 -63
  47. tests/backtest/test_futures_single_trade.py +2 -2
  48. tests/backtest/test_futures_ultra_simple.py +2 -2
  49. tests/backtest/test_polars_lru_eviction.py +470 -0
  50. tests/backtest/test_yahoo.py +42 -0
  51. tests/test_asset.py +4 -4
  52. tests/test_backtest_cache_manager.py +149 -0
  53. tests/test_backtesting_data_source_env.py +50 -10
  54. tests/test_continuous_futures_resolution.py +60 -48
  55. tests/test_data_polars_parity.py +160 -0
  56. tests/test_databento_asset_validation.py +23 -5
  57. tests/test_databento_backtesting.py +1 -1
  58. tests/test_databento_backtesting_polars.py +312 -192
  59. tests/test_databento_data.py +220 -463
  60. tests/test_databento_helper.py +6 -1
  61. tests/test_databento_live.py +10 -10
  62. tests/test_futures_roll.py +38 -0
  63. tests/test_indicator_subplots.py +101 -0
  64. tests/test_market_infinite_loop_bug.py +77 -3
  65. tests/test_polars_resample.py +67 -0
  66. tests/test_polygon_helper.py +46 -0
  67. tests/test_thetadata_backwards_compat.py +97 -0
  68. tests/test_thetadata_helper.py +222 -23
  69. tests/test_thetadata_pandas_verification.py +186 -0
  70. lumibot/__pycache__/__init__.cpython-312.pyc +0 -0
  71. lumibot/__pycache__/constants.cpython-312.pyc +0 -0
  72. lumibot/__pycache__/credentials.cpython-312.pyc +0 -0
  73. lumibot/backtesting/__pycache__/__init__.cpython-312.pyc +0 -0
  74. lumibot/backtesting/__pycache__/alpaca_backtesting.cpython-312.pyc +0 -0
  75. lumibot/backtesting/__pycache__/alpha_vantage_backtesting.cpython-312.pyc +0 -0
  76. lumibot/backtesting/__pycache__/backtesting_broker.cpython-312.pyc +0 -0
  77. lumibot/backtesting/__pycache__/ccxt_backtesting.cpython-312.pyc +0 -0
  78. lumibot/backtesting/__pycache__/databento_backtesting.cpython-312.pyc +0 -0
  79. lumibot/backtesting/__pycache__/interactive_brokers_rest_backtesting.cpython-312.pyc +0 -0
  80. lumibot/backtesting/__pycache__/pandas_backtesting.cpython-312.pyc +0 -0
  81. lumibot/backtesting/__pycache__/polygon_backtesting.cpython-312.pyc +0 -0
  82. lumibot/backtesting/__pycache__/thetadata_backtesting.cpython-312.pyc +0 -0
  83. lumibot/backtesting/__pycache__/yahoo_backtesting.cpython-312.pyc +0 -0
  84. lumibot/brokers/__pycache__/__init__.cpython-312.pyc +0 -0
  85. lumibot/brokers/__pycache__/alpaca.cpython-312.pyc +0 -0
  86. lumibot/brokers/__pycache__/bitunix.cpython-312.pyc +0 -0
  87. lumibot/brokers/__pycache__/broker.cpython-312.pyc +0 -0
  88. lumibot/brokers/__pycache__/ccxt.cpython-312.pyc +0 -0
  89. lumibot/brokers/__pycache__/example_broker.cpython-312.pyc +0 -0
  90. lumibot/brokers/__pycache__/interactive_brokers.cpython-312.pyc +0 -0
  91. lumibot/brokers/__pycache__/interactive_brokers_rest.cpython-312.pyc +0 -0
  92. lumibot/brokers/__pycache__/projectx.cpython-312.pyc +0 -0
  93. lumibot/brokers/__pycache__/schwab.cpython-312.pyc +0 -0
  94. lumibot/brokers/__pycache__/tradier.cpython-312.pyc +0 -0
  95. lumibot/brokers/__pycache__/tradovate.cpython-312.pyc +0 -0
  96. lumibot/data_sources/__pycache__/__init__.cpython-312.pyc +0 -0
  97. lumibot/data_sources/__pycache__/alpaca_data.cpython-312.pyc +0 -0
  98. lumibot/data_sources/__pycache__/alpha_vantage_data.cpython-312.pyc +0 -0
  99. lumibot/data_sources/__pycache__/bitunix_data.cpython-312.pyc +0 -0
  100. lumibot/data_sources/__pycache__/ccxt_backtesting_data.cpython-312.pyc +0 -0
  101. lumibot/data_sources/__pycache__/ccxt_data.cpython-312.pyc +0 -0
  102. lumibot/data_sources/__pycache__/data_source.cpython-312.pyc +0 -0
  103. lumibot/data_sources/__pycache__/data_source_backtesting.cpython-312.pyc +0 -0
  104. lumibot/data_sources/__pycache__/databento_data_polars_backtesting.cpython-312.pyc +0 -0
  105. lumibot/data_sources/__pycache__/databento_data_polars_live.cpython-312.pyc +0 -0
  106. lumibot/data_sources/__pycache__/example_broker_data.cpython-312.pyc +0 -0
  107. lumibot/data_sources/__pycache__/exceptions.cpython-312.pyc +0 -0
  108. lumibot/data_sources/__pycache__/interactive_brokers_data.cpython-312.pyc +0 -0
  109. lumibot/data_sources/__pycache__/interactive_brokers_rest_data.cpython-312.pyc +0 -0
  110. lumibot/data_sources/__pycache__/pandas_data.cpython-312.pyc +0 -0
  111. lumibot/data_sources/__pycache__/polars_mixin.cpython-312.pyc +0 -0
  112. lumibot/data_sources/__pycache__/polygon_data_polars.cpython-312.pyc +0 -0
  113. lumibot/data_sources/__pycache__/projectx_data.cpython-312.pyc +0 -0
  114. lumibot/data_sources/__pycache__/schwab_data.cpython-312.pyc +0 -0
  115. lumibot/data_sources/__pycache__/tradier_data.cpython-312.pyc +0 -0
  116. lumibot/data_sources/__pycache__/tradovate_data.cpython-312.pyc +0 -0
  117. lumibot/data_sources/__pycache__/yahoo_data_polars.cpython-312.pyc +0 -0
  118. lumibot/entities/__pycache__/__init__.cpython-312.pyc +0 -0
  119. lumibot/entities/__pycache__/asset.cpython-312.pyc +0 -0
  120. lumibot/entities/__pycache__/bar.cpython-312.pyc +0 -0
  121. lumibot/entities/__pycache__/bars.cpython-312.pyc +0 -0
  122. lumibot/entities/__pycache__/chains.cpython-312.pyc +0 -0
  123. lumibot/entities/__pycache__/data.cpython-312.pyc +0 -0
  124. lumibot/entities/__pycache__/dataline.cpython-312.pyc +0 -0
  125. lumibot/entities/__pycache__/order.cpython-312.pyc +0 -0
  126. lumibot/entities/__pycache__/position.cpython-312.pyc +0 -0
  127. lumibot/entities/__pycache__/quote.cpython-312.pyc +0 -0
  128. lumibot/entities/__pycache__/trading_fee.cpython-312.pyc +0 -0
  129. lumibot/example_strategies/__pycache__/__init__.cpython-312.pyc +0 -0
  130. lumibot/example_strategies/__pycache__/test_broker_functions.cpython-312-pytest-8.4.1.pyc +0 -0
  131. lumibot/strategies/__pycache__/__init__.cpython-312.pyc +0 -0
  132. lumibot/strategies/__pycache__/_strategy.cpython-312.pyc +0 -0
  133. lumibot/strategies/__pycache__/strategy.cpython-312.pyc +0 -0
  134. lumibot/strategies/__pycache__/strategy_executor.cpython-312.pyc +0 -0
  135. lumibot/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  136. lumibot/tools/__pycache__/alpaca_helpers.cpython-312.pyc +0 -0
  137. lumibot/tools/__pycache__/bitunix_helpers.cpython-312.pyc +0 -0
  138. lumibot/tools/__pycache__/black_scholes.cpython-312.pyc +0 -0
  139. lumibot/tools/__pycache__/ccxt_data_store.cpython-312.pyc +0 -0
  140. lumibot/tools/__pycache__/databento_helper.cpython-312.pyc +0 -0
  141. lumibot/tools/__pycache__/databento_helper_polars.cpython-312.pyc +0 -0
  142. lumibot/tools/__pycache__/debugers.cpython-312.pyc +0 -0
  143. lumibot/tools/__pycache__/decorators.cpython-312.pyc +0 -0
  144. lumibot/tools/__pycache__/helpers.cpython-312.pyc +0 -0
  145. lumibot/tools/__pycache__/indicators.cpython-312.pyc +0 -0
  146. lumibot/tools/__pycache__/lumibot_logger.cpython-312.pyc +0 -0
  147. lumibot/tools/__pycache__/pandas.cpython-312.pyc +0 -0
  148. lumibot/tools/__pycache__/polygon_helper.cpython-312.pyc +0 -0
  149. lumibot/tools/__pycache__/polygon_helper_async.cpython-312.pyc +0 -0
  150. lumibot/tools/__pycache__/polygon_helper_polars_optimized.cpython-312.pyc +0 -0
  151. lumibot/tools/__pycache__/projectx_helpers.cpython-312.pyc +0 -0
  152. lumibot/tools/__pycache__/schwab_helper.cpython-312.pyc +0 -0
  153. lumibot/tools/__pycache__/thetadata_helper.cpython-312.pyc +0 -0
  154. lumibot/tools/__pycache__/types.cpython-312.pyc +0 -0
  155. lumibot/tools/__pycache__/yahoo_helper.cpython-312.pyc +0 -0
  156. lumibot/tools/__pycache__/yahoo_helper_polars_optimized.cpython-312.pyc +0 -0
  157. lumibot/traders/__pycache__/__init__.cpython-312.pyc +0 -0
  158. lumibot/traders/__pycache__/trader.cpython-312.pyc +0 -0
  159. lumibot/trading_builtins/__pycache__/__init__.cpython-312.pyc +0 -0
  160. lumibot/trading_builtins/__pycache__/custom_stream.cpython-312.pyc +0 -0
  161. lumibot/trading_builtins/__pycache__/safe_list.cpython-312.pyc +0 -0
  162. {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/WHEEL +0 -0
  163. {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/licenses/LICENSE +0 -0
  164. {lumibot-4.1.2.dist-info → lumibot-4.2.0.dist-info}/top_level.txt +0 -0
@@ -9,12 +9,18 @@ from decimal import Decimal
9
9
  import pandas as pd
10
10
  from lumibot import LUMIBOT_CACHE_FOLDER
11
11
  from lumibot.entities import Asset
12
- from lumibot.tools import databento_roll
12
+ from lumibot.tools import futures_roll
13
+ from termcolor import colored
13
14
 
14
15
  # Set up module-specific logger
15
16
  from lumibot.tools.lumibot_logger import get_logger
16
17
  logger = get_logger(__name__)
17
18
 
19
+
20
+ class DataBentoAuthenticationError(RuntimeError):
21
+ """Raised when DataBento rejects authentication credentials."""
22
+ pass
23
+
18
24
  # DataBento imports (will be installed as dependency)
19
25
  try:
20
26
  import databento as db
@@ -161,11 +167,16 @@ class DataBentoClient:
161
167
  continue
162
168
  else:
163
169
  logger.error(f"DataBento authentication failed after {self.max_retries} retries")
170
+ raise DataBentoAuthenticationError(
171
+ f"DataBento authentication failed after {self.max_retries} retries: {str(e)}"
172
+ ) from e
164
173
 
165
174
  # For non-auth errors, don't retry - fail fast
166
- logger.error("DATABENTO_API_ERROR: DataBento API error: %s | Symbols: %s, Start: %s, End: %s",
167
- str(e), symbols, start, end)
168
- raise e
175
+ logger.error(
176
+ "DATABENTO_API_ERROR: DataBento API error: %s | Symbols: %s, Start: %s, End: %s",
177
+ str(e), symbols, start, end
178
+ )
179
+ raise
169
180
 
170
181
  # This should never be reached, but just in case
171
182
  raise Exception(f"DataBento request failed after {self.max_retries} retries")
@@ -593,13 +604,29 @@ def _filter_front_month_rows_pandas(
593
604
  if df.empty or "symbol" not in df.columns or schedule is None:
594
605
  return df
595
606
 
607
+ index_tz = getattr(df.index, "tz", None)
608
+
609
+ def _align(ts: datetime | pd.Timestamp | None) -> pd.Timestamp | None:
610
+ if ts is None:
611
+ return None
612
+ ts_pd = pd.Timestamp(ts)
613
+ if index_tz is None:
614
+ return ts_pd.tz_localize(None) if ts_pd.tz is not None else ts_pd
615
+ if ts_pd.tz is None:
616
+ ts_pd = ts_pd.tz_localize(index_tz)
617
+ else:
618
+ ts_pd = ts_pd.tz_convert(index_tz)
619
+ return ts_pd
620
+
596
621
  mask = pd.Series(False, index=df.index)
597
622
  for symbol, start_dt, end_dt in schedule:
598
623
  cond = df["symbol"] == symbol
599
- if start_dt is not None:
600
- cond &= df.index >= start_dt
601
- if end_dt is not None:
602
- cond &= df.index < end_dt
624
+ start_aligned = _align(start_dt)
625
+ end_aligned = _align(end_dt)
626
+ if start_aligned is not None:
627
+ cond &= df.index >= start_aligned
628
+ if end_aligned is not None:
629
+ cond &= df.index < end_aligned
603
630
  mask |= cond
604
631
 
605
632
  filtered = df.loc[mask]
@@ -783,15 +810,31 @@ def get_price_data_from_databento(
783
810
  start_naive = start.replace(tzinfo=None) if start.tzinfo is not None else start
784
811
  end_naive = end.replace(tzinfo=None) if end.tzinfo is not None else end
785
812
 
786
- if asset.asset_type == Asset.AssetType.CONT_FUTURE:
813
+ roll_asset = asset
814
+ if asset.asset_type == Asset.AssetType.FUTURE and not asset.expiration:
815
+ roll_asset = Asset(asset.symbol, Asset.AssetType.CONT_FUTURE)
816
+
817
+ if roll_asset.asset_type == Asset.AssetType.CONT_FUTURE:
787
818
  schedule_start = start
788
- symbols = databento_roll.resolve_symbols_for_range(asset, schedule_start, end)
789
- front_symbol = databento_roll.resolve_symbol_for_datetime(asset, reference_date or start)
819
+ symbols = futures_roll.resolve_symbols_for_range(
820
+ roll_asset,
821
+ schedule_start,
822
+ end,
823
+ year_digits=1,
824
+ )
825
+ front_symbol = futures_roll.resolve_symbol_for_datetime(
826
+ roll_asset,
827
+ reference_date or start,
828
+ year_digits=1,
829
+ )
790
830
  if front_symbol not in symbols:
791
831
  symbols.insert(0, front_symbol)
792
832
  else:
793
833
  schedule_start = start
794
- front_symbol = _format_futures_symbol_for_databento(asset)
834
+ front_symbol = _format_futures_symbol_for_databento(
835
+ asset,
836
+ reference_date=reference_date or start,
837
+ )
795
838
  symbols = [front_symbol]
796
839
 
797
840
  # Ensure multiplier is populated using the first contract.
@@ -856,6 +899,13 @@ def get_price_data_from_databento(
856
899
  end=end_naive,
857
900
  **kwargs,
858
901
  )
902
+ except DataBentoAuthenticationError as exc:
903
+ auth_msg = colored(
904
+ f"❌ DataBento authentication failed while requesting {symbol}: {exc}",
905
+ "red"
906
+ )
907
+ logger.error(auth_msg)
908
+ raise
859
909
  except Exception as exc:
860
910
  logger.warning(f"Error fetching {symbol} from DataBento: {exc}")
861
911
  continue
@@ -877,38 +927,11 @@ def get_price_data_from_databento(
877
927
  combined = pd.concat(frames, axis=0)
878
928
  combined.sort_index(inplace=True)
879
929
 
880
- definition_client: Optional[DataBentoClient] = None
881
-
882
- def get_definition(symbol_code: str) -> Optional[Dict]:
883
- nonlocal definition_client
884
- cache_key = (symbol_code, dataset)
885
- if cache_key in _INSTRUMENT_DEFINITION_CACHE:
886
- return _INSTRUMENT_DEFINITION_CACHE[cache_key]
887
- if definition_client is None:
888
- try:
889
- definition_client = DataBentoClient(api_key=api_key)
890
- except Exception as exc:
891
- logger.warning(f"Unable to create DataBento definition client: {exc}")
892
- return None
893
- try:
894
- definition = definition_client.get_instrument_definition(
895
- dataset=dataset,
896
- symbol=symbol_code,
897
- reference_date=reference_date or start,
898
- )
899
- except Exception as exc:
900
- logger.warning(f"Failed to fetch definition for {symbol_code}: {exc}")
901
- return None
902
- if definition:
903
- _INSTRUMENT_DEFINITION_CACHE[cache_key] = definition
904
- return definition
905
-
906
- schedule = databento_roll.build_roll_schedule(
907
- asset,
930
+ schedule = futures_roll.build_roll_schedule(
931
+ roll_asset,
908
932
  schedule_start,
909
933
  end,
910
- definition_provider=get_definition,
911
- roll_days=databento_roll.ROLL_DAYS_BEFORE_EXPIRATION,
934
+ year_digits=1,
912
935
  )
913
936
 
914
937
  if schedule: