在 GitHub 上查看

Limits RSI Momentum Bot 策略

概要

该策略基于相对强弱指数(RSI)和动量(Momentum)指标,通过在K线开盘价附近放置限价单来实现折价买入和溢价卖出。

交易规则

  • 仅在指定的时间段内运行。
  • 每根完成的K线都会计算 RSI 和 Momentum 值。
  • 当 RSI 和 Momentum 同时低于买入阈值时,在开盘价下方放置买入限价单。
  • 当 RSI 和 Momentum 同时高于卖出阈值时,在开盘价上方放置卖出限价单。
  • 开仓后会取消相反方向的挂单。
  • 使用 StartProtection 自动管理止损和止盈。

参数

  • Volume – 下单数量。
  • LimitOrderDistance – 距离开盘价的价格步长,用于放置挂单。
  • TakeProfit – 盈利目标(价格步长)。
  • StopLoss – 亏损限制(价格步长)。
  • RsiPeriod – RSI 计算周期。
  • RsiBuyRestrict / RsiSellRestrict – 允许做多或做空的 RSI 阈值。
  • MomentumPeriod – Momentum 计算周期。
  • MomentumBuyRestrict / MomentumSellRestrict – 允许做多或做空的 Momentum 阈值。
  • StartTime / EndTime – 交易时间区间。
  • CandleType – 用于计算指标的K线周期。

备注

该策略由 MQL4 脚本 "The Limits Bot with RSI & Momentum" 转换,并使用 StockSharp 的高级 API。

using System;
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>
/// Strategy that places limit orders based on RSI and Momentum values.
/// A buy limit is set below the candle open when both indicators signal oversold.
/// A sell limit is set above the open when indicators show overbought conditions.
/// Opposite pending order is cancelled once a position is opened.
/// Stop-loss and take-profit are managed via StartProtection.
/// </summary>
public class LimitsRsiMomentumBotStrategy : Strategy
{
	private readonly StrategyParam<int> _limitOrderDistance;
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiBuyRestrict;
	private readonly StrategyParam<decimal> _rsiSellRestrict;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<decimal> _momentumBuyRestrict;
	private readonly StrategyParam<decimal> _momentumSellRestrict;
	private readonly StrategyParam<TimeSpan> _startTime;
	private readonly StrategyParam<TimeSpan> _endTime;
	private readonly StrategyParam<DataType> _candleType;

	private Order _buyOrder;
	private Order _sellOrder;


	/// <summary>
	/// Distance from candle open to place limit orders in price steps.
	/// </summary>
	public int LimitOrderDistance
	{
		get => _limitOrderDistance.Value;
		set => _limitOrderDistance.Value = value;
	}

	/// <summary>
	/// Take profit in price steps.
	/// </summary>
	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Stop loss in price steps.
	/// </summary>
	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// RSI period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI threshold for long entries.
	/// </summary>
	public decimal RsiBuyRestrict
	{
		get => _rsiBuyRestrict.Value;
		set => _rsiBuyRestrict.Value = value;
	}

	/// <summary>
	/// RSI threshold for short entries.
	/// </summary>
	public decimal RsiSellRestrict
	{
		get => _rsiSellRestrict.Value;
		set => _rsiSellRestrict.Value = value;
	}

	/// <summary>
	/// Momentum period.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// Momentum threshold for long entries.
	/// </summary>
	public decimal MomentumBuyRestrict
	{
		get => _momentumBuyRestrict.Value;
		set => _momentumBuyRestrict.Value = value;
	}

	/// <summary>
	/// Momentum threshold for short entries.
	/// </summary>
	public decimal MomentumSellRestrict
	{
		get => _momentumSellRestrict.Value;
		set => _momentumSellRestrict.Value = value;
	}

	/// <summary>
	/// Trading start time.
	/// </summary>
	public TimeSpan StartTime
	{
		get => _startTime.Value;
		set => _startTime.Value = value;
	}

	/// <summary>
	/// Trading end time.
	/// </summary>
	public TimeSpan EndTime
	{
		get => _endTime.Value;
		set => _endTime.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="LimitsRsiMomentumBotStrategy"/>.
	/// </summary>
	public LimitsRsiMomentumBotStrategy()
	{

		_limitOrderDistance = Param(nameof(LimitOrderDistance), 5)
			.SetGreaterThanZero()
			.SetDisplay("Limit Order Distance", "Distance from candle open in price steps", "Trading")
			
			.SetOptimize(3, 10, 1);

		_takeProfit = Param(nameof(TakeProfit), 35)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Profit target in price steps", "Protection")
			
			.SetOptimize(20, 60, 5);

		_stopLoss = Param(nameof(StopLoss), 8)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Loss limit in price steps", "Protection")
			
			.SetOptimize(5, 20, 1);

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

		_rsiBuyRestrict = Param(nameof(RsiBuyRestrict), 30m)
			.SetDisplay("RSI Buy Threshold", "Max RSI value to allow buys", "Indicators");

		_rsiSellRestrict = Param(nameof(RsiSellRestrict), 70m)
			.SetDisplay("RSI Sell Threshold", "Min RSI value to allow sells", "Indicators");

		_momentumPeriod = Param(nameof(MomentumPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Period for Momentum calculation", "Indicators");

		_momentumBuyRestrict = Param(nameof(MomentumBuyRestrict), 1m)
			.SetDisplay("Momentum Buy Threshold", "Max Momentum value to allow buys", "Indicators");

		_momentumSellRestrict = Param(nameof(MomentumSellRestrict), 1m)
			.SetDisplay("Momentum Sell Threshold", "Min Momentum value to allow sells", "Indicators");

		_startTime = Param(nameof(StartTime), TimeSpan.Zero)
			.SetDisplay("Start Time", "Trading start time", "Trading");

		_endTime = Param(nameof(EndTime), new TimeSpan(23, 59, 0))
			.SetDisplay("End Time", "Trading end time", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for strategy", "Trading");
	}

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

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

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var momentum = new Momentum { Length = MomentumPeriod };

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

		var step = Security.PriceStep ?? 1m;
		StartProtection(
			takeProfit: new Unit(TakeProfit * step, UnitTypes.Absolute),
			stopLoss: new Unit(StopLoss * step, UnitTypes.Absolute));

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_buyOrder = null;
		_sellOrder = null;
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (!IsTradingTime(candle.OpenTime))
			return;

		var step = Security.PriceStep ?? 1m;
		var buySignal = rsiValue < RsiBuyRestrict && momentumValue < MomentumBuyRestrict && Position <= 0;
		var sellSignal = rsiValue > RsiSellRestrict && momentumValue > MomentumSellRestrict && Position >= 0;

		if (buySignal)
		{
			if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
			{
				CancelOrder(_sellOrder);
				_sellOrder = null;
			}

			if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
				return;

			var price = candle.OpenPrice - LimitOrderDistance * step;
			_buyOrder = BuyLimit(price);
		}
		else if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
		{
			CancelOrder(_buyOrder);
			_buyOrder = null;
		}

		if (sellSignal)
		{
			if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
			{
				CancelOrder(_buyOrder);
				_buyOrder = null;
			}

			if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
				return;

			var price = candle.OpenPrice + LimitOrderDistance * step;
			_sellOrder = SellLimit(price);
		}
		else if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
		{
			CancelOrder(_sellOrder);
			_sellOrder = null;
		}
	}

	private bool IsTradingTime(DateTimeOffset time)
	{
		var t = time.TimeOfDay;
		return t >= StartTime && t <= EndTime;
	}

	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);

		if (Position > 0)
		{
			if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
			{
				CancelOrder(_sellOrder);
				_sellOrder = null;
			}
		}
		else if (Position < 0)
		{
			if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
			{
				CancelOrder(_buyOrder);
				_buyOrder = null;
			}
		}
		else
		{
			_buyOrder = null;
			_sellOrder = null;
		}
	}
}