GitHub で見る

Macd Secrets戦略

概要

Macd Secrets戦略は、MetaTrader 向けの元エキスパートアドバイザー "Macd Secrets I" に着想を得た、マルチタイムフレームの momentum フォローシステムです。StockSharp 版は高レベル API を使い、3 つの時間枠で MACD 方向をそろえながら、線形加重移動平均 (LWMA) のベースラインと momentum 偏差チェックで取引をフィルターすることに重点を置きます。戦略は常に 1 つのネットポジションだけを保持するため、複数注文をピラミッディングできた元 EA と比べて、単純で透明なリスクプロファイルを提供します。

シグナル生成

ロングセットアップ

  1. 実行時間枠で高速 LWMA が低速 LWMA より下にあり、価格がトレンドチャネルの下側付近で取引されていることを示します (元の EA も同じフィルターを適用します)。
  2. MACD ラインが、追跡するすべての時間枠 (実行、トレンド確認、月次確認) でシグナルラインより上にあります。これは MQL 版のトリプル MACD 整列を再現します。
  3. トレンド時間枠で直近 3 つの momentum 読み取り値の少なくとも 1 つが、設定された最小値 (デフォルト 0.3) だけ 100 から乖離しています。この偏差計算は EA の MathAbs(100 - Momentum) ロジックを再現します。
  4. 現在オープンポジションがありません。

条件が満たされると、設定数量で成行買い注文を出します。

ショートセットアップ

  1. MACD ラインが、実行、トレンド、月次の各時間枠でシグナルラインより下にあります。
  2. トレンド時間枠で直近 3 つの momentum 偏差の少なくとも 1 つが、設定されたショートしきい値を超えています。
  3. 現在オープンポジションがありません (この移植版はヘッジとスケーリングを避けます)。

すべてのルールが成立すると、成行売り注文を送信します。

取引管理

  • 戦略は任意で、stop-loss と take-profit の両方にポイントベース距離を使った保護注文を開始できます。これらの距離は、ポイントを価格増分へ変換するため、銘柄の価格ステップを掛けます。
  • 元の EA の trailing-stop、breakeven、equity ベース保護ロジックは含まれていません。StockSharp の保護は起動時に 1 回だけ適用されます。
  • シグナルは、バー内ノイズを避けるため、確定ローソク足でのみ評価されます。

マルチタイムフレームデータ

  • 主要時間枠: 実行頻度 (デフォルト 15 分)。MACD と LWMA ペアはここで計算されます。
  • トレンド時間枠: 上位時間枠の確認 (デフォルト 1 時間)。この購読で MACD と momentum の両方が動作します。momentum 偏差は直近 3 本の確定ローソク足から収集されます。
  • 月次時間枠: 長期 MACD 確認 (カレンダー月に近づけるためデフォルト 30 日)。

戦略は GetWorkingSecurities をオーバーライドし、3 つすべての購読が最初からコネクターに要求されるようにします。

パラメーター

名前 説明 デフォルト
OrderVolume ロット単位の取引数量。正の値である必要があります。 0.1
TakeProfitPoints ポイント単位で測定される take-profit 距離。無効にするにはゼロに設定します。 50
StopLossPoints ポイント単位の stop-loss 距離。無効にするにはゼロに設定します。 20
FastMaPeriod 主要時間枠での高速 LWMA 長。 6
SlowMaPeriod 主要時間枠での低速 LWMA 長。 85
MacdFastPeriod 各 MACD インスタンスが使用する高速 EMA 期間。 12
MacdSlowPeriod 各 MACD インスタンスが使用する低速 EMA 期間。 26
MacdSignalPeriod MACD のシグナル EMA 期間。 9
MomentumPeriod トレンド時間枠での momentum ルックバック。 14
MomentumBuyThreshold ロング取引に必要な 100 からの最小絶対偏差。 0.3
MomentumSellThreshold ショート取引に必要な 100 からの最小絶対偏差。 0.3
PrimaryCandleType 実行用ローソク足タイプ。デフォルトは 15 分時間枠。 15m
TrendCandleType 確認用ローソク足タイプ。デフォルトは 1 時間時間枠。 1h
MonthlyCandleType 長期確認用ローソク足タイプ。デフォルトは 30 日バー。 30d

使用上の注意

  • LWMA フィルターは意図的に非対称です。元の MQL スクリプトで観察された動作に合わせ、ロング取引だけが高速 LWMA が低速 LWMA より下にあることを要求します。
  • この移植版は単一のネットポジションを取引するため、ソースコードにあるマーチンゲール型のポジションサイジング (LotsOptimized) を省略しています。積み上げが必要な場合は、約定数量を追跡して OrderVolume と比較することで再導入できます。
  • 接続されたブローカーまたはデータソースが 3 つすべてのローソク足時間枠を提供できることを確認してください。そうでない場合、戦略はインジケーター形成を待って停止したままになります。
  • 30 日足が利用できない市場では、カスタム DataType パラメーターを指定して月次時間枠を調整することを検討してください。
  • 戦略は完全に確定ローソク足で動作し、履歴インジケーターバッファーを直接読み取らないため、StockSharp のインジケーター利用ガイドラインに準拠しています。

元のEAとの違い

  • trailing-stop、breakeven、金額ベースのエグジット、口座全体の equity 保護は移植していません。代わりに、静的距離による StockSharp 保護を使います。
  • 明確さのため、注文ピラミッディングとマーチンゲールロジックは省略しています。ポジションサイジングは一定のままです。
  • 通知 (アラート、メール、プッシュメッセージ) は実装されていません。

免責事項

アルゴリズム取引には大きな金融リスクがあります。実資金で運用する前に、履歴データとシミュレーション環境で戦略をテストしてください。

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;

public class MacdSecretsStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public MacdSecretsStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}