public static IReadOnlyDictionary<DateTime, DiscountCurve> BulkGetHistoricEodDiscountCurvesFromCarbon(ICarbonClient client,
            List<DateTime> settleDates, string ccy, bool bOis)
        {
            var inputDateKind = settleDates.FirstOrDefault().Kind;
            var dict = new ConcurrentDictionary<DateTime, DiscountCurve>();
            var dates = new List<DateTime>();
            foreach (var settleDate in settleDates)
            {
                DiscountCurve data;
                var key = GetKeyForDiscountCurveCache(settleDate, ccy, bOis);

                if (DicountCurveDataCache.TryGetValue(key, out data))
                {
                    dict[settleDate] = data;
                }
                else if (settleDate <= DateTime.Today)
                {
                    dates.Add(settleDate);
                }
            }

            if (dates.Count == 0) return dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

            var curveName = GetCurveName(ccy, bOis);
            if (string.IsNullOrEmpty(curveName)) return new Dictionary<DateTime, DiscountCurve>();

            try
            {
                var start = dates.Min();
                var end = dates.Max();

                if (dates.Count > 16)
                {
                    start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, DateTimeKind.Utc);
                    end = new DateTime(end.Year, end.Month, end.Day, 0, 0, 0, DateTimeKind.Utc);
                    var datas = client.GetTimeSeriesData(curveName.ToUpperInvariant(), "interestcurves-ot", start, end, "dfs");
                    if (datas != null)
                    {
                        Parallel.ForEach(datas, messagePackObject =>
                        {
                            try
                            {
                                var curve = messagePackObject.Value.ConvertMessagePackObjectToDiscountFactors();
                                if (curve == null) return;
                                var dicountCurve = new DiscountCurve(new FinCADTable(curve, false));

                                var date = new DateTime(messagePackObject.Key.Year, messagePackObject.Key.Month, messagePackObject.Key.Day, 0, 0, 0, inputDateKind);
                                dict.TryAdd(date, dicountCurve);

                                var key = GetKeyForDiscountCurveCache(messagePackObject.Key, ccy, bOis);
                                DicountCurveDataCache.TryAdd(key, dicountCurve);
                            }
                            catch (Exception ex)
                            {
                                Log.ErrorFormat("Failed to convert curve {0} : {1}", curveName, ex);
                            }
                        });
                    }
                }
                else
                {
                    Parallel.ForEach(dates, date =>
                    {
                        try
                        {
                            var curve = GetHistoricEodDiscountCurveFromCarbon(client, date, ccy, bOis);
                            if (curve == null) return;

                            dict.TryAdd(date, curve);
                            var key = GetKeyForDiscountCurveCache(date, ccy, bOis);
                            DicountCurveDataCache.TryAdd(key, curve);
                        }
                        catch (Exception ex)
                        {
                            SLog.log.ErrorFormat("Failed to get curve {0} : {1}", curveName, ex);
                        }
                    });
                }
            }
            catch (Exception ex)
            {
                SLog.log.ErrorFormat("Failed to get curve {0} : {1}", curveName, ex);
            }

            return dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }
        private static IReadOnlyDictionary<string, IReadOnlyDictionary<DateTime, double>> BulkGetHistoricPricesFromCarbonOneBond(ICarbonClient client,
            AnalyticsBondStaticData bondData,
            List<DateTime> dateSeries)
        {
            var dict = new Dictionary<DateTime, double>();
            var dates = new List<DateTime>();

            if (bondData != null)
            {
                foreach (var date in dateSeries)
                {
                    var d = GetBondPricesFromCache(bondData.Id, date);
                    if (d.HasValue)
                    {
                        dict[date] = d.Value;
                    }
                    else if (date <= DateTime.Today)
                    {
                        dates.Add(date);
                    }
                }

                if (dates.Count > 0)
                {
                    var inputDateKind = dateSeries.FirstOrDefault().Kind;

                    var snapSource = GetBondPriceSnapSource(bondData);
                    var end = dates.Max();
                    var start = dates.Min();

                    SLog.log.InfoFormat("Request bond price for {0} days", dates.Count);

                    start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, DateTimeKind.Utc);
                    end = new DateTime(end.Year, end.Month, end.Day, 0, 0, 0, DateTimeKind.Utc);

                    var datas = client.GetTimeSeriesData(bondData.Id, snapSource, start, end, "close");
                    if (datas != null)
                    {
                        foreach (var data in datas)
                        {
                            try
                            {
                                var close = data.Value.AsDouble();
                                var date = new DateTime(data.Key.Year, data.Key.Month, data.Key.Day, 0, 0, 0, inputDateKind);
                                dict[date] = close;
                                AddBondPricesToCache(bondData.Id, date, close);
                            }
                            catch (Exception ex)
                            {
                                SLog.log.ErrorFormat("Failed to convert close price for {0} : {1}", bondData.Id, ex);
                            }
                        }
                    }
                }
            }

            return new Dictionary<string, IReadOnlyDictionary<DateTime, double>> { { bondData.Id, dict } };
        }
        private static IReadOnlyDictionary<DateTime, double> GetGcRates(ICarbonClient client, BondAnalytics.Country country, List<DateTime> dates)
        {
            var result = new Dictionary<DateTime, double>();
            var inputDateKind = dates.FirstOrDefault().Kind;

            switch (country)
            {
                case BondAnalytics.Country.US:
                    var datesToRequest = new List<DateTime>();
                    foreach (var date in dates)
                    {
                        var d = GetGcRateFromCache(country, date);
                        if (d.HasValue)
                        {
                            result[date] = d.Value;
                        }
                        else if (date <= DateTime.Today)
                        {
                            datesToRequest.Add(date);
                        }
                    }
                    if (datesToRequest.Count == 0) return result;
                    var nomgcRateDates = datesToRequest.Where(d => d < new DateTime(2016, 6, 25)).ToList();
                    var usgcRateDates = datesToRequest.Where(d => d >= new DateTime(2016, 6, 25)).ToList();
                    if (nomgcRateDates.Count > 0)
                    {
                        var end = nomgcRateDates.Max();
                        var start = nomgcRateDates.Min();

                        SLog.log.InfoFormat("Request gc rates for {0} days", nomgcRateDates.Count);

                        start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, DateTimeKind.Utc);
                        end = new DateTime(end.Year, end.Month, end.Day, 0, 0, 0, DateTimeKind.Utc);
                        var datas = client.GetTimeSeriesData("NomuraGCRate", "eod", start, end, "gcRate");
                        if (datas != null)
                        {
                            foreach (var data in datas)
                            {
                                try
                                {
                                    var gcRate = data.Value.AsDouble();
                                    var date = new DateTime(data.Key.Year, data.Key.Month, data.Key.Day, 0, 0, 0, inputDateKind);
                                    result[date] = gcRate;
                                    AddGcRateToCache(country, date, gcRate);
                                }
                                catch (Exception ex)
                                {
                                    // ignored
                                }
                            }
                        }
                    }

                    if (usgcRateDates.Count > 0)
                    {
                        var end = usgcRateDates.Max();
                        var start = usgcRateDates.Min();

                        SLog.log.InfoFormat("Request gc rates for {0} days", usgcRateDates.Count);

                        start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, DateTimeKind.Utc);
                        end = new DateTime(end.Year, end.Month, end.Day, 0, 0, 0, DateTimeKind.Utc);

                        var datas = client.GetTimeSeriesData("USGCRate", "eod", start, end, "gcRate");
                        if (datas != null)
                        {
                            foreach (var data in datas)
                            {
                                try
                                {
                                    var gcRate = data.Value.AsDouble();
                                    var date = new DateTime(data.Key.Year, data.Key.Month, data.Key.Day, 0, 0, 0, inputDateKind);
                                    result[date] = gcRate;
                                    AddGcRateToCache(country, date, gcRate);
                                }
                                catch (Exception ex)
                                {
                                    // ignored
                                }
                            }
                        }
                    }

                    break;
                case BondAnalytics.Country.DE:
                    break;
                case BondAnalytics.Country.FR:
                    break;
                case BondAnalytics.Country.IT:
                    break;
                case BondAnalytics.Country.ES:
                    break;
                case BondAnalytics.Country.UK:
                    break;
            }
            return result;
        }