SSB5_123 マルチインジケーター戦略
概要
この戦略は、MetaTrader 5 エキスパートアドバイザー「ssb5_123」のStockSharpポートです。元のコードはYury V. ReshetovによるSSB(Step by Step)コレクションから来ており、方向性ブレイクアウトを確認するためにいくつかのクラシックなオシレーターを組み合わせています。StockSharpバージョンは、高レベルのキャンドルサブスクリプションAPIとネイティブインジケーター実装を使用しながら、同じロジックを維持します。
アルゴリズムは完成したキャンドルのみで動作します。現在のバーの始値と前のバーを比較し、Awesome Oscillator、MACD、OsMAヒストグラムのモメンタムと方向を確認し、価格が平滑化移動平均の上または下で取引されていることを検証します。追加の確認は、%Kと%Dの両方がレベル50の上または下にあることを要求するストキャスティクスオシレーターから得られます。
インジケーターとシグナル
以下のインジケーターはMetaTraderバージョンとまったく同じように使用されます:
- 平滑化移動平均(SMMA): キャンドルの始値から計算された45期間の平滑化移動平均。エントリー方向は、始値が平均の正しい側にあることを要求します。
- MACD(速い47、遅い95、シグナル74): メインラインはロングトレードでは正(ショートトレードでは負)でなければならず、前のキャンドルと比較して上昇(下落)していなければなりません。
- OsMAヒストグラム: MACDからシグナルラインを引いて計算されます。ヒストグラムはロングトレードでは減少し、ショートトレードでは増加する必要があり、元の
fosma1()関数を反映します。
- Awesome Oscillator: メジアン価格のデフォルト5/34平滑化移動平均を使用します。オシレーター値はロングでは正(ショートでは負)でなければならず、最後の2バー間のモメンタムはトレード方向を指していなければなりません。
- ストキャスティクスオシレーター(K=25、D=12、Slowing=56): %Kと%Dの両ラインはロングトレードでは50以上、ショートトレードでは50以下でなければならず、レジームフィルターを提供します。
取引ロジック
- 新しい完成したキャンドルを待つ。
- ロングセットアップを評価する。以下のすべての条件が真でなければならない:
- 現在のキャンドルの始値が前のキャンドルの始値以下である。
- Awesome Oscillatorが正で、前の値より下落している。
- MACDメインラインが正で、前の値より上昇している。
- OsMAヒストグラムが増加していない(現在のヒストグラム - 前のヒストグラム ≤ 0)。
- 現在のキャンドルの始値が平滑化移動平均の上にある。
- ストキャスティクスの%Kと%Dラインが50以上にある。
- ショートセットアップを評価する。以下のすべての条件が真でなければならない:
- 現在のキャンドルの始値が前のキャンドルの始値以上である。
- Awesome Oscillatorが負で、前の値より上昇している。
- MACDメインラインが負で、前の値より下落している。
- OsMAヒストグラムが減少していない(現在のヒストグラム - 前のヒストグラム ≥ 0)。
- 現在のキャンドルの始値が平滑化移動平均の下にある。
- ストキャスティクスの%Kと%Dラインが50以下にある。
- ポジションがすでに存在する場合、反対のシグナルが即座にそれを閉じ、元のMetaTraderオーダー管理を複製します。
- フラット時、ロングエントリーが優先されます:両方のシグナルが真になる場合(すべてのインジケーターが正確にゼロの場合に可能)、戦略はロングポジションを開きます。そうでなければ、ショート条件のみが満たされる場合にショートポジションを開きます。
パラメーター
- SMMA Period – 平滑化移動平均フィルターの長さ(デフォルト45)。
- MACD Fast / Slow / Signal – MACDインジケーターのEMA期間(47 / 95 / 74)。
- Stochastic %K / %D / Slowing – ストキャスティクスオシレーターのメイン期間、平滑化期間、追加スローイング(25 / 12 / 56)。
- Order Volume – 成行注文に使用される数量(デフォルト1)。
- Candle Type – 入力キャンドルのタイムフレーム(デフォルト1時間)。MetaTraderで使用されるタイムフレームに合わせて調整します。
使用上の注意
- 戦略は完成したキャンドルのみで動作し、バー内の更新は無視されます。
- 前のキャンドルのインジケーター値はキャッシュされ、モメンタムの比較が元の
fao1、fmacd1、fosma1ヘルパー関数の正確な動作と一致するようにします。
- 元のエキスパートアドバイザーには組み込みのストップロスまたはテイクプロフィットルールはありません。必要に応じてリスク管理を外部で追加する必要があります。
- デフォルトのインジケーター設定は提供されたMQLパラメーターと一致しますが、すべての値は
StrategyParamオブジェクトとして公開され、StockSharpオプティマイザーを通じて最適化できます。
変換の考慮事項
- MetaTraderバージョンはマジックナンバーと手動ボリューム検証を使用しています;これらの部分はStockSharpでは不要であり、省略されました。
- 注文クローズのロジックはMQLスクリプトと同じ優先順位に従います:ポジションが最初にクローズされ、新しいエントリーは戦略がフラットな場合のみ行われます。
- Awesome OscillatorとMACDの実装はStockSharpインジケーターライブラリから来ており、元のコードに存在する手動バッファー処理の必要性を排除します。
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>
/// SSB5 123 strategy. Uses triple EMA alignment for 1-2-3 pattern detection.
/// </summary>
public class Ssb5123Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _midPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast;
private decimal? _prevMid;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int MidPeriod
{
get => _midPeriod.Value;
set => _midPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public Ssb5123Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_midPeriod = Param(nameof(MidPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Mid EMA", "Middle EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevMid = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null;
_prevMid = null;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var mid = new ExponentialMovingAverage { Length = MidPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, mid, slow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, mid);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal midVal, decimal slowVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fastVal;
_prevMid = midVal;
return;
}
if (_prevFast == null || _prevMid == null)
{
_prevFast = fastVal;
_prevMid = midVal;
return;
}
// Fast crosses above mid while both above slow
if (_prevFast.Value <= _prevMid.Value && fastVal > midVal && midVal > slowVal && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Fast crosses below mid while both below slow
else if (_prevFast.Value >= _prevMid.Value && fastVal < midVal && midVal < slowVal && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevFast = fastVal;
_prevMid = midVal;
}
}
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 ssb5123_strategy(Strategy):
def __init__(self):
super(ssb5123_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 5) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._mid_period = self.Param("MidPeriod", 12) \
.SetDisplay("Mid EMA", "Middle EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 26) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_mid = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def MidPeriod(self):
return self._mid_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(ssb5123_strategy, self).OnReseted()
self._prev_fast = None
self._prev_mid = None
def OnStarted2(self, time):
super(ssb5123_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_mid = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
mid = ExponentialMovingAverage()
mid.Length = self.MidPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, mid, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, mid)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, mid_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
mv = float(mid_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_mid is None:
self._prev_fast = fv
self._prev_mid = mv
return
# Fast crosses above mid while both above slow
if self._prev_fast <= self._prev_mid and fv > mv and mv > sv and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Fast crosses below mid while both below slow
elif self._prev_fast >= self._prev_mid and fv < mv and mv < sv and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fv
self._prev_mid = mv
def CreateClone(self):
return ssb5123_strategy()