preview
Implementing a Fluent Interface Builder Pattern for MQL5 Order Construction

Implementing a Fluent Interface Builder Pattern for MQL5 Order Construction

MetaTrader 5Trading systems |
69 0
Ushana Kevin Iorkumbul
Ushana Kevin Iorkumbul

Introduction

Every order submission in MQL5 begins with populating an MqlTradeRequest structure. The structure contains twenty-one fields covering action type, symbol, volume, price, stop levels, expiration, filling mode, and identifiers. In practice, most fields are interdependent. A market order must not include an expiration timestamp. A limit order requires a valid price distinct from the current market. A stop loss above a long entry will be rejected by the broker or, on permissive servers, normalized in a way that may close the position immediately after opening.

The native approach to populating this structure involves direct field assignment in sequence, with no language-level enforcement of logical consistency between fields. The compiler validates types but not semantics. If a developer sets request.sl = entry_price + 500 * _Point for a buy order, the code remains syntactically valid but fails at execution. It returns an error that may be ignored. In the worst case, server-side normalization can result in an unprotected position, depending on broker rules.

Consider what a minimally correct market buy order population looks like in the raw struct pattern:

MqlTradeRequest request = {};
MqlTradeResult  result  = {};

request.action       = TRADE_ACTION_DEAL;
request.symbol       = _Symbol;
request.volume       = 0.10;
request.type         = ORDER_TYPE_BUY;
request.price        = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
request.sl           = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - 500 * _Point;
request.tp           = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + 1000 * _Point;
request.deviation    = 10;
request.magic        = 100001;
request.comment      = "Manual buy";
request.type_filling = ORDER_FILLING_IOC;

if(!OrderSend(request, result))
   PrintFormat("Error: %d", GetLastError());

This block requires the developer to know, recall, and correctly apply the relationship between action, type, price, sl, and tp without any scaffolding. In a production EA with multiple order types submitted through different code paths, a single copy-paste error in one of these assignments will produce a structurally inconsistent request. The OrderCheck() function can catch some of these errors before server dispatch, but calling it correctly requires additional boilerplate that is often omitted under development pressure.

The fluent builder pattern restructures this process. Instead of assigning struct fields directly, the developer calls self-documenting methods on a builder object in sequence. Each method validates its own input domain before storing the value, and the terminal Send() method performs a final consistency check across all stored fields before constructing the request and dispatching it. The call chain itself documents the order's intent in a form that is readable as prose.

Comparison of coupled manual MqlTradeRequest field assignments (left) versus the step-by-step validated fluent COrderBuilder chain (right).

Figure 1: Comparison of coupled manual MqlTradeRequest field assignments (left) versus the step-by-step validated fluent COrderBuilder chain (right).


Builder Architecture and State Machine Model

The COrderBuilder class operates as a lightweight state machine. Its internal state consists of a shadow copy of the eventual MqlTradeRequest fields, augmented by a set of boolean validity flags that track which builder methods have been called and whether each call produced a valid input. The Send() method evaluates these flags collectively before constructing the final struct.

This flag-based state model is preferable to an exception-throwing approach for two reasons specific to MQL5. First, MQL5 does not support C++-style exception propagation; error handling must be done through return values and state inspection. Second, a builder object that accumulates errors silently and reports them at the point of dispatch gives the caller a single, predictable location to check for configuration failures, rather than requiring error handling at every method call site.

State machine diagram of the builder

Figure 2: State machine diagram of the COrderBuilder, illustrating valid configuration paths, the global INVALID failure state, and the terminal Send() evaluation gate used to efficiently accumulate and handle MQL5 errors.

The builder maintains a separate m_error_message string that accumulates the description of the first validation failure encountered. This string is readable via a GetLastError() accessor method, following the same diagnostic pattern used by MQL5's own trade functions. Callers who need only a success or failure signal check the return value of Send(); callers who need diagnostic output for logging read the error string.

Internal Field Map

The builder's private member layout mirrors the logical groups within MqlTradeRequest rather than its physical field order. This grouping reflects the semantic dependencies that the builder enforces:

Builder Member Maps To Dependency Group Default Value
m_symbol request.symbol Identity "" (invalid until set)
m_volume request.volume Identity 0.0 (invalid until set)
m_magic request.magic Identity 0
m_comment request.comment Identity ""
m_action request.action Direction TRADE_ACTION_DEAL
m_order_type request.type Direction ORDER_TYPE_BUY
m_price request.price Price Level 0.0
m_sl request.sl Stop Level 0.0 (optional)
m_tp request.tp Stop Level 0.0 (optional)
m_deviation request.deviation Execution 10
m_filling request.type_filling Execution ORDER_FILLING_IOC
m_expiration request.expiration Pending Only 0
m_stoplimit_price request.stoplimit Pending Only
0.0

Validity Flag Set

Flag Set By Guards Against
m_symbol_valid Symbol() Empty string, unrecognized symbols
m_volume_valid Volume() Zero, negative, below minimum lot
m_direction_valid Buy(), Sell() Uninitialized direction state
m_price_valid AtMarket(), AtPrice(), AtStop() Zero price, pending order with no price
m_stops_consistent StopLoss(), TakeProfit() Inverted SL/TP relative to direction


Method Design and Validation Logic

Each builder method returns a pointer to the builder instance (COrderBuilder*). This return type is what enables method chaining: the receiver of a method call is the same object, allowing the next method to be appended immediately. In MQL5, this pattern is implemented by returning *this at the end of each chainable method, which dereferences the implicit this pointer to produce a reference to the current instance.

The Pointer Return Mechanism

Each chainable builder method returns COrderBuilder*, a pointer to the current object instance. This return type is what enables method chaining in MQL5. At the end of every chainable method, return this; yields the address of the current instance as a typed pointer, which the compiler resolves correctly as the receiver for the next method call in the chain.

MQL5 does not support reference return types on class methods. A declaration of the form COrderBuilder &Buy() produces a compile-time error in MetaEditor. The pointer return type COrderBuilder* is the correct and only supported substitute for the C++ *this reference pattern in MQL5. In MQL5, method chaining is typically implemented via pointers; calls are written in a chained form depending on the object declaration (instance vs. pointer).

The practical implication for callers is that the builder variable may be declared either as a stack instance or as a heap pointer. For a stack instance, the chain is called on the object directly and the pointer returned by each method is used only as the receiver for the next call, never stored. For a heap-allocated instance, the caller is responsible for deleting the object when finished:

//--- Stack instance: object is destroyed automatically at scope exit
COrderBuilder builder;
MqlTradeResult result = {};
bool ok = builder->Symbol(_Symbol).Volume(0.10).Buy()
                 .AtMarket().StopLoss(sl).TakeProfit(tp)
                 .Send(result);

//--- Heap instance: caller manages lifetime explicitly
COrderBuilder *builder = new COrderBuilder();
MqlTradeResult result = {};
bool ok = builder->Symbol(_Symbol).Volume(0.10).Buy()
                 .AtMarket().StopLoss(sl).TakeProfit(tp)
                 .Send(result);
delete builder;

The Send() method and Reset() method do not return COrderBuilder*. Send() returns bool as the terminal result of the dispatch gate, and Reset() returns void. These two methods intentionally break the chain because they represent state transitions that should not be silently continued: after Send() the caller must inspect the result, and after Reset() the builder is in a blank state that requires a fresh Symbol() call before any other method is meaningful.

Direction Methods and Action Coupling

The Buy() and Sell() methods set both the order type and the action simultaneously, because in MQL5 the action field and the type field are always paired and their pairing depends on the order category. A market buy is TRADE_ACTION_DEAL plus ORDER_TYPE_BUY. A pending buy limit is TRADE_ACTION_PENDING plus ORDER_TYPE_BUY_LIMIT. The builder exposes separate methods for each combination rather than requiring the caller to set these two fields independently, eliminating the pairing error entirely.

Builder Method Sets action Sets type Order Category
Buy() TRADE_ACTION_DEAL ORDER_TYPE_BUY Market execution
Sell() TRADE_ACTION_DEAL
ORDER_TYPE_SELL
Market execution
BuyLimit(double price) TRADE_ACTION_PENDING ORDER_TYPE_BUY_LIMIT Pending limit
SellLimit(double price) TRADE_ACTION_PENDING ORDER_TYPE_SELL_LIMIT Pending limit
BuyStop(double price) TRADE_ACTION_PENDING ORDER_TYPE_BUY_STOP Pending stop
SellStop(double price) TRADE_ACTION_PENDING ORDER_TYPE_SELL_STOP Pending stop
BuyStopLimit(double price, double stoplimit) TRADE_ACTION_PENDING ORDER_TYPE_BUY_STOP_LIMIT Pending stop-limit
SellStopLimit(double price, double stoplimit) TRADE_ACTION_PENDING
ORDER_TYPE_SELL_STOP_LIMIT Pending stop-limit

Stop Level Validation

Stop loss and take profit validation is the most structurally significant check the builder performs, because the error here is directional and therefore cannot be caught by range checks alone. A stop loss of 1.0800 is numerically valid in isolation. Whether it is logically valid depends entirely on whether the order is a buy or a sell and what the entry price is.

The StopLoss() method defers its consistency check to the internal ValidateStops() helper, which is also called by TakeProfit() and by Send(). This deferred design is necessary because the developer may call StopLoss() before the price is finalized for a pending order, or before calling Buy() versus Sell(). The builder stores the raw value immediately and re-runs the consistency check each time a related field changes.

bool COrderBuilder::ValidateStops()
  {
//--- Skip validation if direction has not been established
   if(!m_direction_valid)
      return(true);

   double entry = (m_price > 0.0) ? m_price : SymbolInfoDouble(m_symbol, SYMBOL_ASK);
   if(m_order_type == ORDER_TYPE_SELL || m_order_type == ORDER_TYPE_SELL_LIMIT ||
      m_order_type == ORDER_TYPE_SELL_STOP || m_order_type == ORDER_TYPE_SELL_STOP_LIMIT)
      entry = (m_price > 0.0) ? m_price : SymbolInfoDouble(m_symbol, SYMBOL_BID);

   bool is_buy = (m_order_type == ORDER_TYPE_BUY || m_order_type == ORDER_TYPE_BUY_LIMIT ||
                  m_order_type == ORDER_TYPE_BUY_STOP || m_order_type == ORDER_TYPE_BUY_STOP_LIMIT);

   double point        = SymbolInfoDouble(m_symbol, SYMBOL_POINT);
   long   stops_level  = SymbolInfoInteger(m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
   double min_distance = stops_level * point;

   if(m_sl > 0.0)
     {
      if(is_buy && m_sl >= entry)
        {
         m_error_message = StringFormat(
                           "StopLoss %.5f is at or above buy entry %.5f.", m_sl, entry);
         m_stops_consistent = false;
         return(false);
        }
      if(!is_buy && m_sl <= entry)
        {
         m_error_message = StringFormat(
                           "StopLoss %.5f is at or below sell entry %.5f.", m_sl, entry);
         m_stops_consistent = false;
         return(false);
        }
      if(MathAbs(entry - m_sl) < min_distance)
        {
         m_error_message = StringFormat(
                           "StopLoss distance %.5f is below minimum stops level %ld points.",
                           MathAbs(entry - m_sl) / point, stops_level);
         m_stops_consistent = false;
         return(false);
        }
     }

   if(m_tp > 0.0)
     {
      if(is_buy && m_tp <= entry)
        {
         m_error_message = StringFormat(
                           "TakeProfit %.5f is at or below buy entry %.5f.", m_tp, entry);
         m_stops_consistent = false;
         return(false);
        }
      if(!is_buy && m_tp >= entry)
        {
         m_error_message = StringFormat(
                           "TakeProfit %.5f is at or above sell entry %.5f.", m_tp, entry);
         m_stops_consistent = false;
         return(false);
        }
      if(MathAbs(entry - m_tp) < min_distance)
        {
         m_error_message = StringFormat(
                           "TakeProfit distance %.5f is below minimum stops level %ld points.",
                           MathAbs(entry - m_tp) / point, stops_level);
         m_stops_consistent = false;
         return(false);
        }
     }

   m_stops_consistent = true;
   return(true);
  }

The broker's SYMBOL_TRADE_STOPS_LEVEL constraint is checked here explicitly. This is a minimum distance in points that the broker enforces between the order price and any stop or take profit level. Submissions that violate this constraint return retcode 10016 (TRADE_RETCODE_INVALID_STOPS) from the server. Catching this in the builder before server dispatch eliminates a round-trip failure and its associated handling cost.

Valid versus invalid SL/TP placements relative to the broker-enforced SYMBOL_TRADE_STOPS_LEVEL exclusion zones for both buy and sell orders.

Figure 3: Valid versus invalid SL/TP placements relative to the broker-enforced SYMBOL_TRADE_STOPS_LEVEL exclusion zones for both buy and sell orders.


The Send() Method: Pre-Dispatch Gate

The Send() method is the terminal node of every builder chain. Its responsibilities are ordered deliberately: first it checks that all mandatory fields have been validated, then it runs a final cross-field consistency pass, then it calls OrderCheck() to invoke the broker's pre-flight validation, and only then does it call OrderSend().

This four-stage gate structure means that most configuration errors are caught locally before any network communication occurs. The OrderCheck() call in stage three is particularly important for pending orders, where the relationship between the pending price and current market conditions involves server-side rules that the builder cannot replicate independently without querying live tick data.

  • Stage 1: Flag completeness check → All m_*_valid flags true?
  • Stage 2: Cross-field consistency → ValidateStops() passes?
  • Stage 3: Broker pre-flight → OrderCheck() returns true?
  • Stage 4: Server dispatch → OrderSend() succeeds?

If any stage fails, Send() returns false and sets m_error_message. The MqlTradeResult output remains in the state produced by the failing stage. Stages one and two produce no server communication. Stage three populates result.retcode indirectly: OrderCheck() requires a MqlTradeCheckResult parameter rather than MqlTradeResult, so the builder declares a local MqlTradeCheckResult check_result for the call and manually copies check_result.retcode into the caller's result.retcode on failure. Stage four populates the full result structure.

Execution flowchart of the four-stage Send() validation pipeline, distinguishing between zero-latency local checks and server-side network round-trips.

Figure 4: Execution flowchart of the four-stage Send() validation pipeline, distinguishing between zero-latency local checks and server-side network round-trips.

The Reset() Design Choice

After a successful or failed Send(), the builder's state is preserved. It is not automatically reset. This design choice allows the caller to inspect the builder's final state for diagnostic purposes after a failure, and it allows the builder to be reused for a structurally similar order by modifying only the fields that differ. A Reset() method is provided for callers that want to start a fresh configuration.

The alternative design, auto-resetting on every Send() call, would force the caller to re-supply all fields for every order even when submitting a series of structurally identical orders with only the volume varying. With manual reset, there is a small risk of reusing stale state, but it improves efficiency for repetitive submissions. The documentation of this behavior is itself a mitigation: any caller reading the builder's API will encounter the explicit Reset() method, which communicates that the state persists.


Method Reference Table

The complete public interface of COrderBuilder is documented below. The table lists every chainable and terminal method, its effect on the internal state, and the validation it enforces before storing the value.

Builder Method MqlTradeRequest Target Fields Validation Constraint Fail-Safe Response
Symbol(string) → COrderBuilder* symbol Non-empty, passes SymbolSelect() m_symbol_valid = false, error message set
Volume(double) → COrderBuilder* volume Within [SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_MAX], normalized to lot step Out-of-range sets flag false, error message set
Magic(ulong) → COrderBuilder* magic Unrestricted; stored directly N/A
Comment(string) → COrderBuilder* comment Unrestricted; stored directly N/A
Deviation(ulong) → COrderBuilder* deviation Unrestricted; stored directly N/A
Filling(ENUM_ORDER_TYPE_FILLING) → COrderBuilder* type_filling Unrestricted; stored directly N/A
Buy() → COrderBuilder* action, type Requires m_symbol_valid true Error message set, direction flag false
Sell() → COrderBuilder* action, type
Requires m_symbol_valid true Error message set, direction flag false
BuyLimit(double) → COrderBuilder* action, type, price Price below current ask Error message set, price/direction flags false
SellLimit(double) → COrderBuilder* action, type, price Price above current bid Error message set, price/direction flags false
BuyStop(double) → COrderBuilder* action, type, price Price above current ask Error message set, price/direction flags false
SellStop(double) → COrderBuilder* action, type, price Price below current bid Error message set, price/direction flags false
BuyStopLimit(double, double) → COrderBuilder* action, type, price, stoplimit Stop above ask; limit below stop Error message set, flags false
SellStopLimit(double, double) → COrderBuilder* action, type, price, stoplimit
Stop below bid; limit above stop Error message set, flags false
AtMarket() → COrderBuilder* price (reference), type_filling Direction must be set; live quote must be available Symbol must be valid; error if quote unavailable
AtPrice(double) → COrderBuilder* price Non-zero, positive Error message set, price flag false
StopLoss(double) → COrderBuilder* sl Directional validity, stops-level distance m_stops_consistent = false, error message set
TakeProfit(double) → COrderBuilder* tp Directional validity, stops-level distance m_stops_consistent = false, error message set
Expiry(datetime) → COrderBuilder* expiration, type_time Wraps request.expiration. Future timestamp; sets ORDER_TIME_SPECIFIED Past timestamps rejected, error message set
Reset() → void All fields Resets all fields and flags to defaults N/A
Send(MqlTradeResult &) → bool Constructs full MqlTradeRequest Four-stage gate: flags, consistency, OrderCheck(), OrderSend() Returns false at first failing stage
IsValid() → bool None (diagnostic) Returns true only if all validity flags are true N/A
ErrorMessage() → string None (diagnostic) Returns the last accumulated error string N/A


Overhead Analysis and Practical Constraints

Validation latency: Every StopLoss() and TakeProfit() call invokes SymbolInfoDouble() and SymbolInfoInteger() to retrieve the current point value and stops level. These are memory reads against the terminal's symbol cache and do not involve network calls; their latency is in the range of tens of nanoseconds on typical hardware. The SymbolSelect() call in Symbol() is the costliest of the per-method validations, as it may trigger a symbol subscription request if the symbol is not already in the Market Watch list. This should be considered when constructing builders for symbols outside the EA's primary instrument in latency-sensitive contexts.

Object reuse vs. fresh instantiation: The builder is a class with no dynamic members beyond its string fields. Declaring it as a stack-local variable and allowing it to be destroyed at scope exit is both safe and leak-free for single-submission use. For EAs that submit orders at high frequency, declaring a single builder at class scope and calling Reset() between submissions avoids repeated construction and destruction of the object's member string fields, which are heap-managed in MQL5. If the builder is heap-allocated via new COrderBuilder() for pointer-chain syntax, the calling code must call delete on the pointer when the builder is no longer needed. Assigning NULL to the pointer after deletion is recommended to prevent accidental double-free in long-running EA sessions.

The method order constraint: Method chaining has a subtlety that does not exist in struct population: the caller must call direction methods before stop level methods, because stop validation is directional. Calling StopLoss(1.0800) before Buy() on a builder with no direction set will skip the directional consistency check and store the value unconditionally, deferring the directional check to the next method that provides direction or to the final Send() call. This is the intended behavior, but developers must understand that the builder's internal ValidateStops() is re-invoked during Send() regardless of when StopLoss() was called, so a late direction assignment will still trigger the correct validation before dispatch.

No multi-threading safety: MQL5 EAs execute on a single thread per instance. The builder is not thread-safe by design. If future MetaTrader 5 runtime versions support concurrent execution contexts within a single EA, the builder's state members would require mutex protection.

OrderCheck() limitations: The broker pre-flight call in stage three of Send() validates the request against current margin and account conditions but does not guarantee acceptance by the server, because market conditions can change in the network round-trip between OrderCheck() and OrderSend(). The builder cannot eliminate this race; it can only reduce the probability of submission failure through pre-dispatch validation.


Usage Patterns

A market buy with stops, using the builder in a local variable:

MqlTradeResult result = {};
COrderBuilder  builder;
bool ok = builder.Symbol(_Symbol)
                 .Volume(0.10)
                 .Magic(100001)
                 .Comment("Trend entry")
                 .Deviation(10)
                 .Buy()
                 .AtMarket()
                 .StopLoss(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - 500 * _Point)
                 .TakeProfit(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + 1000 * _Point)
                 .Send(result);

if(!ok)
   PrintFormat("[COrderBuilder] Submission failed: %s", builder.ErrorMessage());
else
   PrintFormat("[COrderBuilder] Order placed. Deal: %lld", result.deal);

A pending buy limit order with expiration:

MqlTradeResult result = {};
COrderBuilder  builder;
double         limit_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - 200 * _Point;

bool ok = builder.Symbol(_Symbol)
                 .Volume(0.05)
                 .Magic(100002)
                 .BuyLimit(limit_price)
                 .StopLoss(limit_price - 300 * _Point)
                 .TakeProfit(limit_price + 600 * _Point)
                 .Expiry(TimeCurrent() + 3600)
                 .Send(result);

if(!ok)
   PrintFormat("[COrderBuilder] Pending order failed: %s", builder.ErrorMessage());

A builder instance reused across multiple submissions in a class context:

//--- In EA class header: declared as a value member, not a pointer
COrderBuilder m_builder;

//--- In OnTick() or order dispatch method
MqlTradeResult result = {};
m_builder.Reset();

bool ok = m_builder.Symbol(_Symbol)
                   .Volume(inp_lot_size)
                   .Magic(inp_magic_number)
                   .Sell()
                   .AtMarket()
                   .StopLoss(SymbolInfoDouble(_Symbol, SYMBOL_BID) + 400 * _Point)
                   .TakeProfit(SymbolInfoDouble(_Symbol, SYMBOL_BID) - 800 * _Point)
                   .Send(result);

UML sequence diagram illustrating the fluent builder configuration process and the sequential internal and external validation gates triggered during terminal Send() execution.

Figure 5: UML sequence diagram illustrating the fluent builder configuration process and the sequential internal and external validation gates triggered during terminal Send() execution.


Conclusion

The COrderBuilder class relocates semantic validation from an implicit, developer-responsibility concern to an explicit, structure-enforced guarantee. By binding directional logic to stop level arithmetic, coupling action and order type into single method calls, and running a multi-stage pre-dispatch gate before any server communication occurs, the builder eliminates the class of errors that raw MqlTradeRequest population leaves open indefinitely.

The trade-off is a modest increase in per-submission code path length. Each method call carries a small validation overhead, and the Send() method's four-stage gate adds execution steps that do not exist in a direct struct-and-send pattern. For the vast majority of EA designs, where order submission occurs on signal events measured in seconds or minutes rather than microseconds, this overhead is architecturally negligible. For systems where tick-level latency is a hard constraint, the validation logic can be pre-run once during signal generation, with Send() called only after all flags are known to be valid, reducing the gate's dispatch-time cost to a single OrderCheck() and OrderSend() sequence.

The pattern also enforces a discipline that scales across development teams. A developer reading a chain of builder calls can determine the complete intent of an order submission without tracing field assignments across multiple functions. This readability benefit compounds over the lifecycle of a complex EA where order logic evolves, is handed between developers, or is audited against regulatory requirements.


Programs used in the article:

# Name Type Description
1 OrderBuilder.mqh Include File COrderBuilder class: full method-chaining fluent builder, internal state machine, multi-stage pre-dispatch validation gate, and stop level consistency engine.
2 OrderBuilderDemo.mq5 Demo EA Demonstration EA exercising COrderBuilder across market buy, market sell, pending buy limit, pending sell stop, and pending buy stop-limit order types; includes error-state trapping and diagnostic output.
Attached files |
OrderBuilder.mqh (27.22 KB)
MQL5 Wizard Techniques you should know (Part 95): Using Disjoint Set Union and Deep Belief Network in a Custom Signal Class MQL5 Wizard Techniques you should know (Part 95): Using Disjoint Set Union and Deep Belief Network in a Custom Signal Class
For this article we switch to a custom MQL5 Wizard class that examines entry Signals. Our custom class is ‘CSignalDSUDBN’ this time around, and is coded by combining the Disjoint Set Union algorithm with a Deep Belief network. As has been the case throughout these series, our model is testable with MQL5 Wizard-Assembled Expert Advisors that can be tuned with different trailing stops and money management classes.
Engineering a Self-Healing Expert Advisor in MQL5 (Part 2): Restart-Safe Virtual Trade Protection Engineering a Self-Healing Expert Advisor in MQL5 (Part 2): Restart-Safe Virtual Trade Protection
Build a restart-aware virtual protection layer on top of the SQLite persistence from Part 1. The EA reconstructs hidden stop-loss and take-profit after restart, verifies current price against recovered exits, and closes or continues positions accordingly. The result is a consistent recovery path that detects managed positions and sustains safe runtime management.
CSV Data Analysis (Part 3): Engineering a Python Analytics Pipeline for MetaTrader 5 CSV Exports CSV Data Analysis (Part 3): Engineering a Python Analytics Pipeline for MetaTrader 5 CSV Exports
MetaTrader 5 provides rich performance data but limited structural analysis. This article shows how to export results to CSV from MQL5 and build five Python visualizations that expose cross-asset parameter consistency, the lag‑versus‑noise trade-off, walk‑forward decay, drawdown depth and duration, and intraday hour‑by‑day clusters. A unified automation module runs the full pipeline on any new export to deliver repeatable diagnostics.
Building a Type-Safe Event Bus in MQL5: Decoupling EA Components Without Global Variables Building a Type-Safe Event Bus in MQL5: Decoupling EA Components Without Global Variables
A typed publish-subscribe event bus in MQL5 replaces global variables and direct cross-references. Using an abstract listener interface and an enum-indexed subscription table, a signal engine, order manager, and drawdown monitor communicate only through the bus, with no shared state. The article analyzes dispatch overhead, pointer validation, and recursive publish risks, helping you design decoupled, testable EAs.