GitHub で見る

Flat Trend Strategy

Overview

The Flat Trend Strategy reproduces the core ideas of the original Flat Trend expert advisor by combining multi-speed trend filters, ADX confirmation and a standard deviation "juice" breakout filter. The strategy focuses on detecting the moment when price leaves a ranging phase and momentum expands, allowing it to join directional moves with dynamic position protection.

Trading Logic

  1. Trend filters – three exponential moving averages (EMAs) with configurable lengths represent the trigger, first filter and second filter. Their slope and the price position relative to each EMA are translated into states:
    • Strong bullish (price above EMA and EMA rising).
    • Moderate bullish (price above EMA but slope neutral).
    • Strong bearish (price below EMA and EMA falling).
    • Moderate bearish (price below EMA but slope neutral).
  2. Entry rules
    • Long trades require bullish states on the trigger and filter EMA. The second filter can be optionally ignored. Strict mode forces the use of only strong bullish states.
    • Short trades mirror the conditions for bearish states.
    • Optional ADX confirmation ensures that the Average Directional Index exceeds a threshold and, when enabled, the +DI and –DI components agree with the trade direction.
    • The "juice" filter verifies that the standard deviation of prices is above a user-defined breakout level, preventing trades during flat volatility phases.
    • Trading can be restricted to a selected intraday window.
  3. Exit rules
    • Opposite trend states on the trigger EMA initiate an exit. In strict mode the strategy waits for the strongest counter-signal.
    • Dynamic stops exit positions whenever price touches the calculated stop level.

Risk Management

  • Initial stop – calculated either from a static pip distance or from the Average True Range (ATR) value, emulating the ADR-based logic of the original EA.
  • Trailing stop – moves with the highest (or lowest) price since entry using the ATR multiplied by a divisor.
  • Break-even – once price advances by the configured distance, the stop moves beyond the entry price by a small lock value.

Parameters

Name Description
TriggerLength EMA length for the trigger filter.
FilterLength1 EMA length for the first confirmation filter.
FilterLength2 EMA length for the second confirmation filter.
UseOnlyPrimaryIndicators Use only trigger and first filter for entries.
IgnoreModerateForEntry Require strong trend states for new trades.
IgnoreModerateForExit Require strong counter-signals to close trades.
UseTradingHours Enable intraday trading window.
TradingHourBegin / TradingHourEnd Start and end hour of the trading window.
UseJuiceFilter, JuicePeriod, JuiceThreshold Standard deviation breakout filter parameters.
UseAdxFilter, AdxPeriod, AdxThreshold, UseDirectionalFilter ADX strength and DI confirmation.
UseAdrForStop, StopLossPips Initial stop-loss configuration.
TrailingDivisor ATR multiplier for trailing stop calculation.
BreakEvenPips, BreakEvenLockPips Break-even activation and lock distance.
AtrPeriod ATR lookback used for volatility estimation.
CandleType Primary candle timeframe.

Indicator Summary

  • Exponential Moving Average (EMA) – three instances for multi-speed trend assessment.
  • Standard Deviation – models the "juice" volatility breakout filter.
  • Average True Range (ATR) – measures volatility for stops and trailing.
  • Average Directional Index (ADX) – confirms trend strength and direction.

Usage Notes

  1. Ensure the strategy security has a defined PriceStep; otherwise the default step of 0.0001 is used for pip-based distances.
  2. The strategy uses market orders (BuyMarket, SellMarket) and automatically scales volume when reversing positions.
  3. Dynamic stops are simulated internally by closing positions when the virtual stop level is touched.
  4. Combine the trading window and strict entry options to focus on high-liquidity sessions and avoid choppy periods.
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>
/// Flat Trend strategy - breakout from low volatility using ATR and EMA.
/// Buys when ATR expands and price is above EMA.
/// Sells when ATR expands and price is below EMA.
/// </summary>
public class FlatTrendStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevAtr;
	private decimal _prevClose;
	private decimal _prevEma;
	private bool _hasPrev;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public FlatTrendStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("EMA Period", "EMA trend filter", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "ATR volatility period", "Indicators");

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

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevAtr = 0m; _prevClose = 0m; _prevEma = 0m; _hasPrev = false; }

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

		_hasPrev = false;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };

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

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

		var close = candle.ClosePrice;

		if (!_hasPrev)
		{
			_prevAtr = atr;
			_prevClose = close;
			_prevEma = ema;
			_hasPrev = true;
			return;
		}

		// Volatility expansion: ATR increasing
		var atrExpanding = atr > _prevAtr;

		// Breakout above EMA with expanding volatility
		if (atrExpanding && _prevClose <= _prevEma && close > ema && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Breakout below EMA with expanding volatility
		else if (atrExpanding && _prevClose >= _prevEma && close < ema && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevAtr = atr;
		_prevClose = close;
		_prevEma = ema;
	}
}