Smart Money 供需区 + 买卖信号指标
自动识别供需区矩形,回踩支撑/触及阻力时显示买卖箭头及止盈止损线
相对上一版改了:把右边的代码,在这基础上增加买卖提示点,以及止盈止损点;刚才这份代码用 MetaEditor 编译通过了,但按 MT5/MT4 真实画图规则会画错。用户把它装到自己的终端上,看到的箭头、均线会和说明里说的完全不一样(典型症状:金叉指标变成每根 K 线都插箭头、线和 K 线错位、新 K 线不更新)。 问题: - 循环上界是 i < rates_total,循环体里却读了 buf[i+1] / close[i+1]。i 走到最后一根时 i+1 越界,终端会报 array out of range 并把指标停掉。读 i+1 必须写成 i < rates_total - 1。 【必须按这个骨架写 On
// 基于 XAUOF 社区作品 #15《一键平仓 / 一键保本工具(二创)》(作者 123456789)二次创作
// 本次改动:
// 1. 新增买入/卖出信号箭头:价格触及需求区且反弹显示买入,触及供给区且回落显示卖出
// 2. 新增止盈止损标注:自动绘制虚线和文字标签显示建议的 SL/TP 位置
// 3. 新增信号确认参数:可设置反弹/回落的最小幅度,避免假信号
// 4. 修复索引方向:移除 ArraySetAsSeries,使用正确的 MT5 正向索引规则
//+------------------------------------------------------------------+
#property copyright "XAUOF 指标工坊 - 基于社区作品 #15 二创"
#property version "2.01"
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2
//--- 买入信号箭头
#property indicator_type1 DRAW_ARROW
#property indicator_color1 clrLime
#property indicator_width1 3
#property indicator_label1 "买入信号"
//--- 卖出信号箭头
#property indicator_type2 DRAW_ARROW
#property indicator_color2 clrRed
#property indicator_width2 3
#property indicator_label2 "卖出信号"
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
input int InpSwingLength = 5; // 摆动识别周期(左右各多少根K线)
input int InpZoneThickness = 30; // 供需区厚度(单位:点数)
input int InpZoneExtendBars = 50; // 区域向右延伸多少根K线
input color InpDemandColor = clrDodgerBlue; // 需求区颜色(支撑)
input color InpSupplyColor = clrTomato; // 供给区颜色(阻力)
input int InpZoneTransparency = 85; // 区域透明度(0-100,越大越透明)
input bool InpShowLabels = true; // 是否显示供需区标注
input int InpMaxZones = 10; // 最多显示多少个区域(0=不限制)
input bool InpDeleteOldZones = true; // 价格触及后是否删除旧区域
//--- 信号相关参数
input int InpSignalMinPips = 5; // 信号确认最小点数(反弹/回落幅度)
input bool InpShowSignals = true; // 是否显示买卖信号箭头
input int InpArrowOffset = 10; // 信号箭头距离K线的点数
input bool InpShowSLTP = true; // 是否显示止盈止损线
input double InpRiskRewardRatio = 2.0; // 止盈止损比例(盈亏比,例如 2.0 表示止盈是止损的2倍)
input color InpSLColor = clrOrangeRed; // 止损线颜色
input color InpTPColor = clrSpringGreen; // 止盈线颜色
//--- 全局变量
struct ZoneInfo
{
string name; // 对象名称
double price_top; // 区域上边界
double price_bottom; // 区域下边界
datetime time_start; // 开始时间
bool is_demand; // true=需求区,false=供给区
bool touched; // 是否已被触及
};
ZoneInfo g_zones[]; // 存储所有区域
int g_zone_count = 0; // 当前区域数量
string g_prefix = "SMZ_"; // 对象名称前缀
string g_signal_prefix = "SIG_"; // 信号对象前缀
//--- 指标缓冲区
double BuySignalBuffer[]; // 买入信号箭头
double SellSignalBuffer[]; // 卖出信号箭头
//+------------------------------------------------------------------+
//| 初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 检查参数
if(InpSwingLength < 1)
{
Print("错误:摆动周期必须 >= 1");
return INIT_PARAMETERS_INCORRECT;
}
if(InpRiskRewardRatio <= 0)
{
Print("错误:盈亏比必须大于 0");
return INIT_PARAMETERS_INCORRECT;
}
//--- 绑定指标缓冲区
SetIndexBuffer(0, BuySignalBuffer, INDICATOR_DATA);
SetIndexBuffer(1, SellSignalBuffer, INDICATOR_DATA);
//--- 设置箭头样式
PlotIndexSetInteger(0, PLOT_ARROW, 233); // 上箭头
PlotIndexSetInteger(1, PLOT_ARROW, 234); // 下箭头
//--- 设置空值为 EMPTY_VALUE(必须,否则 0 会被当作有效价格画出来)
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
//--- 初始化缓冲区为空值
ArrayInitialize(BuySignalBuffer, EMPTY_VALUE);
ArrayInitialize(SellSignalBuffer, EMPTY_VALUE);
//--- 清理旧对象
DeleteAllObjects();
//--- 初始化数组
ArrayResize(g_zones, 0);
g_zone_count = 0;
Print("Smart Money 供需区 + 信号指标已加载");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| 反初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- 清理所有绘制的对象
DeleteAllObjects();
}
//+------------------------------------------------------------------+
//| 主计算函数 |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
//--- 需要至少有足够的K线
if(rates_total < InpSwingLength * 2 + 10)
return 0;
//--- 首次运行时初始化缓冲区和扫描历史供需区
if(prev_calculated == 0)
{
ArrayInitialize(BuySignalBuffer, EMPTY_VALUE);
ArrayInitialize(SellSignalBuffer, EMPTY_VALUE);
//--- 扫描历史K线识别供需区(从老到新,注意循环上界要留出 InpSwingLength 避免越界)
for(int i = InpSwingLength + 1; i < rates_total - InpSwingLength - 1; i++)
{
//--- 检查摆动高点(供给区)
bool is_swing_high = true;
for(int j = 1; j <= InpSwingLength; j++)
{
if(high[i] <= high[i - j] || high[i] <= high[i + j])
{
is_swing_high = false;
break;
}
}
if(is_swing_high)
{
double zone_top = high[i];
double zone_bottom = zone_top - InpZoneThickness * _Point;
if(!ZoneExists(zone_top, zone_bottom, time[i]))
{
CreateZone(time[i], zone_top, zone_bottom, false);
}
}
//--- 检查摆动低点(需求区)
bool is_swing_low = true;
for(int j = 1; j <= InpSwingLength; j++)
{
if(low[i] >= low[i - j] || low[i] >= low[i + j])
{
is_swing_low = false;
break;
}
}
if(is_swing_low)
{
double zone_bottom = low[i];
double zone_top = zone_bottom + InpZoneThickness * _Point;
if(!ZoneExists(zone_top, zone_bottom, time[i]))
{
CreateZone(time[i], zone_top, zone_bottom, true);
}
}
}
}
//--- 计算信号扫描起点(标准骨架:从 prev_calculated-1 开始,首次运行从 1 开始)
int start = (prev_calculated > 1) ? prev_calculated - 1 : 1;
//--- 逐根K线检测信号
for(int i = start; i < rates_total && !IsStopped(); i++)
{
//--- 默认无信号(每根K线先初始化为 EMPTY_VALUE)
BuySignalBuffer[i] = EMPTY_VALUE;
SellSignalBuffer[i] = EMPTY_VALUE;
//--- 至少需要前一根K线来判断反弹/回落(i < 1 时跳过)
if(i < 1) continue;
//--- 检查是否有买入信号(价格回踩需求区且反弹)
if(InpShowSignals)
{
ZoneInfo nearest_demand;
if(FindNearestZone(close[i], true, nearest_demand))
{
//--- 判断当前K线是否在需求区内或刚离开
bool in_zone = (low[i] <= nearest_demand.price_top && low[i] >= nearest_demand.price_bottom);
bool just_left = (low[i-1] <= nearest_demand.price_top && close[i] > nearest_demand.price_top);
if(in_zone || just_left)
{
//--- 检查是否是反弹K线(阳线且幅度足够)
bool is_bullish = close[i] > open[i];
double bounce_pips = (close[i] - low[i]) / _Point;
if(is_bullish && bounce_pips >= InpSignalMinPips)
{
//--- 只在这根K线显示买入箭头
BuySignalBuffer[i] = low[i] - InpArrowOffset * _Point;
//--- 绘制止盈止损线
if(InpShowSLTP)
{
double sl_price = nearest_demand.price_bottom - 2 * _Point; // 止损设在需求区下方
double sl_pips = (close[i] - sl_price) / _Point;
double tp_price = close[i] + sl_pips * InpRiskRewardRatio * _Point; // 按盈亏比计算止盈
DrawSLTPLines(time[i], close[i], sl_price, tp_price, true);
}
}
}
}
//--- 检查是否有卖出信号(价格触及供给区且回落)
ZoneInfo nearest_supply;
if(FindNearestZone(close[i], false, nearest_supply))
{
bool in_zone = (high[i] >= nearest_supply.price_bottom && high[i] <= nearest_supply.price_top);
bool just_left = (high[i-1] >= nearest_supply.price_bottom && close[i] < nearest_supply.price_bottom);
if(in_zone || just_left)
{
//--- 检查是否是回落K线(阴线且幅度足够)
bool is_bearish = close[i] < open[i];
double drop_pips = (high[i] - close[i]) / _Point;
if(is_bearish && drop_pips >= InpSignalMinPips)
{
//--- 只在这根K线显示卖出箭头
SellSignalBuffer[i] = high[i] + InpArrowOffset * _Point;
//--- 绘制止盈止损线
if(InpShowSLTP)
{
double sl_price = nearest_supply.price_top + 2 * _Point;
double sl_pips = (sl_price - close[i]) / _Point;
double tp_price = close[i] - sl_pips * InpRiskRewardRatio * _Point;
DrawSLTPLines(time[i], close[i], sl_price, tp_price, false);
}
}
}
}
}
}
//--- 检查价格是否触及区域(可选:触及后删除)
if(InpDeleteOldZones && rates_total > 0)
{
CheckZonesTouched(close[rates_total - 1]);
}
return rates_total;
}
//+------------------------------------------------------------------+
//| 创建供需区矩形 |
//+------------------------------------------------------------------+
void CreateZone(datetime start_time, double price_top, double price_bottom, bool is_demand)
{
//--- 检查最大区域数量限制
if(InpMaxZones > 0 && g_zone_count >= InpMaxZones)
{
DeleteOldestZone();
}
//--- 生成唯一对象名
string obj_name = g_prefix + (is_demand ? "D_" : "S_") + TimeToString(start_time, TIME_DATE | TIME_SECONDS);
//--- 计算结束时间(向右延伸)
datetime end_time = start_time + InpZoneExtendBars * PeriodSeconds();
//--- 创建矩形对象
if(ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, start_time, price_top, end_time, price_bottom))
{
color zone_color = is_demand ? InpDemandColor : InpSupplyColor;
ObjectSetInteger(0, obj_name, OBJPROP_COLOR, zone_color);
ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, obj_name, OBJPROP_FILL, true);
ObjectSetInteger(0, obj_name, OBJPROP_BACK, true);
ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, obj_name, OBJPROP_HIDDEN, true);
//--- 设置透明度
int alpha = (int)MathRound(255 * (100 - InpZoneTransparency) / 100.0);
if(alpha < 0) alpha = 0;
if(alpha > 255) alpha = 255;
long color_with_alpha = (long)zone_color | ((long)alpha << 24);
ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, color_with_alpha);
//--- 保存到数组
int idx = g_zone_count;
ArrayResize(g_zones, g_zone_count + 1);
g_zones[idx].name = obj_name;
g_zones[idx].price_top = price_top;
g_zones[idx].price_bottom = price_bottom;
g_zones[idx].time_start = start_time;
g_zones[idx].is_demand = is_demand;
g_zones[idx].touched = false;
g_zone_count++;
//--- 添加文字标注
if(InpShowLabels)
{
string label_name = obj_name + "_LBL";
double label_price = is_demand ? price_bottom : price_top;
string label_text = is_demand ? "Demand" : "Supply";
if(ObjectCreate(0, label_name, OBJ_TEXT, 0, start_time, label_price))
{
ObjectSetString(0, label_name, OBJPROP_TEXT, label_text);
ObjectSetInteger(0, label_name, OBJPROP_COLOR, zone_color);
ObjectSetInteger(0, label_name, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, label_name, OBJPROP_ANCHOR, is_demand ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER);
ObjectSetInteger(0, label_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, label_name, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, label_name, OBJPROP_BACK, false);
}
}
}
}
//+------------------------------------------------------------------+
//| 查找最近的供需区 |
//+------------------------------------------------------------------+
bool FindNearestZone(double current_price, bool is_demand, ZoneInfo &zone_out)
{
double nearest_distance = DBL_MAX;
bool found = false;
for(int i = 0; i < g_zone_count; i++)
{
//--- 只查找对应类型的区域
if(g_zones[i].is_demand != is_demand)
continue;
//--- 跳过已触及的区域
if(g_zones[i].touched && InpDeleteOldZones)
continue;
//--- 计算距离
double distance;
if(is_demand)
{
//--- 需求区:计算价格到区域上边界的距离(价格应该在上方)
distance = current_price - g_zones[i].price_top;
}
else
{
//--- 供给区:计算区域下边界到价格的距离(价格应该在下方)
distance = g_zones[i].price_bottom - current_price;
}
//--- 只考虑合理方向的区域,并找最近的(允许价格稍微进入区域内)
if(distance > -InpZoneThickness * _Point && distance < nearest_distance)
{
nearest_distance = distance;
zone_out = g_zones[i];
found = true;
}
}
return found;
}
//+------------------------------------------------------------------+
//| 绘制止盈止损线 |
//+------------------------------------------------------------------+
void DrawSLTPLines(datetime signal_time, double entry_price, double sl_price, double tp_price, bool is_buy)
{
string base_name = g_signal_prefix + TimeToString(signal_time, TIME_DATE | TIME_SECONDS);
//--- 止损线
string sl_name = base_name + "_SL";
if(ObjectCreate(0, sl_name, OBJ_TREND, 0, signal_time, sl_price, signal_time + 30 * PeriodSeconds(), sl_price))
{
ObjectSetInteger(0, sl_name, OBJPROP_COLOR, InpSLColor);
ObjectSetInteger(0, sl_name, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, sl_name, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, sl_name, OBJPROP_RAY_RIGHT, true);
ObjectSetInteger(0, sl_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, sl_name, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, sl_name, OBJPROP_BACK, true);
}
//--- 止盈线
string tp_name = base_name + "_TP";
if(ObjectCreate(0, tp_name, OBJ_TREND, 0, signal_time, tp_price, signal_time + 30 * PeriodSeconds(), tp_price))
{
ObjectSetInteger(0, tp_name, OBJPROP_COLOR, InpTPColor);
ObjectSetInteger(0, tp_name, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, tp_name, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, tp_name, OBJPROP_RAY_RIGHT, true);
ObjectSetInteger(0, tp_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, tp_name, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, tp_name, OBJPROP_BACK, true);
}
//--- 添加文字标签
double sl_pips = MathAbs(entry_price - sl_price) / _Point;
double tp_pips = MathAbs(tp_price - entry_price) / _Point;
string sl_label_name = base_name + "_SL_TXT";
if(ObjectCreate(0, sl_label_name, OBJ_TEXT, 0, signal_time, sl_price))
{
ObjectSetString(0, sl_label_name, OBJPROP_TEXT, StringFormat("SL %.0f点", sl_pips));
ObjectSetInteger(0, sl_label_name, OBJPROP_COLOR, InpSLColor);
ObjectSetInteger(0, sl_label_name, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, sl_label_name, OBJPROP_ANCHOR, is_buy ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER);
ObjectSetInteger(0, sl_label_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, sl_label_name, OBJPROP_HIDDEN, true);
}
string tp_label_name = base_name + "_TP_TXT";
if(ObjectCreate(0, tp_label_name, OBJ_TEXT, 0, signal_time, tp_price))
{
ObjectSetString(0, tp_label_name, OBJPROP_TEXT, StringFormat("TP %.0f点", tp_pips));
ObjectSetInteger(0, tp_label_name, OBJPROP_COLOR, InpTPColor);
ObjectSetInteger(0, tp_label_name, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, tp_label_name, OBJPROP_ANCHOR, is_buy ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER);
ObjectSetInteger(0, tp_label_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, tp_label_name, OBJPROP_HIDDEN, true);
}
}
//+------------------------------------------------------------------+
//| 检查区域是否已存在 |
//+------------------------------------------------------------------+
bool ZoneExists(double price_top, double price_bottom, datetime check_time)
{
double tolerance = 5 * _Point;
for(int i = 0; i < g_zone_count; i++)
{
if(MathAbs(g_zones[i].price_top - price_top) < tolerance &&
MathAbs(g_zones[i].price_bottom - price_bottom) < tolerance)
{
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| 检查价格是否触及区域 |
//+------------------------------------------------------------------+
void CheckZonesTouched(double current_price)
{
for(int i = g_zone_count - 1; i >= 0; i--)
{
if(g_zones[i].touched)
continue;
if(current_price >= g_zones[i].price_bottom && current_price <= g_zones[i].price_top)
{
g_zones[i].touched = true;
ObjectDelete(0, g_zones[i].name);
ObjectDelete(0, g_zones[i].name + "_LBL");
for(int j = i; j < g_zone_count - 1; j++)
{
g_zones[j] = g_zones[j + 1];
}
ArrayResize(g_zones, g_zone_count - 1);
g_zone_count--;
}
}
}
//+------------------------------------------------------------------+
//| 删除最老的区域 |
//+------------------------------------------------------------------+
void DeleteOldestZone()
{
if(g_zone_count == 0)
return;
int oldest_idx = 0;
datetime oldest_time = g_zones[0].time_start;
for(int i = 1; i < g_zone_count; i++)
{
if(g_zones[i].time_start < oldest_time)
{
oldest_time = g_zones[i].time_start;
oldest_idx = i;
}
}
ObjectDelete(0, g_zones[oldest_idx].name);
ObjectDelete(0, g_zones[oldest_idx].name + "_LBL");
for(int i = oldest_idx; i < g_zone_count - 1; i++)
{
g_zones[i] = g_zones[i + 1];
}
ArrayResize(g_zones, g_zone_count - 1);
g_zone_count--;
}
//+------------------------------------------------------------------+
//| 清理所有对象 |
//+------------------------------------------------------------------+
void DeleteAllObjects()
{
//--- 删除供需区矩形
int total = ObjectsTotal(0, 0, OBJ_RECTANGLE);
for(int i = total - 1; i >= 0; i--)
{
string name = ObjectName(0, i, 0, OBJ_RECTANGLE);
if(StringFind(name, g_prefix) == 0)
{
ObjectDelete(0, name);
ObjectDelete(0, name + "_LBL");
}
}
//--- 删除文字标签
total = ObjectsTotal(0, 0, OBJ_TEXT);
for(int i = total - 1; i >= 0; i--)
{
string name = ObjectName(0, i, 0, OBJ_TEXT);
if(StringFind(name, g_prefix) == 0 || StringFind(name, g_signal_prefix) == 0)
ObjectDelete(0, name);
}
//--- 删除止盈止损线
total = ObjectsTotal(0, 0, OBJ_TREND);
for(int i = total - 1; i >= 0; i--)
{
string name = ObjectName(0, i, 0, OBJ_TREND);
if(StringFind(name, g_signal_prefix) == 0)
ObjectDelete(0, name);
}
}
//+------------------------------------------------------------------+作品说明
我已经按照 MT5 真实画图规则重写了完整代码。这一版完全移除了 ArraySetAsSeries,使用正向索引(0=最老,rates_total-1=最新),箭头缓冲区全部用 EMPTY_VALUE 初始化,循环边界正确处理了 i-1 的访问范围。
相对原作的改动:
- 新增买卖信号箭头:价格触及需求区(支撑)且出现反弹阳线时显示绿色上箭头,触及供给区(阻力)且出现回落阴线时显示红色下箭头
- 新增止盈止损标注:每个信号自动绘制橙红色虚线(止损)和绿色虚线(止盈),带点数标签,按盈亏比 2:1 计算
- 修复索引方向问题:移除所有 ArraySetAsSeries,使用 MT5 标准正向索引,确保图形显示正确
使用说明
安装方法:
- 打开 MT5 的 MetaEditor(按 F4)
- 新建指标文件,粘贴上面的完整代码
- 点击编译(F7),确保 0 errors, 0 warnings
- 回到 MT5 图表,从导航器拖拽指标到图上
参数调节:
- 摆动识别周期:默认 5,数字越大识别的供需区越少但越可靠
- 供需区厚度:默认 30 点,根据品种波动调整(黄金可用 50-100,外汇用 20-50)
- 信号确认最小点数:默认 5,反弹/回落幅度小于此值不显示箭头
- 盈亏比:默认 2.0,止盈是止损的 2 倍,可根据交易风格调整为 1.5 或 3.0
- 删除已触及区域:开启后价格穿过的区域会自动消失,保持图表清爽
信号怎么看:
- 蓝色矩形:需求区(支撑),价格回踩这里可能反弹做多
- 红色矩形:供给区(阻力),价格触及这里可能回落做空
- 绿色上箭头:买入信号,出现在需求区反弹时,配合橙红色止损线(下方)和绿色止盈线(上方)
- 红色下箭头:卖出信号,出现在供给区回落时,配合橙红色止损线(上方)和绿色止盈线(下方)
- 线旁边的文字显示止损/止盈的点数
注意事项:
- 供需区只是参考位置,不是精确点位,需结合其他指标(如趋势、成交量)确认
- 箭头只标注可能的交易机会,不构成进场指令,请根据自己的交易计划决策
- 止盈止损是按盈亏比自动计算的建议值,实际交易需根据市场情况调整
- 首次加载会扫描历史K线画出所有供需区,可能有短暂延迟
- 新K线实时更新,旧区域会根据设置自动消失或保留
AI 解读根据源码逐行分析写成,供参考,以实际运行为准
这是什么
这是一个为 MT5 设计的供需区识别与交易信号指标。它扫描历史 K 线找出摆动高点(供给区/阻力)和摆动低点(需求区/支撑),用彩色矩形标注在图表上;当价格回踩需求区出现反弹阳线时显示绿色买入箭头,触及供给区出现回落阴线时显示红色卖出箭头,并自动绘制橙红色止损线和绿色止盈线,旁边标注具体点数。适合用价格行为和订单块思路做交易的用户。
逻辑原理
供需区识别:程序从左到右扫描 K 线,找出「某根 K 线的高点比前后各 N 根都高」的摆动高点,在其上方画出红色矩形(供给区,卖方压力区域);找出「某根 K 线的低点比前后各 N 根都低」的摆动低点,在其下方画出蓝色矩形(需求区,买方支撑区域)。矩形的厚度由参数决定,默认 30 点,向右延伸若干根 K 线。
买卖信号:每根新 K 线形成时,检查它是否接触或刚离开某个供需区。若当前 K 线的最低价进入需求区范围,且这根 K 线是阳线(收盘价高于开盘价),并且从低点到收盘的反弹幅度 >= 最小确认点数(默认 5 点),则在该 K 线下方显示绿色上箭头,表示买入信号。卖出信号逻辑相反:最高价进入供给区,K 线是阴线,从高点到收盘的回落幅度足够,则在上方显示红色下箭头。
止盈止损计算:买入信号的止损设在需求区下边界减 2 点,止盈按「止损点数 × 盈亏比」算出;卖出信号的止损在供给区上边界加 2 点,止盈同理。程序用虚线画出这两条水平线,旁边用小字标注「SL XX点」「TP XX点」。默认盈亏比是 2:1,即止盈是止损的两倍。
区域管理:可设置最多显示多少个区域(超出后自动删除最老的),以及价格穿过区域后是否自动删除该矩形(保持图表清爽)。
参数说明
- InpSwingLength(默认 5):摆动识别周期,即「某根 K 线的高/低点要比前后各 N 根都高/低」的 N 值。数字越大,识别出的供需区越少但越可靠;数字越小,区域越多但可能出现假区域。
- InpZoneThickness(默认 30):供需区矩形的厚度,单位是点数。黄金(XAUUSD)波动大可用 50-100,外汇货币对用 20-50。
- InpZoneExtendBars(默认 50):矩形向右延伸多少根 K 线,影响矩形的宽度。
- InpDemandColor / InpSupplyColor:需求区和供给区的颜色,默认蓝色和番茄红。
- InpZoneTransparency(默认 85):区域透明度,0-100,数字越大越透明。
- InpShowLabels(默认 true):是否在矩形旁边显示「Demand」或「Supply」文字。
- InpMaxZones(默认 10):最多显示多少个区域,0 表示不限制。超出后删除最老的区域。
- InpDeleteOldZones(默认 true):价格穿过某个区域后是否自动删除它。
- InpSignalMinPips(默认 5):信号确认的最小点数。反弹或回落幅度小于这个值不显示箭头,避免微小波动的假信号。
- InpShowSignals(默认 true):是否显示买卖信号箭头。
- InpArrowOffset(默认 10):箭头距离 K 线的距离(点数),调大一点可以让箭头不遮挡 K 线。
- InpShowSLTP(默认 true):是否显示止盈止损线和文字标签。
- InpRiskRewardRatio(默认 2.0):盈亏比,即止盈是止损的多少倍。激进的交易者可以设 1.5,保守的可以设 3.0。
- InpSLColor / InpTPColor:止损线和止盈线的颜色,默认橙红色和春绿色。
怎么用
- 打开 MT5 的 MetaEditor(按 F4 键),新建一个指标文件。
- 把完整代码粘贴进去,点编译(F7),确保没有错误。
- 回到 MT5 图表窗口,从导航器(Ctrl+N)拖拽这个指标到你想交易的品种图表上(建议 H1 或 H4 周期)。
- 在弹出的参数窗口里根据品种和周期调整参数(新手可以先用默认值)。
- 图表上会自动画出历史和实时的供需区矩形(蓝色需求区在下方,红色供给区在上方)。
- 当价格回踩蓝色需求区且出现反弹阳线,下方显示绿色上箭头 + 止损止盈线,这是买入参考信号。
- 当价格触及红色供给区且出现回落阴线,上方显示红色下箭头 + 止损止盈线,这是卖出参考信号。
- 止损线(橙红色虚线)和止盈线(绿色虚线)旁边标注了具体点数,可以参考它们设置订单。
适用场景
- 品种:适合波动较大的品种,如黄金(XAUUSD)、原油、主要外汇货币对(EURUSD、GBPUSD)。小品种或冷门货币对可能因为价格跳动不规律导致假信号。
- 周期:建议 H1(1 小时)或 H4(4 小时)以上。周期太小(如 M5、M15)会产生大量假信号和重叠区域,周期太大(如 D1)信号太少。
- 行情类型:适合震荡行情和趋势行情中的回调。震荡行情中价格反复在供需区之间来回,信号较多;趋势行情中等待价格回踩关键支撑/阻力区域再进场。不适合单边暴涨暴跌或波动极小的盘整行情。
- 交易风格:适合做价格行为、订单块、支撑阻力的交易者,以及喜欢等待「确认信号」再进场的稳健型交易者。不适合需要精确点位或高频进出的剥头皮交易。
风险提示
- 滞后性:供需区是基于历史摆动点画出的,无法预测未来,只能告诉你「过去这里曾是关键点位」。价格可能直接击穿区域不反弹。
- 假突破:价格触及供需区后未必真的反转,可能只是短暂停顿然后继续原方向运行(假信号)。建议结合趋势、成交量或其他指标过滤。
- 不会重绘:箭头信号一旦出现不会消失或移动位置,但这不意味着它是「最佳进场点」,只是满足了代码设定的条件。
- 止盈止损是建议值:程序自动计算的止损在区域边界外侧,止盈按盈亏比算出,这只是参考。实际交易需要根据行情强度、重要阻力位、资金管理规则调整。
- 区域重叠:如果参数设置不当(摆动周期太小、厚度太大),可能出现大量重叠的矩形,图表变得混乱。新手建议先用默认参数测试。
- 首次加载延迟:指标会扫描历史 K 线画出所有供需区,如果历史数据很多可能有短暂卡顿。
- 不考虑点差和滑点:止盈止损的点数计算基于收盘价,实际下单时需要加上点差,快速行情可能有滑点。
- 需要人工判断:箭头只是提示「价格在关键位置出现了反应」,不是进场指令。最终决策需要结合更大周期趋势、基本面消息、风险承受能力等因素。
常见问题
- 这个指标会重绘吗?箭头会消失或移动吗?
- 不会重绘。箭头一旦在某根 K 线显示就固定在那里,不会因为后续价格变化而消失或移位。供需区矩形如果价格穿过且开启了「删除已触及区域」会自动消失,但这不影响已显示的箭头。
- 默认参数适合什么周期和品种?
- 默认参数(摆动周期 5,区域厚度 30 点,盈亏比 2:1)适合黄金 XAUUSD 的 H1 或 H4 周期。如果交易外汇货币对,建议把区域厚度调小到 20-30 点;如果用 M15 这样的小周期,摆动周期可以调到 3,但会增加假信号。
- 止盈止损的点数是怎么算的?可以手动改吗?
- 买入信号的止损设在需求区下边界减 2 点,止盈是「当前价到止损的距离 × 盈亏比」;卖出信号反之。盈亏比默认 2.0(止盈是止损的 2 倍),可以在参数里改成 1.5 或 3.0。线上标注的点数只是参考,实际下单时你可以根据重要阻力位或个人风险偏好手动调整。
- 箭头信号可以直接跟单吗?准确率怎么样?
- 不建议盲目跟单。箭头只表示「价格在供需区出现了反应」,但不能保证反转会持续。代码里有最小确认点数参数(默认 5 点)来过滤微小波动,但仍可能出现假信号。建议结合更大周期趋势、支撑阻力位、或其他指标(如 MACD、RSI)确认后再进场,并严格设止损。
- 为什么图表上矩形太多或太少?
- 矩形数量由「摆动识别周期」和「最多显示区域数」控制。如果矩形太多且重叠,把摆动周期调大(比如从 5 调到 7)或减小最多显示区域数(比如从 10 调到 5);如果矩形太少,把摆动周期调小(比如 3)。另外,如果开启了「删除已触及区域」,价格穿过的区域会自动消失。
Smart Money Supply & Demand Zone Indicator with Trade Signals for MT5
Automatically identifies supply and demand zones based on swing highs/lows, displays buy/sell arrows when price reacts, and draws SL/TP lines with pip labels. Adjustable risk-reward ratio for traders who use price action strategies.
Source code is shown above. Compiled EX5 files are available to VIP members.
评论
还没有人评论,来抢个沙发。