Momentum Divergence 策略
该策略比较动量指标与价格走势,用于提前发现可能的反转。当价格创出新高或新低而动量未能跟随时,即形成背离,显示趋势减弱。
测试表明年均收益约为 106%,该策略在股票市场表现最佳。
若价格形成更低的低点而动量指标形成更高的低点,出现看涨背离,可做多;若价格创更高的高点而动量未创新高,则形成看跌背离,可做空。动量重新穿越零轴或背离失效时平仓。
适合希望捕捉转折点而非追随趋势的交易者,并通过止损控制若市场继续背离信号方向时的风险。
细节
- 入场条件:
- 多头: 价格更低而动量更高
- 空头: 价格更高而动量更低
- 多/空: 双向
- 离场条件:
- 多头: 动量跌破零轴
- 空头: 动量升破零轴
- 止损: 固定止损
- 默认值:
MomentumPeriod= 14StopLossPercent= 2 更低低点指收盘价低于前一收盘价,同时动量(收盘价减去MomentumPeriod根K线前的收盘价)高于其前值;更高高点与之相反。零轴离场需等待入场后动量穿越零轴。固定止损为入场价的StopLossPercent百分比,在K线之间同样监控;设为0则关闭。规则中没有用到移动平均,因此去掉了MaPeriod。与持仓方向相反的入场信号会反转持仓。CandleType= TimeSpan.FromMinutes(5)
- 过滤器:
- 类别: Reversal
- 方向: 双向
- 指标: Momentum
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内
- 季节性: 否
- 神经网络: 否
- 背离: 是
- 风险等级: 中等
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>
/// Momentum Divergence strategy.
/// A close below the previous close while MomentumPeriod momentum rises above its previous value is a bullish divergence and goes long;
/// a close above the previous close while momentum falls is a bearish one and goes short, reversing an opposite position. A long closes
/// when momentum crosses below zero and a short when it crosses above zero, and a percent stop limits the loss.
/// </summary>
public class MomentumDivergenceStrategy : Strategy
{
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevClose;
private decimal? _prevMomentum;
/// <summary>
/// Period of the momentum indicator.
/// </summary>
public int MomentumPeriod
{
get => _momentumPeriod.Value;
set => _momentumPeriod.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MomentumDivergenceStrategy()
{
_momentumPeriod = Param(nameof(MomentumPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Momentum Period", "Period of the momentum indicator", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = null;
_prevMomentum = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = null;
_prevMomentum = null;
var momentum = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(momentum, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, momentum);
}
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue momentumValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!momentumValue.IsFormed)
return;
var close = candle.ClosePrice;
var value = momentumValue.GetValue<decimal>();
var prevClose = _prevClose;
var prevMomentum = _prevMomentum;
_prevClose = close;
_prevMomentum = value;
if (prevClose is not decimal lastClose || prevMomentum is not decimal lastMomentum)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var bullish = close < lastClose && value > lastMomentum;
var bearish = close > lastClose && value < lastMomentum;
if (bullish && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (bearish && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && lastMomentum >= 0 && value < 0)
SellMarket(Position);
else if (Position < 0 && lastMomentum <= 0 && value > 0)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import Momentum
from StockSharp.Algo.Strategies import Strategy
class momentum_divergence_strategy(Strategy):
"""
Momentum Divergence strategy.
A close below the previous close while MomentumPeriod momentum rises above its previous value is a bullish divergence and goes long;
a close above the previous close while momentum falls is a bearish one and goes short, reversing an opposite position. A long closes
when momentum crosses below zero and a short when it crosses above zero, and a percent stop limits the loss.
"""
def __init__(self):
super(momentum_divergence_strategy, self).__init__()
self._momentum_period = self.Param("MomentumPeriod", 14).SetGreaterThanZero().SetDisplay("Momentum Period", "Period of the momentum indicator", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_close = None
self._prev_momentum = None
def OnReseted(self):
super(momentum_divergence_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(momentum_divergence_strategy, self).OnStarted2(time)
self._reset_state()
momentum = Momentum()
momentum.Length = self._momentum_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(momentum, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, momentum)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, momentum_value):
if candle.State != CandleStates.Finished:
return
if not momentum_value.IsFormed:
return
close = candle.ClosePrice
value = momentum_value.GetValue[Decimal](None)
last_close = self._prev_close
last_momentum = self._prev_momentum
self._prev_close = close
self._prev_momentum = value
if last_close is None or last_momentum is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
bullish = close < last_close and value > last_momentum
bearish = close > last_close and value < last_momentum
if bullish and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif bearish and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and last_momentum >= 0 and value < 0:
self.SellMarket(self.Position)
elif self.Position < 0 and last_momentum <= 0 and value > 0:
self.BuyMarket(-self.Position)
def CreateClone(self):
return momentum_divergence_strategy()