Ver en GitHub

Starter V6 Mod Strategy (StockSharp Conversion)

Overview

The Starter V6 Mod strategy is a StockSharp high-level API conversion of the MetaTrader 5 Expert Advisor Starter_v6mod. The original system combines a Laguerre RSI oscillator, dual exponential moving averages, a commodity channel index filter, and a grid-style position management module. This port preserves the multi-layered confirmation logic while adapting position handling, money management, and protective actions to the StockSharp environment.

Trading Logic

Indicators

  • Laguerre RSI proxy – modeled via a normalized 14-period RSI to emulate the 0-1 scale used by the original Laguerre oscillator. The level pair LevelDown / LevelUp (default 0.15 / 0.85) defines oversold and overbought zones.
  • Slow EMA (120) and Fast EMA (40) – both calculated on the median candle price. Their relative displacement acts as a trend direction filter. The AngleThreshold parameter converts the EMA spread into a tick distance that gates trading directions.
  • Commodity Channel Index (14) – confirms momentum direction by requiring negative values for long entries and positive values for short entries.

Entry Conditions

  1. Determine the trend bias from the EMA spread:
    • If the slow EMA minus the fast EMA is less than -AngleThreshold ticks, only long positions may be initiated.
    • If the spread is greater than AngleThreshold, only shorts may be initiated.
    • Otherwise, the market is considered flat and no new positions are opened.
  2. When the trend bias allows a direction, check the oscillator and momentum filters:
    • Long setup – Laguerre proxy below LevelDown, slow EMA < previous slow EMA, fast EMA < previous fast EMA, and CCI < 0.
    • Short setup – Laguerre proxy above LevelUp, slow EMA > previous slow EMA, fast EMA > previous fast EMA, and CCI > 0.
  3. Grid spacing – when stacking positions in the same direction, the current price must be at least GridStepPips below the lowest long entry or above the highest short entry. This replicates the averaging logic of the original EA.
  4. Position count – the total number of simultaneous grid entries cannot exceed MaxOpenTrades.

Exit Conditions

  • Laguerre exits – longs close when the oscillator crosses above LevelUp; shorts close when it falls below LevelDown.
  • Stop-loss / Take-profit – expressed in pips, converted to instrument price increments. The conversion tracks the original adjustment for symbols with 3/5 decimal pricing.
  • Trailing stop – activates after price advances by (TrailingStopPips + TrailingStepPips) and then follows price with an offset of TrailingStopPips.
  • Friday protections – no new trades are allowed after 18:00 (terminal time) and all open positions are liquidated after 20:00.

Money Management

  • Volume sizing – either fixed (UseManualVolume = true) or risk-based. In risk mode, volume equals (equity * RiskPercent) / (StopLoss distance in price units).
  • Equity cutoff – trading stops when the current equity falls below EquityCutoff.
  • Daily loss limit – if the strategy records MaxLossesPerDay losing exits on the current date, no further positions are opened.
  • Loss recovery – after each losing exit, the next position size is divided by DecreaseFactor^lossesToday, mirroring the original position scaling logic.

Implementation Notes

  • The conversion uses the StockSharp high-level SubscribeCandles().Bind(...) pipeline to stream finished candles and indicator values into the decision logic.
  • StockSharp does not ship a native Laguerre RSI, so a normalized RSI is used as a proxy. The thresholds match the 0-1 Laguerre range.
  • The EMA angle filter is reproduced by measuring the spread between the slow and fast EMA values in ticks, providing a directional gate similar to the original emaangle custom indicator.
  • Manual stop and trailing management are performed within the candle processing routine to maintain parity with the MQL trailing modifications.
  • Grid bookkeeping tracks average entry price, lowest/highest fill price, and trailing levels to emulate the MQL multi-position workflow while working within StockSharp’s aggregated position model.

Parameters

Name Default Description
UseManualVolume false Toggle between fixed and risk-based position sizing.
ManualVolume 1 Volume used when manual sizing is enabled or risk sizing cannot be computed.
RiskPercent 5 Percentage of equity risked per trade when automatic sizing is active.
StopLossPips 35 Stop-loss distance in pips.
TakeProfitPips 10 Take-profit distance in pips.
TrailingStopPips 0 Trailing stop distance in pips (0 disables trailing).
TrailingStepPips 5 Minimum advance before the trailing stop begins to follow price.
DecreaseFactor 1.6 Factor applied to reduce size after each loss.
MaxLossesPerDay 3 Maximum losing exits permitted per calendar day.
EquityCutoff 800 Equity threshold that halts new trades.
MaxOpenTrades 10 Maximum number of simultaneous grid entries.
GridStepPips 30 Minimum spacing between stacked entries in the same direction.
LongEmaPeriod 120 Period of the slow EMA filter.
ShortEmaPeriod 40 Period of the fast EMA filter.
CciPeriod 14 Commodity Channel Index period.
AngleThreshold 3 EMA spread threshold expressed in ticks.
LevelUp 0.85 Upper Laguerre level.
LevelDown 0.15 Lower Laguerre level.
CandleType 15m Candle timeframe used for calculations.

Usage Tips

  1. Configure the CandleType parameter to match the timeframe used in the original MT5 setup (the EA is often deployed on 15-minute charts).
  2. Align risk settings with account specifications. When using risk-based sizing, ensure StopLossPips reflects the instrument’s volatility because it directly affects calculated volume.
  3. Review exchange trading hours. The built-in Friday protection assumes the server clock aligns with the desired session close.
  4. Enable chart drawing (via CreateChartArea) to visualize EMA, RSI proxy, CCI, and executed trades for debugging or optimization.
  5. When porting parameter sets from MT5 backtests, remember that the RSI proxy approximates the Laguerre oscillator; minor threshold tuning may be needed to match original signal timing.

Files

  • CS/StarterV6ModStrategy.cs – StockSharp strategy implementation.
  • README.md – English documentation (this file).
  • README_zh.md – Simplified Chinese documentation.
  • README_ru.md – Russian documentation.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Conversion of the Starter_v6mod Expert Advisor using the high-level StockSharp API.
/// </summary>
public class StarterV6ModStrategy : Strategy
{
	private readonly StrategyParam<bool> _useManualVolume;
	private readonly StrategyParam<decimal> _manualVolume;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<decimal> _decreaseFactor;
	private readonly StrategyParam<int> _maxLossesPerDay;
	private readonly StrategyParam<decimal> _equityCutoff;
	private readonly StrategyParam<int> _maxOpenTrades;
	private readonly StrategyParam<int> _gridStepPips;
	private readonly StrategyParam<int> _longEmaPeriod;
	private readonly StrategyParam<int> _shortEmaPeriod;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _angleThreshold;
	private readonly StrategyParam<decimal> _levelUp;
	private readonly StrategyParam<decimal> _levelDown;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _longEma;
	private ExponentialMovingAverage _shortEma;
	private CommodityChannelIndex _cci;
	private RelativeStrengthIndex _laguerreProxy;

	private decimal? _prevLongEma;
	private decimal? _prevShortEma;

	/// <summary>
	/// Use manual volume instead of risk calculation.
	/// </summary>
	public bool UseManualVolume
	{
		get => _useManualVolume.Value;
		set => _useManualVolume.Value = value;
	}

	/// <summary>
	/// Manual volume for each new entry.
	/// </summary>
	public decimal ManualVolume
	{
		get => _manualVolume.Value;
		set => _manualVolume.Value = value;
	}

	/// <summary>
	/// Risk percentage used when position sizing is automatic.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public int TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Additional distance required before the trailing stop starts to follow the price.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Multiplier used to reduce the position size after losses.
	/// </summary>
	public decimal DecreaseFactor
	{
		get => _decreaseFactor.Value;
		set => _decreaseFactor.Value = value;
	}

	/// <summary>
	/// Maximum number of losing trades allowed per day.
	/// </summary>
	public int MaxLossesPerDay
	{
		get => _maxLossesPerDay.Value;
		set => _maxLossesPerDay.Value = value;
	}

	/// <summary>
	/// Equity threshold below which the strategy stops opening new trades.
	/// </summary>
	public decimal EquityCutoff
	{
		get => _equityCutoff.Value;
		set => _equityCutoff.Value = value;
	}

	/// <summary>
	/// Maximum number of simultaneously opened grid positions.
	/// </summary>
	public int MaxOpenTrades
	{
		get => _maxOpenTrades.Value;
		set => _maxOpenTrades.Value = value;
	}

	/// <summary>
	/// Grid step in pips used when stacking positions.
	/// </summary>
	public int GridStepPips
	{
		get => _gridStepPips.Value;
		set => _gridStepPips.Value = value;
	}

	/// <summary>
	/// Period for the slow EMA trend filter.
	/// </summary>
	public int LongEmaPeriod
	{
		get => _longEmaPeriod.Value;
		set => _longEmaPeriod.Value = value;
	}

	/// <summary>
	/// Period for the fast EMA trend filter.
	/// </summary>
	public int ShortEmaPeriod
	{
		get => _shortEmaPeriod.Value;
		set => _shortEmaPeriod.Value = value;
	}

	/// <summary>
	/// Period for the CCI momentum filter.
	/// </summary>
	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	/// <summary>
	/// Threshold in ticks for the EMA spread trend detector.
	/// </summary>
	public decimal AngleThreshold
	{
		get => _angleThreshold.Value;
		set => _angleThreshold.Value = value;
	}

	/// <summary>
	/// Upper Laguerre RSI level.
	/// </summary>
	public decimal LevelUp
	{
		get => _levelUp.Value;
		set => _levelUp.Value = value;
	}

	/// <summary>
	/// Lower Laguerre RSI level.
	/// </summary>
	public decimal LevelDown
	{
		get => _levelDown.Value;
		set => _levelDown.Value = value;
	}

	/// <summary>
	/// Candle data type used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="StarterV6ModStrategy"/> class.
	/// </summary>
	public StarterV6ModStrategy()
	{
		_useManualVolume = Param(nameof(UseManualVolume), true)
		.SetDisplay("Manual Volume", "Use manual volume instead of risk-based sizing", "Money Management");

		_manualVolume = Param(nameof(ManualVolume), 1m)
		.SetRange(0.01m, 100m)
		.SetDisplay("Volume", "Manual volume per trade", "Money Management")
		;

		_riskPercent = Param(nameof(RiskPercent), 5m)
		.SetRange(0.5m, 20m)
		.SetDisplay("Risk %", "Risk percentage when auto-sizing trades", "Money Management")
		;

		_stopLossPips = Param(nameof(StopLossPips), 35)
		.SetRange(0, 500)
		.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Risk Management");

		_takeProfitPips = Param(nameof(TakeProfitPips), 10)
		.SetRange(0, 500)
		.SetDisplay("Take Profit", "Take-profit distance in pips", "Risk Management");

		_trailingStopPips = Param(nameof(TrailingStopPips), 0)
		.SetRange(0, 500)
		.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk Management");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
		.SetRange(0, 500)
		.SetDisplay("Trailing Step", "Additional distance before trailing activates", "Risk Management");

		_decreaseFactor = Param(nameof(DecreaseFactor), 1.6m)
		.SetRange(1m, 10m)
		.SetDisplay("Decrease Factor", "Volume reduction factor after losses", "Money Management");

		_maxLossesPerDay = Param(nameof(MaxLossesPerDay), 3)
		.SetRange(0, 20)
		.SetDisplay("Daily Loss Limit", "Maximum number of losses per day", "Risk Management");

		_equityCutoff = Param(nameof(EquityCutoff), 800m)
		.SetRange(0m, 1_000_000m)
		.SetDisplay("Equity Cutoff", "Stop trading if equity drops below this value", "Risk Management");

		_maxOpenTrades = Param(nameof(MaxOpenTrades), 10)
		.SetRange(1, 100)
		.SetDisplay("Max Trades", "Maximum simultaneous grid positions", "General");

		_gridStepPips = Param(nameof(GridStepPips), 30)
		.SetRange(0, 500)
		.SetDisplay("Grid Step", "Minimum pip distance between stacked entries", "General");

		_longEmaPeriod = Param(nameof(LongEmaPeriod), 120)
		.SetRange(10, 400)
		.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
		;

		_shortEmaPeriod = Param(nameof(ShortEmaPeriod), 40)
		.SetRange(5, 200)
		.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
		;

		_cciPeriod = Param(nameof(CciPeriod), 14)
		.SetRange(5, 100)
		.SetDisplay("CCI Period", "CCI indicator length", "Indicators")
		;

		_angleThreshold = Param(nameof(AngleThreshold), 3m)
		.SetRange(0m, 50m)
		.SetDisplay("Angle Threshold", "EMA spread threshold measured in ticks", "Indicators");

		_levelUp = Param(nameof(LevelUp), 0.85m)
		.SetRange(0.1m, 1m)
		.SetDisplay("Laguerre Up", "Upper Laguerre RSI level", "Indicators");

		_levelDown = Param(nameof(LevelDown), 0.15m)
		.SetRange(0m, 0.9m)
		.SetDisplay("Laguerre Down", "Lower Laguerre RSI level", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for analysis", "General");
	}

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

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

		_longEma = null;
		_shortEma = null;
		_cci = null;
		_laguerreProxy = null;
		_prevLongEma = null;
		_prevShortEma = null;
	}

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

		_longEma = new ExponentialMovingAverage { Length = LongEmaPeriod };
		_shortEma = new ExponentialMovingAverage { Length = ShortEmaPeriod };
		_cci = new CommodityChannelIndex { Length = CciPeriod };
		_laguerreProxy = new RelativeStrengthIndex { Length = 14 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_longEma, _shortEma, _cci, _laguerreProxy, ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent));

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _longEma);
			DrawIndicator(area, _shortEma);
			DrawIndicator(area, _cci);
			DrawIndicator(area, _laguerreProxy);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal longEmaValue, decimal shortEmaValue, decimal cciValue, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevLongEma is null || _prevShortEma is null)
		{
			_prevLongEma = longEmaValue;
			_prevShortEma = shortEmaValue;
			return;
		}

		if (Position != 0)
		{
			_prevLongEma = longEmaValue;
			_prevShortEma = shortEmaValue;
			return;
		}

		var laguerre = rsiValue / 100m;

		// Buy: RSI low (oversold), EMAs falling (pullback), CCI negative
		var buySignal = laguerre < LevelDown && cciValue < 0m;

		// Sell: RSI high (overbought), EMAs rising, CCI positive
		var sellSignal = laguerre > LevelUp && cciValue > 0m;

		if (buySignal)
			BuyMarket();
		else if (sellSignal)
			SellMarket();

		_prevLongEma = longEmaValue;
		_prevShortEma = shortEmaValue;
	}
}