lumibot 4.0.22__py3-none-any.whl → 4.1.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/__pycache__/__init__.cpython-312.pyc +0 -0
  2. lumibot/__pycache__/constants.cpython-312.pyc +0 -0
  3. lumibot/__pycache__/credentials.cpython-312.pyc +0 -0
  4. lumibot/backtesting/__init__.py +6 -5
  5. lumibot/backtesting/__pycache__/__init__.cpython-312.pyc +0 -0
  6. lumibot/backtesting/__pycache__/alpaca_backtesting.cpython-312.pyc +0 -0
  7. lumibot/backtesting/__pycache__/alpha_vantage_backtesting.cpython-312.pyc +0 -0
  8. lumibot/backtesting/__pycache__/backtesting_broker.cpython-312.pyc +0 -0
  9. lumibot/backtesting/__pycache__/ccxt_backtesting.cpython-312.pyc +0 -0
  10. lumibot/backtesting/__pycache__/databento_backtesting.cpython-312.pyc +0 -0
  11. lumibot/backtesting/__pycache__/interactive_brokers_rest_backtesting.cpython-312.pyc +0 -0
  12. lumibot/backtesting/__pycache__/pandas_backtesting.cpython-312.pyc +0 -0
  13. lumibot/backtesting/__pycache__/polygon_backtesting.cpython-312.pyc +0 -0
  14. lumibot/backtesting/__pycache__/thetadata_backtesting.cpython-312.pyc +0 -0
  15. lumibot/backtesting/__pycache__/yahoo_backtesting.cpython-312.pyc +0 -0
  16. lumibot/backtesting/backtesting_broker.py +209 -9
  17. lumibot/backtesting/databento_backtesting.py +141 -24
  18. lumibot/backtesting/thetadata_backtesting.py +63 -42
  19. lumibot/brokers/__pycache__/__init__.cpython-312.pyc +0 -0
  20. lumibot/brokers/__pycache__/alpaca.cpython-312.pyc +0 -0
  21. lumibot/brokers/__pycache__/bitunix.cpython-312.pyc +0 -0
  22. lumibot/brokers/__pycache__/broker.cpython-312.pyc +0 -0
  23. lumibot/brokers/__pycache__/ccxt.cpython-312.pyc +0 -0
  24. lumibot/brokers/__pycache__/example_broker.cpython-312.pyc +0 -0
  25. lumibot/brokers/__pycache__/interactive_brokers.cpython-312.pyc +0 -0
  26. lumibot/brokers/__pycache__/interactive_brokers_rest.cpython-312.pyc +0 -0
  27. lumibot/brokers/__pycache__/projectx.cpython-312.pyc +0 -0
  28. lumibot/brokers/__pycache__/schwab.cpython-312.pyc +0 -0
  29. lumibot/brokers/__pycache__/tradier.cpython-312.pyc +0 -0
  30. lumibot/brokers/__pycache__/tradovate.cpython-312.pyc +0 -0
  31. lumibot/brokers/alpaca.py +11 -1
  32. lumibot/brokers/tradeovate.py +475 -0
  33. lumibot/components/grok_news_helper.py +284 -0
  34. lumibot/components/options_helper.py +90 -34
  35. lumibot/credentials.py +3 -0
  36. lumibot/data_sources/__init__.py +2 -1
  37. lumibot/data_sources/__pycache__/__init__.cpython-312.pyc +0 -0
  38. lumibot/data_sources/__pycache__/alpaca_data.cpython-312.pyc +0 -0
  39. lumibot/data_sources/__pycache__/alpha_vantage_data.cpython-312.pyc +0 -0
  40. lumibot/data_sources/__pycache__/bitunix_data.cpython-312.pyc +0 -0
  41. lumibot/data_sources/__pycache__/ccxt_backtesting_data.cpython-312.pyc +0 -0
  42. lumibot/data_sources/__pycache__/ccxt_data.cpython-312.pyc +0 -0
  43. lumibot/data_sources/__pycache__/data_source.cpython-312.pyc +0 -0
  44. lumibot/data_sources/__pycache__/data_source_backtesting.cpython-312.pyc +0 -0
  45. lumibot/data_sources/__pycache__/databento_data_polars_backtesting.cpython-312.pyc +0 -0
  46. lumibot/data_sources/__pycache__/databento_data_polars_live.cpython-312.pyc +0 -0
  47. lumibot/data_sources/__pycache__/example_broker_data.cpython-312.pyc +0 -0
  48. lumibot/data_sources/__pycache__/exceptions.cpython-312.pyc +0 -0
  49. lumibot/data_sources/__pycache__/interactive_brokers_data.cpython-312.pyc +0 -0
  50. lumibot/data_sources/__pycache__/interactive_brokers_rest_data.cpython-312.pyc +0 -0
  51. lumibot/data_sources/__pycache__/pandas_data.cpython-312.pyc +0 -0
  52. lumibot/data_sources/__pycache__/polars_mixin.cpython-312.pyc +0 -0
  53. lumibot/data_sources/__pycache__/polygon_data_polars.cpython-312.pyc +0 -0
  54. lumibot/data_sources/__pycache__/projectx_data.cpython-312.pyc +0 -0
  55. lumibot/data_sources/__pycache__/schwab_data.cpython-312.pyc +0 -0
  56. lumibot/data_sources/__pycache__/tradier_data.cpython-312.pyc +0 -0
  57. lumibot/data_sources/__pycache__/tradovate_data.cpython-312.pyc +0 -0
  58. lumibot/data_sources/__pycache__/yahoo_data_polars.cpython-312.pyc +0 -0
  59. lumibot/data_sources/data_source_backtesting.py +3 -5
  60. lumibot/data_sources/databento_data.py +5 -5
  61. lumibot/data_sources/databento_data_polars_backtesting.py +636 -0
  62. lumibot/data_sources/databento_data_polars_live.py +793 -0
  63. lumibot/data_sources/pandas_data.py +6 -3
  64. lumibot/data_sources/polars_mixin.py +126 -21
  65. lumibot/data_sources/tradeovate_data.py +80 -0
  66. lumibot/data_sources/tradier_data.py +2 -1
  67. lumibot/entities/__pycache__/__init__.cpython-312.pyc +0 -0
  68. lumibot/entities/__pycache__/asset.cpython-312.pyc +0 -0
  69. lumibot/entities/__pycache__/bar.cpython-312.pyc +0 -0
  70. lumibot/entities/__pycache__/bars.cpython-312.pyc +0 -0
  71. lumibot/entities/__pycache__/chains.cpython-312.pyc +0 -0
  72. lumibot/entities/__pycache__/data.cpython-312.pyc +0 -0
  73. lumibot/entities/__pycache__/dataline.cpython-312.pyc +0 -0
  74. lumibot/entities/__pycache__/order.cpython-312.pyc +0 -0
  75. lumibot/entities/__pycache__/position.cpython-312.pyc +0 -0
  76. lumibot/entities/__pycache__/quote.cpython-312.pyc +0 -0
  77. lumibot/entities/__pycache__/trading_fee.cpython-312.pyc +0 -0
  78. lumibot/entities/asset.py +8 -0
  79. lumibot/entities/order.py +1 -1
  80. lumibot/entities/quote.py +14 -0
  81. lumibot/example_strategies/__pycache__/__init__.cpython-312.pyc +0 -0
  82. lumibot/example_strategies/__pycache__/test_broker_functions.cpython-312-pytest-8.4.1.pyc +0 -0
  83. lumibot/strategies/__pycache__/__init__.cpython-312.pyc +0 -0
  84. lumibot/strategies/__pycache__/_strategy.cpython-312.pyc +0 -0
  85. lumibot/strategies/__pycache__/strategy.cpython-312.pyc +0 -0
  86. lumibot/strategies/__pycache__/strategy_executor.cpython-312.pyc +0 -0
  87. lumibot/strategies/_strategy.py +95 -27
  88. lumibot/strategies/strategy.py +5 -6
  89. lumibot/strategies/strategy_executor.py +2 -2
  90. lumibot/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  91. lumibot/tools/__pycache__/alpaca_helpers.cpython-312.pyc +0 -0
  92. lumibot/tools/__pycache__/bitunix_helpers.cpython-312.pyc +0 -0
  93. lumibot/tools/__pycache__/black_scholes.cpython-312.pyc +0 -0
  94. lumibot/tools/__pycache__/ccxt_data_store.cpython-312.pyc +0 -0
  95. lumibot/tools/__pycache__/databento_helper.cpython-312.pyc +0 -0
  96. lumibot/tools/__pycache__/databento_helper_polars.cpython-312.pyc +0 -0
  97. lumibot/tools/__pycache__/debugers.cpython-312.pyc +0 -0
  98. lumibot/tools/__pycache__/decorators.cpython-312.pyc +0 -0
  99. lumibot/tools/__pycache__/helpers.cpython-312.pyc +0 -0
  100. lumibot/tools/__pycache__/indicators.cpython-312.pyc +0 -0
  101. lumibot/tools/__pycache__/lumibot_logger.cpython-312.pyc +0 -0
  102. lumibot/tools/__pycache__/pandas.cpython-312.pyc +0 -0
  103. lumibot/tools/__pycache__/polygon_helper.cpython-312.pyc +0 -0
  104. lumibot/tools/__pycache__/polygon_helper_async.cpython-312.pyc +0 -0
  105. lumibot/tools/__pycache__/polygon_helper_polars_optimized.cpython-312.pyc +0 -0
  106. lumibot/tools/__pycache__/projectx_helpers.cpython-312.pyc +0 -0
  107. lumibot/tools/__pycache__/schwab_helper.cpython-312.pyc +0 -0
  108. lumibot/tools/__pycache__/thetadata_helper.cpython-312.pyc +0 -0
  109. lumibot/tools/__pycache__/types.cpython-312.pyc +0 -0
  110. lumibot/tools/__pycache__/yahoo_helper.cpython-312.pyc +0 -0
  111. lumibot/tools/__pycache__/yahoo_helper_polars_optimized.cpython-312.pyc +0 -0
  112. lumibot/tools/databento_helper.py +384 -133
  113. lumibot/tools/databento_helper_polars.py +218 -156
  114. lumibot/tools/databento_roll.py +216 -0
  115. lumibot/tools/lumibot_logger.py +32 -17
  116. lumibot/tools/polygon_helper.py +65 -0
  117. lumibot/tools/thetadata_helper.py +588 -70
  118. lumibot/traders/__pycache__/__init__.cpython-312.pyc +0 -0
  119. lumibot/traders/__pycache__/trader.cpython-312.pyc +0 -0
  120. lumibot/traders/trader.py +1 -1
  121. lumibot/trading_builtins/__pycache__/__init__.cpython-312.pyc +0 -0
  122. lumibot/trading_builtins/__pycache__/custom_stream.cpython-312.pyc +0 -0
  123. lumibot/trading_builtins/__pycache__/safe_list.cpython-312.pyc +0 -0
  124. {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/METADATA +1 -2
  125. {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/RECORD +164 -46
  126. tests/backtest/check_timing_offset.py +198 -0
  127. tests/backtest/check_volume_spike.py +112 -0
  128. tests/backtest/comprehensive_comparison.py +166 -0
  129. tests/backtest/debug_comparison.py +91 -0
  130. tests/backtest/diagnose_price_difference.py +97 -0
  131. tests/backtest/direct_api_comparison.py +203 -0
  132. tests/backtest/profile_thetadata_vs_polygon.py +255 -0
  133. tests/backtest/root_cause_analysis.py +109 -0
  134. tests/backtest/test_accuracy_verification.py +244 -0
  135. tests/backtest/test_daily_data_timestamp_comparison.py +801 -0
  136. tests/backtest/test_databento.py +57 -0
  137. tests/backtest/test_databento_comprehensive_trading.py +564 -0
  138. tests/backtest/test_debug_avg_fill_price.py +112 -0
  139. tests/backtest/test_dividends.py +8 -3
  140. tests/backtest/test_example_strategies.py +54 -47
  141. tests/backtest/test_futures_edge_cases.py +451 -0
  142. tests/backtest/test_futures_single_trade.py +270 -0
  143. tests/backtest/test_futures_ultra_simple.py +191 -0
  144. tests/backtest/test_index_data_verification.py +348 -0
  145. tests/backtest/test_polygon.py +45 -24
  146. tests/backtest/test_thetadata.py +246 -60
  147. tests/backtest/test_thetadata_comprehensive.py +729 -0
  148. tests/backtest/test_thetadata_vs_polygon.py +557 -0
  149. tests/backtest/test_yahoo.py +1 -2
  150. tests/conftest.py +20 -0
  151. tests/test_backtesting_data_source_env.py +249 -0
  152. tests/test_backtesting_quiet_logs_complete.py +10 -11
  153. tests/test_databento_helper.py +73 -86
  154. tests/test_databento_live.py +10 -10
  155. tests/test_databento_timezone_fixes.py +21 -4
  156. tests/test_get_historical_prices.py +6 -6
  157. tests/test_options_helper.py +162 -40
  158. tests/test_polygon_helper.py +21 -13
  159. tests/test_quiet_logs_requirements.py +5 -5
  160. tests/test_thetadata_helper.py +487 -171
  161. tests/test_yahoo_data.py +125 -0
  162. {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/LICENSE +0 -0
  163. {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/WHEEL +0 -0
  164. {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  from decimal import Decimal
2
2
  from typing import Union
3
3
 
4
+ import logging
4
5
  import pandas as pd
5
6
  import subprocess
6
7
  from datetime import date, timedelta
@@ -9,6 +10,8 @@ from lumibot.data_sources import PandasData
9
10
  from lumibot.entities import Asset, Data
10
11
  from lumibot.tools import thetadata_helper
11
12
 
13
+ logger = logging.getLogger(__name__)
14
+
12
15
 
13
16
  START_BUFFER = timedelta(days=5)
14
17
 
@@ -18,6 +21,9 @@ class ThetaDataBacktesting(PandasData):
18
21
  Backtesting implementation of ThetaData
19
22
  """
20
23
 
24
+ # Enable fallback to last_price when bid/ask quotes are unavailable for options
25
+ option_quote_fallback_allowed = True
26
+
21
27
  def __init__(
22
28
  self,
23
29
  datetime_start,
@@ -28,7 +34,9 @@ class ThetaDataBacktesting(PandasData):
28
34
  use_quote_data=True,
29
35
  **kwargs,
30
36
  ):
31
- super().__init__(datetime_start=datetime_start, datetime_end=datetime_end, pandas_data=pandas_data, **kwargs)
37
+ # Pass allow_option_quote_fallback to parent to enable fallback mechanism
38
+ super().__init__(datetime_start=datetime_start, datetime_end=datetime_end, pandas_data=pandas_data,
39
+ allow_option_quote_fallback=True, **kwargs)
32
40
 
33
41
  self._username = username
34
42
  self._password = password
@@ -83,6 +91,12 @@ class ThetaDataBacktesting(PandasData):
83
91
  dict
84
92
  A dictionary with the keys being the asset and the values being the PandasData objects.
85
93
  """
94
+ # DEBUG: Log when strike 157 is requested
95
+ if hasattr(asset, 'strike') and asset.strike == 157:
96
+ import traceback
97
+ logger.info(f"\n[DEBUG STRIKE 157] _update_pandas_data called for asset: {asset}")
98
+ logger.info(f"[DEBUG STRIKE 157] Traceback:\n{''.join(traceback.format_stack())}")
99
+
86
100
  search_asset = asset
87
101
  asset_separated = asset
88
102
  quote_asset = quote if quote is not None else Asset("USD", "forex")
@@ -160,13 +174,16 @@ class ThetaDataBacktesting(PandasData):
160
174
  timespan=ts_unit,
161
175
  quote_asset=quote_asset,
162
176
  dt=date_time_now,
163
- datastyle="ohlc"
177
+ datastyle="ohlc",
178
+ include_after_hours=True # Default to True for extended hours data
164
179
  )
165
180
  if df_ohlc is None:
166
181
  logger.info(f"\nSKIP: No OHLC data found for {asset_separated} from ThetaData")
167
182
  return None
168
183
 
169
- if self._use_quote_data:
184
+ # Quote data (bid/ask) is only available for intraday data (minute, hour, second)
185
+ # For daily+ data, only use OHLC
186
+ if self._use_quote_data and ts_unit in ["minute", "hour", "second"]:
170
187
  # Get quote data from ThetaData
171
188
  df_quote = thetadata_helper.get_price_data(
172
189
  self._username,
@@ -177,7 +194,8 @@ class ThetaDataBacktesting(PandasData):
177
194
  timespan=ts_unit,
178
195
  quote_asset=quote_asset,
179
196
  dt=date_time_now,
180
- datastyle="quote"
197
+ datastyle="quote",
198
+ include_after_hours=True # Default to True for extended hours data
181
199
  )
182
200
 
183
201
  # Check if we have data
@@ -185,8 +203,21 @@ class ThetaDataBacktesting(PandasData):
185
203
  logger.info(f"\nSKIP: No QUOTE data found for {quote_asset} from ThetaData")
186
204
  return None
187
205
 
188
- # Combine the ohlc and quote data
189
- df = pd.concat([df_ohlc, df_quote], axis=1, join='inner')
206
+ # Combine the ohlc and quote data using outer join to preserve all data
207
+ # Use forward fill for missing quote values (ThetaData's recommended approach)
208
+ df = pd.concat([df_ohlc, df_quote], axis=1, join='outer')
209
+
210
+ # Forward fill missing quote values
211
+ quote_columns = ['bid', 'ask', 'bid_size', 'ask_size', 'bid_condition', 'ask_condition', 'bid_exchange', 'ask_exchange']
212
+ existing_quote_cols = [col for col in quote_columns if col in df.columns]
213
+ if existing_quote_cols:
214
+ df[existing_quote_cols] = df[existing_quote_cols].fillna(method='ffill')
215
+
216
+ # Log how much forward filling occurred
217
+ if 'bid' in df.columns and 'ask' in df.columns:
218
+ remaining_nulls = df[['bid', 'ask']].isna().sum().sum()
219
+ if remaining_nulls > 0:
220
+ logger.info(f"Forward-filled missing quote values for {asset_separated}. {remaining_nulls} nulls remain at start of data.")
190
221
  else:
191
222
  df = df_ohlc
192
223
 
@@ -288,8 +319,7 @@ class ThetaDataBacktesting(PandasData):
288
319
 
289
320
  def get_chains(self, asset):
290
321
  """
291
- Integrates the ThetaData client library into the LumiBot backtest for Options Data in the same
292
- structure as Interactive Brokers options chain data
322
+ Get option chains using cached implementation (matches Polygon pattern).
293
323
 
294
324
  Parameters
295
325
  ----------
@@ -298,40 +328,31 @@ class ThetaDataBacktesting(PandasData):
298
328
 
299
329
  Returns
300
330
  -------
301
- dictionary:
302
- A dictionary nested with a dictionary of ThetaData Option Contracts information broken out by Exchange,
303
- with embedded lists for Expirations and Strikes.
304
- {'SMART': {'TradingClass': 'SPY', 'Multiplier': 100, 'Expirations': [], 'Strikes': []}}
305
-
306
- - `TradingClass` (str) eg: `FB`
307
- - `Multiplier` (str) eg: `100`
308
- - `Expirations` (list of str) eg: [`20230616`, ...]
309
- - `Strikes` (list of floats) eg: [`100.0`, ...]
331
+ Chains:
332
+ A Chains entity object (dict subclass) with the structure:
333
+ {
334
+ "Multiplier": 100,
335
+ "Exchange": "SMART",
336
+ "Chains": {
337
+ "CALL": {
338
+ "2023-07-31": [100.0, 101.0, ...],
339
+ ...
340
+ },
341
+ "PUT": {
342
+ "2023-07-31": [100.0, 101.0, ...],
343
+ ...
344
+ }
345
+ }
346
+ }
310
347
  """
348
+ from lumibot.entities import Chains
311
349
 
312
- # All Option Contracts | get_chains matching IBKR |
313
- # {'SMART': {'TradingClass': 'SPY', 'Multiplier': 100, 'Expirations': [], 'Strikes': []}}
314
- option_contracts = {"SMART": {"TradingClass": None, "Multiplier": None, "Expirations": [], "Strikes": []}}
315
- contracts = option_contracts["SMART"] # initialize contracts
316
- today = self.get_datetime().date()
317
-
318
- # Get expirations from thetadata_helper
319
- expirations = thetadata_helper.get_expirations(self._username, self._password, asset.symbol, today)
320
-
321
- # Get the first of the expirations and convert to datetime
322
- expiration = expirations[0].replace("-", "")
323
- expiration_dt = date(int(expiration[:4]), int(expiration[4:6]), int(expiration[6:8]))
324
-
325
- # Get strikes from thetadata_helper
326
- strikes = thetadata_helper.get_strikes(self._username, self._password, asset.symbol, expiration_dt)
327
-
328
- # Add the data to the contracts dictionary
329
- contracts["TradingClass"] = asset.symbol
330
- contracts["Multiplier"] = 100
331
- contracts["Expirations"] = expirations
332
- contracts["Strikes"] = strikes
333
-
334
- # Add the data to the option_contracts dictionary
335
- option_contracts["SMART"] = contracts
350
+ chains_dict = thetadata_helper.get_chains_cached(
351
+ username=self._username,
352
+ password=self._password,
353
+ asset=asset,
354
+ current_date=self.get_datetime().date()
355
+ )
336
356
 
337
- return option_contracts
357
+ # Wrap in Chains entity for modern API
358
+ return Chains(chains_dict)
lumibot/brokers/alpaca.py CHANGED
@@ -555,7 +555,11 @@ class Alpaca(Broker):
555
555
 
556
556
  # Handle None quantity - skip invalid orders
557
557
  if qty_value is None:
558
- logger.warning(f"Skipping order {identifier_value} - quantity is None (invalid order data from Alpaca)")
558
+ logger.warning(
559
+ f"Skipping order {identifier_value} - quantity is None (invalid order data from Alpaca). "
560
+ f"Order details: symbol={symbol}, side={side_value}, status={status_value}, "
561
+ f"order_type={order_type_value}, raw_data={resp_raw}"
562
+ )
559
563
  return None
560
564
 
561
565
  # Construct Order object
@@ -1170,6 +1174,11 @@ class Alpaca(Broker):
1170
1174
  strategy_name = strategy.name if strategy else "default"
1171
1175
  order = self._parse_broker_order(alpaca_order, strategy_name=strategy_name)
1172
1176
 
1177
+ # Skip if parsing returned None (invalid order data)
1178
+ if order is None:
1179
+ logger.warning(f"OAuth Polling: Skipping invalid order from Alpaca - _parse_broker_order returned None")
1180
+ continue
1181
+
1173
1182
  logger.debug(f"OAuth Polling: Processing Alpaca order {order.identifier} with status {order.status}")
1174
1183
 
1175
1184
  # Check if this order exists in our stored orders
@@ -1279,6 +1288,7 @@ class Alpaca(Broker):
1279
1288
  raise ValueError(error_msg)
1280
1289
  else:
1281
1290
  logger.error(f"OAuth Polling error: {e}")
1291
+ logger.error(f"Full traceback: {traceback.format_exc()}")
1282
1292
  # No need to schedule next poll - PollingStream handles this automatically via timeout
1283
1293
 
1284
1294
  def _run_stream(self):