Overall Statistics
Total Orders
107581
Average Win
0.02%
Average Loss
-0.01%
Compounding Annual Return
24.490%
Drawdown
20.700%
Expectancy
0.287
Start Equity
1000000
End Equity
17442496.74
Net Profit
1644.250%
Sharpe Ratio
1.335
Sortino Ratio
1.653
Probabilistic Sharpe Ratio
96.824%
Loss Rate
57%
Win Rate
43%
Profit-Loss Ratio
2.00
Alpha
0.109
Beta
0.437
Annual Standard Deviation
0.111
Annual Variance
0.012
Information Ratio
0.487
Tracking Error
0.122
Treynor Ratio
0.339
Total Fees
$747555.70
Estimated Strategy Capacity
$4000.00
Lowest Capacity Asset
FB YTLZP82EVU5H
Portfolio Turnover
18.05%
Drawdown Recovery
578
#region imports
from AlgorithmImports import *
#endregion

import numpy as np
from datetime import timedelta

from Risk.TrailingStopRiskManagementModel import TrailingStopRiskManagementModel
from Execution.StandardDeviationExecutionModel import StandardDeviationExecutionModel


class Two_2Algorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2013, 1, 1)
        self.SetCash(1_000_000)

        self.SetBrokerageModel(AlphaStreamsBrokerageModel())
        self.SetTimeZone(TimeZones.Chicago)

        self.tickers = ["AAPL", "MSFT", "AMZN", "FB", "TSLA", "GOOGL", "GOOG", "NVDA", "JNJ", "JPM"]
        self.symbols = []

        self._benchmark_ticker = "SPY"
        self._reference_ticker = "TECS"
        self.matrix_1_dates = 15

        self.short_target_weight = -0.09
        self.long_target_weight = 0.165
        self.short_update_weight = -0.08

        self.SetRiskManagement(TrailingStopRiskManagementModel(0.047))
        self.SetExecution(StandardDeviationExecutionModel(30, 1.25, Resolution.Minute))

        for t in self.tickers:
            self.symbols.append(self.AddEquity(t, Resolution.Minute).Symbol)

        self._benchmark = self.AddEquity(self._benchmark_ticker, Resolution.Minute).Symbol
        self._reference = self.AddEquity(self._reference_ticker, Resolution.Minute).Symbol
        self.SetBenchmark(self._benchmark_ticker)

        daterule_week = self.DateRules.WeekStart(self._benchmark)
        daterule_day = self.DateRules.EveryDay(self._benchmark)

        self.Schedule.On(daterule_week, self.TimeRules.AfterMarketOpen(self._benchmark, 8), self.rebalance)
        self.Schedule.On(daterule_day, self.TimeRules.AfterMarketOpen(self._benchmark, 18), self.update)
        self.Schedule.On(daterule_day, self.TimeRules.AfterMarketOpen(self._benchmark, 54), self.update)
        self.Schedule.On(daterule_day, self.TimeRules.AfterMarketOpen(self._benchmark, 164), self.update)
        self.Schedule.On(daterule_day, self.TimeRules.AfterMarketOpen(self._benchmark, 264), self.update)
        self.Schedule.On(daterule_day, self.TimeRules.AfterMarketOpen(self._benchmark, 324), self.update)
        self.Schedule.On(daterule_day, self.TimeRules.BeforeMarketClose(self._benchmark, 1), self.update)

        self.short_candidates = []
        self.long_candidates = []

        self.started = False

    def OnData(self, data: Slice):
        if self.started:
            return
        self.rebalance()
        self.started = True

    def _regression_intercept_slope(self, first, second):
        first = np.asarray(first, dtype=float)
        second = np.asarray(second, dtype=float)

        if len(first) < 2 or len(second) < 2:
            return 0.0, 0.0

        first = np.diff(first) / first[:-1]
        second = np.diff(second) / second[:-1]

        n = min(len(first), len(second))
        first = first[-n:]
        second = second[-n:]

        A = np.vstack([first, np.ones(len(first))]).T
        slope, intercept = np.linalg.lstsq(A, second, rcond=None)[0]
        return float(intercept), float(slope)

    def rebalance(self):
        hist = self.History(self.symbols + [self._reference], self.matrix_1_dates, Resolution.Daily)
        if hist.empty:
            return

        idx0 = hist.index.get_level_values(0)
        if self._reference not in idx0:
            return

        ranked = []
        ref_close = hist.loc[self._reference]["close"].tolist()
        if len(ref_close) < 2:
            return

        for sym in self.symbols:
            if sym not in idx0:
                continue

            sym_close = hist.loc[sym]["close"].tolist()
            if len(sym_close) != len(ref_close) or len(sym_close) < 2:
                continue

            intercept, _ = self._regression_intercept_slope(sym_close, ref_close)
            ranked.append((sym, intercept))

        ranked.sort(key=lambda x: x[1], reverse=True)
        if len(ranked) < 3:
            return

        self.short_candidates = [ranked[0][0]]
        self.long_candidates = [x[0] for x in ranked[-5:]]

        invested_symbols = [kvp.Key for kvp in self.Portfolio if kvp.Value.Invested]
        for sym in invested_symbols:
            if sym not in self.long_candidates:
                self.EmitInsights(Insight.Price(sym, timedelta(seconds=1), InsightDirection.Flat))
                self.Liquidate(sym)

        for sym in self.short_candidates:
            self.EmitInsights(Insight.Price(sym, Expiry.EndOfWeek, InsightDirection.Down))
            self.SetHoldings(sym, self.short_target_weight)

        for sym in self.long_candidates:
            self.EmitInsights(Insight.Price(sym, Expiry.EndOfWeek, InsightDirection.Up))
            self.SetHoldings(sym, self.long_target_weight)

    def update(self):
        for sym in self.short_candidates:
            self.EmitInsights(Insight.Price(sym, Expiry.EndOfWeek, InsightDirection.Down))
            self.SetHoldings(sym, self.short_update_weight)