Reco RSI 网格策略
概述
该策略使用 StockSharp 的高级 API 重现 MetaTrader 平台上的 "Reco" 智能交易系统。算法首先根据相对强弱指数(RSI)开仓,然后在价格朝不利方向移动时逐步建立反向仓位形成网格。网格订单的距离和手数按几何级数增长,当累积盈亏达到预设阈值时一次性平仓。
交易逻辑
- 初始信号:当 RSI 超过设定的超买或超卖区间时触发。RSI 高于卖出区则开空,低于买入区则开多。
- 网格扩展:首单后监控价格相对于最后一笔交易的移动,当价格偏离达到计算出的距离时发送反向市价单。每一步的距离按 Distance Multiplier 递增,可由 Max Distance 和 Min Distance 限制。
- 手数放大:每个新订单的数量等于初始 Lot 乘以 Lot Multiplier 的阶乘,允许设置最大和最小手数。
- 退出规则:启用 Use Close Profit 时,当累计利润超过 Profit First Order 并按 Profit Multiplier 递增的目标值时全部平仓。启用 Use Close Lose 时,对亏损使用 Lose First Order 和 Lose 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class reco_rsi_grid_strategy(Strategy):
def __init__(self):
super(reco_rsi_grid_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI indicator period", "Signal")
self._rsi_sell_zone = self.Param("RsiSellZone", 70.0) \
.SetDisplay("RSI Sell Zone", "RSI level to sell", "Signal")
self._rsi_buy_zone = self.Param("RsiBuyZone", 30.0) \
.SetDisplay("RSI Buy Zone", "RSI level to buy", "Signal")
self._grid_step = self.Param("GridStep", 200.0) \
.SetDisplay("Grid Step", "Distance between grid orders", "Grid")
self._max_orders = self.Param("MaxOrders", 5) \
.SetDisplay("Max Orders", "Maximum number of grid orders", "Grid")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._last_order_price = 0.0
self._last_order_is_buy = False
self._orders_total = 0
self._entry_price = 0.0
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def rsi_sell_zone(self):
return self._rsi_sell_zone.Value
@property
def rsi_buy_zone(self):
return self._rsi_buy_zone.Value
@property
def grid_step(self):
return self._grid_step.Value
@property
def max_orders(self):
return self._max_orders.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(reco_rsi_grid_strategy, self).OnReseted()
self._last_order_price = 0.0
self._last_order_is_buy = False
self._orders_total = 0
self._entry_price = 0.0
def OnStarted2(self, time):
super(reco_rsi_grid_strategy, self).OnStarted2(time)
self._last_order_price = 0.0
self._last_order_is_buy = False
self._orders_total = 0
self._entry_price = 0.0
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def _get_signal(self, price, rsi_value):
grid_step = float(self.grid_step)
max_ord = int(self.max_orders)
if self._orders_total == 0:
if rsi_value >= float(self.rsi_sell_zone):
return -1
if rsi_value <= float(self.rsi_buy_zone):
return 1
return 0
if max_ord > 0 and self._orders_total >= max_ord:
self._orders_total = 0
self._last_order_price = 0.0
return 0
if self._last_order_is_buy and price <= self._last_order_price - grid_step:
return 1
elif not self._last_order_is_buy and price >= self._last_order_price + grid_step:
return -1
return 0
def process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rsi_value = float(rsi_value)
price = float(candle.ClosePrice)
grid_step = float(self.grid_step)
if self._orders_total > 0 and self._entry_price > 0:
if self.Position > 0:
unrealized = price - self._entry_price
else:
unrealized = self._entry_price - price
if unrealized > grid_step * 0.5:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._orders_total = 0
self._last_order_price = 0.0
self._entry_price = 0.0
return
signal = self._get_signal(price, rsi_value)
if signal > 0 and self.Position <= 0:
self.BuyMarket()
self._last_order_is_buy = True
self._last_order_price = price
self._entry_price = price
self._orders_total += 1
elif signal < 0 and self.Position >= 0:
self.SellMarket()
self._last_order_is_buy = False
self._last_order_price = price
self._entry_price = price
self._orders_total += 1
def CreateClone(self):
return reco_rsi_grid_strategy()