auto_EA/Experts/优化卖出.mq5
thRee03 78f37ded23
2025-10-21 18:08:13 +08:00

578 lines
No EOL
36 KiB
MQL5

//+------------------------------------------------------------------+
//| Trailing Stop SELL EA |
//+------------------------------------------------------------------+
#property copyright "thRee03"
#property link "None"
#property version "8.88"
#property strict
// 输入参数
input bool EnableOpenOrder = true; // 开单总开关,false时不开单但仍可移动止盈
input int Seconds = 180; // 下单最小间隔秒数
input double Lots = 0.01; // 每次下单手数
input int Deviation = 10; // 最大允许滑点(点)
input double TP_Points = 10; // 止盈点数(下单时设置TP)
input bool EnableCloseAllBuyOnSell = true; // 开空单时全平多单,true=全平,false=不平
input int MaxOrders = 10; // 最大持仓单数
input int MagicNumber = 12346; // 魔术编号
input double ProfitTargetUSD = 2.0; // 单仓盈利目标(美元,满足即平仓)
input bool CloseOtherMagics = true; // 盈利平仓时是否包含其他魔术编号持仓
input bool EnableSAR = true; // 是否启用SAR信号过滤(false=不开启任何SAR限制)
input bool iSAR_Reverse = false; // iSAR反向开单:true=只要SAR在K线下方或由上转下就开空,false=只要SAR在K线上方或由下转上就开空
input double SAR_Acceleration = 0.02; // SAR加速度(建议0.01~0.03,趋势信号更灵敏)
input double SAR_Start = 0.0; // SAR初始值(0为默认)
input double SAR_Maximum = 0.2; // SAR最大值(建议0.1~0.2)
input int SAR_SmoothBars = 1; // SAR平滑根数(取近N根均值)
input double TrailingStopUSD = 2.0; // 单仓利润回撤止盈(美元,移动止盈)
input bool EnableDrawdownOpen = true; // 启用当天最低价反弹加仓(true=启用,false=关闭)
input double DrawdownOpenUSD = 10.0; // 距离当天最低价反弹N美元时开空单
input bool EnableAddOrderEveryUSD = true; // 启用每涨N美元加仓(true=启用,false=关闭)
input double AddOrderEveryUSD = 5.0; // 下单后每上涨N美元再加一单(以上次加仓价为基准)
input int DrawdownMaxOrders = 3; // 当天最低价反弹加仓最大单数(独立于MaxOrders)
input bool TrailingStopOnlyThisMagic = true; // 只对本魔术号仓位移动止盈(true=只本EA,false=所有SELL仓位)
input double MinBalance = 1000.0; // 最低账户余额(低于不下单)
// 全局变量
double g_point = 0.0;
int g_digits = 0;
double g_min_lot = 0.0;
double g_lot_step = 0.0;
double g_max_lot = 0.0;
int g_filling_mode = 0;
int orderCount = 0;
datetime lastOrderTime = 0;
double g_lowest_price = 0.0; // 历史最低价
double g_last_add_price = 0.0; // 上次加仓价
// 统计当前EA持仓中注释为DD的单数(反弹加仓单)
int GetDrawdownOrderCount(const string sym, const ulong magic)
{
int count = 0;
int total = (int)PositionsTotal();
for(int i=0; i<total; ++i)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) == sym &&
PositionGetInteger(POSITION_MAGIC) == (long)magic &&
PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
string comment = PositionGetString(POSITION_COMMENT);
if(comment == "DD")
count++;
}
}
return count;
}
// 工具函数
double AlignToStep(double val, double step, bool up=false)
{
if(step <= 0.0) return val;
double k = up ? MathCeil(val/step) : MathFloor(val/step);
double v = k*step;
v = NormalizeDouble(v, 2);
return MathMax(step, v);
}
double AlignToPoint(double price)
{
if(g_point <= 0) return price;
return NormalizeDouble(MathRound(price/g_point)*g_point, g_digits);
}
int GetCurrentPositions(const string sym, const ulong magic)
{
int count = 0;
int total = (int)PositionsTotal();
for(int i=0; i<total; ++i)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) == sym &&
PositionGetInteger(POSITION_MAGIC) == (long)magic &&
PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
count++;
}
}
return count;
}
void CheckAndCloseProfitablePositions()
{
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickValue <= 0) return;
int total = (int)PositionsTotal();
for(int i=total-1; i>=0; --i)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol ||
PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_SELL) continue;
if(!CloseOtherMagics && PositionGetInteger(POSITION_MAGIC) != (long)MagicNumber) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double volume = PositionGetDouble(POSITION_VOLUME);
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) continue;
double currentPrice = tick.ask;
double profit = ((openPrice - currentPrice) / g_point) * volume * tickValue;
if(profit >= ProfitTargetUSD)
{
// 平仓
MqlTradeRequest req; MqlTradeResult res;
ZeroMemory(req); ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.type = ORDER_TYPE_BUY;
req.volume = volume;
req.price = currentPrice;
req.deviation = Deviation;
req.type_filling = GetFillingMode();
req.magic = PositionGetInteger(POSITION_MAGIC); // 使用原魔术编号
req.position = ticket;
if(OrderSend(req, res) && (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED))
{
Print("Closed profitable position. Ticket: ", ticket, " Profit: ", profit, " USD, Magic: ", PositionGetInteger(POSITION_MAGIC));
}
else
{
Print("Failed to close position: ", res.retcode);
}
}
}
}
void CloseAllBuyPositions()
{
int total = (int)PositionsTotal();
for(int i=total-1; i>=0; --i)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol ||
PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY) continue;
double volume = PositionGetDouble(POSITION_VOLUME);
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) continue;
double currentPrice = tick.bid; // 平 BUY 用 bid
// 平仓
MqlTradeRequest req; MqlTradeResult res;
ZeroMemory(req); ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.type = ORDER_TYPE_SELL;
req.volume = volume;
req.price = currentPrice;
req.deviation = Deviation;
req.type_filling = GetFillingMode();
req.magic = PositionGetInteger(POSITION_MAGIC);
req.position = ticket;
if(OrderSend(req, res) && (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED))
{
Print("Closed BUY position. Ticket: ", ticket, " Magic: ", PositionGetInteger(POSITION_MAGIC));
}
else
{
Print("Failed to close BUY position: ", res.retcode);
}
}
}
void TrailingStop()
{
if(TrailingStopUSD <= 0) return;
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickValue <= 0) return;
int total = (int)PositionsTotal();
for(int i=0; i<total; ++i)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol ||
PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_SELL) continue;
if(TrailingStopOnlyThisMagic && PositionGetInteger(POSITION_MAGIC) != (long)MagicNumber) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double volume = PositionGetDouble(POSITION_VOLUME);
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) continue;
double currentPrice = tick.ask;
double profitUSD = ((openPrice - currentPrice) / g_point) * volume * tickValue;
if(profitUSD > TrailingStopUSD)
{
double priceRetracement = TrailingStopUSD / (volume * tickValue) * g_point;
double newSL = currentPrice + priceRetracement;
newSL = AlignToPoint(newSL);
newSL = MathMin(newSL, openPrice); // 确保止损不高于开仓价
if(newSL < currentSL || currentSL == 0)
{
// 修改止损
MqlTradeRequest req; MqlTradeResult res;
ZeroMemory(req); ZeroMemory(res);
req.action = TRADE_ACTION_SLTP;
req.symbol = _Symbol;
req.position = ticket;
req.sl = newSL;
req.magic = PositionGetInteger(POSITION_MAGIC);
if(OrderSend(req, res) && (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED))
{
Print("Trailing stop updated for position ", ticket, " to ", newSL, " (Profit: ", profitUSD, " USD)");
}
else
{
Print("Failed to update trailing stop: ", res.retcode);
}
}
}
}
}
bool LoadSymbolSpec()
{
g_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
g_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
g_min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
g_lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
g_max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
g_filling_mode = (int)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if(g_point<=0 || g_min_lot<=0 || g_lot_step<=0 || g_max_lot<=0)
{
Print("Spec load failed");
return false;
}
return true;
}
ENUM_ORDER_TYPE_FILLING GetFillingMode()
{
return ORDER_FILLING_IOC; // 强制使用 IOC
}
// EA 初始化
int OnInit()
{
if(!LoadSymbolSpec())
{
Print("Failed to load symbol spec");
return INIT_FAILED;
}
orderCount = 0;
lastOrderTime = 0;
Print("EA initialized. Max orders: ", MaxOrders, ", Interval: ", Seconds, " seconds, Magic: ", MagicNumber);
return INIT_SUCCEEDED;
}
// EA 反初始化
void OnDeinit(const int reason)
{
// 无需操作
}
// EA 主循环
void OnTick()
{
TrailingStop(); // 移动止盈
CheckAndCloseProfitablePositions(); // 检查并平仓盈利仓位
// 获取当前Bid价
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick))
{
Print("Failed to get tick");
return;
}
double bid = tick.bid;
// 获取当天最低价
double dayLow = iLow(_Symbol, PERIOD_D1, 0);
// 计算距离当天最低价的反弹
double drawdown = bid - dayLow;
int currentPositions = GetCurrentPositions(_Symbol, MagicNumber);
int remainingOrders = MaxOrders - currentPositions;
if(remainingOrders <= 0) {
Print("不开单:已达最大持仓 currentPositions=", currentPositions, " MaxOrders=", MaxOrders);
return;
}
if(!EnableOpenOrder) {
Print("不开单:开单总开关EnableOpenOrder=false");
return;
}
// 检查余额
double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE);
if(currentBalance < MinBalance)
{
Print("不开单:余额不足 currentBalance=", currentBalance, " MinBalance=", MinBalance);
return;
}
// 历史低点反弹加仓(不受时间间隔限制,受DrawdownMaxOrders限制)
bool canOpenDrawdown = false;
int drawdownOrders = GetDrawdownOrderCount(_Symbol, MagicNumber);
if(EnableOpenOrder && EnableDrawdownOpen && DrawdownOpenUSD > 0 && drawdown >= DrawdownOpenUSD && drawdownOrders < DrawdownMaxOrders)
{
canOpenDrawdown = true;
}
// 每涨N美元加仓(不受时间间隔限制,受MaxOrders限制)
bool canAddOrder = false;
if(EnableOpenOrder && EnableAddOrderEveryUSD && AddOrderEveryUSD > 0 && currentPositions > 0 && currentPositions < MaxOrders)
{
if(g_last_add_price == 0.0) g_last_add_price = bid; // 首次赋值
double priceRise = bid - g_last_add_price;
if(priceRise >= AddOrderEveryUSD)
canAddOrder = true;
}
// 只要满足任一条件就下单(不受时间间隔限制)
// 反弹加仓受DrawdownMaxOrders限制,加仓受MaxOrders限制
if((canOpenDrawdown && drawdownOrders < DrawdownMaxOrders) || (canAddOrder && currentPositions < MaxOrders))
{
// 对齐手数
double vol = Lots;
vol = MathMax(g_min_lot, vol);
vol = AlignToStep(vol, g_lot_step);
vol = MathMin(vol, g_max_lot);
if(vol < g_min_lot - 1e-8)
{
Print("Volume too small");
return;
}
// 对齐价格
double price = AlignToPoint(bid);
// 计算TP
double tp_price = bid - TP_Points;
tp_price = AlignToPoint(tp_price);
// 选择合约
if(!SymbolSelect(_Symbol, true))
{
Print("Symbol not selectable");
return;
}
// 发送订单
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.type = ORDER_TYPE_SELL;
req.volume = vol;
req.price = price;
req.sl = 0.0;
req.tp = tp_price;
req.deviation = Deviation;
req.type_filling = GetFillingMode();
req.magic = MagicNumber;
// 注释区分:反弹加仓写DD,其余为空
if(canOpenDrawdown && drawdownOrders < DrawdownMaxOrders)
req.comment = "DD";
else
req.comment = "";
if(OrderSend(req, res))
{
if(res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED)
{
orderCount++;
lastOrderTime = TimeCurrent();
g_last_add_price = bid; // 记录本次加仓价
Print("[DD/Add] SELL order ", orderCount, " placed. Drawdown=", drawdown, " AddOrderEveryUSD=", AddOrderEveryUSD, " Bid=", bid, " Lowest=", g_lowest_price, " currentPositions=", currentPositions+1, "/", MaxOrders, " comment=", req.comment);
if(EnableCloseAllBuyOnSell)
CloseAllBuyPositions();
}
else
{
Print("Order failed: ", res.retcode, " Bid: ", bid);
}
}
else
{
Print("OrderSend failed, error code: ", GetLastError());
}
return; // 本tick只下一个单
}
// 其余原有下单逻辑(受时间间隔、SAR等限制)
// 取近SAR_SmoothBars根bar的自定义SAR均值,提升平滑度
double sarSum = 0.0;
int sarCount = 0;
for(int i=1; i<=SAR_SmoothBars; ++i)
{
double v = 0.0;
int sarHandle = iSAR(_Symbol, PERIOD_CURRENT, SAR_Acceleration, SAR_Maximum);
if(sarHandle != INVALID_HANDLE)
{
double sar[1];
if(CopyBuffer(sarHandle, 0, i-1, 1, sar) == 1)
{
v = sar[0];
}
IndicatorRelease(sarHandle);
}
if(v > 0.0 && v < bid * 100 && v > bid / 100)
{
sarSum += v;
sarCount++;
}
}
double sarValue = (sarCount > 0) ? (sarSum / sarCount) : 0.0;
// SAR值有效性保护
if(sarValue <= 0.0 || sarValue > bid * 100 || sarValue < bid / 100)
{
Print("[Warn] 平滑SAR均值异常,跳过本次开单。sarValue=", sarValue, " bid=", bid);
return;
}
// 获取当前RSI值
int rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
if(rsiHandle == INVALID_HANDLE)
{
Print("Failed to create RSI handle");
return;
}
double rsi[1];
if(CopyBuffer(rsiHandle, 0, 0, 1, rsi) != 1)
{
Print("Failed to copy RSI buffer");
IndicatorRelease(rsiHandle);
return;
}
IndicatorRelease(rsiHandle);
if(lastOrderTime > 0 && TimeCurrent() - lastOrderTime < Seconds) return; // 未到间隔时间
// SAR信号过滤
bool sarSignal = true;
if(EnableSAR)
{
sarSignal = false;
double prevSar = 0.0, prevClose = 0.0;
// 取上一根bar的SAR均值
double sarSum2 = 0.0;
int sarCount2 = 0;
for(int i=2; i<=SAR_SmoothBars+1; ++i)
{
double v = 0.0;
int sarHandle2 = iSAR(_Symbol, PERIOD_CURRENT, SAR_Acceleration, SAR_Maximum);
if(sarHandle2 != INVALID_HANDLE)
{
double sarBuf[1];
if(CopyBuffer(sarHandle2, 0, i-1, 1, sarBuf) == 1)
{
v = sarBuf[0];
}
IndicatorRelease(sarHandle2);
}
if(v > 0.0 && v < bid * 100 && v > bid / 100)
{
sarSum2 += v;
sarCount2++;
}
}
prevSar = (sarCount2 > 0) ? (sarSum2 / sarCount2) : 0.0;
double closeBuf[2];
if(CopyClose(_Symbol, PERIOD_CURRENT, 0, 2, closeBuf) == 2)
{
prevClose = closeBuf[1];
if(iSAR_Reverse)
{
// true:只要SAR在K线下方 或 由上转下就开空
if(sarValue < bid) sarSignal = true; // SAR在K线下方
else if(prevSar > prevClose && sarValue < bid) sarSignal = true; // 由上转下
}
else
{
// false:只要SAR在K线上方 或 由下转上就开空
if(sarValue > bid) sarSignal = true; // SAR在K线上方
else if(prevSar < prevClose && sarValue > bid) sarSignal = true; // 由下转上
}
}
}
if(!sarSignal) return;
// 对齐手数
double vol = Lots;
vol = MathMax(g_min_lot, vol);
vol = AlignToStep(vol, g_lot_step);
vol = MathMin(vol, g_max_lot);
if(vol < g_min_lot - 1e-8)
{
Print("Volume too small");
return;
}
// 对齐价格
double price = AlignToPoint(bid);
// 计算TP
double tp_price = bid - TP_Points;
tp_price = AlignToPoint(tp_price);
// 选择合约
if(!SymbolSelect(_Symbol, true))
{
Print("Symbol not selectable");
return;
}
// 发送订单
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.type = ORDER_TYPE_SELL;
req.volume = vol;
req.price = price;
req.sl = 0.0;
req.tp = tp_price;
req.deviation = Deviation;
req.type_filling = GetFillingMode();
req.magic = MagicNumber;
if(OrderSend(req, res))
{
if(res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED)
{
orderCount++;
lastOrderTime = TimeCurrent();
Print("SELL order ", orderCount, " placed successfully. Volume: ", vol, " Price: ", price, " TP: ", tp_price, " TP Spread: ", TP_Points, " Bid: ", bid, " SAR: ", sarValue, " (Remaining: ", remainingOrders-1, ")");
if(EnableCloseAllBuyOnSell)
CloseAllBuyPositions(); // 根据参数决定是否平多单
}
else
{
Print("Order failed: ", res.retcode, " Bid: ", bid, " SAR: ", sarValue);
}
}
else
{
Print("OrderSend failed, error code: ", GetLastError());
}
}
//+------------------------------------------------------------------+