计算止损 (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()
计算并存储的单一全局值。