Linha ATR causal controlada por RSI do fechamento ou MFI do preço típico e volume. Cruzamentos confirmados com a linha de dois candles atrás abrem, invertem completamente ou fecham posições.
Linha e sinais
Somente candles concluídos, cinco minutos por padrão. ATR Wilder nativo e oscilador selecionado compartilham Length=14, positivo. O oscilador não selecionado não é registrado. Ambos precisam estar formados:
Oscilador ≥50: candidato low - Multiplier * ATR; linha = máximo entre candidato e linha anterior.
Oscilador <50: candidato high + Multiplier * ATR; linha = mínimo entre candidato e linha anterior.
Primeira linha formada = candidato selecionado, sem inventar histórico. Multiplier=1, positivo.
ATR inclui gaps em relação ao fechamento anterior, inicia com média crescente e depois Wilder. RSI suaviza ganhos/perdas do fechamento por Wilder e conserva o resultado anterior quando o fechamento não muda. MFI usa typical=(high+low+close)/3 vezes volume: típico crescente é fluxo positivo, decrescente negativo, igual nenhum. Nas últimas Length velas, 100 * positiveSum / (positiveSum + negativeSum), ou 100 sem fluxo negativo.
Cruzamento bruto comprado em t exige line[t-1] <= line[t-3] e line[t] > line[t-2]; vendido usa ≥ e < respectivamente. Exige quatro valores formados.
Compra é confirmada somente se existe sinal bruto vendido anterior mais recente que a compra bruta anterior, ou ainda não existe compra anterior. Venda é simétrica. Cada sinal bruto é registrado após a comparação, inclusive quando a negociação é rejeitada. O primeiro não opera sem sinal contrário observado. Repetições do mesmo lado exigem sinal contrário intermediário.
Execução e padrões
Com ambos os lados habilitados, abre Volume de zero ou envia Volume + abs(Position) para inversão completa da posição oposta. Mesmo lado mantém. Apenas comprado: venda confirmada fecha toda a compra sem abrir venda; apenas vendido é simétrico. Direção desabilitada de zero não age. Ordens pendentes bloqueiam duplicados enquanto linha e histórico avançam. Sinais rejeitados não são adiados; não há espera de seis horas.
Não há stop-loss/take-profit de preço separados. A saída unilateral usa sinal contrário confirmado, não ordem stop de preço ATR.
Parâmetro
Padrão
Length
14, período compartilhado positivo
Multiplier
1, distância ATR positiva
IsUseMfi
false seleciona RSI; true MFI
IsLongEnabled / IsShortEnabled
true / true; pelo menos um lado habilitado
CandleType
5 minutos
Testes C# e Python calculam independentemente ATR, RSI/MFI, linha e cruzamentos brutos/confirmados no arquivo BTC/TON. Cada ordem e posição real é verificada por momento, direção, inversão completa ou saída unilateral. Ambos os osciladores e sinais separados por menos de seis horas são exercitados. Não comprovam lucratividade.
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()