Overall Statistics |
Total Trades 24 Average Win 51.95% Average Loss -29.62% Compounding Annual Return 45.577% Drawdown 14.900% Expectancy 0.377 Net Profit 49.217% Sharpe Ratio 2.022 Probabilistic Sharpe Ratio 79.578% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.75 Alpha 0.214 Beta 1.01 Annual Standard Deviation 0.191 Annual Variance 0.036 Information Ratio 1.394 Tracking Error 0.155 Treynor Ratio 0.382 Total Fees $1407.00 |
using QuantConnect.Data; using QuantConnect.Data.Custom.CBOE; using QuantConnect.Data.Custom; using QuantConnect.Indicators; using System.Collections.Generic; using System; using System.Linq; using System.Drawing; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Option; using QuantConnect.Data.Market; using QuantConnect.Orders; namespace QuantConnect.Algorithm.CSharp { public class PutCreditSpread : QCAlgorithm { public class CustomBuyingPowerModel : BuyingPowerModel { public override GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower( GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters) { var quantity = base.GetMaximumOrderQuantityForTargetBuyingPower(parameters).Quantity; quantity = Math.Floor(quantity / 100) * 100; return new GetMaximumOrderQuantityResult(quantity); } public override HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder( HasSufficientBuyingPowerForOrderParameters parameters) { return new HasSufficientBuyingPowerForOrderResult(true); } } private Security _vix; private Equity _spx; private Option _spxOption; private OptionContract _shortPut; private OptionContract _longPut; private RelativeStrengthIndex _vixRSI; private bool _inPosition = false; private decimal _openPortfolioValue; private decimal _netCredit; private DateTime _expiry; private DateTime _exitDate; public override void Initialize() { SetStartDate(2019, 2, 1); //Set Start Date SetEndDate(2020, 2, 24); SetCash(150000); //Set Strategy Cash // Add securities _vix = AddData<CBOE>("VIX"); _spx = AddEquity("SPY", Resolution.Minute); _vixRSI = new RelativeStrengthIndex(10); // Charting var stockPlot = new Chart("Trade Plot"); var mainRsi = new Series("RSI", SeriesType.Line, "", Color.Aqua); var Ob = new Series("Over Bought", SeriesType.Line, "", Color.Navy); var Os = new Series("Over Sold", SeriesType.Line, "", Color.Navy); var Om = new Series("Mid", SeriesType.Line, "", Color.Navy); stockPlot.AddSeries(mainRsi); stockPlot.AddSeries(Ob); stockPlot.AddSeries(Os); stockPlot.AddSeries(Om); AddChart(stockPlot); // Add Options _spxOption = AddOption("SPY", Resolution.Minute); _spxOption.PriceModel = OptionPriceModels.CrankNicolsonFD(); _spxOption.SetBuyingPowerModel(new CustomBuyingPowerModel()); // Set our custom filter for this option chain _spxOption.SetFilter(universe => from symbol in universe .OnlyApplyFilterAtMarketOpen() .PutsOnly() .Strikes(-30, 0) .Expiration(TimeSpan.FromDays(25), TimeSpan.FromDays(45)) select symbol); // Use the underlying equity as the benchmark SetBenchmark("SPY"); SetWarmUp(TimeSpan.FromDays(30)); } public override void OnData(Slice data) { // Check entry once a day if (Time.Hour == 9 && Time.Minute == 31) { if (IsWarmingUp) return; // Update RSI _vixRSI.Update(Time, _vix.Close); Plot("Trade Plot", "RSI", _vixRSI); Plot("Trade Plot", "Over Bought", 80); Plot("Trade Plot", "Over Sold", 20); Plot("Trade Plot", "Mid", 50); if (_vixRSI >= 60 && !_inPosition) { EnterPosition(data); } } // Always check the exit if (_inPosition) CheckExit(data); } private void EnterPosition(Slice data) { // Can't invest 100% due to margin. Can increase leverage or lower our invest percent. var investPercent = .9m; // Delta for short puts var shortDelta = -.20m; // Delta for long put var longDelta = -.10m; // Helper variables _shortPut = null; _longPut = null; var deltaShortDiff = 100m; var deltaLongDiff = 100m; var w1 = Time.AddDays(25).Date; var w2 = Time.AddDays(45).Date; // Loop through chain to find target options OptionChain chain; if (data.OptionChains.TryGetValue(_spxOption.Symbol, out chain)) { // Find short put contract foreach (var contract in chain.Contracts.Values) { if (!(contract.Expiry.Date > w1 && contract.Expiry.Date < w2)) continue; // Calculate the difference between the contract Delta and our short target Delta var shortDiff = shortDelta - contract.Greeks.Delta; // Check to see if this is the closest delta if (shortDiff < deltaShortDiff && shortDiff > 0) { deltaShortDiff = shortDiff; _shortPut = contract; } // Calculate the difference between the contract Delta and our long target Delta var longDiff = longDelta - contract.Greeks.Delta; // Check to see if this is the closest delta if (longDiff < deltaLongDiff && longDiff > 0) { deltaLongDiff = longDiff; _longPut = contract; } } if (_shortPut == null || _longPut == null) { Debug("Could not find strikes near our target Delta"); return; } if (_shortPut.Strike == _longPut.Strike) { Debug("Strikes of long and short were equivalent, not trade made."); } Debug($"Short Delta:{_shortPut.Greeks.Delta} Long Delta:{_longPut.Greeks.Delta}"); Debug($"Short Expiry Date: {_shortPut.Expiry} Long Expiry Date: {_longPut.Expiry}"); // Calculate qty of both legs var margin = Portfolio.GetBuyingPower(_shortPut.Symbol, OrderDirection.Sell); var qty = margin * investPercent / ((_shortPut.AskPrice + _longPut.BidPrice) * 100); Debug($"Underlying Price: {_shortPut.UnderlyingLastPrice}"); Debug($"{Time.Date} SELL STRIKE: {_shortPut.Strike} {_shortPut.AskPrice:F2} SPY {_shortPut.Expiry.Date} Sell Put Vertical Spread"); Debug($"{Time.Date} BUY STRIKE: {_longPut.Strike} {_longPut.AskPrice:F2} SPY {_longPut.Expiry.Date} Sell Put Vertical Spread"); if (_shortPut.Expiry.Date != _longPut.Expiry.Date) { Debug("Expiry dates don't match!"); } if (qty < 1) { Debug("Not enough cash to buy."); } else { // Buy legs and store net credit var longOrder = Buy(_longPut.Symbol, Math.Floor(qty)); Debug($"Long Put: S:{_longPut.Strike} Q:{longOrder.QuantityFilled:F0} @ {longOrder.AverageFillPrice:F2} "); var shortOrder = Sell(_shortPut.Symbol, Math.Floor(qty)); _netCredit = -shortOrder.AverageFillPrice * shortOrder.QuantityFilled * 100; Debug($"Short Put: S:{_shortPut.Strike} Q:{shortOrder.QuantityFilled:F0} @ {shortOrder.AverageFillPrice:F2} "); _inPosition = true; _expiry = _longPut.Expiry.Date; // Get last Trading day before expiration date _exitDate = TradingCalendar.GetTradingDays(_expiry.AddDays(-7), _expiry.AddDays(-1)).Last().Date.Date; _openPortfolioValue = Portfolio.TotalPortfolioValue; Debug($"Exit at {_netCredit*.7m:F0} profit or on {_exitDate}. "); } } } private void ExitPosition() { Liquidate(); _inPosition = false; } private void CheckExit(Slice data) { // Exit day before expiration // if (Time.Date == _exitDate) // { // Debug($"Date: {Time.ToShortDateString()} Price: {_shortPut.UnderlyingLastPrice:F2} Strike: {_shortPut.Strike:F2} - Exiting before expiration"); // ExitPosition(); // } var change = Portfolio.TotalPortfolioValue - _openPortfolioValue; // Check if we have 70% of max profit (net credit) if(Portfolio[_shortPut.Symbol].UnrealizedProfitPercent >= 0.70M) { Debug($"Profit %: {Portfolio[_shortPut.Symbol].UnrealizedProfitPercent}"); Debug($"Profit $: {Portfolio[_shortPut.Symbol].UnrealizedProfit}"); //Debug($"change / _netCredit: {change/_netCredit}"); Debug($"Date: {Time.ToShortDateString()} Price: {_shortPut.UnderlyingLastPrice} Strike: {_shortPut.Strike} - Exiting due to hitting 70% profit"); ExitPosition(); } /** if(Portfolio[_shortPut.Symbol].UnrealizedProfitPercent < -0.50M) { Debug($"Profit: {Portfolio[_shortPut.Symbol].UnrealizedProfitPercent}"); Debug($"Date: {Time.ToShortDateString()} Price: {_spx.Price} Strike: {_shortPut.Strike} - LOSS!"); ExitPosition(); } */ // if(_spx.Price > _shortPut.Strike && (_spx.Price - _shortPut.Strike) < 3.00M && Portfolio[_shortPut.Symbol].UnrealizedProfitPercent < -0.50M) { // Debug($"Profit %: {Portfolio[_shortPut.Symbol].UnrealizedProfitPercent}"); // Debug($"Profit $: {Portfolio[_shortPut.Symbol].UnrealizedProfit}"); // Debug($"Date: {Time.ToShortDateString()} Price: {_spx.Price} Strike: {_shortPut.Strike} - LOSS!"); // ExitPosition(); // } } } }