| Overall Statistics |
|
Total Orders 655 Average Win 2.20% Average Loss -0.99% Compounding Annual Return 20.959% Drawdown 15.300% Expectancy 0.362 Start Equity 150000 End Equity 438361.34 Net Profit 192.241% Sharpe Ratio 0.85 Sortino Ratio 0.668 Probabilistic Sharpe Ratio 50.794% Loss Rate 58% Win Rate 42% Profit-Loss Ratio 2.22 Alpha 0.083 Beta 0.345 Annual Standard Deviation 0.14 Annual Variance 0.02 Information Ratio 0.094 Tracking Error 0.162 Treynor Ratio 0.345 Total Fees $4638.66 Estimated Strategy Capacity $26000000.00 Lowest Capacity Asset VX YYDBHHPS76JT Portfolio Turnover 7.40% Drawdown Recovery 189 |
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(150000)
# === 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
# === SLIPPAGE TRACKING ===
self.last_slippage_reset_date = None
self.daily_slippage_dollars = 0.0
self.daily_trades_count = 0
# === 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 < -4500:
self.Log(f"[DRAWDOWN ALERT] {holding.Symbol.Value} drawdown > $4500: ${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.029:
# self.Log(f"[STOP LOSS] {holding.Symbol.Value} hit -2.5%.")
self.close_all_positions()
return
# Take Profit
if holding.UnrealizedProfitPercent > 0.07:
# self.Log(f"[TAKE PROFIT] {holding.Symbol.Value} hit +6.5%.")
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 7.5% VIX gap
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.08: # 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 OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
return
order = self.Transactions.GetOrderById(orderEvent.OrderId)
# Reset daily slippage at start of new trading day
if self.last_slippage_reset_date is None or self.Time.date() > self.last_slippage_reset_date:
self.daily_slippage_dollars = 0.0
self.daily_trades_count = 0
self.last_slippage_reset_date = self.Time.date()
# Slippage calc
market_price = self.Securities[order.Symbol].Price
if market_price > 0:
filled_price = orderEvent.FillPrice
if order.Direction == OrderDirection.Buy:
slippage_per_unit = filled_price - market_price
else:
slippage_per_unit = market_price - filled_price
trade_slippage_dollars = slippage_per_unit * order.AbsoluteQuantity
self.daily_slippage_dollars += trade_slippage_dollars
self.daily_trades_count += 1
def OnEndOfDay(self, symbol):
pass