lumibot 4.0.23__py3-none-any.whl → 4.1.1__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 (161) 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 +145 -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/__pycache__/__init__.cpython-312.pyc +0 -0
  37. lumibot/data_sources/__pycache__/alpaca_data.cpython-312.pyc +0 -0
  38. lumibot/data_sources/__pycache__/alpha_vantage_data.cpython-312.pyc +0 -0
  39. lumibot/data_sources/__pycache__/bitunix_data.cpython-312.pyc +0 -0
  40. lumibot/data_sources/__pycache__/ccxt_backtesting_data.cpython-312.pyc +0 -0
  41. lumibot/data_sources/__pycache__/ccxt_data.cpython-312.pyc +0 -0
  42. lumibot/data_sources/__pycache__/data_source.cpython-312.pyc +0 -0
  43. lumibot/data_sources/__pycache__/data_source_backtesting.cpython-312.pyc +0 -0
  44. lumibot/data_sources/__pycache__/databento_data_polars_backtesting.cpython-312.pyc +0 -0
  45. lumibot/data_sources/__pycache__/databento_data_polars_live.cpython-312.pyc +0 -0
  46. lumibot/data_sources/__pycache__/example_broker_data.cpython-312.pyc +0 -0
  47. lumibot/data_sources/__pycache__/exceptions.cpython-312.pyc +0 -0
  48. lumibot/data_sources/__pycache__/interactive_brokers_data.cpython-312.pyc +0 -0
  49. lumibot/data_sources/__pycache__/interactive_brokers_rest_data.cpython-312.pyc +0 -0
  50. lumibot/data_sources/__pycache__/pandas_data.cpython-312.pyc +0 -0
  51. lumibot/data_sources/__pycache__/polars_mixin.cpython-312.pyc +0 -0
  52. lumibot/data_sources/__pycache__/polygon_data_polars.cpython-312.pyc +0 -0
  53. lumibot/data_sources/__pycache__/projectx_data.cpython-312.pyc +0 -0
  54. lumibot/data_sources/__pycache__/schwab_data.cpython-312.pyc +0 -0
  55. lumibot/data_sources/__pycache__/tradier_data.cpython-312.pyc +0 -0
  56. lumibot/data_sources/__pycache__/tradovate_data.cpython-312.pyc +0 -0
  57. lumibot/data_sources/__pycache__/yahoo_data_polars.cpython-312.pyc +0 -0
  58. lumibot/data_sources/data_source_backtesting.py +3 -5
  59. lumibot/data_sources/databento_data_polars_backtesting.py +194 -48
  60. lumibot/data_sources/pandas_data.py +6 -3
  61. lumibot/data_sources/polars_mixin.py +126 -21
  62. lumibot/data_sources/tradeovate_data.py +80 -0
  63. lumibot/data_sources/tradier_data.py +2 -1
  64. lumibot/entities/__pycache__/__init__.cpython-312.pyc +0 -0
  65. lumibot/entities/__pycache__/asset.cpython-312.pyc +0 -0
  66. lumibot/entities/__pycache__/bar.cpython-312.pyc +0 -0
  67. lumibot/entities/__pycache__/bars.cpython-312.pyc +0 -0
  68. lumibot/entities/__pycache__/chains.cpython-312.pyc +0 -0
  69. lumibot/entities/__pycache__/data.cpython-312.pyc +0 -0
  70. lumibot/entities/__pycache__/dataline.cpython-312.pyc +0 -0
  71. lumibot/entities/__pycache__/order.cpython-312.pyc +0 -0
  72. lumibot/entities/__pycache__/position.cpython-312.pyc +0 -0
  73. lumibot/entities/__pycache__/quote.cpython-312.pyc +0 -0
  74. lumibot/entities/__pycache__/trading_fee.cpython-312.pyc +0 -0
  75. lumibot/entities/asset.py +8 -0
  76. lumibot/entities/order.py +1 -1
  77. lumibot/entities/quote.py +14 -0
  78. lumibot/example_strategies/__pycache__/__init__.cpython-312.pyc +0 -0
  79. lumibot/example_strategies/__pycache__/test_broker_functions.cpython-312-pytest-8.4.1.pyc +0 -0
  80. lumibot/strategies/__pycache__/__init__.cpython-312.pyc +0 -0
  81. lumibot/strategies/__pycache__/_strategy.cpython-312.pyc +0 -0
  82. lumibot/strategies/__pycache__/strategy.cpython-312.pyc +0 -0
  83. lumibot/strategies/__pycache__/strategy_executor.cpython-312.pyc +0 -0
  84. lumibot/strategies/_strategy.py +95 -27
  85. lumibot/strategies/strategy.py +5 -6
  86. lumibot/strategies/strategy_executor.py +2 -2
  87. lumibot/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  88. lumibot/tools/__pycache__/alpaca_helpers.cpython-312.pyc +0 -0
  89. lumibot/tools/__pycache__/bitunix_helpers.cpython-312.pyc +0 -0
  90. lumibot/tools/__pycache__/black_scholes.cpython-312.pyc +0 -0
  91. lumibot/tools/__pycache__/ccxt_data_store.cpython-312.pyc +0 -0
  92. lumibot/tools/__pycache__/databento_helper.cpython-312.pyc +0 -0
  93. lumibot/tools/__pycache__/databento_helper_polars.cpython-312.pyc +0 -0
  94. lumibot/tools/__pycache__/debugers.cpython-312.pyc +0 -0
  95. lumibot/tools/__pycache__/decorators.cpython-312.pyc +0 -0
  96. lumibot/tools/__pycache__/helpers.cpython-312.pyc +0 -0
  97. lumibot/tools/__pycache__/indicators.cpython-312.pyc +0 -0
  98. lumibot/tools/__pycache__/lumibot_logger.cpython-312.pyc +0 -0
  99. lumibot/tools/__pycache__/pandas.cpython-312.pyc +0 -0
  100. lumibot/tools/__pycache__/polygon_helper.cpython-312.pyc +0 -0
  101. lumibot/tools/__pycache__/polygon_helper_async.cpython-312.pyc +0 -0
  102. lumibot/tools/__pycache__/polygon_helper_polars_optimized.cpython-312.pyc +0 -0
  103. lumibot/tools/__pycache__/projectx_helpers.cpython-312.pyc +0 -0
  104. lumibot/tools/__pycache__/schwab_helper.cpython-312.pyc +0 -0
  105. lumibot/tools/__pycache__/thetadata_helper.cpython-312.pyc +0 -0
  106. lumibot/tools/__pycache__/types.cpython-312.pyc +0 -0
  107. lumibot/tools/__pycache__/yahoo_helper.cpython-312.pyc +0 -0
  108. lumibot/tools/__pycache__/yahoo_helper_polars_optimized.cpython-312.pyc +0 -0
  109. lumibot/tools/databento_helper.py +384 -133
  110. lumibot/tools/databento_helper_polars.py +218 -156
  111. lumibot/tools/databento_roll.py +216 -0
  112. lumibot/tools/lumibot_logger.py +32 -17
  113. lumibot/tools/polygon_helper.py +65 -0
  114. lumibot/tools/thetadata_helper.py +588 -70
  115. lumibot/traders/__pycache__/__init__.cpython-312.pyc +0 -0
  116. lumibot/traders/__pycache__/trader.cpython-312.pyc +0 -0
  117. lumibot/traders/trader.py +1 -1
  118. lumibot/trading_builtins/__pycache__/__init__.cpython-312.pyc +0 -0
  119. lumibot/trading_builtins/__pycache__/custom_stream.cpython-312.pyc +0 -0
  120. lumibot/trading_builtins/__pycache__/safe_list.cpython-312.pyc +0 -0
  121. lumibot-4.1.1.data/data/ThetaTerminal.jar +0 -0
  122. {lumibot-4.0.23.dist-info → lumibot-4.1.1.dist-info}/METADATA +1 -2
  123. {lumibot-4.0.23.dist-info → lumibot-4.1.1.dist-info}/RECORD +161 -44
  124. tests/backtest/check_timing_offset.py +198 -0
  125. tests/backtest/check_volume_spike.py +112 -0
  126. tests/backtest/comprehensive_comparison.py +166 -0
  127. tests/backtest/debug_comparison.py +91 -0
  128. tests/backtest/diagnose_price_difference.py +97 -0
  129. tests/backtest/direct_api_comparison.py +203 -0
  130. tests/backtest/profile_thetadata_vs_polygon.py +255 -0
  131. tests/backtest/root_cause_analysis.py +109 -0
  132. tests/backtest/test_accuracy_verification.py +244 -0
  133. tests/backtest/test_daily_data_timestamp_comparison.py +801 -0
  134. tests/backtest/test_databento.py +4 -0
  135. tests/backtest/test_databento_comprehensive_trading.py +564 -0
  136. tests/backtest/test_debug_avg_fill_price.py +112 -0
  137. tests/backtest/test_dividends.py +8 -3
  138. tests/backtest/test_example_strategies.py +54 -47
  139. tests/backtest/test_futures_edge_cases.py +451 -0
  140. tests/backtest/test_futures_single_trade.py +270 -0
  141. tests/backtest/test_futures_ultra_simple.py +191 -0
  142. tests/backtest/test_index_data_verification.py +348 -0
  143. tests/backtest/test_polygon.py +45 -24
  144. tests/backtest/test_thetadata.py +246 -60
  145. tests/backtest/test_thetadata_comprehensive.py +729 -0
  146. tests/backtest/test_thetadata_vs_polygon.py +557 -0
  147. tests/backtest/test_yahoo.py +1 -2
  148. tests/conftest.py +20 -0
  149. tests/test_backtesting_data_source_env.py +249 -0
  150. tests/test_backtesting_quiet_logs_complete.py +10 -11
  151. tests/test_databento_helper.py +76 -90
  152. tests/test_databento_timezone_fixes.py +21 -4
  153. tests/test_get_historical_prices.py +6 -6
  154. tests/test_options_helper.py +162 -40
  155. tests/test_polygon_helper.py +21 -13
  156. tests/test_quiet_logs_requirements.py +5 -5
  157. tests/test_thetadata_helper.py +487 -171
  158. tests/test_yahoo_data.py +125 -0
  159. {lumibot-4.0.23.dist-info → lumibot-4.1.1.dist-info}/LICENSE +0 -0
  160. {lumibot-4.0.23.dist-info → lumibot-4.1.1.dist-info}/WHEEL +0 -0
  161. {lumibot-4.0.23.dist-info → lumibot-4.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,249 @@
1
+ """
2
+ Test for BACKTESTING_DATA_SOURCE environment variable handling.
3
+ Ensures that datasource_class=None correctly auto-selects from the env var.
4
+ """
5
+ import os
6
+ from datetime import datetime
7
+ from unittest.mock import patch, MagicMock
8
+ import pytest
9
+ from dotenv import load_dotenv
10
+
11
+ # Load environment variables from .env file
12
+ load_dotenv()
13
+
14
+ from lumibot.strategies import Strategy
15
+ from lumibot.backtesting import (
16
+ PolygonDataBacktesting,
17
+ ThetaDataBacktesting,
18
+ YahooDataBacktesting,
19
+ AlpacaBacktesting,
20
+ )
21
+
22
+
23
+ @pytest.fixture
24
+ def restore_theta_credentials():
25
+ """Save and restore ThetaData credentials file after test."""
26
+ creds_path = "/Users/robertgrzesik/ThetaData/ThetaTerminal/creds.txt"
27
+ original = None
28
+
29
+ # Save original credentials if file exists
30
+ if os.path.exists(creds_path):
31
+ with open(creds_path, 'r') as f:
32
+ original = f.read()
33
+
34
+ yield
35
+
36
+ # Restore original credentials
37
+ if original is not None:
38
+ with open(creds_path, 'w') as f:
39
+ f.write(original)
40
+ elif os.path.exists(creds_path):
41
+ # File didn't exist before, remove it
42
+ os.remove(creds_path)
43
+
44
+
45
+ @pytest.fixture
46
+ def clean_environment():
47
+ """Save and restore environment variables after test."""
48
+ original_env = os.environ.copy()
49
+
50
+ yield
51
+
52
+ # Restore original environment completely
53
+ os.environ.clear()
54
+ os.environ.update(original_env)
55
+
56
+
57
+ class SimpleTestStrategy(Strategy):
58
+ """Minimal strategy for testing datasource auto-selection."""
59
+
60
+ def initialize(self):
61
+ self.sleeptime = "1D"
62
+
63
+ def on_trading_iteration(self):
64
+ if self.first_iteration:
65
+ # Just buy one share to have some activity
66
+ order = self.create_order("SPY", quantity=1, side="buy")
67
+ self.submit_order(order)
68
+
69
+
70
+ class TestBacktestingDataSourceEnv:
71
+ """Test BACKTESTING_DATA_SOURCE environment variable."""
72
+
73
+ def test_auto_select_polygon_case_insensitive(self, clean_environment, restore_theta_credentials, caplog):
74
+ """Test that BACKTESTING_DATA_SOURCE=polygon (lowercase) selects PolygonDataBacktesting."""
75
+ # Configure caplog to capture INFO level logs from lumibot.strategies._strategy
76
+ import logging
77
+ caplog.set_level(logging.INFO, logger='lumibot.strategies._strategy')
78
+
79
+ with patch.dict(os.environ, {'BACKTESTING_DATA_SOURCE': 'polygon'}):
80
+ # Re-import credentials to pick up env change
81
+ from importlib import reload
82
+ import lumibot.credentials
83
+ reload(lumibot.credentials)
84
+
85
+ backtesting_start = datetime(2023, 1, 1)
86
+ backtesting_end = datetime(2023, 1, 10) # Shorter backtest for speed
87
+
88
+ # Run a short backtest to verify env var is read
89
+ SimpleTestStrategy.run_backtest(
90
+ None, # Auto-select from env var
91
+ backtesting_start=backtesting_start,
92
+ backtesting_end=backtesting_end,
93
+ polygon_api_key="test_key",
94
+ show_plot=False,
95
+ show_tearsheet=False,
96
+ show_indicators=False,
97
+ show_progress_bar=False,
98
+ )
99
+
100
+ # Verify the log message shows polygon was selected
101
+ assert any("Auto-selected backtesting data source from BACKTESTING_DATA_SOURCE env var: polygon" in record.message
102
+ for record in caplog.records)
103
+
104
+ def test_auto_select_thetadata_case_insensitive(self, clean_environment, restore_theta_credentials, caplog):
105
+ """Test that BACKTESTING_DATA_SOURCE=THETADATA (uppercase) selects ThetaDataBacktesting."""
106
+ with patch.dict(os.environ, {'BACKTESTING_DATA_SOURCE': 'THETADATA'}):
107
+ # Re-import credentials to pick up env change
108
+ from importlib import reload
109
+ import lumibot.credentials
110
+ reload(lumibot.credentials)
111
+
112
+ backtesting_start = datetime(2023, 1, 1)
113
+ backtesting_end = datetime(2023, 1, 10) # Shorter backtest for speed
114
+
115
+ # Try to run backtest - may fail due to test credentials, but that's okay
116
+ try:
117
+ SimpleTestStrategy.run_backtest(
118
+ None, # Auto-select from env var
119
+ backtesting_start=backtesting_start,
120
+ backtesting_end=backtesting_end,
121
+ thetadata_username="test_user",
122
+ thetadata_password="test_pass",
123
+ show_plot=False,
124
+ show_tearsheet=False,
125
+ show_indicators=False,
126
+ show_progress_bar=False,
127
+ )
128
+ except Exception:
129
+ # Expected to fail with test credentials - that's okay
130
+ pass
131
+
132
+ # Verify the log message shows thetadata was selected OR check for ThetaData error
133
+ thetadata_selected = any("Auto-selected backtesting data source from BACKTESTING_DATA_SOURCE env var: THETADATA" in record.message
134
+ for record in caplog.records)
135
+ thetadata_attempted = any("Cannot connect to Theta Data" in record.message or "ThetaData" in record.message
136
+ for record in caplog.records)
137
+ assert thetadata_selected or thetadata_attempted, "ThetaData was not selected from env var"
138
+
139
+ def test_auto_select_yahoo(self, clean_environment, restore_theta_credentials, caplog):
140
+ """Test that BACKTESTING_DATA_SOURCE=Yahoo selects YahooDataBacktesting."""
141
+ with patch.dict(os.environ, {'BACKTESTING_DATA_SOURCE': 'Yahoo'}):
142
+ # Re-import credentials to pick up env change
143
+ from importlib import reload
144
+ import lumibot.credentials
145
+ reload(lumibot.credentials)
146
+
147
+ backtesting_start = datetime(2023, 1, 1)
148
+ backtesting_end = datetime(2023, 1, 10) # Shorter backtest for speed
149
+
150
+ # Run a short backtest to verify env var is read
151
+ # If this completes without error, Yahoo was successfully selected
152
+ SimpleTestStrategy.run_backtest(
153
+ None, # Auto-select from env var
154
+ backtesting_start=backtesting_start,
155
+ backtesting_end=backtesting_end,
156
+ show_plot=False,
157
+ show_tearsheet=False,
158
+ show_indicators=False,
159
+ show_progress_bar=False,
160
+ )
161
+
162
+ # If we got here without exception, Yahoo was successfully used
163
+ # (No explicit verification needed - successful backtest is the proof)
164
+
165
+ def test_invalid_data_source_raises_error(self, clean_environment, restore_theta_credentials):
166
+ """Test that invalid BACKTESTING_DATA_SOURCE raises ValueError."""
167
+ with patch.dict(os.environ, {'BACKTESTING_DATA_SOURCE': 'InvalidSource'}):
168
+ # Re-import credentials to pick up env change
169
+ from importlib import reload
170
+ import lumibot.credentials
171
+ reload(lumibot.credentials)
172
+
173
+ backtesting_start = datetime(2023, 1, 1)
174
+ backtesting_end = datetime(2023, 1, 31)
175
+
176
+ with pytest.raises(ValueError, match="Unknown BACKTESTING_DATA_SOURCE"):
177
+ SimpleTestStrategy.run_backtest(
178
+ None, # Auto-select from env var
179
+ backtesting_start=backtesting_start,
180
+ backtesting_end=backtesting_end,
181
+ show_plot=False,
182
+ show_tearsheet=False,
183
+ show_indicators=False,
184
+ )
185
+
186
+ def test_explicit_datasource_overrides_env(self, clean_environment, restore_theta_credentials, caplog):
187
+ """Test that explicit datasource_class overrides BACKTESTING_DATA_SOURCE env var."""
188
+ with patch.dict(os.environ, {'BACKTESTING_DATA_SOURCE': 'polygon'}):
189
+ # Re-import credentials to pick up env change
190
+ from importlib import reload
191
+ import lumibot.credentials
192
+ reload(lumibot.credentials)
193
+
194
+ backtesting_start = datetime(2023, 1, 1)
195
+ backtesting_end = datetime(2023, 1, 10) # Shorter backtest for speed
196
+
197
+ # Run backtest with explicit Yahoo datasource despite env saying polygon
198
+ SimpleTestStrategy.run_backtest(
199
+ YahooDataBacktesting, # Explicit override
200
+ backtesting_start=backtesting_start,
201
+ backtesting_end=backtesting_end,
202
+ show_plot=False,
203
+ show_tearsheet=False,
204
+ show_indicators=False,
205
+ show_progress_bar=False,
206
+ )
207
+
208
+ # Verify the auto-select message was NOT logged (explicit datasource was used)
209
+ assert not any("Auto-selected backtesting data source" in record.message
210
+ for record in caplog.records)
211
+
212
+ def test_default_thetadata_when_no_env_set(self, clean_environment, restore_theta_credentials, caplog):
213
+ """Test that ThetaData is the default when BACKTESTING_DATA_SOURCE is not set."""
214
+ # Remove BACKTESTING_DATA_SOURCE from env
215
+ env_without_datasource = {k: v for k, v in os.environ.items() if k != 'BACKTESTING_DATA_SOURCE'}
216
+
217
+ with patch.dict(os.environ, env_without_datasource, clear=True):
218
+ # Re-import credentials to pick up env change
219
+ from importlib import reload
220
+ import lumibot.credentials
221
+ reload(lumibot.credentials)
222
+
223
+ backtesting_start = datetime(2023, 1, 1)
224
+ backtesting_end = datetime(2023, 1, 10) # Shorter backtest for speed
225
+
226
+ # Try to run backtest - may fail due to test credentials, but that's okay
227
+ try:
228
+ SimpleTestStrategy.run_backtest(
229
+ None, # Auto-select from env var (should default to ThetaData)
230
+ backtesting_start=backtesting_start,
231
+ backtesting_end=backtesting_end,
232
+ thetadata_username="test_user",
233
+ thetadata_password="test_pass",
234
+ show_plot=False,
235
+ show_tearsheet=False,
236
+ show_indicators=False,
237
+ show_progress_bar=False,
238
+ )
239
+ except Exception:
240
+ # Expected to fail with test credentials - that's okay
241
+ pass
242
+
243
+ # Verify ThetaData was attempted (no auto-select message since it's the default)
244
+ assert any("Cannot connect to Theta Data" in record.message or "ThetaData" in record.message
245
+ for record in caplog.records), "ThetaData was not used as default"
246
+
247
+
248
+ if __name__ == "__main__":
249
+ pytest.main([__file__, "-v"])
@@ -203,13 +203,12 @@ def test_quiet_logs_false_shows_all_info_plus(clean_environment, temp_logfile):
203
203
  handler.flush()
204
204
 
205
205
  console_output = captured_console.getvalue()
206
-
207
- # During backtesting, console should ALWAYS be ERROR+ only (regardless of BACKTESTING_QUIET_LOGS)
208
- # BACKTESTING_QUIET_LOGS only controls file logging
206
+
207
+ # When BACKTESTING_QUIET_LOGS=false, console should show INFO+ during backtesting
209
208
  assert "DEBUG should not appear" not in console_output
210
- assert "INFO should appear" not in console_output # Console should NOT show INFO during backtesting
211
- assert "WARNING should appear" not in console_output # Console should NOT show WARNING during backtesting
212
- assert "ERROR should appear" in console_output # Console SHOULD show ERROR during backtesting
209
+ assert "INFO should appear" in console_output # Console SHOULD show INFO when quiet_logs=false
210
+ assert "WARNING should appear" in console_output # Console SHOULD show WARNING when quiet_logs=false
211
+ assert "ERROR should appear" in console_output # Console SHOULD show ERROR when quiet_logs=false
213
212
 
214
213
  # Read file content
215
214
  with open(temp_logfile, 'r') as f:
@@ -331,11 +330,11 @@ def test_bot_manager_compatibility(clean_environment, temp_logfile):
331
330
  handler.flush()
332
331
 
333
332
  console_output = captured_console.getvalue()
334
-
335
- # Bot Manager only reads from CloudWatch/file logs, not console
336
- # Console should still be ERROR+ only during backtesting
337
- assert "Bot Manager should see this INFO" not in console_output
338
- assert "Bot Manager should see this WARNING" not in console_output
333
+
334
+ # When BACKTESTING_QUIET_LOGS=False, console should show INFO+ during backtesting
335
+ # Bot Manager can read from either CloudWatch/file logs or console
336
+ assert "Bot Manager should see this INFO" in console_output
337
+ assert "Bot Manager should see this WARNING" in console_output
339
338
  assert "Bot Manager should see this ERROR" in console_output
340
339
 
341
340
  # Read file content
@@ -16,8 +16,7 @@ class TestDataBentoHelper(unittest.TestCase):
16
16
  self.api_key = "test_api_key"
17
17
  self.test_asset_future = Asset(
18
18
  symbol="ES",
19
- asset_type="future",
20
- expiration=datetime(2025, 3, 15).date()
19
+ asset_type="CONT_FUTURE"
21
20
  )
22
21
  self.test_asset_stock = Asset(
23
22
  symbol="AAPL",
@@ -41,10 +40,13 @@ class TestDataBentoHelper(unittest.TestCase):
41
40
  result = databento_helper._format_futures_symbol_for_databento(mes_continuous, reference_date)
42
41
  self.assertIn("MESH5", result)
43
42
 
44
- # Test regular future (no expiration) - should return raw symbol
43
+ # Test regular future (no expiration) - should auto-resolve via idiot-proofing
44
+ # Idiot-proofing: futures without expiration are auto-treated as continuous and resolved
45
45
  regular_future = Asset(symbol="ES", asset_type="future")
46
46
  result = databento_helper._format_futures_symbol_for_databento(regular_future)
47
- self.assertEqual(result, "ES")
47
+ # Should resolve to a contract month (e.g., ESZ5 for Dec 2025)
48
+ self.assertIn("ES", result)
49
+ self.assertRegex(result, r"ES[FGHJKMNQUVXZ]\d", "Should auto-resolve to contract format like ESZ5")
48
50
 
49
51
  # Test specific contract with expiration (March 2025 = H25)
50
52
  result = databento_helper._format_futures_symbol_for_databento(self.test_asset_future)
@@ -129,45 +131,42 @@ class TestDataBentoHelper(unittest.TestCase):
129
131
  self.assertEqual(client.timeout, 30)
130
132
  self.assertEqual(client.max_retries, 3)
131
133
 
132
- @patch('lumibot.tools.databento_helper.DATABENTO_AVAILABLE', True)
133
- @patch('lumibot.tools.databento_helper.DataBentoClient')
134
- def test_get_price_data_from_databento_success(self, mock_client_class):
135
- """Test successful data retrieval"""
136
- # Mock client and response
137
- mock_client_instance = Mock()
138
- mock_client_class.return_value = mock_client_instance
139
-
140
- # Create mock DataFrame response
141
- mock_df = pd.DataFrame({
142
- 'ts_event': pd.to_datetime(['2025-01-01 09:30:00', '2025-01-01 09:31:00']),
143
- 'open': [100.0, 101.0],
144
- 'high': [102.0, 103.0],
145
- 'low': [99.0, 100.0],
146
- 'close': [101.0, 102.0],
147
- 'volume': [1000, 1100]
148
- })
149
-
150
- mock_client_instance.get_historical_data.return_value = mock_df
151
-
152
- # Mock cache functions
153
- with patch('lumibot.tools.databento_helper._load_cache', return_value=None), \
154
- patch('lumibot.tools.databento_helper._save_cache') as mock_save:
155
-
156
- result = databento_helper.get_price_data_from_databento(
157
- api_key=self.api_key,
158
- asset=self.test_asset_future,
159
- start=self.start_date,
160
- end=self.end_date,
161
- timestep="minute"
162
- )
163
-
164
- # Verify result
165
- self.assertIsNotNone(result)
166
- self.assertIsInstance(result, pd.DataFrame)
167
- self.assertEqual(len(result), 2)
168
-
169
- # Verify cache was called
170
- mock_save.assert_called_once()
134
+ def test_get_price_data_from_databento_success(self):
135
+ """Test successful data retrieval using real DataBento API"""
136
+ import os
137
+
138
+ # Use real API key from environment
139
+ api_key = os.environ.get("DATABENTO_API_KEY")
140
+ if not api_key:
141
+ self.skipTest("DATABENTO_API_KEY not found in environment")
142
+
143
+ # Use Aug 2024 dates (past data that definitely exists)
144
+ start_date = datetime(2024, 8, 20)
145
+ end_date = datetime(2024, 8, 21)
146
+
147
+ # Test with ES continuous futures (will resolve to appropriate contract)
148
+ es_asset = Asset(
149
+ symbol="ES",
150
+ asset_type=Asset.AssetType.CONT_FUTURE
151
+ )
152
+
153
+ result = databento_helper.get_price_data_from_databento(
154
+ api_key=api_key,
155
+ asset=es_asset,
156
+ start=start_date,
157
+ end=end_date,
158
+ timestep="minute"
159
+ )
160
+
161
+ # Verify result
162
+ self.assertIsNotNone(result, "Should return data from DataBento API")
163
+ self.assertIsInstance(result, pd.DataFrame)
164
+ self.assertGreater(len(result), 0, "Should have at least some data rows")
165
+
166
+ # Verify DataFrame has expected columns
167
+ expected_columns = ['open', 'high', 'low', 'close', 'volume']
168
+ for col in expected_columns:
169
+ self.assertIn(col, result.columns, f"DataFrame should have {col} column")
171
170
 
172
171
  @patch('lumibot.tools.databento_helper.DATABENTO_AVAILABLE', False)
173
172
  def test_get_price_data_databento_unavailable(self):
@@ -217,55 +216,42 @@ class TestDataBentoHelper(unittest.TestCase):
217
216
  self.end_date,
218
217
  "minute"
219
218
  )
220
-
221
- expected_name = "ES_20250315_minute_20250101_20250131.parquet"
219
+
220
+ expected_name = "ES_minute_202501010000_202501310000.parquet"
222
221
  self.assertEqual(filename.name, expected_name)
223
222
 
224
- @patch('lumibot.tools.databento_helper.DATABENTO_AVAILABLE', True)
225
- @patch('lumibot.tools.databento_helper.DataBentoClient')
226
- def test_no_retry_logic_for_correct_symbol(self, mock_client_class):
227
- """Test that the function uses correct symbol/dataset without retry logic"""
228
- # Mock client and response
229
- mock_client_instance = Mock()
230
- mock_client_class.return_value = mock_client_instance
231
-
232
- # Create mock DataFrame response
233
- mock_df = pd.DataFrame({
234
- 'ts_event': pd.to_datetime(['2025-01-01 09:30:00', '2025-01-01 09:31:00']),
235
- 'open': [100.0, 101.0],
236
- 'high': [102.0, 103.0],
237
- 'low': [99.0, 100.0],
238
- 'close': [101.0, 102.0],
239
- 'volume': [1000, 1100]
240
- })
241
-
242
- mock_client_instance.get_historical_data.return_value = mock_df
243
-
244
- # Mock cache functions
245
- with patch('lumibot.tools.databento_helper._load_cache', return_value=None), \
246
- patch('lumibot.tools.databento_helper._save_cache'):
247
-
248
- # Test with MES continuous futures
249
- mes_asset = Asset(symbol="MES", asset_type="future")
250
- result = databento_helper.get_price_data_from_databento(
251
- api_key=self.api_key,
252
- asset=mes_asset,
253
- start=self.start_date,
254
- end=self.end_date,
255
- timestep="minute"
256
- )
257
-
258
- # Verify result
259
- self.assertIsNotNone(result)
260
-
261
- # Verify that get_historical_data was called exactly once with correct parameters
262
- mock_client_instance.get_historical_data.assert_called_once()
263
- call_args = mock_client_instance.get_historical_data.call_args
264
-
265
- # Check that it was called with the correct symbol (MES) and dataset (GLBX.MDP3)
266
- self.assertEqual(call_args[1]['symbols'], 'MES')
267
- self.assertEqual(call_args[1]['dataset'], 'GLBX.MDP3')
268
- self.assertEqual(call_args[1]['schema'], 'ohlcv-1m')
223
+ def test_no_retry_logic_for_correct_symbol(self):
224
+ """Test that the function uses correct symbol/dataset without retry logic - using real API"""
225
+ import os
226
+
227
+ # Use real API key from environment
228
+ api_key = os.environ.get("DATABENTO_API_KEY")
229
+ if not api_key:
230
+ self.skipTest("DATABENTO_API_KEY not found in environment")
231
+
232
+ # Use recent dates that should have data
233
+ start_date = datetime(2025, 1, 2)
234
+ end_date = datetime(2025, 1, 3)
235
+
236
+ # Test with MES continuous futures
237
+ mes_asset = Asset(symbol="MES", asset_type="future")
238
+ result = databento_helper.get_price_data_from_databento(
239
+ api_key=api_key,
240
+ asset=mes_asset,
241
+ start=start_date,
242
+ end=end_date,
243
+ timestep="minute"
244
+ )
245
+
246
+ # Verify result
247
+ self.assertIsNotNone(result, "Should return data for MES futures")
248
+ self.assertIsInstance(result, pd.DataFrame)
249
+ self.assertGreater(len(result), 0, "Should have data rows for MES")
250
+
251
+ # Verify DataFrame structure
252
+ expected_columns = ['open', 'high', 'low', 'close', 'volume']
253
+ for col in expected_columns:
254
+ self.assertIn(col, result.columns, f"DataFrame should have {col} column")
269
255
 
270
256
  def test_continuous_futures_integration_edge_cases(self):
271
257
  """Test edge cases for continuous futures integration with Asset class"""
@@ -273,10 +273,27 @@ def test_databento_api_integration(api_key_available):
273
273
  """Test DataBento API integration when API key is available"""
274
274
  if not api_key_available:
275
275
  pytest.skip("DataBento API key not available")
276
-
277
- # This test would run with real API key if available
278
- # For now, we skip to avoid API calls in unit tests
279
- pass
276
+
277
+ # Test actual DataBento API integration with real credentials
278
+ from lumibot.credentials import DATABENTO_CONFIG
279
+
280
+ # Verify credentials are available
281
+ api_key = DATABENTO_CONFIG.get('API_KEY')
282
+ if not api_key or api_key == '<your key here>':
283
+ pytest.skip("Valid DataBento API key not configured")
284
+
285
+ # Initialize DataBento data source with real API key
286
+ data_source = DataBentoData(api_key=api_key)
287
+
288
+ # Verify initialization succeeded
289
+ assert data_source is not None
290
+ assert data_source.SOURCE == "DATABENTO"
291
+ assert data_source._api_key == api_key # Note: private attribute
292
+
293
+ # Verify the data source is a live data source (not backtesting)
294
+ assert hasattr(data_source, 'get_last_price') or hasattr(data_source, 'get_quote')
295
+
296
+ print(f"✓ DataBento API integration successful with key: {api_key[:10]}...")
280
297
 
281
298
 
282
299
  if __name__ == "__main__":
@@ -128,10 +128,10 @@ class TestBacktestingDataSources:
128
128
  timestep = "day"
129
129
  tzinfo = pytz.timezone('America/New_York')
130
130
 
131
- datetime_start = tzinfo.localize(datetime(2019, 1, 2))
132
- datetime_end = tzinfo.localize(datetime(2019, 12, 31))
131
+ datetime_start = tzinfo.localize(datetime(2025, 1, 2))
132
+ datetime_end = tzinfo.localize(datetime(2025, 12, 31))
133
133
  # First trading day after MLK day
134
- now = tzinfo.localize(datetime(2019, 1, 22)).replace(hour=9, minute=30)
134
+ now = tzinfo.localize(datetime(2025, 1, 21)).replace(hour=9, minute=30)
135
135
  data_source = PolygonDataBacktesting(
136
136
  datetime_start,
137
137
  datetime_end,
@@ -162,10 +162,10 @@ class TestBacktestingDataSources:
162
162
  timestep = "day"
163
163
  tzinfo = pytz.timezone('America/New_York')
164
164
 
165
- datetime_start = tzinfo.localize(datetime(2019, 1, 2))
166
- datetime_end = tzinfo.localize(datetime(2019, 12, 31))
165
+ datetime_start = tzinfo.localize(datetime(2025, 1, 2))
166
+ datetime_end = tzinfo.localize(datetime(2025, 12, 31))
167
167
  # First trading day after MLK day
168
- now = tzinfo.localize(datetime(2019, 1, 22)).replace(hour=9, minute=30)
168
+ now = tzinfo.localize(datetime(2025, 1, 21)).replace(hour=9, minute=30)
169
169
 
170
170
  length = 10
171
171
  data_source = PolygonDataBacktesting(