GitHub で見る
Urdala Trol ヘッジング・グリッド戦略
概要
Urdala Trol ヘッジング・グリッド戦略は、MetaTrader 5 のエキスパートアドバイザー Urdala_Trol.mq5 を StockSharp 高レベル API に直接移植したものである。この戦略は両方向のポジションを継続的に維持し、ストップが発動された際にマーチンゲール型のグリッドを使ってポジションをスケールする。インジケーターを使用せず、Level1 データ(最良 bid/ask)のみで動作する。
トレーディングロジック
- 初期ヘッジ(ステップ 0) – アクティブなポジションがない場合、戦略は直ちに Base Volume パラメーターを使用して1つのロングと1つのショートの成行注文を出す。
- 負け側のスケールイン(ステップ 1.2) – 一方向のみのポジションが残っており、そのサイドの最も損失を出しているポジションが現在の価格から少なくとも
Grid Step pips 離れている場合、戦略は同方向に追加ポジションを開く。新しいボリュームは、最も利益の低いポジションのボリュームに Min Lots Multiplier * minVolumeStep を加えたものとなり、minVolumeStep は銘柄の VolumeStep または MinVolume から導出される。
- ストップロス処理(ステップ 1.1) – ポジションがストップロス(トレーリング調整を含む)でマイナス結果で決済された場合、出口価格から
Min Nearest pips 以内に既存の取引がなければ、戦略は同方向に再エントリーする。
- 利益確定ストップの反応(ステップ 2.1) – ストップがポジションを利益で決済した場合、戦略は直ちにスケールされたボリュームで反対方向に取引を開く。
- トレーリングストップ – 価格がエントリーを超えて
Trailing Stop + Trailing Step pips 進んだら、ストップを Trailing Stop pips の距離を保つよう調整する。トレーリングはオプションであり、両方のパラメーターがゼロより大きい場合のみ適用される。
pips で表されるすべての距離は、銘柄の PriceStep を通じて絶対価格オフセットに変換される。5桁または3桁の相場の場合、オリジナル MQL の "adjusted point" ロジックに合わせてステップに10を掛ける。
パラメーター
| パラメーター |
デフォルト |
説明 |
BaseVolume |
0.1 |
最初のヘッジペアを開くために使用される初期ロットサイズ。 |
MinLotsMultiplier |
3 |
スケーリング時に負け取引のボリュームに追加される最小ロット数。 |
StopLossPips |
50 |
pips 単位のストップロス距離。ゼロを指定するとストップとトレーリングロジックが無効になる。 |
TrailingStopPips |
5 |
pips 単位のトレーリングストップ距離。ゼロでトレーリングを無効化。 |
TrailingStepPips |
5 |
トレーリングストップが動く前に必要な追加 pip 距離。トレーリングが有効な場合は正の値が必要。 |
GridStepPips |
50 |
新規スケールイン注文が発注される前の、負けポジションと現在価格との最小価格距離(pips)。 |
MinNearestPips |
3 |
既存のポジションが最後のストップ価格からこの距離より近い場合、戦略は即時再エントリーをスキップする。 |
実装上の注意
SubscribeLevel1() を使用して bid/ask の更新を追跡し、毎ティックで意思決定エンジンを実行する。
- 注文は高レベルの
RegisterOrder ヘルパーを通じて登録され、OnOwnTradeReceived を通じて正確な追跡が可能。
- StockSharp のポートフォリオはデフォルトでネットポジションベースであるため、ヘッジ動作を再現するために個別のポジションオブジェクトを内部で管理する。
- ストップロスとトレーリングのロジックは、閾値を超えた際に成行注文を送ることで戦略内で実行される。ネイティブのストップ注文は登録しない。
使用上のヒント
- 流動性の高い銘柄とポートフォリオを戦略に割り当て、正確な換算のために
PriceStep、VolumeStep、最小/最大ボリューム値が設定されていることを確認する。
- 戦略を起動すると、直ちにヘッジペアが構築され、以降はオリジナルの MQL ロジックに従ってストップイベントに反応する。
- 銘柄のボラティリティに合わせて pip パラメーターを調整する。
Grid Step の値を大きくすると追加注文の頻度が減り、Min Lots Multiplier を大きくするとマーチンゲールの成長が加速する。
- 結果として生じるエクスポージャーを注意深く監視する。マーチンゲール動作は、複数のストップが連続して発動するとボリュームを急速に拡大させる可能性がある。
この変換タスクの要件に合わせて、このフォルダーには Python 実装を意図的に提供していない。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Urdala Trol strategy (simplified). Uses EMA with trailing stop logic
/// for grid-style entries based on trend direction.
/// </summary>
public class UrdalaTrolStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _trailingPercent;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public decimal TrailingPercent
{
get => _trailingPercent.Value;
set => _trailingPercent.Value = value;
}
public UrdalaTrolStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
_emaLength = Param(nameof(EmaLength), 14)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "Indicators");
_trailingPercent = Param(nameof(TrailingPercent), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Trailing %", "Trailing stop percent", "Risk");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaLength };
decimal highSinceEntry = 0;
decimal lowSinceEntry = decimal.MaxValue;
decimal prevClose = 0;
decimal prevEma = 0;
var hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, (ICandleMessage candle, decimal emaVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevClose = candle.ClosePrice;
prevEma = emaVal;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevClose = candle.ClosePrice;
prevEma = emaVal;
return;
}
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
// Track trailing stop levels
if (Position > 0)
{
if (high > highSinceEntry)
highSinceEntry = high;
var trailStop = highSinceEntry * (1m - TrailingPercent / 100m);
if (close < trailStop)
{
SellMarket();
highSinceEntry = 0;
lowSinceEntry = decimal.MaxValue;
return;
}
}
else if (Position < 0)
{
if (low < lowSinceEntry)
lowSinceEntry = low;
var trailStop = lowSinceEntry * (1m + TrailingPercent / 100m);
if (close > trailStop)
{
BuyMarket();
highSinceEntry = 0;
lowSinceEntry = decimal.MaxValue;
return;
}
}
// Entry signals based on EMA
var bullishCross = prevClose <= prevEma && close > emaVal;
var bearishCross = prevClose >= prevEma && close < emaVal;
if (bullishCross && Position <= 0)
{
BuyMarket();
highSinceEntry = high;
lowSinceEntry = decimal.MaxValue;
}
else if (bearishCross && Position >= 0)
{
SellMarket();
lowSinceEntry = low;
highSinceEntry = 0;
}
prevClose = close;
prevEma = emaVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class urdala_trol_strategy(Strategy):
def __init__(self):
super(urdala_trol_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._ema_length = self.Param("EmaLength", 14) \
.SetDisplay("EMA Length", "EMA period", "Indicators")
self._trailing_percent = self.Param("TrailingPercent", 1.5) \
.SetDisplay("Trailing %", "Trailing stop percent", "Risk")
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def TrailingPercent(self):
return self._trailing_percent.Value
def OnReseted(self):
super(urdala_trol_strategy, self).OnReseted()
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(urdala_trol_strategy, self).OnStarted2(time)
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
ev = float(ema_value)
if not self._has_prev:
self._prev_close = close
self._prev_ema = ev
self._has_prev = True
return
if self.Position > 0:
if high > self._high_since_entry:
self._high_since_entry = high
trail_stop = self._high_since_entry * (1.0 - self.TrailingPercent / 100.0)
if close < trail_stop:
self.SellMarket()
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = close
self._prev_ema = ev
return
elif self.Position < 0:
if low < self._low_since_entry:
self._low_since_entry = low
trail_stop = self._low_since_entry * (1.0 + self.TrailingPercent / 100.0)
if close > trail_stop:
self.BuyMarket()
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = close
self._prev_ema = ev
return
bullish_cross = self._prev_close <= self._prev_ema and close > ev
bearish_cross = self._prev_close >= self._prev_ema and close < ev
if bullish_cross and self.Position <= 0:
self.BuyMarket()
self._high_since_entry = high
self._low_since_entry = 1e18
elif bearish_cross and self.Position >= 0:
self.SellMarket()
self._low_since_entry = low
self._high_since_entry = 0.0
self._prev_close = close
self._prev_ema = ev
def CreateClone(self):
return urdala_trol_strategy()