Эта стратегия представляет собой перенос советника MetaTrader 4 pSAR_alert.mq4 на платформу StockSharp. Исходный скрипт лишь воспроизводил звуковой сигнал, когда Parabolic SAR переходил с одной стороны цены на другую. В версии для StockSharp сигнал преобразован в реальные рыночные заявки, поэтому стратегия автоматически торгует теми же переключениями индикатора.
Логика торговли
Стратегия подписывается на заданный тип свечей и рассчитывает индикатор Parabolic SAR с классическими параметрами: начальный шаг 0.02 и максимум 0.2.
Для каждой завершённой свечи вычисляется текущее значение SAR, дополнительно сохраняется связка «SAR + цена закрытия» предыдущей свечи.
Если на предыдущей свече цена находилась ниже SAR, а на текущей закрылась выше него, индикатор «переворачивается» вниз – стратегия открывает длинную позицию или разворачивает короткую.
Если на предыдущей свече цена находилась выше SAR, а на текущей закрылась ниже, фиксируется переворот вверх – стратегия открывает короткую позицию или разворачивает длинную.
Объём сделки рассчитывается как сумма базового объёма стратегии и абсолютного значения текущей позиции, что гарантирует полное закрытие старого направления перед открытием нового.
При запуске вызывается StartProtection(), чтобы встроенная защита StockSharp отслеживала открытые позиции во время технических сбоев.
Параметры
Параметр
Значение по умолчанию
Описание
AccelerationFactor
0.02
Начальный шаг ускорения индикатора Parabolic SAR, определяет скорость его движения вслед за ценой.
MaxAccelerationFactor
0.2
Максимальное ускорение, ограничивающее реакцию SAR в сильном тренде.
CandleType
Тайм‑фрейм 5 минут
Тип рыночных данных, которые подаются на вход индикатора; позволяет выбрать другой период или вид свечей.
Все параметры реализованы через StrategyParam<T>, поэтому их можно оптимизировать непосредственно в StockSharp Designer.
Работа индикатора
Подписаться на поток свечей с помощью SubscribeCandles.
Привязать поток к индикатору ParabolicSar, чтобы StockSharp автоматически поддерживал его расчёт.
В обработчике хранить предыдущие значения SAR и цены закрытия, сравнивать их с текущими данными.
Определять перевороты, когда SAR переходит из-под цены над ней или наоборот.
Выполнять BuyMarket либо SellMarket и писать подробные сообщения в журнал.
Практические рекомендации
Сигналы формируются только после закрытия свечи, что снижает количество ложных срабатываний внутри бара.
Параметры по умолчанию повторяют исходный советник, однако их можно изменить, чтобы сделать систему более агрессивной или плавной.
Лучшие результаты достигаются на инструментах с выраженными трендами, где перевороты SAR происходят не слишком часто.
При наличии области графика стратегия автоматически выводит свечи, линию Parabolic SAR и собственные сделки для визуального контроля.
Файлы
CS/ParabolicSarCrossoverAlertStrategy.cs – реализация стратегии на C#.
README.md – документация на английском языке.
README_zh.md – документация на китайском языке.
README_ru.md – документация на русском языке.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Parabolic SAR Crossover: EMA crossover with ATR stops.
/// </summary>
public class ParabolicSarCrossoverAlertStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public ParabolicSarCrossoverAlertStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 30)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (fastVal < slowVal && _prevFast >= _prevSlow)
{
SellMarket();
_entryPrice = 0;
}
else if (close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow)
{
BuyMarket();
_entryPrice = 0;
}
else if (close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow)
{
_entryPrice = close;
BuyMarket();
}
else if (fastVal < slowVal && _prevFast >= _prevSlow)
{
_entryPrice = close;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
}
}
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_crossover_alert_strategy(Strategy):
def __init__(self):
super(parabolic_sar_crossover_alert_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10).SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 30).SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period", "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_crossover_alert_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
def OnStarted2(self, time):
super(parabolic_sar_crossover_alert_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_length.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast_ema, slow_ema, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
if self._prev_fast == 0 or self._prev_slow == 0 or atr_val <= 0:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = candle.ClosePrice
if self.Position > 0:
if (fast_val < slow_val and self._prev_fast >= self._prev_slow) or close <= self._entry_price - atr_val * 2:
self.SellMarket()
self._entry_price = 0
elif self.Position < 0:
if (fast_val > slow_val and self._prev_fast <= self._prev_slow) or close >= self._entry_price + atr_val * 2:
self.BuyMarket()
self._entry_price = 0
if self.Position == 0:
if fast_val > slow_val and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fast_val < slow_val and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return parabolic_sar_crossover_alert_strategy()