PresentTrend RMI Synergy
RMI nativo alinhado à SMA do fechamento, seguido de stop de preço SMA/ATR que só aperta a proteção. Não afirma lucratividade.
Indicadores e entrada
Candles concluídos de cinco minutos por padrão. RMI nativo usa d[t]=close[t]-close[t-RmiMomentum], lag3. Nos últimos RmiPeriod=21 impulsos, U é a média dos d positivos (zero nos demais), D a média dos módulos dos d negativos (zero nos demais). RMI=100*U/(U+D); denominador zero não fornece entrada. É o cálculo nativo de impulso relativo com médias móveis.
SMA do fechamento e ATR Wilder nativo compartilham SuperTrendLength=5. ATR inclui gaps ao fechamento anterior, inicia com média crescente e depois Wilder. Após formar os três indicadores, entra somente de zero: compra se RMI>60 e close>SMA; venda se RMI<40 e close<SMA. Ordem a mercado de Volume. Uma posição aberta ignora novos sinais de entrada.
Direction é filtro opcional tipado Sides: null em C#/None em Python permite ambos; Sides.Buy apenas comprado, Sides.Sell apenas vendido. Filtra entradas; o stop sempre fecha o lado real. Sem códigos numéricos ou strings de direção.
Stop móvel
width=SuperTrendMultiplier*ATR, multiplicador padrão4. Compra inicializa stop=SMA-width; cada candle posterior usa max(previousStop,SMA-width). Venda inicializa SMA+width e usa min(previousStop,SMA+width). Nunca afrouxa. Nasce da SMA/ATR do sinal, independente da execução; os nomes SuperTrend preservados configuram estas bandas centradas em SMA.
Bid positivo≤stop comprado ou ask positivo≥stop vendido fecha toda a posição real a mercado. Após atualizar também verifica close; atingir o novo nível pode fechar imediatamente. Cotações entre candles podem disparar. O novo stop vale para preço observado atual/futuro, sem aplicar ao high/low anterior do candle. SMA/ATR pode continuar atualizando proteção com RMI vazio.
É monitoramento local: proteção por cotações entre candles exige Level1 bid/ask; sem elas continuam os fechamentos. Não envia stop nativo à bolsa; execução pode diferir do limiar. Ordem pendente bloqueia duplicados, mas indicadores/stop avançam. O candle de saída não reentra; um posterior elegível pode. Sem EMA, espera de seis horas, inversão imediata por sinal oposto ou take-profit separado.
| Parâmetro |
Padrão |
RmiPeriod |
21, positivo |
RmiMomentum |
3, lag positivo do fechamento |
SuperTrendLength |
5, período positivo compartilhado SMA/ATR |
SuperTrendMultiplier |
4, largura ATR positiva |
Direction |
Sem valor: ambos |
CandleType |
5 minutos |
Testes C# e Python calculam independentemente somas de impulso, SMA, ATR Wilder e stops no arquivo BTC/TON existente. Verificam cada entrada, saída a preço executável e posição real, ambos os lados/filtros, sinais opostos mantidos, stops apertados entre candles e entradas com menos de seis horas.
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()