GitHub で見る
2526 TDI-2 ReOpen戦略
概要
この戦略はMetaTrader 5のエキスパートアドバイザー Exp_TDI-2_ReOpen のC#変換版です。Trend Direction Index(TDI-2)インジケーターを使用してトレードし、元のポジション再エントリーロジックを適用します。C#ポートはStockSharpの高レベルAPIを使用し、MQLバージョンのコア動作を維持します:TDIモメンタムラインとTDIインデックスラインのクロスに反応し、設定可能な価格前進後に利益ポジションをスケールアップし、オプションの保護ストップでトレードを管理します。
インジケーター
- TDI-2インジケーター – このリポジトリに実装されたカスタムのモメンタムベースインジケーター。2本のラインを構築します:
- 方向ライン:
期間 × スムーズドモメンタム、モメンタムは適用価格から期間バー前の価格を引いたもの。
- インデックスライン:
|方向| − (2 × 期間 × スムーズ(|モメンタム|, 2×期間) − |モメンタム|)。
- インジケーターは以下のスムージングメソッドをサポート:単純、指数、スムーズ(RMA)、線形加重移動平均。
- サポートされている適用価格オプションは、TrendFollowとDemarkの式を含む元のMQL実装を再現します。
トレードロジック
- 完了したローソク足ごとに、戦略は Signal Bar(デフォルト:直前の閉じたローソク足)で指定されたバーとその1本前のTDI-2値を評価します。
- 方向ラインがインデックスラインの上にあって、その後下方クロスした場合:
- Allow Long Entries が有効でアクティブなロングポジションがない場合、戦略は新規ロングエントリーを準備します。
- ショートポジションが存在して Allow Short Exits が有効な場合、ショートポジションを決済します。
- 方向ラインがインデックスラインの下にあって、その後上方クロスした場合:
- Allow Short Entries が有効でアクティブなショートポジションがない場合、戦略は新規ショートエントリーを準備します。
- ロングポジションが存在して Allow Long Exits が有効な場合、ロングポジションを決済します。
- 再エントリーロジック(スケールイン):
- ロングポジションを保有中、戦略は最後のロングトレードの約定価格を追跡します。市場が Re-entry Step (points) だけ有利に動き、実行されたロングトレード数がまだ Max Entries を下回っている場合、ベースボリュームで追加のロング注文を開きます。
- 同じロジックが、最新のショート約定価格を使用してショートポジションに適用されます。
- 反対ポジションが存在する状態でポジションを開く場合、戦略は反対のエクスポージャーを閉じて設定されたベースボリュームで新規ポジションを確立するために、サイズを調整した合成成行注文を送信します。
- オプションのストップロスとテイクプロフィットレベルは、インストゥルメントの
PriceStep乗数を使用してStartProtectionを通じてアクティブになります。
パラメーター
| 名前 |
説明 |
デフォルト値 |
| Money Management |
ベース注文ボリューム。 |
0.1 |
| Max Entries |
方向ごとの最大エントリー数(初回トレード+再エントリー)。 |
10 |
| Stop Loss (points) |
インストゥルメントポイントでのストップロス距離。 |
1000 |
| Take Profit (points) |
インストゥルメントポイントでのテイクプロフィット距離。 |
2000 |
| Slippage (points) |
互換性のために保持;StockSharp実装では使用されない。 |
10 |
| Re-entry Step (points) |
既存ポジションにスケールインする前の最小有利な動き。 |
300 |
| Allow Long Entries / Allow Short Entries |
ロング/ショートポジションのオープンを有効化。 |
true |
| Allow Long Exits / Allow Short Exits |
ロング/ショートポジションのクローズを有効化。 |
true |
| Candle Type |
計算に使用するローソク足シリーズ。 |
H4ローソク足 |
| TDI Smoothing |
TDI-2インジケーターのスムージングメソッド。 |
単純MA |
| TDI Period |
モメンタムのルックバック期間。 |
20 |
| TDI Phase |
MQL入力との互換性のために予約済み(サポートされているスムージングモードでは効果なし)。 |
15 |
| Applied Price |
TDI-2が使用する価格ソース。 |
Close |
| Signal Bar |
クロスを評価する際に振り返る閉じたローソク足の数。 |
1 |
追加メモ
- StockSharpインジケーターでサポートされているスムージングメソッド(SMA、EMA、SMMA、LWMA)のみが実装されています。JJMAやT3などの他のMQLモードは利用できません。
- TDI Phase パラメーターは完全性のために保持されています。サポートされているスムージングメソッドには影響せず、デフォルト値のままにしておけます。
- Slippage (points) パラメーターは元のエキスパートアドバイザーとの同等性のために提供されていますが、高レベルAPIでは使用されません。
- スケールインカウンターは、ネットポジションがゼロに戻ると自動的にリセットされます。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend Direction Index re-entry strategy.
/// Trades based on crossings between the TDI momentum line and the TDI index line.
/// </summary>
public class Tdi2ReOpenStrategy : Strategy
{
private readonly StrategyParam<int> _tdiPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal? _lastClose;
private decimal? _directional;
private decimal? _index;
private decimal? _prevDirectional;
private decimal? _prevIndex;
public int TdiPeriod { get => _tdiPeriod.Value; set => _tdiPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Tdi2ReOpenStrategy()
{
_tdiPeriod = Param(nameof(TdiPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("TDI Period", "Momentum lookback period", "Indicator")
.SetOptimize(5, 30, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Data series", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastClose = null;
_directional = null;
_index = null;
_prevDirectional = null;
_prevIndex = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lastClose = null;
_directional = null;
_index = null;
_prevDirectional = null;
_prevIndex = null;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_lastClose is not decimal lastClose)
{
_lastClose = close;
return;
}
var momentum = close - lastClose;
_lastClose = close;
var alpha = 2m / (TdiPeriod + 1m);
if (_directional is not decimal prevDirectionalLine || _index is not decimal prevIndexLine)
{
_directional = momentum;
_index = momentum;
return;
}
var directional = prevDirectionalLine + alpha * (momentum - prevDirectionalLine);
var index = prevIndexLine + alpha * (directional - prevIndexLine);
if (_prevDirectional is not decimal prevDir || _prevIndex is not decimal prevIdx)
{
_directional = directional;
_index = index;
_prevDirectional = prevDirectionalLine;
_prevIndex = prevIndexLine;
return;
}
var crossUp = prevDir <= prevIdx && directional > index;
var crossDown = prevDir >= prevIdx && directional < index;
if (crossUp && Position <= 0)
BuyMarket();
else if (crossDown && Position >= 0)
SellMarket();
_directional = directional;
_index = index;
_prevDirectional = directional;
_prevIndex = index;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class tdi_2_re_open_strategy(Strategy):
"""TDI momentum/index crossover: custom EMA-smoothed momentum lines."""
def __init__(self):
super(tdi_2_re_open_strategy, self).__init__()
self._tdi_period = self.Param("TdiPeriod", 10).SetGreaterThanZero().SetDisplay("TDI Period", "Momentum lookback period", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(tdi_2_re_open_strategy, self).OnReseted()
self._last_close = 0
self._directional = None
self._index = None
self._prev_dir = None
self._prev_idx = None
def OnStarted2(self, time):
super(tdi_2_re_open_strategy, self).OnStarted2(time)
self._last_close = 0
self._directional = None
self._index = None
self._prev_dir = None
self._prev_idx = None
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
if self._last_close == 0:
self._last_close = close
return
momentum = close - self._last_close
self._last_close = close
alpha = 2.0 / (self._tdi_period.Value + 1.0)
if self._directional is None or self._index is None:
self._directional = momentum
self._index = momentum
return
directional = self._directional + alpha * (momentum - self._directional)
index = self._index + alpha * (directional - self._index)
if self._prev_dir is None or self._prev_idx is None:
self._directional = directional
self._index = index
self._prev_dir = self._directional
self._prev_idx = self._index
return
cross_up = self._prev_dir <= self._prev_idx and directional > index
cross_down = self._prev_dir >= self._prev_idx and directional < index
if cross_up and self.Position <= 0:
self.BuyMarket()
elif cross_down and self.Position >= 0:
self.SellMarket()
self._directional = directional
self._index = index
self._prev_dir = directional
self._prev_idx = index
def CreateClone(self):
return tdi_2_re_open_strategy()