在 GitHub 上查看

网格模板策略

概述

该策略是 MetaTrader 4 专家顾问 Grid_Template 的 StockSharp 移植版本。它会在当前买卖价附近构建一组对称的挂单网格,可直接作为突破模板使用,也可以在此基础上叠加自定义入场过滤条件。当所有挂单成交或被取消后,策略会立即重建新网格。实现保留了原始 EA 的资金管理公式,以及在设定小时数后自动撤销未触发挂单的机制。

交易逻辑

  • 订阅 Level1 报价,持续跟踪最佳买价与卖价,无需蜡烛或指标。
  • 当账户没有持仓且策略没有任何活动订单时,分别在卖价上方和买价下方各放置 GridOrders 张止损挂单。
  • 第一层挂单距离当前价格 PriceDistancePips 个点,之后每一层额外再增加 GridStepPips 个点的间距。
  • 所有挂单共用同一笔固定手数(或资金管理计算得到的手数),并使用相同的止盈、止损点数。
  • 挂单被触发成交后,策略立即登记对应的止损 (SellStop/BuyStop) 与止盈 (SellLimit/BuyLimit) 保护单,注释与入场单保持一致,便于识别。
  • 如果在到期时间前没有任何挂单被触发,则撤销所有尚未执行的挂单并重新布置网格。

资金管理

  • UseMoneyManagement 关闭时,所有订单都使用固定参数 StaticVolume 所定义的手数。
  • 当开启资金管理时,手数按照原始模板公式 freeMargin * RiskPercent / 100000 计算,再根据交易所的 VolumeStepVolumeMinVolumeMax 进行四舍五入与裁剪。策略使用投资组合的当前价值近似 MT4 的可用保证金。
  • 计算出的手数会再次根据合约最小手数规范化,如果低于允许范围则返回 0,以避免提交无效订单。

订单与风险控制

  • 买入止损挂单价格 = ask + PriceDistancePips + GridStepPips * level,卖出方向采用对称计算。
  • 保护单在挂单成交后才会注册,从而模拟 MT4 中止盈/止损直接附着在同一交易单上的行为。
  • PendingExpirationHours 控制挂单的存活时间,为 0 时表示挂单会一直保留直至成交或手动取消。
  • 净持仓回到 0 后,策略会自动撤销任何仍处于活动状态的保护单,确保环境干净。

参数

参数 说明
OrderComment 为所有网格订单写入的注释,与原 EA 保持一致。
StaticVolume 在禁用资金管理时使用的固定手数。
UseMoneyManagement 是否启用资金管理公式。
RiskPercent 资金管理公式使用的风险百分比。
TakeProfitPips 每笔网格订单的止盈距离。
StopLossPips 每笔网格订单的止损距离。
PriceDistancePips 当前价格到第一层挂单的点数间距。
GridStepPips 相邻网格层之间额外增加的点数。
GridOrders 每个方向创建的挂单数量。
PendingExpirationHours 挂单网格的有效时长(小时)。

备注

  • 策略本身不包含任何指标条件,可通过继承并重写 TryPlaceGrid 等方法添加自定义过滤逻辑。
  • 由于止盈与止损通过独立订单实现,若出现部分成交,实际执行方式可能与 MT4 的票据式保护价存在细微差异。
  • 运行前请确认交易所的 PriceStepDecimals 设置对应正确的点值,以避免在实盘中出现偏差。
namespace StockSharp.Samples.Strategies;

using System;

using Ecng.Common;

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

/// <summary>
/// Grid Template strategy: places trades at regular grid intervals.
/// Buys when price drops by grid step, sells when price rises by grid step.
/// </summary>
public class GridTemplateStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _gridStepPercent;

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

	public decimal GridStepPercent
	{
		get => _gridStepPercent.Value;
		set => _gridStepPercent.Value = value;
	}

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

		_gridStepPercent = Param(nameof(GridStepPercent), 3.0m)
			.SetGreaterThanZero()
			.SetDisplay("Grid Step %", "Price change percentage for grid level", "Grid");
	}

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

		var sma = new SimpleMovingAverage { Length = 10 };

		decimal? lastTradePrice = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, (candle, smaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

				if (!lastTradePrice.HasValue)
				{
					lastTradePrice = close;
					return;
				}

				var step = lastTradePrice.Value * GridStepPercent / 100m;

				if (close <= lastTradePrice.Value - step)
				{
					BuyMarket();
					lastTradePrice = close;
				}
				else if (close >= lastTradePrice.Value + step)
				{
					SellMarket();
					lastTradePrice = close;
				}
			})
			.Start();

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