Linear Regression Slope Trigger Strategy
Overview
This strategy uses a linear regression slope indicator and a derived trigger line to identify trend changes. A long position is opened when the trigger line crosses above the slope line, while a short position is opened when the trigger line crosses below the slope line. Existing positions are closed when an opposite signal appears. The approach is inspired by the original MQL5 strategy "Exp_LinearRegSlopeV2".
Indicator Logic
- Linear Regression Slope is calculated on candle close prices over a configurable period.
- A trigger line is computed as
2 * slope - slope[Shift], whereslope[Shift]is the slope value from several bars ago. - Crossovers between the trigger and slope lines serve as trading signals.
Trading Rules
- Enter Long: Trigger crosses above slope and short trades are allowed.
- Enter Short: Trigger crosses below slope and long trades are allowed.
- Exit Long: Slope rises above trigger.
- Exit Short: Trigger rises above slope.
Parameters
SlopeLength– Period for calculating the linear regression slope.TriggerShift– Number of bars used to calculate the trigger line.EnableLong– Allows long entries.EnableShort– Allows short entries.TakeProfitPercent– Take‑profit as a percentage of entry price.StopLossPercent– Stop‑loss as a percentage of entry price.CandleType– Timeframe of candles used by the strategy.
Notes
- The strategy operates on completed candles only.
- Protection via
StartProtectionapplies fixed percent-based take‑profit and stop‑loss levels. - Ensure sufficient historical data so the indicator can form its values.
using System;
using System.Collections.Generic;
using Ecng.Common;
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 a smoothed slope line and trigger line.
/// </summary>
public class LinearRegressionSlopeTriggerStrategy : Strategy
{
private readonly StrategyParam<int> _slopeLength;
private readonly StrategyParam<int> _triggerShift;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly ExponentialMovingAverage _trendLine = new();
private readonly SimpleMovingAverage _triggerLine = new();
private decimal _previousTrendValue;
private decimal _previousSlope;
private decimal _previousTrigger;
private bool _isInitialized;
private int _barsSinceTrade;
/// <summary>
/// Period for calculating the smoothed trend line.
/// </summary>
public int SlopeLength
{
get => _slopeLength.Value;
set => _slopeLength.Value = value;
}
/// <summary>
/// Number of bars used for trigger smoothing.
/// </summary>
public int TriggerShift
{
get => _triggerShift.Value;
set => _triggerShift.Value = value;
}
/// <summary>
/// Allow long entries.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Allow short entries.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Take-profit percentage from entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Stop-loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="LinearRegressionSlopeTriggerStrategy"/>.
/// </summary>
public LinearRegressionSlopeTriggerStrategy()
{
_slopeLength = Param(nameof(SlopeLength), 12)
.SetGreaterThanZero()
.SetDisplay("Slope Length", "Period for the smoothed trend line", "Indicator")
.SetOptimize(5, 30, 1);
_triggerShift = Param(nameof(TriggerShift), 2)
.SetGreaterThanZero()
.SetDisplay("Trigger Shift", "Bars used for trigger smoothing", "Indicator")
.SetOptimize(1, 5, 1);
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow long trades", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow short trades", "Trading");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 5m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Take-profit percentage", "Risk Management")
.SetOptimize(2m, 10m, 1m);
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management")
.SetOptimize(1m, 5m, 1m);
_cooldownBars = Param(nameof(CooldownBars), 1)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trendLine.Reset();
_trendLine.Length = 1;
_triggerLine.Reset();
_triggerLine.Length = 1;
_previousTrendValue = 0m;
_previousSlope = 0m;
_previousTrigger = 0m;
_isInitialized = false;
_barsSinceTrade = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_trendLine.Length = SlopeLength;
_triggerLine.Length = TriggerShift;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
StartProtection(
new Unit(TakeProfitPercent, UnitTypes.Percent),
new Unit(StopLossPercent, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var trendValue = _trendLine.Process(new DecimalIndicatorValue(_trendLine, candle.ClosePrice, candle.ServerTime) { IsFinal = true }).ToDecimal();
if (!_trendLine.IsFormed)
return;
if (!_isInitialized)
{
_previousTrendValue = trendValue;
_isInitialized = true;
return;
}
var slope = trendValue - _previousTrendValue;
var trigger = _triggerLine.Process(new DecimalIndicatorValue(_triggerLine, slope, candle.ServerTime) { IsFinal = true }).ToDecimal();
if (!_triggerLine.IsFormed)
{
_previousTrendValue = trendValue;
_previousSlope = slope;
_previousTrigger = slope;
return;
}
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
var buySignal = _previousTrigger <= _previousSlope && trigger > slope && slope > 0m;
var sellSignal = _previousTrigger >= _previousSlope && trigger < slope && slope < 0m;
var closeLong = slope >= 0m && trigger < slope;
var closeShort = slope <= 0m && trigger > slope;
if (_barsSinceTrade >= CooldownBars && Position == 0)
{
if (buySignal && EnableLong)
{
BuyMarket();
_barsSinceTrade = 0;
}
else if (sellSignal && EnableShort)
{
SellMarket();
_barsSinceTrade = 0;
}
}
_previousTrendValue = trendValue;
_previousSlope = slope;
_previousTrigger = trigger;
}
}
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, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class linear_regression_slope_trigger_strategy(Strategy):
def __init__(self):
super(linear_regression_slope_trigger_strategy, self).__init__()
self._slope_length = self.Param("SlopeLength", 12) \
.SetDisplay("Slope Length", "Period for the smoothed trend line", "Indicator")
self._trigger_shift = self.Param("TriggerShift", 2) \
.SetDisplay("Trigger Shift", "Bars used for trigger smoothing", "Indicator")
self._enable_long = self.Param("EnableLong", True) \
.SetDisplay("Enable Long", "Allow long trades", "Trading")
self._enable_short = self.Param("EnableShort", True) \
.SetDisplay("Enable Short", "Allow short trades", "Trading")
self._take_profit_percent = self.Param("TakeProfitPercent", 5.0) \
.SetDisplay("Take Profit %", "Take-profit percentage", "Risk Management")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe for candles", "General")
self._trend_line = ExponentialMovingAverage()
self._trigger_line = SimpleMovingAverage()
self._previous_trend_value = 0.0
self._previous_slope = 0.0
self._previous_trigger = 0.0
self._is_initialized = False
self._bars_since_trade = 0
@property
def SlopeLength(self):
return self._slope_length.Value
@SlopeLength.setter
def SlopeLength(self, value):
self._slope_length.Value = value
@property
def TriggerShift(self):
return self._trigger_shift.Value
@TriggerShift.setter
def TriggerShift(self, value):
self._trigger_shift.Value = value
@property
def EnableLong(self):
return self._enable_long.Value
@EnableLong.setter
def EnableLong(self, value):
self._enable_long.Value = value
@property
def EnableShort(self):
return self._enable_short.Value
@EnableShort.setter
def EnableShort(self, value):
self._enable_short.Value = value
@property
def TakeProfitPercent(self):
return self._take_profit_percent.Value
@TakeProfitPercent.setter
def TakeProfitPercent(self, value):
self._take_profit_percent.Value = value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stop_loss_percent.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(linear_regression_slope_trigger_strategy, self).OnStarted2(time)
self._trend_line.Length = self.SlopeLength
self._trigger_line.Length = self.TriggerShift
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
Unit(self.TakeProfitPercent, UnitTypes.Percent),
Unit(self.StopLossPercent, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
trend_value = float(process_float(self._trend_line, candle.ClosePrice, candle.ServerTime, True))
if not self._trend_line.IsFormed:
return
if not self._is_initialized:
self._previous_trend_value = trend_value
self._is_initialized = True
return
slope = trend_value - self._previous_trend_value
trigger = float(process_float(self._trigger_line, slope, candle.ServerTime, True))
if not self._trigger_line.IsFormed:
self._previous_trend_value = trend_value
self._previous_slope = slope
self._previous_trigger = slope
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
buy_signal = self._previous_trigger <= self._previous_slope and trigger > slope and slope > 0.0
sell_signal = self._previous_trigger >= self._previous_slope and trigger < slope and slope < 0.0
if self._bars_since_trade >= self.CooldownBars and self.Position == 0:
if buy_signal and self.EnableLong:
self.BuyMarket()
self._bars_since_trade = 0
elif sell_signal and self.EnableShort:
self.SellMarket()
self._bars_since_trade = 0
self._previous_trend_value = trend_value
self._previous_slope = slope
self._previous_trigger = trigger
def OnReseted(self):
super(linear_regression_slope_trigger_strategy, self).OnReseted()
self._trend_line.Reset()
self._trigger_line.Reset()
self._previous_trend_value = 0.0
self._previous_slope = 0.0
self._previous_trigger = 0.0
self._is_initialized = False
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return linear_regression_slope_trigger_strategy()