Trend RDS busca secuencias direccionales claras en la acción del precio. Cuando tres velas completadas forman mínimos estrictamente más altos, trata la estructura como un tramo de tendencia alcista. Tres máximos estrictamente más bajos marcan una configuración bajista. Una regla de protección bloquea entradas cuando las mismas tres barras crean simultáneamente tanto mínimos más altos como máximos más bajos, lo que generalmente indica un triángulo contrayente en lugar de un movimiento direccional. La estrategia puede invertir opcionalmente la dirección a través del parámetro Reverse.
El trading está limitado a una ventana de tiempo configurable (predeterminado 09:00–12:00). Cuando la ventana está abierta y aparece un patrón válido, la estrategia cierra cualquier exposición opuesta, abre una nueva posición a mercado al cierre de la vela, y coloca órdenes de stop-loss y take-profit medidas en pips. La distancia en pips se deriva del paso de precio del instrumento, reflejando la lógica original de MetaTrader. Un trailing stop opcional mueve el stop de protección hacia adelante una vez que el precio avanza por la distancia de trailing más el paso de trailing. Los ajustes de trailing se evalúan solo mientras la ventana de sesión está activa.
El tamaño de la posición se recalcula en cada entrada. La estrategia asigna una fracción del capital del portafolio definida por RiskPercent y la divide por el riesgo monetario representado por la distancia de stop elegida. Esto produce un dimensionamiento dinámico que escala con el tamaño de la cuenta y el ancho del stop, respetando el valor mínimo Volume. Establecer cualquier parámetro relacionado con el riesgo en cero deshabilita esa función, permitiendo entradas de tamaño fijo o sin protección cuando se desee.
Detalles
Criterios de entrada: Tres velas consecutivas con mínimos más altos activan largos (o cortos cuando Reverse es verdadero). Tres mínimos consecutivos más bajos activan cortos (o largos en modo reverso). Las señales se ignoran si las mismas tres barras también satisfacen ambas condiciones simultáneamente.
Largo/Corto: Ambas direcciones con un interruptor de reversión opcional.
Criterios de salida: Salidas a mercado cuando los niveles de stop-loss, take-profit o trailing stop rastreados son violados.
Stops: Stop-loss y take-profit fijos en pips con un trailing stop incremental (requiere que ambos parámetros de trailing sean positivos).
Ventana de tiempo: Opera solo entre StartTime y EndTime (predeterminado 09:00–12:00 hora de la bolsa).
Dimensionamiento de posición: Dimensionamiento basado en riesgo usando RiskPercent del capital del portafolio relativo a la distancia de stop actual (recurre a Volume si el dimensionamiento no puede calcularse).
Valores predeterminados:
StopLossPips = 30
TakeProfitPips = 65
TrailingStopPips = 0
TrailingStepPips = 5
RiskPercent = 3
StartTime = 09:00
EndTime = 12:00
Reverse = false
Filtros:
Categoría: Tendencia
Dirección: Ambos
Indicadores: Acción del precio (máximos/mínimos)
Stops: Sí
Complejidad: Intermedio
Marco temporal: Intradía
Estacionalidad: No
Redes neuronales: No
Divergencia: No
Nivel de riesgo: Medio
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()