在 GitHub 上查看

Return Strategy

该策略基于经典的 “Return Strategy” EA 改写。每天在配置的开始小时检查当前是否存在活动挂单,如果没有,则在当前价格上下对称放置成对的买入限价单和卖出限价单。网格的初始距离和逐级步长以点数定义,单笔订单体积既可以固定,也可以根据风险百分比自动计算。订单成交后,策略按固定止损与追踪止损组合管理仓位,并在达到总盈利目标、到达日内截止时间或每周五强制平仓。

原策略面向净头寸账户,旨在抓住特定时间后的回归行情。此次移植保留了这一结构,并利用 StockSharp 的高级 API 完成订单、风险和资金管理。

运行逻辑

  • 建仓时段:在 StartHour,若没有活动挂单,则按照 PendingOrderCount 的数量,在价格下方以 DistancePips 为起点、StepPips 为间隔放置买入限价单,同时在价格上方放置相同数量的卖出限价单。
  • 风险控制:每张挂单可以使用固定的 OrderVolume,也可以在 OrderVolume 为零时,根据 RiskPercent 按资金百分比分配仓位。风险模式下,总风险按照整个网格计算,并均分到每个挂单。
  • 止损管理:成交后立即按照 StopLossPips 设置初始止损。当 TrailingStopPips 大于零时,价格超过触发阈值后按 TrailingStepPips 的步长移动追踪止损。
  • 盈利与退出:实时跟踪净持仓盈利(以点数计算)。当盈利达到 TotalProfitPips,或时间达到 EndHour,或当前是周五,策略都会启动完全平仓并撤销所有挂单。
  • 到期处理:若 ExpirationHours 大于零,挂单在到期时会自动撤销。被撤单或过期的订单会从内部列表中移除,以便下一交易日重新布网。

参数说明

参数 含义
StopLossPips 成交后初始止损的距离(点)。
StartHour 每天布网的小时数(0–23)。
EndHour 每天强制退出的小时数(0–23)。
TotalProfitPips 净浮动盈利达到该点数时触发平仓。
TrailingStopPips 追踪止损距离,设为 0 则不启用。
TrailingStepPips 追踪止损每次移动所需的额外价格变动,启用追踪时必须大于 0。
DistancePips 第一档挂单距离当前价的偏移量。
StepPips 相邻挂单之间的额外间距。
PendingOrderCount 同时放置的买入限价单和卖出限价单数量。
ExpirationHours 挂单的有效期(小时),0 表示永久有效。
OrderVolume 单笔挂单的固定手数,为 0 时改用风险百分比。
RiskPercent OrderVolume 为 0 时,按账户权益的该百分比分配整个网格的风险。
CandleType 用于计时和判定的K线类型。

其他说明

  • 点值换算沿用 MetaTrader 的做法:对于 3 位和 5 位小数报价,会将最小步长乘以 10 作为 1 个点。
  • 使用 RiskPercent 时,风险按照整套挂单计算,并平均分配到每个订单。
  • 代码包含与原始 EA 相同的参数校验:时间必须位于 0–23 之间,启用追踪止损时需要正的步长,且不能同时设置固定手数和风险百分比。
  • 代码中的注释全部为英文,以符合仓库规范。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Mean reversion strategy using Bollinger Bands.
/// Buys when price drops below lower band and sells when price rises above upper band.
/// </summary>
public class ReturnStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<decimal> _width;

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

	public int Period
	{
		get => _period.Value;
		set => _period.Value = value;
	}

	public decimal Width
	{
		get => _width.Value;
		set => _width.Value = value;
	}

	public ReturnStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_period = Param(nameof(Period), 20)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Bollinger Bands period", "Indicators");

		_width = Param(nameof(Width), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Width", "Bollinger Bands width", "Indicators");
	}

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

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

		var ma = new SimpleMovingAverage { Length = Period };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var bandWidth = Width / 100m;
		var upper = middle * (1m + bandWidth);
		var lower = middle * (1m - bandWidth);
		var close = candle.ClosePrice;

		// Buy when price drops below lower band (mean reversion)
		if (close < lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Sell when price rises above upper band
		else if (close > upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
		// Exit long at middle band
		else if (Position > 0 && close >= middle)
		{
			SellMarket();
		}
		// Exit short at middle band
		else if (Position < 0 && close <= middle)
		{
			BuyMarket();
		}
	}
}