Overall Statistics
Total Orders
593
Average Win
4.24%
Average Loss
-1.69%
Compounding Annual Return
38.114%
Drawdown
28.700%
Expectancy
0.448
Start Equity
50000
End Equity
308556.68
Net Profit
517.113%
Sharpe Ratio
1.039
Sortino Ratio
0.802
Probabilistic Sharpe Ratio
53.294%
Loss Rate
59%
Win Rate
41%
Profit-Loss Ratio
2.51
Alpha
0.191
Beta
0.625
Annual Standard Deviation
0.246
Annual Variance
0.061
Information Ratio
0.645
Tracking Error
0.235
Treynor Ratio
0.409
Total Fees
$3843.32
Estimated Strategy Capacity
$57000000.00
Lowest Capacity Asset
VX YZBS4RIOVL61
Portfolio Turnover
10.67%
Drawdown Recovery
186
from AlgorithmImports import *
from datetime import datetime, timedelta
import json

class BTC_VIX_Hedge_Strategy(QCAlgorithm):
    
    def Initialize(self):
        """Initialize the algorithm with BTC/VIX hedge strategy"""
        
        # === PAPER TRADING SETUP ===
        # Comment out broker lines for paper trading (QC default)
        # self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        # self.SetBrokerageModel(BrokerageName.CoinbaseBrokerage)
        
        # === BACKTEST SETTINGS ===
        self.SetStartDate(2020, 5, 3)
        self.SetEndDate(2025, 12, 20)
        self.SetCash(50000)
        
        # === SYMBOLS ===
        #self.btc_future_object = self.AddFuture(Futures.Currencies.MICRO_BTC, Resolution.HOUR)
        self.vix_future = self.AddFuture(Futures.Indices.VIX, Resolution.Hour)
        #self.btc_future_object.SetFilter(30, 90)
        self.vix_future.SetFilter(15, 45)
        self.vix_index = self.AddIndex("VIX", Resolution.Daily).Symbol
        
        # === TRADERSPOST WEBHOOK ===
        self.webhook_url = "https://webhooks.traderspost.io/trading/webhook/fd213722-ba73-4113-8eff-51ba52760495/e257562b94b6865bb4b34012c2dab64a"
        
        # === STATE TRACKING ===
        self.invested = False
        self.previous_vix_close = None
        self.last_vix_update_date = None
        
        # === SIGNAL THRESHOLDS ===
        self.vix_high_threshold = 21.0
        
        # Warm up period
        self.SetWarmUp(10)
        
        self.Log("[INIT] BTC-VIX Strategy with Pyramid Scaling")

    def OnData(self, data):
        """Main trading logic"""
        
        if self.IsWarmingUp:
            return
        
        # Check for expiry
        for holding in self.Portfolio.Values:
            if holding.Symbol.SecurityType == SecurityType.Future and holding.Invested:
                
                # Drawdown Alert
                if holding.UnrealizedProfit < -2000:
                    self.Log(f"[DRAWDOWN ALERT] {holding.Symbol.Value} drawdown > $2000: ${holding.UnrealizedProfit:.2f}")

                if (holding.Symbol.ID.Date - self.Time).days < 5:
                    # self.Log(f"[EXPIRY] Closing all.")
                    self.close_all_positions()
                    return
                
                # Stop Loss
                if holding.UnrealizedProfitPercent < -0.03:
                    # self.Log(f"[STOP LOSS] {holding.Symbol.Value} hit -4%.")
                    self.close_all_positions()
                    return
                
                # Take Profit
                if holding.UnrealizedProfitPercent > 0.08:
                    # self.Log(f"[TAKE PROFIT] {holding.Symbol.Value} hit +8%.")
                    self.close_all_positions()
                    return

        # VIX DATA
        current_vix_close = self.Securities[self.vix_index].Price
        
        # Initialize if None
        if self.last_vix_update_date is None:
            self.previous_vix_close = current_vix_close
            self.last_vix_update_date = self.Time.date()
            return

        # ===== ENTRY LOGIC WITH GAP FILTER =====
        # Only check once per day
        if self.Time.date() > self.last_vix_update_date:
            if self.previous_vix_close is not None:
                vix_change_pct = (current_vix_close - self.previous_vix_close) / self.previous_vix_close
                
                # OPTION 1: Enter on 5% VIX gap (RECOMMENDED - this is what made 0.7 Sharpe)
                if not self.Portfolio.Invested and abs(vix_change_pct) > 0.05:
                    # Dynamic sizing by VIX level
                    if current_vix_close >= 26.0:
                        target_qty = 5
                    elif current_vix_close >= 23.5:
                        target_qty = 4
                    elif current_vix_close >= 21.0:
                        target_qty = 3
                    elif current_vix_close >= 18.0:
                        target_qty = 2
                    else:
                        target_qty = 1
                    
                    self.enter_vix_trade(data, target_qty)
                
                # SCALE UP if already invested AND VIX rises further
                elif self.Portfolio.Invested and vix_change_pct > 0.09:  # Only on UP moves
                    current_qty = sum(abs(h.Quantity) for h in self.Portfolio.Values 
                                     if h.Symbol.SecurityType == SecurityType.Future and h.Invested)
                    
                    # Determine new target based on current VIX
                    if current_vix_close >= 26.0:
                        new_target = 5
                    elif current_vix_close >= 23.5:
                        new_target = 4
                    elif current_vix_close >= 21.0:
                        new_target = 3
                    else:
                        new_target = current_qty  # Don't scale down
                    
                    # Only add more if VIX went UP and we haven't hit max
                    if new_target > current_qty:
                        add_qty = new_target - current_qty
                        # Find current VIX holding
                        for holding in self.Portfolio.Values:
                            if holding.Symbol.SecurityType == SecurityType.Future and holding.Invested:
                                self.MarketOrder(holding.Symbol, -add_qty)
                                
                                # Send webhook
                                self.send_traderspost_webhook(
                                    ticker=holding.Symbol.Value,
                                    action="sell",
                                    quantity=add_qty,
                                    price=self.Securities[holding.Symbol].Price
                                )
                                
                                self.Log(f"[SCALE] VIX jumped to {current_vix_close:.2f}. Adding {add_qty} shorts. Total: {new_target}")
                                break
            
            # Update for next day
            self.previous_vix_close = current_vix_close
            self.last_vix_update_date = self.Time.date()

    def enter_vix_trade(self, data, quantity):
        """Short VIX Futures"""
        vix_chain = data.FutureChains.get(self.vix_future.Symbol)
        if not vix_chain:
            return
        
        vix_contracts = sorted([c for c in vix_chain], key=lambda x: x.Expiry)
        if not vix_contracts:
            return
        
        selected_vix = vix_contracts[0]
        self.MarketOrder(selected_vix.Symbol, -quantity)
        
        # Send webhook to TradersPost
        self.send_traderspost_webhook(
            ticker=selected_vix.Symbol.Value,
            action="sell",  # We're shorting
            quantity=quantity,
            price=selected_vix.LastPrice
        )
        
        self.Log(f"[ENTRY] Short {quantity} {selected_vix.Symbol.Value} | VIX: {self.Securities[self.vix_index].Price}")
    
    def close_all_positions(self):
        """Close all positions and send exit webhook"""
        for holding in self.Portfolio.Values:
            if holding.Invested:
                symbol = holding.Symbol.Value
                quantity = abs(holding.Quantity)
                price = holding.Price
                
                # Liquidate in QuantConnect
                self.Liquidate(holding.Symbol)
                
                # Send exit webhook to TradersPost
                self.send_traderspost_webhook(
                    ticker=symbol,
                    action="exit",
                    quantity=quantity,
                    price=price
                )
                
                self.Log(f"[CLOSE] Closed {symbol}")
        
        self.Log(f"[EXIT] All positions closed | Portfolio: ${self.Portfolio.TotalPortfolioValue:,.2f}")

    def send_traderspost_webhook(self, ticker, action, quantity, price=None):
        """Send webhook to TradersPost"""
        payload = {
            "ticker": ticker,
            "action": action,
            "quantity": quantity
        }
        
        if price is not None:
            payload["price"] = float(price)
        
        json_data = json.dumps(payload)
        
        if self.LiveMode:
            try:
                self.Notify.Web(address=self.webhook_url, data=json_data)
                self.Log(f"[WEBHOOK] Sent to TradersPost: {json_data}")
            except Exception as e:
                self.Log(f"[WEBHOOK ERROR] {str(e)}")
        else:
            self.Log(f"[WEBHOOK BACKTEST] Would send: {json_data}")
    
    def OnEndOfDay(self, symbol):
        pass