(*Solution to Exercise 1: Find max flow*)
Maximize[x12+x13+x14,x12==x23+x25&&x13+x23==x36&&x14+x64==x45&&x25+x45==x57&&x36==x64+x67&&0≤x12≤3&&0≤x13≤1&&0≤x14≤1&&0≤x23≤1&&0≤x25≤1&&0≤x36≤3&&0≤x45≤4&&0≤x64≤4&&0≤x57≤4&&0≤x67≤1,{x12,x13,x14,x23,x25,x36,x45,x64,x57,x67},Method->"Simplex"]

(*Solution to Exercise 2: Seek forex arbitrage trading strategy using LP*)
findArbitrage[forex_,invest_,profit_]:=(
(*Creates variables*)
n=Length[forex];
vars=Array[x,{n,n}];
For[i=1,i<=n,i++,x[i,i]=0];
(*Nonnegative trades only*)
allvars=Drop[Union[Flatten[vars]],1];
nonneg=And@@Table[allvars[[i]]>=0,{i,1,Length[allvars]}];
(*Target function to be maximized: USD IN*)
target=forex[[1]].Transpose[vars][[1]];
Print["Target function: ",target];
(*Constraints*)
budget=Total[vars[[1]]]<=invest;(*total budget available: USD OUT*)
trades=Table[Total[vars[[i]]]<=forex[[i]].Transpose[vars][[i]],{i,2,n}];(* OUT <= IN for all other currencies, to maintain nonnegative balances*)
Print["Constraints: "];
Print[budget];
Print[TableForm[trades]];
Print["x">=0];
(*Seeks an arbitrage to earn profit*)
sol=Maximize[target,budget&&And@@trades&&nonneg&&target<=invest+profit,allvars];
If[sol[[1]]>invest,
(Print["Arbitrage found! Investing USD ",invest," you end up with USD ",sol[[1]]];
Print[graphTrades[sol]];),
Print["No arbitrage is possible under these conditions."]];
Print["Optimal value of target function is ",sol[[1]]];
Print["Optimal solution x[i,j] given by:"];
(*Print values of optimal solution x[i,j]*)
TableForm[MapThread[Prepend,{Append[{Range[n]},vars/.sol[[2]]],{"x",Range[n]}}]]
);
(*Creates graph visualization of arbitrage*)
currencies={"USD","EUR","GBP","JPY","BRL"};
graphTrades[sol_]:=(
graph=CompleteGraph[n];
edges={};
For[i=1,i<=n,i++,
For[j=1,j<=n,j++,
If[i!=j,
If[x[i,j]!=0/.sol[[2]],
edges=Join[edges,{Labeled[i\[DirectedEdge]j,Quantity[x[i,j]/.sol[[2]],currencies[[i]]]]}]]];
];];
Return[Graph[Range[n],edges,PlotTheme->"Minimal",GraphLayout->"CircularEmbedding",VertexLabels->Thread[VertexList[graph]->currencies]]];
);

(*Input FOREX data. Real market data from Mar 23, 2026, retrieved from GoogleFinance*)
forex={{0,1.16184501,1.34419442,0.006317738,0.189836171},{0.8607,0,1.156955,0.005438,0.1636},{0.74394,0.86442,0,0.004698,0.1412},{158.2845,183.8815,212.7755,0,30.076},{5.2677,6.112479,7.074898,0.03323977,0}};
forex//TableForm

findArbitrage[forex,100,5]

(*Examples of arbitrages*)
findArbitrage[forex,100,1]
findArbitrage[forex,100,100]
findArbitrage[forex,1,500]

(*Making money out of nothing: zero investment with positive profit*)
findArbitrage[forex,0,100]
findArbitrage[forex,0,5000000]

NOTE 1: This strategy assumes the bid-ask spread is equal to zero on all currencies (perfect liquidity markets),  simultaneous instant transactions without fees, and unbounded immediate availability of funds in all traded currencies (large amounts are needed to produce profits). Of course, despite the real forex data, these are not realistic conditions.

NOTE 2: Without the upper bound given by specifying the desired profit, this LP would be unbounded when an arbitrage opportunity exists. Indeed, without enforcing a cap on profit, or, alternatively, daily trading limits (i.e., upper bounds on x[i,j]), one could theoretically trade in a cyclic pattern generating profits that reach +infinity. A nice exercise is to adapt the code above to enforce a daily trading limit in each currency pair, instead of the cap on profit. What happens to the maximum profit of arbitrage strategies as these daily trading limits are raised?

(*More artificial examples*)
forex=Table[0.8Boole[i!=j],{i,1,n},{j,1,n}];
findArbitrage[forex,100,10]

forex=Table[1.2Boole[i!=j],{i,1,n},{j,1,n}];
findArbitrage[forex,100,100]

(*In this example, with 4 currencies, the maximum profit is "naturally" bounded; in other words, no matter how large the investment is, as long as it is positive, the profit of the strategy is the same (in percentage). Moreover, it is impossible to make money with zero investment. Why does that happen?*)
forex=Transpose[{{0,1.1486, .7003, 133.38},{.8706,0,.6097, 116.12},{1.4279, 1.6401,0,190.45},{.00750 ,.00861, .00525,0}}];
currencies=currencies[[1;;4]];
forex//TableForm
findArbitrage[forex,100,10]
findArbitrage[forex,0,10]
currencies={"USD","EUR","GBP","JPY","BRL"};

(*Hint: adding one more symmetric currency the "natural" bound disappears, because a triangle arbitrage appears, i.e., a 3-node cycle. 
In the previous example, the trading only happened along a 2-node cycle.*)forex=Transpose[{{0,1.1486, .7003, 133.38,1},{.8706,0,.6097, 116.12,1},{1.4279, 1.6401,0,190.45,1},{.00750 ,.00861, .00525,0,1},{1,1,1,1,0}}];
forex//TableForm
findArbitrage[forex,0,100]