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.
- lumibot/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/__pycache__/constants.cpython-312.pyc +0 -0
- lumibot/__pycache__/credentials.cpython-312.pyc +0 -0
- lumibot/backtesting/__init__.py +6 -5
- lumibot/backtesting/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/alpaca_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/alpha_vantage_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/backtesting_broker.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/ccxt_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/databento_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/interactive_brokers_rest_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/pandas_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/polygon_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/thetadata_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/__pycache__/yahoo_backtesting.cpython-312.pyc +0 -0
- lumibot/backtesting/backtesting_broker.py +209 -9
- lumibot/backtesting/databento_backtesting.py +141 -24
- lumibot/backtesting/thetadata_backtesting.py +63 -42
- lumibot/brokers/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/alpaca.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/bitunix.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/broker.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/ccxt.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/example_broker.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/interactive_brokers.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/interactive_brokers_rest.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/projectx.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/schwab.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/tradier.cpython-312.pyc +0 -0
- lumibot/brokers/__pycache__/tradovate.cpython-312.pyc +0 -0
- lumibot/brokers/alpaca.py +11 -1
- lumibot/brokers/tradeovate.py +475 -0
- lumibot/components/grok_news_helper.py +284 -0
- lumibot/components/options_helper.py +90 -34
- lumibot/credentials.py +3 -0
- lumibot/data_sources/__init__.py +2 -1
- lumibot/data_sources/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/alpaca_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/alpha_vantage_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/bitunix_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/ccxt_backtesting_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/ccxt_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/data_source.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/data_source_backtesting.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/databento_data_polars_backtesting.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/databento_data_polars_live.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/example_broker_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/exceptions.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/interactive_brokers_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/interactive_brokers_rest_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/pandas_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/polars_mixin.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/polygon_data_polars.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/projectx_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/schwab_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/tradier_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/tradovate_data.cpython-312.pyc +0 -0
- lumibot/data_sources/__pycache__/yahoo_data_polars.cpython-312.pyc +0 -0
- lumibot/data_sources/data_source_backtesting.py +3 -5
- lumibot/data_sources/databento_data.py +5 -5
- lumibot/data_sources/databento_data_polars_backtesting.py +636 -0
- lumibot/data_sources/databento_data_polars_live.py +793 -0
- lumibot/data_sources/pandas_data.py +6 -3
- lumibot/data_sources/polars_mixin.py +126 -21
- lumibot/data_sources/tradeovate_data.py +80 -0
- lumibot/data_sources/tradier_data.py +2 -1
- lumibot/entities/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/asset.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/bar.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/bars.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/chains.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/data.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/dataline.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/order.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/position.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/quote.cpython-312.pyc +0 -0
- lumibot/entities/__pycache__/trading_fee.cpython-312.pyc +0 -0
- lumibot/entities/asset.py +8 -0
- lumibot/entities/order.py +1 -1
- lumibot/entities/quote.py +14 -0
- lumibot/example_strategies/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/example_strategies/__pycache__/test_broker_functions.cpython-312-pytest-8.4.1.pyc +0 -0
- lumibot/strategies/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/strategies/__pycache__/_strategy.cpython-312.pyc +0 -0
- lumibot/strategies/__pycache__/strategy.cpython-312.pyc +0 -0
- lumibot/strategies/__pycache__/strategy_executor.cpython-312.pyc +0 -0
- lumibot/strategies/_strategy.py +95 -27
- lumibot/strategies/strategy.py +5 -6
- lumibot/strategies/strategy_executor.py +2 -2
- lumibot/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/alpaca_helpers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/bitunix_helpers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/black_scholes.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/ccxt_data_store.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/databento_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/databento_helper_polars.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/debugers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/decorators.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/helpers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/indicators.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/lumibot_logger.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/pandas.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/polygon_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/polygon_helper_async.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/polygon_helper_polars_optimized.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/projectx_helpers.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/schwab_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/thetadata_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/types.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/yahoo_helper.cpython-312.pyc +0 -0
- lumibot/tools/__pycache__/yahoo_helper_polars_optimized.cpython-312.pyc +0 -0
- lumibot/tools/databento_helper.py +384 -133
- lumibot/tools/databento_helper_polars.py +218 -156
- lumibot/tools/databento_roll.py +216 -0
- lumibot/tools/lumibot_logger.py +32 -17
- lumibot/tools/polygon_helper.py +65 -0
- lumibot/tools/thetadata_helper.py +588 -70
- lumibot/traders/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/traders/__pycache__/trader.cpython-312.pyc +0 -0
- lumibot/traders/trader.py +1 -1
- lumibot/trading_builtins/__pycache__/__init__.cpython-312.pyc +0 -0
- lumibot/trading_builtins/__pycache__/custom_stream.cpython-312.pyc +0 -0
- lumibot/trading_builtins/__pycache__/safe_list.cpython-312.pyc +0 -0
- {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/METADATA +1 -2
- {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/RECORD +164 -46
- tests/backtest/check_timing_offset.py +198 -0
- tests/backtest/check_volume_spike.py +112 -0
- tests/backtest/comprehensive_comparison.py +166 -0
- tests/backtest/debug_comparison.py +91 -0
- tests/backtest/diagnose_price_difference.py +97 -0
- tests/backtest/direct_api_comparison.py +203 -0
- tests/backtest/profile_thetadata_vs_polygon.py +255 -0
- tests/backtest/root_cause_analysis.py +109 -0
- tests/backtest/test_accuracy_verification.py +244 -0
- tests/backtest/test_daily_data_timestamp_comparison.py +801 -0
- tests/backtest/test_databento.py +57 -0
- tests/backtest/test_databento_comprehensive_trading.py +564 -0
- tests/backtest/test_debug_avg_fill_price.py +112 -0
- tests/backtest/test_dividends.py +8 -3
- tests/backtest/test_example_strategies.py +54 -47
- tests/backtest/test_futures_edge_cases.py +451 -0
- tests/backtest/test_futures_single_trade.py +270 -0
- tests/backtest/test_futures_ultra_simple.py +191 -0
- tests/backtest/test_index_data_verification.py +348 -0
- tests/backtest/test_polygon.py +45 -24
- tests/backtest/test_thetadata.py +246 -60
- tests/backtest/test_thetadata_comprehensive.py +729 -0
- tests/backtest/test_thetadata_vs_polygon.py +557 -0
- tests/backtest/test_yahoo.py +1 -2
- tests/conftest.py +20 -0
- tests/test_backtesting_data_source_env.py +249 -0
- tests/test_backtesting_quiet_logs_complete.py +10 -11
- tests/test_databento_helper.py +73 -86
- tests/test_databento_live.py +10 -10
- tests/test_databento_timezone_fixes.py +21 -4
- tests/test_get_historical_prices.py +6 -6
- tests/test_options_helper.py +162 -40
- tests/test_polygon_helper.py +21 -13
- tests/test_quiet_logs_requirements.py +5 -5
- tests/test_thetadata_helper.py +487 -171
- tests/test_yahoo_data.py +125 -0
- {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/LICENSE +0 -0
- {lumibot-4.0.22.dist-info → lumibot-4.1.0.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
A
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
357
|
+
# Wrap in Chains entity for modern API
|
|
358
|
+
return Chains(chains_dict)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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(
|
|
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):
|