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