| Overall Statistics |
|
Total Orders 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Start Equity 100000 End Equity 100000 Net Profit 0% Sharpe Ratio 0 Sortino Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -2.349 Tracking Error 0.057 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset Portfolio Turnover 0% Drawdown Recovery 0 |
# region imports
from AlgorithmImports import *
import json
import threading
import time
from datetime import timedelta, datetime
from openai import OpenAI
# endregion
class LiveSplitWatcher(QCAlgorithm):
def Initialize(self):
self.client = OpenAI(api_key="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")
self.set_start_date(2025, 9, 15)
self.set_end_date(2025, 9, 30)
etfs = ["IWV", "SPY", "IVV", "VOO", "QQQ", "VTI", "IWM", "VTV", "VUG", "VIG", "SCHD", "EFA", "VEA", "EEM", "VWO"]
self.universe_settings.schedule.on(self.date_rules.month_start())
self.universe_settings.extended_market_hours = True
self.universe_settings.data_normalization_mode = DataNormalizationMode.ADJUSTED
self.indexes = [self.add_equity(etf, Resolution.MINUTE).Symbol for etf in etfs]
self._universes = [self.add_universe(self.universe.etf(index,self.universe_settings, self.Filter)) for index in self.indexes]
self.schedule.On(self.date_rules.month_start("SPY"),self.time_rules.before_market_open("SPY", 2*90),self.update_symbol_list)
self.schedule.on(self.date_rules.every_day(), self.time_rules.before_market_open('SPY', 90), self.DailySplitCheck)
self.split_threshold = 0.3
self.results = []
self.sql_queries = []
self.symbols_from_universe = []
def Filter(self, coarse: list[CoarseFundamental]) -> list[Symbol]:
return [coarse_data.symbol for coarse_data in coarse]
def get_scale(self, multiplier: str) -> float:
try:
left, right = multiplier.split(":")
left, right = float(left), float(right)
return round(right / left, 6)
except Exception:
return None
# --------------------------------------------------------------------------
# Monthly scheduled to get universe symbols as etf weights do not change a lot so a monthly check
# Runs at 06:30am exchange-local time
# --------------------------------------------------------------------------
def update_symbol_list(self):
symbols = []
for _universe in self._universes:
history = self.history(_universe, 5, Resolution.DAILY, flatten=True)
if not history.empty:
symbols.extend(history.index.get_level_values(1).unique().tolist())
self.symbols_from_universe = list(set(symbols))
self.Log(f"Updated symbol list with {len(self.symbols_from_universe)} symbols")
def LoadYesterdayCloses(self):
self.yesterday_closes = {}
seen_ids = set()
if not self.symbols_from_universe:
self.update_symbol_list()
for _symbol in self.symbols_from_universe:
if str(_symbol.ID) in seen_ids:
continue
df = self.history(_symbol, 1, Resolution.DAILY, data_normalization_mode = DataNormalizationMode.ADJUSTED, flatten=True)
if not df.empty:
self.yesterday_closes[_symbol] = float(df["close"].iloc[-1])
seen_ids.add(str(_symbol.ID))
self.log(f"Yesterday Close captured at {self.time} at {self.time.date()}")
# --------------------------------------------------------------------------
# Daily scheduled routine to check for potential splits
# Runs at 07:00am exchange-local time
# --------------------------------------------------------------------------
def DailySplitCheck(self):
# update yesterday's close
self.LoadYesterdayCloses()
self.potential_split = set()
for symbol, yesterday_close in self.yesterday_closes.items():
self.add_equity(symbol.Value, Resolution.MINUTE, extended_market_hours=True)
history = self.history(symbol, 30, Resolution.MINUTE, extended_market_hours=True)
if history.empty:
continue
if isinstance(history.index, pd.MultiIndex):
history = history.reset_index(level=1, drop=True)
history['ratio'] = history['close'] / yesterday_close
if (history['ratio'] - 1).abs().max() > self.split_threshold:
self.potential_split.add(symbol)
self.log(f"Potential split detected for {str(symbol)} | Yesterday Close: {yesterday_close}, Premarket max ratio: {history['ratio'].max():.2f}")
self.log(f"Total symbols to process: {len(self.potential_split)}")
prompt = f"""
Check if the stock ticker {symbol.value} had a potential stock split "exactly" on {self.time.date()}.
If YES, PROVIDE a
- answer as YES or NO
- "multiplier" giving the stock split factor ratio ONLY. PROVIDE answer in x:y format ONLY
- "hyperlink" stating the split details and date of the split.
"""
for _ticker in self.potential_split:
self.VerifySplitAndRecord(_ticker,prompt)
try:
if self.results:
self.object_store.save("live_split_results.json", json.dumps(self.results, indent=2, default=str))
except Exception as wf:
self.log(f"Failed to save results in ObjectStore: {wf}")
# --------------------------------------------------------------------------
# Verification: call your OpenAI/web-scrape validator and append to results
# --------------------------------------------------------------------------
def VerifySplitAndRecord(self, symbol, prompt):
try:
if self.client:
try:
response = self.client.responses.create(
model="gpt-4o",
tools=[{"type": "web_search"}],
instructions="You are a financial data expert. Provide concise and accurate answers in JSON format.",
input=prompt,
text = {
"format": {
"type": "json_schema",
"name": "stock_split_check",
"schema": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"description": "YES or NO indicating whether a stock split occurred"
},
"multiplier": {
"type": "string",
"description": "Numeric stock split ratio ONLY. Example: 1:4, 1:3, 2:3, 3:2, 2:1, etc. NO EXTRA TEXT"
},
"link": {
"type": "string",
"description": "Hyperlink (https://...) to the source. Empty string if not available."
}
},
"required": ["answer", "multiplier","link"],
"additionalProperties": False
}
}
}
)
except Exception as oe:
self.log(f"OpenAI request failed for {symbol}: {oe}")
else:
self.log("OpenAI client not configured or not permitted in this environment. Skipping verification call.")
if response.output_text:
parsed = json.loads(response.output_text)
if parsed.get("answer","") == "YES":
sql_query = f"""
INSERT INTO <tablename> (ticker, Date, Split Ratio, Reference Price, Note, Actions)
SELECT ticker, Date, {str(self.get_scale(parsed.get("multiplier","")))}, <reference_price>, NULL, NULL
FROM <tablename>
WHERE ticker = '{str(symbol.id)}'
LIMIT 1;
""".strip()
else:
sql_query = ""
result = {
"Symbol Id": str(symbol.id),
"ticker": symbol.Value,
"date": str(self.time.date()),
"answer": parsed.get("answer",""),
"multiplier": parsed.get("multiplier",""),
"link": parsed.get("link",""),
"sql_query": sql_query
}
self.log(sql_query)
self.results.append(result)
self.queries = []
self.log(f"Process finished for {symbol}: {result['answer']} - {result['multiplier']} - {result['link']}")
else:
self.log(f"No response received for {symbol}")
except Exception as e:
self.log(f"VerifySplitAndRecord exception for {symbol}: {e}")