GitHub で見る

Diff TF MA戦略

概要

  • この戦略はMetaTraderのエキスパートアドバイザー "Diff_TF_MA_EA" をStockSharpに移植したものです。
  • トレードシグナルは、より高い時間軸で計算された単純移動平均と、トレーディング時間軸に再スケールされた別の移動平均を比較することで生成されます。
  • コードは完了済みのローソク足のみを保持し、元のクロスオーバールールを反映し、新しいポジションを開く前に反対側のエクスポージャーを閉じます。

パラメーター

名前 説明
MaPeriod 高い時間軸で計算される単純移動平均の長さ。
CandleType 注文生成に使用するトレーディング時間軸。
HigherCandleType 参照移動平均を提供する高い時間軸。
ReverseSignals クロスオーバールールを逆転させます (弱気クロスで買い、強気クロスで売り)。
Volume BuyMarket/SellMarket 呼び出しで使用される戦略ボリューム (基底クラス Strategy.Volume プロパティで設定)。

トレーディングロジック

  1. トレーディング時間軸 (CandleType) と高い時間軸 (HigherCandleType) の両方をサブスクライブする。
  2. 高い時間軸で MaPeriod の長さを持つ単純移動平均を構築する。
  3. 時間軸の期間比を掛けて高い時間軸の長さをトレーディング時間軸に変換し、トレーディングローソク足でもう一つの移動平均を実行する。
  4. 両方の移動平均について最後の2つの完了値を保存し、完了した各トレーディングローソク足でクロスをチェックする。
  5. 高い時間軸のMAがトレーディングMAを上抜けするとき (ReverseSignalstrue でない限り)、ロングポジションを開くまたは反転する。
  6. 高い時間軸のMAがトレーディングMAを下抜けするとき (ReverseSignalstrue でない限り)、ショートポジションを開くまたは反転する。
  7. 既存のエクスポージャーを相殺するのに十分なボリュームを送信することでポジションをフラット化して反転させる。

使用上の注意

  • 互換性のある時間軸を選択する:高い時間軸は通常、再スケールされた長さが意味を持つようにトレーディング時間軸より大きくする必要があります。
  • デフォルトのボリュームは 1 です。別のサイズが必要な場合は戦略を開始する前に Strategy.Volume を調整してください。
  • MetaTraderバージョンのストップとテイクプロフィットは再現されていません。必要に応じてStockSharpの保護機能でリスク管理を追加できます。
  • ReverseSignals が有効な場合、強気と弱気のアクションが入れ替わり、残りのロジックは変わりません。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Two timeframe moving average crossover strategy.
/// </summary>
public class DiffTfMaStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<DataType> _higherCandleType;
	private readonly StrategyParam<bool> _reverseSignals;

	private SimpleMovingAverage _baseMa;
	private SimpleMovingAverage _higherMa;

	private decimal? _higherMaLast;
	private decimal? _higherMaPrev;
	private decimal? _baseMaLast;
	private decimal? _baseMaPrev;

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

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

	public DataType HigherCandleType
	{
		get => _higherCandleType.Value;
		set => _higherCandleType.Value = value;
	}

	public bool ReverseSignals
	{
		get => _reverseSignals.Value;
		set => _reverseSignals.Value = value;
	}

	public DiffTfMaStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average length on the higher timeframe", "General")
			
			.SetOptimize(5, 30, 5);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Base Candle", "Trading timeframe", "General");

		_higherCandleType = Param(nameof(HigherCandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Higher Candle", "Higher timeframe for confirmation", "General");

		_reverseSignals = Param(nameof(ReverseSignals), false)
			.SetDisplay("Reverse Signals", "Invert the crossover logic", "General");

		Volume = 0.1m;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType), (Security, HigherCandleType)];
	}

	protected override void OnReseted()
	{
		base.OnReseted();

		_higherMaLast = null;
		_higherMaPrev = null;
		_baseMaLast = null;
		_baseMaPrev = null;
	}

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

		if (CandleType.Arg is not TimeSpan baseSpan || baseSpan <= TimeSpan.Zero)
			throw new InvalidOperationException("CandleType must contain a positive TimeSpan argument.");

		if (HigherCandleType.Arg is not TimeSpan higherSpan || higherSpan <= TimeSpan.Zero)
			throw new InvalidOperationException("HigherCandleType must contain a positive TimeSpan argument.");

		var ratio = higherSpan.TotalMinutes / baseSpan.TotalMinutes;
		var baseLength = Math.Max(1, (int)(MaPeriod * ratio));

		_baseMa = new SimpleMovingAverage { Length = baseLength };
		_higherMa = new SimpleMovingAverage { Length = MaPeriod };

		var higherSubscription = SubscribeCandles(HigherCandleType);
		higherSubscription.Bind(_higherMa, ProcessHigher).Start();

		var baseSubscription = SubscribeCandles(CandleType);
		baseSubscription.Bind(_baseMa, ProcessBase).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, baseSubscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessHigher(ICandleMessage candle, decimal higherMaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_higherMa.IsFormed)
			return;

		// Store the last two higher timeframe MA values for crossover comparison.
		_higherMaPrev = _higherMaLast;
		_higherMaLast = higherMaValue;
	}

	private void ProcessBase(ICandleMessage candle, decimal baseMaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_baseMa.IsFormed)
			return;

		// Track the last two base timeframe MA values.
		_baseMaPrev = _baseMaLast;
		_baseMaLast = baseMaValue;

		if (_higherMaPrev is not decimal higherPrev || _higherMaLast is not decimal higherLast)
			return;

		if (_baseMaPrev is not decimal basePrev || _baseMaLast is not decimal baseLast)
			return;

		var crossUp = higherPrev < basePrev && higherLast > baseLast;
		var crossDown = higherPrev > basePrev && higherLast < baseLast;

		if (ReverseSignals)
			(crossUp, crossDown) = (crossDown, crossUp);

		// Execute orders according to the detected crossover direction.
		if (crossUp && Position <= 0)
		{
			BuyMarket(Volume + Math.Abs(Position));
		}
		else if (crossDown && Position >= 0)
		{
			SellMarket(Volume + Math.Abs(Position));
		}
	}
}