ExpBuySellSide策略
该策略是将MetaTrader专家顾问ExpBuySellSide转换到StockSharp API的版本。它结合了基于ATR的止损系统和简化的Step Up/Down趋势过滤器。
ATR模块在每根K线上构建动态带。当价格突破上轨时视为进入多头阶段;跌破下轨则视为进入空头阶段。
Step Up/Down模块比较快速SMA与慢速SMA,并检查它们之间的差值是否在扩大。差值沿交叉方向扩大说明趋势得到确认。
只有当两个模块同时给出同向信号时才开仓。若启用“Close Opposite”,出现反向信号时会先平掉已有仓位。
细节
- 入场条件:
- 做多:收盘价突破ATR上轨且快速SMA相对慢速SMA上升。
- 做空:收盘价跌破ATR下轨且快速SMA相对慢速SMA下降。
- 方向:双向。
- 出场条件:
- 出现反向信号并启用Close Opposite。
- 通过保护函数手动平仓。
- 止损:基于
ATR * Multiplier。 - 默认参数:
ATR Period= 5。ATR Multiplier= 2.5。Fast SMA= 2。Slow SMA= 30。Candle Type= 1小时。Close Opposite= true。
- 过滤标签:
- 类型:趋势跟随
- 方向:双向
- 指标:多个
- 止损:是
- 复杂度:中等
- 时间框架:中期
- 季节性:无
- 神经网络:无
- 背离:无
- 风险等级:中等
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that combines an ATR based stop with a simplified Step Up/Down trend filter.
/// A long position is opened when both modules signal an upward trend.
/// A short position is opened when both modules signal a downward trend.
/// </summary>
public class ExpBuySellSideStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<bool> _closeByOppositeSignal;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevDiff;
private decimal _prevUpper;
private decimal _prevLower;
private int _atrTrend;
/// <summary>
/// ATR calculation period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Multiplier applied to ATR for stop calculation.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Fast SMA length used in trend filter.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow SMA length used in trend filter.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Close opposite position before opening new one.
/// </summary>
public bool CloseByOppositeSignal
{
get => _closeByOppositeSignal.Value;
set => _closeByOppositeSignal.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ExpBuySellSideStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR calculation length", "ATR")
.SetOptimize(3, 20, 1);
_atrMultiplier = Param(nameof(AtrMultiplier), 2.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR band multiplier", "ATR")
.SetOptimize(1m, 5m, 0.5m);
_fastPeriod = Param(nameof(FastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast SMA", "Fast moving average length", "Step Up/Down")
.SetOptimize(1, 10, 1);
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow SMA", "Slow moving average length", "Step Up/Down")
.SetOptimize(10, 60, 5);
_closeByOppositeSignal = Param(nameof(CloseByOppositeSignal), true)
.SetDisplay("Close Opposite", "Close opposite position on signal", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevDiff = 0m;
_prevUpper = 0m;
_prevLower = 0m;
_atrTrend = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Start position protection once.
StartProtection(null, null);
var atr = new ATR { Length = AtrPeriod };
var fast = new SMA { Length = FastPeriod };
var slow = new SMA { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Step Up/Down filter using difference between fast and slow SMA.
var diff = fast - slow;
var stepSignal = 0;
if (fast > slow && diff > _prevDiff)
stepSignal = 1;
else if (fast < slow && diff < _prevDiff)
stepSignal = -1;
_prevDiff = diff;
// ATR based stop similar to SuperTrend.
var upper = candle.HighPrice - AtrMultiplier * atr;
var lower = candle.LowPrice + AtrMultiplier * atr;
var atrSignal = 0;
if (candle.ClosePrice > _prevUpper && _atrTrend <= 0)
{
_atrTrend = 1;
atrSignal = 1;
}
else if (candle.ClosePrice < _prevLower && _atrTrend >= 0)
{
_atrTrend = -1;
atrSignal = -1;
}
_prevUpper = upper;
_prevLower = lower;
var tradeSignal = 0;
if (atrSignal == 1 && stepSignal == 1)
tradeSignal = 1;
else if (atrSignal == -1 && stepSignal == -1)
tradeSignal = -1;
switch (tradeSignal)
{
case 1:
if (CloseByOppositeSignal && Position < 0)
BuyMarket(Volume + Math.Abs(Position));
else if (Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
break;
case -1:
if (CloseByOppositeSignal && Position > 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position >= 0)
SellMarket(Volume + Math.Abs(Position));
break;
}
}
}
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 SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class exp_buy_sell_side_strategy(Strategy):
def __init__(self):
super(exp_buy_sell_side_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 5)
self._atr_multiplier = self.Param("AtrMultiplier", 2.5)
self._fast_period = self.Param("FastPeriod", 5)
self._slow_period = self.Param("SlowPeriod", 30)
self._close_by_opposite_signal = self.Param("CloseByOppositeSignal", True)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_diff = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._atr_trend = 0
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def AtrMultiplier(self):
return self._atr_multiplier.Value
@AtrMultiplier.setter
def AtrMultiplier(self, value):
self._atr_multiplier.Value = value
@property
def FastPeriod(self):
return self._fast_period.Value
@FastPeriod.setter
def FastPeriod(self, value):
self._fast_period.Value = value
@property
def SlowPeriod(self):
return self._slow_period.Value
@SlowPeriod.setter
def SlowPeriod(self, value):
self._slow_period.Value = value
@property
def CloseByOppositeSignal(self):
return self._close_by_opposite_signal.Value
@CloseByOppositeSignal.setter
def CloseByOppositeSignal(self, value):
self._close_by_opposite_signal.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_buy_sell_side_strategy, self).OnStarted2(time)
self._prev_diff = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._atr_trend = 0
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
fast = SimpleMovingAverage()
fast.Length = self.FastPeriod
slow = SimpleMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, atr, self.ProcessCandle).Start()
self.StartProtection(None, None)
def ProcessCandle(self, candle, fast_value, slow_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast = float(fast_value)
slow = float(slow_value)
atr = float(atr_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
mult = float(self.AtrMultiplier)
diff = fast - slow
step_signal = 0
if fast > slow and diff > self._prev_diff:
step_signal = 1
elif fast < slow and diff < self._prev_diff:
step_signal = -1
self._prev_diff = diff
upper = high - mult * atr
lower = low + mult * atr
atr_signal = 0
if close > self._prev_upper and self._atr_trend <= 0:
self._atr_trend = 1
atr_signal = 1
elif close < self._prev_lower and self._atr_trend >= 0:
self._atr_trend = -1
atr_signal = -1
self._prev_upper = upper
self._prev_lower = lower
trade_signal = 0
if atr_signal == 1 and step_signal == 1:
trade_signal = 1
elif atr_signal == -1 and step_signal == -1:
trade_signal = -1
pos = float(self.Position)
vol = float(self.Volume)
if trade_signal == 1:
if self.CloseByOppositeSignal and pos < 0:
self.BuyMarket(vol + abs(pos))
elif pos <= 0:
self.BuyMarket(vol + abs(pos))
elif trade_signal == -1:
if self.CloseByOppositeSignal and pos > 0:
self.SellMarket(vol + abs(pos))
elif pos >= 0:
self.SellMarket(vol + abs(pos))
def OnReseted(self):
super(exp_buy_sell_side_strategy, self).OnReseted()
self._prev_diff = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._atr_trend = 0
def CreateClone(self):
return exp_buy_sell_side_strategy()