forked from QuantConnect/Lean
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSymbolRepresentation.cs
455 lines (395 loc) · 20.2 KB
/
SymbolRepresentation.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
/*
* 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.Linq;
using QuantConnect.Logging;
using System.Globalization;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.FutureOption;
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
{
private static DateTime TodayUtc = DateTime.UtcNow;
/// <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>
/// Helper method to parse and generate a future symbol from a given user friendly representation
/// </summary>
/// <param name="ticker">The future ticker, for example 'ESZ1'</param>
/// <param name="futureYear">Clarifies the year for the current future</param>
/// <returns>The future symbol or null if failed</returns>
public static Symbol ParseFutureSymbol(string ticker, int? futureYear = null)
{
var disambiguatedFutureYear = futureYear ?? TodayUtc.Year;
var parsed = ParseFutureTicker(ticker);
if (parsed == null)
{
return null;
}
var underlying = parsed.Underlying;
var expirationYearShort = parsed.ExpirationYearShort;
var expirationMonth = parsed.ExpirationMonth;
var expirationYear = GetExpirationYear(expirationYearShort, disambiguatedFutureYear);
if (!SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(underlying, SecurityType.Future, out var market))
{
Log.Debug($"SymbolRepresentation.ParseFutureSymbol(): Failed to get market for future '{ticker}' and underlying '{underlying}'");
return null;
}
var expiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(Symbol.Create(underlying, SecurityType.Future, market));
var expiryDate = expiryFunc(new DateTime(expirationYear, expirationMonth, 1));
return Symbol.CreateFuture(underlying, market, expiryDate);
}
/// <summary>
/// Creates a future option Symbol from the provided ticker
/// </summary>
/// <param name="ticker">The future option ticker, for example 'ESZ0 P3590'</param>
/// <param name="strikeScale">Optional the future option strike scale factor</param>
public static Symbol ParseFutureOptionSymbol(string ticker, int strikeScale = 1)
{
var split = ticker.Split(' ');
if (split.Length != 2)
{
return null;
}
var parsed = ParseFutureTicker(split[0]);
if (parsed == null)
{
return null;
}
ticker = parsed.Underlying;
OptionRight right;
if (split[1][0] == 'P' || split[1][0] == 'p')
{
right = OptionRight.Put;
}
else if (split[1][0] == 'C' || split[1][0] == 'c')
{
right = OptionRight.Call;
}
else
{
return null;
}
var strike = split[1].Substring(1);
if (parsed.ExpirationYearShort < 10)
{
parsed.ExpirationYearShort += 20;
}
var expirationYearParsed = 2000 + parsed.ExpirationYearShort;
var expirationDate = new DateTime(expirationYearParsed, parsed.ExpirationMonth, 1);
var strikePrice = decimal.Parse(strike, NumberStyles.Any, CultureInfo.InvariantCulture);
var futureTicker = FuturesOptionsSymbolMappings.MapFromOption(ticker);
if (!SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(futureTicker, SecurityType.Future, out var market))
{
Log.Debug($"SymbolRepresentation.ParseFutureOptionSymbol(): No market found for '{futureTicker}'");
return null;
}
var canonicalFuture = Symbol.Create(futureTicker, SecurityType.Future, market);
var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture)(expirationDate);
var future = Symbol.CreateFuture(futureTicker, market, futureExpiry);
var futureOptionExpiry = FuturesOptionsExpiryFunctions.GetFutureOptionExpiryFromFutureExpiry(future);
return Symbol.CreateOption(future,
market,
OptionStyle.American,
right,
strikePrice / strikeScale,
futureOptionExpiry);
}
/// <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>
/// <param name="securityType">The security type</param>
/// <param name="market">The associated market</param>
/// <returns>Symbol object for the specified OSI option ticker string</returns>
public static Symbol ParseOptionTickerOSI(string ticker, SecurityType securityType = SecurityType.Option, string market = Market.USA)
{
var underlying = ticker.Substring(0, 6).Trim();
var expiration = DateTime.ParseExact(ticker.Substring(6, 6), DateFormat.SixCharacter, null);
OptionRight right;
if (ticker[12] == 'C' || ticker[12] == 'c')
{
right = OptionRight.Call;
}
else if (ticker[12] == 'P' || ticker[12] == 'p')
{
right = OptionRight.Put;
}
else
{
throw new FormatException($"Expected 12th character to be 'C' or 'P' for OptionRight: {ticker} but was '{ticker[12]}'");
}
var strike = Parse.Decimal(ticker.Substring(13, 8)) / 1000m;
SecurityIdentifier underlyingSid;
if (securityType == SecurityType.Option)
{
underlyingSid = SecurityIdentifier.GenerateEquity(underlying, market);
}
else if(securityType == SecurityType.IndexOption)
{
underlyingSid = SecurityIdentifier.GenerateIndex(underlying, market);
}
else
{
throw new NotImplementedException($"ParseOptionTickerOSI(): security type {securityType} not implemented");
}
var sid = SecurityIdentifier.GenerateOption(expiration, underlyingSid, market, 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);
private static int GetExpirationYear(int year, int futureYear)
{
var baseNum = 2000;
while (baseNum + year < futureYear)
{
baseNum += 10;
}
return baseNum + year;
}
}
}