LeMan Trend Strategy
The LeMan Trend strategy derives bullish and bearish pressure from recent highs and lows. It calculates the distance between the current candle and the highest highs and lowest lows over three different lookback periods. These distances are smoothed with an exponential moving average (EMA) to form two lines: bulls and bears. A crossover between these lines signals potential trend changes.
When the bulls line crosses above the bears line, the strategy opens a long position or closes an existing short position. Conversely, when the bears line moves above the bulls line, it opens a short position or exits a long one. The method does not use additional filters, focusing solely on the relative strength of recent highs and lows.
Details
- Entry Criteria
- Long: Bulls line crosses above bears line.
- Short: Bears line crosses above bulls line.
- Long/Short: Both sides supported.
- Exit Criteria
- Opposite crossover closes the active position.
- Stops: None by default.
- Default Values
Min= 13Midle= 21Max= 34EMA period= 3Time frame= 4 hours
- Filters
- Category: Trend following
- Direction: Both
- Indicators: Highest, Lowest, EMA
- Stops: No
- Complexity: Medium
- Timeframe: Medium-term
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Moderate
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>
/// Leman Trend strategy using high/low differences smoothed by EMA.
/// Opens long when bullish pressure exceeds bearish, short otherwise.
/// </summary>
public class LeManTrendStrategy : Strategy
{
private readonly StrategyParam<int> _min;
private readonly StrategyParam<int> _midle;
private readonly StrategyParam<int> _max;
private readonly StrategyParam<int> _periodEma;
private readonly StrategyParam<DataType> _candleType;
private Highest _highMin;
private Highest _highMidle;
private Highest _highMax;
private Lowest _lowMin;
private Lowest _lowMidle;
private Lowest _lowMax;
private ExponentialMovingAverage _bullsEma;
private ExponentialMovingAverage _bearsEma;
private decimal _prevBulls;
private decimal _prevBears;
public int Min
{
get => _min.Value;
set => _min.Value = value;
}
public int Midle
{
get => _midle.Value;
set => _midle.Value = value;
}
public int Max
{
get => _max.Value;
set => _max.Value = value;
}
public int PeriodEma
{
get => _periodEma.Value;
set => _periodEma.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public LeManTrendStrategy()
{
_min = Param(nameof(Min), 13)
.SetGreaterThanZero()
.SetDisplay("Min Period", "Minimum lookback for highs/lows", "Indicator")
.SetOptimize(5, 25, 1);
_midle = Param(nameof(Midle), 21)
.SetGreaterThanZero()
.SetDisplay("Middle Period", "Middle lookback for highs/lows", "Indicator")
.SetOptimize(10, 40, 1);
_max = Param(nameof(Max), 34)
.SetGreaterThanZero()
.SetDisplay("Max Period", "Maximum lookback for highs/lows", "Indicator")
.SetOptimize(20, 60, 1);
_periodEma = Param(nameof(PeriodEma), 3)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Smoothing period for bulls/bears", "Indicator")
.SetOptimize(2, 10, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for calculations", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_highMin = default;
_highMidle = default;
_highMax = default;
_lowMin = default;
_lowMidle = default;
_lowMax = default;
_bullsEma = default;
_bearsEma = default;
_prevBulls = default;
_prevBears = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highMin = new Highest { Length = Min };
_highMidle = new Highest { Length = Midle };
_highMax = new Highest { Length = Max };
_lowMin = new Lowest { Length = Min };
_lowMidle = new Lowest { Length = Midle };
_lowMax = new Lowest { Length = Max };
_bullsEma = new ExponentialMovingAverage { Length = PeriodEma };
_bearsEma = new ExponentialMovingAverage { Length = PeriodEma };
Indicators.Add(_highMin);
Indicators.Add(_highMidle);
Indicators.Add(_highMax);
Indicators.Add(_lowMin);
Indicators.Add(_lowMidle);
Indicators.Add(_lowMax);
Indicators.Add(_bullsEma);
Indicators.Add(_bearsEma);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var closeInput = new DecimalIndicatorValue(_highMin, candle.ClosePrice, candle.OpenTime) { IsFinal = true };
var highMinVal = _highMin.Process(closeInput).ToDecimal();
var highMidleVal = _highMidle.Process(new DecimalIndicatorValue(_highMidle, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
var highMaxVal = _highMax.Process(new DecimalIndicatorValue(_highMax, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
var lowMinVal = _lowMin.Process(new DecimalIndicatorValue(_lowMin, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
var lowMidleVal = _lowMidle.Process(new DecimalIndicatorValue(_lowMidle, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
var lowMaxVal = _lowMax.Process(new DecimalIndicatorValue(_lowMax, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_highMax.IsFormed || !_lowMax.IsFormed)
return;
var hh = (candle.HighPrice - highMinVal) + (candle.HighPrice - highMidleVal) + (candle.HighPrice - highMaxVal);
var ll = (lowMinVal - candle.LowPrice) + (lowMidleVal - candle.LowPrice) + (lowMaxVal - candle.LowPrice);
var bullsVal = _bullsEma.Process(new DecimalIndicatorValue(_bullsEma, hh, candle.OpenTime) { IsFinal = true }).ToDecimal();
var bearsVal = _bearsEma.Process(new DecimalIndicatorValue(_bearsEma, ll, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_bullsEma.IsFormed || !_bearsEma.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_prevBulls <= _prevBears && bullsVal > bearsVal && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (_prevBulls >= _prevBears && bullsVal < bearsVal && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevBulls = bullsVal;
_prevBears = bearsVal;
}
}
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 Highest, Lowest, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class le_man_trend_strategy(Strategy):
def __init__(self):
super(le_man_trend_strategy, self).__init__()
self._min = self.Param("Min", 13) \
.SetDisplay("Min Period", "Minimum lookback for highs/lows", "Indicator")
self._midle = self.Param("Midle", 21) \
.SetDisplay("Middle Period", "Middle lookback for highs/lows", "Indicator")
self._max = self.Param("Max", 34) \
.SetDisplay("Max Period", "Maximum lookback for highs/lows", "Indicator")
self._period_ema = self.Param("PeriodEma", 3) \
.SetDisplay("EMA Period", "Smoothing period for bulls/bears", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for calculations", "General")
self._high_min = None
self._high_midle = None
self._high_max = None
self._low_min = None
self._low_midle = None
self._low_max = None
self._bulls_ema = None
self._bears_ema = None
self._prev_bulls = 0.0
self._prev_bears = 0.0
@property
def min_period(self):
return self._min.Value
@property
def midle_period(self):
return self._midle.Value
@property
def max_period(self):
return self._max.Value
@property
def period_ema(self):
return self._period_ema.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(le_man_trend_strategy, self).OnReseted()
self._high_min = None
self._high_midle = None
self._high_max = None
self._low_min = None
self._low_midle = None
self._low_max = None
self._bulls_ema = None
self._bears_ema = None
self._prev_bulls = 0.0
self._prev_bears = 0.0
def OnStarted2(self, time):
super(le_man_trend_strategy, self).OnStarted2(time)
self._high_min = Highest()
self._high_min.Length = self.min_period
self._high_midle = Highest()
self._high_midle.Length = self.midle_period
self._high_max = Highest()
self._high_max.Length = self.max_period
self._low_min = Lowest()
self._low_min.Length = self.min_period
self._low_midle = Lowest()
self._low_midle.Length = self.midle_period
self._low_max = Lowest()
self._low_max.Length = self.max_period
self._bulls_ema = ExponentialMovingAverage()
self._bulls_ema.Length = self.period_ema
self._bears_ema = ExponentialMovingAverage()
self._bears_ema.Length = self.period_ema
self.Indicators.Add(self._high_min)
self.Indicators.Add(self._high_midle)
self.Indicators.Add(self._high_max)
self.Indicators.Add(self._low_min)
self.Indicators.Add(self._low_midle)
self.Indicators.Add(self._low_max)
self.Indicators.Add(self._bulls_ema)
self.Indicators.Add(self._bears_ema)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close_val = candle.ClosePrice
t = candle.OpenTime
high_min_val = float(process_float(self._high_min, close_val, t, True))
high_midle_val = float(process_float(self._high_midle, close_val, t, True))
high_max_val = float(process_float(self._high_max, close_val, t, True))
low_min_val = float(process_float(self._low_min, close_val, t, True))
low_midle_val = float(process_float(self._low_midle, close_val, t, True))
low_max_val = float(process_float(self._low_max, close_val, t, True))
if not self._high_max.IsFormed or not self._low_max.IsFormed:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
hh = (high - high_min_val) + (high - high_midle_val) + (high - high_max_val)
ll = (low_min_val - low) + (low_midle_val - low) + (low_max_val - low)
bulls_val = float(process_float(self._bulls_ema, hh, t, True))
bears_val = float(process_float(self._bears_ema, ll, t, True))
if not self._bulls_ema.IsFormed or not self._bears_ema.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._prev_bulls <= self._prev_bears and bulls_val > bears_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_bulls >= self._prev_bears and bulls_val < bears_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_bulls = bulls_val
self._prev_bears = bears_val
def CreateClone(self):
return le_man_trend_strategy()