Стратегия Trailing Stop FrCnSar является портом двух MQL-скриптов: TrailingStopFrCnSARen_v4.mq4 и OrderBalansEN_v3_4.mq4. Первый скрипт управлял активными позициями, подтягивая стоп-лоссы по свечам, фракталам, индикатору Parabolic SAR или скорости движения цены. Второй выводил на графике баланс и список открытых ордеров. В StockSharp логика переписана на уровень чистых позиций: стратегия наблюдает один инструмент, рассчитывает уровни стопа выбранным методом и закрывает позицию при достижении барьерного значения. Дополнительно предусмотрен текстовый лог, который заменяет визуальную панель OrderBalans.
Стратегия не открывает сделки самостоятельно. Она используется как защитный модуль: поддерживает внутреннее состояние (последние экстремумы, фракталы, оценку скорости), применяет заданные фильтры и выставляет рыночную заявку на закрытие, когда цена пересекает виртуальный стоп.
Логика работы
Подписка на свечи типа CandleType и обработка только закрытых баров.
Сохранение небольшого буфера максимумов и минимумов для поиска экстремумов и фракталов без обращения к запрещённым функциям.
При активном режиме «скорость» расчёт среднего изменения цены в пунктах на основе VelocityPeriod.
Формирование кандидатного стоп-уровня в зависимости от режима:
Проверка фильтров: требование уже существующего стопа, обязательная прибыль, остановка на уровне безубытка, использование средней цены позиции.
Обновление стопа только если новая величина улучшает текущую не менее чем на StepPoints пунктов.
При касании баром стоп-уровня отправка рыночной заявки на закрытие позиции.
При включённом LogOrderSummary запись в лог баланса, размера позиции, цены входа, текущего стопа и нереализованной прибыли — аналог панели OrderBalans.
Режимы трейлинга
Candle – подтягивание за последними свечными экстремумами с учётом DeltaPoints.
Fractal – использование ближайшего пятибарного фрактала.
Velocity – расстояние до стопа зависит от изменения средней скорости (разница двух последних значений, умноженная на VelocityMultiplier).
Parabolic – следование за Parabolic SAR c настраиваемыми шагом и максимальным ускорением.
FixedPoints – постоянный отступ, аналог функции «больше 4 пунктов» из оригинала.
Off – отключение подтягивания.
Параметры
Имя
Тип
Значение по умолчанию
Описание
Mode
TrailingStopMode
Candle
Выбранный алгоритм трейлинга.
CandleType
DataType
15-минутные свечи
Таймфрейм, по которому рассчитываются уровни.
DeltaPoints
int
0
Отступ в пунктах относительно базового уровня.
StepPoints
int
0
Минимальное улучшение (в пунктах) для обновления стопа.
FixedDistancePoints
int
50
Дистанция в режиме фиксированного трейлинга.
TrailOnlyProfit
bool
true
Разрешать подтягивание только после выхода позиции в прибыль.
TrailOnlyBreakEven
bool
false
Остановиться после достижения безубытка.
RequireExistingStop
bool
false
Игнорировать обновления, пока стоп не рассчитан.
UseGeneralBreakEven
bool
false
Использовать среднюю цену позиции для проверки прибыльности (аналог MQL-функции TProfit).
VelocityPeriod
int
30
Количество закрытий для усреднения скорости.
VelocityMultiplier
decimal
1
Коэффициент влияния скорости на расстояние до стопа.
ParabolicStep
decimal
0.02
Шаг ускорения Parabolic SAR.
ParabolicMaximum
decimal
0.2
Максимальное ускорение Parabolic SAR.
LogOrderSummary
bool
true
Включить вывод текстовой сводки о позиции.
TradeVolume
decimal
1
Объём, используемый при закрытии позиции.
Отличия от оригинала
StockSharp оперирует чистым объёмом, поэтому привязка к отдельным ордерам и магическим номерам удалена.
Velocity рассчитывается как среднее изменение цены в пунктах, что может слегка отличаться от оригинального индикатора, но сохраняет общий характер поведения.
Графические объекты из OrderBalans заменены логами. Это упрощает порт и не требует ручного управления объектами на графике.
Вместо OrderModify применяются методы BuyMarket/SellMarket для немедленного закрытия нетто-позиции.
Рекомендации по использованию
Для визуального контроля подключите стратегию к графику и активируйте отображение свечей и сделок. В режиме Parabolic удобно видеть точки SAR.
Настраивайте DeltaPoints и StepPoints с учётом шага цены инструмента (PriceStep/MinPriceStep).
Если важна строгость оригинальной логики, оставьте TrailOnlyProfit включённым.
Отключите LogOrderSummary, если стратегия запускается массово и необходимо сократить объём журналов.
В режиме Velocity экспериментируйте с VelocityMultiplier: большие значения позволяют быстрее фиксировать прибыль при резких импульсах.
Индикаторы
Parabolic SAR (ParabolicSar)
Буферы максимумов и минимумов свечей (для свечного и фрактального режимов)
Усреднённая скорость закрытий (для режима Velocity)
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trailing Stop FrCnSar: EMA crossover with ATR trailing stops.
/// </summary>
public class TrailingStopFrCnSarStrategy : 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;
private decimal _bestPrice;
public TrailingStopFrCnSarStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 30)
.SetDisplay("Slow EMA Length", "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; _bestPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _bestPrice = 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 (close > _bestPrice) _bestPrice = close;
if (close <= _bestPrice - atrVal * 2m || (fastVal < slowVal && _prevFast >= _prevSlow)) { SellMarket(); _entryPrice = 0; _bestPrice = 0; }
}
else if (Position < 0)
{
if (close < _bestPrice) _bestPrice = close;
if (close >= _bestPrice + atrVal * 2m || (fastVal > slowVal && _prevFast <= _prevSlow)) { BuyMarket(); _entryPrice = 0; _bestPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; _bestPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; _bestPrice = 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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class trailing_stop_fr_cn_sar_strategy(Strategy):
def __init__(self):
super(trailing_stop_fr_cn_sar_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 30) \
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._best_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(trailing_stop_fr_cn_sar_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._best_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close > self._best_price:
self._best_price = close
if close <= self._best_price - av * 2.0 or (fv < sv and self._prev_fast >= self._prev_slow):
self.SellMarket()
self._entry_price = 0.0
self._best_price = 0.0
elif self.Position < 0:
if close < self._best_price:
self._best_price = close
if close >= self._best_price + av * 2.0 or (fv > sv and self._prev_fast <= self._prev_slow):
self.BuyMarket()
self._entry_price = 0.0
self._best_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self._best_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self._best_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(trailing_stop_fr_cn_sar_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._best_price = 0.0
def CreateClone(self):
return trailing_stop_fr_cn_sar_strategy()