Sma Pullback Atr Exits
Правила торговли
На завершённой свече после формирования двух SMA закрытия и ATR Уайлдера купить только при close < быстрая SMA и быстрая SMA > медленная; продать в шорт только при close > быстрая SMA и быстрая SMA < медленная. Равенства не дают входа. Вход объёмом Volume только из нуля с ожиданием активных заявок. На сигнале фиксируются SL = ATR×AtrMultiplierSl и TP = ATR×AtrMultiplierTp. Локальная рыночная native-защита привязывает расстояния к фактическому исполнению и отслеживает bid/ask. Поздний ATR не двигает уровни. Защита закрывает весь остаток; противоположный сигнал не разворачивает открытую позицию.
Параметры
| Параметр | По умолчанию |
|---|---|
CandleType |
TimeSpan.FromMinutes(30).TimeFrame() |
FastSmaLength |
8 |
SlowSmaLength |
30 |
AtrLength |
14 |
AtrMultiplierSl |
1.2 |
AtrMultiplierTp |
2 |
Проверка
Оба языка проверяются на имеющемся архиве BTC/TON: независимые формулы, стороны и объёмы заявок, реальные исполнения, позиции и выходы. Датированные ценовые/факторные и календарные данные проверяют механику; перенос дат OHLC/объёмов не является многолетней рыночной историей. Две корзины рангов, TopN=1 и SMA=2 — явные профили для малого универсума. Исходные длинные периоды и defaults проверяются отдельно. Доходность не заявляется.
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()