Многофреймовый Parabolic SAR
Стратегия объединяет сигналы Parabolic SAR с разных таймфреймов. Лонг открывается, когда цена выше выбранных уровней SAR. Шорт появляется при падении цены ниже SAR. Возможны стоп-лосс, трейлинг-стоп и тейк-профит.
Детали
- Условия входа:
- Лонг: цена выше SAR согласно параметру
LongSource. - Шорт: цена ниже SAR согласно
ShortSource.
- Лонг: цена выше SAR согласно параметру
- Условия выхода:
- Пересечение цены с противоположной стороной SAR или срабатывание защиты.
- Индикаторы:
- Parabolic SAR на текущем таймфрейме
- Опционально Parabolic SAR на старшем и младшем таймфреймах
- Стопы: опциональные стоп-лосс, трейлинг-стоп, тейк-профит через StartProtection.
- Значения по умолчанию:
Acceleration= 0.02MaxAcceleration= 0.2StopLossPercent= 1TrailingPercent= 0.5TakeProfitPercent= 2
- Фильтры:
- Таймфреймы: основной 5м, старший 1д, младший 1м
- Индикаторы: Parabolic SAR
- Стопы: опционально
- Сложность: средняя
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>
/// Multi-timeframe Parabolic SAR strategy.
/// Parabolic SAR is calculated on the main, a higher and a lower timeframe. A long opens when the main candle closes above every SAR
/// selected by LongSource, a short when it closes below every SAR selected by ShortSource, reversing an opposite position.
/// A position closes when price crosses the main timeframe SAR the other way. Percent stop loss and take profit are handled by
/// protection and a percent trailing stop follows the best price since entry.
/// </summary>
public class MultiTimeframeParabolicSarStrategy : Strategy
{
/// <summary>
/// Which SAR levels must confirm an entry.
/// </summary>
public enum SarSources
{
/// <summary>
/// Main timeframe SAR only.
/// </summary>
Current,
/// <summary>
/// Main and higher timeframe SAR.
/// </summary>
CurrentAndHigher,
/// <summary>
/// Main and lower timeframe SAR.
/// </summary>
CurrentAndLower,
/// <summary>
/// Main, higher and lower timeframe SAR.
/// </summary>
All,
}
private readonly StrategyParam<decimal> _acceleration;
private readonly StrategyParam<decimal> _maxAcceleration;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _trailingPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<SarSources> _longSource;
private readonly StrategyParam<SarSources> _shortSource;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _higherCandleType;
private readonly StrategyParam<DataType> _lowerCandleType;
private decimal? _higherSar;
private decimal? _higherClose;
private decimal? _lowerSar;
private decimal? _lowerClose;
private decimal _bestPrice;
/// <summary>
/// Initial acceleration factor.
/// </summary>
public decimal Acceleration
{
get => _acceleration.Value;
set => _acceleration.Value = value;
}
/// <summary>
/// Maximum acceleration factor.
/// </summary>
public decimal MaxAcceleration
{
get => _maxAcceleration.Value;
set => _maxAcceleration.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Trailing stop percentage from the best price since entry.
/// </summary>
public decimal TrailingPercent
{
get => _trailingPercent.Value;
set => _trailingPercent.Value = value;
}
/// <summary>
/// Take profit percentage from entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// SAR levels price must be above for a long entry.
/// </summary>
public SarSources LongSource
{
get => _longSource.Value;
set => _longSource.Value = value;
}
/// <summary>
/// SAR levels price must be below for a short entry.
/// </summary>
public SarSources ShortSource
{
get => _shortSource.Value;
set => _shortSource.Value = value;
}
/// <summary>
/// Main candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Higher timeframe candle type.
/// </summary>
public DataType HigherCandleType
{
get => _higherCandleType.Value;
set => _higherCandleType.Value = value;
}
/// <summary>
/// Lower timeframe candle type.
/// </summary>
public DataType LowerCandleType
{
get => _lowerCandleType.Value;
set => _lowerCandleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MultiTimeframeParabolicSarStrategy()
{
_acceleration = Param(nameof(Acceleration), 0.02m)
.SetGreaterThanZero()
.SetDisplay("Acceleration", "Initial acceleration factor", "Indicators");
_maxAcceleration = Param(nameof(MaxAcceleration), 0.2m)
.SetGreaterThanZero()
.SetDisplay("Max Acceleration", "Maximum acceleration factor", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 1m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_trailingPercent = Param(nameof(TrailingPercent), 0.5m)
.SetNotNegative()
.SetDisplay("Trailing %", "Trailing stop percentage from the best price", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk");
_longSource = Param(nameof(LongSource), SarSources.All)
.SetDisplay("Long Source", "SAR levels price must be above for a long", "Signals");
_shortSource = Param(nameof(ShortSource), SarSources.All)
.SetDisplay("Short Source", "SAR levels price must be below for a short", "Signals");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Main timeframe", "General");
_higherCandleType = Param(nameof(HigherCandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Higher Candle Type", "Higher timeframe", "General");
_lowerCandleType = Param(nameof(LowerCandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Lower Candle Type", "Lower timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, HigherCandleType), (Security, LowerCandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_higherSar = null;
_higherClose = null;
_lowerSar = null;
_lowerClose = null;
_bestPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var sar = CreateSar();
var higherSar = CreateSar();
var lowerSar = CreateSar();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sar, ProcessCandle)
.Start();
SubscribeCandles(HigherCandleType)
.Bind(higherSar, (candle, value) =>
{
if (candle.State != CandleStates.Finished || !higherSar.IsFormed)
return;
_higherSar = value;
_higherClose = candle.ClosePrice;
})
.Start();
SubscribeCandles(LowerCandleType)
.Bind(lowerSar, (candle, value) =>
{
if (candle.State != CandleStates.Finished || !lowerSar.IsFormed)
return;
_lowerSar = value;
_lowerClose = candle.ClosePrice;
})
.Start();
StartProtection(
TakeProfitPercent > 0 ? new Unit(TakeProfitPercent, UnitTypes.Percent) : new Unit(),
StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit(),
useMarketOrders: true,
isLocalStop: true);
// The stops have to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sar);
DrawOwnTrades(area);
}
}
private ParabolicSar CreateSar()
{
return new ParabolicSar
{
Acceleration = Acceleration,
AccelerationStep = Acceleration,
AccelerationMax = MaxAcceleration,
};
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private bool IsAbove(SarSources source, decimal close, decimal sar)
{
if (close <= sar)
return false;
var useHigher = source is SarSources.CurrentAndHigher or SarSources.All;
var useLower = source is SarSources.CurrentAndLower or SarSources.All;
if (useHigher && (_higherSar is not decimal h || _higherClose is not decimal hc || hc <= h))
return false;
if (useLower && (_lowerSar is not decimal l || _lowerClose is not decimal lc || lc <= l))
return false;
return true;
}
private bool IsBelow(SarSources source, decimal close, decimal sar)
{
if (close >= sar)
return false;
var useHigher = source is SarSources.CurrentAndHigher or SarSources.All;
var useLower = source is SarSources.CurrentAndLower or SarSources.All;
if (useHigher && (_higherSar is not decimal h || _higherClose is not decimal hc || hc >= h))
return false;
if (useLower && (_lowerSar is not decimal l || _lowerClose is not decimal lc || lc >= l))
return false;
return true;
}
private void ProcessCandle(ICandleMessage candle, decimal sarValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (IsAbove(LongSource, close, sarValue) && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_bestPrice = close;
return;
}
if (IsBelow(ShortSource, close, sarValue) && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_bestPrice = close;
return;
}
if (Position > 0)
{
_bestPrice = Math.Max(_bestPrice, candle.HighPrice);
if (close < sarValue || (TrailingPercent > 0 && close <= _bestPrice * (1 - TrailingPercent / 100m)))
SellMarket(Position);
}
else if (Position < 0)
{
_bestPrice = _bestPrice == 0 ? candle.LowPrice : Math.Min(_bestPrice, candle.LowPrice);
if (close > sarValue || (TrailingPercent > 0 && close >= _bestPrice * (1 + TrailingPercent / 100m)))
BuyMarket(-Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class multi_timeframe_parabolic_sar_strategy(Strategy):
"""
Multi-timeframe Parabolic SAR strategy.
Parabolic SAR is calculated on the main, a higher and a lower timeframe. A long opens when the main candle closes above every SAR
selected by LongSource, a short when it closes below every SAR selected by ShortSource, reversing an opposite position.
A position closes when price crosses the main timeframe SAR the other way. Percent stop loss and take profit are handled by
protection and a percent trailing stop follows the best price since entry.
LongSource/ShortSource: Current, CurrentAndHigher, CurrentAndLower or All.
"""
def __init__(self):
super(multi_timeframe_parabolic_sar_strategy, self).__init__()
self._acceleration = self.Param("Acceleration", 0.02).SetGreaterThanZero().SetDisplay("Acceleration", "Initial acceleration factor", "Indicators")
self._max_acceleration = self.Param("MaxAcceleration", 0.2).SetGreaterThanZero().SetDisplay("Max Acceleration", "Maximum acceleration factor", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._trailing_percent = self.Param("TrailingPercent", 0.5).SetNotNegative().SetDisplay("Trailing %", "Trailing stop percentage from the best price", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0).SetNotNegative().SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk")
self._long_source = self.Param("LongSource", "All").SetDisplay("Long Source", "SAR levels price must be above for a long", "Signals")
self._short_source = self.Param("ShortSource", "All").SetDisplay("Short Source", "SAR levels price must be below for a short", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Main timeframe", "General")
self._higher_candle_type = self.Param("HigherCandleType", DataType.TimeFrame(TimeSpan.FromDays(1))).SetDisplay("Higher Candle Type", "Higher timeframe", "General")
self._lower_candle_type = self.Param("LowerCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Lower Candle Type", "Lower timeframe", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, self._higher_candle_type.Value), (self.Security, self._lower_candle_type.Value)]
def _reset_state(self):
self._higher_sar = None
self._higher_close = None
self._lower_sar = None
self._lower_close = None
self._best_price = 0.0
def OnReseted(self):
super(multi_timeframe_parabolic_sar_strategy, self).OnReseted()
self._reset_state()
def _create_sar(self):
sar = ParabolicSar()
sar.Acceleration = Decimal(self._acceleration.Value)
sar.AccelerationStep = Decimal(self._acceleration.Value)
sar.AccelerationMax = Decimal(self._max_acceleration.Value)
return sar
def OnStarted2(self, time):
super(multi_timeframe_parabolic_sar_strategy, self).OnStarted2(time)
self._reset_state()
sar = self._create_sar()
self._higher_ind = self._create_sar()
self._lower_ind = self._create_sar()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sar, self._process_candle).Start()
self.SubscribeCandles(self._higher_candle_type.Value).Bind(self._higher_ind, self._process_higher).Start()
self.SubscribeCandles(self._lower_candle_type.Value).Bind(self._lower_ind, self._process_lower).Start()
tp = float(self._take_profit_percent.Value)
sl = float(self._stop_loss_percent.Value)
self.StartProtection(
Unit(Decimal(tp), UnitTypes.Percent) if tp > 0 else Unit(),
Unit(Decimal(sl), UnitTypes.Percent) if sl > 0 else Unit(),
useMarketOrders=True, isLocalStop=True)
# The stops have to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sar)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_higher(self, candle, value):
if candle.State != CandleStates.Finished or not self._higher_ind.IsFormed:
return
self._higher_sar = float(value)
self._higher_close = float(candle.ClosePrice)
def _process_lower(self, candle, value):
if candle.State != CandleStates.Finished or not self._lower_ind.IsFormed:
return
self._lower_sar = float(value)
self._lower_close = float(candle.ClosePrice)
def _uses(self, source):
source = str(source)
return (source in ("CurrentAndHigher", "All"), source in ("CurrentAndLower", "All"))
def _is_above(self, source, close, sar):
if close <= sar:
return False
use_higher, use_lower = self._uses(source)
if use_higher and (self._higher_sar is None or self._higher_close <= self._higher_sar):
return False
if use_lower and (self._lower_sar is None or self._lower_close <= self._lower_sar):
return False
return True
def _is_below(self, source, close, sar):
if close >= sar:
return False
use_higher, use_lower = self._uses(source)
if use_higher and (self._higher_sar is None or self._higher_close >= self._higher_sar):
return False
if use_lower and (self._lower_sar is None or self._lower_close >= self._lower_sar):
return False
return True
def _process_candle(self, candle, sar_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
sar = float(sar_value)
trailing = float(self._trailing_percent.Value)
if self._is_above(self._long_source.Value, close, sar) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._best_price = close
return
if self._is_below(self._short_source.Value, close, sar) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._best_price = close
return
if self.Position > 0:
self._best_price = max(self._best_price, float(candle.HighPrice))
if close < sar or (trailing > 0 and close <= self._best_price * (1.0 - trailing / 100.0)):
self.SellMarket(self.Position)
elif self.Position < 0:
low = float(candle.LowPrice)
self._best_price = low if self._best_price == 0 else min(self._best_price, low)
if close > sar or (trailing > 0 and close >= self._best_price * (1.0 + trailing / 100.0)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return multi_timeframe_parabolic_sar_strategy()