| Overall Statistics |
|
Total Trades 12 Average Win 0.04% Average Loss -0.02% Compounding Annual Return 15.292% Drawdown 1.000% Expectancy 1.988 Net Profit 1.104% Sharpe Ratio 4.028 Loss Rate 20% Win Rate 80% Profit-Loss Ratio 2.73 Alpha 0.344 Beta -14.53 Annual Standard Deviation 0.028 Annual Variance 0.001 Information Ratio 3.432 Tracking Error 0.028 Treynor Ratio -0.008 Total Fees $11.75 |
using QuantConnect.Securities.Option;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Basic template algorithm simply initializes the date range and cash. This is a skeleton
/// framework you can use for designing an algorithm.
/// </summary>
public class ShortStraddleDeltaHedge : QCAlgorithm
{
private decimal PREMIUM = 0.01m;
private int MAX_EXPIRY = 30;
private int _no_K = 2;
private decimal LEV = 1.0m;
private decimal DELTA_THRESHOLD = 0.05m;
private decimal MIN_THRESHOLD_LIQUIDATE = 0.05m;
private decimal OPTION_MULTIPLE = 100.0m;
private bool _assignedOption = false;
private DateTime expiry;
private DateTime last_trading_day;
private Equity equity;
private Symbol equity_symbol;
private Symbol option_symbol;
private Symbol put;
private Symbol call;
private decimal previous_delta = 0.0m;
private decimal delta;
/// <summary>
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
/// </summary>
public override void Initialize()
{
SetStartDate(2017, 1, 1); //Set Start Date
SetEndDate(2017, 12, 30); //Set End Date
SetCash(100000); //Set Strategy Cash
equity = AddEquity("SPY", Resolution.Minute);
equity.SetDataNormalizationMode(DataNormalizationMode.Raw); // IMPORTANT: default
equity_symbol = equity.Symbol;
// Add options
var option = AddOption("SPY", Resolution.Minute);
option_symbol = option.Symbol;
// Debug("Options Symbol: " + option.Symbol.ToString());
// set our strike/expiry filter for this option chain
option.SetFilter(u => u.IncludeWeeklys()
.Strikes(-this._no_K, this._no_K)
.Expiration(TimeSpan.Zero, TimeSpan.FromDays(MAX_EXPIRY)));
option.PriceModel = OptionPriceModels.CrankNicolsonFD();
SetWarmUp(TimeSpan.FromDays(3)); //Warm up 7 days of data.
put = null;
call = null;
// schedule the closing of options on last trading day
Schedule.On(DateRules.EveryDay(equity_symbol),
TimeRules.BeforeMarketClose(equity_symbol, 10),
() =>{
this.close_options();
});
}
/**
* Liquidate opts (with some value) and underlying
*/
private void close_options()
{
if (last_trading_day != Time.Date) return;
Log("On last trading day: liquidate options with value and underlying");
// liquidate options (if invested and in the money [otherwise their price is min of $0.01)
foreach (Securities.SecurityHolding holding in Portfolio.Values)
{
if (holding.Invested)
{
if (Securities[holding.Symbol].AskPrice >= MIN_THRESHOLD_LIQUIDATE)
{
Liquidate(holding.Symbol);
}
}
}
if (Portfolio[this.equity_symbol].Invested)
{
Liquidate(equity.Symbol);
}
}
/// <summary>
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
/// </summary>
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
public override void OnData(Slice slice)
{
try
{
if (IsWarmingUp) return;
// #1. deal with any early assignments
if (_assignedOption)
{
// can't figure out Linq Portfolio.Where(x => x.Value.Invested).Foreach(x => {}) ??
foreach (Securities.SecurityHolding holding in Portfolio.Values)
{
if (holding.Invested)
{
Liquidate(holding.Symbol);
}
}
_assignedOption = false;
}
// #2. sell options, if none
if (!Portfolio.Invested)
{
//Log("get contract");
get_contracts(slice);
if (call == null || put == null)
{
return;
}
decimal unit_price = Securities[equity_symbol].Price * OPTION_MULTIPLE;
int qnty = Convert.ToInt32(Portfolio.TotalPortfolioValue / unit_price);
if (call != null) Sell(call, qnty);
if (put != null) MarketOrder(put, -qnty);
}
// # 3. delta-hedge any existing option
if (Portfolio.Invested && HourIsMinute(10, 1))
{
this.get_greeks(slice);
if (Math.Abs(previous_delta - delta) > DELTA_THRESHOLD)
{
Debug("delta_hedging: call: "+call+", put: "+put+", delta: "+ delta);
Log("delta_hedging: call: "+call+", put: "+put+", delta: "+ delta);
SetHoldings(equity_symbol, delta);
previous_delta = delta; // update previous
}
}
}
catch (Exception e)
{
Error(e.ToString());
}
}
/**
*
*/
private bool HourIsMinute(int hour, int minute)
{
return Time.Hour == hour && Time.Minute == minute;
}
/**
* Get ATM call and put
*/
private void get_contracts(Slice slice)
{
// OptionChain chain;
foreach (KeyValuePair<Symbol, OptionChain> chains in slice.OptionChains)
{
if (chains.Key != option_symbol) continue;
OptionChain chain = chains.Value;
decimal spot_price = chain.Underlying.Price;
Log("spot_price: " + spot_price);
// 1. get furthest expiry
OptionContract[] sortedByExpiry = chain
.OrderByDescending(x => x.Expiry).ToArray();
expiry = sortedByExpiry[0].Expiry.Date;
Debug("Expiry: " + expiry.ToShortDateString());
last_trading_day = this.getLastTradingDay(expiry);
// // get contracts with further expiry and sort them by strike
OptionContract[] sortedByStrike = chain
.Where(x => x.Expiry == expiry)
.OrderBy(x => x.Strike).ToArray();
Log("Expiry used "+expiry.ToShortDateString()+" and shortest "+sortedByExpiry[sortedByExpiry.Length-1].Expiry.Date.ToShortDateString());
Debug("Expiry used "+expiry.ToShortDateString()+" and shortest "+sortedByExpiry[sortedByExpiry.Length-1].Expiry.Date.ToShortDateString());
// // 2a. get the ATM closest CALL to short
OptionContract[] callOptionContracts = sortedByStrike.Where(x => x.Right == OptionRight.Call &&
x.Strike >= spot_price).ToArray();
if (callOptionContracts.Length > 0)
{
call = callOptionContracts[0].Symbol;
}
// 2b. get the ATM closest put to short
OptionContract[] putOptionContracts = sortedByStrike.Where(x => x.Right == OptionRight.Put &&
x.Strike <= spot_price).ToArray();
if (putOptionContracts.Length > 0)
{
put = putOptionContracts[putOptionContracts.Length - 1].Symbol;
}
}
}
/**
*
*/
private void get_greeks(Slice slice)
{
if (put == null || call == null) return;
OptionChain chain;
if (slice.OptionChains.TryGetValue(option_symbol, out chain))
{
var traded_contracts = chain.Where(x => x.Symbol == put ||
x.Symbol == call);
if (traded_contracts == null) return;
delta = traded_contracts.Sum(x => x.Greeks.Delta);
}
}
/**
*
*/
private DateTime getLastTradingDay(DateTime expiry)
{
Debug("Checking Expiry: " + expiry.ToShortDateString());
//# American options cease trading on the third Friday, at the close of business
//# - Weekly options expire the same day as their last trading day, which will usually be a Friday (PM-settled), [or Mondays? & Wednesdays?]
//#
//# SPX cash index options (and other cash index options) expire on the Saturday following the third Friday of the expiration month.
//# However, the last trading day is the Thursday before that third Friday. Settlement price Friday morning opening (AM-settled).
//# http://www.daytradingbias.com/?p=84847
if (DayOfWeek.Saturday == expiry.DayOfWeek)
{
Debug("Expiry on Saturday");
return expiry.AddDays(-1);
}
if (TradingCalendar.GetDaysByType(TradingDayType.PublicHoliday, expiry, expiry)
.ToList().Count != 0)
{
Debug("Expiry on Holiday");
return expiry.AddDays(-1);
}
return expiry;
}
/**
*
*/
}
}