4c085ff853
* Add support for Index SecurityType 🚀 (#5364) * Add Index SecurityType 🚀 * Extend SecurityIdentifier & Lean Data classes with Index support * Add Index SecurityType 🚀 * Extend SecurityIdentifier & Lean Data classes with Index support * Fixes * Added index cross basic template demonstration * WIP: Prototype index security type for LEAN as non tradable asset * Re-adds Index entries to MHDB after rebase * First steps to getting Index Options running * Looks at any instance where we pattern match for an option type and replaces it with a generic call to `.IsOption()` for easier extensibility in the future for additional option security types * Adds IndexOption security and misc. classes * Misc. changes, mainly related to any sort of special casing of equity options and made index options take the same path * Enables index options data for backtesting * Adds new index options market hours to MHDB * Misc. bug fixes for index options * WIP: add live support for index options and indexes * Use OptionMarginModel for Index Options because they both use the same calculation for margin requirements * Fixes contract not found errors on SPX index options and SPX index in IB * Turns out index options' last trading day is the day before expiry, which IB was expecting the last trading day. * Add index option test cases (temp) * LiveOptionChainProvider fix, use Symbol vs. ticker * Description updates to regression algorithms * Fixes bug in live trading for indexes and index options * Adds overridable minimum price variation symbol property * Adds variable sized minimum price variation for index options * Adjusts symbol properties for index options * Misc. bug fixes * Fixes option assignment simulation for European options * Updates index options regression algorithms (WIP) * Fixes bug where index option exercise would trade index underlying * Fixes bugs where SecurityType.Index was getting flagged as tradable * Regression algorithms updates and addresses review * Misc. style fixes and refactoring + a few bug fixes * Updates regression algorithms to run without runtime errors * Adds data for regression algos * Sets DefaultOptionStyle on Canonical and support index options * Update regression algos statistics * Removes bad line in regression algorithm causing build to fail * Minor tweaks * Address review add comment about quoteBar parse scale Co-authored-by: Balamurali Pandranki <balamurali@live.com> Co-authored-by: Jared Broad <jaredbroad@gmail.com> Co-authored-by: Martin-Molinero <martin@quantconnect.com>
318 lines
15 KiB
C#
318 lines
15 KiB
C#
/*
|
|
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
|
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using QuantConnect.Securities.Future;
|
|
using static QuantConnect.StringExtensions;
|
|
|
|
namespace QuantConnect
|
|
{
|
|
/// <summary>
|
|
/// Public static helper class that does parsing/generation of symbol representations (options, futures)
|
|
/// </summary>
|
|
public static class SymbolRepresentation
|
|
{
|
|
/// <summary>
|
|
/// Class contains future ticker properties returned by ParseFutureTicker()
|
|
/// </summary>
|
|
public class FutureTickerProperties
|
|
{
|
|
/// <summary>
|
|
/// Underlying name
|
|
/// </summary>
|
|
public string Underlying { get; set; }
|
|
|
|
/// <summary>
|
|
/// Short expiration year
|
|
/// </summary>
|
|
public int ExpirationYearShort { get; set; }
|
|
|
|
/// <summary>
|
|
/// Expiration month
|
|
/// </summary>
|
|
public int ExpirationMonth { get; set; }
|
|
|
|
/// <summary>
|
|
/// Expiration day
|
|
/// </summary>
|
|
public int ExpirationDay { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Class contains option ticker properties returned by ParseOptionTickerIQFeed()
|
|
/// </summary>
|
|
public class OptionTickerProperties
|
|
{
|
|
/// <summary>
|
|
/// Underlying name
|
|
/// </summary>
|
|
public string Underlying { get; set; }
|
|
|
|
/// <summary>
|
|
/// Option right
|
|
/// </summary>
|
|
public OptionRight OptionRight { get; set; }
|
|
|
|
/// <summary>
|
|
/// Option strike
|
|
/// </summary>
|
|
public decimal OptionStrike { get; set; }
|
|
|
|
/// <summary>
|
|
/// Expiration date
|
|
/// </summary>
|
|
public DateTime ExpirationDate { get; set; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Function returns underlying name, expiration year, expiration month, expiration day for the future contract ticker. Function detects if
|
|
/// the format used is either 1 or 2 digits year, and if day code is present (will default to 1rst day of month). Returns null, if parsing failed.
|
|
/// Format [Ticker][2 digit day code OPTIONAL][1 char month code][2/1 digit year code]
|
|
/// </summary>
|
|
/// <param name="ticker"></param>
|
|
/// <returns>Results containing 1) underlying name, 2) short expiration year, 3) expiration month</returns>
|
|
public static FutureTickerProperties ParseFutureTicker(string ticker)
|
|
{
|
|
var doubleDigitYear = char.IsDigit(ticker.Substring(ticker.Length - 2, 1)[0]);
|
|
var doubleDigitOffset = doubleDigitYear ? 1 : 0;
|
|
|
|
var expirationDayOffset = 0;
|
|
var expirationDay = 1;
|
|
if (ticker.Length > 4 + doubleDigitOffset)
|
|
{
|
|
var potentialExpirationDay = ticker.Substring(ticker.Length - 4 - doubleDigitOffset, 2);
|
|
var containsExpirationDay = char.IsDigit(potentialExpirationDay[0]) && char.IsDigit(potentialExpirationDay[1]);
|
|
expirationDayOffset = containsExpirationDay ? 2 : 0;
|
|
if (containsExpirationDay && !int.TryParse(potentialExpirationDay, out expirationDay))
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
var expirationYearString = ticker.Substring(ticker.Length - 1 - doubleDigitOffset, 1 + doubleDigitOffset);
|
|
var expirationMonthString = ticker.Substring(ticker.Length - 2 - doubleDigitOffset, 1);
|
|
var underlyingString = ticker.Substring(0, ticker.Length - 2 - doubleDigitOffset - expirationDayOffset);
|
|
|
|
int expirationYearShort;
|
|
|
|
if (!int.TryParse(expirationYearString, out expirationYearShort))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!_futuresMonthCodeLookup.ContainsKey(expirationMonthString))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var expirationMonth = _futuresMonthCodeLookup[expirationMonthString];
|
|
|
|
return new FutureTickerProperties
|
|
{
|
|
Underlying = underlyingString,
|
|
ExpirationYearShort = expirationYearShort,
|
|
ExpirationMonth = expirationMonth,
|
|
ExpirationDay = expirationDay
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns future symbol ticker from underlying and expiration date. Function can generate tickers of two formats: one and two digits year.
|
|
/// Format [Ticker][2 digit day code][1 char month code][2/1 digit year code], more information at http://help.tradestation.com/09_01/tradestationhelp/symbology/futures_symbology.htm
|
|
/// </summary>
|
|
/// <param name="underlying">String underlying</param>
|
|
/// <param name="expiration">Expiration date</param>
|
|
/// <param name="doubleDigitsYear">True if year should represented by two digits; False - one digit</param>
|
|
/// <returns></returns>
|
|
public static string GenerateFutureTicker(string underlying, DateTime expiration, bool doubleDigitsYear = true)
|
|
{
|
|
var year = doubleDigitsYear ? expiration.Year % 100 : expiration.Year % 10;
|
|
var month = expiration.Month;
|
|
|
|
var contractMonthDelta = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(underlying, expiration.Date);
|
|
if (contractMonthDelta < 0)
|
|
{
|
|
// For futures that have an expiry after the contract month.
|
|
// This is for dairy contracts, which can and do expire after the contract month.
|
|
var expirationMonth = expiration.AddDays(-(expiration.Day - 1))
|
|
.AddMonths(contractMonthDelta);
|
|
|
|
month = expirationMonth.Month;
|
|
year = doubleDigitsYear ? expirationMonth.Year % 100 : expirationMonth.Year % 10;
|
|
}
|
|
else {
|
|
// These futures expire in the month before or in the contract month
|
|
month += contractMonthDelta;
|
|
|
|
// Get the month back into the allowable range, allowing for a wrap
|
|
// Below is a little algorithm for wrapping numbers with a certain bounds.
|
|
// In this case, were dealing with months, wrapping to years once we get to January
|
|
// As modulo works for [0, x), it's best to subtract 1 (as months are [1, 12] to convert to [0, 11]),
|
|
// do the modulo/integer division, then add 1 back on to get into the correct range again
|
|
month--;
|
|
year += month / 12;
|
|
month %= 12;
|
|
month++;
|
|
}
|
|
|
|
return $"{underlying}{expiration.Day:00}{_futuresMonthLookup[month]}{year}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns option symbol ticker in accordance with OSI symbology
|
|
/// More information can be found at http://www.optionsclearing.com/components/docs/initiatives/symbology/symbology_initiative_v1_8.pdf
|
|
/// </summary>
|
|
/// <param name="symbol">Symbol object to create OSI ticker from</param>
|
|
/// <returns>The OSI ticker representation</returns>
|
|
public static string GenerateOptionTickerOSI(this Symbol symbol)
|
|
{
|
|
if (!symbol.SecurityType.IsOption())
|
|
{
|
|
throw new ArgumentException(Invariant($"{nameof(GenerateOptionTickerOSI)} returns symbol to be an option, received {symbol.SecurityType}."));
|
|
}
|
|
|
|
return GenerateOptionTickerOSI(symbol.Underlying.Value, symbol.ID.OptionRight, symbol.ID.StrikePrice, symbol.ID.Date);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns option symbol ticker in accordance with OSI symbology
|
|
/// More information can be found at http://www.optionsclearing.com/components/docs/initiatives/symbology/symbology_initiative_v1_8.pdf
|
|
/// </summary>
|
|
/// <param name="underlying">Underlying string</param>
|
|
/// <param name="right">Option right</param>
|
|
/// <param name="strikePrice">Option strike</param>
|
|
/// <param name="expiration">Option expiration date</param>
|
|
/// <returns>The OSI ticker representation</returns>
|
|
public static string GenerateOptionTickerOSI(string underlying, OptionRight right, decimal strikePrice, DateTime expiration)
|
|
{
|
|
if (underlying.Length > 5) underlying += " ";
|
|
return Invariant($"{underlying,-6}{expiration.ToStringInvariant(DateFormat.SixCharacter)}{right.ToStringPerformance()[0]}{(strikePrice * 1000m):00000000}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the specified OSI options ticker into a Symbol object
|
|
/// </summary>
|
|
/// <param name="ticker">The OSI compliant option ticker string</param>
|
|
/// <returns>Symbol object for the specified OSI option ticker string</returns>
|
|
public static Symbol ParseOptionTickerOSI(string ticker)
|
|
{
|
|
var underlying = ticker.Substring(0, 6).Trim();
|
|
var expiration = DateTime.ParseExact(ticker.Substring(6, 6), DateFormat.SixCharacter, null);
|
|
OptionRight right;
|
|
if (ticker[12] == 'C') right = OptionRight.Call;
|
|
else if (ticker[12] == 'P') right = OptionRight.Put;
|
|
else throw new FormatException($"Expected 12th character to be 'C' or 'P' for OptionRight: {ticker}");
|
|
var strike = Parse.Decimal(ticker.Substring(13, 8)) / 1000m;
|
|
var underlyingSid = SecurityIdentifier.GenerateEquity(underlying, Market.USA);
|
|
var sid = SecurityIdentifier.GenerateOption(expiration, underlyingSid, Market.USA, strike, right, OptionStyle.American);
|
|
return new Symbol(sid, ticker, new Symbol(underlyingSid, underlying));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function returns option contract parameters (underlying name, expiration date, strike, right) from IQFeed option ticker
|
|
/// Symbology details: http://www.iqfeed.net/symbolguide/index.cfm?symbolguide=guide&displayaction=support%C2%A7ion=guide&web=iqfeed&guide=options&web=IQFeed&type=stock
|
|
/// </summary>
|
|
/// <param name="ticker">IQFeed option ticker</param>
|
|
/// <returns>Results containing 1) underlying name, 2) option right, 3) option strike 4) expiration date</returns>
|
|
public static OptionTickerProperties ParseOptionTickerIQFeed(string ticker)
|
|
{
|
|
// This table describes IQFeed option symbology
|
|
var symbology = new Dictionary<string, Tuple<int, OptionRight>>
|
|
{
|
|
{ "A", Tuple.Create(1, OptionRight.Call) }, { "M", Tuple.Create(1, OptionRight.Put) },
|
|
{ "B", Tuple.Create(2, OptionRight.Call) }, { "N", Tuple.Create(2, OptionRight.Put) },
|
|
{ "C", Tuple.Create(3, OptionRight.Call) }, { "O", Tuple.Create(3, OptionRight.Put) },
|
|
{ "D", Tuple.Create(4, OptionRight.Call) }, { "P", Tuple.Create(4, OptionRight.Put) },
|
|
{ "E", Tuple.Create(5, OptionRight.Call) }, { "Q", Tuple.Create(5, OptionRight.Put) },
|
|
{ "F", Tuple.Create(6, OptionRight.Call) }, { "R", Tuple.Create(6, OptionRight.Put) },
|
|
{ "G", Tuple.Create(7, OptionRight.Call) }, { "S", Tuple.Create(7, OptionRight.Put) },
|
|
{ "H", Tuple.Create(8, OptionRight.Call) }, { "T", Tuple.Create(8, OptionRight.Put) },
|
|
{ "I", Tuple.Create(9, OptionRight.Call) }, { "U", Tuple.Create(9, OptionRight.Put) },
|
|
{ "J", Tuple.Create(10, OptionRight.Call) }, { "V", Tuple.Create(10, OptionRight.Put) },
|
|
{ "K", Tuple.Create(11, OptionRight.Call) }, { "W", Tuple.Create(11, OptionRight.Put) },
|
|
{ "L", Tuple.Create(12, OptionRight.Call) }, { "X", Tuple.Create(12, OptionRight.Put) },
|
|
|
|
};
|
|
|
|
var letterRange = symbology.Keys
|
|
.Select(x => x[0])
|
|
.ToArray();
|
|
var optionTypeDelimiter = ticker.LastIndexOfAny(letterRange);
|
|
var strikePriceString = ticker.Substring(optionTypeDelimiter + 1, ticker.Length - optionTypeDelimiter - 1);
|
|
|
|
var lookupResult = symbology[ticker[optionTypeDelimiter].ToStringInvariant()];
|
|
var month = lookupResult.Item1;
|
|
var optionRight = lookupResult.Item2;
|
|
|
|
var dayString = ticker.Substring(optionTypeDelimiter - 2, 2);
|
|
var yearString = ticker.Substring(optionTypeDelimiter - 4, 2);
|
|
var underlying = ticker.Substring(0, optionTypeDelimiter - 4);
|
|
|
|
// if we cannot parse strike price, we ignore this contract, but log the information.
|
|
decimal strikePrice;
|
|
if (!Decimal.TryParse(strikePriceString, out strikePrice))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int day;
|
|
|
|
if (!int.TryParse(dayString, out day))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int year;
|
|
|
|
if (!int.TryParse(yearString, out year))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var expirationDate = new DateTime(2000 + year, month, day);
|
|
|
|
return new OptionTickerProperties
|
|
{
|
|
Underlying = underlying,
|
|
OptionRight = optionRight,
|
|
OptionStrike = strikePrice,
|
|
ExpirationDate = expirationDate
|
|
};
|
|
}
|
|
|
|
|
|
private static IReadOnlyDictionary<string, int> _futuresMonthCodeLookup = new Dictionary<string, int>
|
|
{
|
|
{ "F", 1 },
|
|
{ "G", 2 },
|
|
{ "H", 3 },
|
|
{ "J", 4 },
|
|
{ "K", 5 },
|
|
{ "M", 6 },
|
|
{ "N", 7 },
|
|
{ "Q", 8 },
|
|
{ "U", 9 },
|
|
{ "V", 10 },
|
|
{ "X", 11 },
|
|
{ "Z", 12 }
|
|
};
|
|
|
|
private static IReadOnlyDictionary<int, string> _futuresMonthLookup = _futuresMonthCodeLookup.ToDictionary(kv => kv.Value, kv => kv.Key);
|
|
}
|
|
}
|