Files
2026-07-13 13:02:50 +08:00

273 lines
11 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 QuantConnect.Securities;
using QuantConnect.Securities.Cfd;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Crypto;
namespace QuantConnect.Util
{
/// <summary>
/// Utility methods for decomposing and comparing currency pairs
/// </summary>
public static class CurrencyPairUtil
{
private static readonly Lazy<SymbolPropertiesDatabase> SymbolPropertiesDatabase =
new Lazy<SymbolPropertiesDatabase>(Securities.SymbolPropertiesDatabase.FromDataFolder);
private static readonly int[][] PotentialStableCoins = { [1, 3], [1, 2], [0, 3], [0, 2] };
/// <summary>
/// Tries to decomposes the specified currency pair into a base and quote currency provided as out parameters
/// </summary>
/// <param name="currencyPair">The input currency pair to be decomposed</param>
/// <param name="baseCurrency">The output base currency</param>
/// <param name="quoteCurrency">The output quote currency</param>
/// <returns>True if was able to decompose the currency pair</returns>
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;
}
/// <summary>
/// Decomposes the specified currency pair into a base and quote currency provided as out parameters
/// </summary>
/// <param name="currencyPair">The input currency pair to be decomposed</param>
/// <param name="baseCurrency">The output base currency</param>
/// <param name="quoteCurrency">The output quote currency</param>
/// <param name="defaultQuoteCurrency">Optionally can provide a default quote currency</param>
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);
}
}
/// <summary>
/// Checks whether a symbol is decomposable into a base and a quote currency
/// </summary>
/// <param name="currencyPair">The pair to check for</param>
/// <returns>True if the pair can be decomposed into base and quote currencies, false if not</returns>
public static bool IsForexDecomposable(string currencyPair)
{
return !string.IsNullOrEmpty(currencyPair) && currencyPair.Length == 6;
}
/// <summary>
/// Checks whether a symbol is decomposable into a base and a quote currency
/// </summary>
/// <param name="currencyPair">The pair to check for</param>
/// <returns>True if the pair can be decomposed into base and quote currencies, false if not</returns>
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;
}
/// <summary>
/// You have currencyPair AB and one known symbol (A or B). This function returns the other symbol (B or A).
/// </summary>
/// <param name="currencyPair">Currency pair AB</param>
/// <param name="knownSymbol">Known part of the currencyPair (either A or B)</param>
/// <returns>The other part of currencyPair (either B or A), or null if known symbol is not part of currencyPair</returns>
public static string CurrencyPairDual(this Symbol currencyPair, string knownSymbol)
{
string baseCurrency;
string quoteCurrency;
DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency);
return CurrencyPairDual(baseCurrency, quoteCurrency, knownSymbol);
}
/// <summary>
/// You have currencyPair AB and one known symbol (A or B). This function returns the other symbol (B or A).
/// </summary>
/// <param name="baseCurrency">The base currency of the currency pair</param>
/// <param name="quoteCurrency">The quote currency of the currency pair</param>
/// <param name="knownSymbol">Known part of the currencyPair (either A or B)</param>
/// <returns>The other part of currencyPair (either B or A), or null if known symbol is not part of the currency pair</returns>
public static string CurrencyPairDual(string baseCurrency, string quoteCurrency, string knownSymbol)
{
if (baseCurrency == knownSymbol)
{
return quoteCurrency;
}
if (quoteCurrency == knownSymbol)
{
return baseCurrency;
}
return null;
}
/// <summary>
/// Represents the relation between two currency pairs
/// </summary>
public enum Match
{
/// <summary>
/// The two currency pairs don't match each other normally nor when one is reversed
/// </summary>
NoMatch,
/// <summary>
/// The two currency pairs match each other exactly
/// </summary>
ExactMatch,
/// <summary>
/// The two currency pairs are the inverse of each other
/// </summary>
InverseMatch
}
/// <summary>
/// Returns how two currency pairs are related to each other
/// </summary>
/// <param name="pairA">The first pair</param>
/// <param name="baseCurrencyB">The base currency of the second pair</param>
/// <param name="quoteCurrencyB">The quote currency of the second pair</param>
/// <returns>The <see cref="Match"/> member that represents the relation between the two pairs</returns>
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;
}
}
}