Lean  $LEAN_TAG$
CurrencyPairUtil.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
21 
22 namespace QuantConnect.Util
23 {
24  /// <summary>
25  /// Utility methods for decomposing and comparing currency pairs
26  /// </summary>
27  public static class CurrencyPairUtil
28  {
29  private static readonly Lazy<SymbolPropertiesDatabase> SymbolPropertiesDatabase =
30  new Lazy<SymbolPropertiesDatabase>(Securities.SymbolPropertiesDatabase.FromDataFolder);
31 
32  private static readonly int[][] PotentialStableCoins = { [1, 3], [1, 2], [0, 3], [0, 2] };
33 
34  /// <summary>
35  /// Tries to decomposes the specified currency pair into a base and quote currency provided as out parameters
36  /// </summary>
37  /// <param name="currencyPair">The input currency pair to be decomposed</param>
38  /// <param name="baseCurrency">The output base currency</param>
39  /// <param name="quoteCurrency">The output quote currency</param>
40  /// <returns>True if was able to decompose the currency pair</returns>
41  public static bool TryDecomposeCurrencyPair(Symbol currencyPair, out string baseCurrency, out string quoteCurrency)
42  {
43  baseCurrency = null;
44  quoteCurrency = null;
45 
46  if (!IsValidSecurityType(currencyPair?.SecurityType, throwException: false))
47  {
48  return false;
49  }
50 
51  try
52  {
53  DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency);
54  return true;
55  }
56  catch
57  {
58  // ignored
59  }
60 
61  return false;
62  }
63 
64  /// <summary>
65  /// Decomposes the specified currency pair into a base and quote currency provided as out parameters
66  /// </summary>
67  /// <param name="currencyPair">The input currency pair to be decomposed</param>
68  /// <param name="baseCurrency">The output base currency</param>
69  /// <param name="quoteCurrency">The output quote currency</param>
70  /// <param name="defaultQuoteCurrency">Optionally can provide a default quote currency</param>
71  public static void DecomposeCurrencyPair(Symbol currencyPair, out string baseCurrency, out string quoteCurrency, string defaultQuoteCurrency = Currencies.USD)
72  {
73  IsValidSecurityType(currencyPair?.SecurityType, throwException: true);
74  var securityType = currencyPair.SecurityType;
75 
76  if (securityType == SecurityType.Forex)
77  {
78  Forex.DecomposeCurrencyPair(currencyPair.Value, out baseCurrency, out quoteCurrency);
79  return;
80  }
81 
82  var symbolProperties = SymbolPropertiesDatabase.Value.GetSymbolProperties(
83  currencyPair.ID.Market,
84  currencyPair,
85  currencyPair.SecurityType,
86  defaultQuoteCurrency);
87 
88  if (securityType == SecurityType.Cfd)
89  {
90  Cfd.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency);
91  }
92  else
93  {
94  Crypto.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency);
95  }
96  }
97 
98  /// <summary>
99  /// Checks whether a symbol is decomposable into a base and a quote currency
100  /// </summary>
101  /// <param name="currencyPair">The pair to check for</param>
102  /// <returns>True if the pair can be decomposed into base and quote currencies, false if not</returns>
103  public static bool IsForexDecomposable(string currencyPair)
104  {
105  return !string.IsNullOrEmpty(currencyPair) && currencyPair.Length == 6;
106  }
107 
108  /// <summary>
109  /// Checks whether a symbol is decomposable into a base and a quote currency
110  /// </summary>
111  /// <param name="currencyPair">The pair to check for</param>
112  /// <returns>True if the pair can be decomposed into base and quote currencies, false if not</returns>
113  public static bool IsDecomposable(Symbol currencyPair)
114  {
115  if (currencyPair == null)
116  {
117  return false;
118  }
119 
120  if (currencyPair.SecurityType == SecurityType.Forex)
121  {
122  return currencyPair.Value.Length == 6;
123  }
124 
125  if (currencyPair.SecurityType == SecurityType.Cfd || currencyPair.SecurityType == SecurityType.Crypto || currencyPair.SecurityType == SecurityType.CryptoFuture)
126  {
127  var symbolProperties = SymbolPropertiesDatabase.Value.GetSymbolProperties(
128  currencyPair.ID.Market,
129  currencyPair,
130  currencyPair.SecurityType,
131  Currencies.USD);
132 
133  return currencyPair.Value.EndsWith(symbolProperties.QuoteCurrency);
134  }
135 
136  return false;
137  }
138 
139  /// <summary>
140  /// You have currencyPair AB and one known symbol (A or B). This function returns the other symbol (B or A).
141  /// </summary>
142  /// <param name="currencyPair">Currency pair AB</param>
143  /// <param name="knownSymbol">Known part of the currencyPair (either A or B)</param>
144  /// <returns>The other part of currencyPair (either B or A), or null if known symbol is not part of currencyPair</returns>
145  public static string CurrencyPairDual(this Symbol currencyPair, string knownSymbol)
146  {
147  string baseCurrency;
148  string quoteCurrency;
149 
150  DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency);
151 
152  return CurrencyPairDual(baseCurrency, quoteCurrency, knownSymbol);
153  }
154 
155  /// <summary>
156  /// You have currencyPair AB and one known symbol (A or B). This function returns the other symbol (B or A).
157  /// </summary>
158  /// <param name="baseCurrency">The base currency of the currency pair</param>
159  /// <param name="quoteCurrency">The quote currency of the currency pair</param>
160  /// <param name="knownSymbol">Known part of the currencyPair (either A or B)</param>
161  /// <returns>The other part of currencyPair (either B or A), or null if known symbol is not part of the currency pair</returns>
162  public static string CurrencyPairDual(string baseCurrency, string quoteCurrency, string knownSymbol)
163  {
164  if (baseCurrency == knownSymbol)
165  {
166  return quoteCurrency;
167  }
168 
169  if (quoteCurrency == knownSymbol)
170  {
171  return baseCurrency;
172  }
173 
174  return null;
175  }
176 
177  /// <summary>
178  /// Represents the relation between two currency pairs
179  /// </summary>
180  public enum Match
181  {
182  /// <summary>
183  /// The two currency pairs don't match each other normally nor when one is reversed
184  /// </summary>
185  NoMatch,
186 
187  /// <summary>
188  /// The two currency pairs match each other exactly
189  /// </summary>
190  ExactMatch,
191 
192  /// <summary>
193  /// The two currency pairs are the inverse of each other
194  /// </summary>
195  InverseMatch
196  }
197 
198  /// <summary>
199  /// Returns how two currency pairs are related to each other
200  /// </summary>
201  /// <param name="pairA">The first pair</param>
202  /// <param name="baseCurrencyB">The base currency of the second pair</param>
203  /// <param name="quoteCurrencyB">The quote currency of the second pair</param>
204  /// <returns>The <see cref="Match"/> member that represents the relation between the two pairs</returns>
205  public static Match ComparePair(this Symbol pairA, string baseCurrencyB, string quoteCurrencyB)
206  {
207  if (!TryDecomposeCurrencyPair(pairA, out var baseCurrencyA, out var quoteCurrencyA))
208  {
209  return Match.NoMatch;
210  }
211 
212  // Check for a stablecoin between the currencies
213  var currencies = new string[] { baseCurrencyA, quoteCurrencyA, baseCurrencyB, quoteCurrencyB };
214  var isThereAnyMatch = false;
215 
216  // Compute all the potential stablecoins
217  foreach (var pair in PotentialStableCoins)
218  {
219  if (Currencies.IsStableCoinWithoutPair(currencies[pair[0]] + currencies[pair[1]], pairA.ID.Market)
220  || Currencies.IsStableCoinWithoutPair(currencies[pair[1]] + currencies[pair[0]], pairA.ID.Market))
221  {
222  // If there's a stablecoin between them, assign to currency in pair A the value
223  // of the currency in pair B
224  currencies[pair[0]] = currencies[pair[1]];
225  isThereAnyMatch = true;
226  }
227  }
228 
229  string pairAValue = isThereAnyMatch ? string.Concat(currencies[0], "||", currencies[1]) : string.Concat(baseCurrencyA, "||", quoteCurrencyA);
230 
231  var directPair = string.Concat(baseCurrencyB, "||", quoteCurrencyB);
232  if (pairAValue == directPair)
233  {
234  return Match.ExactMatch;
235  }
236 
237  var inversePair = string.Concat(quoteCurrencyB, "||", baseCurrencyB);
238  if (pairAValue == inversePair)
239  {
240  return Match.InverseMatch;
241  }
242 
243  return Match.NoMatch;
244  }
245 
246  public static bool IsValidSecurityType(SecurityType? securityType, bool throwException)
247  {
248  if (securityType == null)
249  {
250  if (throwException)
251  {
252  throw new ArgumentException("Currency pair must not be null");
253  }
254  return false;
255  }
256 
257  if (securityType != SecurityType.Forex &&
258  securityType != SecurityType.Cfd &&
259  securityType != SecurityType.Crypto &&
260  securityType != SecurityType.CryptoFuture)
261  {
262  if (throwException)
263  {
264  throw new ArgumentException($"Unsupported security type: {securityType}");
265  }
266  return false;
267  }
268 
269  return true;
270  }
271  }
272 }