GitHub で見る
MACD サンプル 1010 戦略
概要
このモジュールは、MetaTrader エキスパート アドバイザ macd_sample_1010.mq4 を StockSharp の高レベル API に移植します。元のスクリプトでは、Bollinger バンドと単純な資金管理ルールを組み合わせていました。終値が上限バンドと設定可能なバッファを上回って終了すると売り注文が開始され、下限バンドを下回る終値からバッファを引いた場合は買い注文がトリガーされます。一定の利益または損失額 (pips で表される) に達すると、ポジションがクローズされました。 StockSharp バージョンは、要求されたローソク足シリーズをサブスクライブし、BollingerBands インジケーターをバインドし、ローソク足コールバックから成行注文とポジション管理呼び出しを発行することで、同じロジックを再現します。
変換により、完成したキャンドルに対する従来のエキスパートの動作が維持されます。すべての評価はローソク足が完全に形成されたときに行われ、ブレイクアウトとエグジットの決定が MetaTrader プラットフォームの足終値処理と一致することが保証されます。オプションの残高ベースの取引量スケーリングも、MQL4 コードから LotIncrease フラグをエミュレートするために実装されています。
変換メモ
- 高レベルの
SubscribeCandles + Bind ワークフローを使用して、カスタム バッファなしで BollingerBands インジケーターをフィードします。
- StockSharp
StrategyParam<T> インフラストラクチャを採用しているため、すべての入力がユーザー インターフェースに表示され、最適化の準備が整います。
- MetaTrader の pip ベースの計算と一致する、商品の
PriceStep を考慮した計算されたオフセットを使用して BuyMarket と SellMarket を呼び出します。
- 元のエキスパートと同様に、
Portfolio.CurrentValue を通じてオプションのロット スケーリングを実装し (フォールバックとして BeginValue を使用)、結果のボリュームを 500 ロットに制限します。
- 元のスクリプトがバーカウンターを介して制御していたティックごとのチャーンを回避するために、完成したキャンドルに対して厳密に機能します。
- 各処理ブロックの意図を明確にするために、説明的な英語のコメントを追加します。
パラメーター
| パラメータ |
種類 |
デフォルト |
説明 |
ProfitTargetPips |
decimal |
3 |
ポジションを利益で閉じるために必要な有利な動きのピップ数。テイクプロフィットルールを無効にするには、0 に設定します。 |
LossLimitPips |
decimal |
20 |
ポジションが清算されるまでに許容される逆方向の動きのピップ数。ストップロスルールを無効にするには、0 に設定します。 |
BandDistancePips |
decimal |
3 |
ブレイクアウトが確認される前に、上部バンドの上と下部バンドの下に追加されるバッファー (ピップ単位)。 |
BollingerPeriod |
int |
4 |
Bollinger バンドの計算に使用されるローソク足の数。 |
BollingerDeviation |
decimal |
2 |
Bollinger バンド インジケーターによって適用される標準偏差乗数。 |
BaseVolume |
decimal |
1 |
ロットで表される初期取引サイズ。この値は、スケーリング ロジックのベースラインとしても使用されます。 |
LotIncrease |
bool |
true |
有効にすると、現在のポートフォリオ残高と開始残高の比率に従うように、すべてのローソク足の取引量が再計算されます。 |
OneOrderOnly |
bool |
true |
すでにアクティブなポジションがある場合に、ストラテジーが新しいポジションをオープンできないようにします。 StockSharp は集計ポジションを使用するため、無効にしてもネットポジションは引き続き管理されます。 |
CandleType |
DataType |
TimeFrame(15m) |
インジケーターの計算と取引の決定の両方に使用されるローソク足シリーズ。 |
取引ロジック
OnStarted は、構成された期間と偏差を使用して Bollinger バンド インジケーターを作成し、CandleType データ ストリームをサブスクライブし、ProcessCandle メソッドをバインドします。
- 終了したローソク足はそれぞれ
ProcessCandle をトリガーし、シグナルを評価する前に現在の取引高を再計算します (LotIncrease がアクティブな場合)。
- 終値が上限バンドに
BandDistancePips (PriceStep で価格単位に変換) を加えた値より大きい場合、ストラテジーは成行売り注文を送信します。終値が下限バンドからバッファーを差し引いた値を下回っている場合、成行買い注文が送信されます。 OneOrderOnly が true の場合、ネット ポジションが 0 の場合にのみ新しいエントリが試行されます。
- 潜在的なエントリーが処理された後、ストラテジーは現在のポジションを検査します。
- ロングポジションは、利益距離が
ProfitTargetPips に達するか、損失が LossLimitPips に達するとクローズされます。
- ショートポジションは、終値が
ProfitTargetPips までに有利に動くか、LossLimitPips までに反対に動くとクローズされます。
- すべての損益比較は、シンボルの
PriceStep から派生した価格単位で実行され、MetaTrader エキスパートの pip ベースのチェックを忠実に再現します。
ポジションサイジングロジック
LotIncrease が無効になっている場合、戦略はすべてのシグナルで定数値 BaseVolume をトレードします。
LotIncrease が有効な場合、最初のローソク足にはロットごとの開始残高 (initial balance / BaseVolume) が保存されます。後続のローソク足では、現在の残高とそのベースラインの比率が計算され、小数点第 1 位に四捨五入され (MQL4 からの NormalizeDouble(..., 1) を模倣)、結果が最大 500 ロットに固定されます。計算された値は、次の取引の注文量として使用されます。
- ポートフォリオ情報が利用できない場合、戦略は静的な
BaseVolume に正常にフォールバックします。
使用ガイドライン
- 戦略を目的の商品にアタッチし、
Security.PriceStep が取引する予定のピップ サイズを反映していることを確認します。
CandleType でローソク足の時間枠を選択します。元のスクリプトは通常、日中の時間枠 (5 ~ 15 分) で実行されましたが、任意のバー サイズを使用できます。
- 取引の好みに合わせてバンド設定、ピップオフセット、リスク管理を調整します。
- ポジションサイズを口座残高(
LotIncrease)に応じて拡大するか、固定のままにするかを決定します。
- 戦略を開始します。ログを監視して、予想される価格レベルで完了したローソク足でエントリーとエグジットが発生していることを確認します。
- StockSharp は集約されたポジションで動作するため、
OneOrderOnly が無効になっている場合でも、結果は複数の独立したチケットではなく、単一のネット ポジションになります。
- テイクプロフィットとストップロスのルールは、特定の価格レベルで未決注文を登録するのではなく、ストラテジーに直接実装されますが、チェックは終了したローソク足ごとに行われるため、結果として得られる動作は同等です。
- 元のエキスパートからのログ フラグ (
logging、logerrs、logtick) は省略されています。 StockSharp の組み込みロギングは、注文と取引のイベントをすでに記録しています。
- StockSharp はポートフォリオと戦略を通じてより豊富な分析を公開しているため、MetaTrader バージョンのファイルベースのロギングと統計は再作成されません。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD Sample 1010: SMA band mean reversion with ATR stops.
/// </summary>
public class MacdSample1010Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _bandMult;
private decimal _entryPrice;
public MacdSample1010Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_smaLength = Param(nameof(SmaLength), 20)
.SetDisplay("SMA Length", "Band center period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
_bandMult = Param(nameof(BandMult), 2.0m)
.SetDisplay("Band Multiplier", "ATR multiplier for bands.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal BandMult
{
get => _bandMult.Value;
set => _bandMult.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
var sma = new SimpleMovingAverage { Length = SmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
var upper = smaVal + atrVal * BandMult;
var lower = smaVal - atrVal * BandMult;
if (Position > 0)
{
if (close >= smaVal || close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= smaVal || close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close < lower)
{
_entryPrice = close;
BuyMarket();
}
else if (close > upper)
{
_entryPrice = close;
SellMarket();
}
}
}
}
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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class macd_sample_1010_strategy(Strategy):
"""
SMA band mean reversion with ATR-based bands and stops.
"""
def __init__(self):
super(macd_sample_1010_strategy, self).__init__()
self._sma_length = self.Param("SmaLength", 20).SetDisplay("SMA Length", "Band center period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period", "Indicators")
self._band_mult = self.Param("BandMult", 2.0).SetDisplay("Band Multiplier", "ATR multiplier for bands", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_sample_1010_strategy, self).OnReseted()
self._entry_price = 0.0
def OnStarted2(self, time):
super(macd_sample_1010_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._sma_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
sma = float(sma_val)
atr = float(atr_val)
if atr <= 0:
return
close = float(candle.ClosePrice)
bm = float(self._band_mult.Value)
upper = sma + atr * bm
lower = sma - atr * bm
if self.Position > 0:
if close >= sma or close <= self._entry_price - atr * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= sma or close >= self._entry_price + atr * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close < lower:
self._entry_price = close
self.BuyMarket()
elif close > upper:
self._entry_price = close
self.SellMarket()
def CreateClone(self):
return macd_sample_1010_strategy()