Русский Español Português
preview
Forex arbitrage trading: A simple synthetic market maker bot to get started

Forex arbitrage trading: A simple synthetic market maker bot to get started

MetaTrader 5Trading systems |
447 2
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Introduction: From retail trader to institutional thinking

I am often asked how I came to develop TrisWeb_Optimized. The story begins in 2016, when I first plunged into the world of Forex, with the naivety of a newbie and faith in indicator trading. At the time, I still believed in the "holy grail" of trading — a magic combination of indicators that would supposedly turn USD 1,000 into a million in a year.

The revolution in my thinking happened in 2017, when, after a series of painful losses, I began to study how big players actually trade. I meant not those who talk about their "million-dollar earnings" on YouTube, but real institutions – banks, hedge funds, and proprietary trading firms.

And here's what I found: they do not use fancy indicator strategies. They apply mathematical principles, risk management, arbitrage, market making and other approaches based on a fundamental understanding of market mechanisms. That is when I made my decision: I had to trade like a big player, or not trade at all.

I spent the next three years studying institutional trading methods. I immersed myself in the world of intermarket correlations, statistical arbitrage and algorithmic trading. I experimented with Python and MQL, creating prototype systems that mimicked the approaches of large market participants, but were adapted for retail traders with their capital and technology limitations.

In January 2020, on the eve of one of the most turbulent periods in the history of financial markets, Tris_Optimized was born — my answer to the question: "How can a retail trader apply institutional strategies?"

This EA does not attempt to predict market movements, does not rely on technical analysis indicators, and does not require "intuition". Instead, it mathematically calculates potential imbalances between three related currency pairs and places a grid of orders ready to catch these imbalances when they arise.

Over five years of continuous operation in real market conditions, Tris_Optimized has proven its viability. It has survived the pandemic, inflation spikes, interest rate changes, and geopolitical crises — and continues to generate consistent profits (in those rare times when I actually trade, rather than sitting up all night poring over code ideas and the actual codes). This is not a miracle system promising exorbitant returns, but a reliable working tool based on the fundamental principles of institutional trading.

Today, this foundation of institutional thinking allows me to trade successfully in various markets. While I have focused on machine learning strategies in recent years, arbitrage approaches always remain an important part of my trading arsenal. Combining ML statistical methods with the fundamental market patterns used by institutional traders gives me a significant advantage. I was even invited to a small and not widely known hedge fund. Besides me, the fund only has a risk manager and an administrator, and we are just getting ready to launch MetaTrader 5-based infrastructure for hedge funds.

A couple of weeks ago I was reviewing my cloud storage and came across an archive with my old trading robots. Among them was Triss_Optimized, one of my first projects, which, despite its “simplicity” compared to modern ML systems, continues to work and yield results. I ran it on a test account and was surprised - even without optimization for current market conditions, it showed positive results.

That is when I decided to share this code with the community. Not because it's some kind of revolutionary system that will make you a millionaire in a month, but because this code is a good example of how you can apply an institutional approach to trading as a retail trader. It is a kind of bridge between two worlds that can help you make the same transition that I once made.

In this article, I will reveal the mechanics of TrisWeb_Optimized, explain its architecture, and share my experience with its setup and optimization. Ready to dive into the world of professional algorithmic trading? Then let's begin.


Three-currency forex arbitrage: The EURUSD-GBPUSD-EURJPY triangle

Let's start with the fact that triangular arbitrage in Forex is a real ghost hunt. Imagine this situation: you are simultaneously monitoring EURUSD, GBPUSD and EURJPY, trying to catch a fleeting price imbalance - when a closed loop EUR→USD→GBP→EUR will give you at least a couple of pips of net profit. But the truth is that pure arbitrage opportunities are as rare as unicorns in downtown Moscow. Institutions, with their high-frequency algorithms and direct access to liquidity, eat up these opportunities faster than you can blink.

That is why I created TrisWeb_Optimized — an EA that does not wait for ideal conditions, but instead builds a three-dimensional web of orders ready to capture profits from natural market fluctuations. This is not classical arbitrage in the academic sense - it is a guerrilla strategy of a retail trader that exploits the mathematical relationships between three currency pairs.


Synthetic currency pairs in MQL5: A secret door to arbitrage

What is a synthetic currency pair? It is a phantom, a mathematical abstraction that we construct from real traded instruments. For example, given the EURUSD and GBPUSD quotes, we can calculate a synthetic EURGBP, which in theory should exactly match the real EURGBP. But in the real world there are always discrepancies, and they are our daily bread.

TrisWeb_Optimized does not create synthetics explicitly, but plays on their invisible strings. Here is a snippet of code where we get the current prices for each symbol—the first step in hunting for imbalances:

// Get the current prices for each symbol
double priceSymbol1 = GetCurrentPrice(Symbol1);
double priceSymbol2 = GetCurrentPrice(Symbol2);
double priceSymbol3 = GetCurrentPrice(Symbol3);

These three strings are the entry point into a multidimensional space of possibilities, where the slightest discrepancies turn into real money.


TrisWeb_Optimized architecture: Machine that survived the pandemic

When I wrote the first strings of TrisWeb_Optimized in January 2020, the world was on the brink of a pandemic, market turmoil, and insane volatility. No one could have predicted that this simple EA would face a baptism of fire in some of the most extreme market conditions in decades. And you know what? It survived. Moreover, it flourished.

The heart of the system beats in the OnTick() function — it is triggered every time the price changes, analyzing the entire picture on three fronts simultaneously:

void OnTick()
{
   // Calculate orders and profits
   int ordersCount1 = 0, ordersCount2 = 0, ordersCount3 = 0;
   double profit1 = 0, profit2 = 0, profit3 = 0;
   
   // Count pending orders
   CountPendingOrders(ordersCount1, ordersCount2, ordersCount3);
   
   // Calculate open positions and their profit
   CountOpenPositions(ordersCount1, ordersCount2, ordersCount3, profit1, profit2, profit3);
   
   // ... rest of the code
}

It would seem like a normal code. But behind these strings lies an elegant modular architecture — each aspect of the system is isolated as a separate function, like a separate organ in a well-coordinated organism. No confusion, no disorder - just a clear division of responsibilities.


Adjustment of grid parameters: A dance with volatility

Forex is like an ocean – EURUSD flows slowly and majestically like deep currents, while EURJPY can explode in a sudden tsunami. Trying to trade these pairs with the same parameters is a sure path to disappointment. This is why TrisWeb_Optimized allows customizing the grid parameters for each tool:

input group "Step Settings"
input int Step01_Symbol1 = 40;      // Initial step for Symbol1 (in points)
input int Step02_Symbol1 = 60;      // Step between orders for Symbol1 (in points)
input int Step01_Symbol2 = 40;      // Initial step for Symbol2 (in points)
input int Step02_Symbol2 = 60;      // Step between orders for Symbol2 (in points)
input int Step01_Symbol3 = 40;      // Initial step for Symbol3 (in points)
input int Step02_Symbol3 = 60;      // Step between orders for Symbol3 (in points)

Over the five years I have been working with this system, I have developed a unique ritual: for calm pairs like EURUSD, I use standard steps of 40 and 60 points, while for fiery JPY crosses, I increase them to 50 and 80. It is like adjusting the sensitivity of your fishing rod for different types of fish - with the same approach, you will either catch everything or nothing.


Lot size optimization: Adaptive system DNA

Fixed lot size is the beginner's way. A professional knows: volume must breathe along with the account balance. TrisWeb_Optimized can do this thanks to the CalculateOptimalLotSize() function, which I consider my little masterpiece:

double CalculateOptimalLotSize(string symbol, double baseSize)
{
   if(!AutoLotOptimization)
      return baseSize;
      
   double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
   double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   // ... the rest of the calculation code
}
This feature is a real Swiss army knife. It takes into account the current balance, the set risk percentage, the pip value for a specific symbol, the broker's restrictions on the minimum lot size and step, and also — and this is my special pride — automatically adjusts calculations for exotic currencies:
// Adjustment for pairs with JPY and other exotic currencies
string quoteCurrency = StringSubstr(symbol, 3, 3);
if(quoteCurrency == "JPY" || quoteCurrency == "XAU" || quoteCurrency == "XAG")
   pointCost *= 100.0;

For the uninitiated, the value of a pip in pairs with the Japanese yen is 100 times less than in major pairs. Forget about this adjustment, and your robot will either trade microscopic volumes or ruin you in one session.


EA operation time management: Circadian rhythm algorithm

The Forex market operates 24/7, but that does not mean your EA should do the same. There are times when it is best to step aside — the overnight session, with its wide spreads and unnatural movements, often brings more problems than opportunities. TrisWeb_Optimized has a built-in "circadian rhythm" thanks to the IsWorkTime() function:

bool IsWorkTime()
{
   MqlDateTime currentTime;
   TimeToStruct(TimeLocal(), currentTime);
   
   datetime currentTimeSeconds = HoursMinutesToSeconds(currentTime.hour, currentTime.min);
   datetime startTimeSeconds = HoursMinutesToSeconds(StartTimeHour, StartTimeMin);
   datetime endTimeSeconds = HoursMinutesToSeconds(EndTimeHour, EndTimeMin);
   
   return (startTimeSeconds <= currentTimeSeconds && currentTimeSeconds <= endTimeSeconds);
}
The CheckNewDayAndWorkTime() function adds another human trait to the EA — the ability to "start with a clean slate" every new day:
void CheckNewDayAndWorkTime()
{
   if(IsNewDay && IsNewDayReset && IsWorkTime())
   {
      DeleteAllOrders();
      IsNewDay = false;
   }
   
   if(!IsWorkTime())
   {
      IsNewDay = true;
   }
}

This is especially valuable in the long term — the system does not accumulate a backlog of outdated orders, but instead constantly updates the grid in accordance with current market conditions.


Algorithm for exiting positions: The art of leaving on time

In trading, as in life, it is important not only to enter at the right time, but also to exit at the right time. TrisWeb_Optimized uses a simple but efficient criterion - achieving a given level of total profit:

// Total number of orders
int totalOrders = ordersCount1 + ordersCount2 + ordersCount3;
double totalProfit = profit1 + profit2 + profit3;

// If there are no orders, create them; if there are, check for profit.
if(totalOrders == 0)
{
   if(IsWorkTime())
   {
      PlaceInitialOrders();
   }
}
else if(totalProfit > Profit)
{
   DeleteAllOrders();
}
But the devil, as always, is in the details. The system takes into account not just the "bare" profit, but the total result, taking into account swaps and commissions:
double swap = PositionGetDouble(POSITION_SWAP);
double profit = PositionGetDouble(POSITION_PROFIT);
double commission = PositionGetDouble(POSITION_COMMISSION);
double totalProfitForPosition = profit + swap + commission;

This is critical for long-term performance, especially if you hold positions overnight when swaps are charged. Otherwise, the system may ignore profitable combinations or, conversely, consider those profitable that actually bring losses due to negative swaps.


Fine-tuning the order execution: The pact with the market devil

In the world of high-frequency trading, execution quality can be the difference between profit and loss. TrisWeb_Optimized uses low-level MQL5 structures for maximum control over the order placement and execution process:

bool OpenBuyStop(string symbol, double volume, double openPrice)
{
   MqlTradeRequest request = {};
   MqlTradeResult result = {};
   
   request.action = TRADE_ACTION_PENDING;
   request.symbol = symbol;
   request.volume = volume;
   request.type = ORDER_TYPE_BUY_STOP;
   request.price = openPrice;
   request.deviation = Deviation;
   request.magic = EXPERT_MAGIC;
   
   if(UseCommentsInOrders)
      request.comment = OrderComment;
   
   if(!OrderSend(request, result))
   {
      Print("OrderSend error ", GetLastError(), " retcode: ", result.retcode);
      return false;
   }
   
   return true;
}
Particular attention should be paid to the Deviation parameter – it determines how far from the requested price the order can be executed:
input int Deviation = 3;          // Acceptable price deviation

This is your agreement with the market devil: "I am willing to accept performance that is 3 points worse than what I requested, but no more." During periods of high volatility, this can be the difference between a filled order and a missed opportunity. Too small a value and your orders will be rejected due to requotes, too large and you risk getting executed at an unexpectedly bad price.


Real-time information display and analysis: the trader's eyes and ears

When I started developing TrisWeb_Optimized, I realized that one of the key problems with many EAs is that they operate as a "black box," preventing the trader from understanding what is going on. Therefore, I added detailed information output via the DisplayInfo() function:

void DisplayInfo(int totalOrders, double totalProfit, 
                double profit1, double profit2, double profit3,
                int count1, int count2, int count3)
{
   string info = "";
   
   if(AutoLotOptimization)
   {
      double lot1 = CalculateOptimalLotSize(Symbol1, Lot_Symbol1);
      double lot2 = CalculateOptimalLotSize(Symbol2, Lot_Symbol2);
      double lot3 = CalculateOptimalLotSize(Symbol3, Lot_Symbol3);
      
      info += "Auto Lot: " + Symbol1 + "=" + DoubleToString(lot1, 2) + 
              ", " + Symbol2 + "=" + DoubleToString(lot2, 2) + 
              ", " + Symbol3 + "=" + DoubleToString(lot3, 2) + "\n";
   }
   
   // ... the rest of the information generation code
   
   Comment(info);
}
You can choose between a basic display mode (for those who prefer minimalism) and a detailed analysis, including a breakdown for each currency pair:
input bool DisplayDetailedInfo = false;  // Show detailed information

It is like the difference between an old car's basic dashboard, with just a speedometer and fuel gauge, and a modern cockpit with dozens of gauges and monitors — the choice depends on your driving style.

Here is how one of the old sets behaved:

 


Scaling the system: From a simple bot to a trading empire

TrisWeb_Optimized is not just a ready-made EA, but a foundation for building your own arbitrage empire. Over the five years of its evolution, I have modified it repeatedly, adding new features and optimizing existing ones. Here are some directions, in which you can develop the system:

Integration with Python opens up tremendous possibilities for correlation analysis and the application of machine learning methods. Imagine being able to analyze not just current quotes, but historical patterns of interaction between three currency pairs, predicting the most likely imbalances.

Notifications via the Telegram API add mobility to your strategy — you will always be aware of important events, even when you are away from your trading terminal. This is especially valuable when the EA is running on a remote VPS.

Expanding to more currency pairs makes TrisWeb a full-fledged arbitrage platform. Why limit yourself to three instruments when you can create a network of dozens of interconnected pairs, tracking hundreds of potential arbitrage opportunities?

All these modifications require only minimal changes to the existing code, while maintaining its basic structure and operating logic. It is like building additional rooms in an existing house — the foundation has already been laid, all that remains is to expand the living space.


In lieu of conclusion

Five years ago, when the first version of TrisWeb_Optimized was released, I could not have imagined the path it would take. It has survived market storms, periods of extreme volatility and relative calm. It has evolved from a simple script to a mature trading system. But the most important thing is that it never stopped making a profit.

In the next part of the article, we will delve into the practical aspects of setting up TrisWeb_Optimized, examine real-world examples of the system operation, and share secrets for optimizing parameters for various market conditions. Stay tuned – the most interesting things are yet to come!

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/17424

Attached files |
Triss_Optimized.mq5 (40.02 KB)
Last comments | Go to discussion (2)
Evgen Khenkin
Evgen Khenkin | 31 Mar 2025 at 14:19

Interesting approach. Started as is without optimisation. It keeps the balance, there is an increase in small steps.

If you want, you can move the closing of the grid to the end of the day, before the formation of swap. At present, the grid is closed at the start of trading. Or not to use daily closing at all.

There is definitely room for development and application in complex solutions.

Go on!

Stanislav Korotky
Stanislav Korotky | 15 Jan 2026 at 15:25

The article popped up in the English translation, and I wondered what I had missed in the Russian original in March.

It turned out to be fabulous rubbish. ;-)

Features of Custom Indicators Creation Features of Custom Indicators Creation
Creation of Custom Indicators in the MetaTrader trading system has a number of features.
Introduction to MQL5 (Part 34): Mastering API and WebRequest Function in MQL5 (VIII) Introduction to MQL5 (Part 34): Mastering API and WebRequest Function in MQL5 (VIII)
In this article, you will learn how to create an interactive control panel in MetaTrader 5. We cover the basics of adding input fields, action buttons, and labels to display text. Using a project-based approach, you will see how to set up a panel where users can type messages and eventually display server responses from an API.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Creating Custom Indicators in MQL5 (Part 4): Smart WaveTrend Crossover with Dual Oscillators Creating Custom Indicators in MQL5 (Part 4): Smart WaveTrend Crossover with Dual Oscillators
In this article, we develop a custom indicator in MQL5 called Smart WaveTrend Crossover, utilizing dual WaveTrend oscillators—one for generating crossover signals and another for trend filtering—with customizable parameters for channel, average, and moving average lengths. The indicator plots colored candles based on the trend direction, displays buy and sell arrow signals on crossovers, and includes options to enable trend confirmation and adjust visual elements like colors and offsets.