在 GitHub 上查看

Buy Sell Grid 策略

概述

该策略实现了一个简单的网格模型,同时持有多头和空头仓位。当市场移动达到一方的止盈时,另一方仓位也会被关闭,并以更大的数量开启下一层网格。每一层的数量按 VolumeMultiplier 参数呈几何级数增长。

参数

参数 说明
TakeProfitPoints 以价格步长表示的止盈距离。
InitialVolume 第一对订单的数量。
VolumeMultiplier 每个新网格层级的数量乘数。
MaxTrades 允许的最大网格层级数。
CandleType 用于触发策略逻辑的蜡烛类型。

交易逻辑

  1. 启动 – 策略订阅指定的蜡烛序列并开立第一对买卖市场订单。
  2. 监控 – 每根完成的蜡烛都会检查价格与入场价的距离。当达到任一方向的止盈时,两边仓位全部平仓。
  3. 网格推进 – 平仓后,以 VolumeMultiplier 放大的数量开启下一层网格。
  4. 限制 – 该过程重复进行,直到达到 MaxTrades 层为止。

该策略不使用任何指标或复杂计算,适合作为 StockSharp 中订单管理与仓位控制的示例。

备注

  • 代码中的注释全部使用英文。
  • 策略通过高层 API 的 SubscribeCandles 获取市场数据。
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;
	}
}