GitHub で見る

MACD PSAR 戦略を修正しました

概要

この戦略は、MetaTrader エキスパート アドバイザー EA_MACD_FixedPSAR の C# ポートです。 MACD クロスオーバー フィルターと EMA トレンド チェックを組み合わせてトレンド反転を取引します。リスク管理は元の実装を反映しており、固定距離トレーリング ストップと Parabolic SAR スタイルのトレーリング モードの両方をサポートしています。すべての距離はピップ単位で設定され、商品ティックサイズに基づいて内部で価格単位に変換されます。

インジケーター

  • MovingAverageConvergenceDivergenceSignal (12、26、9) は、MACD と信号線を提供します。
  • ExponentialMovingAverage (デフォルトは 26) は、短期的なトレンドの方向を確認します。

取引ロジック

  1. エントリー条件
    • ロング: MACD はゼロ以下のままシグナルラインを上回り、MACD の絶対値は MACD 始値を超え、EMA は前のローソク足と比較して上昇しています。
    • ショート: MACD はゼロを超えたままシグナルラインを下回っており、MACD の絶対値は MACD 始値を超えており、EMA は前のローソク足と比較して下落しています。
  2. Exit Conditions
    • MACD 終値レベルを反対方向に超える MACD の反転。
    • 構成可能なテイクプロフィットとストップロスのレベルはどちらもピップ単位で測定されます。
    • オプションのトレーリングストップ動作:
      • 固定: 最新の終値から一定の距離を維持します。
      • 修正 PSAR: MQL バージョンで使用される増分 Parabolic SAR 調整をエミュレートします。

パラメーター

名前 説明
Volume 成行注文に使用される取引量。
TakeProfitPips 利益確定距離 (pips)。
StopLossPips ピップス単位のストップロス距離。
TrailMode トレーリング ストップ ロジック (NoneFixedFixedPsar)。
TrailingStopPips 固定トレーリングモードの距離。
PsarStep PSAR トレーリング モードの初期加速係数。
PsarMaximum PSAR トレーリング モードの最大加速係数。
MacdOpenLevelPips ポジションをオープンするために必要な最小値 MACD の大きさ (ピップ単位)。
MacdCloseLevelPips ポジションを決済するために必要な最小値 MACD の大きさ (ピップ単位)。
TrendPeriod EMA 期間はトレンドの確認に使用されます。
CandleType インジケーター計算用のローソク足シリーズ タイプ。

注意事項

  • すべてのしきい値はピップ単位で保存され、商品ティック サイズ (MetaTrader 調整をエミュレートする小数点以下 5 桁または 3 桁の修正) を使用して価格単位に変換されます。
  • トレーリングストップロジックは、早期の終了を避けるために、完全に形成されたローソク足でのみ更新されます。
  • この戦略では、利用可能な場合、デフォルトのチャート領域にローソク足、インジケーター、およびトレードマークの両方を描画します。
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;

/// <summary>
/// Simplified from "EA_MACD_FixedPSAR" MetaTrader expert.
/// Combines MACD histogram crossover with EMA trend filter.
/// Buys when MACD histogram goes positive and price above EMA.
/// Sells when MACD histogram goes negative and price below EMA.
/// </summary>
public class MacdFixedPsarStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<int> _trendPeriod;

	private MovingAverageConvergenceDivergence _macd;
	private ExponentialMovingAverage _trendEma;
	private readonly Queue<decimal> _macdHistory = new();
	private decimal? _prevHistogram;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	public int TrendPeriod
	{
		get => _trendPeriod.Value;
		set => _trendPeriod.Value = value;
	}

	public MacdFixedPsarStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for MACD calculations", "General");

		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators");

		_signalPeriod = Param(nameof(SignalPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Signal Period", "Signal line smoothing period", "Indicators");

		_trendPeriod = Param(nameof(TrendPeriod), 60)
			.SetGreaterThanZero()
			.SetDisplay("Trend EMA", "Trend filter EMA period (computed as SMA)", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevHistogram = null;
		_macdHistory.Clear();

		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = FastPeriod },
			LongMa = { Length = SlowPeriod },
		};
		_trendEma = new ExponentialMovingAverage { Length = TrendPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_macd, _trendEma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _macd);
			DrawIndicator(area, _trendEma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal macdValue, decimal trendValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_macd.IsFormed || !_trendEma.IsFormed)
			return;

		var close = candle.ClosePrice;

		// Compute signal line manually
		_macdHistory.Enqueue(macdValue);
		if (_macdHistory.Count > SignalPeriod)
			_macdHistory.Dequeue();

		if (_macdHistory.Count < SignalPeriod)
			return;

		decimal signalSum = 0;
		var history = _macdHistory.ToArray();
		foreach (var v in history)
			signalSum += v;
		var signal = signalSum / history.Length;

		var histogram = macdValue - signal;

		if (_prevHistogram is null)
		{
			_prevHistogram = histogram;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		var crossUp = _prevHistogram.Value <= 0 && histogram > 0;
		var crossDown = _prevHistogram.Value >= 0 && histogram < 0;

		if (crossUp && close > trendValue)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown && close < trendValue)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevHistogram = histogram;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_macd = null;
		_trendEma = null;
		_prevHistogram = null;
		_macdHistory.Clear();

		base.OnReseted();
	}
}