Стратегия Trend RDS ищет направленные последовательности свечей. Если три завершённые свечи формируют строго повышающиеся минимумы, это трактуется как бычий импульс; три строго понижающиеся максимумы задают медвежий сигнал. Защитное правило блокирует сделку, когда те же три бара одновременно создают и повышающиеся минимумы, и понижающиеся максимумы — обычно это признак сужающегося треугольника, а не тренда. Параметр Reverse позволяет при необходимости инвертировать направление сделок.
Торговля ведётся только внутри заданного временного окна (по умолчанию 09:00–12:00). Когда окно открыто и появляется допустимый паттерн, стратегия закрывает противоположную позицию, открывает рыночную заявку по цене закрытия сигнальной свечи и выставляет стоп-лосс и тейк-профит в пунктах. Размер пункта вычисляется из шага цены инструмента, что повторяет логику оригинального советника MetaTrader. Дополнительный трейлинг-стоп переносит защитный уровень вперёд после прохождения цены на величину стопа плюс шаг трейлинга; пересмотр стопа выполняется только в пределах торговой сессии.
Объём позиции пересчитывается при каждом входе. Стратегия выделяет долю капитала портфеля, заданную параметром RiskPercent, и делит её на денежный риск, соответствующий текущему стопу. Такой подход масштабирует объём одновременно с размером счёта и шириной стопа, при этом уважая минимальный Volume. Любой параметр риска, равный нулю, отключает соответствующую функцию и позволяет торговать фиксированным объёмом либо без защитных ордеров.
Подробности
Условия входа: Три последовательные свечи с повышающимися минимумами открывают покупку (или продажу, если активирован Reverse). Три последовательные свечи с понижающимися максимумами открывают продажу (или покупку в режиме реверса). Сигналы игнорируются, если эти же три свечи одновременно удовлетворяют обоим условиям.
Направление: Работа в обе стороны с возможностью инверсии.
Условия выхода: Рыночный выход при достижении стоп-лосса, тейк-профита или трейлинг-стопа.
Стопы: Фиксированные стоп-лосс и тейк-профит в пунктах плюс ступенчатый трейлинг-стоп (оба трейлинг-параметра должны быть положительными).
Торговое окно: Сделки совершаются только между StartTime и EndTime (по умолчанию 09:00–12:00 по времени площадки).
Размер позиции: Расчёт по риску — доля капитала RiskPercent относительно текущей дистанции стопа (если рассчитать невозможно, используется Volume).
Значения по умолчанию:
StopLossPips = 30
TakeProfitPips = 65
TrailingStopPips = 0
TrailingStepPips = 5
RiskPercent = 3
StartTime = 09:00
EndTime = 12:00
Reverse = false
Фильтры:
Категория: Trend
Направление: Оба направления
Индикаторы: Прайс-экшен (максимумы/минимумы)
Стопы: Да
Сложность: Средняя
Таймфрейм: Внутридневной
Сезонность: Нет
Нейросети: Нет
Дивергенции: Нет
Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend recognition strategy based on three consecutive candles.
/// </summary>
public class TrendRdsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<TimeSpan> _startTime;
private readonly StrategyParam<TimeSpan> _endTime;
private readonly StrategyParam<bool> _reverse;
private decimal _prevHigh1;
private decimal _prevHigh2;
private decimal _prevHigh3;
private decimal _prevLow1;
private decimal _prevLow2;
private decimal _prevLow3;
private int _historyCount;
private decimal _entryPrice;
private decimal _stopLossPrice;
private decimal _takeProfitPrice;
/// <summary>
/// Type of candles to analyze.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop loss distance measured in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance measured in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance measured in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Trailing step measured in pips.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Percent of account equity to risk per trade.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Trading session start time (inclusive).
/// </summary>
public TimeSpan StartTime
{
get => _startTime.Value;
set => _startTime.Value = value;
}
/// <summary>
/// Trading session end time (exclusive).
/// </summary>
public TimeSpan EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>
/// Reverse the trade direction when enabled.
/// </summary>
public bool Reverse
{
get => _reverse.Value;
set => _reverse.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="TrendRdsStrategy"/> class.
/// </summary>
public TrendRdsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPips = Param(nameof(StopLossPips), 30)
.SetDisplay("Stop Loss (pips)", "Stop loss distance in pips", "Risk")
;
_takeProfitPips = Param(nameof(TakeProfitPips), 65)
.SetDisplay("Take Profit (pips)", "Take profit distance in pips", "Risk")
;
_trailingStopPips = Param(nameof(TrailingStopPips), 0)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk")
;
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step (pips)", "Trailing step increment", "Risk")
;
_riskPercent = Param(nameof(RiskPercent), 3m)
.SetDisplay("Risk %", "Percent of equity to risk", "Risk")
.SetRange(0m, 100m);
_startTime = Param(nameof(StartTime), new TimeSpan(0, 0, 0))
.SetDisplay("Session Start", "Trading session start time", "Session");
_endTime = Param(nameof(EndTime), new TimeSpan(23, 59, 0))
.SetDisplay("Session End", "Trading session end time", "Session");
_reverse = Param(nameof(Reverse), false)
.SetDisplay("Reverse", "Trade in the opposite direction", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh1 = 0m;
_prevHigh2 = 0m;
_prevHigh3 = 0m;
_prevLow1 = 0m;
_prevLow2 = 0m;
_prevLow3 = 0m;
_historyCount = 0;
_entryPrice = 0m;
_stopLossPrice = 0m;
_takeProfitPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrailingStopPips > 0 && TrailingStepPips <= 0)
{
throw new InvalidOperationException("Trailing step must be greater than zero when trailing stop is enabled.");
}
if (StartTime >= EndTime)
{
throw new InvalidOperationException("Session start time must be earlier than end time.");
}
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
// Skip unfinished candles to work on closed bars only.
if (candle.State != CandleStates.Finished)
return;
var pip = GetPipSize();
// Handle protective exits even outside of the session window.
if (HandleActivePositionExits(candle))
{
UpdateHistory(candle);
return;
}
var candleTime = candle.OpenTime.TimeOfDay;
var inSession = candleTime >= StartTime && candleTime < EndTime;
if (inSession && _historyCount >= 3)
{
var higherLows = _prevLow1 > _prevLow2 && _prevLow2 > _prevLow3;
var lowerHighs = _prevHigh1 < _prevHigh2 && _prevHigh2 < _prevHigh3;
var conflict = higherLows && lowerHighs;
var longSignal = false;
var shortSignal = false;
if (!conflict)
{
if (higherLows)
{
if (Reverse)
shortSignal = true;
else
longSignal = true;
}
if (lowerHighs)
{
if (Reverse)
longSignal = true;
else
shortSignal = true;
}
}
if (longSignal && Position <= 0)
{
EnterLong(candle, pip);
}
else if (shortSignal && Position >= 0)
{
EnterShort(candle, pip);
}
// Update trailing logic after potential entries.
ApplyTrailing(candle, pip);
}
UpdateHistory(candle);
}
private void EnterLong(ICandleMessage candle, decimal pip)
{
var stopOffset = StopLossPips > 0 ? StopLossPips * pip : 0m;
var takeOffset = TakeProfitPips > 0 ? TakeProfitPips * pip : 0m;
var volume = CalculateVolume(stopOffset);
if (Position < 0)
{
volume += Math.Abs(Position);
}
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_stopLossPrice = stopOffset > 0m ? _entryPrice - stopOffset : 0m;
_takeProfitPrice = takeOffset > 0m ? _entryPrice + takeOffset : 0m;
}
private void EnterShort(ICandleMessage candle, decimal pip)
{
var stopOffset = StopLossPips > 0 ? StopLossPips * pip : 0m;
var takeOffset = TakeProfitPips > 0 ? TakeProfitPips * pip : 0m;
var volume = CalculateVolume(stopOffset);
if (Position > 0)
{
volume += Math.Abs(Position);
}
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_stopLossPrice = stopOffset > 0m ? _entryPrice + stopOffset : 0m;
_takeProfitPrice = takeOffset > 0m ? _entryPrice - takeOffset : 0m;
}
private void ApplyTrailing(ICandleMessage candle, decimal pip)
{
if (TrailingStopPips <= 0)
return;
var trailingStop = TrailingStopPips * pip;
var trailingStep = TrailingStepPips * pip;
if (trailingStop <= 0m || trailingStep <= 0m || _entryPrice == 0m)
return;
var price = candle.ClosePrice;
if (Position > 0)
{
var profit = price - _entryPrice;
if (profit > trailingStop + trailingStep)
{
var threshold = price - (trailingStop + trailingStep);
if (_stopLossPrice == 0m || _stopLossPrice < threshold)
{
_stopLossPrice = price - trailingStop;
}
}
}
else if (Position < 0)
{
var profit = _entryPrice - price;
if (profit > trailingStop + trailingStep)
{
var threshold = price + (trailingStop + trailingStep);
if (_stopLossPrice == 0m || _stopLossPrice > threshold)
{
_stopLossPrice = price + trailingStop;
}
}
}
}
private bool HandleActivePositionExits(ICandleMessage candle)
{
var positionVolume = Math.Abs(Position);
if (positionVolume == 0m)
return false;
if (Position > 0)
{
if (_stopLossPrice > 0m && candle.LowPrice <= _stopLossPrice)
{
SellMarket(positionVolume);
ResetPositionState();
return true;
}
if (_takeProfitPrice > 0m && candle.HighPrice >= _takeProfitPrice)
{
SellMarket(positionVolume);
ResetPositionState();
return true;
}
}
else
{
if (_stopLossPrice > 0m && candle.HighPrice >= _stopLossPrice)
{
BuyMarket(positionVolume);
ResetPositionState();
return true;
}
if (_takeProfitPrice > 0m && candle.LowPrice <= _takeProfitPrice)
{
BuyMarket(positionVolume);
ResetPositionState();
return true;
}
}
return false;
}
private decimal CalculateVolume(decimal stopOffset)
{
var baseVolume = Volume > 0m ? Volume : 1m;
var equity = Portfolio?.CurrentValue ?? 0m;
if (stopOffset <= 0m || equity <= 0m)
return baseVolume;
var step = Security?.PriceStep ?? 0m;
var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 1m;
if (step <= 0m)
return baseVolume;
var stepsToStop = stopOffset / step;
if (stepsToStop <= 0m)
return baseVolume;
var riskAmount = equity * RiskPercent / 100m;
if (riskAmount <= 0m)
return baseVolume;
var riskPerUnit = stepsToStop * stepPrice;
if (riskPerUnit <= 0m)
return baseVolume;
var quantity = riskAmount / riskPerUnit;
if (quantity <= 0m)
return baseVolume;
return Math.Max(quantity, baseVolume);
}
private void UpdateHistory(ICandleMessage candle)
{
_prevHigh3 = _prevHigh2;
_prevHigh2 = _prevHigh1;
_prevHigh1 = candle.HighPrice;
_prevLow3 = _prevLow2;
_prevLow2 = _prevLow1;
_prevLow1 = candle.LowPrice;
if (_historyCount < 3)
{
_historyCount++;
}
}
private void ResetPositionState()
{
_entryPrice = 0m;
_stopLossPrice = 0m;
_takeProfitPrice = 0m;
}
private decimal GetPipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 1m;
var pip = step;
var decimals = GetScale(step);
if (decimals == 3 || decimals == 5)
{
pip *= 10m;
}
return pip;
}
private static int GetScale(decimal value)
{
var bits = decimal.GetBits(value);
return (bits[3] >> 16) & 0xFF;
}
}
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 datatype_extensions import *
from indicator_extensions import *
class trend_rds_strategy(Strategy):
"""Trend recognition via three consecutive candle highs/lows with SL/TP management."""
def __init__(self):
super(trend_rds_strategy, self).__init__()
self._sl_points = self.Param("StopLossPoints", 30).SetDisplay("Stop Loss", "Stop loss distance", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 65).SetDisplay("Take Profit", "Take profit distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(trend_rds_strategy, self).OnReseted()
self._prev_high1 = 0
self._prev_high2 = 0
self._prev_high3 = 0
self._prev_low1 = 0
self._prev_low2 = 0
self._prev_low3 = 0
self._history_count = 0
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
def OnStarted2(self, time):
super(trend_rds_strategy, self).OnStarted2(time)
self._prev_high1 = 0
self._prev_high2 = 0
self._prev_high3 = 0
self._prev_low1 = 0
self._prev_low2 = 0
self._prev_low3 = 0
self._history_count = 0
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
sl = self._sl_points.Value
tp = self._tp_points.Value
# Handle exits
if self.Position > 0:
if self._stop_price > 0 and low <= self._stop_price:
self.SellMarket()
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
self._update_history(candle)
return
if self._take_price > 0 and high >= self._take_price:
self.SellMarket()
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
self._update_history(candle)
return
elif self.Position < 0:
if self._stop_price > 0 and high >= self._stop_price:
self.BuyMarket()
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
self._update_history(candle)
return
if self._take_price > 0 and low <= self._take_price:
self.BuyMarket()
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
self._update_history(candle)
return
if self._history_count >= 3:
higher_lows = self._prev_low1 > self._prev_low2 and self._prev_low2 > self._prev_low3
lower_highs = self._prev_high1 < self._prev_high2 and self._prev_high2 < self._prev_high3
conflict = higher_lows and lower_highs
if not conflict:
if higher_lows and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._stop_price = close - sl if sl > 0 else 0
self._take_price = close + tp if tp > 0 else 0
elif lower_highs and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._stop_price = close + sl if sl > 0 else 0
self._take_price = close - tp if tp > 0 else 0
self._update_history(candle)
def _update_history(self, candle):
self._prev_high3 = self._prev_high2
self._prev_high2 = self._prev_high1
self._prev_high1 = float(candle.HighPrice)
self._prev_low3 = self._prev_low2
self._prev_low2 = self._prev_low1
self._prev_low1 = float(candle.LowPrice)
if self._history_count < 3:
self._history_count += 1
def CreateClone(self):
return trend_rds_strategy()