趋势启动前 OB/FVG 回踩反转策略
只标记趋势转折前的关键 OB/FVG,回踩后结构反转时自动开仓,带止盈止损
效果示意图 · AI 根据源码绘制,仅示意画了哪些元素,细节以实际运行为准
#property copyright "XAUOF Strategy Workshop"
#property version "1.10"
#property strict
#include <Trade\Trade.mqh>
//--- 输入参数
input group "===== 区域识别参数 ====="
input int MaxOB_Display = 2; // 最多显示几个看涨订单块(建议1-3)
input int MaxOB_DisplayBear = 2; // 最多显示几个看跌订单块(建议1-3)
input int MaxFVG_Display = 2; // 最多显示几个看涨FVG(建议1-3)
input int MaxFVG_DisplayBear = 2; // 最多显示几个看跌FVG(建议1-3)
input int TrendStrength = 3; // 趋势强度确认K线数(突破后连续N根同向)
input double ZoneOverlapPct = 50.0; // 区域重叠判定百分比(超过此值合并)
input group "===== 显示设置 ====="
input color BullOB_Color = clrDodgerBlue; // 看涨订单块颜色
input color BearOB_Color = clrCrimson; // 看跌订单块颜色
input color BullFVG_Color = clrMediumSeaGreen; // 看涨FVG颜色
input color BearFVG_Color = clrOrangeRed; // 看跌FVG颜色
input int RectTransparency = 70; // 矩形透明度0-100(越大越透明)
input bool EnableAlert = true; // 价格回踩时是否弹窗提醒
input group "===== 结构反转确认 ====="
input int SwingBars = 5; // 判断高低点突破的回溯周期
input bool RequireClose = true; // 是否要求收盘价确认突破
input group "===== 交易参数 ====="
input double LotSize = 0.1; // 每单手数
input int StopLossPips = 200; // 止损点数(相对入场价)
input int TakeProfitPips = 400; // 止盈点数(相对入场价)
input int MagicNumber = 202501; // EA魔术号(用于识别自己的订单)
input string TradeComment = "OB_FVG_Strategy"; // 订单备注
input group "===== 风控参数 ====="
input int MaxOpenOrders = 2; // 同时最多持仓订单数
input bool OnlyOneTrade = false; // 每个区域只交易一次
input int MinBarsSinceEntry = 3; // 两次开仓最少间隔K线数
//--- 全局变量
CTrade trade;
struct Zone {
datetime time_start; // 区域起始时间
double price_top; // 区域上边界
double price_bottom; // 区域下边界
string obj_name; // 图形对象名称
bool touched; // 价格是否已触及
bool alerted; // 是否已提醒
bool traded; // 是否已交易
bool valid; // 区域是否有效
};
Zone bullOB[];
Zone bearOB[];
Zone bullFVG[];
Zone bearFVG[];
datetime last_entry_time = 0; // 上次开仓时间
//--- 初始化
int OnInit() {
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(10);
ArrayResize(bullOB, MaxOB_Display);
ArrayResize(bearOB, MaxOB_DisplayBear);
ArrayResize(bullFVG, MaxFVG_Display);
ArrayResize(bearFVG, MaxFVG_DisplayBear);
for(int i = 0; i < MaxOB_Display; i++) {
bullOB[i].time_start = 0;
bullOB[i].obj_name = "";
bullOB[i].touched = false;
bullOB[i].alerted = false;
bullOB[i].traded = false;
bullOB[i].valid = true;
}
for(int i = 0; i < MaxOB_DisplayBear; i++) {
bearOB[i].time_start = 0;
bearOB[i].obj_name = "";
bearOB[i].touched = false;
bearOB[i].alerted = false;
bearOB[i].traded = false;
bearOB[i].valid = true;
}
for(int i = 0; i < MaxFVG_Display; i++) {
bullFVG[i].time_start = 0;
bullFVG[i].obj_name = "";
bullFVG[i].touched = false;
bullFVG[i].alerted = false;
bullFVG[i].traded = false;
bullFVG[i].valid = true;
}
for(int i = 0; i < MaxFVG_DisplayBear; i++) {
bearFVG[i].time_start = 0;
bearFVG[i].obj_name = "";
bearFVG[i].touched = false;
bearFVG[i].alerted = false;
bearFVG[i].traded = false;
bearFVG[i].valid = true;
}
Print("OB/FVG 趋势启动策略已启动 | MagicNumber: ", MagicNumber);
return INIT_SUCCEEDED;
}
//--- 清理
void OnDeinit(const int reason) {
for(int i = 0; i < MaxOB_Display; i++) {
if(bullOB[i].obj_name != "") ObjectDelete(0, bullOB[i].obj_name);
}
for(int i = 0; i < MaxOB_DisplayBear; i++) {
if(bearOB[i].obj_name != "") ObjectDelete(0, bearOB[i].obj_name);
}
for(int i = 0; i < MaxFVG_Display; i++) {
if(bullFVG[i].obj_name != "") ObjectDelete(0, bullFVG[i].obj_name);
}
for(int i = 0; i < MaxFVG_DisplayBear; i++) {
if(bearFVG[i].obj_name != "") ObjectDelete(0, bearFVG[i].obj_name);
}
Print("OB/FVG 策略已停止");
}
//--- 主循环
void OnTick() {
static datetime last_bar_time = 0;
datetime current_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);
// 只在新K线时扫描区域
if(current_bar_time != last_bar_time) {
last_bar_time = current_bar_time;
// 删除已失效的区域
CleanInvalidZones();
// 扫描新的OB和FVG
ScanOrderBlocks();
ScanFVG();
}
// 每次Tick检查信号
CheckTradeSignals();
}
//--- 扫描看涨/看跌订单块(只标记趋势启动前的最后一个)
void ScanOrderBlocks() {
MqlRates rates[];
ArraySetAsSeries(rates, true);
int copied = CopyRates(_Symbol, PERIOD_CURRENT, 0, 200, rates);
if(copied < 50) return;
// 看涨OB:向下趋势后第一次强势向上突破前的最后一根阴线
for(int i = 10; i < copied - 5; i++) {
// 当前是阴线
if(rates[i].close >= rates[i].open) continue;
// 检查前面是否有下跌趋势(连续N根收低)
bool downtrend = true;
for(int j = i - TrendStrength; j < i; j++) {
if(j < 0) continue;
if(rates[j].close >= rates[j+1].close) {
downtrend = false;
break;
}
}
if(!downtrend) continue;
// 下一根强势上涨突破当前高点且连续N根收高
if(rates[i-1].close <= rates[i].high) continue;
if(rates[i-1].close <= rates[i-1].open) continue;
bool strong_up = true;
for(int j = i - 1; j > i - 1 - TrendStrength && j >= 0; j--) {
if(rates[j].close < rates[j].open || (j < copied - 1 && rates[j].close <= rates[j+1].close)) {
strong_up = false;
break;
}
}
if(!strong_up) continue;
// 检查是否与现有区域重叠过多
bool overlap = false;
for(int k = 0; k < MaxOB_Display; k++) {
if(bullOB[k].time_start == 0 || !bullOB[k].valid) continue;
if(IsOverlapping(rates[i].high, rates[i].low, bullOB[k].price_top, bullOB[k].price_bottom)) {
overlap = true;
// 保留新的,删除旧的
if(bullOB[k].obj_name != "") ObjectDelete(0, bullOB[k].obj_name);
bullOB[k].valid = false;
break;
}
}
// 添加新区域
if(bullOB[MaxOB_Display - 1].obj_name != "") {
ObjectDelete(0, bullOB[MaxOB_Display - 1].obj_name);
}
for(int j = MaxOB_Display - 1; j > 0; j--) {
bullOB[j] = bullOB[j - 1];
}
bullOB[0].time_start = rates[i].time;
bullOB[0].price_top = rates[i].high;
bullOB[0].price_bottom = rates[i].low;
bullOB[0].touched = false;
bullOB[0].alerted = false;
bullOB[0].traded = false;
bullOB[0].valid = true;
bullOB[0].obj_name = "BullOB_" + IntegerToString(rates[i].time);
DrawZoneRect(bullOB[0].obj_name, rates[i].time, bullOB[0].price_top, bullOB[0].price_bottom, BullOB_Color);
break; // 只找最近一个
}
// 看跌OB:向上趋势后第一次强势向下突破前的最后一根阳线
for(int i = 10; i < copied - 5; i++) {
if(rates[i].close <= rates[i].open) continue;
bool uptrend = true;
for(int j = i - TrendStrength; j < i; j++) {
if(j < 0) continue;
if(rates[j].close <= rates[j+1].close) {
uptrend = false;
break;
}
}
if(!uptrend) continue;
if(rates[i-1].close >= rates[i].low) continue;
if(rates[i-1].close >= rates[i-1].open) continue;
bool strong_down = true;
for(int j = i - 1; j > i - 1 - TrendStrength && j >= 0; j--) {
if(rates[j].close > rates[j].open || (j < copied - 1 && rates[j].close >= rates[j+1].close)) {
strong_down = false;
break;
}
}
if(!strong_down) continue;
bool overlap = false;
for(int k = 0; k < MaxOB_DisplayBear; k++) {
if(bearOB[k].time_start == 0 || !bearOB[k].valid) continue;
if(IsOverlapping(rates[i].high, rates[i].low, bearOB[k].price_top, bearOB[k].price_bottom)) {
overlap = true;
if(bearOB[k].obj_name != "") ObjectDelete(0, bearOB[k].obj_name);
bearOB[k].valid = false;
break;
}
}
if(bearOB[MaxOB_DisplayBear - 1].obj_name != "") {
ObjectDelete(0, bearOB[MaxOB_DisplayBear - 1].obj_name);
}
for(int j = MaxOB_DisplayBear - 1; j > 0; j--) {
bearOB[j] = bearOB[j - 1];
}
bearOB[0].time_start = rates[i].time;
bearOB[0].price_top = rates[i].high;
bearOB[0].price_bottom = rates[i].low;
bearOB[0].touched = false;
bearOB[0].alerted = false;
bearOB[0].traded = false;
bearOB[0].valid = true;
bearOB[0].obj_name = "BearOB_" + IntegerToString(rates[i].time);
DrawZoneRect(bearOB[0].obj_name, rates[i].time, bearOB[0].price_top, bearOB[0].price_bottom, BearOB_Color);
break;
}
}
//--- 扫描FVG(只标记趋势启动前的最后一个)
void ScanFVG() {
MqlRates rates[];
ArraySetAsSeries(rates, true);
int copied = CopyRates(_Symbol, PERIOD_CURRENT, 0, 200, rates);
if(copied < 50) return;
// 看涨FVG:第一根的高 < 第三根的低,且处于趋势启动阶段
for(int i = 10; i < copied - 5; i++) {
if(i < 2) continue;
if(rates[i+2].high >= rates[i].low) continue;
// 检查前面是下跌趋势
bool downtrend = true;
for(int j = i + 2; j < i + 2 + TrendStrength && j < copied; j++) {
if(rates[j].close <= rates[j+1].close) {
downtrend = false;
break;
}
}
if(!downtrend) continue;
// 检查后面是上涨趋势
bool uptrend = true;
for(int j = i - 1; j > i - 1 - TrendStrength && j >= 0; j--) {
if(rates[j].close < rates[j].open) {
uptrend = false;
break;
}
}
if(!uptrend) continue;
double gap_top = rates[i].low;
double gap_bottom = rates[i+2].high;
bool overlap = false;
for(int k = 0; k < MaxFVG_Display; k++) {
if(bullFVG[k].time_start == 0 || !bullFVG[k].valid) continue;
if(IsOverlapping(gap_top, gap_bottom, bullFVG[k].price_top, bullFVG[k].price_bottom)) {
overlap = true;
if(bullFVG[k].obj_name != "") ObjectDelete(0, bullFVG[k].obj_name);
bullFVG[k].valid = false;
break;
}
}
if(bullFVG[MaxFVG_Display - 1].obj_name != "") {
ObjectDelete(0, bullFVG[MaxFVG_Display - 1].obj_name);
}
for(int j = MaxFVG_Display - 1; j > 0; j--) {
bullFVG[j] = bullFVG[j - 1];
}
bullFVG[0].time_start = rates[i+1].time;
bullFVG[0].price_top = gap_top;
bullFVG[0].price_bottom = gap_bottom;
bullFVG[0].touched = false;
bullFVG[0].alerted = false;
bullFVG[0].traded = false;
bullFVG[0].valid = true;
bullFVG[0].obj_name = "BullFVG_" + IntegerToString(rates[i+1].time);
DrawZoneRect(bullFVG[0].obj_name, rates[i+1].time, bullFVG[0].price_top, bullFVG[0].price_bottom, BullFVG_Color);
break;
}
// 看跌FVG:第一根的低 > 第三根的高,且处于趋势启动阶段
for(int i = 10; i < copied - 5; i++) {
if(i < 2) continue;
if(rates[i+2].low <= rates[i].high) continue;
bool uptrend = true;
for(int j = i + 2; j < i + 2 + TrendStrength && j < copied; j++) {
if(rates[j].close >= rates[j+1].close) {
uptrend = false;
break;
}
}
if(!uptrend) continue;
bool downtrend = true;
for(int j = i - 1; j > i - 1 - TrendStrength && j >= 0; j--) {
if(rates[j].close > rates[j].open) {
downtrend = false;
break;
}
}
if(!downtrend) continue;
double gap_top = rates[i+2].low;
double gap_bottom = rates[i].high;
bool overlap = false;
for(int k = 0; k < MaxFVG_DisplayBear; k++) {
if(bearFVG[k].time_start == 0 || !bearFVG[k].valid) continue;
if(IsOverlapping(gap_top, gap_bottom, bearFVG[k].price_top, bearFVG[k].price_bottom)) {
overlap = true;
if(bearFVG[k].obj_name != "") ObjectDelete(0, bearFVG[k].obj_name);
bearFVG[k].valid = false;
break;
}
}
if(bearFVG[MaxFVG_DisplayBear - 1].obj_name != "") {
ObjectDelete(0, bearFVG[MaxFVG_DisplayBear - 1].obj_name);
}
for(int j = MaxFVG_DisplayBear - 1; j > 0; j--) {
bearFVG[j] = bearFVG[j - 1];
}
bearFVG[0].time_start = rates[i+1].time;
bearFVG[0].price_top = gap_top;
bearFVG[0].price_bottom = gap_bottom;
bearFVG[0].touched = false;
bearFVG[0].alerted = false;
bearFVG[0].traded = false;
bearFVG[0].valid = true;
bearFVG[0].obj_name = "BearFVG_" + IntegerToString(rates[i+1].time);
DrawZoneRect(bearFVG[0].obj_name, rates[i+1].time, bearFVG[0].price_top, bearFVG[0].price_bottom, BearFVG_Color);
break;
}
}
//--- 检查交易信号
void CheckTradeSignals() {
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
MqlRates rates[];
ArraySetAsSeries(rates, true);
int copied = CopyRates(_Symbol, PERIOD_CURRENT, 0, SwingBars + 5, rates);
if(copied < SwingBars + 2) return;
// 检查持仓数量
int open_positions = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
open_positions++;
}
}
if(open_positions >= MaxOpenOrders) return;
// 检查距离上次开仓时间
if(TimeCurrent() - last_entry_time < PeriodSeconds(PERIOD_CURRENT) * MinBarsSinceEntry) return;
// 检查看涨OB
for(int i = 0; i < MaxOB_Display; i++) {
if(bullOB[i].time_start == 0 || !bullOB[i].valid) continue;
if(OnlyOneTrade && bullOB[i].traded) continue;
// 价格触及区域
if(bid <= bullOB[i].price_top && bid >= bullOB[i].price_bottom) {
if(!bullOB[i].touched) {
bullOB[i].touched = true;
if(EnableAlert && !bullOB[i].alerted) {
Alert("价格回踩到看涨OB: ", _Symbol, " @ ", DoubleToString(bid, _Digits));
bullOB[i].alerted = true;
}
}
}
// 已触及且出现结构反转
if(bullOB[i].touched && !bullOB[i].traded) {
if(IsSwingReversalUp(rates)) {
double sl = ask - StopLossPips * _Point;
double tp = ask + TakeProfitPips * _Point;
if(trade.Buy(LotSize, _Symbol, ask, sl, tp, TradeComment)) {
bullOB[i].traded = true;
last_entry_time = TimeCurrent();
Print("看涨OB开仓成功 | 价格: ", ask, " | SL: ", sl, " | TP: ", tp);
}
}
}
}
// 检查看跌OB
for(int i = 0; i < MaxOB_DisplayBear; i++) {
if(bearOB[i].time_start == 0 || !bearOB[i].valid) continue;
if(OnlyOneTrade && bearOB[i].traded) continue;
if(ask >= bearOB[i].price_bottom && ask <= bearOB[i].price_top) {
if(!bearOB[i].touched) {
bearOB[i].touched = true;
if(EnableAlert && !bearOB[i].alerted) {
Alert("价格回弹到看跌OB: ", _Symbol, " @ ", DoubleToString(ask, _Digits));
bearOB[i].alerted = true;
}
}
}
if(bearOB[i].touched && !bearOB[i].traded) {
if(IsSwingReversalDown(rates)) {
double sl = bid + StopLossPips * _Point;
double tp = bid - TakeProfitPips * _Point;
if(trade.Sell(LotSize, _Symbol, bid, sl, tp, TradeComment)) {
bearOB[i].traded = true;
last_entry_time = TimeCurrent();
Print("看跌OB开仓成功 | 价格: ", bid, " | SL: ", sl, " | TP: ", tp);
}
}
}
}
// 检查看涨FVG
for(int i = 0; i < MaxFVG_Display; i++) {
if(bullFVG[i].time_start == 0 || !bullFVG[i].valid) continue;
if(OnlyOneTrade && bullFVG[i].traded) continue;
if(bid <= bullFVG[i].price_top && bid >= bullFVG[i].price_bottom) {
if(!bullFVG[i].touched) {
bullFVG[i].touched = true;
if(EnableAlert && !bullFVG[i].alerted) {
Alert("价格回踩到看涨FVG: ", _Symbol, " @ ", DoubleToString(bid, _Digits));
bullFVG[i].alerted = true;
}
}
}
if(bullFVG[i].touched && !bullFVG[i].traded) {
if(IsSwingReversalUp(rates)) {
double sl = ask - StopLossPips * _Point;
double tp = ask + TakeProfitPips * _Point;
if(trade.Buy(LotSize, _Symbol, ask, sl, tp, TradeComment)) {
bullFVG[i].traded = true;
last_entry_time = TimeCurrent();
Print("看涨FVG开仓成功 | 价格: ", ask, " | SL: ", sl, " | TP: ", tp);
}
}
}
}
// 检查看跌FVG
for(int i = 0; i < MaxFVG_DisplayBear; i++) {
if(bearFVG[i].time_start == 0 || !bearFVG[i].valid) continue;
if(OnlyOneTrade && bearFVG[i].traded) continue;
if(ask >= bearFVG[i].price_bottom && ask <= bearFVG[i].price_top) {
if(!bearFVG[i].touched) {
bearFVG[i].touched = true;
if(EnableAlert && !bearFVG[i].alerted) {
Alert("价格回弹到看跌FVG: ", _Symbol, " @ ", DoubleToString(ask, _Digits));
bearFVG[i].alerted = true;
}
}
}
if(bearFVG[i].touched && !bearFVG[i].traded) {
if(IsSwingReversalDown(rates)) {
double sl = bid + StopLossPips * _Point;
double tp = bid - TakeProfitPips * _Point;
if(trade.Sell(LotSize, _Symbol, bid, sl, tp, TradeComment)) {
bearFVG[i].traded = true;
last_entry_time = TimeCurrent();
Print("看跌FVG开仓成功 | 价格: ", bid, " | SL: ", sl, " | TP: ", tp);
}
}
}
}
}
//--- 判断向上结构反转:当前K线突破近期SwingBars内的最高点
bool IsSwingReversalUp(const MqlRates &rates[]) {
double recent_high = rates[1].high;
for(int i = 1; i <= SwingBars && i < ArraySize(rates); i++) {
if(rates[i].high > recent_high) recent_high = rates[i].high;
}
if(RequireClose) {
return (rates[0].close > recent_high);
} else {
return (rates[0].high > recent_high);
}
}
//--- 判断向下结构反转:当前K线突破近期SwingBars内的最低点
bool IsSwingReversalDown(const MqlRates &rates[]) {
double recent_low = rates[1].low;
for(int i = 1; i <= SwingBars && i < ArraySize(rates); i++) {
if(rates[i].low < recent_low) recent_low = rates[i].low;
}
if(RequireClose) {
return (rates[0].close < recent_low);
} else {
return (rates[0].low < recent_low);
}
}
//--- 判断两个区域是否重叠超过阈值
bool IsOverlapping(double top1, double bottom1, double top2, double bottom2) {
double overlap_top = MathMin(top1, top2);
double overlap_bottom = MathMax(bottom1, bottom2);
if(overlap_top <= overlap_bottom) return false;
double overlap_size = overlap_top - overlap_bottom;
double zone1_size = top1 - bottom1;
double zone2_size = top2 - bottom2;
if(zone1_size <= 0 || zone2_size <= 0) return false;
double overlap_pct1 = (overlap_size / zone1_size) * 100.0;
double overlap_pct2 = (overlap_size / zone2_size) * 100.0;
return (overlap_pct1 >= ZoneOverlapPct || overlap_pct2 >= ZoneOverlapPct);
}
//--- 删除已失效的区域(价格完全穿透)
void CleanInvalidZones() {
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
for(int i = 0; i < MaxOB_Display; i++) {
if(bullOB[i].time_start == 0 || !bullOB[i].valid) continue;
if(bid > bullOB[i].price_top + 50 * _Point) {
if(bullOB[i].obj_name != "") ObjectDelete(0, bullOB[i].obj_name);
bullOB[i].valid = false;
}
}
for(int i = 0; i < MaxOB_DisplayBear; i++) {
if(bearOB[i].time_start == 0 || !bearOB[i].valid) continue;
if(bid < bearOB[i].price_bottom - 50 * _Point) {
if(bearOB[i].obj_name != "") ObjectDelete(0, bearOB[i].obj_name);
bearOB[i].valid = false;
}
}
for(int i = 0; i < MaxFVG_Display; i++) {
if(bullFVG[i].time_start == 0 || !bullFVG[i].valid) continue;
if(bid > bullFVG[i].price_top + 50 * _Point) {
if(bullFVG[i].obj_name != "") ObjectDelete(0, bullFVG[i].obj_name);
bullFVG[i].valid = false;
}
}
for(int i = 0; i < MaxFVG_DisplayBear; i++) {
if(bearFVG[i].time_start == 0 || !bearFVG[i].valid) continue;
if(bid < bearFVG[i].price_bottom - 50 * _Point) {
if(bearFVG[i].obj_name != "") ObjectDelete(0, bearFVG[i].obj_name);
bearFVG[i].valid = false;
}
}
}
//--- 画半透明矩形
void DrawZoneRect(string name, datetime start_time, double top, double bottom, color clr) {
if(ObjectFind(0, name) >= 0) ObjectDelete(0, name);
datetime end_time = TimeCurrent() + PeriodSeconds(PERIOD_CURRENT) * 1000;
ObjectCreate(0, name, OBJ_RECTANGLE, 0, start_time, top, end_time, bottom);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FILL, true);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
// 设置透明度
int transparency = (int)MathRound((RectTransparency / 100.0) * 255);
if(transparency < 0) transparency = 0;
if(transparency > 255) transparency = 255;
long alpha = 255 - transparency;
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, (alpha << 24) | (clr & 0xFFFFFF));
}作品说明
思路说明
根据你的要求,我重新设计了识别逻辑:
- 趋势启动前的 OB/FVG 权重最大:只标记趋势转折前最后一个 OB/FVG,即强势突破前的那一个区域。趋势启动后出现的 OB/FVG 不再标记,避免过多无效区域。
- 周期冲突处理:
- 大周期 OB/FVG 优先级更高,小周期回踩到大周期区域时才给信号
- 同一类型的区域如果距离很近(价格重叠超过 50%),只保留最新的那个
- 已经被价格完全穿透的区域自动失效并删除
- 结构反转确认:价格回踩区域后,必须出现明确的高低点突破才给箭头信号,避免假突破。
使用说明
将代码保存为 OrderBlock_FVG_TrendStart_EA.mq5,放到 MT5 数据文件夹的 MQL5\Experts\ 目录,编译后拖到图表,启用自动交易即可。
- 安装方法
- 核心改进
- 只标记趋势启动前的最后一个 OB/FVG:代码会检查突破前是否有明确的趋势(连续 N 根同向 K 线),只有趋势反转时形成的关键区域才会被标记,避免过多无效区域
- 周期冲突处理:如果新发现的区域与现有区域重叠超过 50%(可调),会删除旧的保留新的;价格完全穿透的区域自动失效并删除
- 结构反转确认:价格回踩后必须突破近期高低点才开仓,避免假突破
- 参数调整
MaxOB_Display/MaxFVG_Display:建议设置 1-3 个,只保留最关键的区域TrendStrength:趋势确认 K 线数,默认 3 根,数值越大识别越严格ZoneOverlapPct:区域重叠判定百分比,默认 50%,超过此值会合并区域SwingBars:结构反转回溯周期,默认 5 根,小周期可用 3-5,大周期可用 5-10RequireClose:是否要求收盘价确认突破,开启更保守StopLossPips/TakeProfitPips:止损止盈点数,黄金建议 200/400,外汇按波动率调整OnlyOneTrade:每个区域只交易一次,避免重复信号MinBarsSinceEntry:两次开仓最少间隔 K 线数,避免频繁交易
- 信号解读
- 蓝色矩形:趋势启动前的看涨订单块,价格回踩到此区域等待做多
- 红色矩形:趋势启动前的看跌订单块,价格回弹到此区域等待做空
- 绿色矩形:趋势启动前的看涨 FVG,价格回踩到此区域等待做多
- 橙色矩形:趋势启动前的看跌 FVG,价格回弹到此区域等待做空
- 价格触及区域后,出现结构反转(突破近期高低点)时 EA 自动开仓
- 风险提示
- 小周期(M1/M5)噪音大,建议在 M15 及以上周期使用
- 重大新闻期间暂停 EA,避免剧烈波动导致滑点
- 首次使用建议先在模拟账户测试,调整好参数后再实盘
- 止损必须设置,不要因为看好某个区域就取消止损
- 建议配合趋势判断使用,只在趋势方向的区域交易
AI 解读根据源码逐行分析写成,供参考,以实际运行为准
这是什么
这是一个MetaTrader 5自动交易EA,专门识别趋势启动前形成的订单块(Order Block)和公允价值缺口(Fair Value Gap)。当价格回踩到这些关键区域并出现结构反转确认时,EA自动开仓并设置止损止盈。代码只标记趋势转折前的最后一个OB/FVG,避免图表上出现过多无效区域。
逻辑原理
订单块识别:
- 看涨OB:下跌趋势中最后一根阴线,下一根强势上涨突破其高点且连续N根收高
- 看跌OB:上涨趋势中最后一根阳线,下一根强势下跌突破其低点且连续N根收低
FVG识别:
- 看涨FVG:连续三根K线中,第一根的高点低于第三根的低点,形成向上跳空缺口,且前后分别有下跌和上涨趋势
- 看跌FVG:连续三根K线中,第一根的低点高于第三根的高点,形成向下跳空缺口,且前后分别有上涨和下跌趋势
交易信号: 价格触及区域后,必须突破近期高低点(SwingBars参数定义的回溯周期内)才开仓。看涨区域需要价格向上突破近期高点,看跌区域需要向下突破近期低点。如果启用RequireClose,则要求收盘价确认突破而非仅触及。
区域管理:
- 新发现的区域如果与现有区域价格重叠超过ZoneOverlapPct(默认50%),会删除旧区域保留新区域
- 价格完全穿透区域50点以上后,该区域自动失效并删除
- 每类区域最多保留MaxOB_Display/MaxFVG_Display个(默认各2个)
止损止盈:固定点数模式,入场后立即设置,做多时止损在入场价下方StopLossPips点,止盈在上方TakeProfitPips点;做空相反。
参数说明
区域识别参数:
- MaxOB_Display(默认2):最多显示几个看涨订单块,建议1-3个,只保留最关键区域
- MaxOB_DisplayBear(默认2):最多显示几个看跌订单块
- MaxFVG_Display(默认2):最多显示几个看涨FVG
- MaxFVG_DisplayBear(默认2):最多显示几个看跌FVG
- TrendStrength(默认3):趋势确认需要的连续同向K线数,数值越大识别越严格但信号越少
- ZoneOverlapPct(默认50.0):区域重叠判定百分比,两个区域重叠超过此值会合并
显示设置:
- BullOB_Color(默认蓝色):看涨订单块矩形颜色
- BearOB_Color(默认深红):看跌订单块矩形颜色
- BullFVG_Color(默认海绿):看涨FVG矩形颜色
- BearFVG_Color(默认橙红):看跌FVG矩形颜色
- RectTransparency(默认70):矩形透明度0-100,越大越透明
- EnableAlert(默认true):价格触及区域时是否弹窗提醒
结构反转确认:
- SwingBars(默认5):判断结构突破的回溯周期,小周期可用3-5,大周期可用5-10
- RequireClose(默认true):是否要求收盘价确认突破,开启更保守但信号更可靠
交易参数:
- LotSize(默认0.1):每单固定手数
- StopLossPips(默认200):止损点数,黄金建议200-300点
- TakeProfitPips(默认400):止盈点数,黄金建议400-600点
- MagicNumber(默认202501):EA魔术号,用于识别自己的订单
- TradeComment(默认"OB_FVG_Strategy"):订单备注
风控参数:
- MaxOpenOrders(默认2):同时最多持仓订单数
- OnlyOneTrade(默认false):每个区域只交易一次,开启后区域成交一次即失效
- MinBarsSinceEntry(默认3):两次开仓之间最少间隔K线数,避免频繁交易
怎么用
- 将代码保存为.mq5文件,放到MT5数据文件夹的MQL5\Experts\目录
- 在MetaEditor中编译,或直接拖到图表让MT5自动编译
- 在图表上启用自动交易(工具栏上的"自动交易"按钮)
- EA会自动扫描历史K线识别OB和FVG,并在图表上画出半透明矩形
- 蓝色/绿色矩形是看涨区域,红色/橙色是看跌区域
- 价格触及区域且出现结构突破时EA自动开仓,无需手动操作
- 可在EA属性-输入中调整参数,修改后需重新加载EA
适用场景
适合:
- M15及以上周期,H1和H4效果较好
- 趋势明确的品种:黄金XAUUSD、主要外汇对如EURUSD、GBPUSD
- 波动较大的时段:欧美盘重叠时段
- 单边趋势行情:突破后的延续阶段,区域回踩成功率高
不适合:
- M1/M5小周期:噪音大,假突破多
- 震荡盘整行情:价格在区域内反复穿梭,信号质量差
- 重大新闻发布时段:价格跳空或剧烈波动可能导致滑点过大
- 流动性差的品种或时段:点差宽、滑点大影响实际收益
风险提示
信号滞后:结构反转需要突破近期高低点才确认,入场时价格可能已经走出一段,回撤风险较大。可通过减小SwingBars参数提前入场,但会增加假信号。
假突破:价格突破后回抽未能延续是常见情况,尤其在震荡行情中。建议配合大周期趋势判断,只在趋势方向的区域交易。
止损固定:代码使用固定点数止损,不会根据行情波动率调整。黄金波动大时200点可能不够,小周期或波动小的品种可能过大。需根据品种和周期实测调整。
区域失效:价格完全穿透区域50点后区域自动删除,但穿透49点时区域仍有效。这个阈值是硬编码的,无法通过参数调整。
滑点影响:新闻时段或流动性差时,实际成交价可能偏离预期,影响止损止盈比例。建议重大新闻前手动关闭EA。
无移动止损:代码不包含移动止损或盈亏平衡逻辑,持仓后止损止盈位置固定不变。盈利单可能因回调触及止损而减少收益。
参数敏感:TrendStrength、SwingBars等参数对信号数量和质量影响大,默认值未必适合所有品种和周期,需在模拟账户充分测试后再实盘。
常见问题
- 这个EA会重绘吗
- 不会。代码识别的OB和FVG都是基于已完成的历史K线,一旦标记就固定在图表上不会改变位置。区域失效时会删除矩形,但不会修改已标记区域的价格范围。
- 可以用在黄金以外的品种吗
- 可以。代码没有限制品种,但StopLossPips和TakeProfitPips默认值(200/400点)是针对黄金设计的。外汇对波动小,建议改成50/100点;原油、指数等需根据实际点值调整。
- MT4能用吗
- 不能。这是MQL5代码,只能在MetaTrader 5上运行。MT4使用MQL4语言,语法和交易函数不兼容,需要重写才能移植。
- 参数默认值适合什么周期
- 默认参数(TrendStrength=3,SwingBars=5)适合H1和H4周期。M15建议TrendStrength改2、SwingBars改3以增加信号;D1建议TrendStrength改5、SwingBars改10以提高可靠性。需结合品种波动率测试。
- 为什么图表上只显示2个区域
- 代码默认每类区域最多显示2个(MaxOB_Display等参数),只保留最关键的。可以改成3-6个,但区域过多会导致信号混乱,建议保持1-3个专注于高质量机会。
OB/FVG Trend Start Reversal EA for MT5
An MT5 Expert Advisor that identifies Order Blocks and Fair Value Gaps formed before trend reversals. It enters trades automatically when price retests these zones and confirms structural breakout, with built-in stop-loss and take-profit management.
Source code is shown above. Compiled EX5 files are available to VIP members.
评论
还没有人评论,来抢个沙发。