Forex Arbitrage Trading: Relationship Assessment Panel
Introduction
My journey in algorithmic Forex trading has been going on for many years — years of trial, error, sleepless nights and rare but vivid victories. During this time, I've tried everything from simple indicators to complex machine learning systems. But one idea kept bothering me: what if the market isn't so perfect? What if there are cracks behind the chaotic flickering of quotes, through which you can look into its essence and probably get some profit? That's how I came to arbitrage analysis, an approach that became a real discovery for me, almost a revelation.
Today I want to share my work, a system for analyzing fair currency prices with an arbitrage assessment written in MQL5 for MetaTrader 5. It all started with a simple question that kept me awake one night: what if we could calculate the "right" exchange rates — not the ones that the market is giving us, but the ones that should be? Out of this curiosity, a panel was born that not only highlights price deviations from their "ideal" values, but also looks for triangular arbitrage paths — the moments when temporary market inefficiencies appear, and we can use it.
Translating theory into code turned out to be a real adventure — with a lot of pitfalls, countless iterations, and moments when I was ready to give up everything. Let me tell you how dry calculations turned into a living system, how the market tested me over and over again, and how I learned to visualize the results so that they speak for themselves. You will see real examples of how the panel works — with numbers, signals, and arbitrage triangles. I will honestly analyze its strengths and weaknesses, share my experience of adapting to different strategies — from single trades to multi-currency grids, which have become a real field for experimentation for me.
My goal is not just to boast of the code or show off in front of the public. I want to give you a tool — something tangible that you can take, adapt and customize, refine for yourself. I hope this panel will not only inspire you with new ideas, but also help you see the market from a different angle — not as chaos, but as a puzzle that can be solved.
Arbitrage Hedge Funds and the Scale of Forex Arbitrage Trading
When we talk about arbitrage in the foreign exchange market, it is impossible not to mention the main players in this area — specialized hedge funds that have turned mathematical analysis of discrepancies into a multibillion-dollar industry. Their experience and scale of operations shed light on how significant our modest arbitrage indicator can be in the right hands.
Titans of Currency Arbitrage
Among the arbitrage hedge funds, several major players stand out in particular. For example, Millennium Management, with assets under management of over $50 billion, uses a multi-strategic approach, where statistical arbitrage in the foreign exchange markets is one of the key areas. Their teams of quantitative analysts are developing models that are conceptually similar to our fair price matrix, but with a much more complex mathematical framework and computing power.
Citadel Securities, Renaissance Technologies and DE Shaw — are other giants that actively use currency arbitrage. Their advantage is not only in capital, but also in technology: direct access to the interbank market, servers with minimal latency (co-location), dedicated communication channels between key trading platforms. Some funds have invested hundreds of millions of dollars in infrastructure that reduces the delay in order execution to microseconds, an area where our MetaTrader 5 indicator cannot compete, unfortunately.
Theoretical Foundations Of Fair Price Analysis and Arbitration
The idea of analyzing fair prices captured me when I thought: what if the market is sometimes wrong? Years of Forex trading have taught me that exchange rates are not just random numbers on a screen. This is a complex network of relationships, where each pair pulls others along like threads in a huge canvas. And if a hole appears somewhere in this canvas — a discrepancy — then there is a chance to make money.
A fair price is, in fact, the "ideal" exchange rate of a currency pair, which should be if the market were absolutely rational. For direct pairs like EURUSD, everything seems simple: take the Bid and Ask, average them, and that's your guideline. But Forex is not just about direct quotes. There are cross-pairs like EURJPY that can be calculated using base pairs. And then I remembered triangular arbitrage, an old but still alive concept:
EUR/USD * USD/JPY = EUR/JPY
If the real EURJPY exchange rate does not match this calculation, the market has either underestimated or overestimated something. I remember the first time I checked it manually — I was sitting with a calculator, comparing quotes on a demo account. Discrepancies were tiny, in fractions of a percent, but the very fact of their existence lit a spark in me. It was like finding a small crack in a wall that no one notices, but through which you can look inside.
Then I went deeper. Direct quotes are just the iceberg tip. To understand the "fairness" of prices, it is necessary to build a matrix of all currency relations. Imagine: you have 8 currencies — EUR, USD, GBP, JPY and so on. Each of them can be expressed through the other through available pairs. For example, if I have EURUSD and GBPUSD, I can calculate EURGBP as:
EURGBP = EURUSD / GBPUSD
And if you add inverse pairs like USDJPY and JPYUSD, then you can refine the calculations indefinitely. I was particularly hooked on the idea of "dynamic calibration". What if, instead of relying on a single pair, we take all the available quotes and "average" them into a single system? For example, for EURJPY, I can use not only EURUSD and USDJPY, but also other paths: EURGBP and GBPJPY, or even longer chains via CHF or AUD. It's like putting together a puzzle where every detail has to fit perfectly into its place.
But how can one calculate one pair from all the others containing the same currencies? Take EURUSD. I have a direct exchange rate — say 1.10. But I can check it through crosses: through GBP (EURGBP and GBPUSD), through JPY (EURJPY and USDJPY), through CHF and so on. Theoretically, all these paths should give the same value. If not, here's a signal for you. I started pondering: what if we build an 8x8 matrix, where each cell represents the exchange rate of one currency relative to another, and force this matrix to "converge" to a single solution? It's like solving a system of equations where each pair is an equation and the variables are currency rates. For example:
EURUSD = 1. 10 USDJPY = 150 EURJPY = EURUSD * USDJPY = 165
If the real EURJPY is 166, then there is a bias somewhere. And there are dozens of such “equations”, according to the number of pairs. You can take the average of all paths, you can search for the median, or you can even use an iterative method, as in solving systems of linear equations, to find the "best" price.
Arbitrage has become the second piece of this puzzle. Triangular arbitrage is when you go through a cycle of three pairs (EURUSD -> USDJPY -> EURJPY) and come back with profit. But I was wondering: what if we look not only for triangles, but also for longer paths? For example, in four or five pairs? The theory suggested that the longer the path is, the more noise is available, but also more interesting the findings are. Every concept has been tested in practice: I ran the data through the terminal, looked at where the calculations were at odds with reality, and would learn from it.
For me, it has become more than just a set of formulas. It's a philosophy: the market is a living organism full of small mistakes, and if you learn to notice them, you can live in harmony with it. Hopefully, my thoughts will inspire you to dig deeper and see the same opportunities.
Implementation Of the Arbitrage Panel: From Idea To Code
Transforming theory into code is always a challenge. This is how I implemented my arbitrage panel in MQL5:
void CalculateFairValues() {
GetCurrentMarketRates();
InitializeCurrencyMatrix();
FillCurrencyMatrixFromDirectRates();
CalculateCrossRatesArbitrage();
CalculateDiscrepancies();
FindArbitrageOpportunities();
GenerateSignals();
UpdateDisplay();
}This function is the heart of the system. It picks up current quotes, builds a matrix of fair rates, and looks for opportunities. I remember my prolonged struggling with CalculateCrossRatesArbitrage — the market did not always suggest direct pairs, and I had to get out with inversions: if(!g_market_rates[i].is_direct) { double temp_bid = 1.0 / g_market_rates[i].ask; double temp_ask = 1.0 / g_market_rates[i].bid; g_market_rates[i].bid = temp_bid; g_market_rates[i].ask = temp_ask; }I implemented a separate logic for arbitrage:
double CalculateArbitrageProfit(int pair1_idx, int pair2_idx, int pair3_idx, double amount) { double result = amount; // Calculation logic through Bid/Ask for a triangle return result; }
This method calculates profit from a cycle like EURUSD->USDJPY->EURJPY. At first, the results were chaotic — spreads ate up all the benefits. To filter out the noise I had to add an InpArbitrageThreshold threshold.
Next is the matrix of fair rates. InitializeCurrencyMatrix sets units on the diagonal (EUR/EUR = 1), and FillCurrencyMatrixFromDirectRates fills it with direct quotes. But the real magic is in CalculateCrossRatesArbitragevoid CalculateCrossRatesArbitrage() {
for(int iterations = 0; iterations < 3; iterations++) {
for(int i = 0; i < g_currencies_count; i++) {
for(int j = 0; j < g_currencies_count; j++) {
if(i == j) continue;
for(int k = 0; k < g_currencies_count; k++) {
if(k == i || k == j) continue;
if(g_currency_matrix[i][k] != 0 && g_currency_matrix[k][j] != 0) {
double triangleRate = g_currency_matrix[i][k] * g_currency_matrix[k][j];
if(g_currency_matrix[i][j] == 0) g_currency_matrix[i][j] = triangleRate;
else g_currency_matrix[i][j] = (g_currency_matrix[i][j] * 0.7 + triangleRate * 0.3);
g_currency_matrix[j][i] = 1.0 / g_currency_matrix[i][j];
}
}
}
}
}
} Here I iterate through currencies for three times, specifying cross-rates through triangles. The idea is simple: if I have EURUSD and USDJPY, I calculate EURJPY. If there is already a direct EURJPY, I take the weighted average — 70% of the old value, 30% of the new one. Three iterations is a compromise between accuracy and speed. I remember testing this on the EURJPY pair: at first discrepancies were 0.1%, after three cycles they were 0.01%. But the market is not waiting, and I stopped there.
But how can one obtain one pair from all the others? Let's say I want a "fair" EURUSD. What I have is:
- EURGBP and GBPUSD: EURUSD = EURGBP * GBPUSD
- EURJPY and USDJPY: EURUSD = EURJPY / USDJPY
- EURCHF and USDCHF: EURUSD = EURCHF / USDCHF
I can take all these paths and calculate the average or median. Or go further and do iterative convergence, as in the Gauss-Seidel method for equation systems. For example:
double CalculatePairFairValue(string base, string quote) { int baseIdx = GetCurrencyIndex(base); int quoteIdx = GetCurrencyIndex(quote); double sum = 0.0; int count = 0; for(int k = 0; k < g_currencies_count; k++) { if(k == baseIdx || k == quoteIdx) continue; if(g_currency_matrix[baseIdx][k] != 0 && g_currency_matrix[k][quoteIdx] != 0) { sum += g_currency_matrix[baseIdx][k] * g_currency_matrix[k][quoteIdx]; count++; } if(g_currency_matrix[k][baseIdx] != 0 && g_currency_matrix[quoteIdx][k] != 0) { sum += g_currency_matrix[quoteIdx][k] / g_currency_matrix[k][baseIdx]; count++; } } return (count > 0) ? sum / count : g_currency_matrix[baseIdx][quoteIdx]; }Every piece of code is a struggle with reality. But when I saw the first signals and triangles, I realized: it's working.
Graphical Interface: Visualization of Features
The code would remain just a set of numbers without clear visualization. I put my heart and soul into the interface:
bool CreateGraphics() { ObjectCreate(0, "FVPanel_Main", OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, "FVPanel_Main", OBJPROP_XDISTANCE, 20); ObjectSetInteger(0, "FVPanel_Main", OBJPROP_YDISTANCE, 20); ObjectSetInteger(0, "FVPanel_Main", OBJPROP_XSIZE, 900); ObjectSetInteger(0, "FVPanel_Main", OBJPROP_YSIZE, 560); ObjectSetInteger(0, "FVPanel_Main", OBJPROP_BGCOLOR, InpBgColor); CreateText("FVPanel_Header", 220, 30, "ANALYSIS OF FAIR CURRENCY PRICES", InpHeaderTextColor, 12, true); CreateText("FVPanel_Signals", 60, 60, "SIGNALS OF DEVIATION FROM FAIR PRICE", InpBuySignalColor, 10, true); CreateText("FVPanel_Arbitrage", 460, 60, “ARBITRAGE OPPORTUNITIES", InpArbitrageColor, 10, true); return true; }
The panel is divided into three zones: deviation signals, arbitrage paths, and exchange rate matrix. Signals like "EURUSD: +0.04% — SELL" are highlighted in green or red, and arbitrage triangles like "EURUSD->USDJPY->EURJPY: +0.02%" immediately show the direction of transactions.

The exchange rate matrix is my personal fetish. It shows all 8 currencies in a table where deviations from fair prices are highlighted in color. I remember spending hours choosing fonts and indentations to make everything look neat.
It is updated through a timer:
void OnTimer() { g_timer_counter++; if(g_timer_counter >= InpUpdateInterval) { CalculateFairValues(); g_timer_counter = 0; } }
This makes the system alive — the data flows in real time, like the market pulse.
Work Results and Honest Assessment
I haven't tried the system on real data yet. I'm making a robot based on it, and this is about the 5th-6th attempt to create a working arbitrage robot. Here is a video of system operation:
Reflections On Fair Prices And Matrices
How can I get fair prices from matrices? It became my obsession. An 8x8 matrix is like a market map. Each cell represents an exchange rate of one currency to another. But the data is noisy: the broker does not provide all pairs, quotes jump, spreads interfere. I started experimenting.
Approach 1: Average for paths
For EURUSD, I take all the crosses:
- EURGBP * GBPUSD
- EURJPY / USDJPY
- EURCHF / USDCHF
Calculating the average. It's simple, but it ignores weights, because EURJPY can be more liquid than EURCHF.
Approach 2: Iterative convergence
As in the Gauss-Seidel method: I update each cell of the matrix until the changes become minimal. The code above (CalculateCrossRatesArbitrage) is a simplified version. But I thought further: what if we add weights in terms of trading volume? Or if we take into account volatility of the pair?
Approach 3: Minimizing errors
We can set an optimization task: minimize the sum of the squares of discrepancies between the current quotes and the matrix. For example:
Error = Σ (MarketRate[i] - MatrixRate[i])^2 Solving through gradient descent or something like SVD. It may already take a long time for MQL5, but in Python I would give it a try.
Approach 4: Long paths
Why only triangles? I can calculate EURUSD through a EURGBP->GBPCHF->CHFUSD chain. The more paths, the more accurate the findings are, but also the more noise is made. In practice, long chains are drowning in spreads and fees, like in a swamp...
These reflections are theoretical yet. The real market is not a textbook, and ideal prices remain a dream. But even getting close to them is already a step forward.
Conclusion
Starting with the idea of fair prices, I did not expect to receive such a system. It was a trial-and-error journey, from bugs in the matrix to frustrations in arbitrage. But every step taught me something.
The market is not chaotic, but a complex mechanism. Fair prices and arbitrage are like a magnifying glass through which you can see its parts. Visualization makes data alive, adaptation makes it flexible. The panel is not perfect, but it already gives me confidence.
I have a lot of plans, from ML to real tests. Forex does not stand still, so neither do I. I hope my experience will inspire you to look behind the market scenes and find your "injustices."
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/17422
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Python-MetaTrader 5 Strategy Tester (Part 04): Tester 101
Central Force Optimization (CFO) algorithm
Developing Market Memory Zones Indicator: Where Price Is Likely To Return
Introduction to MQL5 (Part 36): Mastering API and WebRequest Function in MQL5 (X)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Absolutely so, everything has been checked already. Besides, the broker can adjust the spread size in its favour at any time, which they do.
You are confusing dealers with brokers.
Broker cannot adjust - it is a criminal offence.
Learn the law.
You're confusing dealers with brokers.
A broker cannot adjust - it is a criminal offence.
Learn the rules of the game.
Orders and positions are also confused here, nothing surprising.
You're confusing dealers with brokers.
A broker cannot adjust - it is a criminal offence.
Learn the rules of the game.
All right, let it be a dealer.
and the discrepancy between synthetic and real compared to the standard deviation is often huge....
Can you give me an example from the log of a huge discrepancy ?
There will be a second article about that
I looked at the code from the second article. It is not connected with the first one in any way. Nowhere in the code is there even an attempt to enter below/above the fair price. It is just a usual netter throwing a sheet of orders to the mercy of fate.