Ver no GitHub

Cs2011 Strategy

Overview

The Cs2011 strategy is a reversal system translated from the original cs2011.mq5 expert advisor. It monitors the MACD histogram and signal line on every finished candle and searches for exhaustion patterns around the zero line. The C# port keeps the core timing rules while exposing them through the high level StockSharp API.

Trading logic

  • Zero line reversals – when the MACD value from the previous bar is above zero while the bar before that was below zero, the strategy issues a short signal. The opposite transition (from positive to negative) issues a long signal. This mimics the contrarian entries implemented in the MQL5 script.
  • Signal line extremes – the strategy stores the last three signal-line readings. A local maximum while MACD stayed negative triggers an additional short entry; a local minimum while MACD stayed positive triggers a long entry. This reproduces the pattern checks based on Sig[0], Sig[1] and Sig[2] in the source EA.
  • Signals are evaluated only on finished candles supplied by SubscribeCandles, so partial data is ignored.

Position handling

  • The strategy targets a fixed absolute position size (TargetVolume). When a bullish signal arrives it buys enough contracts to reach +TargetVolume. Bearish signals do the same for -TargetVolume. Existing exposure in the same direction is respected – no additional orders are placed if the target is already reached.
  • StartProtection mirrors the original take-profit and stop-loss settings. Point distances are converted into UnitTypes.Point values and passed to the built-in risk module. Leaving either value at 0 disables the corresponding barrier.
  • High level helpers (BuyMarket, SellMarket) are used instead of the low level request structure from the MQL version.

Parameters

Name Default Description
TargetVolume 1 lot Absolute position size achieved after a signal. Replaces the Risk × balance sizing routine from the EA.
TakeProfitPoints 2200 Distance in price points for take-profit management. 0 disables the take-profit.
StopLossPoints 0 Distance in price points for the stop-loss. 0 disables the stop-loss, matching the EA defaults.
FastEmaPeriod 30 Fast EMA length for the MACD core.
SlowEmaPeriod 500 Slow EMA length for MACD.
SignalPeriod 36 Signal line smoothing period.
CandleType 1 hour time frame Candle source used by SubscribeCandles. Adjust this to match the chart period used in MetaTrader.

All parameters are registered through Param() so they can be optimized inside the StockSharp optimizer UI.

Differences from the MQL5 version

  • The money-management routine (Money_M) relied on historical deals and the MetaTrader account balance. StockSharp strategies operate on broker-agnostic portfolios, therefore the port exposes a simple TargetVolume parameter. Users can connect their own money management by overriding the parameter value or the ExecuteSignals method.
  • Order requests are simplified to single market orders. Re-try logic, spread-based deviation and trade context checks are handled by StockSharp infrastructure.
  • The strategy runs on candle subscriptions instead of the custom IsNewBar helper. This guarantees that only fully formed candles are processed.

Usage notes

  1. Configure the security, portfolio and candle type before launching the strategy.
  2. Tune TargetVolume to match the desired nominal lot size.
  3. Optionally adjust TakeProfitPoints and StopLossPoints to reproduce the protective levels from the original EA.
  4. Start the strategy – logging messages record every trade trigger along with the targeted exposure.

The code contains English inline comments describing each step of the porting process.


namespace StockSharp.Samples.Strategies;

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

/// <summary>
/// MACD based reversal strategy converted from the original cs2011 MetaTrader 5 expert advisor.
/// It reacts to zero line crosses and local extremes of the MACD signal line.
/// </summary>
public class Cs2011Strategy : Strategy
{
	private readonly StrategyParam<decimal> _targetVolume;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _macdPrev1;
	private decimal? _macdPrev2;
	private decimal? _signalPrev1;
	private decimal? _signalPrev2;
	private decimal? _signalPrev3;

	/// <summary>
	/// Target absolute position in lots when a bullish signal appears.
	/// </summary>
	public decimal TargetVolume
	{
		get => _targetVolume.Value;
		set => _targetVolume.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in instrument points.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in instrument points.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Fast EMA length used in MACD calculation.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA length used in MACD calculation.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

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

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters with defaults close to the original expert.
	/// </summary>
	public Cs2011Strategy()
	{
		_targetVolume = Param(nameof(TargetVolume), 1m)
			.SetDisplay("Target Volume", "Absolute position size targeted on entries", "Risk")
			
			.SetOptimize(0.5m, 3m, 0.5m);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2200)
			.SetDisplay("Take Profit (points)", "Take-profit distance in price points", "Risk")
			
			.SetOptimize(200, 4000, 200);

		_stopLossPoints = Param(nameof(StopLossPoints), 0)
			.SetDisplay("Stop Loss (points)", "Stop-loss distance in price points", "Risk")
			
			.SetOptimize(0, 2000, 200);

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 30)
			.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators")
			
			.SetOptimize(10, 60, 5);

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 50)
			.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators")
			
			.SetOptimize(200, 700, 20);

		_signalPeriod = Param(nameof(SignalPeriod), 36)
			.SetDisplay("Signal Period", "Signal line period for MACD", "Indicators")
			
			.SetOptimize(10, 60, 5);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Source timeframe for MACD", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_macdPrev1 = null;
		_macdPrev2 = null;
		_signalPrev1 = null;
		_signalPrev2 = null;
		_signalPrev3 = null;
	}

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

		Volume = TargetVolume;

		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = FastEmaPeriod },
				LongMa = { Length = SlowEmaPeriod }
			},
			SignalMa = { Length = SignalPeriod }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(macd, ProcessCandle)
			.Start();

		var chartArea = CreateChartArea();
		if (chartArea != null)
		{
			DrawCandles(chartArea, subscription);
			DrawIndicator(chartArea, macd);
			DrawOwnTrades(chartArea);
		}

		// removed StartProtection
	}

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

		if (indicatorValue is not IMovingAverageConvergenceDivergenceSignalValue macdValue)
			return;

		if (macdValue.Macd is not decimal macd || macdValue.Signal is not decimal signal)
			return;

		var prevMacd1 = _macdPrev1;
		var prevMacd2 = _macdPrev2;
		var prevSignal1 = _signalPrev1;
		var prevSignal2 = _signalPrev2;
		var prevSignal3 = _signalPrev3;

		var upSignal = false;
		var downSignal = false;

		if (prevMacd1.HasValue && prevMacd2.HasValue)
		{
			if (prevMacd1 > 0m && prevMacd2 < 0m)
				downSignal = true;

			if (prevMacd1 < 0m && prevMacd2 > 0m)
				upSignal = true;
		}

		if (prevMacd2.HasValue && prevSignal1.HasValue && prevSignal2.HasValue && prevSignal3.HasValue)
		{
			if (prevMacd2 < 0m && prevSignal1 < prevSignal2 && prevSignal2 > prevSignal3)
				downSignal = true;

			if (prevMacd2 > 0m && prevSignal1 > prevSignal2 && prevSignal2 < prevSignal3)
				upSignal = true;
		}

		if (upSignal || downSignal)
			ExecuteSignals(upSignal, downSignal);

		_macdPrev2 = _macdPrev1;
		_macdPrev1 = macd;
		_signalPrev3 = _signalPrev2;
		_signalPrev2 = _signalPrev1;
		_signalPrev1 = signal;
	}

	private void ExecuteSignals(bool upSignal, bool downSignal)
	{
		// removed IsFormedAndOnlineAndAllowTrading guard

		if (upSignal)
		{
			var targetPosition = TargetVolume;
			var difference = targetPosition - Position;
			if (difference > 0m)
			{
				BuyMarket(difference);
				LogInfo($"Buy signal executed. Target={targetPosition:F2}, current position after order={Position + difference:F2}");
			}
		}

		if (downSignal)
		{
			var targetPosition = -TargetVolume;
			var difference = targetPosition - Position;
			if (difference < 0m)
			{
				SellMarket(-difference);
				LogInfo($"Sell signal executed. Target={targetPosition:F2}, current position after order={Position - difference:F2}");
			}
		}
	}
}