在 GitHub 上查看

MACD Four Colors 2 Martingale

该策略将 MetaTrader 上的 "MACD Four Colors 2 Martingale" 专家顾问迁移到 StockSharp,并完整保留基于 MACD 颜色判读与马丁格尔加仓的交易流程。

策略简介

原始指标会把 MACD 柱状图染成五种颜色。在默认的“新版”配色中,颜色随着 MACD 线处于零轴上方/下方以及向上/向下运动而改变。当颜色从银色跳到黄色(MACD 仍为负值并再次走弱)或者从红色跳到蓝色(MACD 在零轴上方开始回落)时,顾问就会开仓。StockSharp 版本通过重建 MACD 的颜色序列来复制这些信号。

同一时间只允许存在一个方向的持仓篮子。只有当新的成交价能够改善当前篮子的均价(做多需要更低、做空需要更高)时才会加仓。每一次追加订单都会把上一笔成交量乘以参数 LotCoefficient,从而实现原策略的马丁格尔放大。

交易规则

  • 指标处理:使用 MovingAverageConvergenceDivergenceSignal 指标,默认参数 12/26/9。
  • 颜色重建:比较最近两根 MACD 数值。负值上升对应颜色 1(银色),正值上升对应颜色 2(红色),正值下降对应颜色 3(蓝色),负值下降对应颜色 4(黄色)。
  • 做多条件:当颜色从 1 变为 4 且上一根 MACD 仍小于 0 时触发。只有在没有空头仓位且当前价格低于所有既有多头入场价时才会执行买入。
  • 做空条件:当颜色从 2 变为 3 且上一根 MACD 大于 0 时触发。只有在没有多头仓位且当前价格高于所有既有空头入场价时才会执行卖出。
  • 仓位管理:第一笔订单使用 InitialVolume。同一篮子中的后续订单将成交量乘以 LotCoefficient。当系数 ≤ 0 时,乘数被视为关闭。
  • 盈亏控制:每根收盘 K 线都会计算浮动盈亏。达到 TargetProfit 时会平掉所有仓位并重置循环;跌破 MaxDrawdown(亏损阈值)同样会全部平仓并重新开始。和 MQL 版本一样,允许输入负数阈值。
  • 离场方式:除资金目标外没有额外的自动止损,仓位会一直持有直到触发风险限制或人工干预。

参数说明

  • CandleType (DataType,默认 1 小时) – 计算 MACD 的时间框架。
  • InitialVolume (decimal,默认 1) – 篮子中第一笔订单的成交量。
  • LotCoefficient (decimal,默认 2) – 马丁格尔加仓时乘以上一笔成交量的系数。
  • MaxDrawdown (decimal,默认 50) – 以货币计的浮亏阈值。大于 0 时监控 -MaxDrawdown,小于 0 时直接使用该值。
  • TargetProfit (decimal,默认 150) – 以货币计的浮盈目标。输入负值会像原版那样反向比较。
  • FastEmaPeriod (int,默认 12) – MACD 快速 EMA 的周期。
  • SlowEmaPeriod (int,默认 26) – MACD 慢速 EMA 的周期。
  • SignalPeriod (int,默认 9) – MACD 信号线的 EMA 周期。

使用建议

  • 需要交易品种提供 PriceStepStepPrice,策略才能正确换算浮动盈亏。
  • 马丁格尔可能迅速放大仓位,请先确认资金与风险承受能力,再投入真实账户。
  • 启用图表区域后可同时观察 K 线、MACD 指标以及策略成交,便于复核信号。

目录标签

  • 类别:趋势 / 动量均值回归
  • 方向:双向(多头与空头篮子)
  • 指标:MACD
  • 止损:仅限资金目标
  • 时间框架:可配置(默认 1 小时)
  • 复杂度:中等
  • 风险:高(源于马丁格尔加仓)
  • 自动化程度:全自动
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD histogram crossover with martingale volume scaling.
/// Based on the MACD Four Colors 2 Martingale expert advisor.
/// </summary>
public class MacdFourColors2MartingaleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _lotCoefficient;
	private readonly StrategyParam<int> _maxMartingale;

	private MovingAverageConvergenceDivergence _macd;
	private readonly System.Collections.Generic.Queue<decimal> _macdHistory = new();
	private decimal? _prevHistogram;
	private decimal _currentVolume;
	private int _consecutiveLosses;
	private decimal _entryPrice;

	/// <summary>
	/// Type of candles used for MACD analysis.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Period of the fast EMA inside MACD.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Period of the slow EMA inside MACD.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	/// <summary>
	/// Period of the signal line.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	/// <summary>
	/// Multiplier applied after a losing trade.
	/// </summary>
	public decimal LotCoefficient
	{
		get => _lotCoefficient.Value;
		set => _lotCoefficient.Value = value;
	}

	/// <summary>
	/// Maximum number of martingale steps before resetting.
	/// </summary>
	public int MaxMartingale
	{
		get => _maxMartingale.Value;
		set => _maxMartingale.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public MacdFourColors2MartingaleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for MACD analysis", "General");

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 20)
			.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators")
			.SetGreaterThanZero();

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 50)
			.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators")
			.SetGreaterThanZero();

		_signalPeriod = Param(nameof(SignalPeriod), 12)
			.SetDisplay("Signal Period", "Signal line smoothing period", "Indicators")
			.SetGreaterThanZero();

		_lotCoefficient = Param(nameof(LotCoefficient), 1.5m)
			.SetDisplay("Lot Coefficient", "Multiplier after a loss", "Money Management")
			.SetGreaterThanZero();

		_maxMartingale = Param(nameof(MaxMartingale), 5)
			.SetDisplay("Max Martingale", "Maximum consecutive doublings", "Money Management")
			.SetGreaterThanZero();
	}

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

		_prevHistogram = null;
		_currentVolume = Volume > 0 ? Volume : 1;
		_consecutiveLosses = 0;
		_entryPrice = 0;
		_macdHistory.Clear();

		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = FastEmaPeriod },
			LongMa = { Length = SlowEmaPeriod }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_macd, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal macdValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_macdHistory.Enqueue(macdValue);
		while (_macdHistory.Count > SignalPeriod)
			_macdHistory.Dequeue();

		if (!_macd.IsFormed || _macdHistory.Count < SignalPeriod)
		{
			_prevHistogram = null;
			return;
		}

		// Calculate signal line (SMA of MACD)
		var sum = 0m;
		var history = _macdHistory.ToArray();
		foreach (var v in history)
			sum += v;
		var signalValue = sum / history.Length;

		var histogram = macdValue - signalValue;

		if (_prevHistogram is null)
		{
			_prevHistogram = histogram;
			return;
		}

		var crossUp = _prevHistogram < 0 && histogram >= 0;
		var crossDown = _prevHistogram >= 0 && histogram < 0;

		if (crossUp)
		{
			// Check for loss on closing short
			if (Position < 0)
			{
				var pnl = _entryPrice - candle.ClosePrice;
				if (pnl < 0)
				{
					_consecutiveLosses++;
					if (_consecutiveLosses <= MaxMartingale)
						_currentVolume *= LotCoefficient;
				}
				else
				{
					_consecutiveLosses = 0;
					_currentVolume = Volume > 0 ? Volume : 1;
				}

				BuyMarket(Math.Abs(Position) + _currentVolume);
				_entryPrice = candle.ClosePrice;
			}
			else if (Position == 0)
			{
				BuyMarket(_currentVolume);
				_entryPrice = candle.ClosePrice;
			}
		}
		else if (crossDown)
		{
			// Check for loss on closing long
			if (Position > 0)
			{
				var pnl = candle.ClosePrice - _entryPrice;
				if (pnl < 0)
				{
					_consecutiveLosses++;
					if (_consecutiveLosses <= MaxMartingale)
						_currentVolume *= LotCoefficient;
				}
				else
				{
					_consecutiveLosses = 0;
					_currentVolume = Volume > 0 ? Volume : 1;
				}

				SellMarket(Math.Abs(Position) + _currentVolume);
				_entryPrice = candle.ClosePrice;
			}
			else if (Position == 0)
			{
				SellMarket(_currentVolume);
				_entryPrice = candle.ClosePrice;
			}
		}

		_prevHistogram = histogram;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_macd = null;
		_prevHistogram = null;
		_currentVolume = 0;
		_consecutiveLosses = 0;
		_entryPrice = 0;
		_macdHistory.Clear();

		base.OnReseted();
	}
}