在 GitHub 上查看

Grid 再平衡策略

Grid 再平衡策略是基于 StockSharp 高级 API 重写的 Mission Automate「Grid」专家顾问。策略在多头与空头网格之间交替运行,并始终在当前方向上维持阶梯式限价单。当组合仓位触及统一的止盈价位后,策略会平掉全部仓位、取消所有挂单,并以相反方向开始下一轮循环。

工作流程

  1. 开启循环:当没有持仓也没有挂单时,策略按照 FirstTradeSide 指定的方向,用 StartVolume 手开立市价仓位。
  2. 铺设网格:每次在当前方向成交后,会在距离 GridStepPoints(通过品种的 PriceStep 转换为绝对价格)的价位重新挂出一个限价单。新订单的手数等于最近一次成交手数乘以 LotMultiplier
  3. 基于均价的止盈:每次加仓后都会重新计算加权平均持仓价格。整体止盈价设置为平均价加/减 TargetPoints(同样通过 PriceStep 转换)。策略使用 K 线的最高价和最低价来模拟服务器端触发逻辑。
  4. 结束循环:当价格触及止盈位时,策略用市价单平掉全部仓位,撤销剩余挂单,记录本次循环的方向,并在下一轮采用相反方向。

参数说明

  • FirstTradeSide:首个循环的方向(BuySell),每完成一轮会自动切换方向。
  • StartVolume:每个循环首单的市价手数。
  • LotMultiplier:为下一层网格计算手数时使用的乘数,大于 1 时形成类似马丁格尔的加仓。
  • GridStepPoints:网格层之间的距离(点数),通过 Security.PriceStep 转换成实际价格间隔。
  • TargetPoints:止盈距离(点数),相对于持仓加权平均价计算。
  • CandleType:用于监控高低点、判定止盈触发的 K 线类型。

风控与实现细节

  • 策略没有设置硬性止损,行情逆行时会持续加仓。
  • 任意时刻只会保持一个挂单,成交后立即安排下一层网格。
  • 只有在没有持仓、没有挂单且品种提供了有效 PriceStep 时才会启动新的循环。
  • 所有计算都在策略内部完成,没有使用全局集合或指标缓冲区,符合项目规范。
  • 循环结束时会统一撤销挂单,避免遗留旧订单。

其他说明

  • 所有以点数表示的参数都会乘以 Security.PriceStep 转换成价格;若价格步长未知,策略会等待交易所提供数据。
  • 实现完全依赖高层 API(SubscribeCandlesBindBuyMarketSellMarketBuyLimitSellLimit)。
  • 本任务按要求不提供 Python 版本。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Grid Rebalance strategy: RSI + EMA crossover-based.
/// Buys when close crosses above EMA with RSI confirmation.
/// Sells when close crosses below EMA with RSI confirmation.
/// </summary>
public class GridRebalanceStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;

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

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public GridRebalanceStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");

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

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

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		decimal? prevClose = null;
		decimal? prevEma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, ema, (candle, rsiVal, emaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

				if (prevClose.HasValue && prevEma.HasValue)
				{
					var crossUp = prevClose.Value <= prevEma.Value && close > emaVal;
					var crossDown = prevClose.Value >= prevEma.Value && close < emaVal;

					if (crossUp && rsiVal < 55m && Position <= 0)
						BuyMarket();
					else if (crossDown && rsiVal > 45m && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevEma = emaVal;
			})
			.Start();

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