持仓限制控制器 - 自动风控系统
实时显示持仓统计,自动监控总手数,达到限制后禁止新开仓并自动平掉超限订单,提供一键平仓、移动止盈、固定止损、挂单价止盈功能
效果示意图 · AI 根据源码绘制,仅示意画了哪些元素,细节以实际运行为准
#property copyright "XAUOF 指标工坊"
#property version "1.40"
#property strict
#include <Trade\Trade.mqh>
//--- 可调参数
input double MaxTotalLots = 10.0; // 最大总手数限制(0=不限制)
input bool AutoCloseLimitExcess = true; // 超限自动平仓(true=自动平,false=仅警告)
input int TrailingStopPoints = 300; // 移动止盈点数(0=不启用)
input int FixedStopLossPoints = 500; // 固定止损点数(用于一键设置)
input int BreakEvenPoints = 200; // 挂单价止盈点数(盈利超过此值移动到开仓价)
input int InitPanelX = 20; // 面板初始左边距(像素)
input int InitPanelY = 30; // 面板初始上边距(像素)
input int PanelWidth = 340; // 面板宽度(像素)
input int PanelHeight = 360; // 面板高度(像素)
input color PanelBgColor = clrDarkSlateGray; // 面板背景色
input color TitleBgColor = clrNavy; // 标题栏背景色
input color TextColor = clrWhite; // 文字颜色
input color BuyColor = clrLime; // 多头数据颜色
input color SellColor = clrRed; // 空头数据颜色
input color ProfitColor = clrGold; // 盈亏数据颜色
input int FontSize = 10; // 字体大小
input int RefreshMS = 500; // 刷新间隔(毫秒)
//--- 全局变量
CTrade trade;
uint lastRefreshTime = 0;
datetime lastCheckTime = 0;
bool trailingStopEnabled = false; // 移动止盈开关
// 面板位置和大小
int panelX, panelY;
int panelW, panelH;
// 拖动相关变量
bool isDragging = false;
bool isResizing = false;
int dragStartX, dragStartY;
int resizeStartX, resizeStartY;
int resizeStartW, resizeStartH;
// 界面对象名称
string panelBg = "StatPanelBg";
string panelTitle = "StatPanelTitle";
string resizeHandle = "StatResizeHandle";
string labelBuyLots = "LabelBuyLots";
string labelBuyCount = "LabelBuyCount";
string labelSellLots = "LabelSellLots";
string labelSellCount = "LabelSellCount";
string labelTotalLots = "LabelTotalLots";
string labelTotalProfit = "LabelTotalProfit";
string labelLimitWarning = "LabelLimitWarning";
string labelSeparator1 = "LabelSeparator1";
string labelSeparator2 = "LabelSeparator2";
string labelSeparator3 = "LabelSeparator3";
string btnCloseAll = "BtnCloseAll";
string btnCloseBuy = "BtnCloseBuy";
string btnCloseSell = "BtnCloseSell";
string btnTrailing = "BtnTrailing";
string btnFixedSL = "BtnFixedSL";
string btnBreakEven = "BtnBreakEven";
//+------------------------------------------------------------------+
//| 初始化 |
//+------------------------------------------------------------------+
int OnInit()
{
// 初始化面板位置和大小
panelX = InitPanelX;
panelY = InitPanelY;
panelW = PanelWidth;
panelH = PanelHeight;
// 创建面板界面
CreatePanel();
// 启用图表事件
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
Print("======================================");
Print("持仓限制控制器已启动");
if(MaxTotalLots > 0)
{
Print("总手数限制: ", MaxTotalLots, " 手");
Print("超限处理: ", AutoCloseLimitExcess ? "自动平仓" : "仅警告");
}
else
{
Print("总手数限制: 无限制");
}
Print("移动止盈: ", TrailingStopPoints, " 点");
Print("固定止损: ", FixedStopLossPoints, " 点");
Print("挂单价止盈触发: ", BreakEvenPoints, " 点");
Print("提示:可拖动标题栏移动面板");
Print("======================================");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| 清理 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// 删除所有界面对象
ObjectDelete(0, panelBg);
ObjectDelete(0, panelTitle);
ObjectDelete(0, resizeHandle);
ObjectDelete(0, labelBuyLots);
ObjectDelete(0, labelBuyCount);
ObjectDelete(0, labelSellLots);
ObjectDelete(0, labelSellCount);
ObjectDelete(0, labelTotalLots);
ObjectDelete(0, labelTotalProfit);
ObjectDelete(0, labelLimitWarning);
ObjectDelete(0, labelSeparator1);
ObjectDelete(0, labelSeparator2);
ObjectDelete(0, labelSeparator3);
ObjectDelete(0, btnCloseAll);
ObjectDelete(0, btnCloseBuy);
ObjectDelete(0, btnCloseSell);
ObjectDelete(0, btnTrailing);
ObjectDelete(0, btnFixedSL);
ObjectDelete(0, btnBreakEven);
ChartRedraw();
}
//+------------------------------------------------------------------+
//| 每个 Tick 更新 |
//+------------------------------------------------------------------+
void OnTick()
{
// 按时间间隔刷新面板
if(GetTickCount() - lastRefreshTime > (uint)RefreshMS)
{
UpdatePanel();
lastRefreshTime = GetTickCount();
}
// 每秒检查一次手数限制
datetime currentTime = TimeCurrent();
if(currentTime > lastCheckTime)
{
CheckPositionLimit();
lastCheckTime = currentTime;
}
// 如果启用了移动止盈,执行移动止盈逻辑
if(trailingStopEnabled && TrailingStopPoints > 0)
{
ProcessTrailingStop();
}
}
//+------------------------------------------------------------------+
//| 交易事件监控(关键:在这里拦截新订单) |
//+------------------------------------------------------------------+
void OnTrade()
{
// 立即检查手数限制
CheckPositionLimit();
}
//+------------------------------------------------------------------+
//| 检查持仓手数限制 |
//+------------------------------------------------------------------+
void CheckPositionLimit()
{
// 如果没有设置限制,直接返回
if(MaxTotalLots <= 0) return;
// 统计当前总手数
double totalLots = 0;
int totalCount = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
totalLots += PositionGetDouble(POSITION_VOLUME);
totalCount++;
}
// 判断是否超过限制
if(totalLots > MaxTotalLots)
{
double excessLots = totalLots - MaxTotalLots;
Print("⚠ 警告:总手数 ", DoubleToString(totalLots, 2),
" 超过限制 ", DoubleToString(MaxTotalLots, 2),
" 手,超出 ", DoubleToString(excessLots, 2), " 手");
// 如果启用自动平仓
if(AutoCloseLimitExcess)
{
Print("开始自动平掉超限订单...");
CloseExcessPositions(excessLots);
}
else
{
Print("提示:请手动平仓或调整限制参数");
}
}
}
//+------------------------------------------------------------------+
//| 平掉超限订单(按开仓时间从新到旧) |
//+------------------------------------------------------------------+
void CloseExcessPositions(double excessLots)
{
// 创建订单列表(按开仓时间排序)
struct PositionInfo
{
ulong ticket;
datetime openTime;
double lots;
};
PositionInfo positions[];
int count = 0;
// 收集所有持仓
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
count++;
ArrayResize(positions, count);
positions[count-1].ticket = ticket;
positions[count-1].openTime = (datetime)PositionGetInteger(POSITION_TIME);
positions[count-1].lots = PositionGetDouble(POSITION_VOLUME);
}
// 按开仓时间从新到旧排序(最新的订单先平)
for(int i = 0; i < count - 1; i++)
{
for(int j = i + 1; j < count; j++)
{
if(positions[i].openTime < positions[j].openTime)
{
PositionInfo temp = positions[i];
positions[i] = positions[j];
positions[j] = temp;
}
}
}
// 从最新的订单开始平仓
double closedLots = 0;
int closedCount = 0;
for(int i = 0; i < count; i++)
{
if(closedLots >= excessLots) break;
ulong ticket = positions[i].ticket;
double lots = positions[i].lots;
Print("正在平仓订单 #", ticket, " (", DoubleToString(lots, 2), " 手)...");
if(trade.PositionClose(ticket))
{
closedLots += lots;
closedCount++;
Print("✓ 订单 #", ticket, " 已平仓");
}
else
{
Print("✗ 订单 #", ticket, " 平仓失败: ", trade.ResultRetcodeDescription());
}
}
Print("自动平仓完成:平掉 ", closedCount, " 单,共 ", DoubleToString(closedLots, 2), " 手");
}
//+------------------------------------------------------------------+
//| 处理移动止盈 |
//+------------------------------------------------------------------+
void ProcessTrailingStop()
{
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double currentPrice = (type == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double newSL = 0;
bool needUpdate = false;
if(type == POSITION_TYPE_BUY)
{
// 多单:当前价 - 移动止盈点数
newSL = currentPrice - TrailingStopPoints * point;
// 新止损必须高于开仓价且高于当前止损
if(newSL > openPrice && (currentSL == 0 || newSL > currentSL))
{
needUpdate = true;
}
}
else if(type == POSITION_TYPE_SELL)
{
// 空单:当前价 + 移动止盈点数
newSL = currentPrice + TrailingStopPoints * point;
// 新止损必须低于开仓价且低于当前止损(或当前止损为0)
if(newSL < openPrice && (currentSL == 0 || newSL < currentSL))
{
needUpdate = true;
}
}
// 执行修改
if(needUpdate)
{
newSL = NormalizeDouble(newSL, digits);
if(trade.PositionModify(ticket, newSL, currentTP))
{
Print("移动止盈:订单 #", ticket, " 止损已更新至 ", DoubleToString(newSL, digits));
}
}
}
}
//+------------------------------------------------------------------+
//| 设置固定止损 |
//+------------------------------------------------------------------+
void SetFixedStopLoss()
{
if(FixedStopLossPoints <= 0)
{
MessageBox("固定止损点数必须大于0", "参数错误", MB_OK | MB_ICONERROR);
return;
}
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
int updated = 0, failed = 0;
Print("======================================");
Print("开始设置固定止损 ", FixedStopLossPoints, " 点...");
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentTP = PositionGetDouble(POSITION_TP);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double newSL = 0;
if(type == POSITION_TYPE_BUY)
{
newSL = openPrice - FixedStopLossPoints * point;
}
else if(type == POSITION_TYPE_SELL)
{
newSL = openPrice + FixedStopLossPoints * point;
}
newSL = NormalizeDouble(newSL, digits);
if(trade.PositionModify(ticket, newSL, currentTP))
{
updated++;
Print("✓ 订单 #", ticket, " 止损已设置为 ", DoubleToString(newSL, digits));
}
else
{
failed++;
Print("✗ 订单 #", ticket, " 设置失败: ", trade.ResultRetcodeDescription());
}
}
string msg = "固定止损设置完成\n\n成功: " + IntegerToString(updated) + " 单";
if(failed > 0) msg += "\n失败: " + IntegerToString(failed) + " 单";
Print("固定止损设置完成:成功 ", updated, " 单,失败 ", failed, " 单");
Print("======================================");
MessageBox(msg, "固定止损结果", MB_OK | MB_ICONINFORMATION);
}
//+------------------------------------------------------------------+
//| 设置挂单价止盈(保本) |
//+------------------------------------------------------------------+
void SetBreakEven()
{
if(BreakEvenPoints <= 0)
{
MessageBox("挂单价止盈点数必须大于0", "参数错误", MB_OK | MB_ICONERROR);
return;
}
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
int updated = 0, skipped = 0, failed = 0;
Print("======================================");
Print("开始设置挂单价止盈,触发阈值 ", BreakEvenPoints, " 点...");
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double currentPrice = (type == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double profitPoints = 0;
bool canSetBreakEven = false;
if(type == POSITION_TYPE_BUY)
{
profitPoints = (currentPrice - openPrice) / point;
// 盈利超过阈值,且当前止损低于开仓价(或没设止损)
if(profitPoints >= BreakEvenPoints && (currentSL == 0 || currentSL < openPrice))
{
canSetBreakEven = true;
}
}
else if(type == POSITION_TYPE_SELL)
{
profitPoints = (openPrice - currentPrice) / point;
// 盈利超过阈值,且当前止损高于开仓价(或没设止损)
if(profitPoints >= BreakEvenPoints && (currentSL == 0 || currentSL > openPrice))
{
canSetBreakEven = true;
}
}
if(canSetBreakEven)
{
double newSL = NormalizeDouble(openPrice, digits);
if(trade.PositionModify(ticket, newSL, currentTP))
{
updated++;
Print("✓ 订单 #", ticket, " 止损已移至开仓价 ", DoubleToString(newSL, digits),
" (盈利 ", DoubleToString(profitPoints, 1), " 点)");
}
else
{
failed++;
Print("✗ 订单 #", ticket, " 设置失败: ", trade.ResultRetcodeDescription());
}
}
else
{
skipped++;
Print("跳过订单 #", ticket, " (盈利 ", DoubleToString(profitPoints, 1), " 点,未达阈值或已设置)");
}
}
string msg = "挂单价止盈设置完成\n\n";
msg += "成功: " + IntegerToString(updated) + " 单\n";
msg += "跳过: " + IntegerToString(skipped) + " 单";
if(failed > 0) msg += "\n失败: " + IntegerToString(failed) + " 单";
Print("挂单价止盈完成:成功 ", updated, " 单,跳过 ", skipped, " 单,失败 ", failed, " 单");
Print("======================================");
MessageBox(msg, "挂单价止盈结果", MB_OK | MB_ICONINFORMATION);
}
//+------------------------------------------------------------------+
//| 图表事件处理 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
// 鼠标移动事件 - 处理拖动
if(id == CHARTEVENT_MOUSE_MOVE)
{
int x = (int)lparam;
int y = (int)dparam;
int mouseState = (int)sparam; // 0=无按钮, 1=左键按下
// 正在拖动面板
if(isDragging && mouseState == 1)
{
panelX = x - dragStartX;
panelY = y - dragStartY;
UpdatePanelPosition();
}
else if(isDragging && mouseState == 0)
{
isDragging = false;
}
// 正在调整大小
if(isResizing && mouseState == 1)
{
panelW = resizeStartW + (x - resizeStartX);
panelH = resizeStartH + (y - resizeStartY);
// 限制最小尺寸
if(panelW < 280) panelW = 280;
if(panelH < 320) panelH = 320;
UpdatePanelSize();
}
else if(isResizing && mouseState == 0)
{
isResizing = false;
}
}
// 对象点击事件
if(id == CHARTEVENT_OBJECT_CLICK)
{
// 点击标题栏开始拖动
if(sparam == panelTitle)
{
isDragging = true;
dragStartX = (int)lparam - panelX;
dragStartY = (int)dparam - panelY;
ObjectSetInteger(0, panelTitle, OBJPROP_STATE, false);
return;
}
// 点击调整大小手柄
if(sparam == resizeHandle)
{
isResizing = true;
resizeStartX = (int)lparam;
resizeStartY = (int)dparam;
resizeStartW = panelW;
resizeStartH = panelH;
ObjectSetInteger(0, resizeHandle, OBJPROP_STATE, false);
return;
}
// 一键平仓按钮
if(sparam == btnCloseAll)
{
if(MessageBox("确定要平掉当前品种所有持仓吗?", "一键平仓", MB_YESNO | MB_ICONWARNING) == IDYES)
{
CloseAllPositions();
}
ObjectSetInteger(0, btnCloseAll, OBJPROP_STATE, false);
}
// 一键平多按钮
if(sparam == btnCloseBuy)
{
if(MessageBox("确定要平掉所有多头持仓吗?", "一键平多", MB_YESNO | MB_ICONQUESTION) == IDYES)
{
ClosePositionsByType(POSITION_TYPE_BUY);
}
ObjectSetInteger(0, btnCloseBuy, OBJPROP_STATE, false);
}
// 一键平空按钮
if(sparam == btnCloseSell)
{
if(MessageBox("确定要平掉所有空头持仓吗?", "一键平空", MB_YESNO | MB_ICONQUESTION) == IDYES)
{
ClosePositionsByType(POSITION_TYPE_SELL);
}
ObjectSetInteger(0, btnCloseSell, OBJPROP_STATE, false);
}
// 移动止盈按钮
if(sparam == btnTrailing)
{
trailingStopEnabled = !trailingStopEnabled;
ObjectSetInteger(0, btnTrailing, OBJPROP_BGCOLOR, trailingStopEnabled ? clrLimeGreen : clrGreen);
ObjectSetString(0, btnTrailing, OBJPROP_TEXT, trailingStopEnabled ? "移动止盈 ●" : "移动止盈");
Print("移动止盈 ", trailingStopEnabled ? "已启用" : "已停用");
ObjectSetInteger(0, btnTrailing, OBJPROP_STATE, false);
}
// 固定止损按钮
if(sparam == btnFixedSL)
{
if(MessageBox("为所有持仓设置固定止损 " + IntegerToString(FixedStopLossPoints) + " 点?", "固定止损", MB_YESNO | MB_ICONQUESTION) == IDYES)
{
SetFixedStopLoss();
}
ObjectSetInteger(0, btnFixedSL, OBJPROP_STATE, false);
}
// 挂单价止盈按钮
if(sparam == btnBreakEven)
{
if(MessageBox("将盈利订单的止损移动到开仓价(保本)?", "挂单价止盈", MB_YESNO | MB_ICONQUESTION) == IDYES)
{
SetBreakEven();
}
ObjectSetInteger(0, btnBreakEven, OBJPROP_STATE, false);
}
ChartRedraw();
}
}
//+------------------------------------------------------------------+
//| 关闭所有持仓 |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
int closed = 0, failed = 0;
Print("======================================");
Print("开始执行一键平仓...");
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double lots = PositionGetDouble(POSITION_VOLUME);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
string typeName = (type == POSITION_TYPE_BUY) ? "多" : "空";
if(trade.PositionClose(ticket))
{
closed++;
Print("✓ 已平仓 ", typeName, "单 #", ticket, " (", DoubleToString(lots, 2), " 手)");
}
else
{
failed++;
Print("✗ 平仓失败 ", typeName, "单 #", ticket, " 错误: ", trade.ResultRetcodeDescription());
}
}
string msg = "一键平仓完成\n\n成功: " + IntegerToString(closed) + " 单";
if(failed > 0) msg += "\n失败: " + IntegerToString(failed) + " 单";
Print("一键平仓完成:成功 ", closed, " 单,失败 ", failed, " 单");
Print("======================================");
MessageBox(msg, "一键平仓结果", MB_OK | MB_ICONINFORMATION);
UpdatePanel();
}
//+------------------------------------------------------------------+
//| 按类型关闭持仓 |
//+------------------------------------------------------------------+
void ClosePositionsByType(ENUM_POSITION_TYPE posType)
{
int closed = 0, failed = 0;
string typeName = (posType == POSITION_TYPE_BUY) ? "多头" : "空头";
Print("======================================");
Print("开始执行一键平", typeName, "...");
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(type != posType) continue;
double lots = PositionGetDouble(POSITION_VOLUME);
if(trade.PositionClose(ticket))
{
closed++;
Print("✓ 已平", typeName, "订单 #", ticket, " (", DoubleToString(lots, 2), " 手)");
}
else
{
failed++;
Print("✗ 平", typeName, "失败 #", ticket, " 错误: ", trade.ResultRetcodeDescription());
}
}
string msg = "一键平" + typeName + "完成\n\n成功: " + IntegerToString(closed) + " 单";
if(failed > 0) msg += "\n失败: " + IntegerToString(failed) + " 单";
Print("一键平", typeName, "完成:成功 ", closed, " 单,失败 ", failed, " 单");
Print("======================================");
MessageBox(msg, "一键平" + (posType == POSITION_TYPE_BUY ? "多" : "空") + "结果", MB_OK | MB_ICONINFORMATION);
UpdatePanel();
}
//+------------------------------------------------------------------+
//| 创建面板 |
//+------------------------------------------------------------------+
void CreatePanel()
{
// 创建背景矩形
ObjectCreate(0, panelBg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, panelBg, OBJPROP_XDISTANCE, panelX);
ObjectSetInteger(0, panelBg, OBJPROP_YDISTANCE, panelY);
ObjectSetInteger(0, panelBg, OBJPROP_XSIZE, panelW);
ObjectSetInteger(0, panelBg, OBJPROP_YSIZE, panelH);
ObjectSetInteger(0, panelBg, OBJPROP_BGCOLOR, PanelBgColor);
ObjectSetInteger(0, panelBg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, panelBg, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, panelBg, OBJPROP_BACK, false);
ObjectSetInteger(0, panelBg, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, panelBg, OBJPROP_HIDDEN, true);
// 创建标题栏(可点击拖动)
ObjectCreate(0, panelTitle, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, panelTitle, OBJPROP_XDISTANCE, panelX);
ObjectSetInteger(0, panelTitle, OBJPROP_YDISTANCE, panelY);
ObjectSetInteger(0, panelTitle, OBJPROP_XSIZE, panelW);
ObjectSetInteger(0, panelTitle, OBJPROP_YSIZE, 28);
ObjectSetString(0, panelTitle, OBJPROP_TEXT, "【" + _Symbol + " 持仓限制控制器】");
ObjectSetInteger(0, panelTitle, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, panelTitle, OBJPROP_BGCOLOR, TitleBgColor);
ObjectSetInteger(0, panelTitle, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, panelTitle, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, panelTitle, OBJPROP_FONTSIZE, FontSize);
ObjectSetString(0, panelTitle, OBJPROP_FONT, "Microsoft YaHei");
// 调整大小手柄(右下角按钮)
ObjectCreate(0, resizeHandle, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, resizeHandle, OBJPROP_XDISTANCE, panelX + panelW - 18);
ObjectSetInteger(0, resizeHandle, OBJPROP_YDISTANCE, panelY + panelH - 18);
ObjectSetInteger(0, resizeHandle, OBJPROP_XSIZE, 18);
ObjectSetInteger(0, resizeHandle, OBJPROP_YSIZE, 18);
ObjectSetString(0, resizeHandle, OBJPROP_TEXT, "⤡");
ObjectSetInteger(0, resizeHandle, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, resizeHandle, OBJPROP_BGCOLOR, clrDimGray);
ObjectSetInteger(0, resizeHandle, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, resizeHandle, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, resizeHandle, OBJPROP_FONTSIZE, 9);
// 创建数据显示标签 - 每项单独一行
int startY = panelY + 40;
int lineHeight = 20;
// 分隔线1
CreateLabel(labelSeparator1, panelX + 10, startY, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", clrGray, FontSize - 2);
// 多头手数
CreateLabel(labelBuyLots, panelX + 15, startY + lineHeight, "多头手数: 0.00", BuyColor, FontSize);
// 多头订单数
CreateLabel(labelBuyCount, panelX + 15, startY + lineHeight * 2, "多头订单: 0 单", BuyColor, FontSize - 1);
// 空头手数
CreateLabel(labelSellLots, panelX + 15, startY + lineHeight * 3 + 5, "空头手数: 0.00", SellColor, FontSize);
// 空头订单数
CreateLabel(labelSellCount, panelX + 15, startY + lineHeight * 4 + 5, "空头订单: 0 单", SellColor, FontSize - 1);
// 分隔线2
CreateLabel(labelSeparator2, panelX + 10, startY + lineHeight * 5 + 10, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", clrGray, FontSize - 2);
// 总手数
CreateLabel(labelTotalLots, panelX + 15, startY + lineHeight * 6 + 10, "总手数: 0.00", TextColor, FontSize + 1);
// 总盈亏
CreateLabel(labelTotalProfit, panelX + 15, startY + lineHeight * 7 + 10, "总盈亏: 0.00 USD", ProfitColor, FontSize + 1);
// 分隔线3
CreateLabel(labelSeparator3, panelX + 10, startY + lineHeight * 8 + 15, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", clrGray, FontSize - 2);
// 手数限制警告
CreateLabel(labelLimitWarning, panelX + 15, startY + lineHeight * 9 + 15, "手数限制: 无限制", clrLightGray, FontSize);
// 创建平仓按钮(第一行)
int btnY1 = panelY + panelH - 75;
int btnWidth = (panelW - 50) / 3;
CreateButton(btnCloseAll, panelX + 10, btnY1, btnWidth, 30, "一键平仓", clrCrimson);
CreateButton(btnCloseBuy, panelX + 20 + btnWidth, btnY1, btnWidth, 30, "一键平多", clrOrangeRed);
CreateButton(btnCloseSell, panelX + 30 + btnWidth * 2, btnY1, btnWidth, 30, "一键平空", clrDodgerBlue);
// 创建止盈止损按钮(第二行)
int btnY2 = panelY + panelH - 38;
CreateButton(btnTrailing, panelX + 10, btnY2, btnWidth, 30, "移动止盈", clrGreen);
CreateButton(btnFixedSL, panelX + 20 + btnWidth, btnY2, btnWidth, 30, "固定止损", clrDarkOrange);
CreateButton(btnBreakEven, panelX + 30 + btnWidth * 2, btnY2, btnWidth, 30, "挂单价止盈", clrMediumSeaGreen);
}
//+------------------------------------------------------------------+
//| 更新面板位置 |
//+------------------------------------------------------------------+
void UpdatePanelPosition()
{
// 更新背景
ObjectSetInteger(0, panelBg, OBJPROP_XDISTANCE, panelX);
ObjectSetInteger(0, panelBg, OBJPROP_YDISTANCE, panelY);
// 更新标题栏
ObjectSetInteger(0, panelTitle, OBJPROP_XDISTANCE, panelX);
ObjectSetInteger(0, panelTitle, OBJPROP_YDISTANCE, panelY);
// 更新调整大小手柄
ObjectSetInteger(0, resizeHandle, OBJPROP_XDISTANCE, panelX + panelW - 18);
ObjectSetInteger(0, resizeHandle, OBJPROP_YDISTANCE, panelY + panelH - 18);
// 更新所有标签位置
int startY = panelY + 40;
int lineHeight = 20;
ObjectSetInteger(0, labelSeparator1, OBJPROP_XDISTANCE, panelX + 10);
ObjectSetInteger(0, labelSeparator1, OBJPROP_YDISTANCE, startY);
ObjectSetInteger(0, labelBuyLots, OBJPROP_XDISTANCE, panelX + 15);
ObjectSetInteger(0, labelBuyLots, OBJPROP_YDISTANCE, startY + lineHeight);
ObjectSetInteger(0, labelBuyCount, OBJPROP_XDISTANCE, panelX + 15);
ObjectSetInteger(0, labelBuyCount, OBJPROP_YDISTANCE, startY + lineHeight * 2);
ObjectSetInteger(0, labelSellLots, OBJPROP_XDISTANCE, panelX + 15);
ObjectSetInteger(0, labelSellLots, OBJPROP_YDISTANCE, startY + lineHeight * 3 + 5);
ObjectSetInteger(0, labelSellCount, OBJPROP_XDISTANCE, panelX + 15);
ObjectSetInteger(0, labelSellCount, OBJPROP_YDISTANCE, startY + lineHeight * 4 + 5);
ObjectSetInteger(0, labelSeparator2, OBJPROP_XDISTANCE, panelX + 10);
ObjectSetInteger(0, labelSeparator2, OBJPROP_YDISTANCE, startY + lineHeight * 5 + 10);
ObjectSetInteger(0, labelTotalLots, OBJPROP_XDISTANCE, panelX + 15);
ObjectSetInteger(0, labelTotalLots, OBJPROP_YDISTANCE, startY + lineHeight * 6 + 10);
ObjectSetInteger(0, labelTotalProfit, OBJPROP_XDISTANCE, panelX + 15);
ObjectSetInteger(0, labelTotalProfit, OBJPROP_YDISTANCE, startY + lineHeight * 7 + 10);
ObjectSetInteger(0, labelSeparator3, OBJPROP_XDISTANCE, panelX + 10);
ObjectSetInteger(0, labelSeparator3, OBJPROP_YDISTANCE, startY + lineHeight * 8 + 15);
ObjectSetInteger(0, labelLimitWarning, OBJPROP_XDISTANCE, panelX + 15);
ObjectSetInteger(0, labelLimitWarning, OBJPROP_YDISTANCE, startY + lineHeight * 9 + 15);
// 更新按钮位置
UpdateButtonPositions();
ChartRedraw();
}
//+------------------------------------------------------------------+
//| 更新面板大小 |
//+------------------------------------------------------------------+
void UpdatePanelSize()
{
// 更新背景大小
ObjectSetInteger(0, panelBg, OBJPROP_XSIZE, panelW);
ObjectSetInteger(0, panelBg, OBJPROP_YSIZE, panelH);
// 更新标题栏宽度
ObjectSetInteger(0, panelTitle, OBJPROP_XSIZE, panelW);
// 更新调整大小手柄位置
ObjectSetInteger(0, resizeHandle, OBJPROP_XDISTANCE, panelX + panelW - 18);
ObjectSetInteger(0, resizeHandle, OBJPROP_YDISTANCE, panelY + panelH - 18);
// 更新按钮位置和大小
UpdateButtonPositions();
ChartRedraw();
}
//+------------------------------------------------------------------+
//| 更新按钮位置 |
//+------------------------------------------------------------------+
void UpdateButtonPositions()
{
int btnY1 = panelY + panelH - 75;
int btnY2 = panelY + panelH - 38;
int btnWidth = (panelW - 50) / 3;
// 第一行平仓按钮
ObjectSetInteger(0, btnCloseAll, OBJPROP_XDISTANCE, panelX + 10);
ObjectSetInteger(0, btnCloseAll, OBJPROP_YDISTANCE, btnY1);
ObjectSetInteger(0, btnCloseAll, OBJPROP_XSIZE, btnWidth);
ObjectSetInteger(0, btnCloseBuy, OBJPROP_XDISTANCE, panelX + 20 + btnWidth);
ObjectSetInteger(0, btnCloseBuy, OBJPROP_YDISTANCE, btnY1);
ObjectSetInteger(0, btnCloseBuy, OBJPROP_XSIZE, btnWidth);
ObjectSetInteger(0, btnCloseSell, OBJPROP_XDISTANCE, panelX + 30 + btnWidth * 2);
ObjectSetInteger(0, btnCloseSell, OBJPROP_YDISTANCE, btnY1);
ObjectSetInteger(0, btnCloseSell, OBJPROP_XSIZE, btnWidth);
// 第二行止盈止损按钮
ObjectSetInteger(0, btnTrailing, OBJPROP_XDISTANCE, panelX + 10);
ObjectSetInteger(0, btnTrailing, OBJPROP_YDISTANCE, btnY2);
ObjectSetInteger(0, btnTrailing, OBJPROP_XSIZE, btnWidth);
ObjectSetInteger(0, btnFixedSL, OBJPROP_XDISTANCE, panelX + 20 + btnWidth);
ObjectSetInteger(0, btnFixedSL, OBJPROP_YDISTANCE, btnY2);
ObjectSetInteger(0, btnFixedSL, OBJPROP_XSIZE, btnWidth);
ObjectSetInteger(0, btnBreakEven, OBJPROP_XDISTANCE, panelX + 30 + btnWidth * 2);
ObjectSetInteger(0, btnBreakEven, OBJPROP_YDISTANCE, btnY2);
ObjectSetInteger(0, btnBreakEven, OBJPROP_XSIZE, btnWidth);
}
//+------------------------------------------------------------------+
//| 更新面板显示数据 |
//+------------------------------------------------------------------+
void UpdatePanel()
{
double buyLots = 0, sellLots = 0, totalProfit = 0;
int buyCount = 0, sellCount = 0;
// 统计所有持仓
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double lots = PositionGetDouble(POSITION_VOLUME);
double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(type == POSITION_TYPE_BUY)
{
buyLots += lots;
buyCount++;
}
else if(type == POSITION_TYPE_SELL)
{
sellLots += lots;
sellCount++;
}
totalProfit += profit;
}
double totalLots = buyLots + sellLots;
string currency = AccountInfoString(ACCOUNT_CURRENCY);
// 更新各项数据显示 - 每项单独一行
ObjectSetString(0, labelBuyLots, OBJPROP_TEXT, "多头手数: " + DoubleToString(buyLots, 2));
ObjectSetString(0, labelBuyCount, OBJPROP_TEXT, "多头订单: " + IntegerToString(buyCount) + " 单");
ObjectSetString(0, labelSellLots, OBJPROP_TEXT, "空头手数: " + DoubleToString(sellLots, 2));
ObjectSetString(0, labelSellCount, OBJPROP_TEXT, "空头订单: " + IntegerToString(sellCount) + " 单");
ObjectSetString(0, labelTotalLots, OBJPROP_TEXT, "总手数: " + DoubleToString(totalLots, 2));
// 总盈亏根据正负显示不同颜色
string profitText = "总盈亏: " + DoubleToString(totalProfit, 2) + " " + currency;
ObjectSetString(0, labelTotalProfit, OBJPROP_TEXT, profitText);
if(totalProfit > 0)
ObjectSetInteger(0, labelTotalProfit, OBJPROP_COLOR, clrLime);
else if(totalProfit < 0)
ObjectSetInteger(0, labelTotalProfit, OBJPROP_COLOR, clrRed);
else
ObjectSetInteger(0, labelTotalProfit, OBJPROP_COLOR, ProfitColor);
// 更新手数限制警告
if(MaxTotalLots > 0)
{
double remainingLots = MaxTotalLots - totalLots;
if(totalLots > MaxTotalLots)
{
// 超过限制 - 红色闪烁警告
string warningText = "⚠⚠ 超限 " + DoubleToString(totalLots - MaxTotalLots, 2) + " 手!";
if(AutoCloseLimitExcess)
warningText += " [自动平仓中]";
ObjectSetString(0, labelLimitWarning, OBJPROP_TEXT, warningText);
ObjectSetInteger(0, labelLimitWarning, OBJPROP_COLOR, clrRed);
}
else if(totalLots >= MaxTotalLots)
{
// 刚好达到限制 - 红色警告
string warningText = "⚠ 已达限制 " + DoubleToString(MaxTotalLots, 2) + " 手 - 禁止开仓";
ObjectSetString(0, labelLimitWarning, OBJPROP_TEXT, warningText);
ObjectSetInteger(0, labelLimitWarning, OBJPROP_COLOR, clrRed);
}
else if(totalLots >= MaxTotalLots * 0.8)
{
// 接近限制(80%以上)- 黄色警告
string warningText = "提示: 剩余 " + DoubleToString(remainingLots, 2) + " 手可用";
ObjectSetString(0, labelLimitWarning, OBJPROP_TEXT, warningText);
ObjectSetInteger(0, labelLimitWarning, OBJPROP_COLOR, clrYellow);
}
else
{
// 正常状态 - 灰色
string warningText = "手数限制: " + DoubleToString(MaxTotalLots, 2) + " 手";
ObjectSetString(0, labelLimitWarning, OBJPROP_TEXT, warningText);
ObjectSetInteger(0, labelLimitWarning, OBJPROP_COLOR, clrLightGray);
}
}
else
{
// 无限制
ObjectSetString(0, labelLimitWarning, OBJPROP_TEXT, "手数限制: 无限制");
ObjectSetInteger(0, labelLimitWarning, OBJPROP_COLOR, clrLightGray);
}
ChartRedraw();
}
//+------------------------------------------------------------------+
//| 创建文本标签 |
//+------------------------------------------------------------------+
void CreateLabel(string name, int x, int y, string text, color clr, int fontSize)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
//+------------------------------------------------------------------+
//| 创建按钮 |
//+------------------------------------------------------------------+
void CreateButton(string name, int x, int y, int width, int height, string text, color bgColor)
{
ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgColor);
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, FontSize - 1);
ObjectSetString(0, name, OBJPROP_FONT, "Microsoft YaHei");
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}作品说明
使用说明
1. 安装方法
- 将代码保存为
PositionLimitController.mq5,放入MQL5/Experts文件夹 - 在 MetaEditor 中编译(F7)
- 在 MT5 图表上拖入该 EA,勾选"允许自动交易"
2. 持仓统计显示(每项单独一行)
面板显示以下信息:
- 多头手数:所有多头持仓的总手数(绿色)
- 多头订单:多头订单数量(绿色)
- 空头手数:所有空头持仓的总手数(红色)
- 空头订单:空头订单数量(红色)
- 总手数:多空合计的总手数(白色)
- 总盈亏:所有持仓的盈亏总和,含隔夜利息(盈利绿色,亏损红色)
- 手数限制警告:实时显示手数限制状态
3. 总手数限制功能
- MaxTotalLots:设置最大总手数(例如 10.0 手)
- AutoCloseLimitExcess:超限是否自动平仓
- true:自动平掉最新的订单直到符合限制
- false:仅警告提示
- 三种状态显示:
- 灰色:正常状态
- 黄色:已使用 80% 以上手数
- 红色:已达到或超过限制
4. 平仓按钮(第一行)
- 一键平仓:关闭当前品种所有持仓(多空都平)
- 一键平多:只关闭所有多头持仓
- 一键平空:只关闭所有空头持仓
所有平仓操作都需要二次确认,防止误操作。
5. 止盈止损按钮(第二行)
- 移动止盈:
- 点击启用/停用移动止盈功能
- 启用后按钮变亮绿色并显示 ●
- 自动追踪价格,持续将止损向有利方向移动
- 移动距离由
TrailingStopPoints参数控制(默认 300 点) - 只会向保护浮盈的方向移动,不会放宽止损
- 固定止损:
- 一键为所有持仓设置固定止损
- 距离开仓价
FixedStopLossPoints点(默认 500 点) - 多单:开仓价 - 500 点
- 空单:开仓价 + 500 点
- 挂单价止盈:
- 将盈利订单的止损移至开仓价(保本)
- 只处理盈利超过
BreakEvenPoints点的订单(默认 200 点) - 锁定利润,避免盈利回吐为亏损
6. 参数设置
- MaxTotalLots:最大总手数限制(0 = 不限制)
- AutoCloseLimitExcess:超限是否自动平仓
- TrailingStopPoints:移动止盈点数(0 = 不启用)
- FixedStopLossPoints:固定止损点数
- BreakEvenPoints:挂单价止盈触发点数
- InitPanelX/Y:面板初始位置
- PanelWidth/Height:面板初始大小
- RefreshMS:刷新间隔(毫秒)
7. 操作提示
- 面板可拖动标题栏移动位置
- 右下角可拖动调整大小
- 所有操作都有日志记录在 MT5 的"专家"选项卡
- 移动止盈启用时会在每个 Tick 自动执行
- 固定止损和挂单价止盈需手动点击执行
8. 注意事项
- 只统计和操作当前图表品种的持仓
- 手数限制功能在
OnTrade()事件中监控,新订单触发时立即检查 - 移动止盈只会向有利方向移动止损,不会放宽
- 挂单价止盈只处理达到盈利阈值且未设置保本的订单
- 所有修改操作都有二次确认对话框,防止误操作
AI 解读根据源码逐行分析写成,供参考,以实际运行为准
这是什么
这是一个为 MT5 设计的持仓管理和风控工具,以面板形式显示当前品种的多空持仓统计(手数、订单数、总盈亏),并能设定总手数上限。当持仓手数超过限制时,EA 自动按开仓时间从新到旧平掉超限订单,防止过度持仓。同时提供一键平仓、移动止盈、固定止损、挂单价止盈等快捷操作,适合需要严格控制风险敞口或同时运行多个策略的交易者。
逻辑原理
持仓统计:每隔 500 毫秒(可调)扫描一次当前品种的所有持仓,分别统计多头手数、空头手数、订单数量和总盈亏(含隔夜利息),在面板上实时显示。
手数限制监控:在每次交易事件(OnTrade)和每秒 Tick 检查时,计算当前总手数。若超过 MaxTotalLots 设定值,根据 AutoCloseLimitExcess 参数决定是仅警告还是自动平仓。自动平仓时,EA 按开仓时间从新到旧排序所有订单,逐个平仓直到手数回到限制以下。
移动止盈:点击按钮启用后,每个 Tick 检查所有持仓,对多单将止损向上移至「当前买价 - TrailingStopPoints 点」,对空单向下移至「当前卖价 + TrailingStopPoints 点」。新止损必须比当前止损更有利且高于(多单)或低于(空单)开仓价,确保只锁定浮盈。
固定止损:点击后一次性为所有持仓设置距离开仓价 FixedStopLossPoints 点的止损(多单在下方,空单在上方)。
挂单价止盈(保本):扫描所有持仓,对盈利超过 BreakEvenPoints 点的订单,将止损移至开仓价,锁定保本。
参数说明
- MaxTotalLots(默认 10.0):允许的最大总手数。设为 0 表示不限制。
- AutoCloseLimitExcess(默认 true):超限时是否自动平仓。false 时只在日志中警告。
- TrailingStopPoints(默认 300):移动止盈的跟踪点数。设为 0 则该功能不可用。
- FixedStopLossPoints(默认 500):一键固定止损距离开仓价的点数。
- BreakEvenPoints(默认 200):触发挂单价止盈的最小盈利点数。
- InitPanelX / InitPanelY(默认 20 / 30):面板初始位置的左上角坐标(像素)。
- PanelWidth / PanelHeight(默认 340 / 360):面板初始宽度和高度(像素),运行时可拖动右下角调整。
- PanelBgColor / TitleBgColor / TextColor / BuyColor / SellColor / ProfitColor:面板和文字的颜色设置。
- FontSize(默认 10):面板文字大小。
- RefreshMS(默认 500):面板刷新间隔,单位毫秒。
怎么用
将代码保存为 .mq5 文件,放入 MT5 的 MQL5/Experts 文件夹,用 MetaEditor 编译后拖到任意图表上(只操作拖入图表对应的品种),勾选"允许自动交易"。面板会显示在图表左上角,拖动标题栏可移动,拖动右下角手柄可调整大小。
- 查看统计:面板实时显示当前品种的多空手数、订单数、总盈亏和手数限制状态(灰色正常,黄色接近限制,红色超限)。
- 一键平仓:点击「一键平仓」「一键平多」「一键平空」按钮,弹窗二次确认后执行。
- 移动止盈:点击「移动止盈」按钮启用(按钮变亮绿色并显示 ●),EA 自动追踪价格调整止损;再次点击停用。
- 固定止损:点击后弹窗确认,为所有持仓设置固定点数止损。
- 挂单价止盈:点击后弹窗确认,对盈利订单将止损移至开仓价。
EA 只统计和操作挂载图表的品种,其他品种不受影响。
适用场景
- 多策略叠加交易:同时运行多个 EA 或手动加仓时,用总手数限制防止仓位失控。
- 日内短线或剥头皮:快速开平仓时,用移动止盈和一键平仓提高操作效率。
- 品种不限:适用于任何 MT5 交易品种,但点数参数需根据品种和周期调整(如外汇、黄金、指数的点值差异大)。
- 周期不限:EA 不依赖 K 线数据,任意周期图表均可使用。
不适合无法容忍自动平仓的场景(如长线持仓、网格策略希望保留所有订单)。若需手动控制每笔平仓,将 AutoCloseLimitExcess 设为 false。
风险提示
- 自动平仓不可逆:启用
AutoCloseLimitExcess后,超限订单会立即被平掉,无法恢复。建议初次使用时先在模拟盘测试限制值是否合理。 - 按时间平仓可能不是最优:代码按开仓时间从新到旧平仓,不考虑盈亏或品种。如果需要按盈亏或其他规则平仓,需修改
CloseExcessPositions函数。 - 移动止盈可能过早平仓:
TrailingStopPoints设置过小会导致价格小幅回撤时触发止损,截断盈利。根据品种波动性和交易周期调整点数(波动大的品种如黄金可能需要更大的跟踪距离)。 - 点数参数需校准:外汇 5 位报价品种(如 EURUSD)的 1 点是 0.00001,4 位品种(如 USDJPY)是 0.01,黄金可能是 0.01 或 0.1。参数中的"点"是 MT5 的 Point 单位,不同品种含义不同,需根据实际报价调整数值。
- 滑点和延迟:一键平仓和自动超限平仓都使用市价单,在快速行情或流动性差的品种上可能产生滑点。
- 仅操作当前品种:EA 只统计和平仓挂载图表的品种,不管理账户内其他品种的持仓。如果需要全账户手数限制,需修改代码去掉品种过滤。
- 不保证订单一定能平:平仓操作可能因网络延迟、经纪商限制、流动性不足等原因失败,代码会在日志中记录失败订单但不会重试。
常见问题
- 这个 EA 会自动开仓吗
- 不会。它只负责监控和管理现有持仓,不会主动开新单。所有开仓由用户手动操作或其他 EA 执行。
- 手数限制功能会限制其他品种吗
- 不会。EA 只统计和限制挂载图表对应品种的持仓,账户中其他品种不受影响。如果需要限制全账户手数,需修改代码去掉 `POSITION_SYMBOL != _Symbol` 的过滤。
- 移动止盈会重绘或取消已设置的止损吗
- 移动止盈只会向保护浮盈的方向移动止损(多单向上,空单向下),不会放宽或取消止损。如果当前止损已经比新计算的更有利,EA 不会修改。
- 默认参数适合什么品种和周期
- 默认值(移动止盈 300 点、固定止损 500 点)以 5 位报价的外汇品种(如 EURUSD)为参考,大致对应 30 / 50 个基点。黄金等波动大的品种需要成倍增加点数,4 位报价品种需要除以 10。周期不限,但日内短线建议缩小点数,长线则放大。
- 超限自动平仓后能恢复订单吗
- 不能。平仓操作不可逆,被平掉的订单无法恢复。建议先在模拟账户测试 MaxTotalLots 和 AutoCloseLimitExcess 的组合,确认符合预期再用于实盘。
Position Limit Controller EA for MT5
An EA that displays real-time position statistics, monitors total lot size, automatically closes excess positions when limits are reached, and provides one-click close, trailing stop, fixed stop-loss, and break-even functions for MT5 multi-strategy risk management.
Source code is shown above. Compiled EX5 files are available to VIP members.
评论
还没有人评论,来抢个沙发。