Exemple #1
0
 public string GetKey(
     MDSDictionaryType dictType, MDSRequestType requestType,
     MDSProviderId sourceProvider, MDSProviderId targetProvider, string sourceValue)
 {
     //return String.Format("{0};{1};{2}:{3};{4}:{5}",
     return
         ($"{dictType.ToString()};{requestType.ToString()}:{sourceProvider.ToString()};{sourceValue.ToUpper()}:{targetProvider.ToString()}");
 }
Exemple #2
0
 private void AddUnitsMap(int priority, MDSRequestType requestType, string sourcePattern, PriceQuoteUnitsEnum targetUnits)
 {
     _rules.Locked(rules => rules.Add(new MapRule(
                                          MDSDictionaryType.QuoteUnits,
                                          MDSProviderId.GlobalIB, _provider,
                                          requestType,
                                          priority,
                                          sourcePattern, targetUnits.ToString())));
 }
Exemple #3
0
 private void AddFieldMap(int priority, MDSRequestType requestType, string sourcePattern, string targetPattern)
 {
     _rules.Locked(rules => rules.Add(new MapRule(
                                          MDSDictionaryType.FieldName,
                                          MDSProviderId.GlobalIB, _provider,
                                          requestType,
                                          priority,
                                          sourcePattern, targetPattern)));
 }
Exemple #4
0
 protected virtual MDSResult <QuotedAssetSet> OnRequestPricingStructure(
     IModuleInfo clientInfo,
     Guid requestId,
     MDSRequestType requestType,
     NamedValueSet requestParams,
     DateTimeOffset subsExpires,
     NamedValueSet structureParams)
 {
     throw new NotSupportedException("This provider (" + ProviderId.ToString() + ") does not support the OnRequestPricingStructure method!");
 }
Exemple #5
0
 protected virtual MDSResult <QuotedAssetSet> OnRequestMarketData(
     IModuleInfo clientInfo,
     Guid requestId,
     MDSRequestType requestType,
     NamedValueSet requestParams,
     DateTimeOffset subsExpires,
     QuotedAssetSet standardQuotedAssetSet)
 {
     throw new NotSupportedException("This provider (" + ProviderId + ") does not support the OnRequestMarketData method!");
 }
 protected override MDSResult <QuotedAssetSet> OnRequestMarketData(
     IModuleInfo clientInfo,
     Guid requestId,
     MDSRequestType requestType,
     NamedValueSet requestParams,
     DateTimeOffset subsExpires,
     QuotedAssetSet standardQuotedAssetSet)
 {
     return(RunBloombergRequest(clientInfo, requestId, requestParams, MDSRequestType.Current, DateTimeOffset.Now, standardQuotedAssetSet));
 }
Exemple #7
0
 public SubscriptionDetail(
     Guid subscriptionId,
     TimeSpan subsLifetime,
     MdsSubsState initialState,
     MDSRequestType requestType,
     AsyncQueueCallback <QuotedAssetSet> clientCallback)
 {
     SubsId      = subscriptionId;
     SubsExpires = DateTimeOffset.Now + subsLifetime;
     //_DataLifetime = dataLifetime;
     SubsState      = initialState;
     RequestType    = requestType;
     ClientCallback = clientCallback;
 }
Exemple #8
0
 public MDSResult <QuotedAssetSet> RequestMarketQuotes(
     IModuleInfo clientInfo,
     Guid requestId,
     NamedValueSet requestParams,
     MDSRequestType requestType,
     DateTimeOffset subsExpires,
     QuotedAssetSet standardQuotedAssetSet)
 {
     Logger.LogDebug("{0}.RequestMarketQuotes", GetType().Name);
     Logger.LogDebug("  Inputs:");
     Logger.LogDebug("    Request Id  : {0}", requestId);
     Logger.LogDebug("    Request Type: {0}", requestType);
     Logger.LogDebug("    Other params: {0}", requestParams.Serialise());
     return(OnRequestMarketData(
                clientInfo, requestId, requestType, requestParams, subsExpires, standardQuotedAssetSet));
 }
Exemple #9
0
 public MapRule(MDSDictionaryType dictType,
                MDSProviderId sourceProvider, MDSProviderId targetProvider,
                MDSRequestType requestType,
                int priority,
                string searchValue, string outputValue)
 {
     Disabled       = false;
     DictType       = dictType;
     SourceProvider = sourceProvider;
     TargetProvider = targetProvider;
     RequestType    = requestType;
     Priority       = priority;
     SearchValue    = searchValue;
     OutputValue    = outputValue;
     Regex          = new Regex("^" + searchValue + "$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
 }
Exemple #10
0
        protected override MDSResult <QuotedAssetSet> OnRequestPricingStructure(
            IModuleInfo clientInfo,
            Guid requestId,
            MDSRequestType requestType,
            NamedValueSet requestParams,
            DateTimeOffset subsExpires,
            NamedValueSet structureParams)
        {
            var combinedResult = new QuotedAssetSet();
            var debugRequest   = requestParams.GetValue <bool>("DebugRequest", false);

            Client.DebugRequests = debugRequest;
            IExpression           query           = Expr.BoolAND(structureParams);
            List <QuotedAssetSet> providerResults = Client.LoadObjects <QuotedAssetSet>(query);

            Client.DebugRequests = false;
            // merge multiple results
            combinedResult = providerResults.Aggregate(combinedResult, (current, providerResult) => current.Merge(providerResult, false, true, true));
            return(new MDSResult <QuotedAssetSet>
            {
                Result = combinedResult
            });
        }
Exemple #11
0
        protected override MDSResult <QuotedAssetSet> OnRequestMarketData(
            IModuleInfo clientInfo,
            Guid requestId,
            MDSRequestType requestType,
            NamedValueSet requestParams,
            DateTimeOffset subsExpires,
            QuotedAssetSet standardQuotedAssetSet)
        {
            // process request parameters
            RequestProperties requestProperties = ProcessRequestParams(requestParams);

            // process the asset/quote lists to produce 1 or more instrument/field matrices
            RequestContext requestContext = ConvertStandardAssetQuotesToProviderInstrFieldCodes(requestType, standardQuotedAssetSet);

            // process the instr/field code sets
            var results = new List <BasicAssetValuation>();

            foreach (ProviderInstrFieldCodeSet instrFieldCodeSet in requestContext.ProviderInstrFieldCodeSets)
            {
                var providerResults = new Dictionary <string, BasicQuotation>();
                // simulated reply
                var rng = new Random();
                //List<BasicAssetValuation> results = new List<BasicAssetValuation>();
                foreach (string t in instrFieldCodeSet.InstrumentIds)
                {
                    var quote = new BasicAssetValuation
                    {
                        objectReference =
                            new AnyAssetReference {
                            href = t
                        },
                        quote = new BasicQuotation[instrFieldCodeSet.FieldNames.Count]
                    };
                    foreach (string t1 in instrFieldCodeSet.FieldNames)
                    {
                        var providerQuote = new BasicQuotation
                        {
                            measureType =
                                new AssetMeasureType
                            {
                                Value = AssetMeasureEnum.MarketQuote.ToString()
                            },
                            quoteUnits =
                                new PriceQuoteUnits
                            {
                                Value = PriceQuoteUnitsEnum.Rate.ToString()
                            },
                            value          = Convert.ToDecimal(rng.NextDouble() * 10.0),
                            valueSpecified = true
                        };
                        // field value
                        // simulator returns a random decimal value
                        // field done
                        string providerQuoteKey = FormatProviderQuoteKey(t, t1);
                        providerResults[providerQuoteKey] = providerQuote;
                    }
                }

                // process provider results
                //Dictionary<string, BasicQuotation> providerResults = BuildProviderResultsIndex(pages);
                results.AddRange(ConvertProviderResultsToStandardValuations(providerResults, requestContext));
            }
            return(new MDSResult <QuotedAssetSet>
            {
                Result = new QuotedAssetSet {
                    assetQuote = results.ToArray()
                }
            });
        }
Exemple #12
0
 public MDSResult <QuotedAssetSet> RequestPricingStructure(IModuleInfo clientInfo, Guid requestId, NamedValueSet requestParams, MDSRequestType requestType, DateTimeOffset subsExpires, NamedValueSet structureParams)
 {
     Logger.LogDebug("{0}.RequestPricingStructure", GetType().Name);
     Logger.LogDebug("  Inputs:");
     Logger.LogDebug("    Request Id  : {0}", requestId);
     Logger.LogDebug("    Request Type: {0}", requestType);
     Logger.LogDebug("    Curve params: {0}", structureParams.Serialise());
     Logger.LogDebug("    Other params: {0}", requestParams.Serialise());
     return(OnRequestPricingStructure(
                clientInfo, requestId, requestType, requestParams, subsExpires, structureParams));
 }
Exemple #13
0
        protected RequestContext ConvertStandardAssetQuotesToProviderInstrFieldCodes(
            MDSRequestType requestType,
            QuotedAssetSet standardQuotedAssetSet)
        {
            // extract assets/quotes that require market quotes
            var standardAssets = new List <Asset>();
            var standardQuotes = new List <BasicQuotation>();
            {
                // build a request/response map (indexed by instrument id)
                var instrumentMap = new Dictionary <string, Asset>();
                //List<Pair<Asset, BasicQuotation>> completeAssetQuotes = new List<Pair<Asset, BasicQuotation>>();

                foreach (Asset asset in standardQuotedAssetSet.instrumentSet.Items)
                {
                    instrumentMap[asset.id.ToLower()] = asset;
                }
                foreach (BasicAssetValuation quoteInstr in standardQuotedAssetSet.assetQuote)
                {
                    string instrId = quoteInstr.objectReference.href;
                    if (!instrumentMap.TryGetValue(instrId.ToLower(), out var asset))
                    {
                        throw new ApplicationException($"Cannot find instrument '{instrId}' for assetQuote");
                    }
                    foreach (BasicQuotation quoteField in quoteInstr.quote)
                    {
                        if (quoteField.valueSpecified)
                        {
                            // value provided - don't get from market
                            //completeAssetQuotes.Add(new Pair<Asset, BasicQuotation>(asset, quoteField));
                        }
                        else
                        {
                            // value not supplied - get from market
                            BasicQuotation quote = BasicQuotationHelper.Clone(quoteField);
                            standardAssets.Add(asset);
                            standardQuotes.Add(quote);
                        }
                    }
                }
            }
            var requestItems       = new List <RequestItem>();
            var instrConversionMap = new Dictionary <string, string>();
            var instrUniquenessMap = new Dictionary <string, string>();
            var internalInstrIds   = new List <string>();
            var fieldConversionMap = new Dictionary <string, string>();
            var fieldUniquenessMap = new Dictionary <string, string>();
            var internalFieldIds   = new List <string>();

            Logger.LogDebug("    Mappings    :");
            for (int i = 0; i < standardAssets.Count; i++)
            {
                // map asset to provider instrument id
                Asset  standardAsset   = standardAssets[i];
                string internalInstrId = standardAsset.id;
                internalInstrIds.Add(internalInstrId);
                string providerInstrId = _marketDataMap.Convert(
                    MDSDictionaryType.Instrument, requestType,
                    MDSProviderId.GlobalIB, ProviderId, internalInstrId,
                    ConvertFailMode.ThrowException);
                // update 1-way map
                instrConversionMap[internalInstrId.ToLower()] = providerInstrId;
                instrUniquenessMap[providerInstrId.ToLower()] = providerInstrId;
                // map quote to provider field name
                BasicQuotation standardQuote   = standardQuotes[i];
                string         internalFieldId = standardQuote.GetStandardFieldName();
                internalFieldIds.Add(internalFieldId);
                string providerFieldId = _marketDataMap.Convert(
                    MDSDictionaryType.FieldName, requestType,
                    MDSProviderId.GlobalIB, ProviderId, internalFieldId,
                    ConvertFailMode.ThrowException);
                // update 1-way map
                fieldConversionMap[internalFieldId.ToLower()] = providerFieldId;
                fieldUniquenessMap[providerFieldId.ToLower()] = providerFieldId;
                // get provider units
                string providerUnitsId = _marketDataMap.Convert(
                    MDSDictionaryType.QuoteUnits, requestType,
                    MDSProviderId.GlobalIB, ProviderId, String.Format("{0}/{1}", internalInstrId, internalFieldId),
                    ConvertFailMode.ThrowException);
                var requestItem = new RequestItem
                {
                    StandardAsset        = standardAsset,
                    StandardQuote        = standardQuote,
                    StandardUnits        = PriceQuoteUnitsScheme.ParseEnumString(standardQuote.quoteUnits.Value),
                    ProviderInstrumentId = providerInstrId,
                    ProviderFieldName    = providerFieldId,
                    ProviderUnits        = PriceQuoteUnitsScheme.ParseEnumString(providerUnitsId)
                };
                requestItems.Add(requestItem);
                // debug
                Logger.LogDebug("      [{0}] '{1}/{2}' ({3}) --> '{4}/{5}' ({6})", i,
                                internalInstrIds[i], internalFieldIds[i], standardQuote.quoteUnits.Value,
                                instrConversionMap[internalInstrIds[i].ToLower()], fieldConversionMap[internalFieldIds[i].ToLower()], providerUnitsId);
                // end debug
            }
            var providerInstrIds = new List <string>(instrUniquenessMap.Values);
            var providerFieldIds = new List <string>(fieldUniquenessMap.Values);
            // build provider instr/field code sets - todo - for now just build 1
            var results = new List <ProviderInstrFieldCodeSet>();
            var result  = new ProviderInstrFieldCodeSet(providerInstrIds, providerFieldIds);

            results.Add(result);
            return(new RequestContext
            {
                RequestItems = requestItems,
                ProviderInstrFieldCodeSets = results,
                InstrConversionMap = instrConversionMap,
                FieldConversionMap = fieldConversionMap
            });
        }
Exemple #14
0
        public string Convert(
            MDSDictionaryType dictType, MDSRequestType requestType,
            MDSProviderId sourceProvider, MDSProviderId targetProvider,
            string sourceValue, ConvertFailMode failMode)
        {
            // method:
            // 1. check for cached result
            // 2. find relevant maps (by dicttype, reqtype, assettype, source, target)
            // 3. process maps in priority order until hit, fail if no hit
            // 4. save outbound and inbound results in cache
            if (dictType == MDSDictionaryType.Undefined)
            {
                throw new ArgumentNullException(nameof(dictType));
            }
            if (requestType == MDSRequestType.Undefined)
            {
                throw new ArgumentNullException(nameof(requestType));
            }
            //if (assetType == MDSAssetType.Undefined)
            //    throw new ArgumentNullException("assetType");
            if (sourceProvider == MDSProviderId.Undefined)
            {
                throw new ArgumentNullException(nameof(sourceProvider));
            }
            if (targetProvider == MDSProviderId.Undefined)
            {
                throw new ArgumentNullException(nameof(targetProvider));
            }
            if (sourceValue == null)
            {
                throw new ArgumentNullException(nameof(sourceValue));
            }
            sourceValue = sourceValue.Trim().ToUpper();
            if (sourceValue == "")
            {
                throw new ArgumentNullException(nameof(sourceValue));
            }
            const string cUnknownValue = "[unknown]";
            // check for cached result
            string forwardKey = GetKey(dictType, requestType, //assetType,
                                       sourceProvider, targetProvider, sourceValue);

            lock (_forwardCache)
            {
                if (_forwardCache.TryGetValue(forwardKey, out var cachedResult))
                {
                    //Logger.LogDebug("Converted {0} {1} '{2}' to {3} '{4}' (via forward cache)",
                    //    sourceProvider.ToString(), dictType.ToString(), sourceValue, targetProvider.ToString(), cachedResult);
                    return(cachedResult);
                }
            }
            // find relevant maps (by dicttype, reqtype, assettype, source, target)
            var maps = new List <MapRule>();

            _rules.Locked((rules) =>
            {
                foreach (var rule in rules)
                {
                    if ((!rule.Disabled) &&
                        (rule.DictType == MDSDictionaryType.Undefined || (rule.DictType == dictType)) &&
                        (rule.SourceProvider == MDSProviderId.Undefined || (rule.SourceProvider == sourceProvider)) &&
                        (rule.TargetProvider == MDSProviderId.Undefined || (rule.TargetProvider == targetProvider))
                        //&& (rule.AssetIdType == MDSAssetType.Undefined || (rule.AssetIdType == assetType))
                        && (rule.RequestType == MDSRequestType.Undefined || (rule.RequestType == requestType)))
                    {
                        maps.Add(rule);
                    }
                }
            });
            // process maps in priority order until hit, fail if no hit
            string result   = sourceValue;
            bool   replaced = false;

            foreach (var map in maps)
            {
                Match match = map.Regex.Match(sourceValue);
                if (match.Success)
                {
                    result   = map.Regex.Replace(sourceValue, map.OutputValue);
                    replaced = true;
                    break;
                }
            }
            if (replaced)
            {
                result = result.Trim().ToUpper();
                //_Logger.LogDebug("Converted {0} {1} '{2}' to {3} '{4}' (via mapping rules)",
                //    sourceProvider.ToString(), dictType.ToString(), sourceValue, targetProvider.ToString(), result);
                // update forward and reverse caches
                UpdateForwardCache(forwardKey, result);
                string reverseKey = GetKey(dictType, requestType, //assetType,
                                           targetProvider, sourceProvider, result);
                UpdateReverseCache(reverseKey, sourceValue);
            }
            else
            {
                // no replacement rules - try the reverse cache
                string cachedResult;
                bool   foundInReverseCache;
                lock (_reverseCache)
                {
                    foundInReverseCache = _reverseCache.TryGetValue(forwardKey, out cachedResult);
                }
                if (foundInReverseCache)
                {
                    _logger.LogDebug("Converted {0} {1} '{2}' to {3} '{4}' (via reverse cache)",
                                     sourceProvider.ToString(), dictType.ToString(), sourceValue, targetProvider.ToString(), cachedResult);
                    // update forward cache
                    UpdateForwardCache(forwardKey, cachedResult);
                    return(cachedResult);
                }
                // exhausted all conversion options
                _logger.LogWarning("Cannot convert {0} {1} '{2}' to {3} (no matching map found)",
                                   sourceProvider.ToString(), dictType.ToString(), sourceValue, targetProvider.ToString());
                switch (failMode)
                {
                case ConvertFailMode.PassThrough:
                    result = sourceValue;
                    break;

                case ConvertFailMode.ReturnNull:
                    result = null;
                    break;

                case ConvertFailMode.ReturnUnknown:
                    result = cUnknownValue;
                    break;

                default:
                    throw new ApplicationException(
                              String.Format("Cannot convert {0} {1} '{2}' to {3} ",
                                            sourceProvider.ToString(), dictType.ToString(), sourceValue, targetProvider.ToString()));
                }
            }
            return(result);
        }
Exemple #15
0
        private MDSResult <QuotedAssetSet> RunBloombergRequest(
            IModuleInfo clientInfo,
            Guid requestId,
            NamedValueSet requestParams,
            MDSRequestType requestType,
            DateTimeOffset subsExpires,
            QuotedAssetSet standardQuotedAssetSet)
        {
            // process request parameters
            //BloombergRequestProps requestProps = ProcessRequestParams(requestParams);

            // process the asset/quote lists to produce 1 or more instrument/field matrices
            RequestContext requestContext = ConvertStandardAssetQuotesToProviderInstrFieldCodes(requestType, standardQuotedAssetSet);

            // process the instr/field code sets
            var results = new List <BasicAssetValuation>();

            foreach (ProviderInstrFieldCodeSet instrFieldCodeSet in requestContext.ProviderInstrFieldCodeSets)
            {
                Request req;
                switch (requestType)
                {
                case MDSRequestType.Current:
                    req = new RequestForStatic {
                        SubscriptionMode = SubscriptionMode.ByRequest
                    };
                    break;

                case MDSRequestType.History:
                    req = new RequestForHistory {
                        SubscriptionMode = SubscriptionMode.ByRequest
                    };
                    throw new NotImplementedException("dataRequestType=History");

                case MDSRequestType.Realtime:
                    req = new RequestForRealtime {
                        SubscriptionMode = SubscriptionMode.ByField
                    };
                    break;

                default:
                    throw new NotSupportedException("dataRequestType");
                }

                // process request parameters
                //BloombergRequestProps requestProps = ProcessRequestParams(requestParams);

                // Multi-user mode logon check required. Note: some clients have exemptions:
                // - unit test clients
                // - server mode apps (eg. CurveGenerator)
                // Exempted clients must never forward any market data, only derived data.

                Logger.LogDebug("  Identity   : {0} ({1})", clientInfo.Name, clientInfo.UserFullName);
                Logger.LogDebug("  Application: {0} V{1}/{2} ({3}/{4})", clientInfo.ApplName, clientInfo.ApplNVer, clientInfo.ApplFVer, clientInfo.ApplPTok, clientInfo.ApplHash);
                Logger.LogDebug("  Core Client: {0} V{1}/{2} ({3}/{4})", clientInfo.CoreName, clientInfo.CoreNVer, clientInfo.CoreFVer, clientInfo.CorePTok, clientInfo.CoreHash);
                Logger.LogDebug("  Client Env.: {0} ({1} build)", clientInfo.ConfigEnv, clientInfo.BuildEnv);
                Logger.LogDebug("  Addresses  : {0} ({1},{2})", clientInfo.HostName, clientInfo.HostIpV4, String.Join(",", clientInfo.NetAddrs));

                // exemption list - todo - move to database
                //var exemptions = new List<ExemptClient>
                //                     {
                //                         new ExemptClient() {ConfigEnv = EnvId.DEV_Development, AssmName = "TestMds"},
                //                         new ExemptClient() {ConfigEnv = EnvId.DEV_Development, AssmName = "TestWebMdc"},
                //                         new ExemptClient() {AssmName = "nab.QDS.Workflow.CurveGeneration"}
                //                     };
                // - dev apps
                // - all environments

                //bool serverMode = IsExempt(clientInfo, exemptions.ToArray());
                var sapiLicense = new ServerApiLicense();
                if (true) // (serverMode)
                {
                    Logger.LogDebug("Server-mode: client logon check skipped.");
                    sapiLicense.CustomerAlias       = "";
                    sapiLicense.UUID                = 0;
                    sapiLicense.SID                 = 0;
                    sapiLicense.SIDInstance         = 0;
                    sapiLicense.TerminalSID         = 0;
                    sapiLicense.TerminalSIDInstance = 0;
                }
                //else
                //{
                //    _Logger.LogDebug("Multi-user-mode: logon check required.");
                //    sapiLicense.CustomerAlias = requestParams.GetValue<string>(MdpConfigName.Bloomberg_CustName, "unknown");
                //    sapiLicense.UUID = requestParams.GetValue<int>(MdpConfigName.Bloomberg_UUID, 0);
                //    sapiLicense.SID = requestParams.GetValue<int>(MdpConfigName.Bloomberg_SID, 0);
                //    sapiLicense.SIDInstance = requestParams.GetValue<int>(MdpConfigName.Bloomberg_SidN, 0);
                //    sapiLicense.TerminalSID = requestParams.GetValue<int>(MdpConfigName.Bloomberg_TSID, 0);
                //    sapiLicense.TerminalSIDInstance = requestParams.GetValue<int>(MdpConfigName.Bloomberg_TSidN, 0);
                //    IPAddress ipAddress = IPAddress.Parse(clientInfo.HostIpV4);
                //    TerminalMonitor.LogonStatus logonStatus
                //        = TerminalMonitor.Instance.GetLogonStatus(sapiLicense, ipAddress, 5000);
                //    if (logonStatus != TerminalMonitor.LogonStatus.LoggedOn)
                //        throw new ApplicationException(String.Format(
                //            "Bloomberg user (uuid={0}) is not logged on at terminal ({1})", sapiLicense.UUID, ipAddress));
                //}
                req.License = sapiLicense;

                // load instrument ids
                foreach (string instrId in instrFieldCodeSet.InstrumentIds)
                {
                    req.Securities.Add(instrId);
                }

                // load field names
                foreach (string fieldName in instrFieldCodeSet.FieldNames)
                {
                    req.Fields.Add(MarketDataAdapter.FieldTable[fieldName]);
                }

                // call Bloomberg
                if (requestType == MDSRequestType.Realtime)
                {
                    // realtime requests
                    MarketDataAdapter.SendRequest(req);
                    // save subsmap
                    lock (SubsMapExternal)
                    {
                        var subsMap = new RealtimeRequestMap(
                            requestId, req.RequestId.ToString(), requestParams, subsExpires, requestContext);
                        SubsMapExternal.Add(req.RequestId, subsMap);
                    }
                    Logger.LogDebug("[Sent][ReqId={0}][SubsId={1}]", req.RequestId, requestId);
                    return(null);
                }
                // snapshot (non-realtime) requests
                Reply reply = MarketDataAdapter.SynchronousRequest(req, 30000);
                // check for errors
                if (reply == null)
                {
                    throw new ApplicationException(
                              String.Format("SynchronousRequest() returned null!"));
                }
                if (reply.ReplyError != null)
                {
                    throw new ApplicationException(
                              $"SynchronousRequest() failed: {reply.ReplyError.DisplayName}: {reply.ReplyError.Description}");
                }
                // process provider results
                Dictionary <string, BasicQuotation> providerResults = BuildProviderResultsIndex(reply);
                results.AddRange(ConvertProviderResultsToStandardValuations(providerResults, requestContext));
            } // foreach request page
            return(new MDSResult <QuotedAssetSet>
            {
                Result = new QuotedAssetSet {
                    assetQuote = results.ToArray()
                }
            });
        }