mql4-lib

repository·master·Indexed 20 days ago

https://github.com/dingmaotu/mql4-lib

An object-oriented foundation library for MQL4 and MQL5 developers that provides reusable components and a Java-like coding style. It includes modules for language enhancements (Lang), collections (LinkedList, Vector, HashSet, HashMap), serialization (RESP protocol), trading abstractions, chart tools, and utility functions for file management and OpenCL support.

Tokens
6.1K
Snippets
11
Records
18
Agent score
20%

What's inside mql4-lib

  1. Overview of mql4-lib

    master
    mql4-lib is a foundation library designed to bring a professional, object-oriented coding style (similar to Java) to MQL4 and MQL5 programming. It aims to replace the limited standard MQL libraries with reusable, elegant, and component-based designs. While originally targeting MQL4, most components are compatible with MQL5, making it a cross-version library for both MT4 and MT5 (x86/x64).
  2. Core components of mql4-lib

    master

    The library is organized into several specialized directories that extend MQL capabilities:

    • Lang: Modules that enhance the MQL language (e.g., base classes for Scripts, Indicators, and EAs).
    • Collection: Useful collection types.
    • Format: Implementations of serialization formats.
    • Charts: Various chart types and common chart tools.
    • Trade: Abstractions for trading operations.
    • History: Abstractions for historical data.
    • Utils: Various utility functions.
    • UI: Chart objects and UI controls (in progress).
    • OpenCL: OpenCL support for MT4 (in progress).
  3. How Runtime Controlled Indicators work

    master

    In mql4-lib, an Indicator can exist in two modes:

    1. Runtime Controlled (Standalone): The indicator is controlled by the MetaTrader terminal. In this mode, isRuntimeControlled() returns true. You must configure visual styles (e.g., SetIndexStyle, IndicatorShortName) inside the constructor.
    2. Driven (Programmatic): The indicator is driven by your own code (e.g., an EA or Script) using a HistoryData derived class like Renko or TimeSeriesData. In this mode, isRuntimeControlled() returns false, and you are responsible for resizing buffers (e.g., using ArrayResize) within the main() method.

    Using an Indicator in an EA with a Renko Driver

    To use an indicator programmatically, instantiate its parameter class, create the indicator instance, and subscribe to the driver's OnUpdate event.

    //--- OnInit
    DeMarkerParam *param=new DeMarkerParam;
    param.setAvgPeriod(14);
    deMarker=new DeMarker(param);
    
    //--- Subscribe to Renko driver
    renko = new Renko(_Symbol,300);
    renko.OnUpdate+=deMarker;
    
    //--- OnTick
    renko.update(Close[0]);
    
    //--- Access value
    double value = deMarker[0];
    
    //--- OnDeinit
    delete renko;
  4. Parse RESP data with RespMsgParser and RespStreamParser

    master

    The library provides two types of parsers for the RESP protocol:

    1. RespMsgParser: Designed for message-oriented contexts where the entire buffer is received at once. It includes a check() method to validate if a buffer contains a valid RespValue without fully creating the object.
    2. RespStreamParser: Designed for stream-oriented contexts where data may arrive partially. It allows for resuming parsing as more input is provided (inspired by hiredis ReplyReader).

    Both parsers provide a getError() method to retrieve error codes (defined in Mql/Format/RespParseError.mqh) if the parse() method returns NULL.

  5. Calculate profit and target prices using symmetric order semantics

    master

    The library provides two primary operations for price calculation that allow you to express order logic symmetrically, regardless of whether the order is a BUY or a SELL. This approach reduces code complexity by focusing on the direction of profit rather than checking order types manually.

    Profit Calculation: p(s, e)

    Calculates the profit as the absolute price difference from a start price s to an end price e.

    • To calculate loss, use -p(s, e) or p(e, s). Using -p(s, e) is preferred for clarity.

    Target Price Calculation: pp(p, pr)

    Calculates the target price if you start from price p and want to achieve a profit of pr.

    • To target a specific loss, use pp(p, -pr).
    • Convenience Overload: The pr parameter can also accept a value in points directly.

    Expressing Breakeven

    You can express the breakeven condition symmetrically for both BUY and SELL orders using: order.p(order.getOpenPrice(), order.getStopLoss()) >= 0.

    • For a BUY order, this evaluates to openPrice <= stopLoss.
    • For a SELL order, this evaluates to openPrice >= stopLoss.
    • Conceptually: "If we move from the open price to the stop loss price, we still profit."
    // Example of symmetric breakeven check
    if(o.p(o.getOpenPrice(), o.getStopLoss()) >= 0) {
        // Order is at or past breakeven
    }
  6. Use Collections (Lists, Vectors, and Sets) in MQL

    master

    The library provides sophisticated collection types using MQL class templates.

    List Types

    • LinkedList<T>: A linked list implementation. Faster for frequent insertions and deletions.
    • Vector<T>: An array-based implementation (similar to Java's ArrayList). Faster for random access.

    Set Types

    • ArraySet<T>: Array-based set implementation.
    • HashSet<T>: Hash-based set implementation.

    Iteration

    To iterate through collections, use the Iter<T> RAII class or the provided macros. The macros handle resource cleanup automatically.

    • foreach(Type, collection): Uses an internal iterator variable named it.
    • foreachv(Type, var, collection): Declares a specific variable var for the element.
    • foreachorder(pool): Specialized macro for iterating through an order pool.
    LinkedList<Order*> orderList; // linked list based implementation, faster insert/remove
    LinkedList<int> intList; // supports primary types
    Vector<Order*> orderVector; // array based implementation, faster random access
    Vector<int> intVector;
    
    // Using Iter RAII class
    for(Iter<Order*> it(list); !it.end(); it.next())
      {
        Order* o = it.current();
        Print(o.toString());
      }
    
    // Using foreach macro
    foreach(Order*, list)
      {
        Order* o = it.current();
        Print(o.toString());
      }
    
    // Using foreachv macro
    foreachv(Order*, o, list)
      {
        Print(o.toString());
      }
  7. Work with history files using Utils/HistoryFile

    master
    MetaTrader uses special history files to back chart displays. The library provides the Utils/HistoryFile class to wrap operations on these files, which have a fixed structure. This is particularly useful for implementing custom chart types as offline charts (e.g., PriceBreakChart or RenkoChart).
  8. Implement symmetric order semantics with the Order class

    master

    The Order class provides "symmetric order semantics" to simplify logic that must handle both BUY and SELL orders. Instead of writing separate blocks for each type, you can use semantic methods that behave correctly regardless of the order direction.

    Formatting and Conversion

    • f(p): Formats price p to a string based on symbol digits.
    • n(p): Normalizes price p to a double based on symbol digits.
    • ap(p): Gets the absolute price difference of point value p (integer).

    Price Levels

    • s(): Gets the correct price to start an order (Ask for BUY, Bid for SELL).
    • e(): Gets the correct price to end an order (Bid for BUY, Ask for SELL).
  9. Install mql4-lib in MetaTrader

    master

    To install the library, copy the contents into your MetaTrader Data Folder's Include directory. It is recommended to use Mql (Pascal Case) as the root directory name to ensure compatibility across both MT4 and MT5.

    Installation Paths:

    • For MT4: <MetaTrader Data>\MQL4\Include\Mql\<mql4-lib content>
    • For MT5: <MetaTrader Data>\MQL5\Include\Mql\<mql4-lib content>

    Note: It is recommended to use the latest version of MetaTrader 4 or 5, as older versions may lack required features.

  10. Send custom app events from C/C++ to MQL4

    master

    You can bypass MetaTrader's limitations by sending custom events from an external C/C++ application using the PostMessage or PostThreadMessage Win32 API. This mechanism uses a custom WM_KEYDOWN message to trigger OnChartEvent in MQL.

    To ensure the MQL program (specifically those deriving from EventApp) processes the message via its onAppEvent handler, you must encode the event and param using the EncodeKeydownMessage algorithm.

    Limitations:

    • The parameter is limited to a 32-bit integer.
    • This solution may not work in 64-bit MetaTrader 5.
    • It is considered a temporary workaround; the official way to handle asynchronous events is via ChartEventCustom (which is difficult to implement in C++ due to anti-debugging measures).
    #include <Windows.h>
    #include <stdint.h>
    #include <limits.h>
    
    static const int WORD_BIT = sizeof(int16_t)*CHAR_BIT;
    
    void EncodeKeydownMessage(const WORD event,const DWORD param,WPARAM &wparam,LPARAM &lparam)
    {
        DWORD t=(DWORD)event;
        t<<= WORD_BIT;
        t |= 0x80000000;
        DWORD highPart= param & 0xFFFF0000;
        DWORD lowPart = param & 0x0000FFFF;
        wparam = (WPARAM)(t|(highPart>>WORD_BIT));
        lparam = (LPARAM)lowPart;
    }
    
    BOOL MqlSendAppMessage(HWND hwnd, WORD event, DWORD param)
    {
        WPARAM wparam;
        LPARAM lparam;
        EncodeKeydownMessage(event, param, wparam, lparam);
        return PostMessageW(hwnd,WM_KEYDOWN,wparam, lparam);
    }
  11. Create an Expert Advisor (EA) with input parameters

    master

    To create an Expert Advisor with inputs, follow these steps:

    1. Define a parameter class inheriting from AppParam.
    2. Use the ObjectAttr macro to declare fields. This automatically generates getter/setter methods following Java Beans conventions.
    3. Define your EA class inheriting from ExpertAdvisor, passing the parameter class to its constructor.
    4. Use BEGIN_INPUT and END_INPUT blocks to define the inputs for the MetaTrader terminal.
    5. Use DECLARE_EA to register the EA. Set the second parameter to true to indicate it uses input parameters.

    Note: Use the constructor for initialization (instead of OnInit) and the destructor for deinitialization (instead of OnDeinit). Use fail(message, returnCode) within the constructor if initialization fails.

    #include <Mql/Lang/ExpertAdvisor.mqh>
    
    class MyEaParam: public AppParam
    {
      ObjectAttr(string,eaName,EaName);
      ObjectAttr(double,baseLot,BaseLot);
    public:
    };
    
    class MyEa: public ExpertAdvisor
    {
    private:
      MyEaParam *m_param;
    public:
           MyEa(MyEaParam *param)
           :m_param(param)
          {
          }
          ~MyEa()
          {
          }
      void main() {Print("Hello from " + m_param.getEaName());}
    };
    
    BEGIN_INPUT(MyEaParam)
      INPUT(string,EaName,"My EA");
      INPUT(double,BaseLot,0.1);
    END_INPUT
    
    DECLARE_EA(MyEa,true)
  12. Iterate through orders using OrderPool and foreachorder

    master

    Instead of manually looping through OrdersTotal() and using OrderSelect(), you can use the OrderPool class and the foreachorder macro to encapsulate filtering logic.

    To use this, derive a class from HistoryPool (or OrderPool) and implement the matches() method to define your filtering criteria.

    #include <Mql/Trade/OrderPool.mqh>
    
    // Define a pool that only matches profitable orders
    class ProfitHistoryPool: public HistoryPool
      {
    public:
       bool matches() const { return Order::Profit() > 0; }
      };
    
    void OnStart()
      {
       ProfitHistoryPool profitPool;
       
       // Use the foreachorder macro to iterate and filter automatically
       foreachorder(profitPool)
         {
          Order o;
          Print(o.toString());
         }
      }