GitHub で見る
災害戦略 (MQL #7704)
概要
disaster.mq4 という名前の元の MetaTrader エキスパート アドバイザーは、非常に長い単純移動平均 (SMA) 付近のストップ注文を武装させました。現在の価格が平均から十分に離れるまで待ってから、平均回帰スナップバックを捉えようとする 2 つの保留中のストップ注文を保留します。新しい分ごとに SMA が再計算され、保留中の注文が最新のベースラインに調整されます。約定した注文は、固定ストップロスと、同じサイドでの前回の取引が損失で終了した後に縮小する適応テイクプロフィットによって保護されます。
移植メモ
- データ ソース – MQL スクリプトは、
iMA(PERIOD_M1, 590) までの 1 分足を使用します。 StockSharp バージョンは、設定可能なローソク足シリーズ (デフォルトは TimeSpan.FromMinutes(1)) をサブスクライブし、同じルックバックで SMA インジケーターをフィードします。
- トリガー ロジック – MetaTrader は買値/売値を SMA と比較し、未決注文を準備する前に 20 ピップのギャップを必要とします。 C# ポートは、商品
PriceStep/MinPriceStep と 3/5 桁の FX シンボルの 10 倍の乗数を使用して、TriggerDistancePips パラメータを絶対価格距離に変換することでこれを再現します。
- 注文タイプ – EA は、
OrderSend(..., OP_BUYSTOP/OP_SELLSTOP, ...) 経由でストップ注文を登録します。 StockSharp に相当するのは、BuyStop と SellStop です。ポートは両方の順序を独立させ、条件が続く場合はどちらかをアクティブのままにします。
- 動的再配置 – 新しいローソクが到着するたびに、MQL コードは
OrderModify を呼び出し、保留中のストップが新しい SMA を追跡します。 StockSharp は、ReRegisterOrder を呼び出して、キャンセル/再作成せずにアクティブな注文を移動することで同じことを実現します。
- ストップ レベル – MetaTrader はブローカーのストップ レベル (
MODE_STOPLEVEL) を適用します。 StockSharp バージョンは、商品価格ステップに丸め、計算された価格が無効(≤ 0)の場合は再配置を中止することで、同じ安全マージンを間接的に尊重します。
- 保護注文 – MT4 では、ストップロスとテイクプロフィットが未決注文に付加されます。 StockSharp は、エントリー約定の直後に個別のストップ/リミット プロテクション注文を作成し、正確な価格オフセットを反映します。
- 適応テイクプロフィット – EA は、その側の前の取引で損失が発生した場合、次の注文のテイクプロフィット距離を半分にします。ポートは
_lastBuyWasLoss / _lastSellWasLoss フラグを維持し、それに応じてテイクプロフィットディスタンスを調整します。
- 資金管理 – スクリプトのロットサイズは
0.4 * AccountFreeMargin / 1000 で、ブローカーの制限によって制限されます。 StockSharp ポートは、直接の Volume パラメータを公開し、それを VolumeStep、MinVolume、および MaxVolume と調整します。
パラメーター
| パラメータ |
デフォルト |
説明 |
Volume |
0.1 |
注文数量を商品の数量ステップに合わせます。 |
MaPeriod |
590 |
ベースラインとして使用される単純移動平均の長さ。 |
StopLossPips |
30 |
エントリー価格と保護ストップの間の距離。 |
TakeProfitPips |
70 |
基本テイクプロフィットディスタンス。同じサイドで負けたトレードの後は自動的に半分になります。 |
TriggerDistancePips |
20 |
ストップエントリーを準備する前に、価格と SMA の間に必要なギャップ。 |
CandleType |
1-minute time frame |
SMAの餌として使用されるキャンドルシリーズ。 |
すべての pip ベースのパラメータは、楽器 PriceStep または MinPriceStep を通じて変換されます。 10 進数が 3 桁または 5 桁の FX ペアの場合、変換ではステップが 10 倍され、MetaTrader Point の動作と一致します。
ワークフロー
- レベル 1 の相場と分足のローソク足を購読します。
- 各レベル 1 メッセージに保存されている買値/売値を更新します。
- 完了したローソク足ごとに、SMA を再計算し、アクティブな未決注文を新しいベースラインに移動します。
- オープンなポジションがなく、買値と売値のギャップがトリガー距離を超えている場合は、対応する逆指値注文を出します (価格が過小評価されている場合は、SMA より上で売り、それより下で買います)。
- 逆指値注文が約定すると、要求された距離で直ちに逆指値注文と利食い注文を登録します。次の利益確定に適応するために、最後の取引結果を追跡してください。
- 戦略が停止したら、すべての保留/保護注文をキャンセルします。
MQL バージョンとの違い
- ポートは、ブローカーに接続された SL/TP フィールドではなく、StockSharp の保護命令に依存します。動作は同等ですが、アカウント内で明示的な命令を使用します。
- MetaTrader は、
MODE_STOPLEVEL とのストップレベルの間隔を強制します。 StockSharp は、利用可能な価格ステップに四捨五入し、計算された価格が無効な場合は更新をスキップすることで、この要件を代理します。実際には、アダプターが注文価格を検証したら、同じ制約を尊重する必要があります。
- 元のコードは、ティックごとに自由証拠金から取引量を再計算します。 StockSharp ポートは、ブローカー間での動作を明確にし、予測可能なように、
Volume パラメーターを介してユーザーにサイズ設定を任せます。
要件
- 機器は少なくとも
PriceStep または MinPriceStep を公開する必要があります。これらがなければ、pip から価格への変換は 0.0001 に戻ります。これは主要な FX ペアに適しています。
- FX のストップレベル ルールを模倣するには、データ フィードで最良の買い値/売り値の更新 (レベル 1) を配信する必要があります。この戦略は、相場が見つからない場合、ローソク足の終値を使用することで正常に機能を低下させます。
- プロテクション注文には、ストップ注文とリミット注文をサポートするブローカー/取引所が必要です。利用できない場合は、市場からの撤退にフォールバックするようにコードを調整します。
使い方のヒント
- 価格変換を検証するには、デモ口座でマイクロボリューム (
0.01) から始めます。
TriggerDistancePips と TakeProfitPips を一緒に調整します。トリガーが小さいほど取引の頻度が高くなるため、それに応じてテイクプロフィットを下げることを検討してください。
- ログを介して
_lastBuyWasLoss フラグと _lastSellWasLoss フラグを監視し、適応型テイクプロフィット ロジックが MetaTrader の履歴と一致することを確認します。
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>
/// Disaster strategy - SMA with momentum breakout.
/// Buys when close is above SMA and momentum crosses above zero.
/// Sells when close is below SMA and momentum crosses below zero.
/// </summary>
public class DisasterStrategy : Strategy
{
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevMom;
private bool _hasPrev;
public int SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DisasterStrategy()
{
_smaPeriod = Param(nameof(SmaPeriod), 50)
.SetDisplay("SMA Period", "SMA lookback", "Indicators");
_momentumPeriod = Param(nameof(MomentumPeriod), 14)
.SetDisplay("Momentum Period", "Momentum 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(); _prevMom = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var sma = new SimpleMovingAverage { Length = SmaPeriod };
var mom = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, mom, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal sma, decimal mom)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (!_hasPrev)
{
_prevMom = mom;
_hasPrev = true;
return;
}
// Momentum crosses above zero + above SMA = buy
if (_prevMom <= 0 && mom > 0 && close > sma && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Momentum crosses below zero + below SMA = sell
else if (_prevMom >= 0 && mom < 0 && close < sma && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevMom = mom;
}
}
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 SimpleMovingAverage, Momentum
from StockSharp.Algo.Strategies import Strategy
class disaster_strategy(Strategy):
def __init__(self):
super(disaster_strategy, self).__init__()
self._sma_period = self.Param("SmaPeriod", 50).SetDisplay("SMA Period", "SMA lookback", "Indicators")
self._momentum_period = self.Param("MomentumPeriod", 14).SetDisplay("Momentum Period", "Momentum lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_mom = 0.0
self._has_prev = False
@property
def sma_period(self): return self._sma_period.Value
@property
def momentum_period(self): return self._momentum_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(disaster_strategy, self).OnReseted()
self._prev_mom = 0.0; self._has_prev = False
def OnStarted2(self, time):
super(disaster_strategy, self).OnStarted2(time)
self._has_prev = False
sma = SimpleMovingAverage()
sma.Length = self.sma_period
mom = Momentum()
mom.Length = self.momentum_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, mom, self.process_candle).Start()
def process_candle(self, candle, sma, mom):
if candle.State != CandleStates.Finished: return
close = float(candle.ClosePrice); sma_val = float(sma); mom_val = float(mom)
if not self._has_prev:
self._prev_mom = mom_val; self._has_prev = True; return
if self._prev_mom <= 0 and mom_val > 0 and close > sma_val and self.Position <= 0:
if self.Position < 0: self.BuyMarket()
self.BuyMarket()
elif self._prev_mom >= 0 and mom_val < 0 and close < sma_val and self.Position >= 0:
if self.Position > 0: self.SellMarket()
self.SellMarket()
self._prev_mom = mom_val
def CreateClone(self): return disaster_strategy()