在 GitHub 上查看

Reco RSI 网格策略

概述

该策略使用 StockSharp 的高级 API 重现 MetaTrader 平台上的 "Reco" 智能交易系统。算法首先根据相对强弱指数(RSI)开仓,然后在价格朝不利方向移动时逐步建立反向仓位形成网格。网格订单的距离和手数按几何级数增长,当累积盈亏达到预设阈值时一次性平仓。

交易逻辑

  • 初始信号:当 RSI 超过设定的超买或超卖区间时触发。RSI 高于卖出区则开空,低于买入区则开多。
  • 网格扩展:首单后监控价格相对于最后一笔交易的移动,当价格偏离达到计算出的距离时发送反向市价单。每一步的距离按 Distance Multiplier 递增,可由 Max DistanceMin Distance 限制。
  • 手数放大:每个新订单的数量等于初始 Lot 乘以 Lot Multiplier 的阶乘,允许设置最大和最小手数。
  • 退出规则:启用 Use Close Profit 时,当累计利润超过 Profit First Order 并按 Profit Multiplier 递增的目标值时全部平仓。启用 Use Close Lose 时,对亏损使用 Lose First OrderLose Multiplier 进行同样的判断。

参数

名称 说明
RsiPeriod RSI 指标周期。
RsiSellZone 触发卖出信号的 RSI 水平。
RsiBuyZone 触发买入信号的 RSI 水平。
StartDistance 与上一笔订单的初始距离(点)。
DistanceMultiplier 每增加一单距离的倍数。
MaxDistance 距离增长的上限,0 表示不限制。
MinDistance 距离增长的下限,0 表示不限制。
MaxOrders 同时打开的最大订单数,0 表示无限制。
Lot 初始下单手数。
LotMultiplier 手数放大的倍数。
MaxLot 每单的最大手数,0 表示不限制。
MinLot 每单的最小手数,0 表示不限制。
UseCloseProfit 是否按利润目标平仓。
ProfitFirstOrder 首单利润目标。
ProfitMultiplier 后续订单的利润倍数。
UseCloseLose 是否按亏损阈值平仓。
LoseFirstOrder 首单亏损阈值。
LoseMultiplier 后续订单的亏损倍数。
PointMultiplier 将品种最小报价单位转换为“点”的倍数。
CandleType 用于计算指标的蜡烛类型。

说明

  • 策略使用市价单,假设能够立即成交。
  • 采用净持仓模式,反向下单可能减少或反转当前仓位。
  • 代码使用制表符缩进,并包含英文注释以符合项目规范。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// RSI based grid strategy.
/// Opens the first trade when RSI reaches overbought/oversold zones
/// and then adds counter trades as price moves by configurable steps.
/// All positions are closed together on defined profit target.
/// </summary>
public class RecoRsiGridStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiSellZone;
	private readonly StrategyParam<decimal> _rsiBuyZone;
	private readonly StrategyParam<decimal> _gridStep;
	private readonly StrategyParam<int> _maxOrders;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _lastOrderPrice;
	private bool _lastOrderIsBuy;
	private int _ordersTotal;
	private decimal _entryPrice;

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public decimal RsiSellZone { get => _rsiSellZone.Value; set => _rsiSellZone.Value = value; }
	public decimal RsiBuyZone { get => _rsiBuyZone.Value; set => _rsiBuyZone.Value = value; }
	public decimal GridStep { get => _gridStep.Value; set => _gridStep.Value = value; }
	public int MaxOrders { get => _maxOrders.Value; set => _maxOrders.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public RecoRsiGridStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetDisplay("RSI Period", "RSI indicator period", "Signal");

		_rsiSellZone = Param(nameof(RsiSellZone), 70m)
			.SetDisplay("RSI Sell Zone", "RSI level to sell", "Signal");

		_rsiBuyZone = Param(nameof(RsiBuyZone), 30m)
			.SetDisplay("RSI Buy Zone", "RSI level to buy", "Signal");

		_gridStep = Param(nameof(GridStep), 200m)
			.SetDisplay("Grid Step", "Distance between grid orders", "Grid");

		_maxOrders = Param(nameof(MaxOrders), 5)
			.SetDisplay("Max Orders", "Maximum number of grid orders", "Grid");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_lastOrderPrice = 0;
		_lastOrderIsBuy = false;
		_ordersTotal = 0;
		_entryPrice = 0;
	}

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

		_lastOrderPrice = 0;
		_lastOrderIsBuy = false;
		_ordersTotal = 0;
		_entryPrice = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var price = candle.ClosePrice;

		// Check if we should close all on profit
		if (_ordersTotal > 0 && _entryPrice > 0)
		{
			var unrealized = Position > 0
				? price - _entryPrice
				: _entryPrice - price;

			if (unrealized > GridStep * 0.5m)
			{
				// Close all
				if (Position > 0)
					SellMarket();
				else if (Position < 0)
					BuyMarket();

				_ordersTotal = 0;
				_lastOrderPrice = 0;
				_entryPrice = 0;
				return;
			}
		}

		var signal = GetSignal(price, rsiValue);

		if (signal > 0 && Position <= 0)
		{
			BuyMarket();
			_lastOrderIsBuy = true;
			_lastOrderPrice = price;
			_entryPrice = price;
			_ordersTotal++;
		}
		else if (signal < 0 && Position >= 0)
		{
			SellMarket();
			_lastOrderIsBuy = false;
			_lastOrderPrice = price;
			_entryPrice = price;
			_ordersTotal++;
		}
	}

	private int GetSignal(decimal price, decimal rsiValue)
	{
		if (_ordersTotal == 0)
		{
			if (rsiValue >= RsiSellZone)
				return -1;
			if (rsiValue <= RsiBuyZone)
				return 1;
			return 0;
		}

		if (MaxOrders > 0 && _ordersTotal >= MaxOrders)
		{
			// Reset grid when max orders reached
			_ordersTotal = 0;
			_lastOrderPrice = 0;
			return 0;
		}

		// Add counter-trend orders at grid steps
		if (_lastOrderIsBuy && price <= _lastOrderPrice - GridStep)
			return 1;
		else if (!_lastOrderIsBuy && price >= _lastOrderPrice + GridStep)
			return -1;

		return 0;
	}
}