GitHub で見る

買いと売りのグリッド戦略

概要

この戦略は、常にひとつのロングポジションとひとつのショートポジションを開いておくシンプルなグリッドアプローチを実装しています。市場がどちらか一方のテイクプロフィットに達するほど動いたとき、反対側のポジションも決済され、次のグリッドレベルがより大きな量で開かれます。量はVolumeMultiplierパラメーターに従って幾何学的に増加します。

パラメーター

パラメーター 説明
TakeProfitPoints 価格ステップで測定したテイクプロフィットの距離。
InitialVolume 最初の注文ペアに使用する量。
VolumeMultiplier 各新しいグリッドレベルの量に適用する乗数。
MaxTrades 許可されるグリッドレベルの最大数。
CandleType 戦略ロジックをトリガーするために使用するローソク足データの種類。

取引ロジック

  1. 開始 – 戦略は指定されたローソク足シリーズをサブスクライブし、最初の買いと売りの成行注文ペアを発注します。
  2. 監視 – 確定したローソク足ごとに最終価格とエントリー価格を比較します。一方のサイドの利益目標に達した場合、両方のポジションが決済されます。
  3. グリッド進行 – 全ポジション決済後、次のグリッドレベルをVolumeMultiplierで乗算した量で開きます。
  4. 制限MaxTradesレベルが開かれるまでプロセスを繰り返します。

この戦略はインジケーターや複雑な計算を使用しないため、StockSharp内での注文管理とポジション管理のデモンストレーションに適しています。

備考

  • コード内のすべてのコメントは要件通り英語で記述されています。
  • 戦略は市場データ取得にSubscribeCandlesを使用した高レベルAPIを使用しています。
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>
/// Grid strategy using EMA mean-reversion.
/// Buys when price drops below EMA by a threshold, sells when it rises above.
/// </summary>
public class BuySellGridStrategy : Strategy
{
	private readonly StrategyParam<decimal> _gridStepPct;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;

	/// <summary>
	/// Grid step as percentage from EMA.
	/// </summary>
	public decimal GridStepPct
	{
		get => _gridStepPct.Value;
		set => _gridStepPct.Value = value;
	}

	/// <summary>
	/// EMA period for mean reference.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// Candle type used to trigger strategy logic.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="BuySellGridStrategy"/>.
	/// </summary>
	public BuySellGridStrategy()
	{
		_gridStepPct = Param(nameof(GridStepPct), 0.3m)
			.SetDisplay("Grid Step %", "Distance from EMA for grid entry", "General")
			.SetGreaterThanZero();

		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("EMA Period", "EMA period for grid center", "Indicators")
			.SetGreaterThanZero();

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle type for processing", "General");
	}

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

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, ProcessCandle)
			.Start();
	}

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

		var close = candle.ClosePrice;
		var lowerGrid = emaValue * (1m - GridStepPct / 100m);
		var upperGrid = emaValue * (1m + GridStepPct / 100m);

		if (Position == 0)
		{
			if (close <= lowerGrid)
			{
				BuyMarket();
				_entryPrice = close;
			}
			else if (close >= upperGrid)
			{
				SellMarket();
				_entryPrice = close;
			}
		}
		else if (Position > 0)
		{
			// Take profit at EMA or above
			if (close >= emaValue)
			{
				SellMarket();
			}
			// Add on further dip
			else if (close <= _entryPrice * (1m - GridStepPct / 100m))
			{
				BuyMarket();
				_entryPrice = close;
			}
		}
		else if (Position < 0)
		{
			// Take profit at EMA or below
			if (close <= emaValue)
			{
				BuyMarket();
			}
			// Add on further rally
			else if (close >= _entryPrice * (1m + GridStepPct / 100m))
			{
				SellMarket();
				_entryPrice = close;
			}
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
	}
}