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)来执行这些衍生转换,可能涉及多种货币跳跃,最好的方法是什么?
答案 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速率的计算需要多个操作链,这种改进的精度可能特别重要。
另一个评论是,引入更简单/更短的汇率列表可能是禁止的,因此目标始终是相同的[真实或虚构]货币。我假设我们应该在可用时使用列出的费率。
所以计算派生率应该成为[简化]网络解决方案,其中
答案 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;
}