Skip to content

MT5策略优化:利用自定义评分挖掘最佳交易!

开发出有正向期望的交易策略后,该如何选择上线参数?是盈利最多、胜率最高,还是盈亏比最佳?组建投资组合时,若策略相关性过高(如两个货币对策略相关性达0.42),又该如何筛选?本文将通过自定义评分函数(MT5的onTester()回调函数)解决这些问题,并举例说明。

核心问题:高相关性策略的筛选

当两个策略(如均针对含日元的货币对、逻辑相似)相关性达0.42时,传统指标(如夏普比率、CAGR、最大结余)可能因资金管理或仓位计算模式不同而失真。此时需通过自定义评分函数(MT5的onTester()方法)按特定逻辑评分,例如筛选“正收益月数/年数”更高的策略,提升持仓舒适度和执行性。

实战案例:基于正收益月数的评分

1. 策略测试与评分结果

通过MT5策略测试器运行后,两策略的正收益月数分别为101个月88个月。从持仓体验出发,选择101M策略上线。

2. 代码实现

以下为评价函数核心代码:

cpp
enum ENUM_TESTER_MODE
{
    TESTER_MODE_MONTH,
    TESTER_MODE_YEAR
};
input group "评估函数" 
input ENUM_TESTER_MODE testerMode = TESTER_MODE_MONTH;

double OnTester()
{
    double score = 0;
    if (testerMode == TESTER_MODE_MONTH)
        score = MonthProfit();
    else if (testerMode == TESTER_MODE_YEAR)
        score = YearProfit();
    if (TesterStatistics(STAT_TRADES) < 100)
        score = 0.0;
    return score;
}
// 正收益年数
#include <Generic\HashMap.mqh>
double YearProfit()
{
    HistorySelect(0, TimeCurrent());
    MqlDateTime dealTimeStruct;

    // 使用ChashMAP存储每年的收益
    CHashMap<int, double> yearProfitMap;
    int numDeals = HistoryDealsTotal();
    for (int dealID = 0; dealID < numDeals; dealID++)
    {
        ulong dealTicket = HistoryDealGetTicket(dealID);
        if (HistoryDealGetInteger(dealTicket, DEAL_ENTRY) != DEAL_ENTRY_OUT)
            continue;
        // 计算交易的净收益 包含交易的利润 手续费 佣金 库存费
        double tradeNetProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT) +
                                HistoryDealGetDouble(dealTicket, DEAL_SWAP) +
                                (2 * HistoryDealGetDouble(dealTicket, DEAL_COMMISSION));
        datetime dealTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);

        TimeToStruct(dealTime, dealTimeStruct);
        int year = dealTimeStruct.year;
        double existingProfit = 0;
        if (yearProfitMap.TryGetValue(year, existingProfit))
        {
            yearProfitMap.TrySetValue(year, existingProfit + tradeNetProfit);
        }
        else
        {
            yearProfitMap.Add(year, tradeNetProfit);
        }
    }
    int countProfitYears = 0;
    int years[];
    double profits[];
    ArrayResize(years, yearProfitMap.Count());
    ArrayResize(profits, yearProfitMap.Count());
    // 将hashmap 的键值复制到数组中
    yearProfitMap.CopyTo(years, profits, 0);
    // 遍历数组
    for (int i = 0; i < ArraySize(years); i++)
    {
        if (profits[i] > 0)
        {
            countProfitYears++;
            Print(years[i], " 年 盈利", profits[i]);
        }
    }
    return countProfitYears;
}

double MonthProfit()
{
    HistorySelect(0, TimeCurrent());
    MqlDateTime dealTimeStruct;

    // 使用ChashMAP存储每月的收益
    CHashMap<string, double> monthProfitMap;
    int numDeals = HistoryDealsTotal();
    for (int dealID = 0; dealID < numDeals; dealID++)
    {
        ulong dealTicket = HistoryDealGetTicket(dealID);
        if (HistoryDealGetInteger(dealTicket, DEAL_ENTRY) != DEAL_ENTRY_OUT)
            continue;
        // 计算交易的净收益 包含交易的利润 手续费 佣金 库存费
        double tradeNetProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT) +
                                HistoryDealGetDouble(dealTicket, DEAL_SWAP) +
                                (2 * HistoryDealGetDouble(dealTicket, DEAL_COMMISSION));
        datetime dealTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
        TimeToStruct(dealTime, dealTimeStruct);
        string yearMonth = StringFormat("%04d-%02d", dealTimeStruct.year, dealTimeStruct.mon);

        double existingProfit = 0;
        if (monthProfitMap.TryGetValue(yearMonth, existingProfit))
        {
            monthProfitMap.TrySetValue(yearMonth, existingProfit + tradeNetProfit);
        }
        else
        {
            monthProfitMap.Add(yearMonth, tradeNetProfit);
        }
    }
    int countProfitMonths = 0;
    string months[];
    double profits[];
    ArrayResize(months, monthProfitMap.Count());
    ArrayResize(profits, monthProfitMap.Count());
    // 将hashmap 的键值复制到数组中
    monthProfitMap.CopyTo(months, profits, 0);
    // 遍历数组
    for (int i = 0; i < ArraySize(months); i++)
    {
        if (profits[i] > 0)
        {
            countProfitMonths++;
            Print(months[i], " 月 盈利", profits[i]);
        }
    }
    return countProfitMonths;
}

统计结果显示该策略12年间有101个月是正收益的

3. 关键逻辑说明

  • 数据结构:使用CHashmap存储年份与对应净收益,高效统计正收益年数(月数逻辑类似,提取月份替代年份即可)。
  • 交易成本:包含手续费(双向)和库存费,确保计算严谨性。
  • 样本过滤:交易笔数<100时评分置0,避免小样本偏差。

技术拓展:自定义评分的灵活性

  • 可配置指标:除正收益月数/年数外,可自定义评分规则(如最大回撤、盈利因子、交易频率等)。
  • 数据结构优势CHashmap(哈希表)相比数组/结构体,在高频数据查询和存储时更高效,代码更简洁。

总结

通过MT5的onTester()回调函数自定义评分,可突破系统自带指标的局限性,按实际需求(如持仓体验、风险偏好)筛选策略。建议在参数优化或投资组合构建时,结合自定义评分与相关性分析,实现策略的科学配置。