Home
/
Strategy examples
View on 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
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).
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.
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
Ensure the strategy security has a defined PriceStep; otherwise the default step of 0.0001 is used for pip-based distances.
The strategy uses market orders (BuyMarket, SellMarket) and automatically scales volume when reversing positions.
Dynamic stops are simulated internally by closing positions when the virtual stop level is touched.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class flat_trend_strategy(Strategy):
def __init__(self):
super(flat_trend_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR volatility period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_atr = 0.0
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
@property
def ema_period(self):
return self._ema_period.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(flat_trend_strategy, self).OnReseted()
self._prev_atr = 0.0
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(flat_trend_strategy, self).OnStarted2(time)
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
atr = AverageTrueRange()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, atr, self.process_candle).Start()
def process_candle(self, candle, ema, atr):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema)
atr_val = float(atr)
if not self._has_prev:
self._prev_atr = atr_val
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
return
atr_expanding = atr_val > self._prev_atr
if atr_expanding and self._prev_close <= self._prev_ema and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif atr_expanding and self._prev_close >= self._prev_ema and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_atr = atr_val
self._prev_close = close
self._prev_ema = ema_val
def CreateClone(self):
return flat_trend_strategy()