Overall Statistics
Total Orders
23450
Average Win
0.04%
Average Loss
-0.02%
Compounding Annual Return
21.435%
Drawdown
18.800%
Expectancy
0.497
Start Equity
1000000
End Equity
8475520.51
Net Profit
747.552%
Sharpe Ratio
1.266
Sortino Ratio
1.573
Probabilistic Sharpe Ratio
95.407%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
2.06
Alpha
0.091
Beta
0.42
Annual Standard Deviation
0.098
Annual Variance
0.01
Information Ratio
0.398
Tracking Error
0.114
Treynor Ratio
0.295
Total Fees
$200734.51
Estimated Strategy Capacity
$1700000.00
Lowest Capacity Asset
VGSH UHVG8V7B7YAT
Portfolio Turnover
8.88%
Drawdown Recovery
470
#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(2015, 1, 1)
        self.SetCash(1_000_000)
        self.SetTimeZone("America/Chicago")

        self.benchmark_ticker = "SPY"
        self.reference_ticker_defensive = "DBP"
        self.reference_ticker_growth = "TQQQ"

        self.matrix_1_dates = 17          # defensive
        self.growth_lookback = 17         # growth 

        self.defensive_picks = 4
        self.growth_picks = 2

        self.base_position_weight = 0.1625
        self.max_total_exposure = 0.95

        self.SetBrokerageModel(AlphaStreamsBrokerageModel())
        self.SetRiskManagement(TrailingStopRiskManagementModel(0.054))
        self.SetExecution(StandardDeviationExecutionModel(40, 1.85, Resolution.Minute))

        self.defensive_tickers = [
            "GLD","SHY","JPST","SVOL","XLE","XME", "XLP", "UGL", "IAU", "XLU",
            "AAPL","MSFT","JNJ","HD","UNP","NVDA", "BOXX", "BIL", "VGSH", "SLV"]

        self.growth_candidates = [
            "SOXX","QQQ","XLK","SMH",
            "VGT","QLD","TQQQ","SOXL", "TECL"]

        self.defensive_symbols = []
        self.growth_symbols = []

        def safe_add(t):
            try:
                return self.AddEquity(t, Resolution.Minute).Symbol
            except:
                return None

        for t in self.defensive_tickers:
            s = safe_add(t)
            if s:
                self.defensive_symbols.append(s)

        for t in self.growth_candidates:
            s = safe_add(t)
            if s:
                self.growth_symbols.append(s)

        self._benchmark_symbol = safe_add(self.benchmark_ticker)
        self._reference_def_symbol = safe_add(self.reference_ticker_defensive)
        self._reference_growth_symbol = safe_add(self.reference_ticker_growth)

        self.SetBenchmark(self._benchmark_symbol)

        self.Schedule.On(
            self.DateRules.MonthStart(self._benchmark_symbol),
            self.TimeRules.AfterMarketOpen(self._benchmark_symbol, 12),
            self.rebalance_defensive
        )

        self.Schedule.On(
            self.DateRules.WeekStart(self._benchmark_symbol),
            self.TimeRules.AfterMarketOpen(self._benchmark_symbol, 10),
            self.rebalance_growth
        )

        self.shaken_defensive = []
        self.shaken_growth = []
        self.started = False

    def OnData(self, data: Slice):
        if not self.started:
            self.rebalance(run_defensive=True, run_growth=True)
            self.started = True

    def matrix_1(self, first, second):
        first = np.array(first)
        first = np.diff(first) / first[:-1]
        second = np.array(second)
        second = np.diff(second) / second[:-1]
        A = np.vstack([first, np.ones(len(first))]).T
        basis = np.linalg.lstsq(A, second, rcond=None)[0]
        return basis[1], basis[0] 

    def rebalance_defensive(self):
        self.rebalance(run_defensive=True, run_growth=False)

    def rebalance_growth(self):
        self.rebalance(run_defensive=False, run_growth=True)

    def rebalance(self, run_defensive=True, run_growth=True):

        lookback = max(self.matrix_1_dates, self.growth_lookback)
        all_syms = list(set(
            self.defensive_symbols +
            self.growth_symbols +
            [self._reference_def_symbol, self._reference_growth_symbol]
        ))

        history = self.History(all_syms, lookback, Resolution.Daily)
        if history.empty:
            return

        if run_defensive:
            ranked = []
            for s in self.defensive_symbols:
                if s not in history.index.get_level_values(0):
                    continue

                a = history.loc[s]["close"][-self.matrix_1_dates:]
                b = history.loc[self._reference_def_symbol]["close"][-self.matrix_1_dates:]

                if len(a) != len(b) or len(a) < 3:
                    continue

                intercept, _ = self.matrix_1(list(a), list(b))
                ranked.append((s, intercept))

            ranked.sort(key=lambda x: x[1], reverse=True)
            self.shaken_defensive = [x[0] for x in ranked[:self.defensive_picks]]

        if run_growth:
            ranked = []
            for s in self.growth_symbols:
                if s == self._reference_growth_symbol:
                    continue

                if s not in history.index.get_level_values(0):
                    continue

                a = history.loc[s]["close"][-self.growth_lookback:]
                b = history.loc[self._reference_growth_symbol]["close"][-self.growth_lookback:]

                if len(a) != len(b) or len(a) < 3:
                    continue

                _, slope = self.matrix_1(list(a), list(b))
                ranked.append((s, slope))

            ranked.sort(key=lambda x: x[1], reverse=True)
            self.shaken_growth = [x[0] for x in ranked[:self.growth_picks]]

        targets = list(dict.fromkeys(self.shaken_defensive + self.shaken_growth))

        desired = self.base_position_weight * len(targets)
        scale = min(1.0, self.max_total_exposure / desired)
        w = self.base_position_weight * scale

        invested = [kv.Key for kv in self.Portfolio if kv.Value.Invested]

        for s in invested:
            if s not in targets and (run_defensive or s not in self.shaken_defensive):
                self.Liquidate(s)

        for s in targets:
            self.SetHoldings(s, w)