public static SortedDictionary<DateTime, double> GetRepoOISSpread(this ICarbonClient client, BondAnalytics.Country country, List<DateTime> asOfDate, List<DateTime> holidays)
        {
            // Not the most efficient function, but split out from original
            object o = null;
            int nDates = asOfDate.Count;
            //double[,] result = new double[nDates, 2];
            //result[0, 0] = -1.0;
            var discCurves = new SortedDictionary<DateTime, Tuple<DateTime, double>[]>();
            var spreads = new SortedDictionary<DateTime, double>();
            var result = new SortedDictionary<DateTime, double>();
            int nAvDates = 20;

            string ccy = getRepoCCYFromCountry(country);

            if (ccy == null)
            {
                // This country hasn't been configured yet.
                return result;
            }

            var datelist = new List<DateTime>();
            for (var k = 0; k < nDates; k++)
            {
                var dateSeries = new List<DateTime> { asOfDate[k] };

                for (var i = 1; i < nAvDates; i++)
                {
                    dateSeries.Add(BondAnalytics.PrevBusDay(dateSeries.Last().AddDays(-1), holidays));
                }

                datelist.AddRange(dateSeries);
            }

            datelist = datelist.Distinct().ToList();

            var curvesResult = client.BulkGetHistoricEodDiscountCurves(datelist, ccy, true);
            var gcRatesResult = GetGcRates(client, country, datelist);

            for (int k = 0; k < nDates; k++)
            {
                List<DateTime> dateSeries = new List<DateTime> { asOfDate[k] };
                //  Need 20 business days prior to asOfDate[k]
                for (int i = 1; i < nAvDates; i++)
                {
                    dateSeries.Add(BondAnalytics.PrevBusDay(dateSeries.Last().AddDays(-1), holidays));
                }

                //  Need discount curves for each date. May already have them.
                for (int i = 0; i < nAvDates; i++)
                {
                    if (!discCurves.ContainsKey(dateSeries[i]))
                    {
                        DiscountCurve curve;
                        curvesResult.TryGetValue(dateSeries[i], out curve);
                        if (curve != null)
                            discCurves.Add(dateSeries[i], curve.AsTuples());
                    }
                }
                //  Need repo spread history for each date. May already have it.
                //  If US then use Nomura data, otherwise use ICAP via Carbon
                //                var gmcrProvider = new GenericPricePropertyProvider("gcmr", 1M);
                for (int i = 0; i < nAvDates; i++)
                {
                    if (spreads.ContainsKey(dateSeries[i]))
                        continue;

                    double scaleFactor = 0.01;
                    if (country == BondAnalytics.Country.US)
                    {
                        try
                        {
                            double value;
                            if (gcRatesResult.TryGetValue(dateSeries[i], out value))
                            {
                                o = value;
                            }
                            scaleFactor = 1.0;
                        }
                        catch
                        {
                            //  Missing data
                        }
                    }
                    else if (country == BondAnalytics.Country.DE)
                    {
                        try
                        {
                            o = client.GetCbnTimeSeriesDataCell("GC_GERMAN_SUB_10YR", "eod-icaprepo", dateSeries[i],
                                "wtdRate", new Dictionary<string, string> { { "term", "T-N" } });
                        }
                        catch
                        {
                            //  Missing data
                        }
                    }
                    else if (country == BondAnalytics.Country.FR)
                    {
                        try
                        {
                            o = client.GetCbnTimeSeriesDataCell("GC_FRANCE_FIXED_SUB_10YR", "eod-icaprepo",
                                dateSeries[i], "wtdRate", new Dictionary<string, string> { { "term", "T-N" } });
                        }
                        catch
                        {
                            //  Missing data
                        }
                    }
                    else if (country == BondAnalytics.Country.IT)
                    {
                        try
                        {
                            o = client.GetCbnTimeSeriesDataCell("GC_ITALY_CCP_ALL_BONDS", "eod-icaprepo", dateSeries[i],
                                "wtdRate");
                        }
                        catch
                        {
                            //  Missing data
                        }
                    }
                    else if (country == BondAnalytics.Country.ES)
                    {
                        try
                        {
                            o = client.GetCbnTimeSeriesDataCell("GC_CNET_SPAIN", "eod-icaprepo", dateSeries[i],
                                "wtdRate");
                        }
                        catch
                        {
                            //  Missing data
                        }
                    }
                    else if (country == BondAnalytics.Country.UK)
                    {
                        try
                        {
                            o = client.GetCbnTimeSeriesDataCell("GC_GILT_LCH", "eod-icaprepo", dateSeries[i], "wtdRate",
                                new Dictionary<string, string> { { "term", "O" } });
                        }
                        catch
                        {
                            //  Missing data
                        }
                    }
                    else if (country == BondAnalytics.Country.JP)
                    {
                        try
                        {
                            o = client.GetCbnTimeSeriesDataCell("JPYGCRate", "eod", dateSeries[i], "gcRate");
                        }
                        catch
                        {
                            //  Missing data
                        }
                    }

                    if (discCurves.ContainsKey(dateSeries[i]))
                    {
                        spreads.Add(dateSeries[i],
                            ((o as double?) * scaleFactor ?? 0.0) -
                            Math.Round(
                                (1.0 / discCurves[dateSeries[i]].ElementAt(1).Item2 - 1.0) * 360.0 /
                                (discCurves[dateSeries[i]].ElementAt(1).Item1 -
                                 discCurves[dateSeries[i]].ElementAt(0).Item1).TotalDays, 5));
                    }
                }
                double repoSpread = 0.0;
                for (int i = 0; i < nAvDates; i++)
                {
                    if (spreads.ContainsKey(dateSeries[i]))
                        repoSpread += spreads[dateSeries[i]] / nAvDates;
                }
                //result[k, 0] = repoSpread;
                result[asOfDate[k]] = repoSpread;
            }
            return result;
        }
        //  Alternative that allows for an arbitrary set of forward dates
        public static double[,] GetRepoRate(this ICarbonClient client, BondAnalytics.Country country,
            List<DateTime> asOfDate, List<DateTime> forwardDates, List<DateTime> holidays, bool bCalcRate)
        {
            int nDates = asOfDate.Count;
            double[,] result = new double[nDates, 2];
            result[0, 0] = -1.0;
            var discCurves = new SortedDictionary<DateTime, Tuple<DateTime, double>[]>();
            //var spreads = new SortedDictionary<DateTime, double>();
            int nAvDates = 20;
            String ccy = getRepoCCYFromCountry(country);

            // Check that asOfDates and forwardDates have same length
            if (asOfDate.Count != forwardDates.Count)
                return result;

            // Setup
            var datelist = new List<DateTime>();
            for (var k = 0; k < nDates; k++)
            {
                var dateSeries = new List<DateTime> { asOfDate[k] };

                for (var i = 1; i < nAvDates; i++)
                {
                    dateSeries.Add(BondAnalytics.PrevBusDay(dateSeries.Last().AddDays(-1), holidays));
                }

                datelist.AddRange(dateSeries);
            }

            datelist = datelist.Distinct().ToList();

            var curvesResult = client.BulkGetHistoricEodDiscountCurves(datelist, ccy, true);
            var repoois = GetRepoOISSpread(client, country, datelist, holidays);

            if (repoois.Count < 0) return result; // skip if no repos

            // Enhance results with repo rates
            for (var k = 0; k < nDates; k++)
            {
                if (repoois.ContainsKey(asOfDate[k]))
                {

                    // Setup spread.
                    double repoSpread = repoois[asOfDate[k]];
                    result[k, 0] = repoSpread;

                    // Setup repo rate
                    if (bCalcRate)
                    {
                        // Get Curves...
                        List<DateTime> dateSeries = new List<DateTime> { asOfDate[k] };
                        //  Need 20 business days prior to asOfDate[k]
                        for (int i = 1; i < nAvDates; i++)
                        {
                            dateSeries.Add(BondAnalytics.PrevBusDay(dateSeries.Last().AddDays(-1), holidays));
                        }
                        //  Need discount curves for each date. May already have them.
                        for (int i = 0; i < nAvDates; i++)
                        {
                            if (!discCurves.ContainsKey(dateSeries[i]))
                            {
                                DiscountCurve curve;
                                curvesResult.TryGetValue(dateSeries[i], out curve);
                                if (curve != null)
                                    discCurves.Add(dateSeries[i], curve.AsTuples());
                            }
                        }

                        // Calc repo rate
                        double oisSwap = -1.0;
                        //  Have already caught the case of missing discount curve above.
                        if (discCurves.ContainsKey(asOfDate[k]))
                        {
                            DateTime[] discDfDates = discCurves[asOfDate[k]].Select(x => x.Item1).ToArray();
                            double[] discDfs = discCurves[asOfDate[k]].Select(x => x.Item2).ToArray();

                            BondAnalytics.DayCountType oisDct = (country == BondAnalytics.Country.CA ||
                                                                 country == BondAnalytics.Country.UK
                                ? BondAnalytics.DayCountType.Act365
                                : BondAnalytics.DayCountType.Act360);
                            oisSwap = asOfDate[k] < forwardDates[k]
                                ? BondAnalytics.CalcMMS(asOfDate[k], forwardDates[k], oisDct, 12, 12, discDfDates,
                                    discDfs,
                                    discDfDates, discDfs, holidays, null, null, null, null, null, DateTime.MinValue, 5)
                                : 0.0;
                        }
                        double repoRate = (oisSwap > -1.0 ? oisSwap + repoSpread : -1.0);
                        result[k, 1] = repoRate;
                    }
                }
            }

            return result;
        }
        // Compute repo rates based on an input repo-ois spread
        public static double[,] CalcRepoRatesFromOisSpread(this ICarbonClient client, Series<DateTime, double> repoSpdSrs, BondAnalytics.Country country, List<DateTime> asOfDate, List<DateTime> forwardDates,
            List<DateTime> holidays, Dictionary<DateTime, Tuple<DateTime, double>[]> discCurves = null)
        {
            // Init
            var holSet = new HashSet<DateTime>(holidays);
            var ccy = getRepoCCYFromCountry(country);

            // Checks
            if (asOfDate.Count != forwardDates.Count)
            {
                throw new ArgumentException("#Error: as of dates and forward dates need to have the same length!");
            }

            
            // Get swap curves if not specified
            if (discCurves == null)
            {
                discCurves = new Dictionary<DateTime, Tuple<DateTime, double>[]>();
                var rawCurves = client.BulkGetHistoricEodDiscountCurves(asOfDate, ccy, true);

                foreach (var kvp in rawCurves)
                {
                    discCurves[kvp.Key] = kvp.Value.AsTuples();
                }
            }

            // Get Results
            double[,] results = new double[asOfDate.Count, 2];
            for (int i = 0; i < asOfDate.Count; ++i)
            {
                var dte = asOfDate[i];
                var fwddte = forwardDates[i];
                var spdAttempt = repoSpdSrs.TryGet(dte);
                double repoRate;
                double repoSpread;

                if (spdAttempt.HasValue && !holSet.Contains(dte))
                {

                    // Calc repo rate
                    repoSpread = spdAttempt.Value;
                    double oisSwap = double.NaN;

                    //  Have already caught the case of missing discount curve above.
                    if (discCurves.ContainsKey(dte))
                    {
                        var tups = discCurves[dte];
                        DateTime[] discDfDates = tups.Select(x => x.Item1).ToArray();
                        double[] discDfs = tups.Select(x => x.Item2).ToArray();

                        BondAnalytics.DayCountType oisDct = (country == BondAnalytics.Country.CA ||
                                                             country == BondAnalytics.Country.UK
                            ? BondAnalytics.DayCountType.Act365
                            : BondAnalytics.DayCountType.Act360);

                        oisSwap = dte < fwddte
                            ? BondAnalytics.CalcMMS(dte, fwddte, oisDct, 12, 12, discDfDates,
                                discDfs,
                                discDfDates, discDfs, holidays, null, null, null, null, null, DateTime.MinValue, 5)
                            : double.NaN;
                    }

                    repoRate = double.IsNaN(oisSwap) ? double.NaN : oisSwap + repoSpread;
                }
                else
                {
                    repoRate = double.NaN;
                    repoSpread = double.NaN;
                }

                results[i, 0] = repoSpread;
                results[i, 1] = repoRate;
            }
            return results;
        }