/* * 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 QuantConnect.Securities; using QuantConnect.Securities.Cfd; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; namespace QuantConnect.Util { /// /// Utility methods for decomposing and comparing currency pairs /// public static class CurrencyPairUtil { private static readonly Lazy SymbolPropertiesDatabase = new Lazy(Securities.SymbolPropertiesDatabase.FromDataFolder); private static readonly int[][] PotentialStableCoins = { [1, 3], [1, 2], [0, 3], [0, 2] }; /// /// Tries to decomposes the specified currency pair into a base and quote currency provided as out parameters /// /// The input currency pair to be decomposed /// The output base currency /// The output quote currency /// True if was able to decompose the currency pair public static bool TryDecomposeCurrencyPair(Symbol currencyPair, out string baseCurrency, out string quoteCurrency) { baseCurrency = null; quoteCurrency = null; if (!IsValidSecurityType(currencyPair?.SecurityType, throwException: false)) { return false; } try { DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency); return true; } catch { // ignored } return false; } /// /// Decomposes the specified currency pair into a base and quote currency provided as out parameters /// /// The input currency pair to be decomposed /// The output base currency /// The output quote currency /// Optionally can provide a default quote currency public static void DecomposeCurrencyPair(Symbol currencyPair, out string baseCurrency, out string quoteCurrency, string defaultQuoteCurrency = Currencies.USD) { IsValidSecurityType(currencyPair?.SecurityType, throwException: true); var securityType = currencyPair.SecurityType; if (securityType == SecurityType.Forex) { Forex.DecomposeCurrencyPair(currencyPair.Value, out baseCurrency, out quoteCurrency); return; } var symbolProperties = SymbolPropertiesDatabase.Value.GetSymbolProperties( currencyPair.ID.Market, currencyPair, currencyPair.SecurityType, defaultQuoteCurrency); if (securityType == SecurityType.Cfd) { Cfd.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency); } else { Crypto.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency); } } /// /// Checks whether a symbol is decomposable into a base and a quote currency /// /// The pair to check for /// True if the pair can be decomposed into base and quote currencies, false if not public static bool IsForexDecomposable(string currencyPair) { return !string.IsNullOrEmpty(currencyPair) && currencyPair.Length == 6; } /// /// Checks whether a symbol is decomposable into a base and a quote currency /// /// The pair to check for /// True if the pair can be decomposed into base and quote currencies, false if not public static bool IsDecomposable(Symbol currencyPair) { if (currencyPair == null) { return false; } if (currencyPair.SecurityType == SecurityType.Forex) { return currencyPair.Value.Length == 6; } if (currencyPair.SecurityType == SecurityType.Cfd || currencyPair.SecurityType == SecurityType.Crypto || currencyPair.SecurityType == SecurityType.CryptoFuture) { var symbolProperties = SymbolPropertiesDatabase.Value.GetSymbolProperties( currencyPair.ID.Market, currencyPair, currencyPair.SecurityType, Currencies.USD); return currencyPair.Value.EndsWith(symbolProperties.QuoteCurrency); } return false; } /// /// You have currencyPair AB and one known symbol (A or B). This function returns the other symbol (B or A). /// /// Currency pair AB /// Known part of the currencyPair (either A or B) /// The other part of currencyPair (either B or A), or null if known symbol is not part of currencyPair public static string CurrencyPairDual(this Symbol currencyPair, string knownSymbol) { string baseCurrency; string quoteCurrency; DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency); return CurrencyPairDual(baseCurrency, quoteCurrency, knownSymbol); } /// /// You have currencyPair AB and one known symbol (A or B). This function returns the other symbol (B or A). /// /// The base currency of the currency pair /// The quote currency of the currency pair /// Known part of the currencyPair (either A or B) /// The other part of currencyPair (either B or A), or null if known symbol is not part of the currency pair public static string CurrencyPairDual(string baseCurrency, string quoteCurrency, string knownSymbol) { if (baseCurrency == knownSymbol) { return quoteCurrency; } if (quoteCurrency == knownSymbol) { return baseCurrency; } return null; } /// /// Represents the relation between two currency pairs /// public enum Match { /// /// The two currency pairs don't match each other normally nor when one is reversed /// NoMatch, /// /// The two currency pairs match each other exactly /// ExactMatch, /// /// The two currency pairs are the inverse of each other /// InverseMatch } /// /// Returns how two currency pairs are related to each other /// /// The first pair /// The base currency of the second pair /// The quote currency of the second pair /// The member that represents the relation between the two pairs public static Match ComparePair(this Symbol pairA, string baseCurrencyB, string quoteCurrencyB) { if (!TryDecomposeCurrencyPair(pairA, out var baseCurrencyA, out var quoteCurrencyA)) { return Match.NoMatch; } // Check for a stablecoin between the currencies var currencies = new string[] { baseCurrencyA, quoteCurrencyA, baseCurrencyB, quoteCurrencyB }; var isThereAnyMatch = false; // Compute all the potential stablecoins foreach (var pair in PotentialStableCoins) { if (Currencies.IsStableCoinWithoutPair(currencies[pair[0]] + currencies[pair[1]], pairA.ID.Market) || Currencies.IsStableCoinWithoutPair(currencies[pair[1]] + currencies[pair[0]], pairA.ID.Market)) { // If there's a stablecoin between them, assign to currency in pair A the value // of the currency in pair B currencies[pair[0]] = currencies[pair[1]]; isThereAnyMatch = true; } } string pairAValue = isThereAnyMatch ? string.Concat(currencies[0], "||", currencies[1]) : string.Concat(baseCurrencyA, "||", quoteCurrencyA); var directPair = string.Concat(baseCurrencyB, "||", quoteCurrencyB); if (pairAValue == directPair) { return Match.ExactMatch; } var inversePair = string.Concat(quoteCurrencyB, "||", baseCurrencyB); if (pairAValue == inversePair) { return Match.InverseMatch; } return Match.NoMatch; } public static bool IsValidSecurityType(SecurityType? securityType, bool throwException) { if (securityType == null) { if (throwException) { throw new ArgumentException("Currency pair must not be null"); } return false; } if (securityType != SecurityType.Forex && securityType != SecurityType.Cfd && securityType != SecurityType.Crypto && securityType != SecurityType.CryptoFuture) { if (throwException) { throw new ArgumentException($"Unsupported security type: {securityType}"); } return false; } return true; } } }