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();
            // }
        }
    }
}