Overall Statistics
Total Trades
77
Average Win
1.84%
Average Loss
-2.07%
Compounding Annual Return
3.376%
Drawdown
15.800%
Expectancy
0.056
Net Profit
5.244%
Sharpe Ratio
0.223
Probabilistic Sharpe Ratio
12.263%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
0.89
Alpha
0.037
Beta
-0.417
Annual Standard Deviation
0.174
Annual Variance
0.03
Information Ratio
0.146
Tracking Error
0.295
Treynor Ratio
-0.093
Total Fees
$170.81
Estimated Strategy Capacity
$690000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
# region imports
from AlgorithmImports import *
# endregion

class HipsterFluorescentPinkMule(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2021, 7, 28)  # Set Start Date
        self.SetEndDate(2023, 2, 8)
        self.SetCash(100000)  # Set Strategy Cash
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.dataset_symbol = self.AddData(RegalyticsRegulatoryArticles, "REG").Symbol
        self.lookback_period = timedelta(days=90)
        
        self.Train(self.DateRules.Every(DayOfWeek.Sunday), self.TimeRules.At(0,0), self.train_model)
        self.train_model()


        #self.SetBenchmark(self.symbol)

    def train_model(self):
        spy_history = self.History(self.symbol, self.lookback_period).loc[self.symbol]
        daily_returns = spy_history['open'].pct_change()[1:]

        reg_history = self.History[RegalyticsRegulatoryArticles](self.dataset_symbol, self.lookback_period)

        returns_by_alert_type = {}
        for articles in reg_history:
            for article in articles:
                future_spy_returns = daily_returns.loc[daily_returns.index > article.Time] # Is this time the same time we get in backtests?
                if len(future_spy_returns) < 2:
                    continue
                if article.AlertType not in returns_by_alert_type:
                    returns_by_alert_type[article.AlertType] = []
                returns_by_alert_type[article.AlertType].append(future_spy_returns.iloc[1])
                
        self.expected_return_by_alert_type = {
            alert_type: sum(future_returns)/len(future_returns) 
                for alert_type, future_returns in returns_by_alert_type.items()
        }


    def OnData(self, slice: Slice) -> None:
        # Parse articles
        if not slice.ContainsKey(self.dataset_symbol):
            return
        articles = slice[self.dataset_symbol]
        expected_returns = []
        alert_types = set()
        for article in articles:
            alert_types.add(article.AlertType)
        for alert_type in alert_types:
            if alert_type in self.expected_return_by_alert_type:
                expected_returns.append(self.expected_return_by_alert_type[alert_type])
        if len(expected_returns) == 0:
            return
        expected_return = sum(expected_returns) / len(expected_returns)
        if expected_return > 0 and not self.Portfolio[self.symbol].IsLong:
            self.SetHoldings(self.symbol, 1)
        elif expected_return < 0 and not self.Portfolio[self.symbol].IsShort:
            self.SetHoldings(self.symbol, -1)