├── src ├── __pycache__ │ ├── Arbitrage.cpython-38.pyc │ └── ForexArbitrage.cpython-38.pyc ├── FA_required_libraries.sh ├── SampleTest1.py └── ForexArbitrage.py └── README.md /src/__pycache__/Arbitrage.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUKANT007/Arbitrage-Finder/HEAD/src/__pycache__/Arbitrage.cpython-38.pyc -------------------------------------------------------------------------------- /src/__pycache__/ForexArbitrage.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SUKANT007/Arbitrage-Finder/HEAD/src/__pycache__/ForexArbitrage.cpython-38.pyc -------------------------------------------------------------------------------- /src/FA_required_libraries.sh: -------------------------------------------------------------------------------- 1 | pip3 install numpy 2 | pip3 install matplotlib.pyplot 3 | pip3 install networkx 4 | pip3 install pandas_datareader 5 | pip3 install forex-python 6 | -------------------------------------------------------------------------------- /src/SampleTest1.py: -------------------------------------------------------------------------------- 1 | import ForexArbitrage 2 | 3 | # v1 = 5 4 | # # Major Currency Pairs (Us Dollar, Euro, Pound Sterling, Swiss Franc, Canadian Dollar) 5 | # tickers1 = ['USD', 'EUR', 'GBP', 'CHF', 'CAD'] 6 | 7 | # g = ForexArbitrage.Graph(v1, tickers1) 8 | # g.Bellman_Ford() 9 | 10 | # v2 = 4 11 | # # Exotic Currency Pairs (Turkish Lira, Mexican Peso, Hungarian Forint, Thai Baht) 12 | # tickers2 = ['TRY', 'MXN', 'HUF', 'THB'] 13 | 14 | # g = ForexArbitrage.Graph(v2, tickers2) 15 | # g.Bellman_Ford() 16 | 17 | v3 = 6 18 | tickers3 = ['EUR', 'USD', 'HUF', 'MXN', 'TRY', 'CAD'] # Mixed Currencies 19 | 20 | g = ForexArbitrage.Graph(v3, tickers3) 21 | g.Bellman_Ford() 22 | 23 | 24 | # v4 = 9 25 | # tickers4 = ['EUR', 'AUD', 'USD', 'CNY', 'NZD', 'HUF', 'MXN', 'CAD', 'TRY'] 26 | # g = ForexArbitrage.Graph(v4, tickers4) 27 | # g.Bellman_Ford() 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arbitrage Finder 2 | 3 | Program that detects arbitrage opportunities in the foreign exchange market using Bellman-Ford algorithm. 4 | 5 | Given as input a list of currencies, the program: 6 | 7 | - Gathers currency pairs through forex-python and organizes them in a matrix data structure. 8 | - Abstracts the exchange market creating a graph model with currency as nodes and exchange rates as edges. 9 | - Finds negative cycles in the graph using Bellman-Ford algorithm, that correspond to an arbitrage opportunity. 10 | - Computes the possible profit given by the concatenation of the negative cycle. 11 | - Displays the graph with the negative cycle highlighted using the networkx library. 12 | 13 | ## Requirements 14 | 15 | ```bash 16 | pip3 install numpy 17 | pip3 install matplotlib.pyplot 18 | pip3 install networkx 19 | pip3 install pandas_datareader 20 | pip3 install forex-python 21 | 22 | 23 | ``` 24 | 25 | ## Screenshots 26 | 27 | [![Graph.png](https://i.postimg.cc/J4G19XYR/Graph.png)](https://postimg.cc/VdPQtJch) 28 | -------------------------------------------------------------------------------- /src/ForexArbitrage.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | import pandas_datareader as pd 4 | import networkx as nx 5 | import matplotlib.pyplot as plt 6 | from forex_python.converter import CurrencyRates 7 | 8 | 9 | class Graph: 10 | 11 | def __init__(self, num, labels): 12 | self.vertices = num 13 | self.arches = [] 14 | self.tickers = labels 15 | self.currency_matrix = np.zeros((num, num)) 16 | 17 | def define_arches(self, s, e, v): # s=start, e=end, v=value 18 | self.arches.append([s, e, v]) 19 | 20 | # Building a matrix data structure and appending edges to graph 21 | def build_graph(self): 22 | c = CurrencyRates() 23 | for i in range(self.vertices): 24 | for j in range(self.vertices): 25 | if i == j: 26 | # The diagonal is always equal to 1 27 | data = 1 28 | else: 29 | # Concatenating --> ex. "USDEUR=X" 30 | aux = str(self.tickers[i] + self.tickers[j] + '=X') 31 | 32 | # Live exchange rate in adj close column 33 | # data = round(pd.get_data_yahoo(aux).iat[-1, -1], 5) 34 | data = round(float(c.get_rate( 35 | self.tickers[i], self.tickers[j])), 5) 36 | 37 | print(aux, data) 38 | 39 | # Keeping track of exchange rates 40 | self.currency_matrix[i][j] = data 41 | self.define_arches(i, j, round(-math.log(data, 10), 5)) 42 | 43 | def Bellman_Ford(self): 44 | 45 | # 1° Creating graph 46 | print('\nCollecting data, computing Bellman Ford algorithm, searching for arbitrage opportunity...') 47 | self.build_graph() 48 | 49 | # 2° Initializing distances between vertices 50 | dist = [float("Inf")] * self.vertices 51 | path = [float("Inf")] * self.vertices 52 | dist[0] = 0 53 | path[0] = 0 54 | profit = 1 55 | 56 | # 3° Relaxing all edges and checking for short distance with nested loops 57 | for _ in range(self.vertices - 1): 58 | for s, e, v in self.arches: 59 | if dist[s] != float("Inf") and dist[s] + v < dist[e]: 60 | dist[e] = dist[s] + v 61 | path[e] = s 62 | 63 | # 4° Detecting negative cycles 64 | Neg_cycles = self.Negative_Cycle(dist, path) 65 | 66 | # 5° Results, if there is a negative cycle --> computing possible profit 67 | if not Neg_cycles: 68 | print("\nNo arbitrage opportunity.") 69 | self.Display_Graph(path, 0, 0) 70 | 71 | else: 72 | 73 | for neg_cycle in Neg_cycles: 74 | print("\nFound negative cycle:") 75 | print(' ' + " --> ".join([self.tickers[i] 76 | for i in neg_cycle[::-1]])) 77 | prec = neg_cycle[-1] 78 | for i in neg_cycle[-2::-1]: 79 | profit *= self.currency_matrix[prec][i] 80 | prec = i 81 | profit = round(profit, 4) 82 | print(" Profit: ", profit) 83 | self.Display_Graph(neg_cycle, profit, 1) 84 | 85 | def Negative_Cycle(self, dist, path): 86 | Neg_cycles = [] 87 | flag = False 88 | for s, e, v in self.arches: 89 | # Verifying distance after the algo has converged 90 | if dist[s] + v < dist[e] and dist[s] != float("Inf"): 91 | neg_cycle = [e, s] 92 | aux = s # auxiliary variable 93 | 94 | while path[aux] not in neg_cycle: # Going backwards in original path 95 | neg_cycle.append(path[aux]) 96 | aux = path[aux] 97 | neg_cycle.append(path[aux]) 98 | 99 | # Selecting valid cycle 100 | if neg_cycle[0] == neg_cycle[-1] and len(neg_cycle) > 3: 101 | Neg_cycles.append(neg_cycle) 102 | flag = True 103 | 104 | if(flag): 105 | return Neg_cycles 106 | else: 107 | return False 108 | 109 | def Display_Graph(self, path, profit, flag): 110 | 111 | path_edges = [] 112 | graph_view = nx.MultiDiGraph() 113 | 114 | for s, e, v in self.arches: 115 | graph_view.add_edge(s, e, weight=round(10**(-v), 4)) 116 | 117 | pos = nx.circular_layout(graph_view) 118 | 119 | if flag == 0: 120 | 121 | plt.title('NO Arbitrage Opportunity, NO Negative Cycle', fontsize=20) 122 | 123 | else: 124 | 125 | # Colouring the negative cycle 126 | for i in range(len(path)-1): 127 | path_edges.append((path[i+1], path[i])) 128 | 129 | plt.text(-1.3, -1.3, "Found Negative Cycle: \n\n" + ' ' + " --> ".join([self.tickers[i] for i in path[::-1]]) 130 | + "\n\nProfit: " + str(profit), 131 | bbox=dict(boxstyle="square", facecolor="white"), size=12.5) 132 | plt.title('ARBITRAGE OPPORTUNITY', fontsize=20) 133 | 134 | edge_labels = dict([((u, v,), d['weight']) 135 | for u, v, d in graph_view.edges(data=True)]) 136 | edge_colors = [ 137 | 'black' if not edge in path_edges else 'red' for edge in graph_view.edges()] 138 | node_colors = ['green' for path in graph_view.nodes()] 139 | 140 | labels = {} 141 | for i in range(len(self.tickers)): 142 | labels[i] = self.tickers[i] 143 | 144 | nx.draw_networkx_edge_labels( 145 | graph_view, pos, label_pos=0.28, edge_labels=edge_labels) 146 | nx.draw(graph_view, pos, node_size=1500, node_color=node_colors, edge_color=edge_colors, with_labels=False, 147 | connectionstyle='arc3, rad = 0.1') 148 | nx.draw_networkx_labels(graph_view, pos, labels, 149 | font_size=16, font_color='black') 150 | 151 | plt.show() 152 | 153 | 154 | if __name__ == "__main__": 155 | 156 | tickers = [] 157 | 158 | print('Insert number of currencies: ') 159 | numV = int(input()) 160 | 161 | print('Insert tickers of currencies (3-letter symbols): ') 162 | 163 | for x in range(numV): 164 | tickers.append(str(input())) 165 | 166 | g = Graph(numV, tickers) 167 | g.Bellman_Ford() 168 | --------------------------------------------------------------------------------