PresentTrend RMI Synergy
RMI nativo alineado con SMA del cierre, seguido de stop de precio SMA/ATR que solo se ajusta a favor de la protección. No se afirma rentabilidad.
Indicadores y entrada
Velas terminadas de cinco minutos por defecto. RMI nativo usa d[t]=close[t]-close[t-RmiMomentum], lag3. En los últimos RmiPeriod=21 impulsos, U es la media de d positivos (cero en los demás), D la media de valores absolutos de d negativos (cero en los demás). RMI=100*U/(U+D); denominador cero no produce entrada. Es el cálculo nativo de impulso relativo con medias móviles.
SMA del cierre y ATR Wilder nativo comparten SuperTrendLength=5. ATR incluye saltos al cierre anterior, comienza con media creciente y después Wilder. Tras formar los tres indicadores, solo desde cero: largo si RMI>60 y close>SMA; corto si RMI<40 y close<SMA. Orden de mercado por Volume. Una posición abierta ignora nuevas señales de entrada.
Direction es un filtro tipado opcional Sides: null en C#/None en Python permite ambos; Sides.Buy solo largos, Sides.Sell solo cortos. Solo filtra entradas; el stop siempre cierra el lado real. Sin códigos de dirección numéricos o cadenas.
Stop dinámico
width=SuperTrendMultiplier*ATR, multiplicador4 por defecto. Largo inicializa stop=SMA-width y cada vela posterior usa max(previousStop,SMA-width); corto inicia SMA+width y usa min(previousStop,SMA+width). Nunca se afloja. Nace de SMA/ATR de la señal, independientemente del precio ejecutado; los nombres SuperTrend conservados configuran estas bandas centradas en SMA.
Bid positivo≤stop largo o ask positivo≥stop corto cierra toda la posición real a mercado. Tras actualizar el stop también se comprueba close; si alcanza el nuevo nivel, sale inmediatamente. Cotizaciones entre velas pueden activarlo. El nuevo nivel se aplica al precio observado actual/futuro, no al high/low anterior de la vela. SMA/ATR puede seguir actualizando la protección con RMI vacío.
Es monitoreo local; protección intravela por cotizaciones requiere Level1 bid/ask, y sin ellas quedan los cierres. No envía stop nativo al mercado; la ejecución puede diferir del umbral. Una orden pendiente bloquea duplicados, pero indicadores/stop avanzan. La vela de salida no reentra; una posterior elegible sí. Sin EMA, espera de seis horas, inversión inmediata por señal opuesta ni take-profit separado.
| Parámetro |
Valor |
RmiPeriod |
21, positivo |
RmiMomentum |
3, lag positivo del cierre |
SuperTrendLength |
5, período SMA/ATR positivo compartido |
SuperTrendMultiplier |
4, anchura ATR positiva |
Direction |
Sin valor: ambos |
CandleType |
5 minutos |
Pruebas C# y Python calculan independientemente sumas de impulso, SMA, ATR Wilder y stops en el archivo BTC/TON. Comprueban cada entrada, salida a precio ejecutable y posición real, ambos lados y filtros, señales opuestas mantenidas, stops ajustados entre velas y entradas con menos de seis horas de separación.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Opens flat-position trades on native RMI momentum and SMA alignment,
/// then exits only at a ratcheted SMA/Wilder ATR price stop.
/// </summary>
public class PresentTrendRmiSynergyStrategy : Strategy
{
private readonly StrategyParam<int> _rmiPeriod;
private readonly StrategyParam<int> _rmiMomentum;
private readonly StrategyParam<int> _superTrendLength;
private readonly StrategyParam<decimal> _superTrendMultiplier;
private readonly StrategyParam<Sides?> _direction;
private readonly StrategyParam<DataType> _candleType;
private decimal? _stopPrice;
private Order _pendingOrder;
/// <summary>Initializes RMI21 with momentum3, SMA/ATR5, multiplier4 and both directions on five-minute candles.</summary>
public PresentTrendRmiSynergyStrategy()
{
_rmiPeriod = Param(nameof(RmiPeriod), 21).SetGreaterThanZero()
.SetDisplay("RMI Period", "Native rolling mean period of positive/negative momentum", "Indicators");
_rmiMomentum = Param(nameof(RmiMomentum), 3).SetGreaterThanZero()
.SetDisplay("RMI Momentum", "Closed-bar lag of each close-price momentum sample", "Indicators");
_superTrendLength = Param(nameof(SuperTrendLength), 5).SetGreaterThanZero()
.SetDisplay("SuperTrend Length", "Shared close SMA and Wilder ATR period for trailing bands", "Protection");
_superTrendMultiplier = Param(nameof(SuperTrendMultiplier), 4m).SetGreaterThanZero()
.SetDisplay("SuperTrend Multiplier", "ATR width of the ratcheted SMA price stop", "Protection");
_direction = Param<Sides?>(nameof(Direction), null)
.SetDisplay("Direction", "Entry side filter: Buy for long, Sell for short, unset for both", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Finished candles supplying RMI, SMA and ATR", "General");
OrderRegistering += order => _pendingOrder = order;
}
/// <summary>Positive native RMI rolling mean period.</summary>
public int RmiPeriod { get => _rmiPeriod.Value; set => _rmiPeriod.Value = value; }
/// <summary>Positive close-price momentum lag, in finished candles.</summary>
public int RmiMomentum { get => _rmiMomentum.Value; set => _rmiMomentum.Value = value; }
/// <summary>Positive shared SMA and Wilder ATR period.</summary>
public int SuperTrendLength { get => _superTrendLength.Value; set => _superTrendLength.Value = value; }
/// <summary>Positive ATR multiplier of the trailing stop.</summary>
public decimal SuperTrendMultiplier { get => _superTrendMultiplier.Value; set => _superTrendMultiplier.Value = value; }
/// <summary>Allowed entry side; null permits both. Stops always close the held side.</summary>
public Sides? Direction { get => _direction.Value; set => _direction.Value = value; }
/// <summary>Finished candle type; five minutes by default.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
private void ResetState()
{
_stopPrice = null;
_pendingOrder = null;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (RmiPeriod <= 0 || RmiMomentum <= 0 || SuperTrendLength <= 0 || SuperTrendMultiplier <= 0m
|| Direction is Sides direction && direction is not (Sides.Buy or Sides.Sell))
throw new ArgumentException("Periods and ATR multiplier must be positive; direction must be Buy, Sell or unset for both.");
ResetState();
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveQuote).Start();
}
var rmi = new RelativeMomentumIndex { Length = RmiPeriod, MomentumPeriod = RmiMomentum };
var mean = new SimpleMovingAverage { Length = SuperTrendLength };
var atr = new AverageTrueRange { Length = SuperTrendLength };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx([rmi, mean, atr], ProcessCandle, allowEmpty: true).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, mean);
DrawOwnTrades(area);
}
}
private bool IsPending => _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
private void Submit(Sides side, decimal volume, string comment)
=> RegisterOrder(new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = volume,
Comment = comment,
});
private bool TryStopExit(decimal price)
{
if (Position == 0m || _stopPrice is not decimal stop || IsPending || !IsFormedAndOnlineAndAllowTrading())
return false;
if (Position > 0m ? price > stop : price < stop)
return false;
Submit(Position > 0m ? Sides.Sell : Sides.Buy, Math.Abs(Position), "RMI ATR trailing stop");
return true;
}
private void ObserveQuote(Level1ChangeMessage quote)
{
var field = Position > 0m ? Level1Fields.BestBidPrice : Level1Fields.BestAskPrice;
if (quote.TryGetDecimal(field) is decimal price && price > 0m)
TryStopExit(price);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished || values[1].IsEmpty || !values[1].IsFormed
|| values[2].IsEmpty || !values[2].IsFormed)
return;
var mean = values[1].ToDecimal();
var width = values[2].ToDecimal() * SuperTrendMultiplier;
if (Position != 0m)
{
var candidate = Position > 0m ? mean - width : mean + width;
_stopPrice = _stopPrice is decimal previous ? Position > 0m ? Math.Max(previous, candidate) : Math.Min(previous, candidate) : candidate;
TryStopExit(candle.ClosePrice);
return;
}
if (values[0].IsEmpty || !values[0].IsFormed || IsPending || !IsFormedAndOnlineAndAllowTrading())
return;
var rmi = values[0].ToDecimal();
Sides? side = rmi > 60m && candle.ClosePrice > mean ? Sides.Buy
: rmi < 40m && candle.ClosePrice < mean ? Sides.Sell : null;
if (side is not Sides entry || Direction is Sides allowed && entry != allowed)
return;
_stopPrice = entry == Sides.Buy ? mean - width : mean + width;
Submit(entry, Volume, "RMI SMA aligned entry");
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Array, ArgumentException, Decimal, Nullable, TimeSpan
from StockSharp.Messages import CandleStates, DataType, Level1Fields, OrderStates, OrderTypes, Sides
from StockSharp.BusinessEntities import Order, Subscription
from StockSharp.Algo.Indicators import AverageTrueRange, IIndicator, RelativeMomentumIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class present_trend_rmi_synergy_strategy(Strategy):
def __init__(self):
super(present_trend_rmi_synergy_strategy, self).__init__()
self._rmi_period = self.Param("RmiPeriod", 21).SetGreaterThanZero() \
.SetDisplay("RMI Period", "Native rolling mean period of positive/negative momentum", "Indicators")
self._rmi_momentum = self.Param("RmiMomentum", 3).SetGreaterThanZero() \
.SetDisplay("RMI Momentum", "Closed-bar lag of each close-price momentum sample", "Indicators")
self._super_trend_length = self.Param("SuperTrendLength", 5).SetGreaterThanZero() \
.SetDisplay("SuperTrend Length", "Shared close SMA and Wilder ATR period for trailing bands", "Protection")
self._super_trend_multiplier = self.Param("SuperTrendMultiplier", Decimal(4)).SetGreaterThanZero() \
.SetDisplay("SuperTrend Multiplier", "ATR width of the ratcheted SMA price stop", "Protection")
self._direction = self.Param[Nullable[Sides]]("Direction", None) \
.SetDisplay("Direction", "Entry side filter: Buy for long, Sell for short, unset for both", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Finished candles supplying RMI, SMA and ATR", "General")
self._reset_state()
self.OrderRegistering += self._observe_order
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def _reset_state(self):
self._stop_price = None
self._pending_order = None
def OnReseted(self):
super(present_trend_rmi_synergy_strategy, self).OnReseted()
self._reset_state()
def _observe_order(self, order):
self._pending_order = order
def OnStarted2(self, time):
super(present_trend_rmi_synergy_strategy, self).OnStarted2(time)
direction = self._direction.Value
if self._rmi_period.Value <= 0 or self._rmi_momentum.Value <= 0 or self._super_trend_length.Value <= 0 \
or self._super_trend_multiplier.Value <= 0 \
or direction is not None and direction != Sides.Buy and direction != Sides.Sell:
raise ArgumentException("Periods and ATR multiplier must be positive; direction must be Buy, Sell or unset for both.")
self._reset_state()
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_quote).Start()
rmi = RelativeMomentumIndex()
rmi.Length = self._rmi_period.Value
rmi.MomentumPeriod = self._rmi_momentum.Value
mean = SimpleMovingAverage()
mean.Length = self._super_trend_length.Value
atr = AverageTrueRange()
atr.Length = self._super_trend_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(Array[IIndicator]([rmi, mean, atr]), self.OnProcess, True).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, mean)
self.DrawOwnTrades(area)
def _is_pending(self):
order = self._pending_order
return order is not None and order.State != OrderStates.Done and order.State != OrderStates.Failed
def _submit(self, side, volume, comment):
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = volume
order.Comment = comment
self.RegisterOrder(order)
def _try_stop_exit(self, price):
stop = self._stop_price
if self.Position == 0 or stop is None or self._is_pending() or not self.IsFormedAndOnlineAndAllowTrading():
return False
if price > stop if self.Position > 0 else price < stop:
return False
self._submit(Sides.Sell if self.Position > 0 else Sides.Buy, abs(self.Position), "RMI ATR trailing stop")
return True
def _observe_quote(self, quote):
field = Level1Fields.BestBidPrice if self.Position > 0 else Level1Fields.BestAskPrice
if quote.Changes.ContainsKey(field) and quote.Changes[field] > 0:
self._try_stop_exit(quote.Changes[field])
def OnProcess(self, candle, values):
if candle.State != CandleStates.Finished or values[1].IsEmpty or not values[1].IsFormed \
or values[2].IsEmpty or not values[2].IsFormed:
return
mean = values[1].GetValue[Decimal](None)
width = values[2].GetValue[Decimal](None) * self._super_trend_multiplier.Value
if self.Position != 0:
candidate = mean - width if self.Position > 0 else mean + width
self._stop_price = candidate if self._stop_price is None \
else max(self._stop_price, candidate) if self.Position > 0 else min(self._stop_price, candidate)
self._try_stop_exit(candle.ClosePrice)
return
if values[0].IsEmpty or not values[0].IsFormed or self._is_pending() or not self.IsFormedAndOnlineAndAllowTrading():
return
rmi = values[0].GetValue[Decimal](None)
side = Sides.Buy if rmi > 60 and candle.ClosePrice > mean \
else Sides.Sell if rmi < 40 and candle.ClosePrice < mean else None
direction = self._direction.Value
if side is None or direction is not None and side != direction:
return
self._stop_price = mean - width if side == Sides.Buy else mean + width
self._submit(side, self.Volume, "RMI SMA aligned entry")
def CreateClone(self):
return present_trend_rmi_synergy_strategy()