How to Get and Analyze Profit History in MQL5?

Introduction to Profit History in MQL5

Understanding past trading performance is paramount for any serious trader or strategy developer. The profit history provides an unvarnished record of executed trades, allowing for objective evaluation of a strategy’s effectiveness, identification of strengths and weaknesses, and refinement of trading parameters.

Understanding the Importance of Profit History Analysis

Analyzing profit history goes beyond merely looking at the final account balance. It involves dissecting individual trades, assessing profitability per symbol or strategy, calculating key performance metrics like drawdown, win rate, and average profit/loss per trade. This deep dive provides the necessary data points to make informed decisions about strategy optimization, risk management, and capital allocation. Without rigorous history analysis, improving a trading system becomes a matter of guesswork rather than data-driven refinement.

Overview of MQL5 and its Capabilities

MQL5 (MetaQuotes Language 5) is the high-level language for developing automated trading strategies, custom technical indicators, and scripts within the MetaTrader 5 platform. It offers a comprehensive set of tools for market analysis, trade execution, and accessing historical data. Compared to MQL4, MQL5 provides enhanced object-oriented programming features, a more robust event handling model, and direct access to the history of deals and orders, which are crucial for accurate profit history analysis.

MQL5 Functions for Accessing Account History

MQL5 provides specific functions designed to access the trading history stored within the terminal. The primary functions involved in retrieving deal and order history are:

  • HistorySelect(from, to): Selects a range of history data (deals and orders) based on time.
  • HistoryDealsTotal(): Returns the number of deals within the selected history range.
  • HistoryDealGetInteger(position_id, property_id): Gets an integer property of a specific deal.
  • HistoryDealGetDouble(position_id, property_id): Gets a double property of a specific deal.
  • HistoryDealGetString(position_id, property_id): Gets a string property of a specific deal.
  • HistoryOrdersTotal(): Returns the number of orders within the selected history range.
  • HistoryOrderGetInteger(position_id, property_id), HistoryOrderGetDouble(position_id, property_id), HistoryOrderGetString(position_id, property_id): Get properties of a specific order.

While order history is relevant for understanding execution flow (entry/exit points, fills), profit calculation is directly tied to deals, as deals represent the actual transactions where profit or loss is realized.

Accessing Profit History Data in MQL5

The process of accessing profit history involves selecting the desired time frame and then iterating through the deals that occurred within that period.

Using HistorySelect() Function

HistorySelect(from, to) is the gateway to historical data. It takes two datetime arguments specifying the start and end times of the desired history range. If the function returns true, the history for that period is successfully loaded into memory and accessible via subsequent HistoryDealsTotal() or HistoryOrdersTotal() calls. A false return indicates an error, which can be investigated using GetLastError(). It’s crucial to select only the required history range to manage memory efficiently.

Filtering History by Time Period

The from and to parameters of HistorySelect() allow precise filtering of the history data. For example, to get history for the last month, you would calculate the datetime for the start of the previous month and use TimeCurrent() or TimeGMT() for the end time. This temporal filtering is essential for analyzing performance over specific periods, such as monthly, quarterly, or annual results.

Accessing Specific Deal Properties

Once history is selected, you iterate through the deals using a loop from 0 to HistoryDealsTotal() - 1. Inside the loop, HistoryDealGetInteger(), HistoryDealGetDouble(), and HistoryDealGetString() are used with deal property identifiers (enums like DEAL_PROFIT, DEAL_SYMBOL, DEAL_TIME, DEAL_TYPE, DEAL_ENTRY, etc.) to retrieve details for each deal. DEAL_PROFIT is the most direct property for profit/loss calculation. DEAL_ENTRY indicates if the deal is an entry (DEAL_ENTRY_IN), exit (DEAL_ENTRY_OUT), or reverse (DEAL_ENTRY_INOUT). For profit analysis, both DEAL_ENTRY_IN and DEAL_ENTRY_OUT deals on closed positions contribute to the net profit/loss.

Understanding Order Types and Their Impact on Profit

While deals represent the executed transactions and thus the profit/loss, they are linked to orders (DEAL_ORDER property). Orders define the parameters of the intended transaction (buy/sell, type, price). Although you primarily work with deal profit, understanding the associated order type (ORDER_TYPE) gives context (e.g., market execution, limit order, stop loss fill). However, for direct profit calculation, focusing on the DEAL_PROFIT of closed positions (DEAL_ENTRY_OUT or DEAL_ENTRY_INOUT associated with a closed position) is the standard approach.

Analyzing Profit Data with MQL5

With the ability to access deal properties, we can perform various calculations to evaluate performance.

Calculating Total Profit and Loss

The most basic analysis is summing the DEAL_PROFIT property for all deals within the selected history range. This provides the gross profit/loss before considering commissions or swaps (which are often included in DEAL_PROFIT, but verification with broker specifics is advised). A simple loop iterating through HistoryDealsTotal() and accumulating the DEAL_PROFIT value is sufficient.

Calculating drawdown based on history deals

Calculating drawdown from historical deals requires tracking the evolution of the equity or balance over time. A common approach is to iterate through deals chronologically (using DEAL_TIME), maintaining a running equity value. Drawdown is the largest peak-to-trough decline in this equity curve. You would track the highest equity achieved so far (PeakEquity) and the current equity (CurrentEquity). When CurrentEquity drops below PeakEquity, the difference (PeakEquity - CurrentEquity) is a potential drawdown. The maximum value of this difference recorded is the maximum drawdown. This calculation is more complex than simple summation as it requires chronological processing and state management.

Calculating win rate ratio

Win rate is the percentage of profitable trades out of the total number of trades. You iterate through the deals, counting the total number of relevant closing deals (typically DEAL_ENTRY_OUT). Simultaneously, count the number of these closing deals where DEAL_PROFIT > 0. The win rate is then (Count of Winning Deals / Total Count of Closing Deals) * 100. Be mindful of how partial closures or reversals are handled in your deal logic.

Identifying Profitable Trading Strategies

Analyzing profit history helps distinguish between luck and a consistently profitable edge. By categorizing trades (e.g., by symbol, time of day, or even internal strategy logic if recorded), you can analyze the performance of subsets of trades. Metrics like profit factor (Gross Profit / Gross Loss), average win/loss, and maximum consecutive wins/losses, calculated from the deal history, provide deeper insights into the strategy’s characteristics and robustness.

Practical Examples and Code Snippets

Here are some code examples demonstrating the concepts.

Example: Retrieving and Printing Profit History

This snippet selects history for the last 30 days and prints details for each deal.

void PrintProfitHistory(int days)
{
    datetime from = TimeCurrent() - days * 24 * 60 * 60;
    datetime to = TimeCurrent();

    if (HistorySelect(from, to))
    {
        int deals_total = HistoryDealsTotal();
        Print("--- History for last ", days, " days ---");
        Print("Total Deals: ", deals_total);

        for (int i = 0; i < deals_total; i++)
        {
            ulong deal_ticket = HistoryDealGetTicket(i);
            if (deal_ticket > 0)
            {
                long deal_time    = HistoryDealGetInteger(deal_ticket, DEAL_TIME);
                string symbol     = HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
                double profit     = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);
                long entry_type   = HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
                long type         = HistoryDealGetInteger(deal_ticket, DEAL_TYPE);

                PrintFormat("Deal #%I64u: Time=%s, Symbol=%s, Profit=%.2f, Entry=%s, Type=%s",
                            deal_ticket,
                            TimeToString(deal_time),
                            symbol,
                            profit,
                            EnumToString((ENUM_DEAL_ENTRY)entry_type),
                            EnumToString((ENUM_DEAL_TYPE)type));
            }
        }
        Print("----------------------------");
    }
    else
    {
        Print("HistorySelect failed: ", GetLastError());
    }
}

// Example usage:
// PrintProfitHistory(30);

Example: Calculating and Displaying Monthly Profit

This example calculates profit aggregated by month.

void CalculateMonthlyProfit(int years_back)
{
    datetime from = TimeCurrent() - years_back * 365 * 24 * 60 * 60; // Approx years back
    datetime to = TimeCurrent();

    // Use a map to store profit per month (YYYY-MM format)
    map<string, double> monthly_profits;

    if (HistorySelect(from, to))
    {
        int deals_total = HistoryDealsTotal();
        Print("--- Monthly Profit Analysis (last ", years_back, " years) ---");

        for (int i = 0; i < deals_total; i++)
        {
            ulong deal_ticket = HistoryDealGetTicket(i);
            if (deal_ticket > 0)
            {
                long deal_time = HistoryDealGetInteger(deal_ticket, DEAL_TIME);
                double profit  = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);
                long entry     = HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);

                // Only consider deals that close positions for profit calculation
                if (entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT)
                {
                    // Format date as YYYY-MM for the map key
                    MqlDateTime dt;
                    TimeToStruct(deal_time, dt);
                    string month_key = IntegerToString(dt.year) + "-" + (dt.mon < 10 ? "0" : "") + IntegerToString(dt.mon);

                    monthly_profits[month_key] += profit;
                }
            }
        }

        // Iterate through the map and print results
        Print("Month\tProfit");
        Print("--------------------");
        string key;
        double value;
        for (int i = 0; i < monthly_profits.Limit(); i++)
        {
            if (monthly_profits.Next(i, key, value))
            {
                 PrintFormat("%s\t%.2f", key, value);
            }
        }
        Print("-------------------------------------");

    }
    else
    {
        Print("HistorySelect failed: ", GetLastError());
    }
}

// Example usage:
// CalculateMonthlyProfit(1); // Analyze history for the last 1 year

Example: Identifying most profitable symbol

This snippet calculates total profit per symbol.

void FindMostProfitableSymbol(int days)
{
    datetime from = TimeCurrent() - days * 24 * 60 * 60;
    datetime to = TimeCurrent();

    map<string, double> symbol_profits;

    if (HistorySelect(from, to))
    {
        int deals_total = HistoryDealsTotal();
        Print("--- Symbol Profitability Analysis (last ", days, " days) ---");

        for (int i = 0; i < deals_total; i++)
        {
            ulong deal_ticket = HistoryDealGetTicket(i);
            if (deal_ticket > 0)
            {
                string symbol = HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
                double profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);
                long entry    = HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);

                 if (entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT)
                {
                   symbol_profits[symbol] += profit;
                }
            }
        }

        // Find the symbol with the maximum profit
        string most_profitable_symbol = "N/A";
        double max_profit = -DBL_MAX; // Initialize with a very small number

         string key;
        double value;
        for (int i = 0; i < symbol_profits.Limit(); i++)
        {
            if (symbol_profits.Next(i, key, value))
            {
                 PrintFormat("Symbol: %s, Total Profit: %.2f", key, value);
                if (value > max_profit)
                {
                    max_profit = value;
                    most_profitable_symbol = key;
                }
            }
        }

        Print("----------------------------");
        PrintFormat("Most profitable symbol: %s (Total Profit: %.2f)", most_profitable_symbol, max_profit);

    }
    else
    {
        Print("HistorySelect failed: ", GetLastError());
    }
}

// Example usage:
// FindMostProfitableSymbol(90); // Analyze history for the last 90 days

Best Practices and Considerations

Effective and reliable history analysis requires attention to detail in data handling and code structure.

Efficient Data Handling and Memory Management

HistorySelect() loads all specified history into the terminal’s memory. Selecting unnecessarily large periods can consume significant resources, especially with tick history or a very large number of trades. Always select the narrowest possible time range required for your analysis. While MQL5 has automatic memory management, avoiding the creation of excessive or large dynamic arrays/objects when processing vast history data is prudent. Process data iteratively within the loop rather than loading everything into custom data structures if memory is a constraint.

Error Handling and Debugging

Always check the return value of HistorySelect(). If it returns false, use GetLastError() to understand the reason (e.g., invalid time range, terminal busy). When accessing deal or order properties, use the ticket number obtained from HistoryDealGetTicket() or HistoryOrderGetTicket() as the identifier, not the loop index i. Using Print() and Comment() with strategic output can help debug the logic, especially when iterating through deals and performing calculations.

Optimizing Code for Performance

For very long history ranges or complex calculations (like high-resolution drawdown), the analysis function can become time-consuming. Avoid redundant calls to history functions inside loops. Retrieve deal properties once per deal and store them in local variables if accessed multiple times. If running analysis frequently, consider implementing caching mechanisms or processing history incrementally. For extremely high-performance requirements, external data processing might be considered, but MQL5’s built-in history functions are generally efficient for typical analysis tasks within the terminal’s limitations.


Leave a Reply