Skip to content

Commit

Permalink
Future SymbolRepresentation Future Parsing (#7695)
Browse files Browse the repository at this point in the history
- Improve future symbol representation future parsing. Adding more unit
  tests
  • Loading branch information
Martin-Molinero authored Jan 15, 2024
1 parent cc8caa9 commit bf2f310
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 11 deletions.
40 changes: 34 additions & 6 deletions Common/SymbolRepresentation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ public class FutureTickerProperties
/// <summary>
/// Short expiration year
/// </summary>
public int ExpirationYearShort { get; set; }
public int ExpirationYearShort { get; set; }

/// <summary>
/// Short expiration year digits
/// </summary>
public int ExpirationYearShortLength { get; set; }

/// <summary>
/// Expiration month
Expand Down Expand Up @@ -133,6 +138,7 @@ public static FutureTickerProperties ParseFutureTicker(string ticker)
{
Underlying = underlyingString,
ExpirationYearShort = expirationYearShort,
ExpirationYearShortLength = expirationYearString.Length,
ExpirationMonth = expirationMonth,
ExpirationDay = expirationDay
};
Expand All @@ -153,9 +159,8 @@ public static Symbol ParseFutureSymbol(string ticker, int? futureYear = null)
}

var underlying = parsed.Underlying;
var expirationYearShort = parsed.ExpirationYearShort;
var expirationMonth = parsed.ExpirationMonth;
var expirationYear = futureYear ?? GetExpirationYear(expirationYearShort);
var expirationYear = GetExpirationYear(futureYear, parsed);

if (!SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(underlying, SecurityType.Future, out var market))
{
Expand Down Expand Up @@ -467,12 +472,35 @@ public static OptionTickerProperties ParseOptionTickerIQFeed(string ticker)
/// Get the expiration year from short year (two-digit integer).
/// Examples: NQZ23 and NQZ3 for Dec 2023
/// </summary>
/// <param name="futureYear">Clarifies the year for the current future</param>
/// <param name="shortYear">Year in 2 digits format (23 represents 2023)</param>
/// <returns>Tickers from live trading may not provide the four-digit year.</returns>
private static int GetExpirationYear(int shortYear)
private static int GetExpirationYear(int? futureYear, FutureTickerProperties parsed)
{
var baseYear = shortYear > 9 ? 2000 : 10 * (int)Math.Floor(DateTime.UtcNow.Year / 10d);
return baseYear + shortYear;
if(futureYear.HasValue)
{
var referenceYear = 1900 + parsed.ExpirationYearShort;
while(referenceYear < futureYear.Value)
{
referenceYear += 10;
}

return referenceYear;
}

var currentYear = DateTime.UtcNow.Year;
if (parsed.ExpirationYearShortLength > 1)
{
// we are given a double digit year
return 2000 + parsed.ExpirationYearShort;
}

var baseYear = ((int)Math.Round(currentYear / 10.0)) * 10 + parsed.ExpirationYearShort;
while (baseYear < currentYear)
{
baseYear += 10;
}
return baseYear;
}
}
}
45 changes: 40 additions & 5 deletions Tests/Common/SymbolRepresentationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,48 @@ public void GenerateFutureTickerExpiringInNextMonth(string ticker, int year, int
Assert.AreEqual(expectedValue, result);
}

[TestCase("VXZ2", 2012, 19)]
[TestCase("VXZ2", null, 21)]
public void GenerateFutureSymbolFromTickerMissingDecadeInfo(string ticker, int? futureYear, int day)
[TestCase("CLU0", 2008, "2010-08-20")]
[TestCase("CLU1", 2008, "2011-08-22")]
[TestCase("CLU2", 2008, "2012-08-21")]
[TestCase("CLU8", 2008, "2008-08-20")]
[TestCase("CLU9", 2008, "2009-08-20")]
public void GenerateFutureSymbolFromTickerKnownYearSingleDigit(string ticker, int futureYear, DateTime expectedExpiration)
{
var result = SymbolRepresentation.ParseFutureSymbol(ticker, futureYear);
// When the future year is not provided, we have an ambiguous case (2002, 2012, 1902, etc) and default current decade 2020
Assert.AreEqual(new DateTime(futureYear ?? 2022, 12, day), result.ID.Date.Date);
Assert.AreEqual(expectedExpiration, result.ID.Date.Date);
}

[TestCase("CLU20", 2020, "2020-08-20")]
[TestCase("CLU21", 2020, "2021-08-20")]
[TestCase("CLU22", 2020, "2022-08-22")]
[TestCase("CLU28", 2020, "2028-08-22")]
[TestCase("CLU29", 2020, "2029-08-21")]
public void GenerateFutureSymbolFromTickerUnknownYearSingleDigit(string ticker, int futureYear, DateTime expectedExpiration)
{
var result = SymbolRepresentation.ParseFutureSymbol(ticker, futureYear);
Assert.AreEqual(expectedExpiration, result.ID.Date.Date);
}

[TestCase("CLU20", "2020-08-20")]
[TestCase("CLU21", "2021-08-20")]
[TestCase("CLU22", "2022-08-22")]
[TestCase("CLU28", "2028-08-22")]
[TestCase("CLU29", "2029-08-21")]
public void GenerateFutureSymbolFromTickerUnknownYearSingleDigit(string ticker, DateTime expectedExpiration)
{
var result = SymbolRepresentation.ParseFutureSymbol(ticker);
Assert.AreEqual(expectedExpiration, result.ID.Date.Date);
}

[TestCase("CLU0", "2030-08-20")]
[TestCase("CLU1", "2031-08-20")]
[TestCase("CLU2", "2032-08-20")]
[TestCase("CLU8", "2028-08-22")]
[TestCase("CLU9", "2029-08-21")]
public void GenerateFutureSymbolFromTickerUnknownYearDoubleDigit(string ticker, DateTime expectedExpiration)
{
var result = SymbolRepresentation.ParseFutureSymbol(ticker);
Assert.AreEqual(expectedExpiration, result.ID.Date.Date);
}

[TestCase("NQZ23")]
Expand Down

0 comments on commit bf2f310

Please sign in to comment.