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