Стратегия Separate Trade
Обзор
Стратегия Separate Trade представляет собой порт MetaTrader 5 эксперта «Separate trade» на платформу StockSharp. В версии для StockSharp сохранены все исходные фильтры, но они реализованы через высокоуровневый API, что облегчает визуальную настройку и оптимизацию. Основная идея — искать точки разворота, когда рынок демонстрирует низкую волатильность и минимальное расхождение между скользящими средними. В рамках StockSharp используется только одна суммарная позиция, что соответствует ограничению на количество позиций в оригинальном советнике.
Индикаторы и данные
- Две скользящие средние с общим методом сглаживания (SMA, EMA, SMMA или линейно-взвешенная) и единым источником цен.
- Два индикатора ATR с независимыми периодами и порогами для длинных и коротких сделок.
- Два индикатора стандартного отклонения, рассчитывающие волатильность по выбранной цене и контролирующие отсутствие расширения диапазона.
- Источник данных задаётся параметром
CandleType, что позволяет подключать стратегию к любому таймфрейму или типу свечей, поддерживаемому StockSharp.
Параметры
| Параметр |
Назначение |
Значение по умолчанию |
TradeVolume |
Объём заявки в лотах. |
1 |
SlowMaPeriod / FastMaPeriod |
Периоды медленной и быстрой скользящих средних. |
65 / 14 |
MaMethod |
Метод сглаживания для обеих средних (Simple, Exponential, Smoothed, LinearWeighted). |
Exponential |
PriceType |
Тип цены для расчёта скользящих и стандартного отклонения. |
Close |
StopLossBuyPips / StopLossSellPips |
Расстояние стоп-лосса для длинных и коротких позиций в пунктах (0 отключает стоп). |
50 |
TakeProfitBuyPips / TakeProfitSellPips |
Расстояние тейк-профита в пунктах (0 отключает тейк). |
50 |
TrailingStopPips |
Базовая дистанция трейлинг-стопа. |
5 |
TrailingStepPips |
Минимальное движение в прибыль перед переносом трейлинг-стопа (обязательно > 0 при активном трейлинге). |
5 |
MaxPositions |
Максимальное количество одновременных нетто-позиций. В реализации StockSharp всегда ведётся одна суммарная позиция. |
1 |
DeltaBuyPips / DeltaSellPips |
Максимально допустимая дистанция между быстрой и медленной средними. Значение 0 отключает фильтр. |
2 |
AtrPeriodBuy / AtrPeriodSell |
Периоды ATR для длинных и коротких сделок. |
26 |
AtrLevelBuy / AtrLevelSell |
Пороги ATR, которые не должны быть превышены перед входом. |
0.0016 |
StdDevPeriodBuy / StdDevPeriodSell |
Периоды стандартного отклонения. |
54 |
StdDevLevelBuy / StdDevLevelSell |
Допустимый максимум стандартного отклонения. |
0.0051 |
CandleType |
Тип свечей, используемый стратегией. |
TimeSpan.FromMinutes(15) |
Логика торговли
- Работа только с завершёнными свечами. Все расчёты выполняются после получения свечи в состоянии
Finished, что повторяет защиту по новому бару из кода MT5.
- Фильтры для длинных сделок. Медленная MA должна находиться ниже быстрой, ATR — меньше
AtrLevelBuy, стандартное отклонение — ниже StdDevLevelBuy, а расстояние между средними — меньше DeltaBuyPips (если параметр больше нуля).
- Фильтры для коротких сделок. Аналогичные требования, но с использованием параметров для продаж и обратного неравенства между средними.
- Контроль повторных входов. После открытия сделки сохраняется время свечи. Новый вход в ту же сторону возможен только на следующей свече, что предотвращает множественные заявки внутри одного бара.
- Исполнение ордеров. Используются рыночные заявки. При появлении сигнала в противоположную сторону стратегия сначала закрывает текущую позицию за счёт добавочного объёма
Volume + Math.Abs(Position).
Управление рисками
- Пересчёт пунктов. Стопы и цели переводятся в цены через
Security.PriceStep. Если инструмент котируется с точностью 3 или 5 знаков после запятой, размер пункта равен PriceStep * 10, как и в MQL-версии.
- Стоп-лосс и тейк-профит. Стратегия отслеживает уровни и закрывает позицию, когда минимум или максимум свечи пересекает соответствующий уровень. Установка параметра в 0 отключает механизм.
- Трейлинг-стоп. Когда прибыль превышает сумму
TrailingStopPips + TrailingStepPips, стоп переносится, сохраняя фиксированную дистанцию до цены. Это повторяет логику функции Trailing() из оригинального кода.
Особенности реализации
- StockSharp оперирует суммарной позицией, поэтому
MaxPositions служит больше для совместимости и предотвращает открытие новых сделок, если значение меньше либо равно текущей величине позиции.
- Все параметры созданы через
StrategyParam, что делает стратегию полностью совместимой с Designer и оптимизатором.
- При включении трейлинг-стопа проверяется положительность шага. В случае нарушения требование стратегия записывает ошибку и останавливается, аналогично проверке
Trailing Step в MQL.
- Комментарии и сообщения оставлены на английском языке в соответствии с правилами репозитория.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Separate trade strategy using dual EMA crossover with ATR volatility filter.
/// Trades when fast EMA crosses slow EMA and ATR confirms adequate volatility.
/// </summary>
public class SeparateTradeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _atrPeriod;
private decimal? _prevFast;
private decimal? _prevSlow;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public SeparateTradeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 65)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null;
_prevSlow = null;
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
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 fast, decimal slow, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_prevFast == null || _prevSlow == null)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast;
_prevSlow = slow;
// Require minimum ATR relative to price
if (atr < candle.ClosePrice * 0.0005m)
return;
// Golden cross
if (!prevAbove && currAbove)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
// Death cross
else if (prevAbove && !currAbove)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
}
}
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 separate_trade_strategy(Strategy):
def __init__(self):
super(separate_trade_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 65) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
def OnReseted(self):
super(separate_trade_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(separate_trade_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, atr, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value, atr_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
# Require minimum ATR relative to price
av = float(atr_value)
close = float(candle.ClosePrice)
if av < close * 0.0005:
return
# Golden cross
if not prev_above and curr_above:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
# Death cross
elif prev_above and not curr_above:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return separate_trade_strategy()