GitHub で見る
XP Trade Manager グリッド (C#)
概要
XP Trade Manager Grid 戦略は、MetaTrader 4 エキスパート アドバイザー XP Trade Manager Grid.mq4 の直接移植です。これは、市場が最新の約定レッグから構成可能なポイント数だけ離れるたびに、新しいポジションを継続的に追加する対称グリッドを自動化します。元の専門家は、最初の 3 つの注文については部分的なテイクプロフィットレベル、ラダーが大きくなった場合の損益分岐点クラスター、およびアカウントの割合に基づくグローバルリスクガードを使用して利益を管理しました。 StockSharp の実装では、同じ考え方を維持しながら、高レベルの API プリミティブ (成行注文、ローソク足のサブスクリプション、戦略パラメーター) を活用します。
取引ロジック
- 最初のエントリー – この戦略は、ユーザーが選択した方向で最初の成行注文を即座に開始します (デフォルトでは売り)。後続のすべての取引はグリッド ラダーにグループ化されます。
- グリッド拡張 – 終値が片側で最新のレッグを超えて
StepPoints * 価格ステップを超えるたびに、同時レッグの合計数が MaxOrders を下回っている限り、その方向に新しい成行注文が発注されます。
- 最初の 3 つのレッグ専用の TP – 各サイドの最初の 3 つのオーダーは、独自のテイクプロフィット オフセット (
TakeProfit1Partitive、TakeProfit2、TakeProfit3) を継承します。ろうそくの高値/安値がそれらのレベルに達すると、足はフラットになります。
- 損益分岐点クラスター – オープンレッグの合計が 4 以上に達すると、戦略はラダー全体の加重損益分岐点価格を計算します。どちらの側がより多くのレッグを持っているかに応じて、対応する合計ターゲット (
TakeProfit4Total … TakeProfit15Total) をアクティブなオーダー全体で割った値によって損益分岐点が相殺されます。価格が計算された目標に達すると、すべてのエクスポージャーがクローズされます。
- サイクルの更新 – サイクルの最初の注文がクローズしたが、収集されたポイント単位の利益がまだ
TakeProfit1Total を下回っている場合、ロジックは市場が最後の出口を超えて TakeProfit1Offset ポイント移動するのを待ってから、最初の注文を再度オープンします。
- リスク管理 – 口座通貨での変動利益(実現+未実現)は、ポートフォリオ開始残高の
RiskPercent パーセントと常に比較されます。損失しきい値に違反すると、はしご全体が直ちに平らになります。
C# ポートは、充填されたすべてのレッグを内部的に追跡します。部分的な約定がサポートされており、ヘッジされた構造(同時の買いと売り)は、MQL エキスパートとまったく同じように解決されます。新しいエクスポージャーが記録される前に、反対の約定がまず未処理のレッグを打ち消します。
パラメーター
| 名前 |
説明 |
CandleType |
戦略を推進するために使用されるデータ型 (デフォルト: 1 分足ローソク足)。 |
OrderVolume |
各成行注文/レッグの出来高。 |
MaxOrders |
両方向の最大同時レッグ数。 |
StepPoints |
連続するグリッド次数間のポイント単位の距離。 |
RiskPercent |
ポートフォリオの開始残高に対する最大許容浮動損失の割合 (%)。 |
TakeProfit1Total |
自動更新が行われない前に、注文 #1 サイクルによって蓄積された合計ポイント目標。 |
TakeProfit1Partitive |
最初のレグのテイクプロフィット距離 (ポイント)。 |
TakeProfit1Offset |
最初のオーダーを再作成する前に必要な最小リトレースメント距離。 |
TakeProfit2 / TakeProfit3 |
レグ #2 と #3 の個別の TP オフセット (ポイント)。 |
TakeProfit4Total … TakeProfit15Total |
ラダーサイズが一致する注文数に達すると、損益分岐点 TP 合計が使用されます。 |
InitialSide |
最初の注文の方向 (買いまたは売り)。 |
注意: すべてのポイントベースの入力は、セキュリティ PriceStep によって自動的にスケーリングされ、MetaTrader の元の Point() ロジックと一致します。
- StockSharp バリアントは、高レベルの API が注文の直接変更を公開しないため、個々のテイクプロフィット値を変更するのではなく、成行注文を通じて最初の 3 つのレッグをクローズします。
- 変動利益の計算は、商品のステップとステップ価格に依存します。特殊な契約仕様を持つブローカーは、それらのフィールドを公開しない場合、微調整が必要になる場合があります。
- MT4 に表示されるプラットフォームレベルのラベル (「利益ピップ」/「利益通貨」) は再現されません。代わりに、内部サイクル統計を使用して、最初の注文をいつ再開するかを決定します。
要件
PriceStep と StepPrice の両方を公開するセキュリティに戦略をアタッチします。
- 取引コネクタが即時またはキャンセルの成行注文をサポートしていることを確認してください。すべてのグリッド レッグは、
BuyMarket/SellMarket ヘルパー メソッドを通じて実行されます。
使用のヒント
- フィード上でグリッドがどのように動作するかを評価するテストを行うときは、小さな
OrderVolume 値から始めます。
- シンボルのボラティリティに合わせて
StepPoints を慎重に調整してください。ステップを大きくすると、オープンレッグの数が減り、したがってドローダウンが減ります。
- スプレッドが広い商品を取引する場合は、時期尚早なリエントリーを避けるために、
TakeProfit1Offset を増やしてください。
- この戦略を組み込みの
StartProtection() 呼び出しと組み合わせて、予期しない切断を監視し、正常に再接続します。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "XP Trade Manager Grid" MetaTrader expert.
/// Uses RSI for entry direction with grid-style position management.
/// </summary>
public class XpTradeManagerGridStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiUpper;
private readonly StrategyParam<decimal> _rsiLower;
private RelativeStrengthIndex _rsi;
private decimal? _prevRsi;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public decimal RsiUpper
{
get => _rsiUpper.Value;
set => _rsiUpper.Value = value;
}
public decimal RsiLower
{
get => _rsiLower.Value;
set => _rsiLower.Value = value;
}
public XpTradeManagerGridStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal generation", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_rsiUpper = Param(nameof(RsiUpper), 70m)
.SetDisplay("RSI Upper", "RSI threshold for sell", "Signals");
_rsiLower = Param(nameof(RsiLower), 30m)
.SetDisplay("RSI Lower", "RSI threshold for buy", "Signals");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = null;
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed)
{
_prevRsi = rsiValue;
return;
}
if (_prevRsi is null)
{
_prevRsi = rsiValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// RSI crosses below oversold -> buy
var crossDown = _prevRsi.Value > RsiLower && rsiValue <= RsiLower;
// RSI crosses above overbought -> sell
var crossUp = _prevRsi.Value < RsiUpper && rsiValue >= RsiUpper;
if (crossDown)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossUp)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevRsi = rsiValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_prevRsi = null;
base.OnReseted();
}
}
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.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class xp_trade_manager_grid_strategy(Strategy):
def __init__(self):
super(xp_trade_manager_grid_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._rsi_period = self.Param("RsiPeriod", 14)
self._rsi_upper = self.Param("RsiUpper", 70.0)
self._rsi_lower = self.Param("RsiLower", 30.0)
self._prev_rsi = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiUpper(self):
return self._rsi_upper.Value
@RsiUpper.setter
def RsiUpper(self, value):
self._rsi_upper.Value = value
@property
def RsiLower(self):
return self._rsi_lower.Value
@RsiLower.setter
def RsiLower(self, value):
self._rsi_lower.Value = value
def OnReseted(self):
super(xp_trade_manager_grid_strategy, self).OnReseted()
self._prev_rsi = None
def OnStarted2(self, time):
super(xp_trade_manager_grid_strategy, self).OnStarted2(time)
self._prev_rsi = None
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._process_candle).Start()
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
if self._prev_rsi is None:
self._prev_rsi = rsi_val
return
rsi_lower = float(self.RsiLower)
rsi_upper = float(self.RsiUpper)
# RSI crosses below oversold -> buy
cross_down = self._prev_rsi > rsi_lower and rsi_val <= rsi_lower
# RSI crosses above overbought -> sell
cross_up = self._prev_rsi < rsi_upper and rsi_val >= rsi_upper
if cross_down:
if self.Position <= 0:
self.BuyMarket()
elif cross_up:
if self.Position >= 0:
self.SellMarket()
self._prev_rsi = rsi_val
def CreateClone(self):
return xp_trade_manager_grid_strategy()