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