Parabolic SAR Limit — это порт советника ytg_Parabolic_exp.mq4, написанного для MetaTrader 4. Стратегия непрерывно отслеживает значение индикатора Parabolic SAR и удерживает возле него лимитные заявки на покупку и продажу. Когда рынок задевает лимит, позиция открывается, а далее стратегия контролирует выход по стоп-лоссу или тейк-профиту, используя экстремумы свечи, что полностью повторяет исходное MQL-решение.
Логика работы
Стратегия подписывается на свечи с настраиваемым таймфреймом (по умолчанию 4 часа) и рассчитывает Parabolic SAR с такими же параметрами step и maximum, как в MT4.
На каждой закрытой свече выполняется последовательность проверок:
Если точка SAR расположена ниже минимума свечи и лучшая цена Bid находится не ближе, чем на MinOrderDistancePoints пунктов выше SAR, создаётся (или переставляется) лимитная заявка на покупку по цене SAR.
Если точка SAR выше максимума свечи и лучшая цена Ask находится не ближе, чем на MinOrderDistancePoints пунктов ниже SAR, создаётся (или переставляется) лимитная заявка на продажу по цене SAR.
Для каждой стороны поддерживается только одна активная лимитная заявка. При смещении SAR предыдущая заявка отменяется и выставляется новая по актуальному уровню.
После исполнения лимитной заявки расстояния стоп-лосса и тейк-профита (в пунктах) конвертируются в абсолютные цены через PriceStep инструмента. Эти уровни запоминаются как виртуальные защитные границы.
На каждой новой свече проверяется, достигла ли цена сохранённых уровней. Если экстремум свечи касается стопа или тейка, позиция закрывается вызовом ClosePosition(), а защитное состояние сбрасывается.
Параметры
CandleType — таймфрейм свечей для сигналов. По умолчанию 4 часа, что соответствует параметру timeframe в MT4.
SarStep — коэффициент ускорения Parabolic SAR (step). Определяет скорость подтягивания SAR к цене.
SarMaximum — максимальное ускорение (maximum). Ограничивает скорость движения SAR.
StopLossPoints — расстояние от цены входа до стоп-лосса в пунктах. Значение 0 отключает стоп.
TakeProfitPoints — расстояние от цены входа до тейк-профита в пунктах. Значение 0 отключает тейк.
MinOrderDistancePoints — аналог MODE_STOPLEVEL в MT4. Лимитные заявки выставляются только если рынок находится дальше этого порога от уровня SAR.
OrderVolume — объём каждой лимитной заявки. Следите за тем, чтобы он совпадал с VolumeStep инструмента.
Все расстояния в пунктах преобразуются в цены через шаг PriceStep, поэтому стратегия корректно работает на инструментах с любым числом знаков.
Поведение в сделках
Возможна одновременная работа в обе стороны: если SAR меняет сторону относительно цены, в стакане могут сосуществовать buy limit и sell limit.
Лимитные заявки постоянно синхронизируются с актуальным значением SAR, устаревшие заявки отменяются до подачи новых.
Защитные уровни реализованы программно через анализ свечей, поскольку высокоуровневый API StockSharp не прикрепляет SL/TP напрямую к отложенным заявкам.
Для точной фильтрации расстояний используются лучшие цены Bid/Ask; если поток Level 1 недоступен, применяется закрытие свечи как приблизительная оценка текущей цены.
Особенности портирования
Параметр MinOrderDistancePoints по умолчанию равен 0. При необходимости установите величину, соответствующую ограничению брокера на минимальное расстояние до рынка.
Защитные уровни сбрасываются автоматически после закрытия позиции или отмены лимитной заявки, что предотвращает наложение старых данных на новые сделки.
В коде C# добавлены подробные комментарии на английском языке, описывающие работу с индикатором и жизненный цикл заявок через высокоуровневый API.
Рекомендации по использованию
Подайте поток заявок Level 1, чтобы условия по расстоянию до рынка рассчитывались по лучшим ценам. При отсутствии котировок оцените, подходит ли закрытие свечи в качестве прокси.
Проверьте PriceStep и VolumeStep выбранного инструмента: параметры в пунктах и объём должны преобразовываться в допустимые значения.
Если требуется более точный контроль стоп-лосса, используйте более мелкий таймфрейм, чтобы проверка защитных уровней выполнялась чаще.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Parabolic SAR strategy: enters on SAR flip (price crosses SAR level).
/// Buys when price moves above SAR, sells when price drops below SAR.
/// </summary>
public class ParabolicSarLimitStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevEma;
private decimal _prevClose;
private decimal _entryPrice;
private bool _wasBullish;
private bool _hasPrev;
public ParabolicSarLimitStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 14)
.SetDisplay("EMA Length", "EMA period acting as dynamic SAR proxy.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR for volatility.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevEma = 0;
_prevClose = 0;
_entryPrice = 0;
_wasBullish = false;
_hasPrev = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevEma = 0;
_prevClose = 0;
_entryPrice = 0;
_wasBullish = false;
_hasPrev = false;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (!_hasPrev)
{
_prevEma = emaVal;
_prevClose = close;
_wasBullish = close > emaVal;
_hasPrev = true;
return;
}
var isBullish = close > emaVal;
var flip = isBullish != _wasBullish;
// Exit on SAR flip
if (Position > 0 && flip && !isBullish)
{
SellMarket();
_entryPrice = 0;
}
else if (Position < 0 && flip && isBullish)
{
BuyMarket();
_entryPrice = 0;
}
// Entry on flip
if (Position == 0 && flip)
{
if (isBullish)
{
_entryPrice = close;
BuyMarket();
}
else
{
_entryPrice = close;
SellMarket();
}
}
_prevEma = emaVal;
_prevClose = close;
_wasBullish = isBullish;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_limit_strategy(Strategy):
def __init__(self):
super(parabolic_sar_limit_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._ema_length = self.Param("EmaLength", 14).SetDisplay("EMA Length", "EMA period acting as dynamic SAR proxy", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR for volatility", "Indicators")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(parabolic_sar_limit_strategy, self).OnReseted()
self._prev_ema = 0
self._prev_close = 0
self._entry_price = 0
self._was_bullish = False
self._has_prev = False
def OnStarted2(self, time):
super(parabolic_sar_limit_strategy, self).OnStarted2(time)
self._prev_ema = 0
self._prev_close = 0
self._entry_price = 0
self._was_bullish = False
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(ema, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
if not self._has_prev:
self._prev_ema = ema_val
self._prev_close = close
self._was_bullish = close > ema_val
self._has_prev = True
return
is_bullish = close > ema_val
flip = is_bullish != self._was_bullish
if self.Position > 0 and flip and not is_bullish:
self.SellMarket()
self._entry_price = 0
elif self.Position < 0 and flip and is_bullish:
self.BuyMarket()
self._entry_price = 0
if self.Position == 0 and flip:
if is_bullish:
self._entry_price = close
self.BuyMarket()
else:
self._entry_price = close
self.SellMarket()
self._prev_ema = ema_val
self._prev_close = close
self._was_bullish = is_bullish
def CreateClone(self):
return parabolic_sar_limit_strategy()