forked from QuantConnect/Lean
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSymbolRepresentation.cs
317 lines (275 loc) · 14.8 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
/*
* 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);
}
}