| Overall Statistics |
|
Total Orders 6 Average Win 21.13% Average Loss 0% Compounding Annual Return 10.502% Drawdown 9.300% Expectancy 0 Start Equity 100000 End Equity 176740.93 Net Profit 76.741% Sharpe Ratio 0.669 Sortino Ratio 0.323 Probabilistic Sharpe Ratio 60.501% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0.065 Annual Variance 0.004 Information Ratio 1.138 Tracking Error 0.065 Treynor Ratio 0 Total Fees $9.01 Estimated Strategy Capacity $1800000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X Portfolio Turnover 0.29% Drawdown Recovery 1306 |
#region imports
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Drawing;
using QuantConnect;
using QuantConnect.Algorithm.Framework;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Algorithm.Selection;
using QuantConnect.Api;
using QuantConnect.Parameters;
using QuantConnect.Benchmarks;
using QuantConnect.Brokerages;
using QuantConnect.Commands;
using QuantConnect.Configuration;
using QuantConnect.Util;
using QuantConnect.Interfaces;
using QuantConnect.Algorithm;
using QuantConnect.Indicators;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Custom;
using QuantConnect.Data.Custom.IconicTypes;
using QuantConnect.DataSource;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.Shortable;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Notifications;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.OptionExercise;
using QuantConnect.Orders.Slippage;
using QuantConnect.Orders.TimeInForces;
using QuantConnect.Python;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Positions;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Crypto;
using QuantConnect.Securities.CryptoFuture;
using QuantConnect.Securities.IndexOption;
using QuantConnect.Securities.Interfaces;
using QuantConnect.Securities.Volatility;
using QuantConnect.Storage;
using QuantConnect.Statistics;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
using Calendar = QuantConnect.Data.Consolidators.Calendar;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
public class Vixspy : QCAlgorithm
{
private Symbol _spy;
private Symbol _vix;
private RollingWindow<TradeBar> _vixHistory;
private DateTime _positionEntryDate;
// Configurable parameters
[Parameter("_vixTopThreshold")]
private decimal _vixTopThreshold = 55m; // VIX threshold to consider as "high"
public override void Initialize()
{
SetStartDate(2020, 1, 1);
SetEndDate(DateTime.Now.Date);
SetCash(100000);
// Set margin account
SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);
// Subscribe to SPY and VIX daily data
_spy = AddEquity("SPY", Resolution.Daily).Symbol;
_vix = AddIndex("VIX", Resolution.Daily).Symbol;
// Track 30 days of VIX history
_vixHistory = new RollingWindow<TradeBar>(30);
// Warm up the history with past data (increased to 180 to warm up SPY history)
SetWarmUp(200);
}
private bool ExitPositionIfNeeded(Slice data)
{
if (Portfolio[_spy].Invested)
{
var sixMonthHigh = History(_spy, 180, Resolution.Daily).Max(bar => bar.High);
var spyBar = data.ContainsKey(_spy) ? data[_spy] : null;
bool isSixMonthHigh = spyBar != null && spyBar.High >= sixMonthHigh;
if (isSixMonthHigh || (Time - _positionEntryDate).TotalDays >= 252) // Exit after 6 months if not at high
{
Liquidate(_spy);
_positionEntryDate = DateTime.MinValue;
return true;
}
}
return false;
}
private void EnterPositionIfConditionsMet(Slice data, decimal vixOpen, decimal vixClose, bool vixBelowThirty, bool vixWasAboveThreshold)
{
if (vixBelowThirty && vixWasAboveThreshold && !Portfolio[_spy].Invested)
{
var highestVix = _vixHistory.Max(bar => bar.High);
SetHoldings(_spy, 1);
_positionEntryDate = data.Time;
// get first contract expiring in 1.5 to 2 years delta 0.5
if (data.OptionChains.TryGetValue(_spy, out var chain))
{
var contract = chain
.Where(x => x.Expiry > Time.AddDays(540) && x.Expiry <= Time.AddDays(720) && x.Greeks.Delta >= 0.6m)
.OrderByDescending(x => x.Greeks.Delta)
.FirstOrDefault();
if (contract != null)
{
MarketOrder(contract.Symbol, 1);
Debug($"Bought 1 call option {contract.Symbol} expiring {contract.Expiry:yyyy-MM-dd} with delta {contract.Greeks.Delta:F2}");
}
else
{
Debug("No suitable option contract found.");
}
}
}
}
public override void OnData(Slice data)
{
if (!data.ContainsKey(_vix)) return;
var vixBar = data[_vix];
var vixOpen = vixBar.Open;
var vixClose = vixBar.Close;
// Add current VIX bar to history
_vixHistory.Add(vixBar);
// Need at least 30 days of VIX history to be ready
if (!_vixHistory.IsReady) return;
// Check if we need to close position (configurable days after entry)
if (ExitPositionIfNeeded(data)) return;
// Check if VIX open or close is below 30
bool vixBelowThirty = vixOpen < 30 || vixClose < 30;
// Check if VIX open, close, or high was above threshold within past 30 days
bool vixWasAboveThreshold = _vixHistory.Any(bar => bar.High > _vixTopThreshold);
// Buy 100% SPY if conditions are met
EnterPositionIfConditionsMet(data, vixOpen, vixClose, vixBelowThirty, vixWasAboveThreshold);
}
}
}