Exp TrendValue Strategy
A strategy based on the TrendValue indicator. It builds dynamic support and resistance bands using weighted moving averages of high and low prices shifted by ATR. A new up or down trend is detected when price crosses the opposite band.
Entry and Exit
- Long Entry: When a new upward trend starts.
- Short Entry: When a new downward trend starts.
- Long Exit: On a downward signal or trend line.
- Short Exit: On an upward signal or trend line.
Parameters
BuyPosOpen/SellPosOpen– enable long/short entries.BuyPosClose/SellPosClose– allow closing long/short positions.StopLossPips– stop loss in price points.TakeProfitPips– take profit in price points.MaPeriod– weighted moving average period.ShiftPercent– percentage shift applied to averages.AtrPeriod– ATR period.AtrSensitivity– multiplier applied to ATR.CandleType– candle timeframe.
Notes
The strategy subscribes to candle data and updates indicators on each finished candle. Market orders are placed when conditions are met and protective stop and take profit levels are tracked internally.
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 the TrendValue indicator combining weighted moving averages and ATR.
/// </summary>
public class ExpTrendValueStrategy : Strategy
{
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _shiftPercent;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrSensitivity;
private readonly StrategyParam<DataType> _candleType;
private readonly WeightedMovingAverage _wmaHigh = new();
private readonly WeightedMovingAverage _wmaLow = new();
private readonly SimpleMovingAverage _rangeAverage = new();
private decimal _prevHighBand;
private decimal _prevLowBand;
private int _prevTrend;
private bool _initialized;
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyPosOpen { get => _buyPosOpen.Value; set => _buyPosOpen.Value = value; }
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellPosOpen { get => _sellPosOpen.Value; set => _sellPosOpen.Value = value; }
/// <summary>
/// Allow closing long positions.
/// </summary>
public bool BuyPosClose { get => _buyPosClose.Value; set => _buyPosClose.Value = value; }
/// <summary>
/// Allow closing short positions.
/// </summary>
public bool SellPosClose { get => _sellPosClose.Value; set => _sellPosClose.Value = value; }
/// <summary>
/// Stop loss size in points.
/// </summary>
public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
/// <summary>
/// Take profit size in points.
/// </summary>
public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
/// <summary>
/// Weighted moving average period.
/// </summary>
public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
/// <summary>
/// Percentage offset applied to moving averages.
/// </summary>
public decimal ShiftPercent { get => _shiftPercent.Value; set => _shiftPercent.Value = value; }
/// <summary>
/// ATR calculation period.
/// </summary>
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
/// <summary>
/// Multiplier for ATR shift.
/// </summary>
public decimal AtrSensitivity { get => _atrSensitivity.Value; set => _atrSensitivity.Value = value; }
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initializes a new instance of <see cref="ExpTrendValueStrategy"/>.
/// </summary>
public ExpTrendValueStrategy()
{
_buyPosOpen = Param(nameof(BuyPosOpen), true).SetDisplay("Enable Long Entry", "Allow opening long positions", "Trading");
_sellPosOpen = Param(nameof(SellPosOpen), true).SetDisplay("Enable Short Entry", "Allow opening short positions", "Trading");
_buyPosClose = Param(nameof(BuyPosClose), true).SetDisplay("Allow Long Exit", "Allow closing long positions", "Trading");
_sellPosClose = Param(nameof(SellPosClose), true).SetDisplay("Allow Short Exit", "Allow closing short positions", "Trading");
_stopLossPips = Param(nameof(StopLossPips), 1000).SetGreaterThanZero().SetDisplay("Stop Loss", "Stop loss in points", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 2000).SetGreaterThanZero().SetDisplay("Take Profit", "Take profit in points", "Risk");
_maPeriod = Param(nameof(MaPeriod), 13).SetGreaterThanZero().SetDisplay("MA Period", "Weighted moving average period", "Indicator");
_shiftPercent = Param(nameof(ShiftPercent), 0.05m).SetDisplay("Shift Percent", "Percentage offset for bands", "Indicator");
_atrPeriod = Param(nameof(AtrPeriod), 15).SetGreaterThanZero().SetDisplay("ATR Period", "Range average period", "Indicator");
_atrSensitivity = Param(nameof(AtrSensitivity), 0.6m).SetGreaterThanZero().SetDisplay("ATR Sensitivity", "Multiplier for range shift", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Timeframe for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_wmaHigh.Reset();
_wmaHigh.Length = 1;
_wmaLow.Reset();
_wmaLow.Length = 1;
_rangeAverage.Reset();
_rangeAverage.Length = 1;
_prevHighBand = _prevLowBand = 0m;
_prevTrend = 0;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_wmaHigh.Length = MaPeriod;
_wmaLow.Length = MaPeriod;
_rangeAverage.Length = AtrPeriod;
var closeTrigger = new SimpleMovingAverage { Length = 1 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(closeTrigger, ProcessCandle).Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal closeValue)
{
if (candle.State != CandleStates.Finished)
return;
var highMa = _wmaHigh.Process(new DecimalIndicatorValue(_wmaHigh, candle.HighPrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
var lowMa = _wmaLow.Process(new DecimalIndicatorValue(_wmaLow, candle.LowPrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_wmaHigh.IsFormed || !_wmaLow.IsFormed)
return;
var rangeValue = _rangeAverage.Process(new DecimalIndicatorValue(_rangeAverage, candle.HighPrice - candle.LowPrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_rangeAverage.IsFormed)
return;
var percentOffset = closeValue * ShiftPercent / 100m;
var rangeOffset = rangeValue * AtrSensitivity * 0.25m;
var highBand = highMa - rangeOffset + percentOffset;
var lowBand = lowMa + rangeOffset - percentOffset;
if (!_initialized)
{
_prevHighBand = highBand;
_prevLowBand = lowBand;
_initialized = true;
return;
}
var centerLine = (highBand + lowBand) / 2m;
var trend = candle.ClosePrice >= centerLine ? 1 : -1;
var upSignal = trend > 0 && _prevTrend <= 0;
var downSignal = trend < 0 && _prevTrend >= 0;
var haveUpTrend = trend > 0;
var haveDownTrend = trend < 0;
_prevHighBand = highBand;
_prevLowBand = lowBand;
_prevTrend = trend;
if (upSignal && Position == 0)
BuyMarket();
else if (downSignal && Position == 0)
SellMarket();
}
}
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 WeightedMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class exp_trend_value_strategy(Strategy):
def __init__(self):
super(exp_trend_value_strategy, self).__init__()
self._buy_pos_open = self.Param("BuyPosOpen", True) \
.SetDisplay("Enable Long Entry", "Allow opening long positions", "Trading")
self._sell_pos_open = self.Param("SellPosOpen", True) \
.SetDisplay("Enable Short Entry", "Allow opening short positions", "Trading")
self._buy_pos_close = self.Param("BuyPosClose", True) \
.SetDisplay("Allow Long Exit", "Allow closing long positions", "Trading")
self._sell_pos_close = self.Param("SellPosClose", True) \
.SetDisplay("Allow Short Exit", "Allow closing short positions", "Trading")
self._stop_loss_pips = self.Param("StopLossPips", 1000) \
.SetDisplay("Stop Loss", "Stop loss in points", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 2000) \
.SetDisplay("Take Profit", "Take profit in points", "Risk")
self._ma_period = self.Param("MaPeriod", 13) \
.SetDisplay("MA Period", "Weighted moving average period", "Indicator")
self._shift_percent = self.Param("ShiftPercent", 0.05) \
.SetDisplay("Shift Percent", "Percentage offset for bands", "Indicator")
self._atr_period = self.Param("AtrPeriod", 15) \
.SetDisplay("ATR Period", "Range average period", "Indicator")
self._atr_sensitivity = self.Param("AtrSensitivity", 0.6) \
.SetDisplay("ATR Sensitivity", "Multiplier for range shift", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe for calculations", "General")
self._wma_high = WeightedMovingAverage()
self._wma_low = WeightedMovingAverage()
self._range_average = SimpleMovingAverage()
self._prev_high_band = 0.0
self._prev_low_band = 0.0
self._prev_trend = 0
self._initialized = False
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
@property
def BuyPosOpen(self):
return self._buy_pos_open.Value
@BuyPosOpen.setter
def BuyPosOpen(self, value):
self._buy_pos_open.Value = value
@property
def SellPosOpen(self):
return self._sell_pos_open.Value
@SellPosOpen.setter
def SellPosOpen(self, value):
self._sell_pos_open.Value = value
@property
def BuyPosClose(self):
return self._buy_pos_close.Value
@BuyPosClose.setter
def BuyPosClose(self, value):
self._buy_pos_close.Value = value
@property
def SellPosClose(self):
return self._sell_pos_close.Value
@SellPosClose.setter
def SellPosClose(self, value):
self._sell_pos_close.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@TakeProfitPips.setter
def TakeProfitPips(self, value):
self._take_profit_pips.Value = value
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def ShiftPercent(self):
return self._shift_percent.Value
@ShiftPercent.setter
def ShiftPercent(self, value):
self._shift_percent.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def AtrSensitivity(self):
return self._atr_sensitivity.Value
@AtrSensitivity.setter
def AtrSensitivity(self, value):
self._atr_sensitivity.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(exp_trend_value_strategy, self).OnStarted2(time)
self._wma_high.Length = self.MaPeriod
self._wma_low.Length = self.MaPeriod
self._range_average.Length = self.AtrPeriod
close_trigger = SimpleMovingAverage()
close_trigger.Length = 1
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(close_trigger, self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, close_value):
if candle.State != CandleStates.Finished:
return
high_ma = float(process_float(self._wma_high, candle.HighPrice, candle.OpenTime, True))
low_ma = float(process_float(self._wma_low, candle.LowPrice, candle.OpenTime, True))
if not self._wma_high.IsFormed or not self._wma_low.IsFormed:
return
range_val = candle.HighPrice - candle.LowPrice
range_avg = float(process_float(self._range_average, range_val, candle.OpenTime, True))
if not self._range_average.IsFormed:
return
cv = float(close_value)
percent_offset = cv * float(self.ShiftPercent) / 100.0
range_offset = range_avg * float(self.AtrSensitivity) * 0.25
high_band = high_ma - range_offset + percent_offset
low_band = low_ma + range_offset - percent_offset
if not self._initialized:
self._prev_high_band = high_band
self._prev_low_band = low_band
self._initialized = True
return
center_line = (high_band + low_band) / 2.0
close_price = float(candle.ClosePrice)
trend = 1 if close_price >= center_line else -1
up_signal = trend > 0 and self._prev_trend <= 0
down_signal = trend < 0 and self._prev_trend >= 0
self._prev_high_band = high_band
self._prev_low_band = low_band
self._prev_trend = trend
if up_signal and self.Position == 0:
self.BuyMarket()
elif down_signal and self.Position == 0:
self.SellMarket()
def OnReseted(self):
super(exp_trend_value_strategy, self).OnReseted()
self._wma_high.Reset()
self._wma_low.Reset()
self._range_average.Reset()
self._prev_high_band = 0.0
self._prev_low_band = 0.0
self._prev_trend = 0
self._initialized = False
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
def CreateClone(self):
return exp_trend_value_strategy()