using System;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
namespace QuantConnect.Algorithm.CSharp
{
public class IronCondor {
public decimal premium = 0;
public OptionContract shortCall;
public OptionContract longCall;
public OptionContract shortPut;
public OptionContract longPut;
}
/// <summary>
/// Implementation of Iron Condor https//www.tastytrade.com/tt/shows/market-measures/episodes/backtesting-iron-condors-in-spx-06-29-2016
/// </summary>
public class TastyIronCondorAlgorithm:QCAlgorithm
{
private const string UnderlyingTicker = "SPY";
const int contracts = 1;
const decimal shortDelta = .3M;
const decimal longDelta = .05M;
const decimal cash = 10000;
const decimal stopProfit = .5M;
const decimal stopLoss = 2M;
public readonly Symbol Underlying = QuantConnect.Symbol.Create(UnderlyingTicker, SecurityType.Equity, Market.USA);
public readonly Symbol OptionSymbol = QuantConnect.Symbol.Create(UnderlyingTicker, SecurityType.Option, Market.USA);
IronCondor ironCondor;
public override void Initialize()
{
this.SetStartDate(2015, 1, 1);
this.SetEndDate(2015, 2, 21);
this.SetCash(cash);
var equity = this.AddEquity(UnderlyingTicker);
var option = this.AddOption(UnderlyingTicker);
// TODO monthly only
option.SetFilter(universe => from symbol in universe
// .WeeklysOnly()
.Expiration(TimeSpan.FromDays(30), TimeSpan.FromDays(40))
select symbol);
option.PriceModel = OptionPriceModels.CrankNicolsonFD();
// use the underlying equity as the benchmark
this.SetBenchmark(equity.Symbol);
this.SetWarmUp(TimeSpan.FromDays(10));
}
public override void OnData(Slice slice)
{
if(this.IsWarmingUp)
{
return;
}
if (!this.Portfolio.Invested) {
this.Enter(slice);
} else {
this.Exit();
}
}
private OptionContract FindShortPut(Slice slice)
{
OptionChain chain;
if (slice.OptionChains.TryGetValue(this.OptionSymbol, out chain))
{
// find put contract with farthest expiration
return (
from optionContract in chain
.OrderByDescending(x => x.Expiry)
.ThenByDescending(x => x.Strike)
where optionContract.Right == OptionRight.Put
where optionContract.Greeks.Delta >= -shortDelta
select optionContract
).FirstOrDefault();
}
return null;
}
private OptionContract FindLongPut(Slice slice, DateTime expirationDate)
{
OptionChain chain;
if (slice.OptionChains.TryGetValue(this.OptionSymbol, out chain))
{
// find put contract with farthest expiration
return (
from optionContract in chain
.OrderByDescending(x => x.Strike)
where optionContract.Expiry == expirationDate
where optionContract.Right == OptionRight.Put
where optionContract.Greeks.Delta >= -longDelta
select optionContract
).FirstOrDefault();
}
return null;
}
private OptionContract FindShortCall(Slice slice, DateTime expirationDate)
{
OptionChain chain;
if (slice.OptionChains.TryGetValue(this.OptionSymbol, out chain))
{
// find put contract with farthest expiration
return (
from optionContract in chain
.OrderByDescending(x => x.Strike)
where optionContract.Expiry == expirationDate
where optionContract.Right == OptionRight.Call
where optionContract.Greeks.Delta <= shortDelta
select optionContract
).LastOrDefault();
}
return null;
}
private OptionContract FindLongCall(Slice slice, DateTime expirationDate)
{
OptionChain chain;
if (slice.OptionChains.TryGetValue(this.OptionSymbol, out chain))
{
// find put contract with farthest expiration
return (
from optionContract in chain
.OrderByDescending(x => x.Strike)
where optionContract.Expiry == expirationDate
where optionContract.Right == OptionRight.Call
where optionContract.Greeks.Delta <= longDelta
select optionContract
).LastOrDefault();
}
return null;
}
private void Enter(Slice slice)
{
OptionContract shortPut = this.FindShortPut(slice);
if(shortPut == null){
return;
}
DateTime expirationDate = shortPut.Expiry;
OptionContract longPut = this.FindLongPut(slice, expirationDate);
if(longPut == null){
return;
}
OptionContract shortCall = this.FindShortCall(slice, expirationDate);
if(shortCall == null){
return;
}
OptionContract longCall = this.FindLongCall(slice, expirationDate);
if(longCall == null){
return;
}
this.ironCondor = new IronCondor();
this.ironCondor.shortPut = shortPut;
this.ironCondor.longPut = longPut;
this.ironCondor.shortCall = shortCall;
this.ironCondor.longCall = longCall;
this.ironCondor.premium = shortCall.BidPrice + shortPut.BidPrice - longCall.AskPrice - longPut.AskPrice;
// enter trade
MarketOrder(longCall.Symbol, contracts);
MarketOrder(shortCall.Symbol, -contracts);
MarketOrder(shortPut.Symbol, -contracts);
MarketOrder(longPut.Symbol, contracts);
Log("premium=" + this.ironCondor.premium);
Log("longCall.Delta=" + this.ironCondor.longCall.Greeks.Delta);
Log("shortCall.Delta=" + this.ironCondor.shortCall.Greeks.Delta);
Log("shortPut.Delta=" + this.ironCondor.shortPut.Greeks.Delta);
Log("longPut.Delta=" + this.ironCondor.longPut.Greeks.Delta);
}
private void Exit()
{
var pnl = this.ironCondor.premium - (this.ironCondor.shortCall.BidPrice + this.ironCondor.shortPut.BidPrice - this.ironCondor.longCall.AskPrice - this.ironCondor.longPut.AskPrice);
// % of premium that can be taken as profit
var gain = pnl / this.ironCondor.premium;
var closing = false;
if(gain >= stopProfit) {
closing = true;
Log("Take " + stopProfit + " of max profit");
}
else if(pnl <= -(this.ironCondor.premium * stopLoss)) {
closing = true;
Log("Stop loss: " + stopLoss);
}
else if(this.ironCondor.shortCall.Expiry.Date == Time.Date) {
closing = true;
Log("Close before expiration");
}
if(closing) {
MarketOrder(this.ironCondor.longCall.Symbol, -contracts);
MarketOrder(this.ironCondor.shortCall.Symbol, contracts);
MarketOrder(this.ironCondor.shortPut.Symbol, contracts);
MarketOrder(this.ironCondor.longPut.Symbol, -contracts);
this.ironCondor = null;
}
}
}
}