Overall Statistics
Total Orders
2686
Average Win
1.22%
Average Loss
-0.68%
Compounding Annual Return
15.445%
Drawdown
22.400%
Expectancy
0.127
Start Equity
100000
End Equity
205112.12
Net Profit
105.112%
Sharpe Ratio
0.533
Sortino Ratio
0.652
Probabilistic Sharpe Ratio
25.031%
Loss Rate
60%
Win Rate
40%
Profit-Loss Ratio
1.80
Alpha
0.041
Beta
0.512
Annual Standard Deviation
0.144
Annual Variance
0.021
Information Ratio
0.047
Tracking Error
0.143
Treynor Ratio
0.15
Total Fees
$26815.06
Estimated Strategy Capacity
$580000.00
Lowest Capacity Asset
SVXY V0H08FY38ZFP
Portfolio Turnover
70.59%
Drawdown Recovery
455
from AlgorithmImports import *


class BasicTemplateAlgorithm(QCAlgorithm):
    """
    Volatility trading strategy using SVXY and UVXY with trend detection.
    Uses moving average crossovers on SVXY and SPY trend indicators for positioning.
    """

    def initialize(self):
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(100_000)
        self.set_benchmark("SPY")
        self._uvxy = self.add_equity("UVXY", Resolution.HOUR)
        self._svxy = self.add_equity("SVXY", Resolution.HOUR)
        spy = self.add_equity("SPY", Resolution.HOUR)
        self.settings.automatic_indicator_warm_up = True
        # Signals dictionary for EMA indicator crossovers.
        self._signals = {
            "svxy": IndicatorExtensions.minus(self.ema(self._svxy, 2), self.ema(self._svxy, 5)),
            "market": IndicatorExtensions.minus(self.sma(spy, 100), self.sma(spy, 200)),
            "spy_bull": IndicatorExtensions.minus(self.ema(spy, 25), self.ema(spy, 50))
        }
        # Track date when stop loss was hit to prevent trading rest of day.
        self._stop_loss_date = None

    def on_data(self, slice):
        # Check if all signals are ready and stop loss has not been hit this day. 
        if (not all(ind.is_ready for ind in self._signals.values()) or 
            self._stop_loss_date == self.time.date()):
            return
        market_uptrend = self._signals["market"].current.value > 0
        svxy_signal = self._signals["svxy"].current.value > 0
        spy_bull = self._signals["spy_bull"].current.value > 0
        svxy_held = self._svxy.holdings.invested
        uvxy_held = self._uvxy.holdings.invested
        # Compute target allocations based on signal and market regime.
        svxy_target = 0.75 if svxy_signal else 0
        uvxy_target = 0.375 if not market_uptrend and not spy_bull and not svxy_signal and svxy_held else 0
        if svxy_target > 0 and not svxy_held:
            self.set_holdings(self._svxy, svxy_target)
            self.liquidate(self._uvxy)
        elif svxy_target == 0 and svxy_held:
            self.liquidate(self._svxy)
        if uvxy_target > 0 and not uvxy_held:
            self.set_holdings(self._uvxy, uvxy_target)
            self.liquidate(self._svxy)
        elif uvxy_target == 0 and uvxy_held:
            self.liquidate(self._uvxy)

    def on_order_event(self, order_event: OrderEvent) -> None:
        # Set a 10% stop loss on SVXY and UVXY.
        if order_event.status != OrderStatus.FILLED:
            return 
        match order_event.ticket.order_type:
            case OrderType.MARKET:
                symbol = order_event.symbol
                if self.securities[symbol].invested:
                    self.stop_market_order(symbol, -order_event.fill_quantity, order_event.fill_price*0.90)
            case OrderType.STOP_MARKET:
                # If stop loss is hit, do not trade until the next trading day.
                self._stop_loss_date = self.time.date()