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