lumibot 4.0.23__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/__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_polars_backtesting.py +194 -48
- 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.23.dist-info → lumibot-4.1.0.dist-info}/METADATA +1 -2
- {lumibot-4.0.23.dist-info → lumibot-4.1.0.dist-info}/RECORD +160 -44
- 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 +4 -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_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.23.dist-info → lumibot-4.1.0.dist-info}/LICENSE +0 -0
- {lumibot-4.0.23.dist-info → lumibot-4.1.0.dist-info}/WHEEL +0 -0
- {lumibot-4.0.23.dist-info → lumibot-4.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import datetime
|
|
3
|
+
from openai import OpenAI
|
|
4
|
+
|
|
5
|
+
class GrokNewsHelper:
|
|
6
|
+
"""
|
|
7
|
+
GrokNewsHelper queries xAI's Grok API via the OpenAI-compatible client to fetch
|
|
8
|
+
financial/news data in a structured JSON format.
|
|
9
|
+
|
|
10
|
+
How it Works:
|
|
11
|
+
-------------
|
|
12
|
+
1. Build a system prompt describing the desired JSON schema in detail.
|
|
13
|
+
2. Use xAI's OpenAI-compatible client to create a chat completion with:
|
|
14
|
+
- A system message (detailed instructions).
|
|
15
|
+
- A user message (the actual user query).
|
|
16
|
+
3. Parse the assistant's text content as JSON.
|
|
17
|
+
4. Return a Python dictionary with fields:
|
|
18
|
+
{
|
|
19
|
+
"query": ...,
|
|
20
|
+
"timestamp_utc": ...,
|
|
21
|
+
"analysis_summary": ...,
|
|
22
|
+
"items": [...]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
--------
|
|
27
|
+
from grok_news_helper import GrokNewsHelper
|
|
28
|
+
|
|
29
|
+
helper = GrokNewsHelper(api_key="YOUR_XAI_API_KEY")
|
|
30
|
+
result = helper.execute_query("What stocks are trending right now on Twitter?")
|
|
31
|
+
print(json.dumps(result, indent=2))
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, api_key: str):
|
|
35
|
+
"""
|
|
36
|
+
Initialize the GrokNewsHelper with your xAI API key.
|
|
37
|
+
Internally creates an xAI OpenAI-compatible client.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
api_key : str
|
|
42
|
+
The xAI API key (required).
|
|
43
|
+
"""
|
|
44
|
+
if not api_key:
|
|
45
|
+
# Try to get the API key from the environment if not provided
|
|
46
|
+
import os
|
|
47
|
+
|
|
48
|
+
# Might be called GROK_API_KEY or XAI_API_KEY
|
|
49
|
+
api_key = os.getenv("GROK_API_KEY")
|
|
50
|
+
if not api_key:
|
|
51
|
+
api_key = os.getenv("XAI_API_KEY")
|
|
52
|
+
|
|
53
|
+
if not api_key:
|
|
54
|
+
raise ValueError("API key is required for GrokNewsHelper. Get one from x.ai and set it as GROK_API_KEY or XAI_API_KEY in your secrets, environment variables, .env file or directly in the code.")
|
|
55
|
+
|
|
56
|
+
self.api_key = api_key
|
|
57
|
+
|
|
58
|
+
# Create the xAI client (OpenAI-compatible)
|
|
59
|
+
self.client = OpenAI(
|
|
60
|
+
api_key=self.api_key,
|
|
61
|
+
base_url="https://api.x.ai/v1",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def build_prompt(self, user_query: str) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Constructs a system prompt describing the required JSON schema in detail
|
|
67
|
+
and instructing the model to avoid hallucinations and remain factual.
|
|
68
|
+
|
|
69
|
+
The schema includes fields like 'symbol', 'confidence', 'magnitude', etc.
|
|
70
|
+
Now also includes an optional 'price_targets' object with float fields.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
user_query : str
|
|
75
|
+
The question or prompt about financial/news data.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
str
|
|
80
|
+
The system message content with instructions for Grok.
|
|
81
|
+
"""
|
|
82
|
+
system_prompt = f"""\
|
|
83
|
+
You are a financial news aggregator assistant with real-time access to Twitter, news feeds,
|
|
84
|
+
and other finance data. Remain factual and accurate in your response (avoid hallucinations).
|
|
85
|
+
|
|
86
|
+
Your task is to return ONLY valid JSON, following this exact schema:
|
|
87
|
+
|
|
88
|
+
JSON Schema:
|
|
89
|
+
{{
|
|
90
|
+
"query": "<string, required - echo the user's query>",
|
|
91
|
+
"timestamp_utc": "<string, required - current UTC time in ISO 8601 format>",
|
|
92
|
+
"analysis_summary": "<string, required - short summary of the findings>",
|
|
93
|
+
"items": [
|
|
94
|
+
{{
|
|
95
|
+
"symbol": "<string, required - e.g. 'AAPL', 'BTC'. Always neds to be a valid symbol>",
|
|
96
|
+
"asset_type": "<string, required - 'stock', 'crypto', 'index', 'commodity', 'forex', 'none'>",
|
|
97
|
+
"headline": "<string, required - short note on why it's trending>",
|
|
98
|
+
"confidence": "<integer, 0-10, required - reliability score>",
|
|
99
|
+
"sentiment_score": "<integer, -10 to 10, required - negative is bearish, positive is bullish>",
|
|
100
|
+
"popularity_metric": "<integer >= 0, required - measure of mentions>",
|
|
101
|
+
"volume_of_messages": "<integer, optional - if known>",
|
|
102
|
+
"magnitude": "<integer, 0-10, required - overall impact level>",
|
|
103
|
+
"type_of_news": "<string, optional - e.g. 'earnings', 'ipo', 'macro', 'ceo_tweet'>",
|
|
104
|
+
"price_targets": {{
|
|
105
|
+
"low": "<float, optional>",
|
|
106
|
+
"high": "<float, optional>",
|
|
107
|
+
"average": "<float, optional>"
|
|
108
|
+
}},
|
|
109
|
+
"additional_info": {{
|
|
110
|
+
"sector": "<string, optional>",
|
|
111
|
+
"recent_events": "<string, optional>",
|
|
112
|
+
"notable_executive_actions": "<string, optional>",
|
|
113
|
+
"macro_support": "<string, optional>",
|
|
114
|
+
"related_tickers": ["<string>", "..."],
|
|
115
|
+
"external_links": ["<string>", "..."]
|
|
116
|
+
}}
|
|
117
|
+
}}
|
|
118
|
+
]
|
|
119
|
+
}}
|
|
120
|
+
|
|
121
|
+
Instructions:
|
|
122
|
+
1) Output MUST be valid JSON (no extra text or markdown).
|
|
123
|
+
2) If no data found, 'items' can be empty.
|
|
124
|
+
3) The 'magnitude' field is required (0-10).
|
|
125
|
+
4) Provide minimal text in 'analysis_summary' (1-3 sentences).
|
|
126
|
+
5) The field 'query' must repeat the user's question exactly.
|
|
127
|
+
|
|
128
|
+
Now, the user's query is:
|
|
129
|
+
\"{user_query}\"
|
|
130
|
+
|
|
131
|
+
Return only valid JSON following the schema.
|
|
132
|
+
"""
|
|
133
|
+
return system_prompt
|
|
134
|
+
|
|
135
|
+
def execute_query(self, user_query: str) -> dict:
|
|
136
|
+
"""
|
|
137
|
+
Executes a query by creating a chat completion via the xAI OpenAI-compatible client.
|
|
138
|
+
We pass a system message (JSON schema instructions) + user message (user_query).
|
|
139
|
+
|
|
140
|
+
Steps:
|
|
141
|
+
------
|
|
142
|
+
1) Build the system prompt with `build_prompt(user_query)`.
|
|
143
|
+
2) Call `client.chat.completions.create(...)` with:
|
|
144
|
+
- model = "grok-2-latest" (or any available model)
|
|
145
|
+
- messages (system + user).
|
|
146
|
+
- temperature=0 to reduce hallucinations.
|
|
147
|
+
3) Parse the assistant's output text as JSON.
|
|
148
|
+
4) Return a dictionary matching the schema. If parsing fails, return an error structure.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
user_query : str
|
|
153
|
+
The textual query about financial/news data (e.g. "Which stocks are trending now?").
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
dict
|
|
158
|
+
A dictionary conforming to the described JSON schema:
|
|
159
|
+
{
|
|
160
|
+
"query": "...",
|
|
161
|
+
"timestamp_utc": "...",
|
|
162
|
+
"analysis_summary": "...",
|
|
163
|
+
"items": [...]
|
|
164
|
+
}
|
|
165
|
+
"""
|
|
166
|
+
# 1) Build system prompt
|
|
167
|
+
system_msg = self.build_prompt(user_query)
|
|
168
|
+
|
|
169
|
+
# 2) Create the chat completion using the xAI client
|
|
170
|
+
try:
|
|
171
|
+
completion = self.client.chat.completions.create(
|
|
172
|
+
model="grok-2-latest",
|
|
173
|
+
messages=[
|
|
174
|
+
{"role": "system", "content": system_msg},
|
|
175
|
+
{"role": "user", "content": user_query}
|
|
176
|
+
],
|
|
177
|
+
temperature=0,
|
|
178
|
+
stream=False
|
|
179
|
+
)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
# If there's a network or API error, fallback with an error structure
|
|
182
|
+
return {
|
|
183
|
+
"query": user_query,
|
|
184
|
+
"timestamp_utc": datetime.datetime.now(datetime.UTC).isoformat()
|
|
185
|
+
if hasattr(datetime, 'UTC') else datetime.datetime.utcnow().isoformat(),
|
|
186
|
+
"analysis_summary": f"Error calling xAI API: {str(e)}",
|
|
187
|
+
"items": []
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# 3) Extract the assistant's text (the content of the first choice)
|
|
191
|
+
try:
|
|
192
|
+
assistant_text = completion.choices[0].message.content
|
|
193
|
+
except (AttributeError, IndexError, KeyError) as e:
|
|
194
|
+
return {
|
|
195
|
+
"query": user_query,
|
|
196
|
+
"timestamp_utc": datetime.datetime.now(datetime.UTC).isoformat()
|
|
197
|
+
if hasattr(datetime, 'UTC') else datetime.datetime.utcnow().isoformat(),
|
|
198
|
+
"analysis_summary": f"Error: Unexpected response format from Grok API. {str(e)}",
|
|
199
|
+
"items": []
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# 4) Parse the assistant's text as JSON
|
|
203
|
+
try:
|
|
204
|
+
data = json.loads(assistant_text)
|
|
205
|
+
except json.JSONDecodeError as e:
|
|
206
|
+
return {
|
|
207
|
+
"query": user_query,
|
|
208
|
+
"timestamp_utc": datetime.datetime.now(datetime.UTC).isoformat()
|
|
209
|
+
if hasattr(datetime, 'UTC') else datetime.datetime.utcnow().isoformat(),
|
|
210
|
+
"analysis_summary": f"Error: LLM output was not valid JSON. {str(e)}",
|
|
211
|
+
"items": []
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# 5) Post-process to ensure numeric fields are integers
|
|
215
|
+
self._post_process_data(data)
|
|
216
|
+
|
|
217
|
+
return data
|
|
218
|
+
|
|
219
|
+
def _post_process_data(self, data: dict) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Enforces that certain fields are integers:
|
|
222
|
+
- confidence
|
|
223
|
+
- sentiment_score
|
|
224
|
+
- popularity_metric
|
|
225
|
+
- magnitude
|
|
226
|
+
- volume_of_messages (if present)
|
|
227
|
+
|
|
228
|
+
Also handles optional price_targets as floats if present.
|
|
229
|
+
|
|
230
|
+
Modifies the 'data' dictionary in-place.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
data : dict
|
|
235
|
+
The parsed JSON dictionary from the assistant.
|
|
236
|
+
"""
|
|
237
|
+
items = data.get("items", [])
|
|
238
|
+
for item in items:
|
|
239
|
+
for int_field in ("confidence", "sentiment_score", "popularity_metric", "magnitude"):
|
|
240
|
+
if int_field in item:
|
|
241
|
+
try:
|
|
242
|
+
item[int_field] = int(item[int_field])
|
|
243
|
+
except (ValueError, TypeError):
|
|
244
|
+
item[int_field] = 0
|
|
245
|
+
|
|
246
|
+
if "volume_of_messages" in item:
|
|
247
|
+
try:
|
|
248
|
+
item["volume_of_messages"] = int(item["volume_of_messages"])
|
|
249
|
+
except (ValueError, TypeError):
|
|
250
|
+
item["volume_of_messages"] = 0
|
|
251
|
+
|
|
252
|
+
# Optional price_targets handling
|
|
253
|
+
if "price_targets" in item and isinstance(item["price_targets"], dict):
|
|
254
|
+
for float_field in ("low", "high", "average"):
|
|
255
|
+
if float_field in item["price_targets"]:
|
|
256
|
+
try:
|
|
257
|
+
item["price_targets"][float_field] = float(item["price_targets"][float_field])
|
|
258
|
+
except (ValueError, TypeError):
|
|
259
|
+
item["price_targets"][float_field] = None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ------------------------------------------------------------------------------
|
|
263
|
+
# Example usage in a standalone script:
|
|
264
|
+
# ------------------------------------------------------------------------------
|
|
265
|
+
if __name__ == "__main__":
|
|
266
|
+
# Attempt to retrieve xAI API key from environment
|
|
267
|
+
import os
|
|
268
|
+
import dotenv
|
|
269
|
+
|
|
270
|
+
dotenv.load_dotenv()
|
|
271
|
+
xai_api_key = os.getenv("XAI_API_KEY")
|
|
272
|
+
if not xai_api_key or xai_api_key == "YOUR_XAI_API_KEY":
|
|
273
|
+
print("WARNING: No valid XAI_API_KEY found in environment!")
|
|
274
|
+
xai_api_key = "YOUR_XAI_API_KEY" # fallback
|
|
275
|
+
|
|
276
|
+
nq_helper = GrokNewsHelper(api_key=xai_api_key)
|
|
277
|
+
|
|
278
|
+
# Example user query
|
|
279
|
+
# user_query = "What drugs from small biotech companies are expected to get FDA approvals soon?"
|
|
280
|
+
user_query = "what is the twitter account @Banana3Stocks recommending to buy or sell right now and at what price targets? he usually says things like 'see you at XX price' or 'XX price soon' or 'run to XX' or 'pivot is XX' or 'XX later this year' or 'ready for $XX'"
|
|
281
|
+
result = nq_helper.execute_query(user_query)
|
|
282
|
+
|
|
283
|
+
# Print the structured response
|
|
284
|
+
print(json.dumps(result, indent=2))
|
|
@@ -66,8 +66,8 @@ class OptionsHelper:
|
|
|
66
66
|
expiry: date, put_or_call: str = "call") -> Optional[Asset]:
|
|
67
67
|
"""
|
|
68
68
|
Find a valid option with the given expiry and strike.
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
First tries the requested strike, then searches nearby strikes from the option chain.
|
|
70
|
+
If no strikes work for this expiry, tries the next expiry date.
|
|
71
71
|
|
|
72
72
|
Parameters
|
|
73
73
|
----------
|
|
@@ -86,55 +86,111 @@ class OptionsHelper:
|
|
|
86
86
|
The valid option asset or None if not found.
|
|
87
87
|
"""
|
|
88
88
|
self.strategy.log_message(f"Finding next valid option for {underlying_asset.symbol} at strike {rounded_underlying_price} and expiry {expiry}", color="blue")
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
|
|
90
|
+
expiry_attempts = 0
|
|
91
|
+
while expiry_attempts < 10:
|
|
92
|
+
# Check if this expiry was previously marked as invalid
|
|
91
93
|
for record in self.non_existing_expiry_dates:
|
|
92
94
|
if (record["underlying_asset_symbol"] == underlying_asset.symbol and
|
|
93
95
|
record["expiry"] == expiry):
|
|
94
96
|
self.strategy.log_message(f"Expiry {expiry} previously invalid for {underlying_asset.symbol}; trying next day.", color="yellow")
|
|
95
97
|
expiry += timedelta(days=1)
|
|
98
|
+
expiry_attempts += 1
|
|
96
99
|
continue
|
|
97
100
|
|
|
98
|
-
option
|
|
99
|
-
underlying_asset.symbol,
|
|
100
|
-
asset_type="option",
|
|
101
|
-
expiration=expiry,
|
|
102
|
-
strike=rounded_underlying_price,
|
|
103
|
-
right=put_or_call,
|
|
104
|
-
underlying_asset=underlying_asset,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
# First check if quote data exists (preferred)
|
|
101
|
+
# Try to get option chain to find available strikes
|
|
108
102
|
try:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
chains = self.strategy.get_chains(underlying_asset)
|
|
104
|
+
self.strategy.log_message(f"Got chains for {underlying_asset.symbol}: {bool(chains)}", color="cyan")
|
|
105
|
+
|
|
106
|
+
if chains:
|
|
107
|
+
# Get available strikes for this expiry
|
|
108
|
+
available_strikes = chains.strikes(expiry, put_or_call.upper())
|
|
109
|
+
self.strategy.log_message(f"Available strikes for {expiry}: {len(available_strikes) if available_strikes else 0} strikes", color="cyan")
|
|
110
|
+
|
|
111
|
+
if not available_strikes:
|
|
112
|
+
self.strategy.log_message(f"No strikes available for {put_or_call.upper()} on {expiry}; trying next expiry.", color="yellow")
|
|
113
|
+
self.non_existing_expiry_dates.append({
|
|
114
|
+
"underlying_asset_symbol": underlying_asset.symbol,
|
|
115
|
+
"expiry": expiry,
|
|
116
|
+
})
|
|
117
|
+
expiry += timedelta(days=1)
|
|
118
|
+
expiry_attempts += 1
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
# Find the closest strike to our target
|
|
122
|
+
closest_strike = min(available_strikes, key=lambda x: abs(x - rounded_underlying_price))
|
|
123
|
+
self.strategy.log_message(f"Target strike {rounded_underlying_price} -> Closest available strike: {closest_strike}", color="green")
|
|
124
|
+
|
|
125
|
+
# Create option with the closest available strike
|
|
126
|
+
option = Asset(
|
|
127
|
+
underlying_asset.symbol,
|
|
128
|
+
asset_type="option",
|
|
129
|
+
expiration=expiry,
|
|
130
|
+
strike=closest_strike,
|
|
131
|
+
right=put_or_call,
|
|
132
|
+
underlying_asset=underlying_asset,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Verify this option has price data
|
|
136
|
+
try:
|
|
137
|
+
quote = self.strategy.get_quote(option)
|
|
138
|
+
has_valid_quote = quote and (quote.bid is not None or quote.ask is not None)
|
|
139
|
+
if has_valid_quote:
|
|
140
|
+
self.strategy.log_message(f"Found valid option: {option.symbol} {option.right} {option.strike} exp {option.expiration}", color="green")
|
|
141
|
+
return option
|
|
142
|
+
except Exception as e:
|
|
143
|
+
self.strategy.log_message(f"Error getting quote for {option.symbol}: {e}", color="yellow")
|
|
144
|
+
|
|
145
|
+
# Fallback to last price
|
|
146
|
+
try:
|
|
147
|
+
price = self.strategy.get_last_price(option)
|
|
148
|
+
if price is not None:
|
|
149
|
+
self.strategy.log_message(f"Found valid option (via last price): {option.symbol} {option.right} {option.strike} exp {option.expiration}", color="green")
|
|
150
|
+
return option
|
|
151
|
+
except Exception as e:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# If closest strike didn't work, this expiry might be invalid
|
|
155
|
+
self.strategy.log_message(f"Could not get price data for strike {closest_strike} on {expiry}; trying next expiry.", color="yellow")
|
|
156
|
+
self.non_existing_expiry_dates.append({
|
|
157
|
+
"underlying_asset_symbol": underlying_asset.symbol,
|
|
158
|
+
"expiry": expiry,
|
|
159
|
+
})
|
|
160
|
+
expiry += timedelta(days=1)
|
|
161
|
+
expiry_attempts += 1
|
|
162
|
+
continue
|
|
116
163
|
|
|
117
|
-
# Fallback to checking last price if no quote
|
|
118
|
-
try:
|
|
119
|
-
price = self.strategy.get_last_price(option)
|
|
120
|
-
self.strategy.log_message(f"Price for option {option.symbol} at expiry {expiry} is {price}", color="blue")
|
|
121
164
|
except Exception as e:
|
|
122
|
-
self.strategy.log_message(f"Error getting
|
|
123
|
-
|
|
165
|
+
self.strategy.log_message(f"Error getting chains for {underlying_asset.symbol}: {e}; falling back to direct strike attempt.", color="yellow")
|
|
166
|
+
# Fallback: Try the exact strike requested (old behavior)
|
|
167
|
+
option = Asset(
|
|
168
|
+
underlying_asset.symbol,
|
|
169
|
+
asset_type="option",
|
|
170
|
+
expiration=expiry,
|
|
171
|
+
strike=rounded_underlying_price,
|
|
172
|
+
right=put_or_call,
|
|
173
|
+
underlying_asset=underlying_asset,
|
|
174
|
+
)
|
|
124
175
|
|
|
125
|
-
|
|
126
|
-
|
|
176
|
+
try:
|
|
177
|
+
price = self.strategy.get_last_price(option)
|
|
178
|
+
if price is not None:
|
|
179
|
+
return option
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
127
182
|
|
|
128
|
-
|
|
183
|
+
# No valid option found for this expiry, try next day
|
|
184
|
+
self.strategy.log_message(f"No valid option found for expiry {expiry}; trying next expiry.", color="yellow")
|
|
129
185
|
self.non_existing_expiry_dates.append({
|
|
130
186
|
"underlying_asset_symbol": underlying_asset.symbol,
|
|
131
187
|
"expiry": expiry,
|
|
132
188
|
})
|
|
133
189
|
expiry += timedelta(days=1)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
190
|
+
expiry_attempts += 1
|
|
191
|
+
|
|
192
|
+
self.strategy.log_message("Exceeded maximum attempts to find a valid option.", color="red")
|
|
193
|
+
return None
|
|
138
194
|
|
|
139
195
|
def get_strike_deltas(self, underlying_asset: Asset, expiry: date, strikes: List[float],
|
|
140
196
|
right: str, stop_greater_than: Optional[float] = None,
|
lumibot/credentials.py
CHANGED
|
@@ -78,6 +78,9 @@ BACKTESTING_END = None
|
|
|
78
78
|
if backtesting_end:
|
|
79
79
|
BACKTESTING_END = parser.parse(backtesting_end)
|
|
80
80
|
|
|
81
|
+
# Get the backtesting data source
|
|
82
|
+
BACKTESTING_DATA_SOURCE = os.environ.get("BACKTESTING_DATA_SOURCE", "ThetaData")
|
|
83
|
+
|
|
81
84
|
# Check if we should hide trades
|
|
82
85
|
hide_trades = os.environ.get("HIDE_TRADES")
|
|
83
86
|
if not hide_trades or hide_trades.lower() == "false":
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -68,11 +68,9 @@ class DataSourceBacktesting(DataSource, ABC):
|
|
|
68
68
|
# catch it here and ignore it in this class. Child classes that need it should error check it themselves.
|
|
69
69
|
self._config = config
|
|
70
70
|
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
quiet_logs_enabled = os.environ.get("BACKTESTING_QUIET_LOGS", "").lower() == "true"
|
|
75
|
-
self._show_progress_bar = show_progress_bar and not quiet_logs_enabled
|
|
71
|
+
# Progress bar should always show when enabled, regardless of quiet_logs
|
|
72
|
+
# Quiet logs only affects INFO/WARNING/ERROR logging, not progress bar
|
|
73
|
+
self._show_progress_bar = show_progress_bar
|
|
76
74
|
|
|
77
75
|
self._progress_csv_path = "logs/progress.csv"
|
|
78
76
|
# Add initialization for the logging timer attribute
|