/* * 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 NUnit.Framework; using QuantConnect.Brokerages; using QuantConnect.Securities; using System; using System.IO; using System.Linq; namespace QuantConnect.Tests.Brokerages { [TestFixture] public class SymbolPropertiesDatabaseSymbolMapperTests { [TestCaseSource(nameof(BrokerageSymbols))] public void ReturnsCryptoSecurityType(string market, string brokerageSymbol, string leanSymbol) { var mapper = new SymbolPropertiesDatabaseSymbolMapper(market); var symbol = mapper.GetLeanSymbol(brokerageSymbol, SecurityType.Crypto, market); Assert.AreEqual(leanSymbol, symbol.Value); Assert.AreEqual(market, symbol.ID.Market); } [TestCaseSource(nameof(BrokerageSymbols))] public void ReturnsCorrectLeanSymbol(string market, string brokerageSymbol, string leanSymbol) { var mapper = new SymbolPropertiesDatabaseSymbolMapper(market); var symbol = mapper.GetLeanSymbol(brokerageSymbol, SecurityType.Crypto, market); Assert.AreEqual(leanSymbol, symbol.Value); Assert.AreEqual(SecurityType.Crypto, symbol.ID.SecurityType); Assert.AreEqual(market, symbol.ID.Market); } [TestCaseSource(nameof(CryptoSymbols))] public void ReturnsCorrectBrokerageSymbol(Symbol symbol, string brokerageSymbol) { var mapper = new SymbolPropertiesDatabaseSymbolMapper(symbol.ID.Market); Assert.AreEqual(brokerageSymbol, mapper.GetBrokerageSymbol(symbol)); } [TestCaseSource(nameof(CurrencyPairs))] public void ThrowsOnCurrencyPairs(string market, string brokerageSymbol) { var mapper = new SymbolPropertiesDatabaseSymbolMapper(market); Assert.Throws(() => mapper.GetBrokerageSecurityType(brokerageSymbol)); } [TestCase(Market.Coinbase)] [TestCase(Market.Bitfinex)] [TestCase(Market.Binance)] public void ThrowsOnNullOrEmptySymbols(string market) { var mapper = new SymbolPropertiesDatabaseSymbolMapper(market); string ticker = null; Assert.IsFalse(mapper.IsKnownBrokerageSymbol(ticker)); Assert.Throws(() => mapper.GetLeanSymbol(ticker, SecurityType.Crypto, market)); Assert.Throws(() => mapper.GetBrokerageSecurityType(ticker)); ticker = ""; Assert.IsFalse(mapper.IsKnownBrokerageSymbol(ticker)); Assert.Throws(() => mapper.GetLeanSymbol(ticker, SecurityType.Crypto, market)); Assert.Throws(() => mapper.GetBrokerageSecurityType(ticker)); Assert.Throws(() => mapper.GetBrokerageSymbol(Symbol.Create(ticker, SecurityType.Crypto, market))); } [TestCaseSource(nameof(UnknownSymbols))] public void ThrowsOnUnknownSymbols(string brokerageSymbol, SecurityType type, string market) { var mapper = new SymbolPropertiesDatabaseSymbolMapper(market); Assert.Throws(() => mapper.GetLeanSymbol(brokerageSymbol, type, market)); Assert.Throws(() => mapper.GetBrokerageSymbol(Symbol.Create(brokerageSymbol.Replace("-", ""), type, market))); } [TestCaseSource(nameof(UnknownSecurityType))] public void ThrowsOnUnknownSecurityType(string brokerageSymbol, SecurityType type, string market) { var mapper = new SymbolPropertiesDatabaseSymbolMapper(market); Assert.Throws(() => mapper.GetLeanSymbol(brokerageSymbol, type, market)); Assert.Throws(() => mapper.GetBrokerageSymbol(Symbol.Create(brokerageSymbol.Replace("-", ""), type, market))); } [TestCaseSource(nameof(UnknownMarket))] public void ThrowsOnUnknownMarket(string brokerageSymbol, SecurityType type, string market) { var mapper = new SymbolPropertiesDatabaseSymbolMapper(market); Assert.Throws(() => mapper.GetLeanSymbol(brokerageSymbol, type, market)); Assert.Throws(() => mapper.GetBrokerageSymbol(Symbol.Create(brokerageSymbol.Replace("-", ""), type, market))); } [TestCase("GetLeanSymbol")] [TestCase("GetBrokerageSymbol")] [TestCase("IsKnownLeanSymbol")] [TestCase("GetBrokerageSecurityType")] [TestCase("IsKnownBrokerageSymbol")] public void SymbolMapperRefreshesMappingsPeriodically(string methodName) { // Test symbol that is not in the SPDB const string market = Market.Binance; const string newSymbol = "CUSTOMCRIPTOUSDT"; const SecurityType securityType = SecurityType.Crypto; var mapper = new SymbolPropertiesDatabaseSymbolMapper(market); var testSymbol = Symbol.Create(newSymbol, securityType, market); // Verify symbol doesn't exist initially using the appropriate method switch (methodName) { case "GetLeanSymbol": Assert.Throws(() => mapper.GetLeanSymbol(newSymbol, securityType, market)); break; case "GetBrokerageSymbol": Assert.Throws(() => mapper.GetBrokerageSymbol(testSymbol)); break; case "IsKnownLeanSymbol": Assert.IsFalse(mapper.IsKnownLeanSymbol(testSymbol)); break; case "GetBrokerageSecurityType": Assert.Throws(() => mapper.GetBrokerageSecurityType(newSymbol)); break; case "IsKnownBrokerageSymbol": Assert.IsFalse(mapper.IsKnownBrokerageSymbol(newSymbol)); break; } // Add manually the new symbol to the SPDB var path = Path.Combine(Globals.DataFolder, "symbol-properties", "symbol-properties-database.csv"); var newEntry = $"{Environment.NewLine}binance,CUSTOMCRIPTOUSDT,crypto,CUSTOMCRIPTOUSDT,USDT,1,0.00000001,0.01,CUSTOMCRIPTOUSDT,0.01"; File.AppendAllText(path, newEntry); try { // Reset the SPDB SymbolPropertiesDatabase.Reset(); // Simulate that 15 minutes have passed since the last reload var lastReloadField = typeof(SymbolPropertiesDatabaseSymbolMapper).GetField("_lastReloadTime", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); lastReloadField.SetValue(mapper, DateTime.UtcNow.AddMinutes(-16)); // Now verify each method can find the symbol switch (methodName) { case "GetLeanSymbol": var symbol = mapper.GetLeanSymbol(newSymbol, securityType, market); Assert.IsNotNull(symbol); Assert.AreEqual(newSymbol, symbol.Value); Assert.AreEqual(securityType, symbol.SecurityType); Assert.AreEqual(market, symbol.ID.Market); break; case "GetBrokerageSymbol": Assert.AreEqual(newSymbol, mapper.GetBrokerageSymbol(testSymbol)); break; case "IsKnownLeanSymbol": Assert.IsTrue(mapper.IsKnownLeanSymbol(testSymbol)); break; case "GetBrokerageSecurityType": Assert.AreEqual(securityType, mapper.GetBrokerageSecurityType(newSymbol)); break; case "IsKnownBrokerageSymbol": Assert.IsTrue(mapper.IsKnownBrokerageSymbol(newSymbol)); break; } } finally { // Clean up the SPDB var lines = File.ReadAllLines(path).Where(l => !l.Contains(newSymbol)).ToArray(); File.WriteAllText(path, string.Join(Environment.NewLine, lines)); SymbolPropertiesDatabase.Reset(); } } private static TestCaseData[] BrokerageSymbols => new[] { new TestCaseData(Market.Coinbase, "ETH-USD", "ETHUSD"), new TestCaseData(Market.Coinbase, "ETH-BTC", "ETHBTC"), new TestCaseData(Market.Coinbase, "BTC-USD", "BTCUSD"), new TestCaseData(Market.Coinbase, "BTC-USDC", "BTCUSDC"), new TestCaseData(Market.Coinbase, "ATOM-USD", "ATOMUSD"), new TestCaseData(Market.Bitfinex, "tBTCUSD", "BTCUSD"), new TestCaseData(Market.Bitfinex, "tBTCUST", "BTCUSDT"), new TestCaseData(Market.Bitfinex, "tETHUSD", "ETHUSD"), new TestCaseData(Market.Bitfinex, "tADAUST", "ADAUSDT"), new TestCaseData(Market.Bitfinex, "tCOMP:USD", "COMPUSD"), new TestCaseData(Market.Bitfinex, "tCOMP:UST", "COMPUSDT"), new TestCaseData(Market.Binance, "ETHUSDT", "ETHUSDT"), new TestCaseData(Market.Binance, "ETHBTC", "ETHBTC"), new TestCaseData(Market.Binance, "BTCUSDT", "BTCUSDT"), new TestCaseData(Market.Binance, "ATOMTUSD", "ATOMTUSD"), new TestCaseData(Market.Binance, "ATOMUSDC", "ATOMUSDC"), new TestCaseData(Market.Binance, "ATOMUSDT", "ATOMUSDT") }; private static TestCaseData[] CryptoSymbols => new[] { new TestCaseData(Symbol.Create("ETHUSD", SecurityType.Crypto, Market.Coinbase), "ETH-USD"), new TestCaseData(Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Coinbase), "BTC-USD"), new TestCaseData(Symbol.Create("ETHBTC", SecurityType.Crypto, Market.Coinbase), "ETH-BTC"), new TestCaseData(Symbol.Create("BTCUSDC", SecurityType.Crypto, Market.Coinbase), "BTC-USDC"), new TestCaseData(Symbol.Create("ATOMUSD", SecurityType.Crypto, Market.Coinbase), "ATOM-USD"), new TestCaseData(Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Bitfinex), "tBTCUSD"), new TestCaseData(Symbol.Create("BTCUSDT", SecurityType.Crypto, Market.Bitfinex), "tBTCUST"), new TestCaseData(Symbol.Create("ETHUSD", SecurityType.Crypto, Market.Bitfinex), "tETHUSD"), new TestCaseData(Symbol.Create("ADAUSDT", SecurityType.Crypto, Market.Bitfinex), "tADAUST"), new TestCaseData(Symbol.Create("COMPUSD", SecurityType.Crypto, Market.Bitfinex), "tCOMP:USD"), new TestCaseData(Symbol.Create("COMPUSDT", SecurityType.Crypto, Market.Bitfinex), "tCOMP:UST"), new TestCaseData(Symbol.Create("ETHUSDT", SecurityType.Crypto, Market.Binance), "ETHUSDT"), new TestCaseData(Symbol.Create("ETHBTC", SecurityType.Crypto, Market.Binance), "ETHBTC"), new TestCaseData(Symbol.Create("BTCUSDT", SecurityType.Crypto, Market.Binance), "BTCUSDT"), new TestCaseData(Symbol.Create("ATOMTUSD", SecurityType.Crypto, Market.Binance), "ATOMTUSD"), new TestCaseData(Symbol.Create("ATOMUSDC", SecurityType.Crypto, Market.Binance), "ATOMUSDC"), new TestCaseData(Symbol.Create("ATOMUSDT", SecurityType.Crypto, Market.Binance), "ATOMUSDT") }; private static TestCaseData[] CurrencyPairs => new[] { new TestCaseData(Market.Coinbase, ""), new TestCaseData(Market.Coinbase, "EURUSD"), new TestCaseData(Market.Coinbase, "GBP-USD"), new TestCaseData(Market.Coinbase, "USD-JPY"), new TestCaseData(Market.Bitfinex, ""), new TestCaseData(Market.Bitfinex, "EURUSD"), new TestCaseData(Market.Bitfinex, "GBP-USD"), new TestCaseData(Market.Bitfinex, "USD-JPY"), new TestCaseData(Market.Binance, ""), new TestCaseData(Market.Binance, "EURUSD"), new TestCaseData(Market.Binance, "GBPUSD"), new TestCaseData(Market.Binance, "USDJPY") }; private static TestCaseData[] UnknownSymbols => new[] { new TestCaseData("AAA-BBB", SecurityType.Crypto, Market.Coinbase), new TestCaseData("USD-BTC", SecurityType.Crypto, Market.Coinbase), new TestCaseData("EUR-USD", SecurityType.Crypto, Market.Coinbase), new TestCaseData("GBP-USD", SecurityType.Crypto, Market.Coinbase), new TestCaseData("USD-JPY", SecurityType.Crypto, Market.Coinbase), new TestCaseData("BTC-ETH", SecurityType.Crypto, Market.Coinbase), new TestCaseData("USDC-BTC", SecurityType.Crypto, Market.Coinbase), new TestCaseData("USD-BTC", SecurityType.Crypto, Market.Bitfinex), new TestCaseData("EUR-USD", SecurityType.Crypto, Market.Bitfinex), new TestCaseData("GBP-USD", SecurityType.Crypto, Market.Bitfinex), new TestCaseData("USD-JPY", SecurityType.Crypto, Market.Bitfinex), new TestCaseData("BTC-ETH", SecurityType.Crypto, Market.Bitfinex), new TestCaseData("USDC-BTC", SecurityType.Crypto, Market.Bitfinex), new TestCaseData("AAABBB", SecurityType.Crypto, Market.Binance), new TestCaseData("USDBTC", SecurityType.Crypto, Market.Binance), new TestCaseData("EURUSD", SecurityType.Crypto, Market.Binance), new TestCaseData("GBPUSD", SecurityType.Crypto, Market.Binance), new TestCaseData("USDJPY", SecurityType.Crypto, Market.Binance), new TestCaseData("BTCETH", SecurityType.Crypto, Market.Binance), new TestCaseData("BTCUSD", SecurityType.Crypto, Market.Binance) }; private static TestCaseData[] UnknownSecurityType => new[] { new TestCaseData("BTC-USD", SecurityType.Forex, Market.Coinbase) }; private static TestCaseData[] UnknownMarket => new[] { new TestCaseData("ETH-USD", SecurityType.Crypto, Market.USA) }; } }