Trendcapture — это перенос советника MetaTrader MQL/7772/Trendcapture.mq4 на высокоуровневый API StockSharp. Исходный алгоритм отслеживает направление Parabolic SAR и открывает сделки только тогда, когда ADX показывает слабый тренд. После закрытия позиции он выбирает направление следующей сделки по результату предыдущей, а при достижении небольшой прибыли переносит стоп-лосс в точку безубытка.
Версия для StockSharp сохраняет эти особенности, используя связки индикаторов и вспомогательные методы регистрации заявок. Сигналы рассчитываются исключительно на закрытых свечах выбранного таймфрейма.
Логика торговли
Индикаторы
Parabolic SAR (ParabolicSar) с настраиваемым шагом и максимальным ускорением.
Average Directional Index (AverageDirectionalIndex), откуда берётся основное значение ADX.
Вход в позицию
Открытой может быть только одна позиция.
Длинная позиция открывается, если:
Текущая «желаемая» ориентация (по итогу последней сделки) — покупка.
Свеча закрылась выше значения SAR.
ADX ниже 20, что соответствует условию слабого тренда в оригинале.
Короткая позиция строится зеркально: желаемое направление — продажа, закрытие ниже SAR, ADX ниже 20.
Управление позицией
После исполнения заявки выставляются стоп-лосс и тейк-профит на расстояниях StopLossPoints и TakeProfitPoints, пересчитанных через шаг цены инструмента.
Как только плавающая прибыль достигает GuardPoints, стоп переносится на цену входа — аналог OrderModify из MQL.
При закрытии позиции направление обновляется: прибыльная сделка сохраняет ориентацию, убыточная или нулевая — меняет её на противоположную.
Параметры
Параметр
Описание
Значение по умолчанию
CandleType
Тип свечей для расчёта сигналов.
Часовые свечи
SarStep
Начальный шаг Parabolic SAR.
0.02
SarMax
Максимальное ускорение Parabolic SAR.
0.2
AdxPeriod
Период сглаживания ADX.
14
TakeProfitPoints
Дистанция до тейк-профита в шагах цены.
180
StopLossPoints
Дистанция до стоп-лосса в шагах цены.
50
GuardPoints
Количество пунктов до перевода стопа в безубыток.
5
MaximumRisk
Коэффициент масштабирования объёма (значение 0.03 повторяет размер лота из оригинала).
0.03
Рекомендации по использованию
Убедитесь, что для инструмента задан PriceStep или хотя бы MinStep, иначе пересчёт пунктов в цены будет некорректным.
Свойство Volume задаёт базовый объём при MaximumRisk = 0.03. Увеличение коэффициента риска пропорционально увеличит подаваемый объём.
Стратегия работает только рыночными ордерами и сразу выставляет защитные заявки, поэтому при отсутствии позиции в стакане не остаётся «забытых» ордеров.
Перенос стопа в безубыток реализован через отмену текущей стоп-заявки и постановку новой по цене входа, что полностью повторяет поведение эксперта MetaTrader.
Файлы
CS/TrendcaptureStrategy.cs — реализация стратегии на C#.
README.md — описание на английском языке.
README_zh.md — перевод на китайский язык.
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>
/// Trendcapture strategy - EMA trend with ADX strength filter.
/// Buys when close is above EMA and ADX indicates trending.
/// Sells when close is below EMA and ADX indicates trending.
/// </summary>
public class TrendcaptureStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevEma;
private bool _hasPrev;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int AdxPeriod { get => _adxPeriod.Value; set => _adxPeriod.Value = value; }
public decimal AdxThreshold { get => _adxThreshold.Value; set => _adxThreshold.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public TrendcaptureStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetDisplay("EMA Period", "EMA lookback", "Indicators");
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetDisplay("ADX Period", "ADX lookback", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 30m)
.SetDisplay("ADX Threshold", "Minimum ADX for trending", "Levels");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevClose = 0m; _prevEma = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = EmaPeriod };
var slow = new ExponentialMovingAverage { Length = EmaPeriod * 3 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!_hasPrev) { _prevClose = fast; _prevEma = slow; _hasPrev = true; return; }
if (_prevClose <= _prevEma && fast > slow && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (_prevClose >= _prevEma && fast < slow && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
_prevClose = fast; _prevEma = slow;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class trendcapture_strategy(Strategy):
def __init__(self):
super(trendcapture_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._adx_period = self.Param("AdxPeriod", 14).SetDisplay("ADX Period", "ADX lookback", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 30.0).SetDisplay("ADX Threshold", "Minimum ADX for trending", "Levels")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False
@property
def ema_period(self): return self._ema_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(trendcapture_strategy, self).OnReseted()
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False
def OnStarted2(self, time):
super(trendcapture_strategy, self).OnStarted2(time)
self._has_prev = False
fast = ExponentialMovingAverage(); fast.Length = self.ema_period
slow = ExponentialMovingAverage(); slow.Length = self.ema_period * 3
sub = self.SubscribeCandles(self.candle_type)
sub.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished: return
f = float(fast); s = float(slow)
if not self._has_prev: self._prev_fast = f; self._prev_slow = s; self._has_prev = True; return
if self._prev_fast <= self._prev_slow and f > s and self.Position <= 0:
if self.Position < 0: self.BuyMarket()
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and f < s and self.Position >= 0:
if self.Position > 0: self.SellMarket()
self.SellMarket()
self._prev_fast = f; self._prev_slow = s
def CreateClone(self): return trendcapture_strategy()