我是灵宝。自研的第三套EA今晚在大非农前后没有预期触发,让我很受打击。经过调试查找总算解决了触发问题,一是自己粗心设错了年份,二是用错了函数它不符合经纪商的默认模式。也许是自己曾亲眼见识过一位高手用这种交易法做的非常好,所以尝试借鉴其主观手法和量价行为开发出EA来,目前也算是有点眉目了,尽管还很粗糙。按照承诺我把MQL5源码放这里了,供大家把玩下。纯属个人兴趣,请勿轻易实盘去赌行情,模拟盘玩玩还行。如果说它真的有价值的话,也必须经过N多次实盘验证后才会有结论。初来宝地,请各路高人多多批评指正!
//+------------------------------------------------------------------+
//| PriceAction_EA.mq5 |
//| 非农CPI事件驱动锁仓交易法 |
//| 纯价格行为模式 - 全自动 |
//| 开发者:ChinaLingbao@outlook.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026"
#property version "1.00"
#property strict
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
input string EventTimeStr = "2026.06.05 15:30"; // 测试时间(服务器) - 改成当前时间+3分钟
input double FixedLotSize = 0.01; // 固定手数
input double TakeProfitRatio = 3.0; // 止盈盈亏比
input int PreEventMinutes = 5; // 提前开仓分钟数
input int PriceActionWaitSeconds = 120; // 价格行为等待秒数(2分钟)
input int PriceActionMinPoints = 500; // 最小波动点数(5美元)
input double VolumeSurgeRatio = 1.0; // 成交量激增倍数
input int RiskPoints = 1000; // 风险点数(1点=0.01美元, 1000点=10美元)
input int Slippage = 100; // 允许滑点
input int MagicNumber = 20260605; // EA魔术号
//+------------------------------------------------------------------+
//| 全局变量 |
//+------------------------------------------------------------------+
datetime eventTime;
datetime preTradeTime;
datetime priceActionDeadline;
datetime decisionTimeOut;
bool tradesOpened = false;
bool decisionMade = false;
int keptDirection = 0;
ulong buyTicket = 0;
ulong sellTicket = 0;
//+------------------------------------------------------------------+
//| 初始化 |
//+------------------------------------------------------------------+
int OnInit()
{
if(!ParseTime(EventTimeStr, eventTime))
{
Print("ERROR: Time format error. Use YYYY.MM.DD HH:MM");
return INIT_FAILED;
}
preTradeTime = eventTime - PreEventMinutes * 60;
priceActionDeadline = eventTime + PriceActionWaitSeconds;
decisionTimeOut = eventTime + 600;
Print("============================================");
Print("PriceAction_EA Loaded");
Print("Event Time: ", TimeToString(eventTime));
Print("Open Time: ", TimeToString(preTradeTime));
Print("Decision Deadline: ", TimeToString(priceActionDeadline));
Print("============================================");
Print("Risk Points: ", RiskPoints, " (", RiskPoints/100.0, " USD)");
Print("Take Profit: ", RiskPoints * TakeProfitRatio, " (", RiskPoints * TakeProfitRatio/100.0, " USD)");
Print("Price Threshold: ", PriceActionMinPoints, " (", PriceActionMinPoints/100.0, " USD)");
Print("Volume Ratio: ", VolumeSurgeRatio);
Print("============================================");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| 反初始化 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("EA Unloaded");
}
//+------------------------------------------------------------------+
//| 主函数 |
//+------------------------------------------------------------------+
void OnTick()
{
datetime now = TimeCurrent();
// 阶段1: 开锁仓
if(!tradesOpened && now >= preTradeTime && now < eventTime)
{
OpenLockPositions();
tradesOpened = true;
Print("[OPEN] Lock positions completed");
}
// 阶段2: 决策
if(tradesOpened && !decisionMade && now >= eventTime)
{
if(now > decisionTimeOut)
{
CloseAllPositions();
decisionMade = true;
Print("[TIMEOUT] Decision timeout, all closed");
return;
}
if(now >= priceActionDeadline)
{
int decision = 0;
string reason = "";
decision = GetPriceActionDecision(reason);
ExecuteDecision(decision, reason);
decisionMade = true;
}
}
// 阶段3: 持仓管理
if(decisionMade && keptDirection != 0)
{
CheckTakeProfit();
ManageTrailingStop();
}
}
//+------------------------------------------------------------------+
//| 开锁仓订单 |
//+------------------------------------------------------------------+
void OpenLockPositions()
{
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick))
{
Print("[ERROR] Failed to get tick");
return;
}
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(point == 0) point = 0.01;
double riskPoints = RiskPoints;
double tpPoints = riskPoints * TakeProfitRatio;
double tpBuy = tick.ask + tpPoints * point;
double tpSell = tick.bid - tpPoints * point;
buyTicket = OpenOrder(ORDER_TYPE_BUY, FixedLotSize, tick.ask, 0, tpBuy, "Lock Buy");
sellTicket = OpenOrder(ORDER_TYPE_SELL, FixedLotSize, tick.bid, 0, tpSell, "Lock Sell");
if(buyTicket > 0)
Print("[ORDER] Buy: ", buyTicket, " @ ", tick.ask);
if(sellTicket > 0)
Print("[ORDER] Sell: ", sellTicket, " @ ", tick.bid);
}
//+------------------------------------------------------------------+
//| 价格行为决策 |
//+------------------------------------------------------------------+
int GetPriceActionDecision(string &reason)
{
MqlRates rates[];
if(CopyRates(_Symbol, PERIOD_M1, 0, 5, rates) < 4)
{
reason = "Failed to get rate data";
return 0;
}
double eventOpen = rates[0].open;
double currentClose = rates[0].close;
long eventVolume = rates[0].tick_volume;
long avgVolume = 0;
for(int i = 1; i < 4; i++)
avgVolume += rates.tick_volume;
avgVolume /= 3;
if(avgVolume == 0)
{
reason = "Volume data error";
return 0;
}
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(point == 0) point = 0.01;
double priceChange = (currentClose - eventOpen) / point;
double volumeRatio = (double)eventVolume / avgVolume;
Print("[PA] Price Change: ", DoubleToString(priceChange, 0), " points");
Print("[PA] Volume Ratio: ", DoubleToString(volumeRatio, 1));
if(MathAbs(priceChange) >= PriceActionMinPoints && volumeRatio >= VolumeSurgeRatio)
{
if(priceChange > 0)
{
reason = "Price UP + Volume SURGE -> BULLISH";
return 1;
}
else
{
reason = "Price DOWN + Volume SURGE -> BEARISH";
return -1;
}
}
reason = "Conditions not met, skip trading";
return 0;
}
//+------------------------------------------------------------------+
//| 执行决策 |
//+------------------------------------------------------------------+
void ExecuteDecision(int decision, string reason)
{
Print("[DECISION] ", reason);
if(decision == 1)
{
CloseOrder(sellTicket);
keptDirection = 1;
Print("[EXECUTE] Keep LONG, closed SHORT");
}
else if(decision == -1)
{
CloseOrder(buyTicket);
keptDirection = -1;
Print("[EXECUTE] Keep SHORT, closed LONG");
}
else
{
CloseOrder(buyTicket);
CloseOrder(sellTicket);
keptDirection = 0;
Print("[EXECUTE] No clear signal, all closed");
}
}
//+------------------------------------------------------------------+
//| 检查止盈 |
//+------------------------------------------------------------------+
void CheckTakeProfit()
{
if(keptDirection == 0) return;
ulong ticket = (keptDirection == 1) ? buyTicket : sellTicket;
if(!PositionSelectByTicket(ticket)) return;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentPrice = (keptDirection == 1) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(point == 0) point = 0.01;
double profitPoints = (keptDirection == 1) ?
(currentPrice - openPrice) / point :
(openPrice - currentPrice) / point;
double targetPoints = RiskPoints * TakeProfitRatio;
if(profitPoints >= targetPoints)
{
CloseOrder(ticket);
Print("[TAKE PROFIT] ", profitPoints, " points");
keptDirection = 0;
}
}
//+------------------------------------------------------------------+
//| 移动止损 - 已添加 IOC 填充模式 |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
if(keptDirection == 0) return;
ulong ticket = (keptDirection == 1) ? buyTicket : sellTicket;
if(!PositionSelectByTicket(ticket)) return;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentPrice = (keptDirection == 1) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(point == 0) point = 0.01;
double profitPoints = (keptDirection == 1) ?
(currentPrice - openPrice) / point :
(openPrice - currentPrice) / point;
int trailStart = 300;
int trailStep = 50;
if(profitPoints >= trailStart)
{
double newSL;
if(keptDirection == 1)
newSL = currentPrice - trailStep * point;
else
newSL = currentPrice + trailStep * point;
if(newSL > currentSL)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_SLTP;
req.position = ticket;
req.sl = newSL;
req.tp = PositionGetDouble(POSITION_TP);
req.symbol = _Symbol;
req.magic = MagicNumber;
req.type_filling = ORDER_FILLING_IOC; // ✅ 已添加填充模式
if(OrderSend(req, res))
{
if(res.retcode == TRADE_RETCODE_DONE)
Print("[TRAIL STOP] Updated to: ", DoubleToString(newSL, _Digits));
else
Print("[TRAIL STOP] Failed, retcode: ", res.retcode);
}
else
{
Print("[TRAIL STOP] OrderSend error: ", GetLastError());
}
}
}
}
//+------------------------------------------------------------------+
//| 全部平仓 |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
CloseOrder(buyTicket);
CloseOrder(sellTicket);
keptDirection = 0;
}
//+------------------------------------------------------------------+
//| 开单函数 - 使用 IOC 填充模式 |
//+------------------------------------------------------------------+
ulong OpenOrder(ENUM_ORDER_TYPE type, double volume, double price, double sl, double tp, string comment)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = volume;
req.type = type;
req.price = price;
req.sl = sl;
req.tp = tp;
req.deviation = Slippage;
req.magic = MagicNumber;
req.comment = comment;
req.type_filling = ORDER_FILLING_IOC; // ✅ Immediate or Cancel - TMGM 支持
if(OrderSend(req, res))
{
if(res.retcode == TRADE_RETCODE_DONE)
return res.order;
else
Print("Open order failed, retcode: ", res.retcode);
}
else
{
Print("Open order failed, error: ", GetLastError());
}
return 0;
}
//+------------------------------------------------------------------+
//| 平仓函数 - 添加 IOC 填充模式 |
//+------------------------------------------------------------------+
void CloseOrder(ulong ticket)
{
if(ticket == 0) return;
if(!PositionSelectByTicket(ticket)) return;
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = PositionGetString(POSITION_SYMBOL);
req.volume = PositionGetDouble(POSITION_VOLUME);
req.deviation = Slippage;
req.position = ticket;
req.type_filling = ORDER_FILLING_IOC; // ✅ 已添加填充模式
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(posType == POSITION_TYPE_BUY)
{
req.type = ORDER_TYPE_SELL;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
}
else
{
req.type = ORDER_TYPE_BUY;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
}
if(OrderSend(req, res))
{
if(res.retcode == TRADE_RETCODE_DONE)
Print("[CLOSE] Order ", ticket, " closed");
else
Print("[CLOSE] Failed, retcode: ", res.retcode);
}
else
{
Print("[CLOSE] OrderSend error: ", GetLastError());
}
}
//+------------------------------------------------------------------+
//| 解析时间 |
//+------------------------------------------------------------------+
bool ParseTime(string str, datetime &out)
{
if(StringLen(str) < 16) return false;
int year = (int)StringToInteger(StringSubstr(str, 0, 4));
int mon = (int)StringToInteger(StringSubstr(str, 5, 2));
int day = (int)StringToInteger(StringSubstr(str, 8, 2));
int hour = (int)StringToInteger(StringSubstr(str, 11, 2));
int min = (int)StringToInteger(StringSubstr(str, 14, 2));
if(year < 2000 || mon < 1 || mon > 12 || day < 1 || day > 31) return false;
if(hour < 0 || hour > 23 || min < 0 || min > 59) return false;
MqlDateTime dt = {0};
dt.year = year;
dt.mon = mon;
dt.day = day;
dt.hour = hour;
dt.min = min;
dt.sec = 0;
out = StructToTime(dt);
return true;
}
//+------------------------------------------------------------------+ |