如何使用C#/ LINQ最佳地计算派生货币汇率转换?

时间:2010-03-11 19:22:51

标签: c# linq currency

 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}
          // ...
 };

鉴于所有货币至少出现一次(作为目标或基础货币)的大量但不完整的汇率清单:我将使用什么算法来获得未直接列出的交易所的汇率?< / p>

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

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

在上面的示例中,派生费率为GBP-> CHF或EUR-> SEK(需要使用转换为EUR-> USD,USD-> CHF,CHF-> SEK)< / p>

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

6 个答案:

答案 0 :(得分:2)

将所有转化的列表添加到单一货币然后将其用于任何转换,这不是更简单吗?所以像(以美元为基础货币):

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]
}

现在,这假设代数是完全交流的。即,如果我转换为EUR-&gt; USD-&gt; GBP,我将获得与从EUR-&gt; GBP转换相同的价格。实际情况可能并非如此,在这种情况下,您需要所有支持的排列。

答案 1 :(得分:2)

首先构建所有货币的图表:

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(n)),但我相信它会起作用。您可以添加许多内容来提高性能(例如,保存每个新的组合速率计算最终会将其转换为有效的O(1))

答案 2 :(得分:1)

有趣的问题!

首先,保持双重/浮点运算。 .NET Decimal type应该足够,并提供更好的精度!考虑到导出的Fx速率的计算需要多个操作链,这种改进的精度可能特别重要。

另一个评论是,引入更简单/更短的汇率列表可能是禁止的,因此目标始终是相同的[真实或虚构]货币。我假设我们应该在可用时使用列出的费率。

所以计算派生率应该成为[简化]网络解决方案,其中

  • 鉴于基本货币和目标货币,我们确定所有 最短路径(从基准到目标),给定列表中的权威(非衍生)费率。 (在所有情况下,我们都希望最短路径为2,但鉴于非常深奥的货币,情况可能并非如此。)
  • 对于这些最短的路径(我认为考虑更长的路径也是荒谬的),我们执行简单的算术转换,并且......
  • 希望确认这些衍生费率都在转换误差的名义边际内,因此取这些利率的平均值
  • 提出一些警告......或者只是通过制作一条圆形路径并在差速器中耙来赚很多钱;-)

答案 3 :(得分:1)

我不知道“双币”的用途是什么......我会忽略它。

尝试: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;
    }

评论

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

获得更便宜的转换

答案 4 :(得分:0)

最直接的算法可能就像Dijkstra的最短路径或您使用该列表生成的图表上的某些内容。由于您事先并不知道路径将持续多长时间,因此LINQ查询可以优雅地解决这个问题。 (不是说它不可能,它可能不是你应该追求的。)

另一方面,如果您知道从任何货币到任何其他货币的路径,并且列表中任何两种货币之间只有一种可能的转换(即,如果USD> EUR和USD&gt;存在CHF,然后EUR&gt; CHF不存在或者您可以忽略它),您可以简单地生成类似双链表和遍历的东西。尽管如此,这不是可以通过LINQ优雅地解决的问题。

答案 5 :(得分:0)

生成所有这些并缓存它们。给定初始设置,此函数将生成所有现有对(在相同列表内),无需图形或递归,通过在迭代时简单扩展初始列表。

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;
}