Lean  $LEAN_TAG$
SecurityCurrencyConversion.cs
1 
2 /*
3  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
4  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16 */
17 
18 using System;
19 using System.Collections.Generic;
20 using System.Linq;
21 using QuantConnect.Util;
22 
24 {
25  /// <summary>
26  /// Provides an implementation of <see cref="ICurrencyConversion"/> to find and use multi-leg currency conversions
27  /// </summary>
29  {
30  /// <summary>
31  /// Class that holds the information of a single step in a multi-leg currency conversion
32  /// </summary>
33  private class Step
34  {
35  /// <summary>
36  /// The security used in this conversion step
37  /// </summary>
38  public Security RateSecurity { get; }
39 
40  /// <summary>
41  /// Whether the price of the security must be inverted in the conversion
42  /// </summary>
43  public bool Inverted { get; }
44 
45  /// <summary>
46  /// Initializes a new instance of the <see cref="Step"/> class
47  /// </summary>
48  /// <param name="rateSecurity">The security to use in this currency conversion step</param>
49  /// <param name="inverted">Whether the price of the security should be inverted in the conversion</param>
50  public Step(Security rateSecurity, bool inverted)
51  {
52  RateSecurity = rateSecurity;
53  Inverted = inverted;
54  }
55  }
56 
57  private readonly List<Step> _steps;
58 
59  private decimal _conversionRate;
60  private bool _conversionRateNeedsUpdate;
61 
62  /// <summary>
63  /// Event fired when the conversion rate is updated
64  /// </summary>
65  public event EventHandler<decimal> ConversionRateUpdated;
66 
67  /// <summary>
68  /// The currency this conversion converts from
69  /// </summary>
70  public string SourceCurrency { get; }
71 
72  /// <summary>
73  /// The currency this conversion converts to
74  /// </summary>
75  public string DestinationCurrency { get; }
76 
77  /// <summary>
78  /// The current conversion rate
79  /// </summary>
80  public decimal ConversionRate
81  {
82  get
83  {
84  if (_conversionRateNeedsUpdate)
85  {
86  var newConversionRate = 1m;
87  var stepWithoutDataFound = false;
88 
89  _steps.ForEach(step =>
90  {
91  if (stepWithoutDataFound)
92  {
93  return;
94  }
95 
96  var lastData = step.RateSecurity.GetLastData();
97  if (lastData == null || lastData.Price == 0m)
98  {
99  newConversionRate = 0m;
100  stepWithoutDataFound = true;
101  return;
102  }
103 
104  if (step.Inverted)
105  {
106  newConversionRate /= lastData.Price;
107  }
108  else
109  {
110  newConversionRate *= lastData.Price;
111  }
112  });
113 
114  _conversionRateNeedsUpdate = false;
115  _conversionRate = newConversionRate;
116  ConversionRateUpdated?.Invoke(this, _conversionRate);
117  }
118 
119  return _conversionRate;
120  }
121  set
122  {
123  if (_conversionRate != value)
124  {
125  // only update if there was actually one
126  _conversionRate = value;
127  _conversionRateNeedsUpdate = false;
128  ConversionRateUpdated?.Invoke(this, _conversionRate);
129 
130  }
131  }
132  }
133 
134  /// <summary>
135  /// The securities which the conversion rate is based on
136  /// </summary>
137  public IEnumerable<Security> ConversionRateSecurities => _steps.Select(step => step.RateSecurity);
138 
139  /// <summary>
140  /// Initializes a new instance of the <see cref="SecurityCurrencyConversion"/> class.
141  /// This constructor is intentionally private as only <see cref="LinearSearch"/> is supposed to create it.
142  /// </summary>
143  /// <param name="sourceCurrency">The currency this conversion converts from</param>
144  /// <param name="destinationCurrency">The currency this conversion converts to</param>
145  /// <param name="steps">The steps between sourceCurrency and destinationCurrency</param>
146  private SecurityCurrencyConversion(string sourceCurrency, string destinationCurrency, List<Step> steps)
147  {
148  SourceCurrency = sourceCurrency;
149  DestinationCurrency = destinationCurrency;
150 
151  _steps = steps;
152  }
153 
154  /// <summary>
155  /// Signals an updates to the internal conversion rate based on the latest data.
156  /// It will set the conversion rate as potentially outdated so it gets re-calculated.
157  /// </summary>
158  public void Update()
159  {
160  _conversionRateNeedsUpdate = true;
161  }
162 
163  /// <summary>
164  /// Finds a conversion between two currencies by looking through all available 1 and 2-leg options
165  /// </summary>
166  /// <param name="sourceCurrency">The currency to convert from</param>
167  /// <param name="destinationCurrency">The currency to convert to</param>
168  /// <param name="existingSecurities">The securities which are already added to the algorithm</param>
169  /// <param name="potentialSymbols">The symbols to consider, may overlap with existingSecurities</param>
170  /// <param name="makeNewSecurity">The function to call when a symbol becomes part of the conversion, must return the security that will provide price data about the symbol</param>
171  /// <returns>A new <see cref="SecurityCurrencyConversion"/> instance representing the conversion from sourceCurrency to destinationCurrency</returns>
172  /// <exception cref="ArgumentException">Thrown when no conversion from sourceCurrency to destinationCurrency can be found</exception>
174  string sourceCurrency,
175  string destinationCurrency,
176  IList<Security> existingSecurities,
177  IEnumerable<Symbol> potentialSymbols,
178  Func<Symbol, Security> makeNewSecurity)
179  {
180  var allSymbols = existingSecurities.Select(sec => sec.Symbol).Concat(potentialSymbols)
182  .ToList();
183 
184  var securitiesBySymbol = existingSecurities.Aggregate(new Dictionary<Symbol, Security>(),
185  (mapping, security) =>
186  {
187  if (!mapping.ContainsKey(security.Symbol))
188  {
189  mapping[security.Symbol] = security;
190  }
191 
192  return mapping;
193  });
194 
195  // Search for 1 leg conversions
196  foreach (var potentialConversionRateSymbol in allSymbols)
197  {
198  var leg1Match = potentialConversionRateSymbol.ComparePair(sourceCurrency, destinationCurrency);
199  if (leg1Match == CurrencyPairUtil.Match.NoMatch)
200  {
201  continue;
202  }
203  var inverted = leg1Match == CurrencyPairUtil.Match.InverseMatch;
204 
205  return new SecurityCurrencyConversion(sourceCurrency, destinationCurrency, new List<Step>(1)
206  {
207  CreateStep(potentialConversionRateSymbol, inverted, securitiesBySymbol, makeNewSecurity)
208  });
209  }
210 
211  // Search for 2 leg conversions
212  foreach (var potentialConversionRateSymbol1 in allSymbols)
213  {
214  var middleCurrency = potentialConversionRateSymbol1.CurrencyPairDual(sourceCurrency);
215  if (middleCurrency == null)
216  {
217  continue;
218  }
219 
220  foreach (var potentialConversionRateSymbol2 in allSymbols)
221  {
222  var leg2Match = potentialConversionRateSymbol2.ComparePair(middleCurrency, destinationCurrency);
223  if (leg2Match == CurrencyPairUtil.Match.NoMatch)
224  {
225  continue;
226  }
227  var secondStepInverted = leg2Match == CurrencyPairUtil.Match.InverseMatch;
228 
229  var steps = new List<Step>(2);
230 
231  // Step 1
232  string baseCurrency;
233  string quoteCurrency;
234 
236  potentialConversionRateSymbol1,
237  out baseCurrency,
238  out quoteCurrency);
239 
240  steps.Add(CreateStep(potentialConversionRateSymbol1,
241  sourceCurrency == quoteCurrency,
242  securitiesBySymbol,
243  makeNewSecurity));
244 
245  // Step 2
246  steps.Add(CreateStep(potentialConversionRateSymbol2,
247  secondStepInverted,
248  securitiesBySymbol,
249  makeNewSecurity));
250 
251  return new SecurityCurrencyConversion(sourceCurrency, destinationCurrency, steps);
252  }
253  }
254 
255  throw new ArgumentException(
256  $"No conversion path found between source currency {sourceCurrency} and destination currency {destinationCurrency}");
257  }
258 
259  /// <summary>
260  /// Creates a new step
261  /// </summary>
262  /// <param name="symbol">The symbol of the step</param>
263  /// <param name="inverted">Whether the step is inverted or not</param>
264  /// <param name="existingSecurities">The existing securities, which are preferred over creating new ones</param>
265  /// <param name="makeNewSecurity">The function to call when a new security must be created</param>
266  private static Step CreateStep(
267  Symbol symbol,
268  bool inverted,
269  IDictionary<Symbol, Security> existingSecurities,
270  Func<Symbol, Security> makeNewSecurity)
271  {
272  Security security;
273  if (existingSecurities.TryGetValue(symbol, out security))
274  {
275  return new Step(security, inverted);
276  }
277 
278  return new Step(makeNewSecurity(symbol), inverted);
279  }
280  }
281 }