View on GitHub

Sweet Spot Extreme Strategy

Sweet Spot Extreme is a direct port of the MetaTrader 4 expert advisor "Sweet_Spot_Extreme.mq4" built on StockSharp's high-level API. The strategy hunts for strong pullbacks inside an existing trend by combining two exponential moving averages on 15-minute candles with a 30-minute Commodity Channel Index (CCI) filter. Position sizing mirrors the original risk controls, including MetaTrader-style lot reduction after losing streaks.

Core logic

  1. Trend slope confirmation. The main EMA (MaPeriod, default 85) and the close EMA (CloseMaPeriod, default 70) are fed with 15-minute median prices. A long setup requires both EMAs to slope upward; a short setup needs both to slope downward.
  2. CCI exhaustion filter. A second candle subscription (30-minute by default) powers the CciPeriod CCI. Long trades only fire when CCI dips below BuyCciLevel (−200), while shorts require CCI above SellCciLevel (+200).
  3. Pyramid limit. The aggregated net position cannot exceed MaxTradesPerSymbol × volume. When a fresh signal appears, the strategy closes any opposite exposure and then adds up to the allowed capacity in the signal direction.
  4. Exits. Positions are closed either when the trend EMA loses its slope advantage (mirroring the MQL condition MA <= MAprevious) or after price travels StopPoints instrument points in favour of the position.

Risk management

  • Risk-based volume. Order size defaults to Portfolio.CurrentValue × MaximumRisk ÷ price. When equity information is missing, the engine falls back to the Lots parameter (or the strategy Volume).
  • Loss streak adjustment. After two or more consecutive losing trades the new order size is reduced by volume × losses ÷ DecreaseFactor, matching the MQL helper LotsOptimized().
  • Normalization. The final volume is aligned with the instrument’s VolumeStep, bounded by MinVolume, and clipped by Security.MaxVolume when provided.

Parameters

Name Default Description
MaxTradesPerSymbol 3 Maximum number of aggregated entries allowed per direction.
Lots 1 Fallback fixed lot size when portfolio equity is unavailable.
MaximumRisk 0.05 Fraction of equity used to size each new trade.
DecreaseFactor 6 Divider that shrinks the next order after consecutive losses.
StopPoints 10 Profit target distance in instrument points. Set to 0 to disable.
MaPeriod 85 EMA period applied to 15-minute candles for the trend slope check.
CloseMaPeriod 70 EMA period applied to 15-minute candles for the close smoothing filter.
CciPeriod 12 Lookback used for the 30-minute CCI filter.
BuyCciLevel -200 Oversold CCI threshold required for long entries.
SellCciLevel 200 Overbought CCI threshold required for short entries.
MinVolume 0.1 Minimum volume allowed after normalization.
TrendCandleType 15m Candle type used for EMA calculations (median price).
CciCandleType 30m Candle type used for the CCI filter.

Notes and limitations

  • StockSharp operates in netting mode, so multiple MT4 tickets are represented as a single aggregated position. The MaxTradesPerSymbol guard therefore limits the net exposure instead of counting individual orders.
  • The original EA relied on AccountFreeMargin for sizing. This port approximates it with Portfolio.CurrentValue; adjust MaximumRisk or Lots to fit your broker’s contract specifications.
  • Ensure both candle subscriptions are enabled in the data source, otherwise the EMA or CCI filters will never form and the strategy will stay idle.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// EMA slope + CCI filter strategy.
/// Buys when EMA slope is up and CCI is oversold.
/// Sells when EMA slope is down and CCI is overbought.
/// Exits on EMA slope reversal.
/// </summary>
public class SweetSpotExtremeStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _buyCciLevel;
	private readonly StrategyParam<decimal> _sellCciLevel;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevEma;
	private decimal _prevPrevEma;
	private bool _hasPrevEma;

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	public decimal BuyCciLevel
	{
		get => _buyCciLevel.Value;
		set => _buyCciLevel.Value = value;
	}

	public decimal SellCciLevel
	{
		get => _sellCciLevel.Value;
		set => _sellCciLevel.Value = value;
	}

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

	public SweetSpotExtremeStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetDisplay("EMA Period", "Trend EMA period", "Indicators");

		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetDisplay("CCI Period", "CCI lookback", "Indicators");

		_buyCciLevel = Param(nameof(BuyCciLevel), -50m)
			.SetDisplay("Buy CCI", "Oversold CCI level for buy", "Indicators");

		_sellCciLevel = Param(nameof(SellCciLevel), 50m)
			.SetDisplay("Sell CCI", "Overbought CCI level for sell", "Indicators");

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

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

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

		_prevEma = 0m;
		_prevPrevEma = 0m;
		_hasPrevEma = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevEma = 0;
		_prevPrevEma = 0;
		_hasPrevEma = false;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var cci = new CommodityChannelIndex { Length = CciPeriod };

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

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

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

		if (!_hasPrevEma)
		{
			_prevPrevEma = emaValue;
			_prevEma = emaValue;
			_hasPrevEma = true;
			return;
		}

		var slopeUp = emaValue > _prevEma && _prevEma > _prevPrevEma;
		var slopeDown = emaValue < _prevEma && _prevEma < _prevPrevEma;

		// Entry
		if (slopeUp && cciValue <= BuyCciLevel && Position <= 0)
		{
			BuyMarket();
		}
		else if (slopeDown && cciValue >= SellCciLevel && Position >= 0)
		{
			SellMarket();
		}
		// Exit on slope reversal
		else if (Position > 0 && emaValue < _prevEma)
		{
			SellMarket();
		}
		else if (Position < 0 && emaValue > _prevEma)
		{
			BuyMarket();
		}

		_prevPrevEma = _prevEma;
		_prevEma = emaValue;
	}
}