Exp TrendValue 策略
该策略基于 TrendValue 指标。它使用加权移动平均线和 ATR 构建动态支撑与阻力带,当价格突破相反边界时视为趋势反转。
入场与出场
- 做多:出现新的上升趋势时。
- 做空:出现新的下降趋势时。
- 平多:出现下降信号或下行带时。
- 平空:出现上升信号或上行带时。
参数
BuyPosOpen/SellPosOpen– 允许开多/开空。BuyPosClose/SellPosClose– 允许平多/平空。StopLossPips– 止损点数。TakeProfitPips– 止盈点数。MaPeriod– 加权移动平均周期。ShiftPercent– 平均线百分比偏移。AtrPeriod– ATR 周期。AtrSensitivity– ATR 乘数。CandleType– K线周期。
说明
策略订阅K线数据,仅在K线完成后更新指标并执行交易,同时内部跟踪止损和止盈水平。
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()