有趣的问题(货币套利)

时间:2010-02-17 16:30:30

标签: c++ c algorithm

套利是利用货币兑换价值差异赚取利润的过程 考虑一个以一定数量的货币X开头的人,经历一系列交易,最后得到更多的X(比他最初的那样)。
给定n种货币和汇率表(nxn),设计一个人应该用来获得最大利润的算法,假设他没有多次执行一次交易。

我想到了这样的解决方案:

  1. 使用修改后的Dijkstra算法查找单一来源最长的产品路径。
  2. 这提供了从源货币到其他货币的最长产品路径。
  3. 现在,迭代所有其他货币并乘以目前为止的最大乘积w(curr,source)(边缘到源的权重)。
  4. 选择所有此类路径的最大值。
  5. 虽然这看起来不错,但我仍然怀疑这个算法的正确性和问题的完整性。(问题是NP-Complete?)因为它有点类似于旅行商问题。

    为此问题寻找您的意见和更好的解决方案(如果有的话)。

    感谢。

    修改:
    谷歌搜索这个主题带我去了here,其中已经解决了套利检测问题,但是最大套利交换没有。这可能会作为参考。

5 个答案:

答案 0 :(得分:29)

Dijkstra's不能在这里使用,因为没有办法修改Dijkstra's以返回最长路径,而不是最短路径。一般来说,longest path problem实际上是您所怀疑的NP-complete,并且与您建议的旅行商问题有关。

您正在寻找的(如您所知)是一个边缘权重大于1的循环,即w 1 * w 2 * w 3 * ...> 1.如果我们记录双方的日志,我们可以重新设想这个问题,将其改为总和而不是产品:

log(w 1 * w 2 * w 3 ...)>日志(1)

=> log(w 1 )+ log(w 2 )+ log(w 3 )...> 0

如果我们采取负面日志......

=> -log(w 1 ) - log(w 2 ) - log(w 3 )...< 0(注意不等式翻转)

所以我们现在只是在图中寻找负循环,可以使用Bellman-Ford算法解决(或者,如果你不需要知道路径,Floyd-Warshall算法)

首先,我们转换图表:

for (int i = 0; i < N; ++i)
  for (int j = 0; j < N; ++j)
    w[i][j] = -log(w[i][j]);

然后我们执行标准的Bellman-Ford

double dis[N], pre[N];

for (int i = 0; i < N; ++i)
   dis[i] = INF, pre[i] = -1;

dis[source] = 0;

for (int k = 0; k < N; ++k)
  for (int i = 0; i < N; ++i)
    for (int j = 0; j < N; ++j)
      if (dis[i] + w[i][j] < dis[j])
        dis[j] = dis[i] + w[i][j], pre[j] = i;

现在我们检查负循环:

for (int i = 0; i < N; ++i)
  for (int j = 0; j < N; ++j)
    if (dis[i] + w[i][j] < dis[j])
      // Node j is part of a negative cycle

然后,您可以使用pre数组来查找负循环。从pre[source]开始,然后继续前进。

答案 1 :(得分:6)

当只有大约150 currencies currently in existence时,这是一个NP难问题的事实并不重要,我怀疑你的外汇经纪人最多只能让你交易20对。因此,我的n种货币算法是:

  1. 制作深度n和分支因子n的树。树的节点是货币,树的根是您的起始货币X。两个节点(货币)之间的每个链接都具有权重w,其中w是两种货币之间的外汇汇率。
  2. 在每个节点,您还应存储累积的外汇汇率(通过将树中高于它的所有外汇汇率计算在一起来计算)。这是根(货币X)与此节点的货币之间的汇率。
  3. 遍历树中表示货币X的所有节点(也许您应该保留指向这些节点的指针列表以加速算法的这个阶段)。只有n^n这些(在大O符号方面非常低效,但请记住,您的n约为20)。具有最高累积外汇汇率的汇率是您的最佳汇率和(如果是正值)这些节点之间通过树的路径表示以货币X开头和结尾的套利周期。
  4. 请注意,您可以修剪树(因此,在步骤1中生成树时,请遵循以下规则,从而将复杂性从O(n^n)降低到O(n)
    1. 如果您到达货币X的节点,请不要生成任何子节点。
    2. 要将分支因子从n减少到1,每个节点都会生成所有n个子节点,并且只添加具有最大累积外汇汇率的子节点(当转换回货币{{1 }})。

答案 2 :(得分:4)

Imho,这个问题有一个简单的数学结构,它适用于一个非常简单的O(N ^ 3)算法。给定一个NxN货币对表格,如果没有套利可行,表格的reduced row echelon form应该只产生1个线性独立的行(即所有其他行是第一行的倍数/线性组合)。

我们可以perform gaussian elimination并检查我们是否只获得1个线性独立的行。如果没有,额外的线性独立行将提供有关套利可用货币对数量的信息。

答案 3 :(得分:1)

记录转换率。然后,您试图找到从X开始的循环,其中具有正,负或零加权边的图中的最大和。这是一个NP难问题,因为在未加权图中找到最大周期的简单问题是NP难。

答案 4 :(得分:0)

除非我完全搞砸了,否则我相信我的实现可以使用Bellman-Ford算法:

#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>


std::vector<std::vector<double>> transform_matrix(std::vector<std::vector<double>>& matrix)
{
    int n = matrix.size();
    int m = matrix[0].size();
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < m; ++j)
        {
            matrix[i][j] = log(matrix[i][j]);
        }
    }
    return matrix;
}

bool is_arbitrage(std::vector<std::vector<double>>& currencies)
{

    std::vector<std::vector<double>> tm = transform_matrix(currencies);

    // Bellman-ford algorithm
    int src = 0;
    int n = tm.size(); 
    std::vector<double> min_dist(n, INFINITY);

    min_dist[src] = 0.0;

    for (int i = 0; i < n - 1; ++i)
    {
        for (int j = 0; j < n; ++j)
        {
            for (int k = 0; k < n; ++k)
            {
                if (min_dist[k] > min_dist[j] + tm[j][k])
                    min_dist[k] = min_dist[j] + tm[j][k];
            }
        }
    }

    for (int j = 0; j < n; ++j)
    {
        for (int k = 0; k < n; ++k)
        {
            if (min_dist[k] > min_dist[j] + tm[j][k])
                return true;
        }
    }
    return false;
}


int main()
{
    std::vector<std::vector<double>> currencies = { {1, 1.30, 1.6}, {.68, 1, 1.1}, {.6, .9, 1} };
    if (is_arbitrage(currencies))
        std::cout << "There exists an arbitrage!" << "\n";
    else
        std::cout << "There does not exist an arbitrage!" << "\n";



    std::cin.get();
}