Overall Statistics
Total Orders
19
Average Win
141.22%
Average Loss
-4.91%
Compounding Annual Return
45.757%
Drawdown
69.800%
Expectancy
15.535
Start Equity
10000
End Equity
524688.80
Net Profit
5146.888%
Sharpe Ratio
0.917
Sortino Ratio
0.917
Probabilistic Sharpe Ratio
22.433%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
28.76
Alpha
0.211
Beta
2.628
Annual Standard Deviation
0.452
Annual Variance
0.204
Information Ratio
1.022
Tracking Error
0.329
Treynor Ratio
0.158
Total Fees
$177.98
Estimated Strategy Capacity
$0
Lowest Capacity Asset
TQQQ UK280CGTCB51
Portfolio Turnover
0.49%
from AlgorithmImports import *
import numpy as np

class VarSwitchStrategyA(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(10000)

        # Add assets
        self.qqq = self.AddEquity("QQQ", Resolution.Daily).Symbol
        self.tqqq = self.AddEquity("TQQQ", Resolution.Daily).Symbol

        # Set resolution and warm-up
        self.lookback = 252
        self.z = 2.58
        self.holding_period = 10
        self.window = RollingWindow[float](self.lookback)
        self.SetWarmUp(self.lookback + self.holding_period, Resolution.Daily)

        # Schedule check
        self.Schedule.On(
            self.DateRules.EveryDay(self.qqq),
            self.TimeRules.AfterMarketOpen(self.qqq, 1),
            self.DailyCheck
        )

        self.current_symbol = None
        self.last_close_prices = []

    def OnData(self, slice: Slice):
        if not slice.Bars.ContainsKey(self.qqq):
            return

        bar = slice[self.qqq]
        if self.window.IsReady:
            prev_close = self.window[0]
            log_return = np.log(bar.Close / prev_close)
            self.window.Add(bar.Close)
        else:
            if self.window.Count > 0:
                log_return = np.log(bar.Close / self.window[0])
            self.window.Add(bar.Close)

        # Track close prices for 10-day return
        self.last_close_prices.append(bar.Close)
        if len(self.last_close_prices) > self.holding_period:
            self.last_close_prices.pop(0)

    def OnWarmupFinished(self):
        self.Debug("Warmup complete.")
        self.current_symbol = self.qqq
        self.SetHoldings(self.current_symbol, 1)

    def DailyCheck(self):
        if not self.window.IsReady or len(self.last_close_prices) < self.holding_period:
            return

        # Compute volatility and VaR
        returns = [np.log(self.window[i] / self.window[i+1]) for i in range(self.lookback - 1)]
        vol = np.std(returns)
        var_10d = -self.z * vol * np.sqrt(self.holding_period)
        var_10d_up = self.z * vol * np.sqrt(self.holding_period)

        # Compute actual 10-day return
        recent_close = self.last_close_prices[-1]
        past_close = self.last_close_prices[0]
        actual_return = np.log(recent_close / past_close)

        # Switch logic
        if self.current_symbol == self.qqq and actual_return < var_10d:
            self.Debug(f"{self.Time.date()} VAR breach down → switching to TQQQ")
            self.SwitchTo(self.tqqq)

        elif self.current_symbol == self.tqqq and actual_return > var_10d_up:
            self.Debug(f"{self.Time.date()} VAR breach up → switching to QQQ")
            self.SwitchTo(self.qqq)

    def SwitchTo(self, symbol):
        self.Liquidate()
        self.SetHoldings(symbol, 1)
        self.current_symbol = symbol