public void LinearSearchFindsTwoLegConversions() { var existingSecurities = new List <Security>(0); var potentialSymbols = new List <Symbol> { Symbols.BTCUSD, Symbols.EURUSD }; var subscriptions = new SubscriptionManager(); var dataManager = new DataManagerStub(); subscriptions.SetDataManager(dataManager); var createdSecurities = new List <Security>(); var makeNewSecurity = new Func <Symbol, Security>(symbol => { var security = CreateSecurity(symbol); createdSecurities.Add(security); return(security); }); var currencyConversion = SecurityCurrencyConversion.LinearSearch( "BTC", "EUR", existingSecurities, potentialSymbols, makeNewSecurity); var securities = currencyConversion.ConversionRateSecurities.ToList(); Assert.AreEqual(2, securities.Count); Assert.AreEqual(createdSecurities, securities); Assert.AreEqual(Symbols.BTCUSD, securities[0].Symbol); Assert.AreEqual(Symbols.EURUSD, securities[1].Symbol); }
public void UpdateCalculatesNewConversionRateTwoLeg( string sourceCurrency, string destinationCurrency, Symbol symbol1, Symbol symbol2, decimal expectedRate) { var existingSecurities = new List <Security> { CreateSecurity(symbol1), CreateSecurity(symbol2) }; var currencyConversion = SecurityCurrencyConversion.LinearSearch( sourceCurrency, destinationCurrency, existingSecurities, new List <Symbol>(0), CreateSecurity); existingSecurities[0].SetMarketPrice(new Tick { Value = 15m }); existingSecurities[1].SetMarketPrice(new Tick { Value = 25m }); Assert.AreEqual(expectedRate, currencyConversion.Update()); }
public void ConversionRateReturnsLatestConversionRate() { var existingSecurities = new List <Security> { CreateSecurity(Symbols.BTCUSD) }; var currencyConversion = SecurityCurrencyConversion.LinearSearch( "BTC", "USD", existingSecurities, new List <Symbol>(0), CreateSecurity); Assert.AreEqual(0m, currencyConversion.ConversionRate); existingSecurities[0].SetMarketPrice(new Tick { Value = 10m }); currencyConversion.Update(); Assert.AreEqual(10m, currencyConversion.ConversionRate); existingSecurities[0].SetMarketPrice(new Tick { Value = 20m }); currencyConversion.Update(); Assert.AreEqual(20m, currencyConversion.ConversionRate); }
public void LinearSearchPrefersExistingSecuritiesOverNewOnesOneLeg() { var existingSecurities = new List <Security> { CreateSecurity(Symbols.EURUSD) }; var potentialSymbols = new List <Symbol> { Symbols.EURUSD }; var subscriptions = new SubscriptionManager(); var dataManager = new DataManagerStub(); subscriptions.SetDataManager(dataManager); var createdSecurities = new List <Security>(); var makeNewSecurity = new Func <Symbol, Security>(symbol => { var security = CreateSecurity(symbol); createdSecurities.Add(security); return(security); }); var currencyConversion = SecurityCurrencyConversion.LinearSearch( "EUR", "USD", existingSecurities, potentialSymbols, makeNewSecurity); var securities = currencyConversion.ConversionRateSecurities.ToList(); Assert.AreEqual(1, securities.Count); Assert.AreEqual(0, createdSecurities.Count); Assert.AreEqual(existingSecurities, securities); }
public void LinearSearchThrowsWhenNoConversionPossible() { var existingSecurities = new List <Security>(0); var potentialSymbols = new List <Symbol> { Symbols.EURGBP }; Assert.Throws <ArgumentException>(() => SecurityCurrencyConversion.LinearSearch( "EUR", "USD", existingSecurities, potentialSymbols, CreateSecurity)); }
public void UpdateReturnsZeroWhenNoData() { var existingSecurities = new List <Security> { CreateSecurity(Symbols.BTCUSD) }; var currencyConversion = SecurityCurrencyConversion.LinearSearch( "BTC", "USD", existingSecurities, new List <Symbol>(0), CreateSecurity); Assert.AreEqual(0m, currencyConversion.Update()); }
public void DestinationCurrencyReturnsCorrectValue() { var existingSecurities = new List <Security>(0); var potentialSymbols = new List <Symbol> { Symbols.EURUSD }; var currencyConversion = SecurityCurrencyConversion.LinearSearch( "EUR", "USD", existingSecurities, potentialSymbols, CreateSecurity); Assert.AreEqual("USD", currencyConversion.DestinationCurrency); }
public void ConversionRateZeroAtStart() { var existingSecurities = new List <Security>(0); var potentialSymbols = new List <Symbol> { Symbols.EURUSD }; var currencyConversion = SecurityCurrencyConversion.LinearSearch( "EUR", "USD", existingSecurities, potentialSymbols, CreateSecurity); Assert.AreEqual(0, currencyConversion.ConversionRate); }
public void UpdateReturnsZeroWhenNoDataForOneOfTwoSymbols() { var existingSecurities = new List <Security> { CreateSecurity(Symbols.ETHBTC), CreateSecurity(Symbols.BTCUSD) }; var currencyConversion = SecurityCurrencyConversion.LinearSearch( "ETH", "USD", existingSecurities, new List <Symbol>(0), CreateSecurity); existingSecurities[0].SetMarketPrice(new Tick { Value = 15m }); Assert.AreEqual(0m, currencyConversion.Update()); }
/// <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.ContainsKey(SecurityType.Crypto) && Currencies.StableCoinsWithoutPairs.Contains(QuantConnect.Symbol.Create(Symbol + accountCurrency, SecurityType.Crypto, marketMap[SecurityType.Crypto]))) { 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); }