Overall Statistics |
Total Trades 30 Average Win 43.91% Average Loss -26.37% Compounding Annual Return 85.898% Drawdown 34.200% Expectancy 0.333 Net Profit 34.319% Sharpe Ratio 1.788 Probabilistic Sharpe Ratio 58.341% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.67 Alpha -0.008 Beta 3.952 Annual Standard Deviation 0.491 Annual Variance 0.241 Information Ratio 1.555 Tracking Error 0.42 Treynor Ratio 0.222 Total Fees $1402.25 |
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, 1, 18); SetCash(100000); //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 .IncludeWeeklys() .OnlyApplyFilterAtMarketOpen() .PutsOnly() .Strikes(-30, 0) .BackMonths() .Expiration(TimeSpan.FromDays(10), TimeSpan.FromDays(50)) 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 >= 50 && !_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 = -.25m; // Delta for long put var longDelta = -.10m; // Helper variables _shortPut = null; _longPut = null; var deltaShortDiff = 100m; var deltaLongDiff = 100m; var w1 = Time.AddDays(10).Date; var w2 = Time.AddDays(50).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}"); // Calculate qty of both legs var margin = Portfolio.GetBuyingPower(_shortPut.Symbol, OrderDirection.Sell); var qty = margin * investPercent / ((_shortPut.AskPrice + _longPut.BidPrice) * 100); Debug($"Short Qty:{qty:F0} @ {_shortPut.AskPrice:F2} Long Qty:{qty:F0} @ {_longPut.BidPrice}"); Debug($"Underlying Price: {_shortPut.UnderlyingLastPrice}"); Debug($"Expiry Date: {_shortPut.Expiry}"); 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 (change / _netCredit > .7m) { Debug($"Date: {Time.ToShortDateString()} Price: {_shortPut.UnderlyingLastPrice:F2} Strike: {_shortPut.Strike:F2} - Exiting due to hitting 70% profit"); ExitPosition(); } // Check if we have 20% loss (net credit) (MIGHT NOT WORK) if (change / _netCredit < -.2m) { Debug($"Date: {Time.ToShortDateString()} Price: {_shortPut.UnderlyingLastPrice:F2} Strike: {_shortPut.Strike:F2} - Exiting due to hitting 70% profit"); ExitPosition(); } } } }