PresentTrend
終値RSIまたは典型価格と出来高のMFIで制御する因果ATRラインです。2本前のライン値との確認済みクロスで、エントリー、完全反転、決済を行います。
ラインとシグナル
確定足のみで、既定は5分足です。標準Wilder ATRと選択したオシレーターは正の共通期間Length=14を使い、未選択のオシレーターは登録しません。両方が形成されてから計算します。
- オシレーター ≥50:候補
low - Multiplier * ATR。ラインは候補と前回ラインの最大値。 - オシレーター <50:候補
high + Multiplier * ATR。ラインは候補と前回ラインの最小値。 - 最初のラインは選択した候補そのものとし、以前の値を仮定しません。
Multiplier=1で正が必要です。
ATRは前回終値からのギャップを含み、拡大する平均から始まり、その後Wilder平滑化を使います。RSIは終値の上昇・下落をWilder平滑化し、終値不変なら前回結果を保持します。MFIはtypical=(high+low+close)/3と出来高の積を使い、typical上昇なら正流、下落なら負流、不変ならどちらにも計上しません。直近Length足で100 * positiveSum / (positiveSum + negativeSum)、負流ゼロなら100です。
tでの生ロングクロスはline[t-1] <= line[t-3]かつline[t] > line[t-2]、ショートはそれぞれ≥と<です。形成済みライン4本が必要です。
ロングの確認には、以前の生ショートが存在し、以前の生ロングより新しいこと(または生ロングがまだないこと)が必要です。ショートは対称です。比較後に各生シグナルを記録し、取引を拒否されたものも含めます。観測済みの反対シグナルがない最初の生シグナルは取引しません。同方向の繰り返しには間に反対シグナルが必要です。
執行と既定値
両方向有効なら、ポジションなしでVolume、反対ポジションならVolume + abs(Position)で完全反転します。同方向は保持します。ロング専用なら確認済みショートで全ロングを閉じ、ショートは開きません。ショート専用は対称です。無効方向でポジションなしなら何もしません。未完了注文は重複を防ぎますが、ラインと生シグナル履歴は進みます。拒否したシグナルを持ち越さず、6時間待機もありません。
独立した価格ストップロスや利確はありません。単方向モードの決済は確認済みの反対シグナルによるもので、ATR価格ストップ注文ではありません。
| パラメーター | 既定値 |
|---|---|
Length |
14、正の共通期間 |
Multiplier |
1、正のATR距離 |
IsUseMfi |
falseでRSI、trueでMFI |
IsLongEnabled / IsShortEnabled |
true / true、少なくとも片側が有効 |
CandleType |
5分 |
C#とPythonは既存BTC/TON履歴からATR、RSI/MFI、ライン、生・確認済みクロスを独立計算し、各注文と実際のポジションの時刻、方向、完全反転、単方向決済を検証します。両オシレーターと6時間未満のシグナル間隔を試します。収益性を証明しません。
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()