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

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

我们已经学会了如何下单,但现在面临一个更精细的问题:如何精确地设置我们的止损(SL)和止盈(TP)?

最常见的方式,就是基于开仓价设置一个固定的“点数(Pips)”距离。比如,一个50 Pips的止损。听起来很简单,但在编程世界里,这背后藏着一个巨大的陷阱。

噩梦的开始:我的50 Pips止损,为什么变成了5 Pips?

想象一个场景:你写好了一个EA,在参数里设置了StopLossPips = 50。你在你的4位报价模拟账户上测试,完美运行。但当你的朋友把它加载到一个5位报价的平台上时,你的EA瞬间就被止损了。复盘一看,一个期望的50 Pips止损,竟然变成了一个微不足道的5 Pips止损。

问题出在哪里?出在了MQL4的内置变量Point上。

Point代表的是价格的最小变动单位

  • 在4位报价平台 (如EURUSD 1.2345),Point = 0.0001,这正好是1 Pip。
  • 但在5位报价平台 (如EURUSD 1.23456),Point = 0.00001,这只是0.1 Pip!

所以,当你用代码openPrice - (50 * Point)去计算止损时,在5位平台上,实际计算的距离就缩水了10倍!这就是灾难的来源。

我们绝不能要求用户去手动输入500来代表50 Pips,这太不友好了。我们的EA必须足够智能,能够自动适应不同的经纪商环境。

屠龙之技:打造我们的“点差通用翻译器”——PipPoint()函数

为了解决这个问题,我们需要创造一个“通用翻译器”。这个函数的目标很明确:无论平台是4位还是5位,当我需要“1 Pip”的距离时,它总能给我返回正确的值(比如,对非日元货币对,永远返回0.0001)。

// 函数:获取指定品种1 Pip对应的价格值 (兼容4位和5位报价)
double PipPoint(string currencySymbol)
{
    int digits = (int)MarketInfo(currencySymbol, MODE_DIGITS); // 获取品种的小数位数

    // 如果小数位数是奇数(3或5),说明是高精度报价
    if (digits == 3 || digits == 5)
    {
        return(Point * 10); // 1 Pip = 10 Points
    }
    // 否则,是标准报价
    else
    {
        return(Point); // 1 Pip = 1 Point
    }
}

有了这个神器,我们计算止损的方式就升级了:

// 先获取当前品种“1 Pip”的标准化价格值
double onePipValue = PipPoint(Symbol());

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

if (StopLossPips > 0)
{
    // 正确的计算方式:用我们标准化的1 Pip值去乘以点数
    stopLossPrice = openPrice - (StopLossPips * onePipValue);

    // 好习惯:对所有价格计算结果进行规范化,确保精度符合服务器要求
    stopLossPrice = NormalizeDouble(stopLossPrice, Digits);
}

现在,无论在哪种平台,StopLossPips * onePipValue都能精确地计算出用户期望的50 Pips距离。

滑点(Slippage)的陷阱:同样的问题

OrderSend()函数中的slippage参数,其单位也是Point。所以,同样地,我们也需要一个函数来把它从用户输入的Pips,转换成服务器需要的Points。

// 函数:根据用户输入的Pips值,计算OrderSend所需的滑点Points值
int GetSlippageInPoints(string currencySymbol, int slippageInPips)
{
    int digits = (int)MarketInfo(currencySymbol, MODE_DIGITS);

    if (digits == 3 || digits == 5)
    {
        return(slippageInPips * 10);
    }
    else
    {
        return(slippageInPips);
    }
}

在下单时使用:

extern int SlippagePips = 5; // 用户输入期望的5 Pips滑点

// 在下单前,调用函数转换
int slippageForOrderSend = GetSlippageInPoints(Symbol(), SlippagePips);

// 在OrderSend中使用转换后的值
OrderSend(Symbol(), OP_BUY, LotSize, Ask, slippageForOrderSend, ...);

专业级优化:将计算结果存为全局变量

PipPoint()GetSlippageInPoints()的计算结果对于一个特定图表来说,在EA运行期间通常是固定不变的。为了避免在OnTick()中每次都重复计算,影响效率,最优的做法是在OnInit()函数中计算一次,并存入全局变量。

// === 全局变量区 ===
double g_onePipValue;    // 全局变量:存储1 Pip对应的价格值
int    g_slippagePoints; // 全局变量:存储转换后的滑点Points值

// === 用户输入 ===
extern int SlippagePips = 5;
extern int StopLossPips = 50;
// ...其他输入参数...

// --- 初始化函数 ---
int OnInit()
{
   // 在EA启动时,一次性计算并存入全局变量
   g_onePipValue = PipPoint(Symbol());
   g_slippagePoints = GetSlippageInPoints(Symbol(), SlippagePips);

   PrintFormat("EA '%s' 初始化: 1 Pip = %f, Slippage Points = %d",
               MQLInfoString(MQL_PROGRAM_NAME), g_onePipValue, g_slippagePoints);

   return(INIT_SUCCEEDED);
}

// --- 核心逻辑函数 ---
void OnTick()
{
   // ...
   // 计算SL/TP时,直接使用全局变量
   double stopLossPrice = Ask - (StopLossPips * g_onePipValue);
   stopLossPrice = NormalizeDouble(stopLossPrice, Digits);

   // 下单时,直接使用全局变量
   int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, g_slippagePoints, ...);
   // ...
}

// --- 自定义函数定义区 ---
// (需要把上面定义的PipPoint和GetSlippageInPoints函数放在这里)
// ...

学长总结

掌握Pips和Points的转换,是区分一个业余和专业EA开发者的重要分水岭。养成使用类似PipPoint()这样的“适配器”函数来处理所有基于点数计算的习惯,将让你的EA变得更健壮、更专业,能够从容应对任何经纪商的报价环境。

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

评论 抢沙发