Divergence MACD Stochastic 策略
该策略将 MetaTrader 5 专家顾问 “Divergence EA pip sl tp” 迁移到 StockSharp 框架。算法在价格行为与 MACD 柱状图之间寻找经典背离,并通过随机指标的超买/超卖过滤进行确认,然后执行反转交易。
交易逻辑
- 根据
CandleType参数订阅主要时间框的K线。 - 在每根收盘K线上计算 MACD 柱状图(
MACD 线 - Signal 线)以及随机指标 %K/%D。 - 维护价格与柱状图最近两个高点和低点,以判断背离。
- 看跌背离:价格创出更高高点,但 MACD 柱状图峰值更低,同时 %K 高于
StochasticUpperLevel,则开空或反手离场多头。 - 看涨背离:价格创出更低低点,但柱状图谷值更高,同时 %K 低于
StochasticLowerLevel,则开多或反手离场空头。 - 可选的
TakeProfitSteps与StopLossSteps会转换成价格步长单位,并在策略启动时一次性启用风险保护。
实现细节
- 使用 StockSharp 高级 API,通过单一K线订阅绑定
MovingAverageConvergenceDivergenceSignal与StochasticOscillator指标。 - 背离状态完全保存在内部字段中,不调用
GetValue方法,符合转换规范。 - 若存在图表区域,会绘制价格K线、MACD 与随机指标以及成交记录。
- 触发信号时通过在基础
Volume上叠加当前持仓的绝对值来实现快速反手。
参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
CandleType |
用于计算背离的时间框。 | 1 小时K线 |
MacdFastLength, MacdSlowLength, MacdSignalLength |
MACD 的快慢 EMA 及信号线长度。 | 12 / 26 / 9 |
MacdDivergenceThreshold |
连续柱状图极值之间所需的最小差值。 | 0.0005 |
StochasticLength |
随机指标快速 %K 的周期。 | 50 |
StochasticSlowK, StochasticSlowD |
%K 与 %D 的平滑长度。 | 9 / 9 |
StochasticUpperLevel, StochasticLowerLevel |
用于确认看跌/看涨信号的超买与超卖阈值。 | 80 / 20 |
TakeProfitSteps, StopLossSteps |
以价格步长表示的止盈/止损距离(0 表示禁用)。 | 50 |
使用方法
- 将策略连接到支持所选时间框的 StockSharp 交易通道。
- 通过
Volume设置基础手数,并根据需求调整指标参数。 - 启动策略,当背离与随机指标条件满足时会自动发送订单。
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 MACD Stochastic" MetaTrader expert.
/// Uses MACD histogram divergence (price vs histogram) with RSI confirmation.
/// MACD is computed manually from two EMAs.
/// </summary>
public class DivergenceMacdStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _rsiPeriod;
// Manual EMA for MACD
private decimal _fastEma;
private decimal _slowEma;
private bool _emaInitialized;
private int _barCount;
private decimal _fastMultiplier;
private decimal _slowMultiplier;
private readonly decimal[] _macdWindow = new decimal[DivergenceLookback];
private readonly decimal[] _priceWindow = new decimal[DivergenceLookback];
private int _windowCount;
private int _windowIndex;
private const int DivergenceLookback = 10;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public DivergenceMacdStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for divergence detection", "General");
_macdFast = Param(nameof(MacdFast), 20)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA length", "Indicators");
_macdSlow = Param(nameof(MacdSlow), 50)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA length", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period for confirmation", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_windowCount = 0;
_windowIndex = 0;
_emaInitialized = false;
_barCount = 0;
_fastMultiplier = 2m / (MacdFast + 1);
_slowMultiplier = 2m / (MacdSlow + 1);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
_barCount++;
// Manual EMA computation
if (!_emaInitialized)
{
_fastEma = close;
_slowEma = close;
_emaInitialized = true;
}
else
{
_fastEma = close * _fastMultiplier + _fastEma * (1 - _fastMultiplier);
_slowEma = close * _slowMultiplier + _slowEma * (1 - _slowMultiplier);
}
if (_barCount < MacdSlow)
return;
var macdLine = _fastEma - _slowEma;
_macdWindow[_windowIndex] = macdLine;
_priceWindow[_windowIndex] = close;
_windowIndex = (_windowIndex + 1) % DivergenceLookback;
if (_windowCount < DivergenceLookback)
_windowCount++;
if (_windowCount < DivergenceLookback)
return;
var volume = Volume;
if (volume <= 0)
volume = 1;
var oldestIndex = _windowIndex;
var newestIndex = (_windowIndex + DivergenceLookback - 1) % DivergenceLookback;
var oldMacd = _macdWindow[oldestIndex];
var newMacd = _macdWindow[newestIndex];
var oldPrice = _priceWindow[oldestIndex];
var newPrice = _priceWindow[newestIndex];
var minPriceMove = oldPrice * 0.005m;
// Bullish divergence: price makes lower low but MACD makes higher low.
var bullishDiv = newPrice < oldPrice - minPriceMove && newMacd > oldMacd;
// Bearish divergence: price makes higher high but MACD makes lower high.
var bearishDiv = newPrice > oldPrice + minPriceMove && newMacd < oldMacd;
if (bullishDiv)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (bearishDiv)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = 0;
_slowEma = 0;
_emaInitialized = false;
_barCount = 0;
_fastMultiplier = 0;
_slowMultiplier = 0;
Array.Clear(_macdWindow, 0, _macdWindow.Length);
Array.Clear(_priceWindow, 0, _priceWindow.Length);
_windowCount = 0;
_windowIndex = 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.Strategies import Strategy
class divergence_macd_stochastic_strategy(Strategy):
DIVERGENCE_LOOKBACK = 10
def __init__(self):
super(divergence_macd_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._macd_fast = self.Param("MacdFast", 20)
self._macd_slow = self.Param("MacdSlow", 50)
self._fast_ema = 0.0
self._slow_ema = 0.0
self._ema_initialized = False
self._bar_count = 0
self._fast_multiplier = 0.0
self._slow_multiplier = 0.0
self._macd_window = []
self._price_window = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MacdFast(self):
return self._macd_fast.Value
@MacdFast.setter
def MacdFast(self, value):
self._macd_fast.Value = value
@property
def MacdSlow(self):
return self._macd_slow.Value
@MacdSlow.setter
def MacdSlow(self, value):
self._macd_slow.Value = value
def OnReseted(self):
super(divergence_macd_stochastic_strategy, self).OnReseted()
self._fast_ema = 0.0
self._slow_ema = 0.0
self._ema_initialized = False
self._bar_count = 0
self._macd_window = []
self._price_window = []
def OnStarted2(self, time):
super(divergence_macd_stochastic_strategy, self).OnStarted2(time)
self._fast_ema = 0.0
self._slow_ema = 0.0
self._ema_initialized = False
self._bar_count = 0
self._fast_multiplier = 2.0 / (self.MacdFast + 1)
self._slow_multiplier = 2.0 / (self.MacdSlow + 1)
self._macd_window = []
self._price_window = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
self._bar_count += 1
if not self._ema_initialized:
self._fast_ema = close
self._slow_ema = close
self._ema_initialized = True
else:
self._fast_ema = close * self._fast_multiplier + self._fast_ema * (1 - self._fast_multiplier)
self._slow_ema = close * self._slow_multiplier + self._slow_ema * (1 - self._slow_multiplier)
if self._bar_count < self.MacdSlow:
return
macd_line = self._fast_ema - self._slow_ema
self._macd_window.append(macd_line)
self._price_window.append(close)
while len(self._macd_window) > self.DIVERGENCE_LOOKBACK:
self._macd_window.pop(0)
self._price_window.pop(0)
if len(self._macd_window) < self.DIVERGENCE_LOOKBACK:
return
old_macd = self._macd_window[0]
new_macd = self._macd_window[-1]
old_price = self._price_window[0]
new_price = self._price_window[-1]
min_price_move = old_price * 0.005
# Bullish divergence: price makes lower low but MACD makes higher low
bullish_div = new_price < old_price - min_price_move and new_macd > old_macd
# Bearish divergence: price makes higher high but MACD makes lower high
bearish_div = new_price > old_price + min_price_move and new_macd < old_macd
if bullish_div:
if self.Position <= 0:
self.BuyMarket()
elif bearish_div:
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return divergence_macd_stochastic_strategy()