Overall Statistics |
Total Trades 8 Average Win 11.31% Average Loss -8.53% Compounding Annual Return 16.242% Drawdown 6.200% Expectancy 0.164 Net Profit 3.681% Sharpe Ratio 1.203 Probabilistic Sharpe Ratio 52.656% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.33 Alpha 0.061 Beta 0.951 Annual Standard Deviation 0.119 Annual Variance 0.014 Information Ratio 0.933 Tracking Error 0.061 Treynor Ratio 0.15 Total Fees $85.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 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, 4, 1); //Set Start Date SetEndDate(2019, 6, 25); SetCash(100000); //Set Strategy Cash // Add securities _vix = AddData<CBOE>("VIX"); _spx = AddEquity("SPY", Resolution.Minute); _vixRSI = new RelativeStrengthIndex(9); // 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(-25, 0) .BackMonths() // .Expiration(TimeSpan.Zero, TimeSpan.FromDays(10)) 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("VixRSI", _vixRSI); 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 = .2m; // Delta for short puts var shortDelta = -.2m; // Delta for long put var longDelta = -.1m; // 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}"); // 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}"); 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; } } } private void ExitPosition() { Liquidate(); _inPosition = false; } private void CheckExit(Slice data) { // var delta = GetDelta(data); // if (delta != Decimal.MinValue && delta < -.55m) // { // Debug($"Date: {Time.ToShortDateString()} Price: {_shortPut.UnderlyingLastPrice:F2} Strike: {_shortPut.Strike:F2} - Exiting due to Delta < .5"); // ExitPosition(); // } 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 % stop loss"); ExitPosition(); } //if (Time.Hour == 15 && Time.Minute == 50 && _nextExpiryDate.AddDays(-1).Date == Time.Date) //{ // Debug($"Date: {Time.ToShortDateString()} Price: {_shortPut.UnderlyingLastPrice:F2} Strike: {_shortPut.Strike:F2} - Exiting before expiration"); // ExitPosition(); //} } } }