//+------------------------------------------------------------------+ //| Trailing Stop EA | //+------------------------------------------------------------------+ #property copyright "thRee03" #property link "None" #property version "8.88" #property strict // 输入参数 input bool EnableCloseAllSellOnBuy = false; // 开多单时全平空单,true=全平,false=不平 input bool EnableOpenOrder = true; // 开单总开关,false时不开单但仍可移动止盈 input int Seconds = 133; // 下单最小间隔秒数 input double Lots = 0.01; // 每次下单手数 input int Deviation = 10; // 最大允许滑点(点) input double TP_Points = 10; // 止盈点数(下单时设置TP) input int MaxOrders = 13; // 最大持仓单数 input int MagicNumber = 12345; // 魔术编号 input double ProfitTargetUSD = 13.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.007; // SAR加速度(建议0.01~0.03,趋势信号更灵敏) input double SAR_Start = 0.076; // SAR初始值(0为默认) input double SAR_Maximum = 0.2; // SAR最大值(建议0.1~0.2) input int SAR_SmoothBars = 1; // SAR平滑根数(取近N根均值) input double TrailingStopUSD = 3.3; // 单仓利润回撤止盈(美元,移动止盈) 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=所有BUY仓位) input double MinBalance = 1200.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_highest_price = 0.0; // 历史最高价 double g_last_add_price = 0.0; // 上次加仓价 // 工具函数 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 0.0) { double sarSum = 0.0; int sarCount = 0; for(int i=1; i<=SAR_SmoothBars; ++i) { double v = CustomSAR(i, SAR_Acceleration, SAR_Maximum, SAR_Start); if(v > 0.0 && v < ask * 100 && v > ask / 100) { sarSum += v; sarCount++; } } if(sarCount > 0) sarValue = sarSum / sarCount; } int total = (int)PositionsTotal(); int closedCount = 0; 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; 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.bid; double profit = ((currentPrice - openPrice) / g_point) * volume * tickValue; Print("[CheckProfit] Ticket:", ticket, " Magic:", PositionGetInteger(POSITION_MAGIC), " Profit:", profit, " USD, Target:", ProfitTargetUSD, " SAR:", sarValue, " Price:", currentPrice); if(profit >= ProfitTargetUSD) { // 平仓 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 profitable position. Ticket: ", ticket, " Profit: ", profit, " USD, Magic: ", PositionGetInteger(POSITION_MAGIC)); closedCount++; } else { Print("Failed to close position: ", res.retcode); } } } if(closedCount == 0) { Print("[CheckProfit] No profitable positions found to close."); } else { Print("[CheckProfit] Closed ", closedCount, " profitable positions."); } } void CloseAllSellPositions() { 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; double volume = PositionGetDouble(POSITION_VOLUME); MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) continue; double currentPrice = tick.ask; // 平 SELL 用 ask // 平仓 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 SELL position. Ticket: ", ticket, " Magic: ", PositionGetInteger(POSITION_MAGIC)); } else { Print("Failed to close SELL 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 TrailingStopUSD) { double priceRetracement = TrailingStopUSD / (volume * tickValue) * g_point; double candidateSL = currentPrice - priceRetracement; candidateSL = AlignToPoint(candidateSL); candidateSL = MathMax(candidateSL, openPrice); // 止损不低于开仓价 if(candidateSL > currentSL || currentSL == 0.0) newSL = candidateSL; // 只允许上移或首次设置 } // 盈利未达阈值或回撤,不下移止损 // 仅当止损需要上移时才发出修改请求 if(newSL > currentSL) { MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_SLTP; req.symbol = _Symbol; req.position = ticket; req.sl = newSL; req.tp = PositionGetDouble(POSITION_TP); // 保留原TP 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), TP kept: ", req.tp); } 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 } // 自定义SAR算法(支持初始量) double CustomSAR(int barsBack, double step, double max, double start) { int bars = Bars(_Symbol, PERIOD_CURRENT); if(bars < 2 + barsBack) return 0.0; double sar = 0.0; double af = (start > 0.0 ? start : step); bool uptrend; double ep; int idx = barsBack + 1; double high1 = iHigh(_Symbol, PERIOD_CURRENT, idx); double low1 = iLow(_Symbol, PERIOD_CURRENT, idx); double close1 = iClose(_Symbol, PERIOD_CURRENT, idx); double high0 = iHigh(_Symbol, PERIOD_CURRENT, idx-1); double low0 = iLow(_Symbol, PERIOD_CURRENT, idx-1); double close0 = iClose(_Symbol, PERIOD_CURRENT, idx-1); // 初始化趋势 if(close0 > close1) { uptrend = true; ep = high0; sar = low1 + af * (high0 - low1); } else { uptrend = false; ep = low0; sar = high1 + af * (low0 - high1); } // 回溯到当前bar for(int i=idx-2; i>=barsBack; --i) { double high = iHigh(_Symbol, PERIOD_CURRENT, i); double low = iLow(_Symbol, PERIOD_CURRENT, i); if(uptrend) { sar = sar + af * (ep - sar); if(low < sar) { uptrend = false; sar = ep; ep = low; af = (start > 0.0 ? start : step); } else { if(high > ep) { ep = high; af = MathMin(af + step, max); } } sar = MathMin(sar, low); } else { sar = sar + af * (ep - sar); if(high > sar) { uptrend = true; sar = ep; ep = high; af = (start > 0.0 ? start : step); } else { if(low < ep) { ep = low; af = MathMin(af + step, max); } } sar = MathMax(sar, high); } } return sar; } // 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(); // 检查并平仓盈利仓位 // 获取当前Ask价 MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) { Print("Failed to get tick"); return; } double ask = tick.ask; // 记录历史最高价 if(g_highest_price < ask || g_highest_price == 0.0) g_highest_price = ask; // 计算距离历史高点的回撤 double drawdown = g_highest_price - ask; // 检查余额 double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); if(currentBalance < MinBalance) { Print("Balance too low: ", currentBalance, " < ", MinBalance, ", not opening order"); return; } int currentPositions = GetCurrentPositions(_Symbol, MagicNumber); int remainingOrders = MaxOrders - currentPositions; if(remainingOrders <= 0) 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 = ask; // 首次赋值 double priceDrop = g_last_add_price - ask; if(priceDrop >= 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(ask); // 计算TP double tp_price = ask + 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_BUY; 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 = ask; // 记录本次加仓价 Print("[DD/Add] BUY order ", orderCount, " placed. Drawdown=", drawdown, " AddOrderEveryUSD=", AddOrderEveryUSD, " Ask=", ask, " Highest=", g_highest_price, " currentPositions=", currentPositions+1, "/", MaxOrders, " comment=", req.comment); if(EnableCloseAllSellOnBuy) CloseAllSellPositions(); } else { Print("Order failed: ", res.retcode, " Ask: ", ask); } } 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 = CustomSAR(i, SAR_Acceleration, SAR_Maximum, SAR_Start); if(v > 0.0 && v < ask * 100 && v > ask / 100) // 有效性保护 { sarSum += v; sarCount++; } } double sarValue = (sarCount > 0) ? (sarSum / sarCount) : 0.0; // SAR值有效性保护 if(sarValue <= 0.0 || sarValue > ask * 100 || sarValue < ask / 100) { Print("[Warn] 平滑CustomSAR均值异常,跳过本次开单。sarValue=", sarValue, " ask=", ask); 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 = CustomSAR(i, SAR_Acceleration, SAR_Maximum, SAR_Start); if(v > 0.0 && v < ask * 100 && v > ask / 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 > ask) sarSignal = true; // SAR在K线上方 else if(prevSar < prevClose && sarValue > ask) sarSignal = true; // 由下转上 } else { // false:只要SAR在K线下方 或 由上转下就开多 if(sarValue < ask) sarSignal = true; // SAR在K线下方 else if(prevSar > prevClose && sarValue < ask) 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(ask); // 计算TP double tp_price = ask + 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_BUY; 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("BUY order ", orderCount, " placed successfully. Volume: ", vol, " Price: ", price, " TP: ", tp_price, " TP Spread: ", TP_Points, " Ask: ", ask, " SAR: ", sarValue, " (Remaining: ", remainingOrders-1, ")"); if(EnableCloseAllSellOnBuy) CloseAllSellPositions(); // 根据参数决定是否平空单 } else { Print("Order failed: ", res.retcode, " Ask: ", ask, " SAR: ", sarValue); } } else { Print("OrderSend failed, error code: ", GetLastError()); } } //+------------------------------------------------------------------+