Línea ATR causal controlada por RSI del cierre o MFI de precio típico y volumen. Los cruces confirmados con la línea de dos velas atrás abren, invierten completamente o cierran posiciones.
Línea y señales
Solo velas terminadas, cinco minutos por defecto. ATR Wilder nativo y el oscilador seleccionado comparten Length=14, positivo. No se registra el oscilador no seleccionado. Se espera a que ambos estén formados:
Oscilador ≥50: candidato low - Multiplier * ATR; línea = máximo del candidato y la línea anterior.
Oscilador <50: candidato high + Multiplier * ATR; línea = mínimo del candidato y la línea anterior.
Primera línea formada = candidato seleccionado, sin inventar historia. Multiplier=1, positivo.
ATR incluye saltos respecto al cierre anterior, empieza con media creciente y después Wilder. RSI suaviza ganancias/pérdidas del cierre por Wilder y conserva el resultado anterior si no cambia el cierre. MFI usa typical=(high+low+close)/3 por volumen: típico creciente aporta flujo positivo, decreciente negativo e igual ninguno. Sobre las últimas Length velas, MFI = 100 * positiveSum / (positiveSum + negativeSum), o 100 sin flujo negativo.
El cruce largo bruto en t exige line[t-1] <= line[t-3] y line[t] > line[t-2]; el corto usa ≥ y < respectivamente. Requiere cuatro valores formados.
Confirma largo solo si existe un corto bruto anterior más reciente que el largo bruto anterior, o aún no existe largo anterior. Corto es simétrico. Después de comparar se registra cada señal bruta, incluidas las rechazadas para operar. La primera señal no opera sin una contraria observada. Las repeticiones del mismo lado necesitan una señal contraria intermedia.
Ejecución y valores predeterminados
Con ambos lados habilitados, envía Volume desde cero o Volume + abs(Position) para invertir completamente una posición opuesta. Mantiene si ya ocupa el mismo lado. En solo largo, un corto confirmado cierra todo el largo sin abrir corto; solo corto es simétrico. Una dirección deshabilitada desde cero no actúa. Las órdenes pendientes bloquean duplicados, mientras línea e historia avanzan. No se aplazan señales rechazadas ni existe espera de seis horas.
Sin stop-loss/take-profit de precio independientes. La salida unilateral es una señal contraria confirmada, no una orden stop ATR de precio.
Parámetro
Valor
Length
14, período compartido positivo
Multiplier
1, distancia ATR positiva
IsUseMfi
false selecciona RSI; true MFI
IsLongEnabled / IsShortEnabled
true / true; al menos uno habilitado
CandleType
5 minutos
Las pruebas C# y Python calculan independientemente ATR, RSI/MFI, línea y cruces brutos/confirmados en el archivo BTC/TON. Verifican cada orden y posición real: momento, lado, inversión completa y cierre unilateral. Cubren ambos osciladores y señales separadas por menos de seis horas. No demuestran rentabilidad.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Uses a causal ATR rail selected by RSI or MFI and confirmed crosses of its two-bar lag.
/// Opposite signals fully reverse or close a position when that entry direction is disabled.
/// </summary>
public class PresentTrendStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<bool> _isUseMfi;
private readonly StrategyParam<bool> _isLongEnabled;
private readonly StrategyParam<bool> _isShortEnabled;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _previousLines = new();
private decimal? _line;
private int _barIndex;
private int? _lastLongSignal;
private int? _lastShortSignal;
private Order _pendingOrder;
/// <summary>Initializes period14, ATR multiplier1, RSI and both entry directions on five-minute candles.</summary>
public PresentTrendStrategy()
{
_length = Param(nameof(Length), 14).SetGreaterThanZero()
.SetDisplay("Length", "Shared Wilder ATR and selected RSI/MFI period", "Indicators");
_multiplier = Param(nameof(Multiplier), 1m).SetGreaterThanZero()
.SetDisplay("Multiplier", "ATR distance of the causal low/high rail", "Indicators");
_isUseMfi = Param(nameof(IsUseMfi), false)
.SetDisplay("Use MFI", "Use volume-weighted typical-price MFI instead of close RSI", "Indicators");
_isLongEnabled = Param(nameof(IsLongEnabled), true)
.SetDisplay("Enable Long", "Allow long entries; short positions still close on an opposite signal", "Trading");
_isShortEnabled = Param(nameof(IsShortEnabled), true)
.SetDisplay("Enable Short", "Allow short entries; long positions still close on an opposite signal", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Finished candles supplying the rail and oscillator", "General");
OrderRegistering += order => _pendingOrder = order;
}
/// <summary>Positive shared ATR and RSI/MFI period.</summary>
public int Length { get => _length.Value; set => _length.Value = value; }
/// <summary>Positive ATR multiplier for the rail.</summary>
public decimal Multiplier { get => _multiplier.Value; set => _multiplier.Value = value; }
/// <summary>Whether to use native typical-price/volume MFI; otherwise native close RSI.</summary>
public bool IsUseMfi { get => _isUseMfi.Value; set => _isUseMfi.Value = value; }
/// <summary>Whether long entries are allowed; opposite signals can still close shorts.</summary>
public bool IsLongEnabled { get => _isLongEnabled.Value; set => _isLongEnabled.Value = value; }
/// <summary>Whether short entries are allowed; opposite signals can still close longs.</summary>
public bool IsShortEnabled { get => _isShortEnabled.Value; set => _isShortEnabled.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)];
private void ResetState()
{
_previousLines.Clear();
_line = null;
_barIndex = 0;
_lastLongSignal = _lastShortSignal = null;
_pendingOrder = null;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Length <= 0 || Multiplier <= 0m || !IsLongEnabled && !IsShortEnabled)
throw new ArgumentException("Period and multiplier must be positive and at least one entry direction enabled.");
ResetState();
var atr = new AverageTrueRange { Length = Length };
IIndicator oscillator = IsUseMfi ? new MoneyFlowIndex { Length = Length } : new RelativeStrengthIndex { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx([atr, oscillator], ProcessCandle, allowEmpty: true).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished || values.Any(value => value.IsEmpty || !value.IsFormed))
return;
var bullish = values[1].ToDecimal() >= 50m;
var candidate = bullish ? candle.LowPrice - Multiplier * values[0].ToDecimal() : candle.HighPrice + Multiplier * values[0].ToDecimal();
var line = _line is decimal previous ? bullish ? Math.Max(previous, candidate) : Math.Min(previous, candidate) : candidate;
var history = _previousLines.ToArray();
Sides? signal = null;
if (history.Length == 3)
{
if (history[2] <= history[0] && line > history[1])
{
if (_lastShortSignal is int lastShort && (_lastLongSignal is null || lastShort > _lastLongSignal))
signal = Sides.Buy;
_lastLongSignal = _barIndex;
}
else if (history[2] >= history[0] && line < history[1])
{
if (_lastLongSignal is int lastLong && (_lastShortSignal is null || lastLong > _lastShortSignal))
signal = Sides.Sell;
_lastShortSignal = _barIndex;
}
}
_line = line;
_previousLines.Enqueue(line);
if (_previousLines.Count > 3)
_previousLines.Dequeue();
_barIndex++;
if (signal is not Sides side || !IsFormedAndOnlineAndAllowTrading()
|| _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed)
|| side == Sides.Buy && Position > 0m || side == Sides.Sell && Position < 0m)
return;
var enabled = side == Sides.Buy ? IsLongEnabled : IsShortEnabled;
if (!enabled && Position == 0m)
return;
RegisterOrder(new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = Math.Abs(Position) + (enabled ? Volume : 0m),
Comment = enabled ? "PresentTrend confirmed cross" : "PresentTrend opposite close",
});
}
}
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, TimeSpan
from StockSharp.Messages import CandleStates, DataType, OrderStates, OrderTypes, Sides
from StockSharp.BusinessEntities import Order
from StockSharp.Algo.Indicators import AverageTrueRange, IIndicator, MoneyFlowIndex, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class present_trend_strategy(Strategy):
def __init__(self):
super(present_trend_strategy, self).__init__()
self._length = self.Param("Length", 14).SetGreaterThanZero() \
.SetDisplay("Length", "Shared Wilder ATR and selected RSI/MFI period", "Indicators")
self._multiplier = self.Param("Multiplier", Decimal.One).SetGreaterThanZero() \
.SetDisplay("Multiplier", "ATR distance of the causal low/high rail", "Indicators")
self._is_use_mfi = self.Param("IsUseMfi", False) \
.SetDisplay("Use MFI", "Use volume-weighted typical-price MFI instead of close RSI", "Indicators")
self._is_long_enabled = self.Param("IsLongEnabled", True) \
.SetDisplay("Enable Long", "Allow long entries; short positions still close on an opposite signal", "Trading")
self._is_short_enabled = self.Param("IsShortEnabled", True) \
.SetDisplay("Enable Short", "Allow short entries; long positions still close on an opposite signal", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Finished candles supplying the rail and oscillator", "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._previous_lines = []
self._line = None
self._bar_index = 0
self._last_long = self._last_short = None
self._pending_order = None
def OnReseted(self):
super(present_trend_strategy, self).OnReseted()
self._reset_state()
def _observe_order(self, order):
self._pending_order = order
def OnStarted2(self, time):
super(present_trend_strategy, self).OnStarted2(time)
if self._length.Value <= 0 or self._multiplier.Value <= 0 \
or not self._is_long_enabled.Value and not self._is_short_enabled.Value:
raise ArgumentException("Period and multiplier must be positive and at least one entry direction enabled.")
self._reset_state()
atr = AverageTrueRange()
atr.Length = self._length.Value
oscillator = MoneyFlowIndex() if self._is_use_mfi.Value else RelativeStrengthIndex()
oscillator.Length = self._length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(Array[IIndicator]([atr, oscillator]), self.OnProcess, True).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def OnProcess(self, candle, values):
if candle.State != CandleStates.Finished or any(value.IsEmpty or not value.IsFormed for value in values):
return
atr = values[0].GetValue[Decimal](None)
bullish = values[1].GetValue[Decimal](None) >= 50
candidate = candle.LowPrice - self._multiplier.Value * atr if bullish \
else candle.HighPrice + self._multiplier.Value * atr
line = candidate if self._line is None else max(self._line, candidate) if bullish else min(self._line, candidate)
history = self._previous_lines
signal = None
if len(history) == 3:
if history[2] <= history[0] and line > history[1]:
if self._last_short is not None and (self._last_long is None or self._last_short > self._last_long):
signal = Sides.Buy
self._last_long = self._bar_index
elif history[2] >= history[0] and line < history[1]:
if self._last_long is not None and (self._last_short is None or self._last_long > self._last_short):
signal = Sides.Sell
self._last_short = self._bar_index
self._line = line
self._previous_lines = (history + [line])[-3:]
self._bar_index += 1
pending = self._pending_order
if signal is None or not self.IsFormedAndOnlineAndAllowTrading() \
or pending is not None and pending.State != OrderStates.Done and pending.State != OrderStates.Failed \
or signal == Sides.Buy and self.Position > 0 or signal == Sides.Sell and self.Position < 0:
return
enabled = self._is_long_enabled.Value if signal == Sides.Buy else self._is_short_enabled.Value
if not enabled and self.Position == 0:
return
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = signal
order.Volume = abs(self.Position) + (self.Volume if enabled else Decimal.Zero)
order.Comment = "PresentTrend confirmed cross" if enabled else "PresentTrend opposite close"
self.RegisterOrder(order)
def CreateClone(self):
return present_trend_strategy()