GitHub で見る
DLM v1.4 グリッド戦略
概要
この戦略は、Alejandro Galindo の MetaTrader 4 エキスパート アドバイザー「DLM v1.4」の StockSharp 移植です。オリジナルのロボットは、フィッシャー変換信号フィルターとマーチンゲール スタイルの平均化スキームを組み合わせており、価格が最後のエントリーに対して変動するたびにポジションのグリッドを段階的に構築します。 StockSharp バージョンは、同じ資金管理のアイデアを維持しながら、実行と保護のロジックを高レベルの API (キャンドル サブスクリプション、インジケーター バインディング、マーケット/リミット ヘルパー) に適応させています。
取引ロジック
- 構成された時間枠から終了したローソク足を分析し、フィッシャー変換とフィッシャー値の SMA 平滑化という 2 つの指標を計算します。
- 2 本の線の相対位置からバスケットの方向を決定します。フィッシャーがスムーザーを上回ったとき、戦略は買いの準備をします。スムーサーを下回ると売却の準備をします。
ReverseSignals フラグはこの解釈を逆転します。
- 方向が利用可能になり、自動取引が有効になったら (
ManualTrading = false)、最初のポジションをすぐにオープンします (成行注文)。
- バスケットがアクティブな間、最新の約定に対して価格が
GridDistancePips 変動するたびに新しいエントリを追加し続けます。 UseLimitOrders フラグに応じて、追加の取引は成行注文 (次のローソク足の終値) として送信されるか、最後の約定からちょうど 1 グリッド ステップ離れた位置にある休止指値注文として送信されます。
- それぞれの新しい取引の量は元のマーチンゲールの増加に従います。
MaxTrades > 12 の場合は基本ロット サイズに 1.5 を掛け、それ以外の場合はサイズを 2 倍にします。基本サイズ自体は固定することも (LotSize)、または UseMoneyManagement が有効な場合はアカウントの資本から導き出すこともできます。
- 約定ごとに集計されたストップロスとテイクプロフィットのレベルが更新されるため、バスケット全体が単一セットの保護レベルを共有します。トレーリングストップ ロジックでは、価格が収益性の高い方向に
GridDistancePips + TrailingStopPips 移動した後にストップを引き締めることができます。
アカウントの保護
- 安全な利益ガード (
SecureProfitProtection): 未決済エントリの数が OrdersToProtect に達すると、未実現利益 (口座通貨) が SecureProfit と比較されます。しきい値に達すると、バスケット全体が直ちに閉じられます。
- 株式保護 (
EquityProtection + EquityProtectionPercent): 現在のポートフォリオの価値を監視し、株式が戦略開始時に取得された株式の選択された割合を下回るたびにバスケットを閉じます。
- 資金ドローダウン保護 (
AccountMoneyProtection + AccountMoneyProtectionValue): 初期資本からの通貨ドローダウンが設定された金額を超えると取引を停止します。
- ライフタイム保護 (
OrdersLifeSeconds): 最新のエントリに最大ライフタイムを強制します。制限を超えると、すべての取引が終了し、マーチンゲール サイクルが停止します。
- 金曜日フィルター (
TradeOnFriday): 無効にすると、金曜日に新しいバスケットが開始されなくなります。
すべての保護的出口は成行注文を使用して約定を保証します。未決の指値注文は、保護ブロックがトリガーされるかグリッドがリセットされるたびにキャンセルされます。
パラメーター
| パラメータ |
説明 |
TakeProfitPips |
すべてのエントリーに適用される共有テイクプロフィットディスタンス (pips)。 |
StopLossPips |
新しい取引ごとの最初のストップロス距離 (pips)。 |
TrailingStopPips |
トリガーしきい値の後にアクティブになるトレーリング ストップの距離。 |
MaxTrades |
バスケット内で許可される平均ステップの最大数。 |
GridDistancePips |
次の注文を追加する前の最小逆動き (pips)。 |
LotSize |
資金管理が無効になっている場合の基本ロット サイズ。 |
UseMoneyManagement |
オリジナルのリスク計算式による残高ベースのサイジングを可能にします。 |
RiskPercent |
動的な基本ロットサイズを導き出すために使用されるリスクパーセンテージ。 |
AccountType |
動的なロット サイズに適用されるスケーリング (0 標準、1 ミニ、2 マイクロ)。 |
SecureProfitProtection |
変動利益ガードを有効にします。 |
SecureProfit |
ガードをトリガーするには未実現利益 (通貨単位) が必要です。 |
OrdersToProtect |
安全な利益が有効になるまでのオープンエントリーの最小数。 |
EquityProtection |
自己資本比率のセーフティネットを有効にします。 |
EquityProtectionPercent |
戦略の開始を基準とした資本パーセンテージのしきい値。 |
AccountMoneyProtection |
ドローダウン (通貨) ベースの保護を有効にします。 |
AccountMoneyProtectionValue |
口座通貨での最大許容ドローダウン。 |
TradeOnFriday |
金曜日に新しいバスケットを開くことを許可/禁止します。 |
OrdersLifeSeconds |
清算前の最新注文の最大存続期間 (秒)。 |
ReverseSignals |
フィッシャー変換の方向を反転します。 |
UseLimitOrders |
マーケットを切り替えて、平均取引のエントリーを制限します。 |
ManualTrading |
true に設定すると、自動エントリが無効になります。 |
CandleType |
インジケーターの計算に使用される時間枠。 |
FisherLength |
フィッシャー変換のルックバックの長さ。 |
SignalSmoothing |
SMA 期間は平滑化フィッシャー値に適用されます。 |
DefaultPipValue |
未実現損益を通貨に変換するために使用されるフォールバック pip 値。 |
注意事項
- リポジトリ ガイドラインの要求に従って、ソース コード内のすべてのコメントは英語で記載されています。
- この戦略は、StockSharp の高レベルの API (
SubscribeCandles、Bind、BuyLimit、SellLimit など) のみに依存しており、インジケーター バッファーを直接操作しません。
- 資金管理の計算では元のリスク計算式が再利用されますが、商品の契約仕様を尊重するため、出来高と価格の調整は
Security.ShrinkVolume と Security.ShrinkPrice を介して行われます。
- この変換により、StockSharp の違いを考慮しながら、MetaTrader EA の動作が可能な限り近く維持されます (たとえば、バスケットの終了では、既存の注文を変更する代わりに成行注文が使用されます)。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// DLM v1 Grid strategy - EMA with RSI direction filter.
/// Buys when EMA is rising and RSI is above 50.
/// Sells when EMA is falling and RSI is below 50.
/// </summary>
public class Dlmv1GridStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevEma;
private bool _hasPrev;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Dlmv1GridStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 14)
.SetDisplay("EMA Period", "EMA lookback", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetDisplay("RSI Period", "RSI lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevEma = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, rsi, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrev)
{
_prevEma = ema;
_hasPrev = true;
return;
}
// EMA rising + RSI above 50 = buy
if (ema > _prevEma && rsi > 50 && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// EMA falling + RSI below 50 = sell
else if (ema < _prevEma && rsi < 50 && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevEma = ema;
}
}
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, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class dlmv1_grid_strategy(Strategy):
def __init__(self):
super(dlmv1_grid_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 14).SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14).SetDisplay("RSI Period", "RSI lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_ema = 0.0
self._has_prev = False
@property
def ema_period(self): return self._ema_period.Value
@property
def rsi_period(self): return self._rsi_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(dlmv1_grid_strategy, self).OnReseted()
self._prev_ema = 0.0; self._has_prev = False
def OnStarted2(self, time):
super(dlmv1_grid_strategy, self).OnStarted2(time)
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, rsi, self.process_candle).Start()
def process_candle(self, candle, ema, rsi):
if candle.State != CandleStates.Finished: return
ema_val = float(ema); rsi_val = float(rsi)
if not self._has_prev:
self._prev_ema = ema_val; self._has_prev = True; return
if ema_val > self._prev_ema and rsi_val > 50 and self.Position <= 0:
if self.Position < 0: self.BuyMarket()
self.BuyMarket()
elif ema_val < self._prev_ema and rsi_val < 50 and self.Position >= 0:
if self.Position > 0: self.SellMarket()
self.SellMarket()
self._prev_ema = ema_val
def CreateClone(self): return dlmv1_grid_strategy()