GitHub で見る

Trend Envelopes Strategy

Trend-following strategy built on the TrendEnvelopes indicator. It combines an EMA with ATR-based bands to detect breakouts. Long positions are opened when price breaks above the upper band and a buy signal appears. Short positions are opened on breaks below the lower band with a sell signal. Opposite bands trigger position exits.

Details

  • Entry Criteria:
    • Long: price closes above upper envelope and generates a buy signal
    • Short: price closes below lower envelope and generates a sell signal
  • Long/Short: Both
  • Exit Criteria: Opposite trend signal
  • Stops: Yes (take profit and stop loss)
  • Default Values:
    • MaPeriod = 14
    • Deviation = 0.2m
    • AtrPeriod = 15
    • AtrSensitivity = 0.5m
    • TakeProfit = 2000 points
    • StopLoss = 1000 points
    • CandleType = TimeSpan.FromHours(4).TimeFrame()
  • Filters:
    • Category: Trend following
    • Direction: Both
    • Indicators: EMA, ATR
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: 4h
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium
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>
/// Strategy based on TrendEnvelopes indicator with ATR-based signals.
/// </summary>
public class TrendEnvelopesStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<decimal> _deviation;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrSensitivity;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _buyEntry;
	private readonly StrategyParam<bool> _sellEntry;
	private readonly StrategyParam<bool> _buyExit;
	private readonly StrategyParam<bool> _sellExit;
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;

	private ExponentialMovingAverage _ma;
	private AverageTrueRange _atr;

	private decimal _prevSmax;
	private decimal _prevSmin;
	private int _prevTrend;
	private bool _initialized;

	/// <summary>
	/// Moving average period.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Percentage deviation for envelopes.
	/// </summary>
	public decimal Deviation
	{
		get => _deviation.Value;
		set => _deviation.Value = value;
	}

	/// <summary>
	/// ATR period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// ATR shift sensitivity.
	/// </summary>
	public decimal AtrSensitivity
	{
		get => _atrSensitivity.Value;
		set => _atrSensitivity.Value = value;
	}

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

	/// <summary>
	/// Whether long entries are allowed.
	/// </summary>
	public bool BuyEntry
	{
		get => _buyEntry.Value;
		set => _buyEntry.Value = value;
	}

	/// <summary>
	/// Whether short entries are allowed.
	/// </summary>
	public bool SellEntry
	{
		get => _sellEntry.Value;
		set => _sellEntry.Value = value;
	}

	/// <summary>
	/// Whether long positions can be closed.
	/// </summary>
	public bool BuyExit
	{
		get => _buyExit.Value;
		set => _buyExit.Value = value;
	}

	/// <summary>
	/// Whether short positions can be closed.
	/// </summary>
	public bool SellExit
	{
		get => _sellExit.Value;
		set => _sellExit.Value = value;
	}

	/// <summary>
	/// Take profit in points.
	/// </summary>
	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Stop loss in points.
	/// </summary>
	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public TrendEnvelopesStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("MA Period", "Moving average length", "Indicator");

		_deviation = Param(nameof(Deviation), 0.2m)
		.SetGreaterThanZero()
		.SetDisplay("Deviation", "Percent offset for envelopes", "Indicator");

		_atrPeriod = Param(nameof(AtrPeriod), 15)
		.SetGreaterThanZero()
		.SetDisplay("ATR Period", "ATR calculation length", "Indicator");

		_atrSensitivity = Param(nameof(AtrSensitivity), 0.5m)
		.SetGreaterThanZero()
		.SetDisplay("ATR Sensitivity", "Multiplier for signal shift", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe for strategy", "General");

		_buyEntry = Param(nameof(BuyEntry), true)
		.SetDisplay("Enable Long Entry", "Allow opening long positions", "Trading");

		_sellEntry = Param(nameof(SellEntry), true)
		.SetDisplay("Enable Short Entry", "Allow opening short positions", "Trading");

		_buyExit = Param(nameof(BuyExit), true)
		.SetDisplay("Enable Long Exit", "Allow closing long positions", "Trading");

		_sellExit = Param(nameof(SellExit), true)
		.SetDisplay("Enable Short Exit", "Allow closing short positions", "Trading");

		_takeProfit = Param(nameof(TakeProfit), 2000)
		.SetGreaterThanZero()
		.SetDisplay("Take Profit", "Target in points", "Protection");

		_stopLoss = Param(nameof(StopLoss), 1000)
		.SetGreaterThanZero()
		.SetDisplay("Stop Loss", "Loss limit in points", "Protection");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_ma = default;
		_atr = default;
		_prevSmax = 0;
		_prevSmin = 0;
		_prevTrend = 0;
		_initialized = false;
	}

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

		_ma = new ExponentialMovingAverage { Length = MaPeriod };
		_atr = new AverageTrueRange { Length = AtrPeriod };

		Indicators.Add(_ma);
		Indicators.Add(_atr);

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

		var step = Security.PriceStep ?? 1m;
		StartProtection(
		takeProfit: new Unit(TakeProfit * step, UnitTypes.Absolute),
		stopLoss: new Unit(StopLoss * step, UnitTypes.Absolute));

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

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

		var maResult = _ma.Process(candle.ClosePrice, candle.OpenTime, true);
		var atrResult = _atr.Process(candle);

		if (!maResult.IsFormed || !atrResult.IsFormed)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var maValue = maResult.ToDecimal();
		var atrValue = atrResult.ToDecimal();

		var smax = (1m + Deviation / 100m) * maValue;
		var smin = (1m - Deviation / 100m) * maValue;
		var trend = _prevTrend;

		if (_initialized)
		{
			if (candle.ClosePrice > _prevSmax)
			trend = 1;
			if (candle.ClosePrice < _prevSmin)
			trend = -1;
		}

		decimal? upSignal = null;
		decimal? downSignal = null;
		var upTrend = false;
		var downTrend = false;

		if (!_initialized)
		{
			_prevSmax = smax;
			_prevSmin = smin;
			_prevTrend = 0;
			_initialized = true;
			return;
		}

		if (trend > 0)
		{
			if (smin < _prevSmin)
			smin = _prevSmin;

			upTrend = true;

			if (_prevTrend <= 0)
			upSignal = smin - AtrSensitivity * atrValue;
		}
		else if (trend < 0)
		{
			if (smax > _prevSmax)
			smax = _prevSmax;

			downTrend = true;

			if (_prevTrend >= 0)
			downSignal = smax + AtrSensitivity * atrValue;
		}

		_prevSmax = smax;
		_prevSmin = smin;
		_prevTrend = trend;

		if (BuyExit && downTrend && Position > 0)
			SellMarket();

		if (SellExit && upTrend && Position < 0)
			BuyMarket();

		if (BuyEntry && upSignal.HasValue && Position <= 0)
			BuyMarket();

		if (SellEntry && downSignal.HasValue && Position >= 0)
			SellMarket();
	}
}