Как лучше всего рассчитать конверсию производной валюты с использованием C # / LINQ? - PullRequest
3 голосов
/ 11 марта 2010
 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 ...
 }

В приведенном выше примере производная ставка будет GBP-> CHF или EUR-> SEK (что потребует использования конвертации для EUR-> USD, USD-> CHF, CHF-> SEK)

Несмотря на то, что я знаю, как выполнять преобразования вручную, я ищу аккуратный способ (возможно, с использованием LINQ) для выполнения этих производных преобразований, возможно, с использованием скачков нескольких валют, каков наилучший способ добиться этого?

Ответы [ 6 ]

2 голосов
/ 11 марта 2010

Сначала создайте график всех ваших валют:

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 голосов
/ 11 марта 2010

Не проще ли иметь список всех конверсий в единую валюту, а затем использовать его для любой конвертации? Так что-то вроде (с долларом США в качестве базовой валюты):

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-> USD-> GBP, я получу то же самое, что конвертировать из EUR-> GBP. В действительности это может быть не так, и в этом случае вам понадобится каждая поддерживаемая перестановка.

1 голос
/ 11 марта 2010

Я понятия не имею, для чего эта "двойная валюта" ... я просто проигнорирую ее.

Попытка: 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));

1 голос
/ 11 марта 2010

Интересная проблема!

Во-первых, держитесь подальше от арифметики с двойной / плавающей точкой . .NET Десятичный тип должен быть вполне достаточным и обеспечивать лучшую точность! Такая повышенная точность может быть особенно важной, учитывая тот факт, что для вычисления производных скоростей Fx требуется цепочка из нескольких операций.

Еще одно замечание: возможно, запрещено вводить более простой / короткий список курсов валют, в котором цель всегда является одной и той же [реальной или фиктивной] валютой. Здесь я предполагаю, что мы должны использовать указанный тариф, если он доступен.

Итак, вычисление производных тарифов должно стать [упрощенным] сетевым решением , в результате чего

  • Учитывая базовую и целевую валюты, мы идентифицируем все кратчайшие пути (от базового к целевому), учитывая официальные (не производные) курсы в списке. (Мы можем надеяться, что кратчайший путь будет 2 во всех случаях, но это может быть не так, учитывая очень эзотерические валюты).
  • для каждого из этих кратчайших путей (я думаю, было бы нелепо также учитывать более длинные пути), мы выполняем простое арифметическое преобразование и ...
  • , мы надеемся, подтвердить, что все эти производные курсы находятся в пределах номинальной погрешности конверсии и, следовательно, принять среднее значение этих показателей
  • поднять бдительность ... или просто заработать много денег, используя круговую траекторию и разгребая дифференциал ;-)
0 голосов
/ 03 мая 2016

Создайте их все и кэшируйте их. При заданном начальном наборе эта функция сгенерирует все существующие пары (внутри одного и того же списка) без графиков и рекурсии путем простого расширения начального списка во время итерации.

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;
}
0 голосов
/ 11 марта 2010

Самый простой алгоритм, вероятно, будет выглядеть как кратчайший путь Dijkstra или что-то на графике, который вы генерируете, используя этот список. Поскольку вы заранее не знаете, как долго будет длиться этот путь, на самом деле это не проблема, которая может быть элегантно решена с помощью запроса LINQ. (Не то, чтобы это невозможно, просто, вероятно, это не то, что вы должны преследовать.)

С другой стороны, если вы знаете, что существует путь от любой валюты к любой другой и что возможна только одна возможная конверсия между любыми двумя валютами в списке (т. Е. Если существуют USD> EUR и USD> CHF , тогда EUR> CHF не существует или вы можете игнорировать это), вы можете просто сгенерировать что-то вроде двусвязного списка и обхода. Опять же, это не то, что можно элегантно решить с помощью LINQ.

...