Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe 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
0.295
Tracking Error
0.197
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
# region imports
from AlgorithmImports import *
import datetime as dt
import ast
import json
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel 
from io import StringIO
import pandas as pd

# endregion

class AdaptableVioletJaguar(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 1, 1)  # Set Start Date
        self.SetEndDate(2023, 2, 4)
        self.SetCash(50000)  # Set Strategy Cash
        self.UniverseSettings.Resolution = Resolution.Daily
        
        self.tickers = ["AAPL"]
        
        self.AddUniverse(self.CoarseSelection, self.FineSelection)
        
        self.symbolDataBySymbol = {}
        self.fineFundamentals = {}

        self.SAVE_EARNINGS = True
        self.loaded_earnings = {}

        if not self.SAVE_EARNINGS:
            self.SetEndDate(2023, 2, 1)
            earnings = json.loads(self.ObjectStore.Read("Earnings_dates"))
            
            temp_earnings = dict(earnings)
            for ticker, str_dates in earnings.items():
                temp_earnings[ticker] = [dt.datetime.strptime(x, '%m/%d/%Y').date() for x in ast.literal_eval(str_dates)]

            self.loaded_earnings = dict(temp_earnings)
            self.Debug(temp_earnings)
            del temp_earnings
            del earnings
            
        elif self.SAVE_EARNINGS and self.ObjectStore.ContainsKey("Earnings_dates"):
            self.SetEndDate(2023, 2, 4)
            self.ObjectStore.Delete("Earnings_dates")     

        self.spy = self.AddEquity("SPY", Resolution.Daily)

        self.Schedule.On(self.DateRules.EveryDay("SPY"),
                 self.TimeRules.AfterMarketOpen("SPY", -10),
                 self.CheckEarningsRules)

    # Long Entry Criteria
    #    at least 30 days since the last earnings report
    #    over 30 days until the next earnings report
    #
    # Short Entry Criteria
    #    less than 10 days until the next earnings report
    #
    # Short Exit Criteria
    #    over 30 days since last earnings report
    #
    def CheckEarningsRules(self):
        if self.SAVE_EARNINGS: return

        cur_date = self.Time.date()

        for symbol, symbolData in self.symbolDataBySymbol.items():
            symbol_earnings = symbolData.earnings_dates
            symbol_earnings_series = pd.Series(symbol_earnings)
            insertion_index = symbol_earnings_series.searchsorted(cur_date) ## Result is the index at which it will exit in the next list

            prev_earnings_date = symbol_earnings[insertion_index-1]
            next_earnings_date = symbol_earnings[insertion_index]  ## WHEN RUNNING ALGORITHM, MAKE SURE self.EndDate IS LESS THAN THE LAST EARNINGS VALUE

            delta_prev = cur_date - prev_earnings_date
            delta_next = next_earnings_date - cur_date

            if delta_prev.days >= 30 and delta_next.days > 30:
                symbolData.long_entry_earnings = True
            else:
                symbolData.long_entry_earnings = False

            if delta_next.days < 10:
                symbolData.short_entry_earnings = True
            else:
                symbolData.short_entry_earnings = False

            if delta_prev.days >= 30:
                symbolData.short_exit_earnings = True
            else:
                symbolData.short_exit_earnings = False

            self.Debug(f"{insertion_index} {self.Time.date()} {symbol.Value} : long etr - {symbolData.long_entry_earnings}, short etr - {symbolData.short_entry_earnings}, short ext - {symbolData.short_exit_earnings}")

    def OnData(self, data: Slice):
        if self.SAVE_EARNINGS:
            for symbol, symbolData in self.symbolDataBySymbol.items():
                symbolData.update_earnings(self.fineFundamentals[symbol].EarningReports.FileDate)
        
    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            if security.Symbol.Value == "SPY": continue

            symbolData = SymbolData(self, security)
            if not self.SAVE_EARNINGS:
                symbolData.earnings_dates = self.loaded_earnings[security.Symbol.Value]
            self.symbolDataBySymbol[security.Symbol] = symbolData

    def CoarseSelection(self, coarse):
        filteredCoarse = [x.Symbol for x in coarse if x.Symbol.Value in self.tickers and x.HasFundamentalData]
        return filteredCoarse
    
    def FineSelection(self, fine):
        for x in fine: 
            self.fineFundamentals[x.Symbol] = x
        
        return [x.Symbol for x in fine]

    def OnEndOfAlgorithm(self):
        if self.SAVE_EARNINGS:
            earnings_dict = {}
            for symbol, symbolData in self.symbolDataBySymbol.items():
                self.Debug(f"{symbol.Value} : {symbolData.earnings_dates}")
                string_earnings = [t.strftime('%m/%d/%Y') for t in symbolData.earnings_dates]
                earnings_dict[str(symbol.Value)] = str(string_earnings)

            dump = json.dumps(earnings_dict)
            self.ObjectStore.Save("Earnings_dates", dump)
            self.Debug(earnings_dict)

        else:
            for symbol, symbolData in self.symbolDataBySymbol.items():
                self.Debug(symbolData.earnings_dates)

    
class SymbolData:
    def __init__(self, algo, security):
        self.algo = algo
        self.security = security
        self.symbol = security.Symbol
        self.earnings_dates = []
        self.long_entry_earnings = False
        self.short_entry_earnings = False
        self.short_exit_earnings = False


    def update_earnings(self, date):
        if date.date() not in self.earnings_dates:
            self.earnings_dates.append(date.date())