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

​MQL4(20):资金管理与动态手数计算

在构建交易策略时,除了精心设计入场、出场、止损和止盈规则外,确定每次交易使用多大的手数是风险管理中至关重要的一环。最简单直接的方法是设置一个固定的手数(例如,通过 extern 变量让用户输入),并在所有交易中都使用这个固定值。然而,这种方法没有考虑到账户资金的变化和单笔交易的潜在风险大小。

接下来的内容中,我们将探讨一种更为先进和专业的风险管理方法:基于风险的动态手数计算。这种方法的核心思想是,根据您预先设定的单笔交易最大可承受风险(通常以账户净值 Equity 的一个百分比来表示,例如 1%、2% 或 3%),结合当前交易的止损距离,来动态计算出本次交易应该使用的合适手数。

过度杠杆是导致账户亏损甚至爆仓的罪魁祸首,采用基于风险的手数计算方法,可以有效地控制单笔亏损对账户整体的影响。普遍推荐的风险控制标准是,确保任何单笔交易在触及止损时的最大亏损额,不超过您当前账户净值的 2-3%。

资金管理与动态手数计算

基于风险动态计算手数其核心思想是:根据您预设的单笔交易愿意承担的最大风险(通常表示为账户净值的一个百分比)以及该笔交易的止损距离(以 pips 为单位)来决定本次下单的手数大小。

计算步骤:

  1. 确定风险参数:

    • 风险百分比: 通过外部变量让用户设定单笔交易最大亏损占账户净值的百分比,例如 2%。
    • 止损点数: 获取当前交易信号对应的实际止损距离(pips)。这通常由交易策略逻辑动态计算得出,或由用户输入。

     

     

    // 输入参数示例
    extern double EquityPercent = 2.0; // 例如,风险设置为 2%
    // 注意:StopLossPips 通常不应是固定输入,而应由策略逻辑根据信号确定
    // extern double StopLossPips  = 50; // 仅为计算演示,实际应是动态
  2. 计算最大可亏损金额:

    • 使用内置函数 AccountEquity() 获取当前账户的实时净值。
    • 计算出以账户货币为单位的最大允许亏损金额:

     

    double riskAmount = AccountEquity() * (EquityPercent / 100.0);
    // 示例: $10,000 净值 * (2.0 / 100.0) = $200
    
  3. 计算每标准手每 Pip 的价值:

    • 理解 MODE_TICKVALUEMarketInfo(Symbol(), MODE_TICKVALUE) 函数返回交易1标准手时,价格每变动一个 Point (最小价格单位) 所对应的账户货币价值。
    • 换算为每 Pip 价值: 由于我们需要基于 Pips 计算手数,并且不同报价精度下 Pip 和 Point 的关系不同 (4位: 1 Pip = 1 Point; 5位: 1 Pip = 10 Points),我们需要将 MODE_TICKVALUE 返回的值统一换算成 “每标准手每波动 1 Pip 的价值”。

     

    double tickValueRaw = MarketInfo(Symbol(), MODE_TICKVALUE);
    double tickValuePerPip = tickValueRaw; // 默认值
    int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
    // 如果是 3 位或 5 位小数报价 (即 1 Pip = 10 Points)
    if (digits == 3 || digits == 5)
    {
        tickValuePerPip = tickValueRaw * 10.0; // 将每 Point 价值乘以 10,得到每 Pip 价值
    }
    // 现在 tickValuePerPip 代表交易 1 标准手时,价格每波动 1 Pip 的盈亏金额 (账户货币单位)
    
  4. 计算动态手数 (calcLots):

    • 公式: (最大允许亏损金额 / 止损点数) / (每标准手每 Pip 的价值)
    • 代码实现:

     

    double calcLots = 0.0;
    double currentStopLossPips = ...; // 获取当前交易的实际止损点数
    
    // 必须确保止损点数和每 Pip 价值都有效(大于0)才能计算
    if (currentStopLossPips > 0 && tickValuePerPip > 0)
    {
        calcLots = (riskAmount / currentStopLossPips) / tickValuePerPip;
    }
    else
    {
        Print("无法计算动态手数:止损点数或TickValue无效!");
        // 此处应有错误处理,例如使用备用手数或放弃交易
    }
    
    • 示例: riskAmount = $200, currentStopLossPips = 50, tickValuePerPip = $10。则 calcLots = ($200 / 50) / $10 = 0.4 标准手。

动态手数优点: 保持单笔风险占净值的比例恒定,有助于控制账户回撤。手数随止损距离反向变化:止损越小,手数越大;止损越大,手数越小。特别适合止损距离相对明确或有逻辑依据的策略。能从较小的止损和/或较大的盈亏比中获益。如果策略使用非常大的止损或不设止损,动态计算出的手数可能过小而失去意义,此时固定手数可能是更好的选择。

结合固定与动态手数模式:

可以在 EA 中设置一个开关,让用户灵活选择。

// --- 输入参数 ---
extern bool   UseDynamicLotSize = true;  // 开关: true=动态手数, false=固定手数
extern double EquityPercent     = 2.0;   // 动态手数参数:风险%
// extern double StopLossPips   = 50;    // 不应是固定输入, 应由策略决定
extern double FixedLotSize      = 0.1;   // 固定手数参数

// --- 在 OnTick() 函数中确定最终手数 ---
double lotSizeToUse = 0.0;
double currentStopLossPips = ...; // 假设已获取当前交易的止损点数

if (UseDynamicLotSize)
{
    // ... 执行上述动态手数计算逻辑,结果存入 lotSizeToUse ...
    // ... 注意包含对 StopLossPips 和 tickValuePerPip 的有效性检查 ...
    if (currentStopLossPips > 0 && tickValuePerPip > 0) {
        lotSizeToUse = (riskAmount / currentStopLossPips) / tickValuePerPip;
    } else {
        Print("动态手数计算失败,使用固定手数替代.");
        lotSizeToUse = FixedLotSize;
    }
}
else // 使用固定手数
{
    lotSizeToUse = FixedLotSize;
}

// --- 对 lotSizeToUse 进行验证和规范化 ---
// ... (见下文) ...

// --- 使用最终验证后的 lotSizeToUse 下单 ---
// OrderSend(..., lotSizeToUse, ...);

验证手数的有效性

计算或指定手数后,必须在调用 OrderSend() 前对其进行严格验证,以确保其符合经纪商的交易限制,否则下单会失败。验证步骤如下:

  1. 对比最小手数: 检查手数是否小于经纪商规定的最小允许交易量。

    • 使用 MarketInfo(Symbol(), MODE_MINLOT) 获取最小手数。

     

    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    if (lotSizeToUse < minLot)
    {
        Print("手数 ", lotSizeToUse, " 过小 (最小: ", minLot, "), 调整为最小值.");
        lotSizeToUse = minLot; // 强制设为最小手数
    }
    
  2. 对比最大手数: 检查手数是否超过经纪商规定的最大单笔交易量。

    • 使用 MarketInfo(Symbol(), MODE_MAXLOT) 获取最大手数。

     

    double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
    if (lotSizeToUse > maxLot)
    {
        Print("手数 ", lotSizeToUse, " 过大 (最大: ", maxLot, "), 调整为最大值.");
        lotSizeToUse = maxLot; // 强制设为最大手数
    }
    
  3. 检查并规范化手数步长: 确保手数符合经纪商要求的最小增量单位(步长),并将其规范化(通常是向下取整到最接近的有效步长倍数,或按指定小数位四舍五入)。

    • 使用 MarketInfo(Symbol(), MODE_LOTSTEP) 获取手数步长 (例如 0.1 或 0.01)。
    • 使用 NormalizeDouble() 进行规范化(四舍五入到指定小数位)。

     

    double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
    int lotDigits = 0; // 用于 NormalizeDouble 的小数位数
    
    if (lotStep == 0.1) lotDigits = 1;       // 迷你手,规范到 1 位小数
    else if (lotStep == 0.01) lotDigits = 2; // 微型手,规范到 2 位小数
    else if (lotStep == 0.001) lotDigits = 3;// (如果未来支持)
    // 可以添加更多 else if 处理其他可能的步长,或基于 log10 计算位数
    
    if (lotDigits > 0) {
        lotSizeToUse = NormalizeDouble(lotSizeToUse, lotDigits);
    }
    // MQL5 更推荐的精确方法: 对齐到步长
    // lotSizeToUse = MathRound(lotSizeToUse / lotStep) * lotStep;
    // 还需要再次检查 lotSizeToUse 是否小于 minLot (因为向下取整可能导致)
    if (lotSizeToUse < minLot) lotSizeToUse = minLot;
    

    注意: NormalizeDouble 是四舍五入。对于手数,有时需要向下取整到符合步长的倍数。MQL5 的 MathRound(value / step) * step 是一种更精确的处理方式,能确保结果是 lotStep 的整数倍。使用 NormalizeDouble 后,或者特别是使用向下取整的方式后,可能需要再次检查结果是否低于 minLot

经过这三步验证和规范化,得到的 lotSizeToUse 才是最终可以安全传递给 OrderSend() 的有效手数。

赞(0)
未经允许不得转载:图道交易 » ​MQL4(20):资金管理与动态手数计算