Slope Direction Line Strategy
This strategy replicates the behavior of the Slope Direction Line Expert Advisor. It analyzes the slope of a linear regression line built on closing prices. A long position is opened when the regression slope turns positive after being negative, while a short position is opened when it turns negative after being positive. Opposite positions are closed on every change of direction. Optional stop-loss and take-profit percentages protect positions via the built-in StartProtection mechanism.
Details
- Indicator –
LinearRegressionfrom StockSharp. The strategy uses theLinearRegSlopecomponent as the signal. - Signal – cross of the slope through zero. A positive slope indicates an uptrend; a negative slope signals a downtrend.
- Entry/Exit – when the slope changes sign the current position is closed and, if allowed, a new position in the direction of the slope is opened.
- Risk control –
StartProtectionis configured with user-defined take-profit and stop-loss percentages.
Parameters
| Name | Description |
|---|---|
CandleType |
Time frame used for building candles. |
Length |
Number of bars used in the linear regression calculation. |
TakeProfitPercent |
Percent distance to take profit from entry price. |
StopLossPercent |
Percent distance to stop loss from entry price. |
AllowLong |
Permit opening long positions. |
AllowShort |
Permit opening short positions. |
Usage
- Add the strategy to a StockSharp application.
- Configure the parameters according to the desired time frame and risk.
- Start the strategy and monitor trades on the chart.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades on changes in the slope of a smoothed trend line.
/// </summary>
public class SlopeDirectionLineStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _allowLong;
private readonly StrategyParam<bool> _allowShort;
private readonly StrategyParam<int> _cooldownBars;
private ExponentialMovingAverage _trend = null!;
private decimal? _prevTrend;
private decimal? _prevSlope;
private int _cooldownRemaining;
/// <summary>
/// Candle type for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Length of the trend calculation.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.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>
/// Allow opening long positions.
/// </summary>
public bool AllowLong
{
get => _allowLong.Value;
set => _allowLong.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool AllowShort
{
get => _allowShort.Value;
set => _allowShort.Value = value;
}
/// <summary>
/// Number of completed candles to wait after a position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="SlopeDirectionLineStrategy"/> class.
/// </summary>
public SlopeDirectionLineStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for candles", "General");
_length = Param(nameof(Length), 20)
.SetGreaterThanZero()
.SetDisplay("Trend Length", "Number of bars in the trend line", "Indicators")
.SetOptimize(10, 30, 5);
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
.SetRange(1m, 5m);
_stopLossPercent = Param(nameof(StopLossPercent), 1m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
.SetRange(0.5m, 5m);
_allowLong = Param(nameof(AllowLong), true)
.SetDisplay("Allow Long", "Enable long entries", "Trading");
_allowShort = Param(nameof(AllowShort), true)
.SetDisplay("Allow Short", "Enable short entries", "Trading");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trend = null!;
_prevTrend = null;
_prevSlope = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_trend = new ExponentialMovingAverage { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_trend, ProcessCandle)
.Start();
StartProtection(new Unit(TakeProfitPercent, UnitTypes.Percent), new Unit(StopLossPercent, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _trend);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal trendValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (_prevTrend is not decimal prevTrend)
{
_prevTrend = trendValue;
return;
}
var currentSlope = trendValue - prevTrend;
if (_prevSlope is decimal prevSlope)
{
if (currentSlope > 0m && prevSlope <= 0m && _cooldownRemaining == 0)
{
if (Position < 0)
BuyMarket();
if (Position <= 0 && AllowLong)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
}
else if (currentSlope < 0m && prevSlope >= 0m && _cooldownRemaining == 0)
{
if (Position > 0)
SellMarket();
if (Position >= 0 && AllowShort)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
}
_prevTrend = trendValue;
_prevSlope = currentSlope;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class slope_direction_line_strategy(Strategy):
def __init__(self):
super(slope_direction_line_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for candles", "General")
self._length = self.Param("Length", 20) \
.SetDisplay("Trend Length", "Number of bars in the trend line", "Indicators")
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._allow_long = self.Param("AllowLong", True) \
.SetDisplay("Allow Long", "Enable long entries", "Trading")
self._allow_short = self.Param("AllowShort", True) \
.SetDisplay("Allow Short", "Enable short entries", "Trading")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._prev_trend = None
self._prev_slope = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
@property
def length(self):
return self._length.Value
@property
def take_profit_percent(self):
return self._take_profit_percent.Value
@property
def stop_loss_percent(self):
return self._stop_loss_percent.Value
@property
def allow_long(self):
return self._allow_long.Value
@property
def allow_short(self):
return self._allow_short.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(slope_direction_line_strategy, self).OnReseted()
self._prev_trend = None
self._prev_slope = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(slope_direction_line_strategy, self).OnStarted2(time)
trend = ExponentialMovingAverage()
trend.Length = self.length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(trend, self.process_candle).Start()
self.StartProtection(
Unit(float(self.take_profit_percent), UnitTypes.Percent),
Unit(float(self.stop_loss_percent), UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, trend)
self.DrawOwnTrades(area)
def process_candle(self, candle, trend_value):
if candle.State != CandleStates.Finished:
return
trend_value = float(trend_value)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if self._prev_trend is None:
self._prev_trend = trend_value
return
current_slope = trend_value - self._prev_trend
if self._prev_slope is not None:
if current_slope > 0 and self._prev_slope <= 0 and self._cooldown_remaining == 0:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0 and self.allow_long:
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif current_slope < 0 and self._prev_slope >= 0 and self._cooldown_remaining == 0:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0 and self.allow_short:
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
self._prev_trend = trend_value
self._prev_slope = current_slope
def CreateClone(self):
return slope_direction_line_strategy()