Открыть на GitHub

Sweet Spot Extreme стратегия

Sweet Spot Extreme — перенос эксперта MetaTrader 4 «Sweet_Spot_Extreme.mq4» на высокоуровневый API StockSharp. Алгоритм ищет резкие откаты внутри сформированного тренда: две экспоненциальные средние на 15-минутных свечах подтверждают направление, а 30-минутный индикатор CCI фильтрует точки входа. Расчёт объёма повторяет логику MQL, включая уменьшение лота после серии убытков.

Основная логика

  1. Подтверждение тренда. Главная EMA (MaPeriod, по умолчанию 85) и EMA сглаживания закрытия (CloseMaPeriod, 70) получают медианные цены 15-минутных свечей. Для покупок обе средние должны расти, для продаж — снижаться.
  2. Фильтр перепроданности/перекупленности. Отдельная подписка на свечи (30 минут по умолчанию) рассчитывает CCI с периодом CciPeriod. Лонг возможен только при значении ниже BuyCciLevel (−200), шорт — выше SellCciLevel (+200).
  3. Ограничение пирамидинга. Совокупная позиция не превышает MaxTradesPerSymbol × объём. При новом сигнале стратегия закрывает встречную позицию и добавляет объём в сторону сигнала в пределах лимита.
  4. Выходы. Сделка закрывается при смене наклона трендовой EMA (аналог условия MQL MA <= MAprevious) либо после прохождения цены на StopPoints пунктов в прибыльную сторону.

Управление риском

  • Размер позиции по доле капитала. Базовый объём рассчитывается как Portfolio.CurrentValue × MaximumRisk ÷ цена. При отсутствии данных по капиталу используется параметр Lots (или свойство Volume).
  • Снижение после убытков. После двух и более убыточных сделок объём уменьшается на объём × число_убытков ÷ DecreaseFactor, что повторяет функцию LotsOptimized() в оригинале.
  • Нормализация. Итоговый объём приводится к шагу инструмента (VolumeStep), ограничивается снизу значением MinVolume и, при наличии, верхним пределом Security.MaxVolume.

Параметры

Параметр Значение по умолчанию Описание
MaxTradesPerSymbol 3 Максимальное число усреднений в одном направлении (по совокупной позиции).
Lots 1 Фиксированный объём, если оценка капитала недоступна.
MaximumRisk 0.05 Доля капитала для расчёта нового объёма.
DecreaseFactor 6 Делитель, уменьшающий следующий лот после серии убытков.
StopPoints 10 Дистанция тейк-профита в пунктах инструмента (0 отключает).
MaPeriod 85 Период EMA на 15-минутных свечах для определения наклона тренда.
CloseMaPeriod 70 Период EMA на 15-минутных свечах для сглаживания цены закрытия.
CciPeriod 12 Период CCI на 30-минутных свечах.
BuyCciLevel -200 Порог перепроданности для входа в лонг.
SellCciLevel 200 Порог перекупленности для входа в шорт.
MinVolume 0.1 Минимально допустимый объём после нормализации.
TrendCandleType 15m Тип свечей для расчёта EMA (медианная цена).
CciCandleType 30m Тип свечей для расчёта CCI.

Особенности и ограничения

  • StockSharp использует неттинговую модель: несколько ордеров MT4 отображаются как одна совокупная позиция. Поэтому MaxTradesPerSymbol ограничивает именно общий объём, а не количество отдельных заявок.
  • В оригинале использовалось AccountFreeMargin. Здесь вместо него применяется Portfolio.CurrentValue. При необходимости подберите MaximumRisk или Lots под специфику брокера.
  • Убедитесь, что поставщик данных отдаёт оба таймфрейма (15 и 30 минут). Без завершённых значений EMA или CCI стратегия не будет торговать.
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;
	}
}