//+------------------------------------------------------------------+ //| 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 // 调试标志 - 设置为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); } } } //+------------------------------------------------------------------+