在 GitHub 上查看
Divergence + EMA + RSI Close Buy Only
概述
该策略将 MetaTrader 的“Divergence + ema + rsi close buy only”专家顾问移植到 StockSharp 的高层 API。它以 5 分钟 K 线为核心进行交易,并结合 1 小时与 日线数据确认趋势方向和超卖状态。系统仅做多:入场需要 MACD 柱状图出现看涨背离,同时满足小时级随机指标在低位金叉与日线 EMA 上行的条件。离场由 RSI 超买阈值以及 StartProtection 提供的止损/止盈协同完成。
交易逻辑
日线 EMA 趋势过滤
- 日线 EMA(9) 必须高于 EMA(20),说明大趋势向上。
- 最新收盘的 5 分钟 K 线需位于日线 EMA(9) 下方,以便在回撤时寻找做多机会。
小时随机指标确认
- 最近一根已完成的 H1 随机指标 %K 必须落在
StochasticLowerBound(默认 0)与 StochasticUpperBound(默认 40)之间。
- %K 在上一根 H1 K 线上需自下而上穿越 %D(当前 %K > %D 且上一根 %K ≤ %D)。
MACD 背离触发(5 分钟)
- MACD 柱状图(主线减去信号线)至少提升
MacdThreshold,同时 5 分钟收盘价低于上一根收盘价,形成看涨背离。
入场执行
- 当所有过滤条件满足且不存在多头持仓时,提交市价买单。如果账户意外持有空头,策略会自动放大下单数量以先行对冲再转为多头。
离场规则
- 当 5 分钟 RSI 上穿
RsiExitLevel(默认 77)时平掉多单。
StopLossPips 与 TakeProfitPips 为正时,StartProtection 会把点值转换为价格距离,自动维护止损与止盈。
订单管理
- 每次下新单前都会取消所有挂单,避免重复成交。
- 交易数量默认使用
TradeVolume,可在优化时调整。
参数
| 参数 |
说明 |
默认值 |
CandleType |
主交易周期。 |
5 分钟 |
HourTimeFrame |
随机指标使用的小时周期。 |
1 小时 |
DayTimeFrame |
EMA 趋势过滤的日线周期。 |
1 天 |
MacdFastPeriod / MacdSlowPeriod / MacdSignalPeriod |
MACD 计算参数。 |
6 / 13 / 5 |
MacdThreshold |
柱状图最小增量,用于判断背离。 |
0.0003 |
DailyFastPeriod / DailySlowPeriod |
日线 EMA 周期。 |
9 / 20 |
StochasticKPeriod / StochasticDPeriod / StochasticSlowing |
小时随机指标设置。 |
30 / 5 / 9 |
StochasticUpperBound / StochasticLowerBound |
%K 接受区间。 |
40 / 0 |
RsiPeriod |
5 分钟 RSI 周期。 |
7 |
RsiExitLevel |
RSI 平仓阈值。 |
77 |
TradeVolume |
下单手数。 |
0.01 |
StopLossPips |
止损距离(点),0 表示关闭。 |
100 |
TakeProfitPips |
止盈距离(点),0 表示关闭。 |
200 |
说明
- 策略同时订阅三个数据源(主周期、H1、D1),通过
Bind/BindEx 与各自的指标联动,实现事件驱动的更新。
- 仅在 K 线收盘后处理数据,与原始 MQL 中使用的
shift 行为保持一致。
- MACD 背离判定采用上一根 K 线的收盘价与柱状图值,忠实还原 fxDreema 构建的逻辑。
- 止损/止盈交由
StartProtection 管理,适用于回测与实时行情,无需额外手动维护保护单。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Divergence EMA RSI Close Buy Only" MetaTrader expert.
/// Long-only strategy: buys when price pulls back below fast EMA with RSI oversold,
/// exits when RSI reaches overbought level.
/// </summary>
public class DivergenceEmaRsiCloseBuyOnlyStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiEntry;
private readonly StrategyParam<decimal> _rsiExit;
private ExponentialMovingAverage _ema;
private RelativeStrengthIndex _rsi;
private decimal? _prevRsi;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public decimal RsiEntry
{
get => _rsiEntry.Value;
set => _rsiEntry.Value = value;
}
public decimal RsiExit
{
get => _rsiExit.Value;
set => _rsiExit.Value = value;
}
public DivergenceEmaRsiCloseBuyOnlyStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signals", "General");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA period for trend filter", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 7)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period for entry/exit", "Indicators");
_rsiEntry = Param(nameof(RsiEntry), 35m)
.SetDisplay("RSI Entry", "RSI level to enter long", "Signals");
_rsiExit = Param(nameof(RsiExit), 65m)
.SetDisplay("RSI Exit", "RSI level to exit long", "Signals");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_prevRsi = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, _rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed || !_rsi.IsFormed)
{
_prevRsi = rsiValue;
return;
}
if (_prevRsi is null)
{
_prevRsi = rsiValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var close = candle.ClosePrice;
// Exit: RSI crosses above exit level
if (Position > 0 && _prevRsi.Value < RsiExit && rsiValue >= RsiExit)
{
SellMarket(Position);
}
// Entry: RSI crosses below entry level and price is near/below EMA (buy the dip)
if (Position <= 0 && _prevRsi.Value > RsiEntry && rsiValue <= RsiEntry && close <= emaValue * 1.005m)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(volume);
}
_prevRsi = rsiValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_ema = null;
_rsi = null;
_prevRsi = null;
base.OnReseted();
}
}
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 ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class divergence_ema_rsi_close_buy_only_strategy(Strategy):
def __init__(self):
super(divergence_ema_rsi_close_buy_only_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._ema_period = self.Param("EmaPeriod", 20)
self._rsi_period = self.Param("RsiPeriod", 7)
self._rsi_entry = self.Param("RsiEntry", 35.0)
self._rsi_exit = self.Param("RsiExit", 65.0)
self._prev_rsi = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiEntry(self):
return self._rsi_entry.Value
@RsiEntry.setter
def RsiEntry(self, value):
self._rsi_entry.Value = value
@property
def RsiExit(self):
return self._rsi_exit.Value
@RsiExit.setter
def RsiExit(self, value):
self._rsi_exit.Value = value
def OnReseted(self):
super(divergence_ema_rsi_close_buy_only_strategy, self).OnReseted()
self._prev_rsi = None
def OnStarted2(self, time):
super(divergence_ema_rsi_close_buy_only_strategy, self).OnStarted2(time)
self._prev_rsi = None
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, rsi, self._process_candle).Start()
def _process_candle(self, candle, ema_value, rsi_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
rsi_val = float(rsi_value)
close = float(candle.ClosePrice)
if self._prev_rsi is None:
self._prev_rsi = rsi_val
return
rsi_entry = float(self.RsiEntry)
rsi_exit = float(self.RsiExit)
# Exit: RSI crosses above exit level
if self.Position > 0 and self._prev_rsi < rsi_exit and rsi_val >= rsi_exit:
self.SellMarket()
# Entry: RSI crosses below entry level and price is near/below EMA
if self.Position <= 0 and self._prev_rsi > rsi_entry and rsi_val <= rsi_entry and close <= ema_val * 1.005:
self.BuyMarket()
self._prev_rsi = rsi_val
def CreateClone(self):
return divergence_ema_rsi_close_buy_only_strategy()