/*
* 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;
}
}
}