LSMA Angle Strategy
This strategy uses the angle of the Least Squares Moving Average (LSMA) to detect trend direction. The angle is approximated by the difference between two LSMA values separated by a configurable number of bars.
- Long entry: LSMA angle rises above the positive threshold.
- Long exit: Angle returns below the positive threshold.
- Short entry: LSMA angle falls below the negative threshold.
- Short exit: Angle returns above the negative threshold.
Parameters
LSMA Period: Length for LSMA calculation.Angle Threshold: Absolute value defining the neutral zone around zero.Start Shift: Older bar used to calculate the angle.End Shift: Recent bar used to calculate the angle.Candle Type: Candle data type for calculation.
Notes
- Angle values are scaled to points depending on the security (1000 for JPY pairs, otherwise 100000).
- Works on completed candles only.
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>
/// LSMA Angle based strategy.
/// Opens long when the LSMA slope rises above a threshold and short when it falls below.
/// </summary>
public class LsmaAngleStrategy : Strategy
{
private readonly StrategyParam<int> _lsmaPeriod;
private readonly StrategyParam<decimal> _slopeThreshold;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevLsma;
private decimal _prevSlope;
public int LsmaPeriod { get => _lsmaPeriod.Value; set => _lsmaPeriod.Value = value; }
public decimal SlopeThreshold { get => _slopeThreshold.Value; set => _slopeThreshold.Value = value; }
public decimal StopLossPct { get => _stopLossPct.Value; set => _stopLossPct.Value = value; }
public decimal TakeProfitPct { get => _takeProfitPct.Value; set => _takeProfitPct.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public LsmaAngleStrategy()
{
_lsmaPeriod = Param(nameof(LsmaPeriod), 25)
.SetGreaterThanZero()
.SetDisplay("LSMA Period", "LSMA calculation length", "Indicator");
_slopeThreshold = Param(nameof(SlopeThreshold), 0.05m)
.SetDisplay("Slope Threshold", "Percentage slope threshold", "Indicator");
_stopLossPct = Param(nameof(StopLossPct), 2m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfitPct = Param(nameof(TakeProfitPct), 3m)
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_prevLsma = null;
_prevSlope = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var lsma = new LinearReg { Length = LsmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(lsma, (candle, lsmaValue) =>
{
if (candle.State != CandleStates.Finished)
return;
if (_prevLsma is null)
{
_prevLsma = lsmaValue;
return;
}
// Calculate slope as percentage change
var slope = _prevLsma.Value != 0 ? (lsmaValue - _prevLsma.Value) / _prevLsma.Value * 100m : 0m;
var wasUp = _prevSlope > SlopeThreshold;
var wasDown = _prevSlope < -SlopeThreshold;
var isUp = slope > SlopeThreshold;
var isDown = slope < -SlopeThreshold;
if (!wasUp && isUp && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (!wasDown && isDown && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
else if (wasUp && !isUp && Position > 0)
{
SellMarket();
}
else if (wasDown && !isDown && Position < 0)
{
BuyMarket();
}
_prevSlope = slope;
_prevLsma = lsmaValue;
})
.Start();
StartProtection(
takeProfit: new Unit(TakeProfitPct, UnitTypes.Percent),
stopLoss: new Unit(StopLossPct, UnitTypes.Percent),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, lsma);
DrawOwnTrades(area);
}
}
}
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 LinearReg
from StockSharp.Algo.Strategies import Strategy
class lsma_angle_strategy(Strategy):
def __init__(self):
super(lsma_angle_strategy, self).__init__()
self._lsma_period = self.Param("LsmaPeriod", 25) \
.SetDisplay("LSMA Period", "LSMA calculation length", "Indicator")
self._slope_threshold = self.Param("SlopeThreshold", 0.05) \
.SetDisplay("Slope Threshold", "Percentage slope threshold", "Indicator")
self._stop_loss_pct = self.Param("StopLossPct", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit_pct = self.Param("TakeProfitPct", 3.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_lsma = None
self._prev_slope = 0.0
@property
def lsma_period(self):
return self._lsma_period.Value
@property
def slope_threshold(self):
return self._slope_threshold.Value
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def take_profit_pct(self):
return self._take_profit_pct.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(lsma_angle_strategy, self).OnReseted()
self._prev_lsma = None
self._prev_slope = 0.0
def OnStarted2(self, time):
super(lsma_angle_strategy, self).OnStarted2(time)
lsma = LinearReg()
lsma.Length = self.lsma_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(lsma, self.process_candle).Start()
self.StartProtection(
takeProfit=Unit(self.take_profit_pct, UnitTypes.Percent),
stopLoss=Unit(self.stop_loss_pct, UnitTypes.Percent),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, lsma)
self.DrawOwnTrades(area)
def process_candle(self, candle, lsma_value):
if candle.State != CandleStates.Finished:
return
lsma_value = float(lsma_value)
if self._prev_lsma is None:
self._prev_lsma = lsma_value
return
slope = (lsma_value - self._prev_lsma) / self._prev_lsma * 100.0 if self._prev_lsma != 0 else 0.0
threshold = float(self.slope_threshold)
was_up = self._prev_slope > threshold
was_down = self._prev_slope < -threshold
is_up = slope > threshold
is_down = slope < -threshold
if not was_up and is_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif not was_down and is_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif was_up and not is_up and self.Position > 0:
self.SellMarket()
elif was_down and not is_down and self.Position < 0:
self.BuyMarket()
self._prev_slope = slope
self._prev_lsma = lsma_value
def CreateClone(self):
return lsma_angle_strategy()