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