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  /// <summary>
33  /// Tries to decomposes the specified currency pair into a base and quote currency provided as out parameters
34  /// </summary>
35  /// <param name="currencyPair">The input currency pair to be decomposed</param>
36  /// <param name="baseCurrency">The output base currency</param>
37  /// <param name="quoteCurrency">The output quote currency</param>
38  /// <returns>True if was able to decompose the currency pair</returns>
39  public static bool TryDecomposeCurrencyPair(Symbol currencyPair, out string baseCurrency, out string quoteCurrency)
40  {
41  baseCurrency = null;
42  quoteCurrency = null;
43 
44  if (!IsValidSecurityType(currencyPair?.SecurityType, throwException: false))
45  {
46  return false;
47  }
48 
49  try
50  {
51  DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency);
52  return true;
53  }
54  catch
55  {
56  // ignored
57  }
58 
59  return false;
60  }
61 
62  /// <summary>
63  /// Decomposes the specified currency pair into a base and quote currency provided as out parameters
64  /// </summary>
65  /// <param name="currencyPair">The input currency pair to be decomposed</param>
66  /// <param name="baseCurrency">The output base currency</param>
67  /// <param name="quoteCurrency">The output quote currency</param>
68  /// <param name="defaultQuoteCurrency">Optionally can provide a default quote currency</param>
69  public static void DecomposeCurrencyPair(Symbol currencyPair, out string baseCurrency, out string quoteCurrency, string defaultQuoteCurrency = Currencies.USD)
70  {
71  IsValidSecurityType(currencyPair?.SecurityType, throwException: true);
72  var securityType = currencyPair.SecurityType;
73 
74  if (securityType == SecurityType.Forex)
75  {
76  Forex.DecomposeCurrencyPair(currencyPair.Value, out baseCurrency, out quoteCurrency);
77  return;
78  }
79 
80  var symbolProperties = SymbolPropertiesDatabase.Value.GetSymbolProperties(
81  currencyPair.ID.Market,
82  currencyPair,
83  currencyPair.SecurityType,
84  defaultQuoteCurrency);
85 
86  if (securityType == SecurityType.Cfd)
87  {
88  Cfd.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency);
89  }
90  else
91  {
92  Crypto.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency);
93  }
94  }
95 
96  /// <summary>
97  /// Checks whether a symbol is decomposable into a base and a quote currency
98  /// </summary>
99  /// <param name="currencyPair">The pair to check for</param>
100  /// <returns>True if the pair can be decomposed into base and quote currencies, false if not</returns>
101  public static bool IsForexDecomposable(string currencyPair)
102  {
103  return !string.IsNullOrEmpty(currencyPair) && currencyPair.Length == 6;
104  }
105 
106  /// <summary>
107  /// Checks whether a symbol is decomposable into a base and a quote currency
108  /// </summary>
109  /// <param name="currencyPair">The pair to check for</param>
110  /// <returns>True if the pair can be decomposed into base and quote currencies, false if not</returns>
111  public static bool IsDecomposable(Symbol currencyPair)
112  {
113  if (currencyPair == null)
114  {
115  return false;
116  }
117 
118  if (currencyPair.SecurityType == SecurityType.Forex)
119  {
120  return currencyPair.Value.Length == 6;
121  }
122 
123  if (currencyPair.SecurityType == SecurityType.Cfd || currencyPair.SecurityType == SecurityType.Crypto || currencyPair.SecurityType == SecurityType.CryptoFuture)
124  {
125  var symbolProperties = SymbolPropertiesDatabase.Value.GetSymbolProperties(
126  currencyPair.ID.Market,
127  currencyPair,
128  currencyPair.SecurityType,
129  Currencies.USD);
130 
131  return currencyPair.Value.EndsWith(symbolProperties.QuoteCurrency);
132  }
133 
134  return false;
135  }
136 
137  /// <summary>
138  /// You have currencyPair AB and one known symbol (A or B). This function returns the other symbol (B or A).
139  /// </summary>
140  /// <param name="currencyPair">Currency pair AB</param>
141  /// <param name="knownSymbol">Known part of the currencyPair (either A or B)</param>
142  /// <returns>The other part of currencyPair (either B or A), or null if known symbol is not part of currencyPair</returns>
143  public static string CurrencyPairDual(this Symbol currencyPair, string knownSymbol)
144  {
145  string baseCurrency;
146  string quoteCurrency;
147 
148  DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency);
149 
150  return CurrencyPairDual(baseCurrency, quoteCurrency, knownSymbol);
151  }
152 
153  /// <summary>
154  /// You have currencyPair AB and one known symbol (A or B). This function returns the other symbol (B or A).
155  /// </summary>
156  /// <param name="baseCurrency">The base currency of the currency pair</param>
157  /// <param name="quoteCurrency">The quote currency of the currency pair</param>
158  /// <param name="knownSymbol">Known part of the currencyPair (either A or B)</param>
159  /// <returns>The other part of currencyPair (either B or A), or null if known symbol is not part of the currency pair</returns>
160  public static string CurrencyPairDual(string baseCurrency, string quoteCurrency, string knownSymbol)
161  {
162  if (baseCurrency == knownSymbol)
163  {
164  return quoteCurrency;
165  }
166 
167  if (quoteCurrency == knownSymbol)
168  {
169  return baseCurrency;
170  }
171 
172  return null;
173  }
174 
175  /// <summary>
176  /// Represents the relation between two currency pairs
177  /// </summary>
178  public enum Match
179  {
180  /// <summary>
181  /// The two currency pairs don't match each other normally nor when one is reversed
182  /// </summary>
183  NoMatch,
184 
185  /// <summary>
186  /// The two currency pairs match each other exactly
187  /// </summary>
188  ExactMatch,
189 
190  /// <summary>
191  /// The two currency pairs are the inverse of each other
192  /// </summary>
193  InverseMatch
194  }
195 
196  /// <summary>
197  /// Returns how two currency pairs are related to each other
198  /// </summary>
199  /// <param name="pairA">The first pair</param>
200  /// <param name="baseCurrencyB">The base currency of the second pair</param>
201  /// <param name="quoteCurrencyB">The quote currency of the second pair</param>
202  /// <returns>The <see cref="Match"/> member that represents the relation between the two pairs</returns>
203  public static Match ComparePair(this Symbol pairA, string baseCurrencyB, string quoteCurrencyB)
204  {
205  var pairAValue = pairA.ID.Symbol;
206 
207  // Check for a stablecoin between the currencies
208  if (TryDecomposeCurrencyPair(pairA, out var baseCurrencyA, out var quoteCurrencyA))
209  {
210  var currencies = new string[] { baseCurrencyA, quoteCurrencyA, baseCurrencyB, quoteCurrencyB};
211  var isThereAnyMatch = false;
212 
213  // Compute all the potential stablecoins
214  var potentialStableCoins = new int[][]
215  {
216  new int[]{ 1, 3 },
217  new int[]{ 1, 2 },
218  new int[]{ 0, 3 },
219  new int[]{ 0, 2 }
220  };
221 
222  foreach(var pair in potentialStableCoins)
223  {
224  if (Currencies.IsStableCoinWithoutPair(currencies[pair[0]] + currencies[pair[1]], pairA.ID.Market)
225  || Currencies.IsStableCoinWithoutPair(currencies[pair[1]] + currencies[pair[0]], pairA.ID.Market))
226  {
227  // If there's a stablecoin between them, assign to currency in pair A the value
228  // of the currency in pair B
229  currencies[pair[0]] = currencies[pair[1]];
230  isThereAnyMatch = true;
231  }
232  }
233 
234  // Update the value of pairAValue if there was a match
235  if (isThereAnyMatch)
236  {
237  pairAValue = currencies[0] + currencies[1];
238  }
239  }
240 
241  if (pairAValue == baseCurrencyB + quoteCurrencyB)
242  {
243  return Match.ExactMatch;
244  }
245 
246  if (pairAValue == quoteCurrencyB + baseCurrencyB)
247  {
248  return Match.InverseMatch;
249  }
250 
251  return Match.NoMatch;
252  }
253 
254  private static bool IsValidSecurityType(SecurityType? securityType, bool throwException)
255  {
256  if (securityType == null)
257  {
258  if (throwException)
259  {
260  throw new ArgumentException("Currency pair must not be null");
261  }
262  return false;
263  }
264 
265  if (securityType != SecurityType.Forex &&
266  securityType != SecurityType.Cfd &&
267  securityType != SecurityType.Crypto &&
268  securityType != SecurityType.CryptoFuture)
269  {
270  if (throwException)
271  {
272  throw new ArgumentException($"Unsupported security type: {securityType}");
273  }
274  return false;
275  }
276 
277  return true;
278  }
279  }
280 }