保持敬畏之心
交易是一场持久战

​MQL4(14):计算止损与止盈价格

计算止损 (SL) 和止盈 (TP) 价格有多种方法。最常用的方法是基于距离开仓价固定点数 (pips) 来设置。例如,设置止损为50pips,意味着实际的止损价格将是开仓价格减去(买单)或加上(卖单)50pips对应的价格距离。

也可以使用技术指标的值(如前N根K线的最高/最低价、ATR 指标计算的距离等)、外部参数直接指定价格,或通过其他复杂的风险管理模型来动态计算。无论用哪种方法,计算出价格后,都需要确保该价格对于特定订单类型是有效的(例如,买单止损价必须低于开仓价),并且满足经纪商的最小止损距离要求 ,这是获取最小止损距离的内置函数(MarketInfo(Symbol(), MODE_STOPLEVEL))。

按点数 (Pips) 计算

这是最常见的方法,我们通常会定义外部输入参数,让用户指定SL和TP的点数 (pips)。

extern int StopLossPips = 50;     // 止损点数 (pips)
extern int TakeProfitPips = 100;  // 止盈点数 (pips)

接下来,我们需要将用户输入的pips值转换为实际的价格。这需要用到内置变量 Point

Point 是一个预定义 double 类型变量,它代表当前交易品种价格的最小单位。

  • 对于4位小数报价的品种 (如 EURUSD 1.2345),Point = 0.0001。
  • 对于2位小数报价的日元对 (如 USDJPY 123.45),Point = 0.01。
  • 对于5位小数报价的品种 (如 EURUSD 1.23456),Point = 0.00001。
  • 对于3位小数报价的日元对 (如 USDJPY 123.456),Point = 0.001。

计算买单止损:

假设我们要为买单计算止损价,开仓价为OpenPrice (对于市价买单即为Ask)。用户设置的止损点数为StopLossPips

double openPrice = Ask; // 买单开仓价
double stopLossPrice = 0;

if (StopLossPips > 0)
{
    // 将pips转换为价格距离:StopLossPips*Point
    // 买单止损价 = 开仓价 - 止损距离
    stopLossPrice = openPrice - (StopLossPips * Point);
    // 对价格进行规范化,确保符合品种的精度
    stopLossPrice = NormalizeDouble(stopLossPrice, Digits);
}
// 现在可以用stopLossPrice变量作为OrderSend 的 StopLoss 参数
// 例如: OrderSend(..., stopLossPrice, ...);

假设 OpenPrice = 1.4600,StopLossPips = 50。

  • 如果经纪商是 4 位报价 (Point = 0.0001),止损距离 = 50 * 0.0001 = 0.0050。stopLossPrice = 1.4600 – 0.0050 = 1.4550。
  • 如果经纪商是 5 位报价 (Point = 0.00001),止损距离 = 50 * 0.00001 = 0.00050。stopLossPrice = 1.46000 – 0.00050 = 1.45950。这显然不是用户期望的50pips 止损,而是变成了5pips!那该怎么办?

处理小数报价,我们自定义个函数:PipPoint() 

为了让用户输入的StopLossPips = 50 无论在哪种报价精度的经纪商那里都代表实际的50pips 距离,我们不能直接使用 Point 变量来乘以pips数量。因为Point反映的是最小变动单位,在5位报价下它代表0.1pips。

直接要求用户在5位报价经纪商那里输入StopLossPips = 500 来表示50pips是不友好且容易出错。因此,我们需要一个方法来获取固定代表1pip的价格值,无论经纪商是4位还是5位(或日元对的2位与3位)。

我们可以引入自定义函数 PipPoint() 。它总是返回对应1pip的价格:对于非日元对返回 0.0001,对于日元对返回 0.01。

// 函数:获取指定品种1pip对应的价格值 (兼容4位和5位报价)
double PipPoint(string currencySymbol)
{
    // 获取品种的小数位数
    int digits = (int)MarketInfo(currencySymbol, MODE_DIGITS); // 强制转为整数
    double pointValue = 0.0;
    // 根据小数位数判断1pip的值
    if (digits == 3 || digits == 5) // 5位报价 或 3位日元对
    {
        pointValue = Point * 10; // 1 pip = 10 points
    }
    else // 4位报价 或 2位日元对
    {
        pointValue = Point; // 1 pip = 1 point
    }
    return(pointValue);
}

使用 PipPoint():

在计算 SL/TP 时,用 PipPoint(Symbol())的返回值来代替 Point 乘以pips 数量(或者说,用PipPoint()返回值代表1pip 的价格值):

double onePipValue = PipPoint(Symbol()); // 获取当前品种 1 pip 的价格值
double openPrice = Ask;
double stopLossPrice = 0;
if (StopLossPips > 0)
{
    // 正确计算 50 pips 距离: StopLossPips * onePipValue
    stopLossPrice = openPrice - (StopLossPips * onePipValue);
    stopLossPrice = NormalizeDouble(stopLossPrice, Digits);
}

这样,无论经纪商是4位还是5位报价,StopLossPips * onePipValue 都能正确计算出50pips对应的价格差。

强烈建议在EA开发中始终使用类似PipPoint()的方法来处理基于pips的计算,以确保在不同报价精度的经纪商那里行为一致。

滑点与Point

OrderSend() 函数中的 slippage 参数的单位是points,即价格的最小变动单位 (Point)。这也意味着,在5位报价经纪商那里,如果想设置允许5pips的滑点,需要传入的 slippage 值应为50。为了自动处理这个问题,我们可以创建一个 GetSlippage() 函数,它接收用户期望的pips值作为输入,并根据当前品种的报价精度返回 OrderSend()所需的points值。

// 函数:根据用户输入的 pips 值和品种精度,计算 OrderSend 所需的滑点 points 值
int GetSlippage(string currencySymbol, int slippageInPips)
{
    int digits = (int)MarketInfo(currencySymbol, MODE_DIGITS);
    int slippageInPoints = 0;
    if (digits == 3 || digits == 5) // 5位报价 或 3位日元对: 1 pip = 10 points
    {
        slippageInPoints = slippageInPips * 10;
    }
    else // 4位报价 或 2位日元对: 1 pip = 1 point
    {
        slippageInPoints = slippageInPips;
    }
    return(slippageInPoints);
}

在 OrderSend() 中使用 GetSlippage():

// 外部输入参数,用户输入期望的 pips 值
extern int SlippagePips = 5; // 用户希望允许 5 pips 滑点

// ... 在下单前调用 GetSlippage 转换 ...
int slippageForOrderSend = GetSlippage(Symbol(), SlippagePips);

OrderSend(Symbol(), OP_BUY, LotSize, Ask, slippageForOrderSend, BuyStopLoss,
          BuyTakeProfit, "Buy Order", MagicNumber, 0, Green);

这样,无论在哪种报价精度的经纪商,用户只需要输入期望的pips值(例如 5),程序会自动计算出 OrderSend() 需要的正确 points 值(5或50)。

将 PipPoint 值和 Slippage Points 值存储为全局变量

如果您的 EA 主要针对单一货币对进行交易,并且 PipPoint()GetSlippage() 的计算结果在 EA 运行期间不会改变(通常是这样),那么为了避免在 start() 函数的每次 tick 中重复计算,可以将这两个关键值在 init() 函数中计算一次,并存储在全局变量中,以提高效率。

// --- 全局变量 ---
double g_onePipValue;    // 存储 1 pip 对应的价格值
int    g_slippagePoints; // 存储转换后的滑点 points 值
extern int SlippagePips = 5; // 用户输入的滑点 pips
extern int StopLossPips = 50; // 止损点数 
extern int TakeProfitPips = 100; //止盈点数
extern double LotSize = 0.1; // 下单手数 
extern int MagicNumber = 123456; // 订单魔术号
// --- 初始化函数 ---
int OnInit()
{
   g_onePipValue = PipPoint(Symbol());
   g_slippagePoints = GetSlippage(Symbol(), SlippagePips);

   // 打印信息供调试或确认
   PrintFormat("EA '%s' 初始化: 交易品种 %s, 1 Pip = %f, Slippage Points = %d",
               WindowExpertName(), Symbol(), g_onePipValue, g_slippagePoints);

   return(INIT_SUCCEEDED);
}

// --- 核心逻辑函数 ---
void OnTick()
{
   // 检查报价是否有效
   if (Ask <= 0 || Bid <= 0) return;
   // 计算 SL/TP 时使用全局变量 g_onePipValue
   double stopLossPrice = Ask - (StopLossPips * g_onePipValue);
   stopLossPrice = NormalizeDouble(stopLossPrice, Digits);
   double takeProfitPrice = Ask + (TakeProfitPips * g_onePipValue);
   takeProfitPrice = NormalizeDouble(takeProfitPrice, Digits);
   // 下单时使用全局变量 g_slippagePoints
   int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, g_slippagePoints,
             stopLossPrice, takeProfitPrice, "Buy Order",MagicNumber, 0, clrGreen);
   // ... 检查 ticket ...

}

// --- 自定义函数定义 ---
// (需要包含 PipPoint 和 GetSlippage 函数的定义)
// ... PipPoint() 函数定义 ...
// ... GetSlippage() 函数定义 ...

这种方法可以提高 OnTick()的执行效率。但请注意,如果您的 EA 设计为需要同时在多个不同报价精度的货币对上交易,或者交易的品种并非EA当前附加的图表品种,那么在每次针对特定品种进行计算或下单时,仍应动态调用 PipPoint()GetSlippage() 函数传入该品种名称,以获取准确的值,而不是依赖于基于 Symbol() 计算并存储的单一全局值。

赞(0)
未经允许不得转载:图道交易 » ​MQL4(14):计算止损与止盈价格