CoeffofLine True Strategy
This strategy ports the MQL5 expert Exp_CoeffofLine_true.mq5 to the StockSharp framework. It tracks the Linear Regression Slope of median prices and reacts to zero crossings.
A long position is opened when the slope becomes positive after being negative. A short position is opened when the slope turns negative after being positive. Existing positions are closed on opposite signals. Only completed candles are processed.
Parameters
- Candle Type – timeframe for the candle series.
- Slope Period – length of the linear regression used to compute the slope.
- Signal Bar – historical bar index used for signal evaluation.
- Buy Open / Sell Open – permissions to open long or short positions.
- Buy Close / Sell Close – permissions to exit long or short positions.
The strategy subscribes to candles, binds the indicator via the high-level API and operates without manual indicator value requests.
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 based on zero crossing of a smoothed slope proxy.
/// </summary>
public class CoeffofLineTrueStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _slopePeriod;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellClose;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _slopes = [];
private ExponentialMovingAverage _slopeProxy = null!;
private decimal? _prevValue;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int SlopePeriod { get => _slopePeriod.Value; set => _slopePeriod.Value = value; }
public int SignalBar { get => _signalBar.Value; set => _signalBar.Value = value; }
public bool BuyPosOpen { get => _buyOpen.Value; set => _buyOpen.Value = value; }
public bool SellPosOpen { get => _sellOpen.Value; set => _sellOpen.Value = value; }
public bool BuyPosClose { get => _buyClose.Value; set => _buyClose.Value = value; }
public bool SellPosClose { get => _sellClose.Value; set => _sellClose.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public CoeffofLineTrueStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
_slopePeriod = Param(nameof(SlopePeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Slope Period", "Slope proxy length", "Parameters");
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Historical bar index for signal", "Parameters");
_buyOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Buy Open", "Allow opening long positions", "Trading");
_sellOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Sell Open", "Allow opening short positions", "Trading");
_buyClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Buy Close", "Allow closing long positions", "Trading");
_sellClose = Param(nameof(SellPosClose), true)
.SetDisplay("Sell Close", "Allow closing short positions", "Trading");
_cooldownBars = Param(nameof(CooldownBars), 4)
.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();
_slopes.Clear();
_slopeProxy = null!;
_prevValue = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_slopeProxy = new ExponentialMovingAverage { Length = SlopePeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_slopeProxy, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal proxyValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (_prevValue is not decimal prevValue)
{
_prevValue = proxyValue;
return;
}
var slope = proxyValue - prevValue;
_prevValue = proxyValue;
_slopes.Add(slope);
if (_slopes.Count > SignalBar + 2)
_slopes.RemoveAt(0);
if (_slopes.Count <= SignalBar + 1)
return;
var prev = _slopes[^ (SignalBar + 1)];
var prev2 = _slopes[^ (SignalBar + 2)];
var buyOpen = BuyPosOpen && prev2 <= 0m && prev > 0m;
var sellOpen = SellPosOpen && prev2 >= 0m && prev < 0m;
var buyClose = BuyPosClose && prev2 >= 0m && prev < 0m;
var sellClose = SellPosClose && prev2 <= 0m && prev > 0m;
if (buyClose && Position > 0)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
if (sellClose && Position < 0)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
if (_cooldownRemaining == 0 && buyOpen)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownBars;
}
if (_cooldownRemaining == 0 && sellOpen)
{
if (Position > 0)
SellMarket();
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class coeffof_line_true_strategy(Strategy):
def __init__(self):
super(coeffof_line_true_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for candles", "General")
self._slope_period = self.Param("SlopePeriod", 5) \
.SetDisplay("Slope Period", "Slope proxy length", "Parameters")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Bar", "Historical bar index for signal", "Parameters")
self._buy_open = self.Param("BuyPosOpen", True) \
.SetDisplay("Buy Open", "Allow opening long positions", "Trading")
self._sell_open = self.Param("SellPosOpen", True) \
.SetDisplay("Sell Open", "Allow opening short positions", "Trading")
self._buy_close = self.Param("BuyPosClose", True) \
.SetDisplay("Buy Close", "Allow closing long positions", "Trading")
self._sell_close = self.Param("SellPosClose", True) \
.SetDisplay("Sell Close", "Allow closing short positions", "Trading")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._slopes = []
self._prev_value = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
@property
def slope_period(self):
return self._slope_period.Value
@property
def signal_bar(self):
return self._signal_bar.Value
@property
def buy_open(self):
return self._buy_open.Value
@property
def sell_open(self):
return self._sell_open.Value
@property
def buy_close(self):
return self._buy_close.Value
@property
def sell_close(self):
return self._sell_close.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(coeffof_line_true_strategy, self).OnReseted()
self._slopes = []
self._prev_value = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(coeffof_line_true_strategy, self).OnStarted2(time)
slope_proxy = ExponentialMovingAverage()
slope_proxy.Length = self.slope_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(slope_proxy, self.process_candle).Start()
def process_candle(self, candle, proxy_value):
if candle.State != CandleStates.Finished:
return
proxy_value = float(proxy_value)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if self._prev_value is None:
self._prev_value = proxy_value
return
slope = proxy_value - self._prev_value
self._prev_value = proxy_value
self._slopes.append(slope)
sb = self.signal_bar
if len(self._slopes) > sb + 2:
self._slopes.pop(0)
if len(self._slopes) <= sb + 1:
return
prev = self._slopes[-(sb + 1)]
prev2 = self._slopes[-(sb + 2)]
buy_open_sig = self.buy_open and prev2 <= 0 and prev > 0
sell_open_sig = self.sell_open and prev2 >= 0 and prev < 0
buy_close_sig = self.buy_close and prev2 >= 0 and prev < 0
sell_close_sig = self.sell_close and prev2 <= 0 and prev > 0
if buy_close_sig and self.Position > 0:
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
if sell_close_sig and self.Position < 0:
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
if self._cooldown_remaining == 0 and buy_open_sig:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
if self._cooldown_remaining == 0 and sell_open_sig:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
def CreateClone(self):
return coeffof_line_true_strategy()