PresentTrend
由收盘价RSI或典型价格/成交量MFI控制的因果ATR线。与两根蜡烛前的线值发生确认交叉时,开仓、完全反转或平仓。
线和信号
仅使用已完成的蜡烛,默认五分钟。原生Wilder ATR和所选振荡器使用同一正周期Length=14;不注册未选振荡器。两个指标都形成后开始计算:
- 振荡器 ≥50:候选值
low - Multiplier * ATR,线为候选值与前一线值的最大值。 - 振荡器 <50:候选值
high + Multiplier * ATR,线为候选值与前一线值的最小值。 - 第一个形成的线值就是所选候选值,不假设更早历史。
Multiplier=1,必须为正。
ATR包含相对前一close的跳空,从增长窗口均值开始,然后Wilder平滑。RSI使用Wilder平滑的close涨跌;close不变时保留前一结果。MFI使用typical=(high+low+close)/3乘成交量:typical上涨计正流、下跌计负流、相等均不计。最后Length根的MFI为100 * positiveSum / (positiveSum + negativeSum);负流为零时为100。
t时的原始多头交叉要求line[t-1] <= line[t-3]且line[t] > line[t-2];空头相应使用≥和<,需四个形成的线值。
多头交叉只有在先前存在原始空头信号,且该空头比先前原始多头更新(或还没有原始多头)时才确认。空头对称。比较之后记录每个原始信号,包括交易被拒绝的信号。没有观测到相反信号时,第一个原始信号不交易;同方向重复原始信号要等相反信号介入后才可再次入场。
执行与默认值
双方启用时,从空仓发出Volume,或用Volume + abs(Position)完全反转相反持仓;同侧信号保持持仓。仅做多时,确认的空头信号完全平多但不开空;仅做空对称。空仓的禁用方向无操作。未完成订单阻止重复,但线和原始信号继续推进。被拒绝信号不排队,无六小时冷却。
没有独立价格止损或止盈。单方向退出是确认的相反信号,不是ATR价格止损订单。
| 参数 | 默认值 |
|---|---|
Length |
14,正的共享周期 |
Multiplier |
1,正的ATR倍数 |
IsUseMfi |
false选择RSI;true选择MFI |
IsLongEnabled / IsShortEnabled |
true / true,至少启用一方 |
CandleType |
5分钟 |
C#与Python测试在现有BTC/TON历史中独立计算ATR、RSI/MFI、线及原始/确认交叉。检查每个订单和实际持仓的时机、方向、完全反转、单方向平仓;覆盖两种振荡器及间隔小于六小时的信号。不证明盈利。
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()