Sma Pullback Atr Exits
交易规则
两条收盘价 SMA 与 Wilder ATR 均形成后,在完整蜡烛上仅当收盘 < 快 SMA 且快 SMA > 慢 SMA 时买入;收盘 > 快 SMA 且快 SMA < 慢 SMA 时做空。相等不入场。仅空仓以 Volume 入场并等待未完成订单。信号时固定 SL = ATR×AtrMultiplierSl、TP = ATR×AtrMultiplierTp,原生本地市价保护以真实成交为锚并监听买卖报价。之后 ATR 不移动水平。保护平掉全部余仓,反向信号不翻转现有仓位。
参数
| 参数 | 默认值 |
|---|---|
CandleType |
TimeSpan.FromMinutes(30).TimeFrame() |
FastSmaLength |
8 |
SlowSmaLength |
30 |
AtrLength |
14 |
AtrMultiplierSl |
1.2 |
AtrMultiplierTp |
2 |
验证
两种语言使用随附 BTC/TON,检查独立公式、订单方向/数量、真实成交、仓位与退出。带日期的价格、因子、日历数据验证机制,重定位 OHLC/成交量不是多年历史。两个排名组、TopN=1、SMA=2 是明确的小证券池测试配置,长窗口和原始默认值另行检查。不承诺盈利。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>SMA pullbacks with entry-frozen ATR distances and native quote exits.</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 Order _pending;
private SimpleMovingAverage _fast;
private SimpleMovingAverage _slow;
private AverageTrueRange _atr;
/// <summary>Finished signal candle type.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>Fast close SMA length.</summary>
public int FastSmaLength { get => _fastSmaLength.Value; set => _fastSmaLength.Value = value; }
/// <summary>Slow close SMA length.</summary>
public int SlowSmaLength { get => _slowSmaLength.Value; set => _slowSmaLength.Value = value; }
/// <summary>Wilder true-range window.</summary>
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <summary>Frozen ATR stop-loss multiplier.</summary>
public decimal AtrMultiplierSl { get => _atrMultiplierSl.Value; set => _atrMultiplierSl.Value = value; }
/// <summary>Frozen ATR take-profit multiplier.</summary>
public decimal AtrMultiplierTp { get => _atrMultiplierTp.Value; set => _atrMultiplierTp.Value = value; }
/// <summary>Initialize the strategy parameters.</summary>
public SmaPullbackAtrExitsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("CandleType", "Finished signal candle type.", "Parameters");
_fastSmaLength = Param(nameof(FastSmaLength), 8).SetRange(1, int.MaxValue)
.SetDisplay("FastSmaLength", "Fast close SMA length.", "Parameters");
_slowSmaLength = Param(nameof(SlowSmaLength), 30).SetRange(2, int.MaxValue)
.SetDisplay("SlowSmaLength", "Slow close SMA length.", "Parameters");
_atrLength = Param(nameof(AtrLength), 14).SetRange(1, int.MaxValue)
.SetDisplay("AtrLength", "Wilder true-range window.", "Parameters");
_atrMultiplierSl = Param(nameof(AtrMultiplierSl), 1.2m).SetRange(0.000001m, decimal.MaxValue)
.SetDisplay("AtrMultiplierSl", "Frozen ATR stop-loss multiplier.", "Parameters");
_atrMultiplierTp = Param(nameof(AtrMultiplierTp), 2m).SetRange(0.000001m, decimal.MaxValue)
.SetDisplay("AtrMultiplierTp", "Frozen ATR take-profit multiplier.", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pending = null;
_fast = null;
_slow = null;
_atr = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
if (FastSmaLength >= SlowSmaLength) throw new InvalidOperationException("FastSmaLength must be smaller than SlowSmaLength.");
base.OnStarted2(time);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quote = new Subscription(DataType.Level1, Security);
quote.MarketData.BuildField = field;
SubscribeLevel1(quote).Start();
}
_fast = new SimpleMovingAverage { Length = FastSmaLength };
_slow = new SimpleMovingAverage { Length = SlowSmaLength };
_atr = new AverageTrueRange { Length = AtrLength };
SubscribeCandles(CandleType).Bind(_fast, _slow, _atr, ProcessCandle).Start();
}
private bool IsPending => _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed);
private void Submit(Sides side, decimal quantity, string comment)
{
_pending = CreateOrder(side, 0m, quantity);
_pending.Type = OrderTypes.Market;
_pending.Comment = comment;
RegisterOrder(_pending);
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal atr)
{
if (candle.State != CandleStates.Finished || !_fast.IsFormed || !_slow.IsFormed || !_atr.IsFormed
|| atr <= 0m || Position != 0m || IsPending || !IsFormedAndOnlineAndAllowTrading()) return;
Sides? side = candle.ClosePrice < fast && fast > slow ? Sides.Buy
: candle.ClosePrice > fast && fast < slow ? Sides.Sell : null;
if (side is null) return;
StartProtection(new Unit(atr * AtrMultiplierTp, UnitTypes.Absolute), new Unit(atr * AtrMultiplierSl, UnitTypes.Absolute),
useMarketOrders: true, isLocalStop: true);
Submit(side.Value, Volume, "0487Entry");
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import DateTime, Decimal, Math, TimeSpan, InvalidOperationException
from System.Globalization import CultureInfo, DateTimeStyles
from System.IO import File
from StockSharp.Algo.Indicators import ExponentialMovingAverage, SimpleMovingAverage, AverageTrueRange, AlligatorLine, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.BusinessEntities import Subscription
from StockSharp.Messages import Level1Fields, ITickTradeMessage, CandleStates, DataType, OrderStates, OrderTypes, Sides, Unit, UnitTypes
class sma_pullback_atr_exits_strategy(Strategy):
"""SMA pullbacks with entry-frozen ATR distances and native quote exits."""
def __init__(self):
super(sma_pullback_atr_exits_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("CandleType", "Finished signal candle type.", "Parameters")
self._fast_sma_length = self.Param("FastSmaLength", 8).SetRange(1, 2147483647) \
.SetDisplay("FastSmaLength", "Fast close SMA length.", "Parameters")
self._slow_sma_length = self.Param("SlowSmaLength", 30).SetRange(2, 2147483647) \
.SetDisplay("SlowSmaLength", "Slow close SMA length.", "Parameters")
self._atr_length = self.Param("AtrLength", 14).SetRange(1, 2147483647) \
.SetDisplay("AtrLength", "Wilder true-range window.", "Parameters")
self._atr_multiplier_sl = self.Param("AtrMultiplierSl", Decimal(1.2)).SetRange(Decimal(0.000001), Decimal.MaxValue) \
.SetDisplay("AtrMultiplierSl", "Frozen ATR stop-loss multiplier.", "Parameters")
self._atr_multiplier_tp = self.Param("AtrMultiplierTp", Decimal(2)).SetRange(Decimal(0.000001), Decimal.MaxValue) \
.SetDisplay("AtrMultiplierTp", "Frozen ATR take-profit multiplier.", "Parameters")
self._clear_state()
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastSmaLength(self):
return self._fast_sma_length.Value
@FastSmaLength.setter
def FastSmaLength(self, value):
self._fast_sma_length.Value = value
@property
def SlowSmaLength(self):
return self._slow_sma_length.Value
@SlowSmaLength.setter
def SlowSmaLength(self, value):
self._slow_sma_length.Value = value
@property
def AtrLength(self):
return self._atr_length.Value
@AtrLength.setter
def AtrLength(self, value):
self._atr_length.Value = value
@property
def AtrMultiplierSl(self):
return self._atr_multiplier_sl.Value
@AtrMultiplierSl.setter
def AtrMultiplierSl(self, value):
self._atr_multiplier_sl.Value = value
@property
def AtrMultiplierTp(self):
return self._atr_multiplier_tp.Value
@AtrMultiplierTp.setter
def AtrMultiplierTp(self, value):
self._atr_multiplier_tp.Value = value
def _clear_state(self):
self._pending = None
self._fast = None
self._slow = None
self._atr = None
def OnReseted(self):
super(sma_pullback_atr_exits_strategy, self).OnReseted()
self._clear_state()
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnStarted2(self, time):
if self.FastSmaLength >= self.SlowSmaLength:
raise InvalidOperationException("FastSmaLength must be smaller than SlowSmaLength.")
super(sma_pullback_atr_exits_strategy, self).OnStarted2(time)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quote = Subscription(DataType.Level1, self.Security)
quote.MarketData.BuildField = field
self.SubscribeLevel1(quote).Start()
self._fast = SimpleMovingAverage()
self._fast.Length = self.FastSmaLength
self._slow = SimpleMovingAverage()
self._slow.Length = self.SlowSmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
self.SubscribeCandles(self.CandleType).Bind(self._fast, self._slow, self._atr, self._process_candle).Start()
def _is_pending(self):
return self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed)
def _submit(self, side, quantity, comment):
self._pending = self.CreateOrder(side, Decimal(0), quantity)
self._pending.Type = OrderTypes.Market
self._pending.Comment = comment
self.RegisterOrder(self._pending)
@staticmethod
def _process(indicator, candle):
value = CandleIndicatorValue(indicator, candle)
value.IsFinal = True
result = indicator.Process(value)
return None if result.IsEmpty else result.GetValue[Decimal](None)
def _process_candle(self, candle, fast, slow, atr):
if candle.State != CandleStates.Finished or not self._fast.IsFormed or not self._slow.IsFormed or not self._atr.IsFormed or atr <= 0 or self.Position != 0 or self._is_pending() or not self.IsFormedAndOnlineAndAllowTrading():
return
side = Sides.Buy if candle.ClosePrice < fast and fast > slow else Sides.Sell if candle.ClosePrice > fast and fast < slow else None
if side is None:
return
self.StartProtection(Unit(atr * self.AtrMultiplierTp, UnitTypes.Absolute), Unit(atr * self.AtrMultiplierSl, UnitTypes.Absolute), useMarketOrders=True, isLocalStop=True)
self._submit(side, self.Volume, "0487Entry")
def CreateClone(self):
return sma_pullback_atr_exits_strategy()