/// <summary> /// Ensures that we have a data feed to convert this currency into the base currency. /// This will add a <see cref="SubscriptionDataConfig"/> and create a <see cref="Security"/> at the lowest resolution if one is not found. /// </summary> /// <param name="securities">The security manager</param> /// <param name="subscriptions">The subscription manager used for searching and adding subscriptions</param> /// <param name="marketMap">The market map that decides which market the new security should be in</param> /// <param name="changes">Will be used to consume <see cref="SecurityChanges.AddedSecurities"/></param> /// <param name="securityService">Will be used to create required new <see cref="Security"/></param> /// <param name="accountCurrency">The account currency</param> /// <param name="defaultResolution">The default resolution to use for the internal subscriptions</param> /// <returns>Returns the added <see cref="SubscriptionDataConfig"/>, otherwise null</returns> public SubscriptionDataConfig EnsureCurrencyDataFeed(SecurityManager securities, SubscriptionManager subscriptions, IReadOnlyDictionary <SecurityType, string> marketMap, SecurityChanges changes, ISecurityService securityService, string accountCurrency, Resolution defaultResolution = Resolution.Minute ) { // this gets called every time we add securities using universe selection, // so must of the time we've already resolved the value and don't need to again if (ConversionRateSecurity != null) { return(null); } if (Symbol == accountCurrency) { ConversionRateSecurity = null; _isBaseCurrency = true; ConversionRate = 1.0m; return(null); } // we require a security that converts this into the base currency string normal = Symbol + accountCurrency; string invert = accountCurrency + Symbol; var securitiesToSearch = securities.Select(kvp => kvp.Value) .Concat(changes.AddedSecurities) .Where(s => s.Type == SecurityType.Forex || s.Type == SecurityType.Cfd || s.Type == SecurityType.Crypto); foreach (var security in securitiesToSearch) { if (security.Symbol.Value == normal) { ConversionRateSecurity = security; return(null); } if (security.Symbol.Value == invert) { ConversionRateSecurity = security; _invertRealTimePrice = true; return(null); } } // if we've made it here we didn't find a security, so we'll need to add one // Create a SecurityType to Market mapping with the markets from SecurityManager members var markets = securities.Select(x => x.Key) .GroupBy(x => x.SecurityType) .ToDictionary(x => x.Key, y => y.Select(symbol => symbol.ID.Market).ToHashSet()); if (markets.ContainsKey(SecurityType.Cfd) && !markets.ContainsKey(SecurityType.Forex)) { markets.Add(SecurityType.Forex, markets[SecurityType.Cfd]); } if (markets.ContainsKey(SecurityType.Forex) && !markets.ContainsKey(SecurityType.Cfd)) { markets.Add(SecurityType.Cfd, markets[SecurityType.Forex]); } var forexEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Forex, marketMap, markets); var cfdEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Cfd, marketMap, markets); var cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Crypto, marketMap, markets); var potentialEntries = forexEntries .Concat(cfdEntries) .Concat(cryptoEntries) .ToList(); if (!potentialEntries.Any(x => Symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) || Symbol == x.Value.QuoteCurrency)) { // currency not found in any tradeable pair Log.Error($"No tradeable pair was found for currency {Symbol}, conversion rate to account currency ({accountCurrency}) will be set to zero."); ConversionRateSecurity = null; ConversionRate = 0m; return(null); } var potentials = potentialEntries .Select(x => QuantConnect.Symbol.Create(x.Key.Symbol, x.Key.SecurityType, x.Key.Market)); var minimumResolution = subscriptions.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(defaultResolution).Min(); foreach (var symbol in potentials) { if (symbol.Value == normal || symbol.Value == invert) { _invertRealTimePrice = symbol.Value == invert; var securityType = symbol.ID.SecurityType; // use the first subscription defined in the subscription manager var type = subscriptions.LookupSubscriptionConfigDataTypes(securityType, minimumResolution, false).First(); var objectType = type.Item1; var tickType = type.Item2; // set this as an internal feed so that the data doesn't get sent into the algorithm's OnData events var config = subscriptions.SubscriptionDataConfigService.Add(symbol, minimumResolution, fillForward: true, extendedMarketHours: false, isInternalFeed: true, subscriptionDataTypes: new List <Tuple <Type, TickType> > { new Tuple <Type, TickType>(objectType, tickType) }).First(); var security = securityService.CreateSecurity(symbol, config, addToSymbolCache: false); ConversionRateSecurity = security; securities.Add(config.Symbol, security); Log.Trace($"Cash.EnsureCurrencyDataFeed(): Adding {symbol.Value} for cash {Symbol} currency feed"); return(config); } } // Special case for crypto markets without direct pairs (They wont be found by the above) // This allows us to add cash for "StableCoins" that are 1-1 with our account currency without needing a conversion security. // Check out the StableCoinsWithoutPairs static var for those that are missing their 1-1 conversion pairs if (Currencies.StableCoinsWithoutPairs.Contains(QuantConnect.Symbol.Create(normal, SecurityType.Crypto, marketMap[SecurityType.Crypto]))) { ConversionRateSecurity = null; ConversionRate = 1.0m; return(null); } // if this still hasn't been set then it's an error condition throw new ArgumentException($"In order to maintain cash in {Symbol} you are required to add a " + $"subscription for Forex pair {Symbol}{accountCurrency} or {accountCurrency}{Symbol}" ); }
/// <summary> /// Ensures that we have a data feed to convert this currency into the base currency. /// This will add a <see cref="SubscriptionDataConfig"/> and create a <see cref="Security"/> at the lowest resolution if one is not found. /// </summary> /// <param name="securities">The security manager</param> /// <param name="subscriptions">The subscription manager used for searching and adding subscriptions</param> /// <param name="marketMap">The market map that decides which market the new security should be in</param> /// <param name="changes">Will be used to consume <see cref="SecurityChanges.AddedSecurities"/></param> /// <param name="securityService">Will be used to create required new <see cref="Security"/></param> /// <param name="accountCurrency">The account currency</param> /// <param name="defaultResolution">The default resolution to use for the internal subscriptions</param> /// <returns>Returns the added <see cref="SubscriptionDataConfig"/>, otherwise null</returns> public List <SubscriptionDataConfig> EnsureCurrencyDataFeed(SecurityManager securities, SubscriptionManager subscriptions, IReadOnlyDictionary <SecurityType, string> marketMap, SecurityChanges changes, ISecurityService securityService, string accountCurrency, Resolution defaultResolution = Resolution.Minute ) { // this gets called every time we add securities using universe selection, // so must of the time we've already resolved the value and don't need to again if (CurrencyConversion != null) { return(null); } if (Symbol == accountCurrency) { _isBaseCurrency = true; CurrencyConversion = null; ConversionRate = 1.0m; return(null); } // existing securities var securitiesToSearch = securities.Select(kvp => kvp.Value) .Concat(changes.AddedSecurities) .Where(s => s.Type == SecurityType.Forex || s.Type == SecurityType.Cfd || s.Type == SecurityType.Crypto); // Create a SecurityType to Market mapping with the markets from SecurityManager members var markets = securities.Select(x => x.Key) .GroupBy(x => x.SecurityType) .ToDictionary(x => x.Key, y => y.Select(symbol => symbol.ID.Market).ToHashSet()); if (markets.ContainsKey(SecurityType.Cfd) && !markets.ContainsKey(SecurityType.Forex)) { markets.Add(SecurityType.Forex, markets[SecurityType.Cfd]); } if (markets.ContainsKey(SecurityType.Forex) && !markets.ContainsKey(SecurityType.Cfd)) { markets.Add(SecurityType.Cfd, markets[SecurityType.Forex]); } var forexEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Forex, marketMap, markets); var cfdEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Cfd, marketMap, markets); var cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Crypto, marketMap, markets); var potentialEntries = forexEntries .Concat(cfdEntries) .Concat(cryptoEntries) .ToList(); if (!potentialEntries.Any(x => Symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) || Symbol == x.Value.QuoteCurrency)) { // currency not found in any tradeable pair Log.Error($"No tradeable pair was found for currency {Symbol}, conversion rate to account currency ({accountCurrency}) will be set to zero."); CurrencyConversion = null; ConversionRate = 0m; return(null); } // Special case for crypto markets without direct pairs (They wont be found by the above) // This allows us to add cash for "StableCoins" that are 1-1 with our account currency without needing a conversion security. // Check out the StableCoinsWithoutPairs static var for those that are missing their 1-1 conversion pairs if (marketMap.TryGetValue(SecurityType.Crypto, out var market) && (Currencies.IsStableCoinWithoutPair(Symbol + accountCurrency, market) || Currencies.IsStableCoinWithoutPair(accountCurrency + Symbol, market))) { CurrencyConversion = null; ConversionRate = 1.0m; return(null); } var requiredSecurities = new List <SubscriptionDataConfig>(); var potentials = potentialEntries .Select(x => QuantConnect.Symbol.Create(x.Key.Symbol, x.Key.SecurityType, x.Key.Market)); var minimumResolution = subscriptions.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(defaultResolution).Min(); var makeNewSecurity = new Func <Symbol, Security>(symbol => { var securityType = symbol.ID.SecurityType; // use the first subscription defined in the subscription manager var type = subscriptions.LookupSubscriptionConfigDataTypes(securityType, minimumResolution, false).First(); var objectType = type.Item1; var tickType = type.Item2; // set this as an internal feed so that the data doesn't get sent into the algorithm's OnData events var config = subscriptions.SubscriptionDataConfigService.Add(symbol, minimumResolution, fillForward: true, extendedMarketHours: false, isInternalFeed: true, subscriptionDataTypes: new List <Tuple <Type, TickType> > { new Tuple <Type, TickType>(objectType, tickType) }).First(); var newSecurity = securityService.CreateSecurity(symbol, config, addToSymbolCache: false); Log.Trace($"Cash.EnsureCurrencyDataFeed(): Adding {symbol.Value} for cash {Symbol} currency feed"); securities.Add(symbol, newSecurity); requiredSecurities.Add(config); return(newSecurity); }); CurrencyConversion = SecurityCurrencyConversion.LinearSearch(Symbol, accountCurrency, securitiesToSearch.ToList(), potentials, makeNewSecurity); return(requiredSecurities); }
/// <summary> /// Ensures that we have a data feed to convert this currency into the base currency. /// This will add a subscription at the lowest resolution if one is not found. /// </summary> /// <param name="securities">The security manager</param> /// <param name="subscriptions">The subscription manager used for searching and adding subscriptions</param> /// <param name="marketHoursDatabase">A security exchange hours provider instance used to resolve exchange hours for new subscriptions</param> /// <param name="symbolPropertiesDatabase">A symbol properties database instance</param> /// <param name="marketMap">The market map that decides which market the new security should be in</param> /// <param name="cashBook">The cash book - used for resolving quote currencies for created conversion securities</param> /// <returns>Returns the added currency security if needed, otherwise null</returns> public Security EnsureCurrencyDataFeed(SecurityManager securities, SubscriptionManager subscriptions, MarketHoursDatabase marketHoursDatabase, SymbolPropertiesDatabase symbolPropertiesDatabase, IReadOnlyDictionary <SecurityType, string> marketMap, CashBook cashBook) { if (Symbol == CashBook.AccountCurrency) { SecuritySymbol = QuantConnect.Symbol.Empty; _isBaseCurrency = true; ConversionRate = 1.0m; return(null); } if (subscriptions.Count == 0) { throw new InvalidOperationException("Unable to add cash when no subscriptions are present. Please add subscriptions in the Initialize() method."); } // we require a subscription that converts this into the base currency string normal = Symbol + CashBook.AccountCurrency; string invert = CashBook.AccountCurrency + Symbol; foreach (var config in subscriptions.Subscriptions.Where(config => config.SecurityType == SecurityType.Forex || config.SecurityType == SecurityType.Cfd)) { if (config.Symbol.Value == normal) { SecuritySymbol = config.Symbol; return(null); } if (config.Symbol.Value == invert) { SecuritySymbol = config.Symbol; _invertRealTimePrice = true; return(null); } } // if we've made it here we didn't find a subscription, so we'll need to add one var currencyPairs = Currencies.CurrencyPairs.Select(x => { // allow XAU or XAG to be used as quote currencies, but pairs including them are CFDs var securityType = Symbol.StartsWith("X") ? SecurityType.Cfd : SecurityType.Forex; var market = marketMap[securityType]; return(QuantConnect.Symbol.Create(x, securityType, market)); }); var minimumResolution = subscriptions.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(Resolution.Minute).Min(); var objectType = minimumResolution == Resolution.Tick ? typeof(Tick) : typeof(TradeBar); foreach (var symbol in currencyPairs) { if (symbol.Value == normal || symbol.Value == invert) { _invertRealTimePrice = symbol.Value == invert; var securityType = symbol.ID.SecurityType; var symbolProperties = symbolPropertiesDatabase.GetSymbolProperties(symbol.ID.Market, symbol.Value, securityType, Symbol); Cash quoteCash; if (!cashBook.TryGetValue(symbolProperties.QuoteCurrency, out quoteCash)) { throw new Exception("Unable to resolve quote cash: " + symbolProperties.QuoteCurrency + ". This is required to add conversion feed: " + symbol.ToString()); } var marketHoursDbEntry = marketHoursDatabase.GetEntry(symbol.ID.Market, symbol.Value, symbol.ID.SecurityType); var exchangeHours = marketHoursDbEntry.ExchangeHours; // set this as an internal feed so that the data doesn't get sent into the algorithm's OnData events var config = subscriptions.Add(objectType, symbol, minimumResolution, marketHoursDbEntry.DataTimeZone, exchangeHours.TimeZone, false, true, false, true); SecuritySymbol = config.Symbol; Security security; if (securityType == SecurityType.Cfd) { security = new Cfd.Cfd(exchangeHours, quoteCash, config, symbolProperties); } else { security = new Forex.Forex(exchangeHours, this, config, symbolProperties); } securities.Add(config.Symbol, security); Log.Trace("Cash.EnsureCurrencyDataFeed(): Adding " + symbol.Value + " for cash " + Symbol + " currency feed"); return(security); } } // if this still hasn't been set then it's an error condition throw new ArgumentException(string.Format("In order to maintain cash in {0} you are required to add a subscription for Forex pair {0}{1} or {1}{0}", Symbol, CashBook.AccountCurrency)); }
/// <summary> /// Ensures that we have a data feed to convert this currency into the base currency. /// This will add a subscription at the lowest resolution if one is not found. /// </summary> /// <param name="securities">The security manager</param> /// <param name="subscriptions">The subscription manager used for searching and adding subscriptions</param> /// <param name="marketHoursDatabase">A security exchange hours provider instance used to resolve exchange hours for new subscriptions</param> /// <param name="symbolPropertiesDatabase">A symbol properties database instance</param> /// <param name="marketMap">The market map that decides which market the new security should be in</param> /// <param name="cashBook">The cash book - used for resolving quote currencies for created conversion securities</param> /// <param name="changes"></param> /// <returns>Returns the added currency security if needed, otherwise null</returns> public Security EnsureCurrencyDataFeed(SecurityManager securities, SubscriptionManager subscriptions, MarketHoursDatabase marketHoursDatabase, SymbolPropertiesDatabase symbolPropertiesDatabase, IReadOnlyDictionary <SecurityType, string> marketMap, CashBook cashBook, SecurityChanges changes ) { // this gets called every time we add securities using universe selection, // so must of the time we've already resolved the value and don't need to again if (ConversionRateSecurity != null) { return(null); } if (Symbol == CashBook.AccountCurrency) { ConversionRateSecurity = null; _isBaseCurrency = true; ConversionRate = 1.0m; return(null); } // we require a security that converts this into the base currency string normal = Symbol + CashBook.AccountCurrency; string invert = CashBook.AccountCurrency + Symbol; var securitiesToSearch = securities.Select(kvp => kvp.Value) .Concat(changes.AddedSecurities) .Where(s => s.Type == SecurityType.Forex || s.Type == SecurityType.Cfd || s.Type == SecurityType.Crypto); foreach (var security in securitiesToSearch) { if (security.Symbol.Value == normal) { ConversionRateSecurity = security; return(null); } if (security.Symbol.Value == invert) { ConversionRateSecurity = security; _invertRealTimePrice = true; return(null); } } // if we've made it here we didn't find a security, so we'll need to add one // Create a SecurityType to Market mapping with the markets from SecurityManager members var markets = securities.Select(x => x.Key).GroupBy(x => x.SecurityType).ToDictionary(x => x.Key, y => y.First().ID.Market); if (markets.ContainsKey(SecurityType.Cfd) && !markets.ContainsKey(SecurityType.Forex)) { markets.Add(SecurityType.Forex, markets[SecurityType.Cfd]); } if (markets.ContainsKey(SecurityType.Forex) && !markets.ContainsKey(SecurityType.Cfd)) { markets.Add(SecurityType.Cfd, markets[SecurityType.Forex]); } var potentials = Currencies.CurrencyPairs.Select(fx => CreateSymbol(marketMap, fx, markets, SecurityType.Forex)) .Concat(Currencies.CfdCurrencyPairs.Select(cfd => CreateSymbol(marketMap, cfd, markets, SecurityType.Cfd))) .Concat(Currencies.CryptoCurrencyPairs.Select(crypto => CreateSymbol(marketMap, crypto, markets, SecurityType.Crypto))); var minimumResolution = subscriptions.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(Resolution.Minute).Min(); foreach (var symbol in potentials) { if (symbol.Value == normal || symbol.Value == invert) { _invertRealTimePrice = symbol.Value == invert; var securityType = symbol.ID.SecurityType; var symbolProperties = symbolPropertiesDatabase.GetSymbolProperties(symbol.ID.Market, symbol.Value, securityType, Symbol); Cash quoteCash; if (!cashBook.TryGetValue(symbolProperties.QuoteCurrency, out quoteCash)) { throw new Exception("Unable to resolve quote cash: " + symbolProperties.QuoteCurrency + ". This is required to add conversion feed: " + symbol.Value); } var marketHoursDbEntry = marketHoursDatabase.GetEntry(symbol.ID.Market, symbol.Value, symbol.ID.SecurityType); var exchangeHours = marketHoursDbEntry.ExchangeHours; // use the first subscription defined in the subscription manager var type = subscriptions.LookupSubscriptionConfigDataTypes(securityType, minimumResolution, false).First(); var objectType = type.Item1; var tickType = type.Item2; // set this as an internal feed so that the data doesn't get sent into the algorithm's OnData events var config = subscriptions.Add(objectType, tickType, symbol, minimumResolution, marketHoursDbEntry.DataTimeZone, exchangeHours.TimeZone, false, true, false, true); Security security; if (securityType == SecurityType.Cfd) { security = new Cfd.Cfd(exchangeHours, quoteCash, config, symbolProperties, cashBook); } else if (securityType == SecurityType.Crypto) { security = new Crypto.Crypto(exchangeHours, quoteCash, config, symbolProperties, cashBook); } else { security = new Forex.Forex(exchangeHours, quoteCash, config, symbolProperties, cashBook); } ConversionRateSecurity = security; securities.Add(config.Symbol, security); Log.Trace("Cash.EnsureCurrencyDataFeed(): Adding " + symbol.Value + " for cash " + Symbol + " currency feed"); return(security); } } // if this still hasn't been set then it's an error condition throw new ArgumentException(string.Format("In order to maintain cash in {0} you are required to add a subscription for Forex pair {0}{1} or {1}{0}", Symbol, CashBook.AccountCurrency)); }
/// <summary> /// Ensures that we have a data feed to conver this currency into the base currency. /// This will add a subscription at the lowest resolution if one is not found. /// </summary> /// <param name="securities">The security manager</param> /// <param name="subscriptions">The subscription manager used for searching and adding subscriptions</param> /// <param name="exchangeHoursProvider">A security exchange hours provider instance used to resolve exchange hours for new subscriptions</param> public void EnsureCurrencyDataFeed(SecurityManager securities, SubscriptionManager subscriptions, SecurityExchangeHoursProvider exchangeHoursProvider) { if (Symbol == CashBook.AccountCurrency) { _isBaseCurrency = true; ConversionRate = 1.0m; return; } if (subscriptions.Count == 0) { throw new InvalidOperationException("Unable to add cash when no subscriptions are present. Please add subscriptions in the Initialize() method."); } // we require a subscription that converts this into the base currency string normal = Symbol + CashBook.AccountCurrency; string invert = CashBook.AccountCurrency + Symbol; for (int i = 0; i < subscriptions.Subscriptions.Count; i++) { var config = subscriptions.Subscriptions[i]; if (config.SecurityType != SecurityType.Forex) { continue; } if (config.Symbol == normal) { _config = config; return; } if (config.Symbol == invert) { _config = config; _invertRealTimePrice = true; return; } } // get the market from the first Forex subscription string market = (from config in subscriptions.Subscriptions where config.SecurityType == SecurityType.Forex select config.Market).FirstOrDefault() ?? subscriptions.Subscriptions[0].Market; // if we've made it here we didn't find a subscription, so we'll need to add one var currencyPairs = Forex.Forex.CurrencyPairs; var minimumResolution = subscriptions.Subscriptions.Min(x => x.Resolution); var objectType = minimumResolution == Resolution.Tick ? typeof(Tick) : typeof(TradeBar); var isTradeBar = objectType == typeof(TradeBar); foreach (var symbol in currencyPairs) { if (symbol == normal || symbol == invert) { _invertRealTimePrice = symbol == invert; var exchangeHours = exchangeHoursProvider.GetExchangeHours(market, symbol, SecurityType.Forex); // set this as an internal feed so that the data doesn't get sent into the algorithm's OnData events _config = subscriptions.Add(objectType, SecurityType.Forex, symbol, minimumResolution, market, exchangeHours.TimeZone, true, false, isTradeBar, isTradeBar, true); var security = new Forex.Forex(this, _config, 1m, false); securities.Add(symbol, security); Log.Trace("Cash.EnsureCurrencyDataFeed(): Adding " + symbol + " for cash " + this.Symbol + " currency feed"); return; } } // if this still hasn't been set then it's an error condition throw new ArgumentException(string.Format("In order to maintain cash in {0} you are required to add a subscription for Forex pair {0}{1} or {1}{0}", Symbol, CashBook.AccountCurrency)); }
/// <summary> /// Ensures that we have a data feed to convert this currency into the base currency. /// This will add a <see cref="SubscriptionDataConfig"/> and create a <see cref="Security"/> at the lowest resolution if one is not found. /// </summary> /// <param name="securities">The security manager</param> /// <param name="subscriptions">The subscription manager used for searching and adding subscriptions</param> /// <param name="marketMap">The market map that decides which market the new security should be in</param> /// <param name="changes">Will be used to consume <see cref="SecurityChanges.AddedSecurities"/></param> /// <param name="securityService">Will be used to create required new <see cref="Security"/></param> /// <param name="accountCurrency">The account currency</param> /// <returns>Returns the added <see cref="SubscriptionDataConfig"/>, otherwise null</returns> public SubscriptionDataConfig EnsureCurrencyDataFeed(SecurityManager securities, SubscriptionManager subscriptions, IReadOnlyDictionary <SecurityType, string> marketMap, SecurityChanges changes, ISecurityService securityService, string accountCurrency ) { // this gets called every time we add securities using universe selection, // so must of the time we've already resolved the value and don't need to again if (ConversionRateSecurity != null) { return(null); } AccountCurrency = accountCurrency; if (Symbol == AccountCurrency) { ConversionRateSecurity = null; _isBaseCurrency = true; ConversionRate = 1.0m; return(null); } // we require a security that converts this into the base currency string normal = Symbol + AccountCurrency; string invert = AccountCurrency + Symbol; var securitiesToSearch = securities.Select(kvp => kvp.Value) .Concat(changes.AddedSecurities) .Where(s => s.Type == SecurityType.Forex || s.Type == SecurityType.Cfd || s.Type == SecurityType.Crypto); foreach (var security in securitiesToSearch) { if (security.Symbol.Value == normal) { ConversionRateSecurity = security; return(null); } if (security.Symbol.Value == invert) { ConversionRateSecurity = security; _invertRealTimePrice = true; return(null); } } // if we've made it here we didn't find a security, so we'll need to add one // Create a SecurityType to Market mapping with the markets from SecurityManager members var markets = securities.Select(x => x.Key).GroupBy(x => x.SecurityType).ToDictionary(x => x.Key, y => y.First().ID.Market); if (markets.ContainsKey(SecurityType.Cfd) && !markets.ContainsKey(SecurityType.Forex)) { markets.Add(SecurityType.Forex, markets[SecurityType.Cfd]); } if (markets.ContainsKey(SecurityType.Forex) && !markets.ContainsKey(SecurityType.Cfd)) { markets.Add(SecurityType.Cfd, markets[SecurityType.Forex]); } var potentials = Currencies.CurrencyPairs.Select(fx => CreateSymbol(marketMap, fx, markets, SecurityType.Forex)) .Concat(Currencies.CfdCurrencyPairs.Select(cfd => CreateSymbol(marketMap, cfd, markets, SecurityType.Cfd))) .Concat(Currencies.CryptoCurrencyPairs.Select(crypto => CreateSymbol(marketMap, crypto, markets, SecurityType.Crypto))); var minimumResolution = subscriptions.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(Resolution.Minute).Min(); foreach (var symbol in potentials) { if (symbol.Value == normal || symbol.Value == invert) { _invertRealTimePrice = symbol.Value == invert; var securityType = symbol.ID.SecurityType; // use the first subscription defined in the subscription manager var type = subscriptions.LookupSubscriptionConfigDataTypes(securityType, minimumResolution, false).First(); var objectType = type.Item1; var tickType = type.Item2; // set this as an internal feed so that the data doesn't get sent into the algorithm's OnData events var config = subscriptions.SubscriptionDataConfigService.Add(symbol, minimumResolution, fillForward: true, extendedMarketHours: false, isInternalFeed: true, subscriptionDataTypes: new List <Tuple <Type, TickType> > { new Tuple <Type, TickType>(objectType, tickType) }).First(); var security = securityService.CreateSecurity(symbol, config, addToSymbolCache: false); ConversionRateSecurity = security; securities.Add(config.Symbol, security); Log.Trace("Cash.EnsureCurrencyDataFeed(): Adding " + symbol.Value + " for cash " + Symbol + " currency feed"); return(config); } } // if this still hasn't been set then it's an error condition throw new ArgumentException(string.Format("In order to maintain cash in {0} you are required to add a subscription for Forex pair {0}{1} or {1}{0}", Symbol, AccountCurrency)); }