SMA 回调 + ATR 出场策略
当短期均线位于长期均线之上或之下时,策略在回调时入场。价格跌破快速 SMA 且仍高于慢速 SMA 时做多;价格升破快速 SMA 且仍低于慢速 SMA 时做空。退出使用从入场价计算的 ATR 倍数。
细节
- 入场条件:
- 多头:收盘价 < 快速 SMA 且快速 SMA > 慢速 SMA。
- 空头:收盘价 > 快速 SMA 且快速 SMA < 慢速 SMA。
- 多空方向:双向。
- 出场条件:
- 价格触及 ATR 止损或 ATR 止盈。
- 止损:使用 ATR 倍数作为止损和止盈。
- 默认参数:
FastSmaLength= 8SlowSmaLength= 30AtrLength= 14AtrMultiplierSl= 1.2AtrMultiplierTp= 2.0
- 过滤器:
- 类别:趋势跟随
- 方向:双向
- 指标:SMA、ATR
- 止损:是
- 复杂度:低
- 周期:任意
- 季节性:无
- 神经网络:无
- 背离:无
- 风险等级:中等
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// SMA Pullback with ATR Exits Strategy.
/// Buys on pullbacks in uptrend and sells on pullbacks in downtrend.
/// Exits are based on ATR multiples.
/// </summary>
public class SmaPullbackAtrExitsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastSmaLength;
private readonly StrategyParam<int> _slowSmaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrMultiplierSl;
private readonly StrategyParam<decimal> _atrMultiplierTp;
private readonly StrategyParam<int> _cooldownBars;
private decimal _entryPrice;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastSmaLength { get => _fastSmaLength.Value; set => _fastSmaLength.Value = value; }
public int SlowSmaLength { get => _slowSmaLength.Value; set => _slowSmaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
public decimal AtrMultiplierSl { get => _atrMultiplierSl.Value; set => _atrMultiplierSl.Value = value; }
public decimal AtrMultiplierTp { get => _atrMultiplierTp.Value; set => _atrMultiplierTp.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public SmaPullbackAtrExitsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_fastSmaLength = Param(nameof(FastSmaLength), 8)
.SetGreaterThanZero()
.SetDisplay("Fast SMA", "Fast SMA length", "Indicators");
_slowSmaLength = Param(nameof(SlowSmaLength), 30)
.SetGreaterThanZero()
.SetDisplay("Slow SMA", "Slow SMA length", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR calculation length", "Indicators");
_atrMultiplierSl = Param(nameof(AtrMultiplierSl), 1.2m)
.SetDisplay("ATR SL Mult", "ATR multiplier for stop-loss", "Risk");
_atrMultiplierTp = Param(nameof(AtrMultiplierTp), 2.0m)
.SetDisplay("ATR TP Mult", "ATR multiplier for take-profit", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastSma = new SimpleMovingAverage { Length = FastSmaLength };
var slowSma = new SimpleMovingAverage { Length = SlowSmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastSma, slowSma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastSma);
DrawIndicator(area, slowSma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastSmaValue, decimal slowSmaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Check stop/TP exits first
if (Position > 0 && _entryPrice > 0)
{
var stop = _entryPrice - atrValue * AtrMultiplierSl;
var target = _entryPrice + atrValue * AtrMultiplierTp;
if (candle.LowPrice <= stop || candle.HighPrice >= target)
{
SellMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
var stop = _entryPrice + atrValue * AtrMultiplierSl;
var target = _entryPrice - atrValue * AtrMultiplierTp;
if (candle.HighPrice >= stop || candle.LowPrice <= target)
{
BuyMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
return;
}
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
var currentPrice = candle.ClosePrice;
// Buy: pullback in uptrend (price below fast SMA, fast > slow)
if (currentPrice < fastSmaValue && fastSmaValue > slowSmaValue && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_entryPrice = currentPrice;
_cooldownRemaining = CooldownBars;
}
// Sell: pullback in downtrend (price above fast SMA, fast < slow)
else if (currentPrice > fastSmaValue && fastSmaValue < slowSmaValue && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_entryPrice = currentPrice;
_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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class sma_pullback_atr_exits_strategy(Strategy):
"""SMA Pullback ATR Exits Strategy."""
def __init__(self):
super(sma_pullback_atr_exits_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._fast_sma_length = self.Param("FastSmaLength", 8) \
.SetDisplay("Fast SMA", "Fast SMA length", "Indicators")
self._slow_sma_length = self.Param("SlowSmaLength", 30) \
.SetDisplay("Slow SMA", "Slow SMA length", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR calculation length", "Indicators")
self._atr_multiplier_sl = self.Param("AtrMultiplierSl", 1.2) \
.SetDisplay("ATR SL Mult", "ATR multiplier for stop-loss", "Risk")
self._atr_multiplier_tp = self.Param("AtrMultiplierTp", 2.0) \
.SetDisplay("ATR TP Mult", "ATR multiplier for take-profit", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._entry_price = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(sma_pullback_atr_exits_strategy, self).OnReseted()
self._entry_price = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(sma_pullback_atr_exits_strategy, self).OnStarted2(time)
fast_sma = SimpleMovingAverage()
fast_sma.Length = int(self._fast_sma_length.Value)
slow_sma = SimpleMovingAverage()
slow_sma.Length = int(self._slow_sma_length.Value)
atr = AverageTrueRange()
atr.Length = int(self._atr_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_sma, slow_sma, atr, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_sma)
self.DrawIndicator(area, slow_sma)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_sma_value, slow_sma_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
atr_v = float(atr_value)
atr_sl = float(self._atr_multiplier_sl.Value)
atr_tp = float(self._atr_multiplier_tp.Value)
cooldown = int(self._cooldown_bars.Value)
# Check stop/TP exits first
if self.Position > 0 and self._entry_price > 0:
stop = self._entry_price - atr_v * atr_sl
target = self._entry_price + atr_v * atr_tp
if float(candle.LowPrice) <= stop or float(candle.HighPrice) >= target:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
return
elif self.Position < 0 and self._entry_price > 0:
stop = self._entry_price + atr_v * atr_sl
target = self._entry_price - atr_v * atr_tp
if float(candle.HighPrice) >= stop or float(candle.LowPrice) <= target:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
close = float(candle.ClosePrice)
fast_v = float(fast_sma_value)
slow_v = float(slow_sma_value)
# Buy: pullback in uptrend
if close < fast_v and fast_v > slow_v and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._entry_price = close
self._cooldown_remaining = cooldown
# Sell: pullback in downtrend
elif close > fast_v and fast_v < slow_v and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._entry_price = close
self._cooldown_remaining = cooldown
def CreateClone(self):
return sma_pullback_atr_exits_strategy()