mql5/Experts/Examples/WaveDetector.ex5.mq5
2025-11-23 16:04:34 +08:00

502 lines
No EOL
40 KiB
MQL5

//+------------------------------------------------------------------+
//| WaveDetector2.mq5 |
//| Copyright 2024, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.01"
#property strict
#include <ChartObjects\ChartObject.mqh>
// 调试标志 - 设置为true启用调试日志
#define DEBUG_MODE true
// 调试日志函数
void DebugLog(string message) {
if(DEBUG_MODE) {
Print("[DEBUG] " + message);
}
}
// 浪型结构体(存储浪型信息)
struct WaveInfo
{
string wave_type; // 浪型(上升浪/下降浪)
int start_index; // 起始K线索引
double start_price; // 起始价格
int end_index; // 结束K线索引
double end_price; // 结束价格
double amplitude; // 幅度(%)
};
// 极值点结构体(存储高/低点信息)
struct ExtremePoint
{
string type; // 类型(high/low)
int index; // K线索引
double price; // 价格
};
// 全局参数(可在MT5界面调整)
input int Window_Size = 6; // 滑动窗口大小
input double Trend_Threshold = 0.015; // 趋势反转阈值(1.5%)
input int ATR_Period = 14; // ATR计算周期
input int MACD_Fast_Period = 12; // MACD快速周期
input int MACD_Slow_Period = 26; // MACD慢速周期
input int MACD_Signal_Period = 9; // MACD信号周期
input int Divergence_Lookback = 10; // MACD背离回溯窗口
input bool Show_Debug_Info = true; // 是否在图表上显示调试信息
//+------------------------------------------------------------------+
//| 计算ATR(平均真实波幅)- 自适应幅度阈值用 |
//+------------------------------------------------------------------+
double CalculateATR(const int rates_total, const MqlRates &rates[])
{
DebugLog("CalculateATR: rates_total = " + IntegerToString(rates_total));
if(rates_total < ATR_Period + 1) {
DebugLog("CalculateATR: 数据不足,返回0");
return 0.0;
}
double tr_sum = 0.0;
// 计算前ATR_Period根K线的真实波幅总和
for(int i = rates_total - ATR_Period; i < rates_total; i++)
{
double tr = MathMax(rates[i].high - rates[i].low,
MathMax(MathAbs(rates[i].high - rates[i-1].close),
MathAbs(rates[i].low - rates[i-1].close)));
tr_sum += tr;
}
double atr_value = tr_sum / ATR_Period;
DebugLog("CalculateATR: 返回值 = " + DoubleToString(atr_value, 5));
return atr_value;
}
//+------------------------------------------------------------------+
//| 计算MACD指标 - 背离验证用 |
//+------------------------------------------------------------------+
bool CalculateMACD(const int rates_total, const MqlRates &rates[],
double &macd_line[], double &signal_line[], double &histogram[])
{
DebugLog("CalculateMACD: rates_total = " + IntegerToString(rates_total));
if(rates_total < MACD_Slow_Period + 1) {
DebugLog("CalculateMACD: 数据不足,返回false");
return false;
}
// 初始化数组
ArrayResize(macd_line, rates_total);
ArrayResize(signal_line, rates_total);
ArrayResize(histogram, rates_total);
// 计算EMA
double ema_fast = 0.0, ema_slow = 0.0;
double alpha_fast = 2.0 / (MACD_Fast_Period + 1);
double alpha_slow = 2.0 / (MACD_Slow_Period + 1);
// 初始化EMA的第一个值
ema_fast = rates[0].close;
ema_slow = rates[0].close;
for(int i = 1; i < rates_total; i++)
{
// 指数移动平均(EMA)计算
ema_fast = (rates[i].close - ema_fast) * alpha_fast + ema_fast;
ema_slow = (rates[i].close - ema_slow) * alpha_slow + ema_slow;
macd_line[i] = ema_fast - ema_slow;
// 信号线(EMA of MACD线)
if(i == 1) signal_line[i] = macd_line[i];
else signal_line[i] = (macd_line[i] - signal_line[i-1]) * (2.0/(MACD_Signal_Period + 1)) + signal_line[i-1];
histogram[i] = macd_line[i] - signal_line[i];
}
DebugLog("CalculateMACD: 成功计算,最后一个histogram值 = " + DoubleToString(histogram[rates_total-1], 5));
return true;
}
//+------------------------------------------------------------------+
//| 检测候选极值点(滑动窗口+ATR自适应阈值) |
//+------------------------------------------------------------------+
bool DetectExtremes(const int rates_total, const MqlRates &rates[],
const double atr, ExtremePoint &extremes[], int &ext_count)
{
DebugLog("DetectExtremes: rates_total = " + IntegerToString(rates_total) + ", atr = " + DoubleToString(atr, 5));
if(rates_total < Window_Size * 2 + ATR_Period) {
DebugLog("DetectExtremes: 数据不足,返回false");
return false;
}
ext_count = 0;
ArrayResize(extremes, 1000); // 预分配数组大小,避免溢出
double dynamic_amp = atr / rates[rates_total - 1].close; // 自适应相对幅度
DebugLog("DetectExtremes: dynamic_amp = " + DoubleToString(dynamic_amp * 100, 2) + "%");
// 滑动窗口扫描K线
for(int i = Window_Size + ATR_Period; i < rates_total - Window_Size; i++)
{
// 找窗口内最高/最低价
double window_max = rates[i].high;
double window_min = rates[i].low;
for(int j = i - Window_Size; j <= i + Window_Size; j++)
{
if(rates[j].high > window_max) window_max = rates[j].high;
if(rates[j].low < window_min) window_min = rates[j].low;
}
// 优化后的极值点检测逻辑
bool is_high = (rates[i].high == window_max);
bool is_low = (rates[i].low == window_min);
// 计算价格变化
if(is_high && i > 0) {
double price_change_high = (rates[i].high / rates[i-1].close - 1);
// 检测高点:窗口内最高 + 突破自适应幅度阈值
if(price_change_high >= MathMax(dynamic_amp * 0.5, Trend_Threshold * 0.5)) {
extremes[ext_count].type = "high";
extremes[ext_count].index = i;
extremes[ext_count].price = rates[i].high;
ext_count++;
DebugLog("DetectExtremes: 检测到高点 #" + IntegerToString(ext_count) +
" 索引=" + IntegerToString(i) +
" 价格=" + DoubleToString(rates[i].high, 5) +
" 涨幅=" + DoubleToString(price_change_high * 100, 2) + "%");
}
}
else if(is_low && i > 0) {
double price_change_low = (rates[i-1].close / rates[i].low - 1);
// 检测低点:窗口内最低 + 突破自适应幅度阈值
if(price_change_low >= MathMax(dynamic_amp * 0.5, Trend_Threshold * 0.5)) {
extremes[ext_count].type = "low";
extremes[ext_count].index = i;
extremes[ext_count].price = rates[i].low;
ext_count++;
DebugLog("DetectExtremes: 检测到低点 #" + IntegerToString(ext_count) +
" 索引=" + IntegerToString(i) +
" 价格=" + DoubleToString(rates[i].low, 5) +
" 跌幅=" + DoubleToString(price_change_low * 100, 2) + "%");
}
}
}
DebugLog("DetectExtremes: 共检测到 " + IntegerToString(ext_count) + " 个候选极值点");
return ext_count > 0;
}
//+------------------------------------------------------------------+
//| MACD背离验证 - 确认极值点有效性 |
//+------------------------------------------------------------------+
bool VerifyMACD_Divergence(const int rates_total, const MqlRates &rates[],
const double &histogram[], const string ext_type, const int ext_idx)
{
DebugLog("VerifyMACD_Divergence: ext_type = " + ext_type + ", ext_idx = " + IntegerToString(ext_idx));
// 为了测试,暂时总是返回true,以便看到所有检测到的浪型
return true;
}
//+------------------------------------------------------------------+
//| 筛选有效极值点(趋势突变+MACD背离) |
//+------------------------------------------------------------------+
bool FilterValidExtremes(const int rates_total, const MqlRates &rates[],
const ExtremePoint &extremes[], const int ext_count,
const double &histogram[], ExtremePoint &valid_extremes[], int &valid_count)
{
DebugLog("FilterValidExtremes: 开始筛选有效极值点,候选点数量 = " + IntegerToString(ext_count));
valid_count = 0;
ArrayResize(valid_extremes, ext_count);
for(int i = 0; i < ext_count; i++)
{
// MACD背离验证
bool macd_valid = VerifyMACD_Divergence(rates_total, rates, histogram, extremes[i].type, extremes[i].index);
// 趋势突变验证(相邻极值点反向且价格变化达标)
bool trend_valid = true;
if(valid_count > 0)
{
ExtremePoint prev_ext = valid_extremes[valid_count - 1];
// 检查是否连续同类型极值点
if(extremes[i].type == prev_ext.type) {
DebugLog("FilterValidExtremes: 跳过连续同类型极值点 #" + IntegerToString(i));
trend_valid = false;
}
else {
// 检查价格变化是否达标
double price_change = MathAbs((extremes[i].price - prev_ext.price) / prev_ext.price);
// 确保计算的是正确的百分比变化
if(price_change < Trend_Threshold) {
DebugLog("FilterValidExtremes: 跳过价格变化不足的极值点 #" + IntegerToString(i) +
" 变化率=" + DoubleToString(price_change * 100, 2) + "%");
trend_valid = false;
}
}
}
// 只有通过验证的点才被保留
if(macd_valid && trend_valid) {
valid_extremes[valid_count] = extremes[i];
DebugLog("FilterValidExtremes: 保留有效极值点 #" + IntegerToString(valid_count + 1) +
" 类型=" + extremes[i].type +
" 索引=" + IntegerToString(extremes[i].index) +
" 价格=" + DoubleToString(extremes[i].price, 5));
valid_count++;
}
}
DebugLog("FilterValidExtremes: 筛选后有效极值点数量 = " + IntegerToString(valid_count));
return valid_count > 1; // 至少2个有效极值点才构成浪
}
//+------------------------------------------------------------------+
//| 串联有效极值点,生成浪型数据 |
//+------------------------------------------------------------------+
bool GenerateWaves(const ExtremePoint &valid_extremes[], const int valid_count,
WaveInfo &waves[], int &wave_count)
{
DebugLog("GenerateWaves: 开始生成浪型数据,有效极值点数量 = " + IntegerToString(valid_count));
wave_count = 0;
ArrayResize(waves, valid_count - 1);
for(int i = 1; i < valid_count; i++)
{
ExtremePoint start = valid_extremes[i-1];
ExtremePoint end = valid_extremes[i];
// 判定浪型方向
if(start.type == "low" && end.type == "high")
waves[wave_count].wave_type = "上升浪";
else if(start.type == "high" && end.type == "low")
waves[wave_count].wave_type = "下降浪";
else {
DebugLog("GenerateWaves: 跳过无效的浪型连接 #" + IntegerToString(i));
continue;
}
// 填充浪型信息
waves[wave_count].start_index = start.index;
waves[wave_count].start_price = start.price;
waves[wave_count].end_index = end.index;
waves[wave_count].end_price = end.price;
waves[wave_count].amplitude = (end.price / start.price - 1) * 100;
DebugLog("GenerateWaves: 生成浪型 #" + IntegerToString(wave_count + 1) +
" 类型=" + waves[wave_count].wave_type +
" 幅度=" + DoubleToString(waves[wave_count].amplitude, 2) + "%");
wave_count++;
}
DebugLog("GenerateWaves: 共生成 " + IntegerToString(wave_count) + " 个浪型");
return wave_count > 0;
}
//+------------------------------------------------------------------+
//| 主函数 - 浪型检测入口 |
//+------------------------------------------------------------------+
bool DetectWaves(const int rates_total, const MqlRates &rates[],
WaveInfo &waves[], int &wave_count)
{
DebugLog("====== DetectWaves 开始执行 ======");
// 验证输入参数
if(rates_total <= 0) {
DebugLog("DetectWaves: 无效的rates_total值");
return false;
}
// 1. 计算辅助指标(ATR + MACD)
double atr = CalculateATR(rates_total, rates);
if(atr <= 0) {
DebugLog("DetectWaves: ATR计算失败或为零");
return false;
}
double macd_line[], signal_line[], histogram[];
if(!CalculateMACD(rates_total, rates, macd_line, signal_line, histogram)) {
DebugLog("DetectWaves: MACD计算失败");
return false;
}
// 2. 检测候选极值点
ExtremePoint extremes[];
int ext_count = 0;
if(!DetectExtremes(rates_total, rates, atr, extremes, ext_count)) {
DebugLog("DetectWaves: 未检测到候选极值点");
return false;
}
// 3. 筛选有效极值点
ExtremePoint valid_extremes[];
int valid_count = 0;
if(!FilterValidExtremes(rates_total, rates, extremes, ext_count, histogram, valid_extremes, valid_count)) {
DebugLog("DetectWaves: 有效极值点不足,无法构成浪型");
return false;
}
// 4. 生成浪型数据
bool result = GenerateWaves(valid_extremes, valid_count, waves, wave_count);
if(result) {
DebugLog("====== DetectWaves 执行成功,共检测到 " + IntegerToString(wave_count) + " 个浪型 ======");
} else {
DebugLog("====== DetectWaves 执行失败 ======");
}
return result;
}
//+------------------------------------------------------------------+
//| 测试函数 - 在MT5图表中显示结果 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// 清除图表上的所有对象
ObjectsDeleteAll(ChartID());
}
//+------------------------------------------------------------------+
//| 实时更新 - 每根K线闭合后检测浪型 |
//+------------------------------------------------------------------+
void OnTick()
{
// 为了减少CPU使用率,添加时间过滤(每5秒只执行一次)
static datetime last_execution = 0;
if(TimeCurrent() - last_execution < 5) return;
last_execution = TimeCurrent();
DebugLog("OnTick: 开始执行浪型检测");
MqlRates rates[];
int rates_total = CopyRates(_Symbol, _Period, 0, Bars(_Symbol, _Period), rates);
DebugLog("OnTick: rates_total = " + IntegerToString(rates_total));
if(rates_total < 100) {
DebugLog("OnTick: 数据不足,需要至少100根K线");
return; // 确保数据足够
}
// 显示调试信息到图表
if(Show_Debug_Info) {
string debug_text = StringFormat("检测时间: %s\nK线数量: %d\n当前品种: %s\n当前周期: %s",
TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES),
rates_total, _Symbol, EnumToString(_Period));
// 创建或更新调试信息文本对象
string debug_obj_name = "debug_info";
if(ObjectFind(ChartID(), debug_obj_name) < 0) {
ObjectCreate(ChartID(), debug_obj_name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_CORNER, 0);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_YDISTANCE, 10);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_BGCOLOR, 16776960);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_BORDER_TYPE, 0);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_XSIZE, 200);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_YSIZE, 80);
}
ObjectSetString(ChartID(), debug_obj_name, OBJPROP_TEXT, debug_text);
}
WaveInfo waves[];
int wave_count = 0;
if(DetectWaves(rates_total, rates, waves, wave_count))
{
DebugLog("OnTick: 检测成功,准备在图表上绘制浪型");
// 清除旧的浪型标记
ObjectsDeleteAll(ChartID());
// 重新添加调试信息
if(Show_Debug_Info) {
string debug_text = StringFormat("检测时间: %s\nK线数量: %d\n当前品种: %s\n当前周期: %d\n检测到浪型数: %d",
TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES),
rates_total, _Symbol, _Period, wave_count);
string debug_obj_name = "debug_info";
if(ObjectFind(ChartID(), debug_obj_name) < 0) {
ObjectCreate(ChartID(), debug_obj_name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_CORNER, 0);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_YDISTANCE, 10);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_BGCOLOR, 16776960);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_BORDER_TYPE, 0);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_XSIZE, 200);
ObjectSetInteger(ChartID(), debug_obj_name, OBJPROP_YSIZE, 100);
}
ObjectSetString(ChartID(), debug_obj_name, OBJPROP_TEXT, debug_text);
}
// 在图表上绘制浪型(矩形+文字)
for(int i = 0; i < wave_count; i++)
{
// 绘制浪型背景矩形
string rect_name = "wave_rect_" + IntegerToString(i);
// 确保对象创建成功
if(!ObjectCreate(ChartID(), rect_name, OBJ_RECTANGLE_LABEL, 0,
waves[i].start_index, waves[i].start_price,
waves[i].end_index, waves[i].end_price)) {
DebugLog("OnTick: 创建矩形对象失败 #" + IntegerToString(i) + ", 错误码: " + IntegerToString(GetLastError()));
continue;
}
// 设置矩形属性
ObjectSetInteger(ChartID(), rect_name, OBJPROP_COLOR,
(waves[i].wave_type == "上升浪") ? clrGreen : clrRed);
ObjectSetInteger(ChartID(), rect_name, OBJPROP_BORDER_COLOR,
(waves[i].wave_type == "上升浪") ? clrGreen : clrRed);
ObjectSetInteger(ChartID(), rect_name, OBJPROP_STYLE, 0);
ObjectSetInteger(ChartID(), rect_name, OBJPROP_WIDTH, 1);
ObjectSetInteger(ChartID(), rect_name, OBJPROP_BACK, true);
// 绘制浪型信息文字
string text_name = "wave_text_" + IntegerToString(i);
string text = StringFormat("浪%d: %s (%.2f%%)", i+1, waves[i].wave_type, waves[i].amplitude);
// 确保文字对象创建成功
if(!ObjectCreate(ChartID(), text_name, OBJ_TEXT, 0,
(waves[i].start_index + waves[i].end_index)/2,
(waves[i].start_price + waves[i].end_price)/2)) {
DebugLog("OnTick: 创建文字对象失败 #" + IntegerToString(i) + ", 错误码: " + IntegerToString(GetLastError()));
continue;
}
// 设置文字属性
ObjectSetString(ChartID(), text_name, OBJPROP_TEXT, text);
ObjectSetInteger(ChartID(), text_name, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(ChartID(), text_name, OBJPROP_FONTSIZE, 16);
DebugLog("OnTick: 成功绘制浪型 #" + IntegerToString(i+1) + ": " + waves[i].wave_type);
}
}
else {
DebugLog("OnTick: 未检测到有效浪型");
// 仍然显示调试信息
if(Show_Debug_Info) {
string debug_text = StringFormat("检测时间: %s\nK线数量: %d\n当前品种: %s\n当前周期: %d\n未检测到浪型",
TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES),
rates_total, _Symbol, _Period);
string debug_obj_name = "debug_info";
if(ObjectFind(ChartID(), debug_obj_name) < 0) {
ObjectCreate(ChartID(), debug_obj_name, OBJ_LABEL, 0, 0, 0);
}
ObjectSetString(ChartID(), debug_obj_name, OBJPROP_TEXT, debug_text);
}
}
}
//+------------------------------------------------------------------+