347 lines
24 KiB
MQL5
347 lines
24 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| currencyArbitrage_demo.mq5 |
|
|
//| Copyright © 2018, Amr Ali |
|
|
//| https://www.mql5.com/en/users/amrali |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright © 2018, Amr Ali"
|
|
#property link "https://www.mql5.com/en/users/amrali"
|
|
#property version "1.000"
|
|
#property description "Currency arbitrage demo using my fast 'circular k-permutations' algorithm."
|
|
#property description " "
|
|
#property description "Minimum arbitrage rate:"
|
|
#property description " "
|
|
#property description "for profitable trading, you need to input values > 1.0 (e.g., 1.00008)"
|
|
#property description "in the script window to overcome spreads and commission fees."
|
|
#property description " "
|
|
#property description "for testing only, please provide values < 1.0 (e.g., 0.99966 or less)"
|
|
#property description "in the script window to display more arbitrage opportunities"
|
|
#property strict
|
|
#property script_show_inputs
|
|
|
|
#include <Permut_library.mqh>
|
|
|
|
// you could also try ring sizes 4 and 5
|
|
input int InpRingSize=3; // RingSize
|
|
|
|
// for profitable trading, you need to input values > 1.0 (e.g., 1.00008)
|
|
// in the script window to overcome spread and commission fees.
|
|
|
|
// for testing only, you may need to input values < 1.0 (e.g., 0.99966 or less)
|
|
// in the script window to display more arbitrage opportunities
|
|
|
|
input double InpArbitrageThreshold=0.99966; // Minimum arbitrage rate
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
// Arbitrage is simultaneously buying and selling the same asset at
|
|
// different prices to get immediate profit.
|
|
|
|
// Arbitrage is the practice of buying an asset and selling it for
|
|
// a higher price in another market or area to take advantage of a
|
|
// difference in price. (buy low and sell high)
|
|
|
|
// Execution risk
|
|
// Triangular aribitrage oppotunities last for few milliseconds, therefore
|
|
// this type of arbitrage is only profitable for institutional traders.
|
|
// Profits from a triangular arbitrage strategy are small but consistent
|
|
// to those who are quickest to spot and act on the imbalance. Because
|
|
// this type of "risk free" tri arb is extremely time sensitive, even a
|
|
// small delay in order placement is enough to nullify any potential profit.
|
|
|
|
// Fees
|
|
// unfortunately, you have to pay fees every time you change money, and
|
|
// these would more than wipe out your profit.
|
|
|
|
// For further reading:
|
|
// https://apiaryfund.com/forum/triangular-arbitrage-basics
|
|
// https://www.kreslik.com/forums/viewtopic.php?t=307
|
|
// http://forum.vamist.ro/blog/2/entry-6-evolution-and-principles-of-the-intercross-arbitrage/
|
|
|
|
//+-----------------------------------------------------------+
|
|
// How to use exchange rates with arbitrage
|
|
|
|
// EUR/USD exchange rate is 1.1898 / 1.1899
|
|
// so, you sell 1 EUR @1.1898 USD (Bid price)
|
|
// so, you buy 1 EUR @1.1899 USD (Ask price)
|
|
|
|
// Convert EUR->USD
|
|
// = SELL euros
|
|
// USD units you get = EUR units x Bid
|
|
// Convert 1 unit left to right (sell pair): multiply x low rate (MODE_BID)
|
|
|
|
// Convert EUR<-USD
|
|
// = BUY euros
|
|
// EUR units you get = USD units x 1 / Ask
|
|
// Convert 1 unit right to left (buy pair): divide / high rate (MODE_ASK)
|
|
//+-----------------------------------------------------------+
|
|
|
|
#define V 8
|
|
|
|
// Priority ranking of major currencies in forex currency pairs.
|
|
// For example, EUR always comes first in all the 7 euro pairs.
|
|
// USD comes last in 4 usd pairs, and comes first in other 3 pairs.
|
|
|
|
string arrCcy[V] = {"EUR", "GBP", "AUD", "NZD", "USD", "CAD", "CHF", "JPY"};
|
|
int mapping[V] = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
// My fast algorithm for k-angular currency arbitrage:
|
|
|
|
void currencyArbitrageProblem(double &graph[][V],int k)
|
|
{
|
|
// number of arbitrage opportunities found
|
|
int counter=0;
|
|
|
|
// array to receive circular permutations one by one
|
|
int ring[];
|
|
|
|
// choose "k out of n currencies"
|
|
CombinationsIterator<int>comboIt(mapping,k,ring);
|
|
|
|
// For each unordered combination of "k out of n currencies":
|
|
// 1) Consider the first currency as the starting and ending point.
|
|
// 2) Generate all (k-1)! permutations of remaining currencies.
|
|
|
|
do
|
|
{
|
|
// fix the first currency of k, permute next k-1 currencies.
|
|
PermutationsIterator<int>perm(ring,1,k-1);
|
|
|
|
do
|
|
{
|
|
// store current Path weight(arbitrage rate)
|
|
double current_pathweight=1;
|
|
|
|
// compute current path weight
|
|
int s = ring[0];
|
|
int c = s;
|
|
for(int i=1; i<k;++i)
|
|
{
|
|
current_pathweight*=graph[c][ring[i]];
|
|
c=ring[i];
|
|
}
|
|
current_pathweight*=graph[c][s];
|
|
|
|
// found arbitrage opportunity
|
|
if(current_pathweight>InpArbitrageThreshold)
|
|
{
|
|
// print the arbitrage path that visits every currency
|
|
// exactly once and returns back to the starting point.
|
|
printf("#%i: arbitrage opportunity %s = %.5f",++counter,RingToString(ring,"/"),current_pathweight);
|
|
|
|
// decode the arbitrage path
|
|
// https://algs4.cs.princeton.edu/44sp/Arbitrage.java.html
|
|
int st=ring[0];
|
|
int from=st;
|
|
int to=-1;
|
|
current_pathweight=1;
|
|
double stake=1000.00;
|
|
for(int i=1; i<k;++i)
|
|
{
|
|
to=ring[i];
|
|
double weight=graph[from][to];
|
|
printf(" %7.2f %s = %7.2f %s",stake,arrCcy[from],(stake*weight),arrCcy[to]);
|
|
stake*=weight;
|
|
from=to;
|
|
}
|
|
printf(" %7.2f %s = %7.2f %s",stake,arrCcy[to],(stake*graph[to][st]),arrCcy[st]);
|
|
|
|
// decode the currency ring
|
|
s = ring[0];
|
|
c = s;
|
|
for(int i=1; i<k;++i)
|
|
{
|
|
printf(PairToString(graph,c,ring[i]));
|
|
c=ring[i];
|
|
}
|
|
printf(PairToString(graph,c,s));
|
|
|
|
}
|
|
|
|
}
|
|
while(perm.next(ring));
|
|
|
|
}
|
|
while(comboIt.next(ring));
|
|
|
|
if(!counter) Print("No arbitrage opportunity");
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
// reverse mapping of int indices to currency strings
|
|
string RingToString(int &arr[],string separator=",")
|
|
{
|
|
int size=ArraySize(arr);
|
|
if(!size) return "[]";
|
|
string str= "["+arrCcy[arr[0]];
|
|
for(int i = 1; i<size;++i)
|
|
str+=separator+arrCcy[arr[i]];
|
|
str+="]";
|
|
return (str);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
string PairToString(double &graph[][V],int i,int j)
|
|
{
|
|
string strType,strSymb,strRate;
|
|
double dblRate=0;
|
|
|
|
if(i<j)
|
|
{
|
|
// bid (sell) prices above the diagonal line
|
|
strType = "Sell";
|
|
strSymb = arrCcy[i] + "/" + arrCcy[j];
|
|
dblRate = graph[i][j];
|
|
}
|
|
|
|
else
|
|
{
|
|
// ask (buy) prices below the diagonal line
|
|
strType = "Buy";
|
|
strSymb = arrCcy[j] + "/" + arrCcy[i];
|
|
dblRate = 1.0 / graph[i][j];
|
|
}
|
|
|
|
// format the exchange rate decimals
|
|
if(arrCcy[i]=="JPY" || arrCcy[j]=="JPY")
|
|
strRate=StringFormat("%.3f",dblRate);
|
|
else
|
|
strRate=StringFormat("%.5f",dblRate);
|
|
|
|
return StringFormat(" * %s %s at %s", strType, strSymb, strRate);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
/*
|
|
* Return the fully qualified name of a currency pair, and add it to the MarketWatch.
|
|
* If there is no applicable symbol name, it returns the passed symbol name, unchanged.
|
|
*
|
|
* Some Brokers add suffixes at the end of symbol names.
|
|
* This function is useful while you work with symbols
|
|
* other than the current chart symbol. Also, when you
|
|
* get symbol names from user's input, or build symbols
|
|
* dynamically, at runtime.
|
|
*/
|
|
string SymbolSearch(string pSymb)
|
|
{
|
|
if(StringLen(pSymb)==0)
|
|
return (NULL);
|
|
|
|
string symbol_name;
|
|
|
|
/** Get symbol name from MarketWatch. */
|
|
symbol_name=getSymbol(pSymb,true);
|
|
if(StringLen(symbol_name) > 0) return (symbol_name);
|
|
|
|
/** Get symbol name from all. */
|
|
symbol_name=getSymbol(pSymb,false);
|
|
|
|
/** If symbol exists, add symbol to MarketWatch. */
|
|
if(StringLen(symbol_name)>0)
|
|
{
|
|
SymbolSelect(symbol_name,true);
|
|
|
|
printf("adding symbol %s to MarketWatch.",symbol_name);
|
|
|
|
/**
|
|
* There is a time lag before being updated.
|
|
* As a workaround, sleep for 1 second.
|
|
*/
|
|
// Sleep(1000);
|
|
|
|
double bid=0.0;
|
|
uint timeOut = 3000, start = GetTickCount();
|
|
bool success = false;
|
|
do
|
|
{
|
|
success=SymbolInfoDouble(symbol_name,SYMBOL_BID,bid);
|
|
}
|
|
while((!success || bid==0.0) && (GetTickCount()-start)<=timeOut && !IsStopped());
|
|
|
|
// return fully qualified symbol name
|
|
return (symbol_name);
|
|
}
|
|
|
|
// cannot get fully qualified name, so return passed symbol name, unchanged.
|
|
return (pSymb);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
string getSymbol(string pSymb,bool MarketWatch)
|
|
{
|
|
// case-insensitive search
|
|
StringToUpper(pSymb);
|
|
|
|
string symbol;
|
|
|
|
for(int i=0,max=SymbolsTotal(MarketWatch); i<max; i++)
|
|
{
|
|
symbol=SymbolName(i,MarketWatch);
|
|
StringToUpper(symbol);
|
|
|
|
if(StringFind(symbol,pSymb)>-1)
|
|
{
|
|
return (SymbolName(i, MarketWatch));
|
|
}
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
// driver program to test above function
|
|
void OnStart()
|
|
{
|
|
if(InpRingSize<2)
|
|
{
|
|
Alert("Error: Incorrect parameters!");
|
|
return;
|
|
}
|
|
|
|
if(!TerminalInfoInteger(TERMINAL_CONNECTED))
|
|
{
|
|
Alert("Error: Terminal is not connected to a trade server!");
|
|
return;
|
|
}
|
|
|
|
// start stopwatch to benchmark
|
|
ulong s_time=GetMicrosecondCount();
|
|
|
|
// matrix representation of exchange rates graph
|
|
double graph[V][V];
|
|
ArrayInitialize(graph,1);
|
|
|
|
for(int i=0; i<V-1;++i) // row is 'base' currency
|
|
{
|
|
for(int j=i+1; j<V;++j) // col is 'quote' currency
|
|
{
|
|
//string symbol=arrCcy[i]+arrCcy[j];
|
|
string symbol=SymbolSearch(arrCcy[i]+arrCcy[j]);
|
|
double vbid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
|
double vask = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
|
|
|
// cache exchange rates in a matrix for efficiency
|
|
// V^2 cells = V.(V - 1) prices + V diagonal cells
|
|
// bid (sell) prices above the diagonal line
|
|
// ask (buy) prices below the diagonal line
|
|
graph[i][j] = vbid; // eur->usd
|
|
graph[j][i] = 1.0 / vask; // eur<-usd
|
|
}
|
|
}
|
|
|
|
int n = ArraySize(arrCcy);
|
|
int k = InpRingSize;
|
|
|
|
currencyArbitrageProblem(graph,k);
|
|
|
|
// count of circular k-permutations = nPk / k
|
|
printf("Done! examined all the %I64i possible arbitrage rings with %i currencies taken from %i. (%i microseconds)",nPk(n,k)/k,k,n,(GetMicrosecondCount()-s_time));
|
|
}
|
|
//+------------------------------------------------------------------+
|