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}")