欢迎访问

# ICT 2022 Mentorship 交易模型开发文档-2-gpt

author emer | 61 人阅读 | 0 人评论 |

//+------------------------------------------------------------------+
//| ICT_XAUUSD_Pro |
//| Full ICT FVG/Displacement Strategy |
//| with CSV Training Data for ML |
//+------------------------------------------------------------------+

property strict

property description "ICT Fair Value Gap + Displacement (XAUUSD) | MT4"

property description "Includes: Risk-based sizing, ATR SL, R-multiple TP,"

property description "H1 SMA trend filter, session guard, spread guard,"

property description "CSV training log (signal/entry/close), passive SL/TP history scan,"

property description "optional ML WebRequest filter."

property version "001.001"

property copyright "free for study"

//========================= 用户参数 ================================
// 交易控制
input bool EnableTrading = true; // 启动交易
input int MaxOpenTrades = 1; // 同品种最多持仓
input int MagicNumber = 20251025; // 魔术号
input double RiskPercent = 1.0; // 风险百分比/单笔(%)
input double R_Multiple_TP = 2.0; // 止盈R倍数(=TP/SL)
input double ATR_SL_Multiplier = 0.0; // 额外ATR倍数加入SL(0=仅用FVG边界)
input int ATR_Period = 14; // ATR周期(交易周期)
input double MinSLPoints = 300; // 最小止损距离(Points)
input double FVG_MinPoints = 50; // FVG最小宽度(Points)
input double DisplacementMinBody = 0.6; // 位移烛实体比例阈值(0~1)

// 过滤
input int MaxSpreadPoints = 300; // 最大点差(Points)
input bool UseSessions = true; // 是否限制交易时段(服务器时区)
input int SessionStartHour = 7; // 允许交易起始小时(伦敦前后可调)
input int SessionEndHour = 20; // 允许交易结束小时(纽约收盘前)
input bool UseTrendFilter = true; // H1趋势过滤(SMA50/200)
input int BiasFastMA = 50; // 快均线(H1)
input int BiasSlowMA = 200; // 慢均线(H1)

// 信号周期(EA 所挂图表周期=交易周期)
input ENUM_TIMEFRAMES TradeTF = PERIOD_M5; // 交易周期(信号、ATR、FVG)
input bool AllowLong = true; // 允许做多
input bool AllowShort = true; // 允许做空

// 模型过滤(可选)
input bool UseModelFilter = false; // 启用模型过滤信号
input string ModelURL = "http://127.0.0.1:5000/predict"; // Flask 接口
input double ApproveThreshold = 0.60; // 执行阈值

// 日志输出
input string CsvFileName = "training_data.csv"; // 输出CSV(位于 MQL4/Files/)
input int HistoryScanMax = 200; // 每tick扫描历史订单上限

// 安全/技术
input int Slippage = 10; // 下单滑点
input bool OnlyOneOrderPerBar = true; // 每根K线最多一次下单

//========================= 全局变量/状态 ===========================
datetime g_lastBarTime = 0;
bool g_inited = false;
int g_csv = -1;
datetime g_lastSignalBarTime = 0;

// 单票元数据(用于close日志)
struct TradeMeta {
int ticket;
int dir; // 1=buy, -1=sell
double fvgWidth;
double disp;
double atr;
string htfBias; // "up"/"down"/"flat"
int smt; // 预留
int news; // 预留
double slPoints;
double tpPoints;
string comment;
datetime entryTime;
double entryPrice;
double slPrice;
double tpPrice;
bool loggedClose;
};
TradeMeta g_metas[64];

//========================= 工具函数 ================================
string TFToStr(int p){
switch(p){
case PERIOD_M1: return "M1";
case PERIOD_M5: return "M5";
case PERIOD_M15: return "M15";
case PERIOD_M30: return "M30";
case PERIOD_H1: return "H1";
case PERIOD_H4: return "H4";
case PERIOD_D1: return "D1";
case PERIOD_W1: return "W1";
case PERIOD_MN1: return "MN1";
}
return IntegerToString(p);
}
string NowStr(){ return TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS); }
string TStr(datetime t){ return TimeToString(t, TIME_DATE|TIME_SECONDS); }
double ToDigits(double v){ return NormalizeDouble(v, (int)MarketInfo(Symbol(), MODE_DIGITS)); }

bool IsNewBar(){
datetime t = iTime(Symbol(), TradeTF, 0);
if(t!=g_lastBarTime){
g_lastBarTime = t;
return true;
}
return false;
}

bool InSession(){
if(!UseSessions) return true;
int h = TimeHour(TimeCurrent());
if(SessionStartHour<=SessionEndHour){
return (h>=SessionStartHour && h<SessionEndHour);
}else{
// 跨日
return (h>=SessionStartHour || h<SessionEndHour);
}
}

bool SpreadOk(){
double sp = MarketInfo(Symbol(), MODE_SPREAD);
return (sp <= MaxSpreadPoints);
}

int CountOpenByMagic(){
int cnt=0;
for(int i=0;i<OrdersTotal();i++){
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol()!=Symbol()) continue;
if(OrderMagicNumber()!=MagicNumber) continue;
cnt++;
}
return cnt;
}

double GetValuePerPoint(){
double ticksize = MarketInfo(Symbol(), MODE_TICKSIZE);
double tickvalue = MarketInfo(Symbol(), MODE_TICKVALUE);
double point = MarketInfo(Symbol(), MODE_POINT);
if(ticksize<=0) ticksize = point;
return tickvalue * (point/ticksize); // 每"Point"价值/每手
}

double GetATR(int period){
return iATR(Symbol(), TradeTF, period, 0);
}

// H1趋势过滤:SMA50> SMA200 => up;反之 down;接近则 flat
string HTFBias(){
int tf = PERIOD_H1;
double f0 = iMA(Symbol(), tf, BiasFastMA, 0, MODE_SMA, PRICE_CLOSE, 0);
double s0 = iMA(Symbol(), tf, BiasSlowMA, 0, MODE_SMA, PRICE_CLOSE, 0);
if(f0>s01.0005) return "up";
if(f0<s0
0.9995) return "down";
return "flat";
}

//========================= CSV 日志模块 =============================
bool CsvInit(){
bool existed = FileIsExist(CsvFileName);
g_csv = FileOpen(CsvFileName, FILE_CSV|FILE_READ|FILE_WRITE|FILE_SHARE_READ|FILE_SHARE_WRITE, ',');
if(g_csv<0){
Print("CsvInit: FileOpen failed. err=", GetLastError());
return false;
}
FileSeek(g_csv, 0, SEEK_END);
if(!existed || FileTell(g_csv)==0){
FileWrite(g_csv,
"type","timestamp","symbol","timeframe","direction",
"fvg_width","disp_strength","atr","htf_bias","smt_div","news_near",
"spread","sl_points","tp_points","session","price",
"comment","ticket","magic",
"result_points","result_profit","rr","hold_minutes",
"entry_time","exit_time","entry_price","exit_price","sl_price","tp_price"
);
FileFlush(g_csv);
}
return true;
}
void CsvDeinit(){ if(g_csv>=0){ FileClose(g_csv); g_csv=-1; } }
void CsvFlush(){ if(g_csv>=0) FileFlush(g_csv); }

string SessionTagByHour(int h){
if(h>=0 && h<7) return "Asia";
if(h>=7 && h<13) return "London";
if(h>=13 && h<20) return "NY";
return "After";
}
void LogSignal(int dir,double fvg,double disp,double atr,string htf,int smt,int news,double slPts,double tpPts,string cmt,double price=0.0){
if(g_csv<0) return;
double spread = MarketInfo(Symbol(), MODE_SPREAD)Point;
if(price<=0) price = (Bid+Ask)
0.5;
FileWrite(g_csv,
"signal", NowStr(), Symbol(), TFToStr(TradeTF), dir,
fvg, disp, atr, htf, smt, news,
spread, slPts, tpPts, SessionTagByHour(TimeHour(TimeCurrent())),
ToDigits(price), cmt, 0, MagicNumber,
"", "", "", "", "", "", "", "", "", ""
);
FileFlush(g_csv);
}
void LogEntry(int ticket,int dir,double fvg,double disp,double atr,string htf,int smt,int news,double slPts,double tpPts,string cmt,double price){
if(g_csv<0) return;
double spread = MarketInfo(Symbol(), MODE_SPREAD)Point;
FileWrite(g_csv,
"entry", NowStr(), Symbol(), TFToStr(TradeTF), dir,
fvg, disp, atr, htf, smt, news,
spread, slPts, tpPts, SessionTagByHour(TimeHour(TimeCurrent())),
ToDigits(price), cmt, ticket, MagicNumber,
"", "", "", "", "", "", "", "", "", ""
);
FileFlush(g_csv);
}
void LogCloseFull(const TradeMeta &m, datetime exit_time, double exit_price, double result_points, double profit){
if(g_csv<0) return;
double rr=0; if(m.slPoints>0) rr = MathAbs(result_points)/m.slPoints;
double hold_min=0; if(exit_time>m.entryTime) hold_min = (exit_time - m.entryTime)/60.0;
FileWrite(g_csv,
"close", TStr(exit_time), Symbol(), TFToStr(TradeTF), m.dir,
m.fvgWidth, m.disp, m.atr, m.htfBias, m.smt, m.news,
MarketInfo(Symbol(), MODE_SPREAD)
Point, m.slPoints, m.tpPoints,
SessionTagByHour(TimeHour(exit_time)),
ToDigits(exit_price), m.comment, m.ticket, MagicNumber,
result_points, profit, rr, hold_min,
TStr(m.entryTime), TStr(exit_time), ToDigits(m.entryPrice), ToDigits(exit_price), ToDigits(m.slPrice), ToDigits(m.tpPrice)
);
FileFlush(g_csv);
}

//========================= FVG 检测 ================================
// FVG 结构
struct FVG {
bool valid;
int dir; // 1=看多FVG(回补做多), -1=看空FVG(回补做空)
double lo; // 区间下边界
double hi; // 区间上边界
int shift; // 形成于哪根(基于TradeTF)的已完成K线 shift
double widthPts; // 宽度(Points)
double disp; // 位移强度(中间烛实体/全长)
datetime time;
};

double BodyRatio(int shift){
double o = iOpen(Symbol(), TradeTF, shift);
double c = iClose(Symbol(), TradeTF, shift);
double h = iHigh(Symbol(), TradeTF, shift);
double l = iLow(Symbol(), TradeTF, shift);
double rng = MathMax(h-l, Point*0.1);
return MathAbs(c-o)/rng;
}

FVG DetectFVG(int maxLookback=20){
// 使用三根: [shift+2] (第一)、shift+1shift
// 仅在最新已收K线(shift=1)附近找最近FVG,向后最多maxLookback
FVG res; res.valid=false;

for(int s=1; s<=maxLookback; s++){
double h1=iHigh(Symbol(), TradeTF, s+2);
double l1=iLow(Symbol(), TradeTF, s+2);
double h3=iHigh(Symbol(), TradeTF, s);
double l3=iLow(Symbol(), TradeTF, s);

  // 位移强度看中间烛(shift+1)
  double disp = BodyRatio(s+1);

  // Bullish FVG: Candle1.high < Candle3.low => 区间[ Candle1.high , Candle3.low ]
  if(l3>h1){
     double lo = h1;
     double hi = l3;
     double widthPts = (hi-lo)/Point;
     if(widthPts>=FVG_MinPoints && disp>=DisplacementMinBody){
        res.valid=true; res.dir=+1; res.lo=lo; res.hi=hi; res.shift=s; res.widthPts=widthPts; res.disp=disp; res.time=iTime(Symbol(), TradeTF, s);
        return res;
     }
  }
  // Bearish FVG: Candle1.low > Candle3.high => 区间[ Candle3.high , Candle1.low ]
  if(l1>h3){
     double lo = h3;
     double hi = l1;
     double widthPts = (hi-lo)/Point;
     if(widthPts>=FVG_MinPoints && disp>=DisplacementMinBody){
        res.valid=true; res.dir=-1; res.lo=lo; res.hi=hi; res.shift=s; res.widthPts=widthPts; res.disp=disp; res.time=iTime(Symbol(), TradeTF, s);
        return res;
     }
  }

}
return res;
}

//========================= 模型过滤(可选) ===========================
bool ModelApprove(int dir, double fvgWidth, double disp, double atr, string htf, int smt, int news, double &confidence){
confidence = 1.0;
if(!UseModelFilter) return true;

// 组织JSON(简化版)
string json = StringFormat("{\"direction\":%d,\"fvg_width\":%.2f,\"displacement_strength\":%.3f,\"atr\":%.2f,\"htf_bias\":\"%s\",\"smt_div\":%d,\"news_near\":%d}",
dir, fvgWidth, disp, atr, htf, smt, news);

char post[], result[];
StringToCharArray(json, post);
string headers;
ResetLastError();
// 使用 MT4 的第二个重载:method, url, cookie, timeout, data[], result[], headers
int res = WebRequest("POST", ModelURL, "", 10000, post, result, headers);
if(res!=-1){
string text = CharArrayToString(result);
int aIdx = StringFind(text, "\"approve\":");
int cIdx = StringFind(text, "\"confidence\":");
int approve = 0;
double conf = 0.0;
if(aIdx>=0){
string sub = StringSubstr(text, aIdx+10, 2);
sub = StringSubstr(sub, 0, 1);
approve = StrToInteger(sub);
}
if(cIdx>=0){
string sub2 = StringSubstr(text, cIdx+13, 10);
conf = StrToDouble(sub2);
}
confidence = conf;
return (approve==1 && conf>=ApproveThreshold);
}else{
Print("WebRequest failed. err=",GetLastError()," url=",ModelURL);
}
return false;
}

//========================= 下单/风控 ===============================
int MetaFindSlotByTicket(int ticket){
for(int i=0;i<ArraySize(g_metas);i++) if(g_metas[i].ticket==ticket) return i;
return -1;
}
int MetaAllocSlot(){
for(int i=0;i<ArraySize(g_metas);i++) if(g_metas[i].ticket<=0 && !g_metas[i].loggedClose) return i;
return 0;
}
void MetaClearSlot(int idx){
if(idx<0) return;
g_metas[idx].ticket=0;
g_metas[idx].loggedClose=false;
}

bool PlaceOrder(int dir, double entry, double sl, double tp, double fvgWidthPts, double disp, double atr, string htfBias, int smt, int news, string cmt, int &outTicket){
double stopPoints = MathMax(MathAbs(entry-sl)/Point, MinSLPoints);
double riskMoney = AccountBalance() (RiskPercent/100.0);
double valPerPt = GetValuePerPoint();
if(valPerPt<=0){ Print("valPerPt<=0"); return false; }
double lotsRaw = riskMoney / (stopPoints
valPerPt);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double lots = MathMax(minLot, MathMin(maxLot, MathFloor(lotsRaw/lotStep)lotStep));
if(lots<minLot
0.999){ Print("lots too small: ", lotsRaw); return false; }

int type = (dir>0?OP_BUY:OP_SELL);
double price = (dir>0?Ask:Bid);

int ticket = OrderSend(Symbol(), type, lots, price, Slippage, sl, tp, cmt, MagicNumber, 0, (dir>0?clrGreen:clrTomato));
if(ticket>0){
outTicket = ticket;
LogEntry(ticket, dir, fvgWidthPts, disp, atr, htfBias, smt, news, stopPoints, MathAbs(tp-entry)/Point, cmt, price);

  int idx=MetaAllocSlot();
  g_metas[idx].ticket = ticket;
  g_metas[idx].dir = dir;
  g_metas[idx].fvgWidth = fvgWidthPts;
  g_metas[idx].disp = disp;
  g_metas[idx].atr = atr;
  g_metas[idx].htfBias = htfBias;
  g_metas[idx].smt = smt; g_metas[idx].news = news;
  g_metas[idx].slPoints = stopPoints;
  g_metas[idx].tpPoints = MathAbs(tp-entry)/Point;
  g_metas[idx].comment  = cmt;
  g_metas[idx].entryTime= TimeCurrent();
  g_metas[idx].entryPrice= price;
  g_metas[idx].slPrice  = sl;
  g_metas[idx].tpPrice  = tp;
  g_metas[idx].loggedClose=false;
  return true;

}else{
Print("OrderSend failed. err=",GetLastError());
}
return false;
}

//========================= 交易逻辑 =================================
bool CanTradeNow(){
if(!EnableTrading) return false;
if(!InSession()) return false;
if(!SpreadOk()) return false;
if(CountOpenByMagic()>=MaxOpenTrades) return false;
return true;
}
bool TrendAllows(int dir){
if(!UseTrendFilter) return true;
string b = HTFBias();
if(dir>0 && b=="up") return true;
if(dir<0 && b=="down") return true;
return false;
}

void TryTradeOnFVG(){
if(OnlyOneOrderPerBar && !IsNewBar()) return;

FVG f = DetectFVG(20);
if(!f.valid) return;

if(f.dir>0 && !AllowLong) return;
if(f.dir<0 && !AllowShort) return;
if(!TrendAllows(f.dir)) return;

double atr = GetATR(ATR_Period);
string htf = HTFBias();

if(g_lastSignalBarTime != f.time){
LogSignal(f.dir, f.widthPts, f.disp, atr, htf, 0, 0, 0, 0, "FVG_new", (Bid+Ask)*0.5);
g_lastSignalBarTime = f.time;
}

if(!CanTradeNow()) return;

double entry=0, sl=0, tp=0;

double extraSL = 0;
if(ATR_SL_Multiplier>0) extraSL = ATR_SL_Multiplier*atr;

if(f.dir>0){
if(Bid<=f.hi && Bid>=f.lo){
entry = Ask;
sl = MathMin(f.lo, Low[1]) - extraSL;
if((entry - sl) < MinSLPointsPoint) sl = entry - MinSLPointsPoint;
double dist = entry - sl;
tp = entry + R_Multiple_TPdist;
}else return;
}else{
if(Ask>=f.lo && Ask<=f.hi){
entry = Bid;
sl = MathMax(f.hi, High[1]) + extraSL;
if((sl - entry) < MinSLPoints
Point) sl = entry + MinSLPointsPoint;
double dist = sl - entry;
tp = entry - R_Multiple_TP
dist;
}else return;
}

double conf=1.0;
if(!ModelApprove(f.dir, f.widthPts, f.disp, atr, htf, 0, 0, conf)){
LogSignal(f.dir, f.widthPts, f.disp, atr, htf, 0, 0, (MathAbs(entry-sl)/Point), (MathAbs(tp-entry)/Point),
StringFormat("BlockedByModel(%.2f)", conf), (Bid+Ask)*0.5);
return;
}

int ticket=-1;
string cmt = StringFormat("ICT_FVG dir=%d w=%.0f disp=%.2f ATR=%.2f", f.dir, f.widthPts, f.disp, atr);
PlaceOrder(f.dir, entry, sl, tp, f.widthPts, f.disp, atr, htf, 0, 0, cmt, ticket);
}

//========================= 被动SL/TP历史补录 =========================
void ScanHistoryAndLogClose(){
int total = OrdersHistoryTotal();
int count = MathMin(total, HistoryScanMax);

for(int i=total-1; i>=0 && count>0; --i, --count){
if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) continue;
if(OrderSymbol()!=Symbol()) continue;
if(OrderMagicNumber()!=MagicNumber) continue;
if(OrderCloseTime()<=0) continue;

  int tkt = OrderTicket();
  int idx = MetaFindSlotByTicket(tkt);
  bool already = false;
  string key = "logged_"+IntegerToString(tkt);
  if(GlobalVariableCheck(key)) already = true;

  if(already) continue;

  TradeMeta m;
  bool haveMeta = false;
  if(idx>=0){
     m = g_metas[idx];
     haveMeta = true;
  }else{
     m.ticket = tkt;
     m.dir = (OrderType()==OP_BUY?1:-1);
     m.fvgWidth=0; m.disp=0; m.atr=0; m.htfBias="unknown"; m.smt=0; m.news=0;
     m.slPoints=0; m.tpPoints=0; m.comment=OrderComment();
     m.entryTime=OrderOpenTime(); m.entryPrice=OrderOpenPrice();
     m.slPrice=OrderStopLoss(); m.tpPrice=OrderTakeProfit();
  }

  double exitp = OrderClosePrice();
  double result_pts = (exitp - m.entryPrice)/Point * (m.dir>0?1:-1);
  double profit = OrderProfit()+OrderSwap()+OrderCommission();

  LogCloseFull(m, OrderCloseTime(), exitp, result_pts, profit);

  GlobalVariableSet(key, 1.0);
  if(idx>=0){ g_metas[idx].loggedClose=true; MetaClearSlot(idx); }

}
}

//========================= 事件函数 =================================
int OnInit(){
g_inited = CsvInit();
if(!g_inited) return(INIT_FAILED);
Print("ICT_XAUUSD_Pro initialized. TF=",TFToStr(TradeTF));
return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason){
CsvDeinit();
Print("ICT_XAUUSD_Pro deinit. reason=",reason);
}

void OnTick(){
if(!g_inited) return;
ScanHistoryAndLogClose();
TryTradeOnFVG();
}

还没有人打赏,支持一下