Files
quantconnect--lean/Tests/Common/SymbolRepresentationTests.cs
2026-07-13 13:02:50 +08:00

325 lines
19 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 NUnit.Framework;
using QuantConnect.Securities;
using Python.Runtime;
using System.Globalization;
namespace QuantConnect.Tests.Common
{
[TestFixture]
public class SymbolRepresentationTests
{
[Test]
public void OptionSymbolAliasMatchesOSI()
{
const string expected = @"MSFT 060318C00047500";
var result = SymbolRepresentation.GenerateOptionTickerOSI("MSFT", OptionRight.Call, 47.50m, new DateTime(2006, 03, 18));
Assert.AreEqual(expected, result);
}
[TestCase("SPXW 230111C02400000", SecurityType.IndexOption, OptionStyle.European, "SPXW", "SPX", "SPX", 2400.00, "2023-01-11")]
[TestCase("SPY 230111C02400000", SecurityType.Option, OptionStyle.American, "SPY", "SPY", "SPY", 2400.00, "2023-01-11")]
[TestCase("GOOG 160318C00320000", SecurityType.Option, OptionStyle.American, "GOOCV", "GOOCV", "GOOG", 320.00, "2016-03-18")]
[TestCase("AAPL240614C00100000", SecurityType.Option, OptionStyle.American, "AAPL", "AAPL", "AAPL", 100.00, "2024-06-14")]
[TestCase("MSFT240614C00150000", SecurityType.Option, OptionStyle.American, "MSFT", "MSFT", "MSFT", 150.00, "2024-06-14")]
[TestCase("AMZN 220630C01000000", SecurityType.Option, OptionStyle.American, "AMZN", "AMZN", "AMZN", 1000.00, "2022-06-30")]
[TestCase("NFLX 230122P00250000", SecurityType.Option, OptionStyle.American, "NFLX", "NFLX", "NFLX", 250.00, "2023-01-22")]
[TestCase("TSLA 240815C00775000", SecurityType.Option, OptionStyle.American, "TSLA", "TSLA", "TSLA", 775.00, "2024-08-15")]
[TestCase("V 231211P00220000", SecurityType.Option, OptionStyle.American, "V", "V", "V", 220.00, "2023-12-11")]
[TestCase("JPM 240501C00130750", SecurityType.Option, OptionStyle.American, "JPM", "JPM", "JPM", 130.75, "2024-05-01")]
[TestCase("IBM 250212P00145000", SecurityType.Option, OptionStyle.American, "IBM", "IBM", "IBM", 145.00, "2025-02-12")]
[TestCase("DIS 230630C00075000", SecurityType.Option, OptionStyle.American, "DIS", "DIS", "DIS", 75.00, "2023-06-30")]
[TestCase("ORCL 231030C00065000", SecurityType.Option, OptionStyle.American, "ORCL", "ORCL", "ORCL", 65.00, "2023-10-30")]
[TestCase("CSCO 230501P00045000", SecurityType.Option, OptionStyle.American, "CSCO", "CSCO", "CSCO", 45.00, "2023-05-01")]
[TestCase("DAX 250715C01000000", SecurityType.IndexOption, OptionStyle.European, "DAX", "DAX", "DAX", 1000.00, "2025-07-15")]
[TestCase("FTSE 230122C00750000", SecurityType.IndexOption, OptionStyle.European, "FTSE", "FTSE", "FTSE", 750.00, "2023-01-22")]
[TestCase("ES20H20 200320P03290000", SecurityType.FutureOption, OptionStyle.American, "ES20H20", "ES", "ES20H20", 3290.00, "2020-03-20")]
[TestCase("BRK.B 260206C00495000", SecurityType.Option, OptionStyle.American, "BRK.B", "BRK.B", "BRK.B", 495.00, "2026-02-06")]
public void ParseOptionTickerOSI(string optionStr, SecurityType securityType, OptionStyle optionStyle,
string expectedTargetOptionTicker, string expectedUnderlyingTicker, string expectedUnderlyingMappedTicker,
decimal expectedStrikePrice, string expectedDate)
{
var result = SymbolRepresentation.ParseOptionTickerOSI(optionStr, securityType, optionStyle, Market.USA);
Assert.AreEqual(expectedTargetOptionTicker, result.ID.Symbol);
Assert.AreEqual(optionStr, result.Value);
Assert.AreEqual(expectedUnderlyingTicker, result.Underlying.ID.Symbol);
Assert.AreEqual(expectedUnderlyingMappedTicker, result.Underlying.Value);
Assert.AreEqual(securityType, result.ID.SecurityType);
Assert.AreEqual(optionStyle, result.ID.OptionStyle);
Assert.AreEqual(expectedStrikePrice, result.ID.StrikePrice);
Assert.AreEqual(DateTime.ParseExact(expectedDate, "yyyy-MM-dd", CultureInfo.InvariantCulture), result.ID.Date);
}
[Test]
public void OptionSymbolAliasAddsPaddingSpaceForSixOrMoreCharacterSymbols()
{
const string expected = @"ABCDEF 060318C00047500";
var symbol = SymbolRepresentation.GenerateOptionTickerOSI("ABCDEF", OptionRight.Call, 47.50m, new DateTime(2006, 03, 18));
Assert.AreEqual(expected, symbol);
}
[TestCase("SPXW", OptionRight.Call, 2400.00, "230111", "SPXW230111C02400000")]
[TestCase("SPY", OptionRight.Put, 250.00, "230615", "SPY230615P00250000")]
[TestCase("AAPL", OptionRight.Call, 100.00, "240614", "AAPL240614C00100000")]
[TestCase("MSFT", OptionRight.Put, 150.00, "240614", "MSFT240614P00150000")]
[TestCase("GOOG", OptionRight.Call, 2000.00, "211231", "GOOG211231C02000000")]
[TestCase("AMZN", OptionRight.Put, 3500.50, "250101", "AMZN250101P03500500")]
[TestCase("NFLX", OptionRight.Call, 500.25, "221201", "NFLX221201C00500250")]
[TestCase("TSLA", OptionRight.Put, 725.00, "241231", "TSLA241231P00725000")]
[TestCase("V", OptionRight.Call, 220.00, "230420", "V230420C00220000")]
[TestCase("JPM", OptionRight.Put, 130.75, "230710", "JPM230710P00130750")]
[TestCase("IBM", OptionRight.Call, 145.00, "250212", "IBM250212C00145000")]
[TestCase("BABA", OptionRight.Put, 88.88, "240508", "BABA240508P00088880")]
public void GenerateOptionTickerOSICompact_ValidInputs(string underlying, OptionRight right, decimal strikePrice, string date, string expected)
{
DateTime expiration = DateTime.ParseExact(date, "yyMMdd", System.Globalization.CultureInfo.InvariantCulture);
string result = SymbolRepresentation.GenerateOptionTickerOSICompact(underlying, right, strikePrice, expiration);
Assert.AreEqual(expected, result);
}
[Test]
public void ParseFuturesTickers()
{
// ticker contains two digits year of expiration, no day expiration
var result = SymbolRepresentation.ParseFutureTicker("EX20");
Assert.AreEqual(result.Underlying, "E");
Assert.AreEqual(result.ExpirationDay, 1);
Assert.AreEqual(result.ExpirationYearShort, 20);
Assert.AreEqual(result.ExpirationMonth, 11); // November
// ticker contains one digit year of expiration, no day expiration
result = SymbolRepresentation.ParseFutureTicker("ABCZ1");
Assert.AreEqual(result.Underlying, "ABC");
Assert.AreEqual(result.ExpirationDay, 1);
Assert.AreEqual(result.ExpirationYearShort, 1);
Assert.AreEqual(result.ExpirationMonth, 12); // December
// ticker contains two digits year of expiration, with day expiration
result = SymbolRepresentation.ParseFutureTicker("ED01X20");
Assert.AreEqual(result.Underlying, "ED");
Assert.AreEqual(result.ExpirationDay, 1);
Assert.AreEqual(result.ExpirationYearShort, 20);
Assert.AreEqual(result.ExpirationMonth, 11); // November
// ticker contains one digit year of expiration, with day expiration
result = SymbolRepresentation.ParseFutureTicker("ABC11Z1");
Assert.AreEqual(result.Underlying, "ABC");
Assert.AreEqual(result.ExpirationDay, 11);
Assert.AreEqual(result.ExpirationYearShort, 1);
Assert.AreEqual(result.ExpirationMonth, 12); // December
}
[Test]
public void GenerateFuturesTickers()
{
const string ticker = @"ED";
var result = SymbolRepresentation.GenerateFutureTicker(ticker, new DateTime(2016, 12, 12));
// ticker contains two digits year of expiration
Assert.AreEqual(result, "ED12Z16");
// ticker contains one digit year of expiration
result = SymbolRepresentation.GenerateFutureTicker(ticker, new DateTime(2016, 12, 12), false);
Assert.AreEqual(result, "ED12Z6");
}
[Test]
public void GenerateFuturesTickersBackAndForth()
{
const string expected = @"ED01Z16";
var result = SymbolRepresentation.ParseFutureTicker(expected);
var ticker = SymbolRepresentation.GenerateFutureTicker(result.Underlying, new DateTime(2000 + result.ExpirationYearShort, result.ExpirationMonth, result.ExpirationDay));
Assert.AreEqual(expected, ticker);
}
[Test]
public void ParseInvalidFuturesTickers()
{
var result = SymbolRepresentation.ParseFutureTicker("invalid");
Assert.AreEqual(result, null);
}
[Test]
public void GenerateOptionTickerWithIndexOptionReturnsCorrectTicker()
{
// Expected ticker for the option contract
var expected = "SPXW2104A3800";
var underlying = Symbols.SPX;
// Create the option contract (IndexOption) with specific parameters
var option = Symbol.CreateOption(
underlying,
"SPXW",
Market.USA,
OptionStyle.European,
OptionRight.Call,
3800m,
new DateTime(2021, 1, 04));
var result = SymbolRepresentation.GenerateOptionTicker(option);
// Assert that the result matches the expected ticker
Assert.AreEqual(expected, result);
}
[TestCase(Futures.Energy.ArgusLLSvsWTIArgusTradeMonth, 2017, 1, 29, "AE529G7", false)] // Previous month
[TestCase(Futures.Energy.ArgusPropaneSaudiAramco, 2017, 1, 29, "A9N29G7", false)] // Previous month
[TestCase(Futures.Energy.BrentCrude, 2017, 1, 29, "B29H7", false)] // Second prior month
[TestCase(Futures.Energy.BrentLastDayFinancial, 2017, 1, 29, "BZ29H7", false)] // Second prior month
[TestCase(Futures.Energy.CrudeOilWTI, 2017, 11, 20, "CL20Z17", true)] // Prior month
[TestCase(Futures.Energy.Gasoline, 2017, 11, 20, "RB20Z17", true)] // Prior month
[TestCase(Futures.Energy.HeatingOil, 2017, 11, 20, "HO20Z17", true)] // Prior month
[TestCase(Futures.Energy.MarsArgusVsWTITradeMonth, 2017, 11, 20, "AYV20Z17", true)] // Prior month
[TestCase(Futures.Energy.NaturalGas, 2017, 11, 20, "NG20Z17", true)] // Prior month
[TestCase(Futures.Energy.NaturalGasHenryHubLastDayFinancial, 2017, 11, 20, "HH20Z17", true)] // Prior month
[TestCase(Futures.Energy.NaturalGasHenryHubPenultimateFinancial, 2017, 11, 20, "HP20Z17", true)] // Prior month
[TestCase(Futures.Energy.WTIHoustonArgusVsWTITradeMonth, 2017, 11, 20, "HTT20Z17", true)] // Prior month
[TestCase(Futures.Energy.WTIHoustonCrudeOil, 2017, 11, 20, "HCL20Z17", true)] // Prior month
[TestCase(Futures.Softs.Sugar11, 2017, 11, 20, "SB20Z17", true)] // Prior month
[TestCase(Futures.Softs.Sugar11CME, 2017, 11, 20, "YO20Z17", true)] // Prior month
public void GenerateFutureTickerExpiringInPreviousMonth(string underlying, int year, int month, int day, string ticker, bool doubleDigitsYear)
{
// CL Dec17 expires in Nov17
var result = SymbolRepresentation.GenerateFutureTicker(underlying, new DateTime(year, month, day), doubleDigitsYear);
Assert.AreEqual(ticker, result);
}
[TestCase(Futures.Energy.ArgusLLSvsWTIArgusTradeMonth, 2016, 12, 29, "AE529F7", false, true)] // Previous month
[TestCase(Futures.Energy.ArgusPropaneSaudiAramco, 2016, 12, 29, "A9N29F7", false, true)] // Previous month
[TestCase(Futures.Energy.BrentCrude, 2016, 11, 29, "B29F7", false, true)] // Second prior month
[TestCase(Futures.Energy.BrentCrude, 2016, 12, 29, "B29G7", false, true)] // Second prior month
[TestCase(Futures.Energy.BrentLastDayFinancial, 2016, 11, 29, "BZ29F7", false, true)] // Second prior month
[TestCase(Futures.Energy.BrentLastDayFinancial, 2016, 12, 29, "BZ29G7", false, true)] // Second prior month
[TestCase(Futures.Energy.CrudeOilWTI, 2016, 12, 20, "CL20F17", true, true)] // Prior month
[TestCase(Futures.Energy.Gasoline, 2016, 12, 20, "RB20F17", true, true)] // Prior month
[TestCase(Futures.Energy.HeatingOil, 2016, 12, 20, "HO20F17", true, true)] // Prior month
[TestCase(Futures.Energy.MarsArgusVsWTITradeMonth, 2016, 12, 20, "AYV20F17", true, true)] // Prior month
[TestCase(Futures.Energy.NaturalGas, 2016, 12, 20, "NG20F17", true, true)] // Prior month
[TestCase(Futures.Energy.NaturalGasHenryHubLastDayFinancial, 2016, 12, 20, "HH20F17", true, true)] // Prior month
[TestCase(Futures.Energy.NaturalGasHenryHubPenultimateFinancial, 2016, 12, 20, "HP20F17", true, true)] // Prior month
[TestCase(Futures.Energy.WTIHoustonArgusVsWTITradeMonth, 2016, 12, 20, "HTT20F17", true, true)] // Prior month
[TestCase(Futures.Energy.WTIHoustonCrudeOil, 2016, 12, 20, "HCL20F17", true, true)] // Prior month
[TestCase(Futures.Softs.Sugar11, 2016, 12, 20, "SB20F17", true, true)] // Prior month
[TestCase(Futures.Softs.Sugar11CME, 2016, 12, 20, "YO20F17", true, true)] // Prior month
[TestCase(Futures.Softs.Sugar11CME, 2016, 12, 20, "YOF17", true, false)] // Prior month
[TestCase(Futures.Softs.Sugar11CME, 2016, 12, 20, "YOF7", false, false)] // Prior month
[TestCase(Futures.Indices.SP500EMini, 2010, 3, 1, "ESH0", false, false)]
public void GenerateFutureTickerExpiringInPreviousMonthOverYearBoundary(string underlying, int year, int month, int day, string ticker, bool doubleDigitsYear, bool includeExpirationDate)
{
// CL Dec17 expires in Nov17
var result = SymbolRepresentation.GenerateFutureTicker(underlying, new DateTime(year, month, day), doubleDigitsYear, includeExpirationDate);
Assert.AreEqual(ticker, result);
}
[TestCase("ABC", 2017, 12, 20, "ABC20Z17", true)] // Generic contract (i.e. expires current month
public void GenerateFutureTickerExpiringInCurrentMonth(string underlying, int year, int month, int day, string ticker, bool doubleDigitsYear)
{
// CL Dec17 expires in Nov17
var result = SymbolRepresentation.GenerateFutureTicker(underlying, new DateTime(year, month, day), doubleDigitsYear);
Assert.AreEqual(ticker, result);
}
[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);
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")]
public void GenerateFutureSymbolFromTickerUnknownYear(string ticker)
{
var result = SymbolRepresentation.ParseFutureSymbol(ticker);
// When the future year is not provided, we have an ambiguous case (1923 or 2023) and default 2000
Assert.AreEqual(new DateTime(2023, 12, 15), result.ID.Date.Date);
}
[TestCase("NQZ99")]
public void GenerateFutureSymbolFromTickerExpiringBefore2000(string ticker)
{
var result = SymbolRepresentation.ParseFutureSymbol(ticker, 1999);
Assert.AreEqual(new DateTime(1999, 12, 17), result.ID.Date.Date);
}
[TestCase("PROPANE_NON_LDH_MONT_BELVIEU", QuantConnect.Securities.Futures.Energy.PropaneNonLDHMontBelvieu)]
[TestCase("ARGUS_PROPANE_FAR_EAST_INDEX_BALMO", QuantConnect.Securities.Futures.Energy.ArgusPropaneFarEastIndexBALMO)]
[TestCase("GASOLINE", QuantConnect.Securities.Futures.Energy.Gasoline)]
[TestCase("NATURAL_GAS", QuantConnect.Securities.Futures.Energy.NaturalGas)]
public void FutureEnergySymbolsWorkInPythonWithPEP8(string FutureEnergyName, string expectedFutureEnergyValue)
{
using (Py.GIL())
{
var pythonModule = PyModule.FromString("testModule", @$"
from AlgorithmImports import *
def return_futures_energy():
return Futures.Energy.{FutureEnergyName};
");
dynamic pythonFunction = pythonModule.GetAttr("return_futures_energy");
var futureEnergyValue = pythonFunction();
Assert.AreEqual(expectedFutureEnergyValue, (futureEnergyValue as PyObject).GetAndDispose<string>());
}
}
}
}