GitHub で見る

CrossoverMA戦略

概要

この戦略は、MetaTrader 5エキスパートアドバイザー CrossoverMA.mq5 のStockSharpポートです。オリジナルのロボットはキャンドルが移動平均線を越えるのを待ち、平均線がブレイクアウトと同じ方向に傾いているときのみポジションを開きます。StockSharpバージョンは同じ動作を維持しながら、キャンドルサブスクリプション、インジケーター管理、自動チャートレンダリングのための高レベルAPIを活用します。

トレードロジック

  1. 設定されたキャンドルシリーズをサブスクライブし、キャンドルのクローズ価格での単純移動平均(SMA)を計算します。
  2. 完了したキャンドルを受信したとき、以下を測定します:
    • SMAからのキャンドルの始値と終値の距離。
    • 現在の値と前の値を比較してSMAの傾きを計算。
  3. シグナルを生成します:
    • 強気ブレイクアウト – キャンドルがSMAの下で開き、上で閉じ、SMAが上昇しています。戦略はショートエクスポージャーを閉じ、ロングポジションを開く/拡張します。
    • 弱気ブレイクアウト – キャンドルがSMAの上で開き、下で閉じ、SMAが下落しています。戦略はロングエクスポージャーを閉じ、ショートポジションを開く/拡張します。
  4. 現在のポジションサイドを変更しない重複シグナルを無視します。

ポートは完成したキャンドルのみが処理され、最初のトレードの前に追加のキャンドルが必要というMetaTraderのルールを維持します(SMAの傾きを測定するため)。

パラメーター

名称 説明 デフォルト 備考
Candle Type キャンドル構築に使用される時間軸。 1分時間軸 StockSharpがサポートする任意のキャンドルデータタイプを選択できます。
MA Length SMAに含まれる完成したキャンドルの数。 12 MetaTraderエキスパートのデフォルト期間と一致します。
Trade Volume エントリー用の成行注文ボリューム。 1 戦略は新しいポジションを開く前に反対のエクスポージャーを閉じます。

すべてのパラメーターはStockSharp DesignerまたはRunnerで最適化に利用できます。

実装ノート

  • 戦略はSubscribeCandlesBindに依存して、インジケーター値を手動の履歴管理なしに直接処理メソッドにストリーミングします。
  • SMAはチャートエリアが利用可能な場合にそこに描画するためにプライベートフィールドに格納されます。
  • シグナルはIsFormedAndOnlineAndAllowTrading()trueを返すときのみ処理され、戦略がグローバルな取引状態を尊重することを保証します。
  • ポジション反転はMetaTraderテンプレートに従います:まず現在のエクスポージャーを閉じ、次に設定されたトレードボリュームで新しいサイドを開きます。

ファイル

  • CS/CrossoverMaStrategy.cs – 変換された戦略のC#実装。
  • README.md – 英語ドキュメント。
  • README_zh.md – 中国語ドキュメント。
  • README_ru.md – ロシア語ドキュメント。

ポーティングの違い

  • StockSharpはポジションサイジングとリスクを外部で管理するため、資金管理、トレーリングストップ、その他のMetaTraderフレームワーククラスは省略されています。Trade Volumeパラメーターはオリジナルエキスパートの固定ロット設定を置き換えます。
  • MetaTraderはキャンドルの始値と終値に対して別々のデータシリーズを使用していました。StockSharpのキャンドルはすでに両方の価格を含むため、追加のインジケーターは必要ありません。
  • インジケーターの初期化、検証、ライフサイクル管理はStockSharpによって自動的に処理され、MQLバージョンの大量のボイラープレートコードが削除されます。
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 CrossoverMaStrategy : 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 CrossoverMaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).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;
	}
}