在 GitHub 上查看

Martingale MA Breakout 策略 (ID 2861)

策略概述

Martingale MA Breakout 是 MetaTrader 5 专家顾问 Martingale.mq5 的移植版本。策略监控当前价格与高周期移动平均线之间的差距,当偏离超过设定的点数阈值时,顺势开仓,并使用固定的止损、止盈和移动止损进行管理。仓位大小遵循简化的马丁格尔规则:亏损后增加手数,盈利后减少手数。

默认情况下,所有指标都基于 6 分钟 K 线计算,平台本身可以运行在任何基础时间框架上。策略始终使用市价单执行交易。

交易规则

  1. 使用选择的平滑方式、价格类型和位移计算当前 K 线的移动平均值。
  2. DistanceFromMaPips 指定的点数转换为绝对价格差。点值的计算模拟了原始 MQL 处理逻辑:当合约精度为 3 或 5 位小数时,价格步长会被乘以 10。
  3. 每当 K 线收盘时:
    • 如果收盘价高于位移后的均线超过指定点数,且当前没有多头敞口,则以市价买入。
    • 如果收盘价低于位移后的均线超过指定点数,且当前没有空头敞口,则以市价卖出。
  4. 在每根完成的 K 线上,策略都会检查止损/止盈触发情况,并根据 TrailingStopPipsTrailingStepPips 更新移动止损。当仓位关闭后,会调用 ResetTradeState 清空所有缓存的保护价位。

资金管理

  • RiskPercent 定义了每笔交易允许承担的资金风险。策略使用 Portfolio.CurrentValue(如果尚无成交则使用 BeginValue)作为账户权益,结合止损距离和合约乘数估算可以承受的最大成交量。
  • 在风险仓位计算完成后,ApplyMartingale 会进一步调整仓位:如果上一笔交易后记录的余额高于当前余额,仓位增加 1 手;反之如果余额提升且当前仓位大于 1 手,则减少 1 手。最终仓位不会低于策略基础手数。
  • 移动止损完全复刻原始 EA:只有当浮动盈利超过 TrailingStopPips + TrailingStepPips 时才会上移/下移止损,使得止损与现价保持 TrailingStopPips 的距离。策略在启动阶段会验证当 TrailingStopPips 非零时 TrailingStepPips 必须大于零,否则抛出异常。

参数说明

参数 说明
StopLossPips 止损距离(点)。设置为 0 时禁用止损及风险计算。
TakeProfitPips 止盈距离(点)。设置为 0 时禁用止盈。
TrailingStopPips 移动止损的固定偏移(点)。需要与 TrailingStepPips 配合使用。
TrailingStepPips 每次调整移动止损之前价格必须额外移动的距离。启用移动止损时不能为 0。
DistanceFromMaPips 触发开仓所需的最小均线偏离点数。
CandleType 用于计算的 K 线类型(默认 6 分钟)。
MaPeriod 移动平均线周期。
MaShift 移动平均线向右平移的柱数,策略内部维护最小缓冲区实现该功能。
MaMethod 移动平均线类型:Simple、Exponential、Smoothed、Weighted。
MaAppliedPrice 参与均线计算的价格类型:收盘、开盘、最高、最低、中值、典型价或加权价。
RiskPercent 占用账户权益的风险百分比。

实现细节

  • 策略仅在 K 线收盘后处理信号,从而模拟原 EA “仅在新柱上工作” 的行为。BuyMarketSellMarket 会自动加上反向头寸的绝对值,实现仓位翻转。
  • 由于高层 API 不直接托管止损/止盈,代码内部会根据收盘价模拟执行。若收盘价触发保护价位,策略会立即通过市价单平仓。
  • 马丁格尔逻辑使用在上一次开仓后保存的余额,与当前余额做对比判断是加仓还是减仓。
  • 当合约缺少有效的价格步长或乘数时,策略会使用默认值 0.00011 避免计算异常。

与原始 EA 的差异

  • 原版 EA 使用实时的买卖价,本移植版使用 K 线的收盘价作为近似值,因为在高层 API 中无法获取逐笔报价。
  • 风险计算基于组合权益与合约乘数,替代了 MQL 中的 CMoneyFixedMargin 工具类。
  • 可选的图形输出仅绘制价格 K 线;默认情况下不会额外绘制指标。
  • TrailingStepPips 的合法性在策略启动时校验,并通过抛出异常阻止配置错误继续运行。
namespace StockSharp.Samples.Strategies;

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Martingale MA Breakout strategy (simplified).
/// Enters long when price crosses above EMA, enters short when below.
/// Uses simple position flipping with market orders.
/// </summary>
public class MartingaleMaBreakoutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _atrPeriod;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	public MartingaleMaBreakoutStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Source candles", "General");

		_maPeriod = Param(nameof(MaPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average period", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var ema = new ExponentialMovingAverage { Length = MaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, atr, (ICandleMessage candle, decimal emaValue, decimal atrValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var distance = Math.Abs(candle.ClosePrice - emaValue);

				// Buy when price breaks above MA by at least half ATR
				if (candle.ClosePrice > emaValue && distance > atrValue && Position <= 0)
				{
					BuyMarket();
				}
				// Sell when price breaks below MA by at least half ATR
				else if (candle.ClosePrice < emaValue && distance > atrValue && Position >= 0)
				{
					SellMarket();
				}
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ema);
			DrawIndicator(area, atr);
			DrawOwnTrades(area);
		}
	}
}