代码之家  ›  专栏  ›  技术社区  ›  chillitom Cee McSharpface

如何使用c_/linq计算导出的汇率转换?

  •  3
  • chillitom Cee McSharpface  · 技术社区  · 14 年前
     class FxRate {
         string Base { get; set; }
         string Target  { get; set; }
         double Rate { get; set; }
     }
    
     private IList<FxRate> rates = new List<FxRate> {
              new FxRate {Base = "EUR", Target = "USD", Rate = 1.3668},
              new FxRate {Base = "GBP", Target = "USD", Rate = 1.5039},
              new FxRate {Base = "USD", Target = "CHF", Rate = 1.0694},
              new FxRate {Base = "CHF", Target = "SEK", Rate = 8.12}
              // ...
     };
    

    给出一个庞大但不完整的汇率列表,其中所有货币至少出现一次(作为目标货币或基础货币):我将使用什么算法来推导未直接列出的汇率?

    我正在寻找一个通用算法的形式:

     public double Rate(string baseCode, string targetCode, double currency)
     {
          return ...
     }
    

    在上面的示例中,导出的汇率为英镑->瑞士法郎或欧元->瑞典克朗(这需要使用欧元->美元,美元->瑞士法郎,瑞士克朗->瑞典克朗的换算)

    虽然我知道如何手动进行转换,但我正在寻找一种整洁的方法(可能使用linq)来执行这些派生转换,可能涉及多个货币跳数,最好的方法是什么?

    6 回复  |  直到 8 年前
        1
  •  2
  •   Joel    14 年前

    首先建立一个所有货币的图表:

    private Dictionary<string, List<string>> _graph
    public void ConstructGraph()
    {
        if (_graph == null) {
            _graph = new Dictionary<string, List<string>>();
            foreach (var rate in rates) {
                if (!_graph.ContainsKey(rate.Base))
                    _graph[rate.Base] = new List<string>();
                if (!_graph.ContainsKey(rate.Target))
                    _graph[rate.Target] = new List<string>();
    
                _graph[rate.Base].Add(rate.Target);
                _graph[rate.Target].Add(rate.Base);
            }
        }
    }
    

    现在使用递归遍历该图:

    public double Rate(string baseCode, string targetCode)
    {
        if (_graph[baseCode].Contains(targetCode)) {
            // found the target code
            return GetKnownRate(baseCode, targetCode);
        }
        else {
            foreach (var code in _graph[baseCode]) {
                // determine if code can be converted to targetCode
                double rate = Rate(code, targetCode);
                if (rate != 0) // if it can than combine with returned rate
                    return rate * GetKnownRate(baseCode, code);
            }
        }
    
        return 0; // baseCode cannot be converted to the targetCode
    }
    public double GetKnownRate(string baseCode, string targetCode) 
    {
        var rate = rates.SingleOrDefault(fr => fr.Base == baseCode && fr.Target == targetCode);
        var rate_i rates.SingleOrDefault(fr => fr.Base == targetCode && fr.Target == baseCode));
        if (rate == null)
            return 1 / rate_i.Rate
        return rate.Rate;
    }
    

    免责声明: 这是未经测试的。此外,我确信这不是解决问题的最有效方法(我认为是),但我相信它会起作用的。为了提高性能,您可以添加一些内容(例如,保存每一个新的合并比率计算,最终将其转换为有效的o(1))

        2
  •  2
  •   Thomas    14 年前

    把所有的货币换算成一种货币,然后用它来换算,难道不简单吗?例如(以美元为基础货币):

    var conversionsToUSD = new Dictionary<string, decimal>();
    
    public decimal Rate ( string baseCode, string targetCode )
    {
        if ( targetCode == "USD" )
            return conversionsToUSD[baseCode];
    
        if ( baseCode == "USD" )
            return 1 / conversionsToUSD[targetCode];
    
        return conversionsToUSD[baseCode] / conversionsToUSD[targetCode]
    }
    

    现在,这假设代数是完全交流的。也就是说,如果我转换为欧元->美元->英镑,我将获得与从欧元->英镑转换相同的收益。实际上可能不是这样,在这种情况下,您需要每个支持的排列。

        3
  •  1
  •   mjv    14 年前

    有趣的问题!

    首先, 远离双/浮点运算 . .NET Decimal type 应该是相当充分和提供更好的精度!考虑到衍生外汇汇率的计算需要一系列的多重操作,这种改进的精度可能特别重要。

    另一种说法是,引入一个更简单/更短的汇率表可能是不受限制的,即目标货币总是相同的[真实或虚构的]货币。我在这里假设我们应该使用列出的价格。

    所以 计算导出的速率应该成为一个[简化的]网络解决方案 由此

    • 给定基础货币和目标货币,我们确定 全部的 这个 最短路径 (从基到目标),给定列表中的权威(非派生)速率。(在所有情况下,我们都希望最短路径是2,但考虑到非常深奥的货币,情况可能并非如此)。
    • 对于这些最短路径中的每一条(我认为也考虑较长的路径是可笑的),我们执行简单的算术转换,并且…
    • 希望能确认这些导出的速率都在转换误差的名义范围内,因此取这些速率的平均值
    • 提高警惕…或者只是通过做一个圆形的路径并利用微分来赚很多钱;-)
        4
  •  1
  •   ANeves    14 年前

    我不知道“双汇”是用来干什么的…我就不管了。

    尝试: List<List<FxRate>> res = Rates("EUR", "CHF"); 产量 {EUR-USD, USD-CHF} .
    看起来很有前途!:)

        public class FxRate
        {
            public string Base { get; set; }
            public string Target { get; set; }
            public double Rate { get; set; }
        }
    
        private List<FxRate> rates = new List<FxRate>
                                        {
                                            new FxRate {Base = "EUR", Target = "USD", Rate = 1.3668},
                                            new FxRate {Base = "GBP", Target = "USD", Rate = 1.5039},
                                            new FxRate {Base = "USD", Target = "CHF", Rate = 1.0694},
                                            new FxRate {Base = "CHF", Target = "SEK", Rate = 8.12}
                                            // ...
                                        };
    
        public List<List<FxRate>> Rates(string baseCode, string targetCode)
        {
            return Rates(baseCode, targetCode, rates.ToArray());
        }
        public List<List<FxRate>> Rates(string baseCode, string targetCode, FxRate[] toSee)
        {
            List<List<FxRate>> results = new List<List<FxRate>>();
    
            List<FxRate> possible = toSee.Where(r => r.Base == baseCode).ToList();
            List<FxRate> hits = possible.Where(p => p.Target == targetCode).ToList();
            if (hits.Count > 0)
            {
                possible.RemoveAll(hits.Contains);
                results.AddRange(hits.Select(hit => new List<FxRate> { hit }));
            }
    
            FxRate[] newToSee = toSee.Where( item => !possible.Contains(item)).ToArray();
            foreach (FxRate posRate in possible)
            {
                List<List<FxRate>> otherConversions = Rates(posRate.Target, targetCode, newToSee);
                FxRate rate = posRate;
                otherConversions.ForEach(result => result.Insert(0, rate));
                results.AddRange(otherConversions);
            }
            return results;
        }
    

    评论?

    附言:你可以用 double minConvertion = res.Min(r => r.Sum(convertion => convertion.Rate));

        5
  •  0
  •   Tanzelax    14 年前

    最直接的算法可能是 Dijkstra 的最短路径或使用该列表生成的图形上的某些内容。因为您事先不知道路径的长度,所以这并不是一个可以通过linq查询优雅地解决的问题。(并不是说这是不可能的,这可能不是你应该追求的。)

    另一方面,如果您知道有一条从任何货币到任何其他货币的路径,并且列表中的任何两种货币之间只有一种可能的转换(即,如果存在美元>欧元和美元>瑞士法郎,则欧元>瑞士法郎不存在或您可以忽略它),则只需生成一些就像一个双链表和遍历。不过,这并不是通过linq可以很好地解决的问题。

        6
  •  0
  •   Tomaz Stih    8 年前

    生成它们并缓存它们。给定初始集,该函数将生成所有现有的对(在同一个列表中),而无需图形或递归,方法是在迭代时简单地展开初始列表。

    public static void CrossRates(List<FxRate> rates)
    {
        for (int i = 0; i < rates.Count; i++)
        {
            FxRate rate = rates[i];
            for (int j = i + 1; j < rates.Count; j++)
            {
                FxRate rate2 = rates[j];
                FxRate cross = CanCross(rate, rate2);
                if (cross != null)
                    if (rates.FirstOrDefault(r => r.Ccy1.Equals(cross.Ccy1) && r.Ccy2.Equals(cross.Ccy2)) == null)
                        rates.Add(cross);
            }
        }
    }
    

    此实用函数将生成单个交叉率。

    public static FxRate CanCross(FxRate r1, FxRate r2)
    {
        FxRate nr = null;
    
        if (r1.Ccy1.Equals(r2.Ccy1) && r1.Ccy2.Equals(r2.Ccy2) ||
            r1.Ccy1.Equals(r2.Ccy2) && r1.Ccy2.Equals(r2.Ccy1)
            ) return null; // Same with same.
    
        if (r1.Ccy1.Equals(r2.Ccy1))
        { // a/b / a/c = c/b
            nr = new FxRate()
            {
                Ccy1 = r2.Ccy2,
                Ccy2 = r1.Ccy2,
                Rate = r1.Rate / r2.Rate
            };
        }
        else if (r1.Ccy1.Equals(r2.Ccy2))
        {
            // a/b * c/a = c/b
            nr = new FxRate()
            {
                Ccy1 = r2.Ccy1,
                Ccy2 = r1.Ccy2,
                Rate = r2.Rate * r1.Rate
            };
        }
        else if (r1.Ccy2.Equals(r2.Ccy2))
        {
            // a/c / b/c = a/b
            nr = new FxRate()
            {
                Ccy1 = r1.Ccy1,
                Ccy2 = r2.Ccy1,
                Rate = r1.Rate / r2.Rate
            };
        }
        else if (r1.Ccy2.Equals(r2.Ccy1))
        {
            // a/c * c/b = a/b
            nr = new FxRate()
            {
                Ccy1 = r1.Ccy1,
                Ccy2 = r2.Ccy2,
                Rate = r1.Rate * r2.Rate
            };
        }
        return nr;
    }