Overall Statistics |
Total Trades 32 Average Win 24.20% Average Loss -16.95% Compounding Annual Return -50.311% Drawdown 74.000% Expectancy 0.133 Net Profit -26.545% Sharpe Ratio 0.681 Probabilistic Sharpe Ratio 34.510% Loss Rate 53% Win Rate 47% Profit-Loss Ratio 1.43 Alpha 0.513 Beta 6.945 Annual Standard Deviation 1.647 Annual Variance 2.712 Information Ratio 0.664 Tracking Error 1.557 Treynor Ratio 0.162 Total Fees $1557.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); //SetEndDate(2020, 2, 24); SetStartDate(2017, 11, 1); SetEndDate(2018, 4, 10); 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); var sell = new Series("Sell", SeriesType.Scatter, "", Color.Red); var buy = new Series("Buy", SeriesType.Scatter, "", Color.Green); stockPlot.AddSeries(mainRsi); stockPlot.AddSeries(Ob); stockPlot.AddSeries(Os); stockPlot.AddSeries(Om); stockPlot.AddSeries(sell); stockPlot.AddSeries(buy); 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(20), 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 == 10 && Time.Minute == 30) { 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 && _vixRSI < 70 && !_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 = -.30m; // Delta for long put var longDelta = -.20m; // Helper variables _shortPut = null; _longPut = null; var deltaShortDiff = 100m; var deltaLongDiff = 100m; var w1 = Time.AddDays(20).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.BidPrice + _longPut.AskPrice) * 100); Debug($"Buying Power:{margin}"); Debug($"Short Qty:{qty:F0} @ {_shortPut.BidPrice:F2} @ ${_shortPut.Strike}"); Debug($"Long Qty:{qty:F0} @ {_longPut.AskPrice} @ ${_longPut.Strike}"); Debug($"Underlying: {_shortPut.UnderlyingLastPrice}"); 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 = (Math.Abs(shortOrder.AverageFillPrice) - Math.Abs(longOrder.AverageFillPrice)) * Math.Abs(longOrder.QuantityFilled) * 100; Debug($"Short Put: S:{_shortPut.Strike} Q:{shortOrder.QuantityFilled:F0} @ {shortOrder.AverageFillPrice:F2} "); _inPosition = true; // plot where we bought on the RSI Plot("Trade Plot", "Buy", _vixRSI); _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, netcredit was: {_netCredit} or on {_exitDate}. "); } } } private void ExitPosition() { Liquidate(); _inPosition = false; Plot("Trade Plot", "Sell", _vixRSI); } private void CheckExit(Slice data) { var change = Portfolio.TotalPortfolioValue - _openPortfolioValue; // Exit day before expiration if (Time.Date == _exitDate) { Debug($"_openPortfolioValue: {_openPortfolioValue}"); Debug($"change: {change} _netCredit: {_netCredit}"); Debug($"Date: {Time.ToShortDateString()} Price: {_shortPut.UnderlyingLastPrice:F2} Strike: {_shortPut.Strike:F2} - Exiting before expiration"); ExitPosition(); } // Check if we have 70% of max profit (net credit) //if(Portfolio[_shortPut.Symbol].UnrealizedProfitPercent >= 0.70M) { if (change / _netCredit > .7m) { Debug($"Profit short put %: {Portfolio[_shortPut.Symbol].UnrealizedProfitPercent}"); Debug($"Profit long put %: {Portfolio[_longPut.Symbol].UnrealizedProfitPercent}"); Debug($"Profit short put $: {Portfolio[_shortPut.Symbol].UnrealizedProfit}"); Debug($"Profit long put $: {Portfolio[_longPut.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) < 2.00M) { // Debug($"Profit short put %: {Portfolio[_shortPut.Symbol].UnrealizedProfitPercent}"); // Debug($"Profit long put %: {Portfolio[_longPut.Symbol].UnrealizedProfitPercent}"); // Debug($"Profit short put $: {Portfolio[_shortPut.Symbol].UnrealizedProfit}"); // Debug($"Profit long put $: {Portfolio[_longPut.Symbol].UnrealizedProfit}"); // Debug($"Date: {Time.ToShortDateString()} Price: {_spx.Price} Strike: {_shortPut.Strike} - LOSS!"); // ExitPosition(); // } if (_vixRSI >= 70) { Debug($"Date: {Time.ToShortDateString()} Price: {_shortPut.UnderlyingLastPrice} Strike: {_shortPut.Strike} - Exiting due to hitting 70 RSI on Vix"); ExitPosition(); } } } }