PresentTrend RMI Synergy
標準Relative Momentum Indexと終値SMAによるエントリー後、保護方向にだけ動くSMA/ATR価格ストップで決済します。収益性は主張しません。
指標とエントリー
既定は確定した5分足です。標準RMIはd[t]=close[t]-close[t-RmiMomentum]、lag3を使います。直近RmiPeriod=21個の勢いのうち、Uは正のdの平均(その他はゼロ)、Dは負のdの絶対値の平均(その他はゼロ)です。RMI=100*U/(U+D)。分母ゼロならエントリー値はありません。標準の移動平均による相対モメンタム計算です。
終値SMAと標準Wilder ATRはSuperTrendLength=5を共有します。ATRは前回終値からのギャップを含み、拡大平均から始まり、その後Wilder平滑化を使います。3指標の形成後、ポジションなしからのみ入ります。RMI>60かつclose>SMAならロング、RMI<40かつclose<SMAならショート。Volumeの成行注文です。保有中は新しいエントリーシグナルを無視します。
Directionは任意の型付きSidesフィルターです。C#のnull/PythonのNoneは双方向、Sides.Buyはロングのみ、Sides.Sellはショートのみ。エントリーを制限し、ストップは常に実際の保有側を閉じます。文字列や数値の方向コードは使いません。
トレーリングストップ
width=SuperTrendMultiplier*ATR、既定倍率4。ロングは初期stop=SMA-widthで、その後の各確定足でmax(previousStop,SMA-width)に更新します。ショートはSMA+widthで開始し、min(previousStop,SMA+width)に更新します。保有中は緩めません。初期値はシグナルのSMA/ATRから決まり、後の約定価格に依存しません。維持したSuperTrendパラメーター名はこのSMA中心の帯を設定します。
正のbid≤ロングstopまたは正のask≥ショートstopで実際の全量を成行決済します。更新後は確定closeも検査し、新stopに達した現在価格なら即時決済できます。足の間の実際の気配でも発動します。新stopを現在・以後の観測価格に適用し、その足の以前のhigh/lowには遡及適用しません。現在RMIが空でもSMA/ATRは保護を更新できます。
ローカル価格監視です。気配による足の間の保護にはLevel1 bid/askが必要で、なければcloseの検査が残ります。取引所のネイティブ逆指値を送信せず、約定価格はしきい値と異なる場合があります。未完了注文は重複を防ぎますが、指標とstopは進みます。決済足では再エントリーせず、後の条件を満たす足で可能です。EMA、6時間待機、反対シグナルによる即時反転、別の利確はありません。
| パラメーター | 既定値 |
|---|---|
RmiPeriod |
21、正 |
RmiMomentum |
3、正の終値lag |
SuperTrendLength |
5、正の共通SMA/ATR期間 |
SuperTrendMultiplier |
4、正のATR幅 |
Direction |
未設定、双方向 |
CandleType |
5分 |
C#とPythonは既存BTC/TON履歴から勢いの和、SMA、Wilder ATR、ストップを独立計算します。各エントリー、執行可能価格での決済、実際のポジション、両方向とフィルター、反対シグナルの無視、狭い足間ストップ、6時間未満のエントリー間隔を検証します。
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()