FiveMinuteRsiCci 策略
FiveMinuteRsiCciStrategy 是 MetaTrader 4 专家顾问 5Mins Rsi Cci EA.mq4 的 StockSharp 版本。原脚本在 5 分钟周期上运行,通过 RSI 阈值突破、开盘价的平滑/指数均线过滤以及快慢 CCI 的正负性来决定方向。C# 移植版沿用相同逻辑,同时利用 StockSharp 的高级 API 完成数据订阅、指标绑定与风控管理。
交易逻辑
- 订阅配置的 K 线类型(默认 5 分钟),实时更新五个指标:RSI、开盘价的平滑移动平均、开盘价的 EMA,以及基于典型价的快/慢 CCI。
- 仅在当前仓位为零且买卖差价低于
MaxSpreadPoints(会换算为价格单位)时评估已收盘的 K 线。 - 多头信号需要满足:
- 平滑均线在 EMA 之上;
- RSI 在上一根与当前 K 线之间向上穿越
BullishRsiLevel; - 快慢 CCI 均大于零。
- 空头信号为上述条件的反向组合(平滑均线在 EMA 之下、RSI 向下穿越
BearishRsiLevel、快慢 CCI 均小于零)。 - 下单手数复刻 EA 的动态头寸算法:
LotCoefficient × sqrt(Equity / EquityDivisor),并根据交易品种的VolumeStep、VolumeMin、VolumeMax进行取整与约束。 - 通过
StartProtection自动附加止损、止盈与跟踪止损距离,所有距离都会从 MetaTrader 点值换算为绝对价格。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
CandleType |
TimeSpan.FromMinutes(5).TimeFrame() |
用于指标与信号评估的 K 线周期。 |
RsiPeriod |
14 |
RSI 计算所使用的周期数。 |
FastSmmaPeriod |
2 |
作用于开盘价的平滑移动平均周期。 |
SlowEmaPeriod |
6 |
作用于开盘价的 EMA 周期。 |
FastCciPeriod |
34 |
基于典型价 (H+L+C)/3 的快 CCI 周期。 |
SlowCciPeriod |
175 |
基于典型价的慢 CCI 周期。 |
BullishRsiLevel |
55 |
RSI 向上突破该值后才允许做多。 |
BearishRsiLevel |
45 |
RSI 向下跌破该值后才允许做空。 |
StopLossPoints |
60 |
止损距离(MetaTrader 点),0 表示关闭。 |
TakeProfitPoints |
0 |
止盈距离(MetaTrader 点),默认关闭以保持原始脚本行为。 |
TrailingStopPoints |
20 |
跟踪止损距离(MetaTrader 点),0 表示关闭。 |
LotCoefficient |
0.01 |
动态手数公式的基础系数。 |
EquityDivisor |
10 |
动态手数公式中平方根的分母 (sqrt(Equity / EquityDivisor))。 |
MaxSpreadPoints |
18 |
允许的最大点差(MetaTrader 点),超出时暂停开仓。 |
说明
- 点差过滤依赖一级行情,若尚未获得最优买/卖价则等待,不会立即开仓。
- 点值换算会自动结合
PriceStep与报价精度;对于 5/3 位小数的品种会额外乘以 10,以匹配 MetaTrader 的Point定义。 - 止损、止盈与跟踪止损全部由 StockSharp 内置保护模块处理,并使用市价方式更新,等效于原 EA 中的
OrderModify行为。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Finished-bar RSI threshold crossings confirmed by open-price SMMA/EMA and two typical-price CCIs.
/// </summary>
public class FiveMinuteRsiCciStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _fastSmmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _fastCciPeriod;
private readonly StrategyParam<int> _slowCciPeriod;
private readonly StrategyParam<decimal> _bullishRsiLevel;
private readonly StrategyParam<decimal> _bearishRsiLevel;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _lotCoefficient;
private readonly StrategyParam<decimal> _equityDivisor;
private readonly StrategyParam<decimal> _maxSpreadPoints;
private RelativeStrengthIndex _rsi;
private SmoothedMovingAverage _smma;
private ExponentialMovingAverage _ema;
private CommodityChannelIndex _fastCci;
private CommodityChannelIndex _slowCci;
private decimal? _previousRsi;
private decimal? _bestBid;
private decimal? _bestAsk;
private decimal? _entryPrice;
private decimal? _trailingStop;
private bool _exitRequested;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int FastSmmaPeriod { get => _fastSmmaPeriod.Value; set => _fastSmmaPeriod.Value = value; }
public int SlowEmaPeriod { get => _slowEmaPeriod.Value; set => _slowEmaPeriod.Value = value; }
public int FastCciPeriod { get => _fastCciPeriod.Value; set => _fastCciPeriod.Value = value; }
public int SlowCciPeriod { get => _slowCciPeriod.Value; set => _slowCciPeriod.Value = value; }
public decimal BullishRsiLevel { get => _bullishRsiLevel.Value; set => _bullishRsiLevel.Value = value; }
public decimal BearishRsiLevel { get => _bearishRsiLevel.Value; set => _bearishRsiLevel.Value = value; }
public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public decimal TrailingStopPoints { get => _trailingStopPoints.Value; set => _trailingStopPoints.Value = value; }
public decimal LotCoefficient { get => _lotCoefficient.Value; set => _lotCoefficient.Value = value; }
public decimal EquityDivisor { get => _equityDivisor.Value; set => _equityDivisor.Value = value; }
public decimal MaxSpreadPoints { get => _maxSpreadPoints.Value; set => _maxSpreadPoints.Value = value; }
public FiveMinuteRsiCciStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame());
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetGreaterThanZero();
_fastSmmaPeriod = Param(nameof(FastSmmaPeriod), 2).SetGreaterThanZero();
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 6).SetGreaterThanZero();
_fastCciPeriod = Param(nameof(FastCciPeriod), 34).SetGreaterThanZero();
_slowCciPeriod = Param(nameof(SlowCciPeriod), 175).SetGreaterThanZero();
_bullishRsiLevel = Param(nameof(BullishRsiLevel), 55m);
_bearishRsiLevel = Param(nameof(BearishRsiLevel), 45m);
_stopLossPoints = Param(nameof(StopLossPoints), 60m).SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m).SetNotNegative();
_trailingStopPoints = Param(nameof(TrailingStopPoints), 20m).SetNotNegative();
_lotCoefficient = Param(nameof(LotCoefficient), 0.01m).SetGreaterThanZero();
_equityDivisor = Param(nameof(EquityDivisor), 10m).SetGreaterThanZero();
_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 18m).SetNotNegative();
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetRuntime();
}
private void ResetRuntime()
{
_previousRsi = null;
_bestBid = null;
_bestAsk = null;
_entryPrice = null;
_trailingStop = null;
_exitRequested = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetRuntime();
_rsi = new() { Length = RsiPeriod };
_smma = new() { Length = FastSmmaPeriod };
_ema = new() { Length = SlowEmaPeriod };
_fastCci = new() { Length = FastCciPeriod };
_slowCci = new() { Length = SlowCciPeriod };
var point = GetPoint();
var takeProfit = TakeProfitPoints > 0m ? new Unit(TakeProfitPoints * point, UnitTypes.Absolute) : new Unit();
var stopLoss = StopLossPoints > 0m ? new Unit(StopLossPoints * point, UnitTypes.Absolute) : new Unit();
StartProtection(takeProfit, stopLoss, useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ProcessLevel1).Start();
}
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
}
private decimal GetPoint()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
throw new InvalidOperationException("Positive Security.PriceStep is required.");
return Security.Decimals is 3 or 5 ? step * 10m : step;
}
private void ProcessLevel1(Level1ChangeMessage message)
{
if (message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid && bid > 0m)
_bestBid = bid;
if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
_bestAsk = ask;
if (Position == 0m || _entryPrice is null || TrailingStopPoints <= 0m || _exitRequested)
return;
var gap = TrailingStopPoints * GetPoint();
var price = Position > 0m ? _bestBid : _bestAsk;
if (price is null)
return;
if (Position > 0m)
{
if (price.Value >= _entryPrice.Value + gap)
{
var candidate = price.Value - gap;
if (_trailingStop is null || candidate > _trailingStop.Value)
_trailingStop = candidate;
}
if (_trailingStop is decimal stop && price.Value <= stop)
RequestExit();
}
else
{
if (price.Value <= _entryPrice.Value - gap)
{
var candidate = price.Value + gap;
if (_trailingStop is null || candidate < _trailingStop.Value)
_trailingStop = candidate;
}
if (_trailingStop is decimal stop && price.Value >= stop)
RequestExit();
}
}
private void RequestExit()
{
if (_exitRequested || Position == 0m)
return;
_exitRequested = true;
ClosePosition();
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position == 0m)
{
_entryPrice = null;
_trailingStop = null;
_exitRequested = false;
}
else if (_entryPrice is null)
{
_entryPrice = trade.Trade.Price;
}
}
private bool IsSpreadAllowed()
=> _bestBid is decimal bid && _bestAsk is decimal ask
&& ask >= bid && ask - bid < MaxSpreadPoints * GetPoint();
private decimal GetVolume()
{
var equity = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
if (equity <= 0m || EquityDivisor <= 0m)
return 0m;
var raw = LotCoefficient * (decimal)Math.Sqrt((double)(equity / EquityDivisor));
var minimum = Security?.MinVolume ?? 0m;
var maximum = Security?.MaxVolume ?? decimal.MaxValue;
var step = Security?.VolumeStep ?? 0m;
if (maximum < minimum)
return 0m;
if (step > 0m)
{
var first = Math.Ceiling(minimum / step) * step;
var last = Math.Floor(maximum / step) * step;
if (first > last)
return 0m;
return Math.Clamp(Math.Floor(raw / step) * step, first, last);
}
return Math.Clamp(raw, minimum, maximum);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var rsi = _rsi.Process(candle.ClosePrice, candle.OpenTime, true);
var smma = _smma.Process(candle.OpenPrice, candle.OpenTime, true);
var ema = _ema.Process(candle.OpenPrice, candle.OpenTime, true);
var fastCci = _fastCci.Process(candle);
var slowCci = _slowCci.Process(candle);
if (!rsi.IsFormed || !smma.IsFormed || !ema.IsFormed || !fastCci.IsFormed || !slowCci.IsFormed)
return;
var currentRsi = rsi.ToDecimal();
if (_previousRsi is decimal previous && Position == 0m && !_exitRequested && IsSpreadAllowed())
{
var volume = GetVolume();
if (volume > 0m)
{
if (previous <= BullishRsiLevel && currentRsi > BullishRsiLevel &&
smma.ToDecimal() > ema.ToDecimal() &&
fastCci.ToDecimal() > 0m && slowCci.ToDecimal() > 0m)
BuyMarket(volume);
else if (previous >= BearishRsiLevel && currentRsi < BearishRsiLevel &&
smma.ToDecimal() < ema.ToDecimal() &&
fastCci.ToDecimal() < 0m && slowCci.ToDecimal() < 0m)
SellMarket(volume);
}
}
_previousRsi = currentRsi;
}
}
import math
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Level1Fields, Unit, UnitTypes
from StockSharp.Algo.Indicators import (
RelativeStrengthIndex, CommodityChannelIndex, SmoothedMovingAverage,
ExponentialMovingAverage, CandleIndicatorValue
)
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import process_float
class five_minute_rsi_cci_strategy(Strategy):
def __init__(self):
super(five_minute_rsi_cci_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._rsi_period = self.Param("RsiPeriod", 14)
self._fast_smma_period = self.Param("FastSmmaPeriod", 2)
self._slow_ema_period = self.Param("SlowEmaPeriod", 6)
self._fast_cci_period = self.Param("FastCciPeriod", 34)
self._slow_cci_period = self.Param("SlowCciPeriod", 175)
self._bullish_rsi_level = self.Param("BullishRsiLevel", 55.0)
self._bearish_rsi_level = self.Param("BearishRsiLevel", 45.0)
self._stop_loss_points = self.Param("StopLossPoints", 60.0)
self._take_profit_points = self.Param("TakeProfitPoints", 0.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 20.0)
self._lot_coefficient = self.Param("LotCoefficient", 0.01)
self._equity_divisor = self.Param("EquityDivisor", 10.0)
self._max_spread_points = self.Param("MaxSpreadPoints", 18.0)
self._reset_runtime()
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def FastSmmaPeriod(self):
return self._fast_smma_period.Value
@FastSmmaPeriod.setter
def FastSmmaPeriod(self, value):
self._fast_smma_period.Value = value
@property
def SlowEmaPeriod(self):
return self._slow_ema_period.Value
@SlowEmaPeriod.setter
def SlowEmaPeriod(self, value):
self._slow_ema_period.Value = value
@property
def FastCciPeriod(self):
return self._fast_cci_period.Value
@FastCciPeriod.setter
def FastCciPeriod(self, value):
self._fast_cci_period.Value = value
@property
def SlowCciPeriod(self):
return self._slow_cci_period.Value
@SlowCciPeriod.setter
def SlowCciPeriod(self, value):
self._slow_cci_period.Value = value
@property
def BullishRsiLevel(self):
return self._bullish_rsi_level.Value
@BullishRsiLevel.setter
def BullishRsiLevel(self, value):
self._bullish_rsi_level.Value = value
@property
def BearishRsiLevel(self):
return self._bearish_rsi_level.Value
@BearishRsiLevel.setter
def BearishRsiLevel(self, value):
self._bearish_rsi_level.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@TrailingStopPoints.setter
def TrailingStopPoints(self, value):
self._trailing_stop_points.Value = value
@property
def LotCoefficient(self):
return self._lot_coefficient.Value
@LotCoefficient.setter
def LotCoefficient(self, value):
self._lot_coefficient.Value = value
@property
def EquityDivisor(self):
return self._equity_divisor.Value
@EquityDivisor.setter
def EquityDivisor(self, value):
self._equity_divisor.Value = value
@property
def MaxSpreadPoints(self):
return self._max_spread_points.Value
@MaxSpreadPoints.setter
def MaxSpreadPoints(self, value):
self._max_spread_points.Value = value
def _reset_runtime(self):
self._previous_rsi = None
self._best_bid = None
self._best_ask = None
self._entry_price = None
self._trailing_stop = None
self._exit_requested = False
def OnReseted(self):
super(five_minute_rsi_cci_strategy, self).OnReseted()
self._reset_runtime()
def _point(self):
if self.Security is None or self.Security.PriceStep is None:
raise ValueError("Positive Security.PriceStep is required.")
step = float(self.Security.PriceStep)
if step <= 0:
raise ValueError("Positive Security.PriceStep is required.")
return step * 10.0 if self.Security.Decimals in (3, 5) else step
def OnStarted2(self, time):
super(five_minute_rsi_cci_strategy, self).OnStarted2(time)
self._reset_runtime()
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
self._smma = SmoothedMovingAverage()
self._smma.Length = self.FastSmmaPeriod
self._ema = ExponentialMovingAverage()
self._ema.Length = self.SlowEmaPeriod
self._fast_cci = CommodityChannelIndex()
self._fast_cci.Length = self.FastCciPeriod
self._slow_cci = CommodityChannelIndex()
self._slow_cci.Length = self.SlowCciPeriod
point = self._point()
stop = Unit(Decimal(self.StopLossPoints * point), UnitTypes.Absolute) if self.StopLossPoints > 0 else Unit()
take = Unit(Decimal(self.TakeProfitPoints * point), UnitTypes.Absolute) if self.TakeProfitPoints > 0 else Unit()
self.StartProtection(take, stop, useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._process_level1).Start()
self.SubscribeCandles(self.CandleType).Bind(self._process_candle).Start()
def _process_level1(self, message):
bid = message.Changes[Level1Fields.BestBidPrice] if message.Changes.ContainsKey(Level1Fields.BestBidPrice) else None
ask = message.Changes[Level1Fields.BestAskPrice] if message.Changes.ContainsKey(Level1Fields.BestAskPrice) else None
if bid is not None and bid > 0:
self._best_bid = float(bid)
if ask is not None and ask > 0:
self._best_ask = float(ask)
if self.Position == 0 or self._entry_price is None or self.TrailingStopPoints <= 0 or self._exit_requested:
return
gap = float(self.TrailingStopPoints) * self._point()
price = self._best_bid if self.Position > 0 else self._best_ask
if price is None:
return
if self.Position > 0:
if price >= self._entry_price + gap:
candidate = price - gap
if self._trailing_stop is None or candidate > self._trailing_stop:
self._trailing_stop = candidate
if self._trailing_stop is not None and price <= self._trailing_stop:
self._request_exit()
else:
if price <= self._entry_price - gap:
candidate = price + gap
if self._trailing_stop is None or candidate < self._trailing_stop:
self._trailing_stop = candidate
if self._trailing_stop is not None and price >= self._trailing_stop:
self._request_exit()
def _request_exit(self):
if self._exit_requested or self.Position == 0:
return
self._exit_requested = True
self.ClosePosition()
def OnOwnTradeReceived(self, trade):
super(five_minute_rsi_cci_strategy, self).OnOwnTradeReceived(trade)
if self.Position == 0:
self._entry_price = None
self._trailing_stop = None
self._exit_requested = False
elif self._entry_price is None:
self._entry_price = float(trade.Trade.TradePrice)
def _spread_allowed(self):
return (self._best_bid is not None and self._best_ask is not None
and self._best_ask >= self._best_bid
and self._best_ask - self._best_bid < float(self.MaxSpreadPoints) * self._point())
def _volume(self):
portfolio = self.Portfolio
if portfolio is None:
return None
equity = portfolio.CurrentValue if portfolio.CurrentValue is not None else portfolio.BeginValue
divisor = Decimal(float(self.EquityDivisor))
if equity is None or equity <= 0 or divisor <= 0:
return None
raw = Decimal.Multiply(Decimal(float(self.LotCoefficient)),
Decimal(math.sqrt(float(Decimal.Divide(equity, divisor)))))
minimum = self.Security.MinVolume if self.Security.MinVolume is not None else Decimal(0)
maximum = self.Security.MaxVolume if self.Security.MaxVolume is not None else Decimal.MaxValue
if minimum > maximum:
return None
step = self.Security.VolumeStep
if step is not None and step > 0:
first = Decimal.Ceiling(Decimal.Divide(minimum, step))
last = Decimal.Floor(Decimal.Divide(maximum, step))
if first > last:
return None
units = max(first, min(last, Decimal.Floor(Decimal.Divide(raw, step))))
raw = Decimal.Multiply(units, step)
else:
raw = max(minimum, min(maximum, raw))
return raw if raw > 0 else None
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
rsi = process_float(self._rsi, candle.ClosePrice, candle.OpenTime, True)
smma = process_float(self._smma, candle.OpenPrice, candle.OpenTime, True)
ema = process_float(self._ema, candle.OpenPrice, candle.OpenTime, True)
fast_in = CandleIndicatorValue(self._fast_cci, candle)
fast_in.IsFinal = True
slow_in = CandleIndicatorValue(self._slow_cci, candle)
slow_in.IsFinal = True
fast_cci = self._fast_cci.Process(fast_in)
slow_cci = self._slow_cci.Process(slow_in)
if not all((rsi.IsFormed, smma.IsFormed, ema.IsFormed, fast_cci.IsFormed, slow_cci.IsFormed)):
return
current = float(rsi)
previous = self._previous_rsi
if previous is not None and self.Position == 0 and not self._exit_requested and self._spread_allowed():
volume = self._volume()
if volume is not None:
if (previous <= float(self.BullishRsiLevel) and current > float(self.BullishRsiLevel)
and float(smma) > float(ema)
and float(fast_cci) > 0 and float(slow_cci) > 0):
self.BuyMarket(volume)
elif (previous >= float(self.BearishRsiLevel) and current < float(self.BearishRsiLevel)
and float(smma) < float(ema)
and float(fast_cci) < 0 and float(slow_cci) < 0):
self.SellMarket(volume)
self._previous_rsi = current
def CreateClone(self):
return five_minute_rsi_cci_strategy()