| 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