Стратегия определяет развороты тренда с помощью индикатора Parabolic SAR. Перед входом в сделку она ожидает заданное число неудачных разворотов, после чего открывается в сторону нового тренда. Расстояния для стоп‑лосса и тейк‑профита задаются либо в пунктах, либо в процентах от цены входа.
Параметры
Reversal Mode – выбор расчёта дистанций: пункты или проценты.
Delta – минимальное движение цены между разворотами.
Negative Signals – сколько отрицательных разворотов должно произойти перед входом.
Stop Loss – расстояние защитного ордера от цены входа.
Take Profit – расстояние целевой прибыли от цены входа.
Candle Type – тип свечей для расчётов.
Логика
Подписка на свечи и расчёт Parabolic SAR.
При смене направления индикатора и движении цены не менее чем на Delta запоминается цена разворота.
Подсчитываются отрицательные развороты, когда цена шла против предыдущего тренда.
При достижении порога Negative Signals открывается позиция в новом направлении.
На каждой свече проверяются уровни стоп‑лосса и тейк‑профита согласно выбранному Reversal Mode.
Позиция закрывается при смене тренда или срабатывании защитных уровней.
Стратегия предназначена для следования за трендом и позволяет оптимизировать параметры delta, стоп‑лосс и тейк‑профит.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend-following breakout strategy based on Parabolic SAR reversals.
/// Opens trades after negative reversals and uses percentage-based SL/TP.
/// </summary>
public class BreakoutBarsTrendStrategy : Strategy
{
private readonly StrategyParam<int> _negatives;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<DataType> _candleType;
private ParabolicSar _parabolic;
private int _lastTrend; // -1, 0, 1
private int _negativeCounter;
public int Negatives
{
get => _negatives.Value;
set => _negatives.Value = value;
}
public decimal StopLossPct
{
get => _stopLossPct.Value;
set => _stopLossPct.Value = value;
}
public decimal TakeProfitPct
{
get => _takeProfitPct.Value;
set => _takeProfitPct.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public BreakoutBarsTrendStrategy()
{
_negatives = Param(nameof(Negatives), 1)
.SetNotNegative()
.SetDisplay("Negative Signals", "Negative reversals before entry", "General");
_stopLossPct = Param(nameof(StopLossPct), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfitPct = Param(nameof(TakeProfitPct), 4m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).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();
_parabolic = default;
_lastTrend = 0;
_negativeCounter = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_parabolic = new ParabolicSar();
Indicators.Add(_parabolic);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(TakeProfitPct, UnitTypes.Percent),
stopLoss: new Unit(StopLossPct, UnitTypes.Percent),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var sarResult = _parabolic.Process(candle);
if (!sarResult.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sarValue = sarResult.ToDecimal();
var trend = sarValue < candle.ClosePrice ? 1 : -1;
if (_lastTrend != 0 && _lastTrend != trend)
{
// Reversal detected
if (trend == 1 && Position < 0)
BuyMarket();
else if (trend == -1 && Position > 0)
SellMarket();
_negativeCounter++;
if (_negativeCounter > Negatives)
{
if (trend == 1 && Position <= 0)
{
BuyMarket();
}
else if (trend == -1 && Position >= 0)
{
SellMarket();
}
_negativeCounter = 0;
}
}
_lastTrend = trend;
}
}
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 ParabolicSar, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class breakout_bars_trend_strategy(Strategy):
def __init__(self):
super(breakout_bars_trend_strategy, self).__init__()
self._negatives = self.Param("Negatives", 1) \
.SetDisplay("Negative Signals", "Negative reversals before entry", "General")
self._stop_loss_pct = self.Param("StopLossPct", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit_pct = self.Param("TakeProfitPct", 4.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._parabolic = None
self._last_trend = 0
self._negative_counter = 0
@property
def negatives(self):
return self._negatives.Value
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def take_profit_pct(self):
return self._take_profit_pct.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(breakout_bars_trend_strategy, self).OnReseted()
self._parabolic = None
self._last_trend = 0
self._negative_counter = 0
def OnStarted2(self, time):
super(breakout_bars_trend_strategy, self).OnStarted2(time)
self._parabolic = ParabolicSar()
self.Indicators.Add(self._parabolic)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
self.StartProtection(
takeProfit=Unit(self.take_profit_pct, UnitTypes.Percent),
stopLoss=Unit(self.stop_loss_pct, UnitTypes.Percent),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
cv = CandleIndicatorValue(self._parabolic, candle)
sar_result = self._parabolic.Process(cv)
if not sar_result.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
sar_value = float(sar_result)
trend = 1 if sar_value < float(candle.ClosePrice) else -1
if self._last_trend != 0 and self._last_trend != trend:
if trend == 1 and self.Position < 0:
self.BuyMarket()
elif trend == -1 and self.Position > 0:
self.SellMarket()
self._negative_counter += 1
if self._negative_counter > int(self.negatives):
if trend == 1 and self.Position <= 0:
self.BuyMarket()
elif trend == -1 and self.Position >= 0:
self.SellMarket()
self._negative_counter = 0
self._last_trend = trend
def CreateClone(self):
return breakout_bars_trend_strategy()