21

Arbitrage is the process of using discrepancies in currency exchange values to earn profit.
Consider a person who starts with some amount of currency X, goes through a series of exchanges and finally ends up with more amount of X(than he initially had).
Given n currencies and a table (nxn) of exchange rates, devise an algorithm that a person should use to avail maximum profit assuming that he doesn't perform one exchange more than once.

I have thought of a solution like this:

  1. Use modified Dijkstra's algorithm to find single source longest product path.
  2. This gives longest product path from source currency to each other currency.
  3. Now, iterate over each other currency and multiply to the maximum product so far, w(curr,source)(weight of edge to source).
  4. Select the maximum of all such paths.

While this appears good, i still doubt of correctness of this algorithm and the completeness of the problem.(i.e Is the problem NP-Complete?) as it somewhat resembles the traveling salesman problem.

Looking for your comments and better solutions(if any) for this problem.

Thanks.

EDIT:
Google search for this topic took me to this here, where arbitrage detection has been addressed but the exchanges for maximum arbitrage is not.This may serve a reference.

sud03r
  • 19,109
  • 16
  • 77
  • 96
  • 2
    Is this homework? I know for a fact that this was a problem in the textbook for the algorithms course I TA'ed a few years ago. – Tyler McHenry Feb 17 '10 at 16:32
  • Yeah, it's from Dasgupta's Algorithms (not saying that's the only place it comes up). And I don't see any connection to Traveling Salesman. You're not trying to visit every currency. – Matthew Flaschen Feb 17 '10 at 16:35
  • 2
    Definitely homework. Also the subject isn't helpful. – Select0r Feb 17 '10 at 16:35
  • not sure what you mean by "product path" – amit kumar Feb 17 '10 at 16:49
  • 1
    By product path, he means that the weight of the path is the product of its edge weights instead of the (usual) sum. – Peter Alexander Feb 17 '10 at 17:21
  • "assuming that he doesn't perform one exchange more than once" - can you only use each currency once, or can you only swap currency A for currency B once? Also, I assume you have to wind up back in your home currency. – forefinger Feb 17 '10 at 17:32
  • @forefinger you can't use the exchange X-->Y once again. – sud03r Feb 17 '10 at 18:04
  • While the algorithm will work, in real-life it won't. Firstly to make a lot of money you need a lot of money, and if you had a lot of money you wouldn't be considering this. Secondly tax and what-not would wipe out a lot of your profits, especially if the money crosses physical borders. – graham.reeds Nov 08 '10 at 09:58

5 Answers5

36

Dijkstra's cannot be used here because there is no way to modify Dijkstra's to return the longest path, rather than the shortest. In general, the longest path problem is in fact NP-complete as you suspected, and is related to the Travelling Salesman Problem as you suggested.

What you are looking for (as you know) is a cycle whose product of edge weights is greater than 1, i.e. w1 * w2 * w3 * ... > 1. We can reimagine this problem to change it to a sum instead of a product if we take the logs of both sides:

log (w1 * w2 * w3 ... ) > log(1)

=> log(w1) + log(w2) + log(w3) ... > 0

And if we take the negative log...

=> -log(w1) - log(w2) - log(w3) ... < 0 (note the inequality flipped)

So we are now just looking for a negative cycle in the graph, which can be solved using the Bellman-Ford algorithm (or, if you don't need the know the path, the Floyd-Warshall algorihtm)

First, we transform the graph:

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

Then we perform a standard 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;

Now we check for negative cycles:

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

You can then use the pre array to find the negative cycles. Start with pre[source] and work your way back.

yuri kilochek
  • 12,709
  • 2
  • 32
  • 59
Peter Alexander
  • 53,344
  • 14
  • 119
  • 168
  • 2
    My apologies, I misread the question. Ah, so you are looking for an opportunity cycle in the graph. That calls for the Bellman-Ford algorithm with the edge weights transformed such that the `w' = -ln(w)`. The reason for that transformation is that it reduces the problem to finding a negative cycle in the graph. I will update my solution with the correct code. – Peter Alexander Feb 17 '10 at 17:04
  • @Neeraj: But it's pretty well-known that `log (A*B) = log A + log B`… – kennytm Feb 17 '10 at 17:22
  • 1
    @Neeraj, I knew of the log transformation because this is a well-known problem with a well-known solution. As Kenny says, it is also well known that you can transform products to sums using logarithms. – Peter Alexander Feb 17 '10 at 17:25
  • @Neeraj, I can assure you that this is the correct solution, and evaluating the cycles is not NP Complete. You are correct in saying that there is no shortest path if there are negative cycles, but if there are no negative cycles then the answer to your question is to simply perform no exchanges as you need a cycle (to get back to the original currency) and anything other than a negative cycle would be unprofitable. If you don't believe me, simply google "arbitrage problem" and see for yourself. As I said, this is a well-known problem and this is the solution. – Peter Alexander Feb 17 '10 at 17:40
  • http://www.cs.ucdavis.edu/~amenta/f05/hw5.pdf The difference is that you are not evaluating every cycle, just select ones. – Peter Alexander Feb 17 '10 at 18:13
  • Maximum weight cycle is NP-Hard. See here: http://stackoverflow.com/questions/3874794/cycle-of-maximum-weight-in-a-graph –  Feb 11 '11 at 07:11
6

The fact that it is an NP-hard problem doesn't really matter when there are only about 150 currencies currently in existence, and I suspect your FX broker will only let you trade at most 20 pairs anyway. My algorithm for n currencies is therefore:

  1. Make a tree of depth n and branching factor n. The nodes of the tree are currencies and the root of the tree is your starting currency X. Each link between two nodes (currencies) has weight w, where w is the FX rate between the two currencies.
  2. At each node you should also store the cumulative fx rate (calculated by multiplying all the FX rates above it in the tree together). This is the FX rate between the root (currency X) and the currency of this node.
  3. Iterate through all the nodes in the tree that represent currency X (maybe you should keep a list of pointers to these nodes to speed up this stage of the algorithm). There will only be n^n of these (very inefficient in terms of big-O notation, but remember your n is about 20). The one with the highest cumulative FX rate is your best FX rate and (if it is positive) the path through the tree between these nodes represents an arbitrage cycle starting and ending at currency X.
  4. Note that you can prune the tree (and so reduce the complexity from O(n^n) to O(n) by following these rules when generating the tree in step 1:
    1. If you get to a node for currency X, don't generate any child nodes.
    2. To reduce the branching factor from n to 1, at each node generate all n child nodes and only add the child node with the greatest cumulative FX rate (when converted back to currency X).
Nicholas White
  • 2,702
  • 3
  • 24
  • 28
5

Imho, there is a simple mathematical structure to this problem that lends itself to a very simple O(N^3) Algorithm. Given a NxN table of currency pairs, the reduced row echelon form of the table should yield just 1 linearly independent row (i.e. all the other rows are multiples/linear combinations of the first row) if no arbitrage is possible.

We can just perform gaussian elimination and check if we get just 1 linearly independent row. If not, the extra linearly independent rows will give information about the number of pairs of currency available for arbitrage.

JP_smasher
  • 981
  • 12
  • 15
  • This may be an algorithm for some other form of arbitrage, but almost surely not for cross-currency arbitrage discussed here. Or am I missing something here? – Dmitrii I. Jun 02 '17 at 06:36
1

Take the log of the conversion rates. Then you are trying to find the cycle starting at X with the largest sum in a graph with positive, negative or zero-weighted edges. This is an NP-hard problem, as the simpler problem of finding the largest cycle in an unweighted graph is NP-hard.

amit kumar
  • 20,438
  • 23
  • 90
  • 126
  • It is very unlikely that you found a polynomial time solution for an NP-hard *problem*. Note that Amit said NP-hard *problem* (not algorithm, whatever you think that means). Also note that he said NP-*hard* problem (not complete). –  Feb 17 '10 at 19:19
  • @rgrig.. yes the problem if modeled that way would be NP-complete but it may be possible that there are alternate ways to model the problem which may not be NP-complete. – sud03r Feb 17 '10 at 20:33
  • 1
    @Neeraj To clarify: Finding a way to make profit i.e. finding a positive cycle is in P (Poita_ has a solution for that). But finding the highest profit path is NP-hard. – amit kumar Feb 18 '10 at 09:38
0

Unless I totally messed this up, I believe my implementation works using Bellman-Ford algorithm:

#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();
}
Wolfy
  • 548
  • 2
  • 9
  • 29