MACD Hidden Markov Model
MACD Hidden Markov Model 策略基于 MACD Hidden Markov Model。
测试表明年均收益约为 61%,该策略在加密市场表现最佳。
当 Markov confirms trend changes 在日内(5m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 MacdFast, MacdSlow 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
MacdFast = 12MacdSlow = 26MacdSignal = 9CandleType = TimeSpan.FromMinutes(5).TimeFrame()HmmHistoryLength = 100
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: Markov
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (5m)
- 季节性: 否
- 神经网络: 是
- 背离: 否
- 风险等级: 中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD strategy with Hidden Markov Model for state detection.
/// </summary>
public class MacdHmmStrategy : Strategy
{
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _hmmHistoryLength;
private readonly StrategyParam<int> _signalCooldownBars;
private MovingAverageConvergenceDivergenceSignal _macd;
// Hidden Markov Model states
private enum MarketStates
{
Bullish,
Neutral,
Bearish
}
private MarketStates _currentState = MarketStates.Neutral;
// Data for HMM calculations
private readonly List<decimal> _priceChanges = [];
private readonly List<decimal> _volumes = [];
private decimal _prevPrice;
private decimal? _prevMacd;
private decimal? _prevSignal;
private int _cooldownRemaining;
/// <summary>
/// MACD fast period.
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// MACD slow period.
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// MACD signal period.
/// </summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>
/// Candle type to use for the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Length of history for Hidden Markov Model.
/// </summary>
public int HmmHistoryLength
{
get => _hmmHistoryLength.Value;
set => _hmmHistoryLength.Value = value;
}
/// <summary>
/// Bars to wait between trading actions.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MacdHmmStrategy"/>.
/// </summary>
public MacdHmmStrategy()
{
_macdFast = Param(nameof(MacdFast), 12)
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD", "Indicators")
.SetOptimize(8, 20, 2);
_macdSlow = Param(nameof(MacdSlow), 26)
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD", "Indicators")
.SetOptimize(20, 40, 2);
_macdSignal = Param(nameof(MacdSignal), 9)
.SetDisplay("MACD Signal Period", "Signal EMA period for MACD", "Indicators")
.SetOptimize(7, 15, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_hmmHistoryLength = Param(nameof(HmmHistoryLength), 100)
.SetDisplay("HMM History Length", "Length of history for Hidden Markov Model", "HMM Parameters")
.SetOptimize(50, 200, 10);
_signalCooldownBars = Param(nameof(SignalCooldownBars), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between position changes", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentState = MarketStates.Neutral;
_prevPrice = 0;
_prevMacd = null;
_prevSignal = null;
_cooldownRemaining = 0;
_priceChanges.Clear();
_volumes.Clear();
_macd?.Reset();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create MACD indicator
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
},
SignalMa = { Length = MacdSignal }
};
// Create subscription and bind indicator
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
// Setup position protection
StartProtection(
new Unit(2, UnitTypes.Percent),
new Unit(2, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Update HMM data
UpdateHmmData(candle);
// Determine market state using HMM
CalculateMarketState();
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (macdValue is not IMovingAverageConvergenceDivergenceSignalValue macdTyped ||
macdTyped.Macd is not decimal macd ||
macdTyped.Signal is not decimal signal)
return;
if (_prevMacd is not decimal previousMacd || _prevSignal is not decimal previousSignal)
{
_prevMacd = macd;
_prevSignal = signal;
return;
}
var crossUp = previousMacd <= previousSignal && macd > signal;
var crossDown = previousMacd >= previousSignal && macd < signal;
var longExit = Position > 0 && (_currentState == MarketStates.Bearish || crossDown);
var shortExit = Position < 0 && (_currentState == MarketStates.Bullish || crossUp);
// Generate trade signals based on MACD transitions and HMM state.
if (longExit)
{
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
}
else if (shortExit)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && crossUp && _currentState == MarketStates.Bullish && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && crossDown && _currentState == MarketStates.Bearish && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
_prevMacd = macd;
_prevSignal = signal;
}
private void UpdateHmmData(ICandleMessage candle)
{
// Calculate price change
if (_prevPrice > 0)
{
decimal priceChange = candle.ClosePrice - _prevPrice;
_priceChanges.Add(priceChange);
_volumes.Add(candle.TotalVolume);
// Maintain the desired history length
while (_priceChanges.Count > HmmHistoryLength)
{
_priceChanges.RemoveAt(0);
_volumes.RemoveAt(0);
}
}
_prevPrice = candle.ClosePrice;
}
private void CalculateMarketState()
{
// Only perform state calculation when we have enough data
if (_priceChanges.Count < 10)
return;
// Simple HMM approximation using recent price changes and volume patterns
// Note: This is a simplified implementation - a real HMM would use proper state transition probabilities
// Calculate statistics of recent price changes
var priceChanges = _priceChanges.ToArray();
var volumes = _volumes.ToArray();
var startIndex = Math.Max(0, priceChanges.Length - 10);
var positiveChanges = 0;
var negativeChanges = 0;
for (var i = startIndex; i < priceChanges.Length; i++)
{
if (priceChanges[i] > 0)
positiveChanges++;
else if (priceChanges[i] < 0)
negativeChanges++;
}
// Calculate average volume for up and down days
decimal upVolume = 0;
decimal downVolume = 0;
int upCount = 0;
int downCount = 0;
for (var i = startIndex; i < priceChanges.Length; i++)
{
if (priceChanges[i] > 0)
{
upVolume += volumes[i];
upCount++;
}
else if (priceChanges[i] < 0)
{
downVolume += volumes[i];
downCount++;
}
}
upVolume = upCount > 0 ? upVolume / upCount : 0;
downVolume = downCount > 0 ? downVolume / downCount : 0;
// Determine market state based on price change direction and volume
if (positiveChanges >= 7 || (positiveChanges >= 6 && upVolume > downVolume * 1.5m))
{
_currentState = MarketStates.Bullish;
}
else if (negativeChanges >= 7 || (negativeChanges >= 6 && downVolume > upVolume * 1.5m))
{
_currentState = MarketStates.Bearish;
}
else
{
_currentState = MarketStates.Neutral;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class macd_hidden_markov_model_strategy(Strategy):
"""
MACD strategy with Hidden Markov Model for state detection.
"""
BULLISH = 0
NEUTRAL = 1
BEARISH = 2
def __init__(self):
super(macd_hidden_markov_model_strategy, self).__init__()
self._macd_fast = self.Param("MacdFast", 12) \
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD", "Indicators")
self._macd_slow = self.Param("MacdSlow", 26) \
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD", "Indicators")
self._macd_signal = self.Param("MacdSignal", 9) \
.SetDisplay("MACD Signal Period", "Signal EMA period for MACD", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._hmm_history_length = self.Param("HmmHistoryLength", 100) \
.SetDisplay("HMM History Length", "Length of history for Hidden Markov Model", "HMM Parameters")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 12) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown", "Bars to wait between position changes", "Trading")
self._current_state = macd_hidden_markov_model_strategy.NEUTRAL
self._price_changes = []
self._volumes = []
self._prev_price = 0.0
self._prev_macd = None
self._prev_signal = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_hidden_markov_model_strategy, self).OnReseted()
self._current_state = macd_hidden_markov_model_strategy.NEUTRAL
self._prev_price = 0.0
self._prev_macd = None
self._prev_signal = None
self._cooldown_remaining = 0
self._price_changes = []
self._volumes = []
def OnStarted2(self, time):
super(macd_hidden_markov_model_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = int(self._macd_fast.Value)
macd.Macd.LongMa.Length = int(self._macd_slow.Value)
macd.SignalMa.Length = int(self._macd_signal.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(2, UnitTypes.Percent)
)
def _process_candle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
self._update_hmm_data(candle)
self._calculate_market_state()
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
macd_val = macd_value.Macd
signal_val = macd_value.Signal
if macd_val is None or signal_val is None:
return
macd_f = float(macd_val)
signal_f = float(signal_val)
if self._prev_macd is None or self._prev_signal is None:
self._prev_macd = macd_f
self._prev_signal = signal_f
return
cross_up = self._prev_macd <= self._prev_signal and macd_f > signal_f
cross_down = self._prev_macd >= self._prev_signal and macd_f < signal_f
long_exit = self.Position > 0 and (self._current_state == macd_hidden_markov_model_strategy.BEARISH or cross_down)
short_exit = self.Position < 0 and (self._current_state == macd_hidden_markov_model_strategy.BULLISH or cross_up)
cd = int(self._signal_cooldown_bars.Value)
if long_exit:
self.SellMarket(self.Position)
self._cooldown_remaining = cd
elif short_exit:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cd
elif self._cooldown_remaining == 0 and cross_up and self._current_state == macd_hidden_markov_model_strategy.BULLISH and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
self._cooldown_remaining = cd
elif self._cooldown_remaining == 0 and cross_down and self._current_state == macd_hidden_markov_model_strategy.BEARISH and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
self._cooldown_remaining = cd
self._prev_macd = macd_f
self._prev_signal = signal_f
def _update_hmm_data(self, candle):
close_price = float(candle.ClosePrice)
if self._prev_price > 0:
price_change = close_price - self._prev_price
self._price_changes.append(price_change)
self._volumes.append(float(candle.TotalVolume))
hmm_len = int(self._hmm_history_length.Value)
while len(self._price_changes) > hmm_len:
self._price_changes.pop(0)
self._volumes.pop(0)
self._prev_price = close_price
def _calculate_market_state(self):
if len(self._price_changes) < 10:
return
start_index = max(0, len(self._price_changes) - 10)
positive_changes = 0
negative_changes = 0
for i in range(start_index, len(self._price_changes)):
if self._price_changes[i] > 0:
positive_changes += 1
elif self._price_changes[i] < 0:
negative_changes += 1
up_volume = 0.0
down_volume = 0.0
up_count = 0
down_count = 0
for i in range(start_index, len(self._price_changes)):
if self._price_changes[i] > 0:
up_volume += self._volumes[i]
up_count += 1
elif self._price_changes[i] < 0:
down_volume += self._volumes[i]
down_count += 1
up_volume = up_volume / up_count if up_count > 0 else 0.0
down_volume = down_volume / down_count if down_count > 0 else 0.0
if positive_changes >= 7 or (positive_changes >= 6 and up_volume > down_volume * 1.5):
self._current_state = macd_hidden_markov_model_strategy.BULLISH
elif negative_changes >= 7 or (negative_changes >= 6 and down_volume > up_volume * 1.5):
self._current_state = macd_hidden_markov_model_strategy.BEARISH
else:
self._current_state = macd_hidden_markov_model_strategy.NEUTRAL
def CreateClone(self):
return macd_hidden_markov_model_strategy()