MACD Sentiment Filter
MACD Sentiment Filter 策略基于 MACD Sentiment Filter。
当 its indicators confirms filtered entries 在日内(15m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 MacdFast, MacdSlow 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
MacdFast = 12MacdSlow = 26MacdSignal = 9Threshold = 0.5mStopLoss = 2mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: multiple indicators
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (15m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD with Sentiment Filter strategy.
/// Entry condition:
/// Long: MACD > Signal && Sentiment_Score > Threshold
/// Short: MACD < Signal && Sentiment_Score < -Threshold
/// Exit condition:
/// Long: MACD < Signal
/// Short: MACD > Signal
/// </summary>
public class MacdWithSentimentFilterStrategy : Strategy
{
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<decimal> _threshold;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
// Sentiment score from external data source (simplified with simulation for this example)
private decimal _sentimentScore;
// Last MACD and Signal values stored from the previous candle
private decimal _prevMacd;
private decimal _prevSignal;
private bool _hasPreviousMacd;
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>
/// Sentiment threshold for entry signal.
/// </summary>
public decimal Threshold
{
get => _threshold.Value;
set => _threshold.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Bars to wait between position changes.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor with default parameters.
/// </summary>
public MacdWithSentimentFilterStrategy()
{
_macdFast = Param(nameof(MacdFast), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast moving average period for MACD", "MACD Settings")
.SetOptimize(8, 20, 1);
_macdSlow = Param(nameof(MacdSlow), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow moving average period for MACD", "MACD Settings")
.SetOptimize(20, 34, 2);
_macdSignal = Param(nameof(MacdSignal), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal line period for MACD", "MACD Settings")
.SetOptimize(5, 13, 1);
_threshold = Param(nameof(Threshold), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Sentiment Threshold", "Threshold for sentiment filter", "Sentiment Settings")
.SetOptimize(0.2m, 0.8m, 0.1m);
_cooldownBars = Param(nameof(CooldownBars), 24)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General");
_stopLoss = Param(nameof(StopLoss), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (%)", "Stop Loss percentage from entry price", "Risk Management")
.SetOptimize(1m, 3m, 0.5m);
_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();
// reset stored values
_prevMacd = default;
_prevSignal = default;
_sentimentScore = default;
_hasPreviousMacd = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create MACD indicator
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
},
SignalMa = { Length = MacdSignal }
};
var _macdInd = macd;
// Subscribe to candles
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(c => ProcessCandle(c, _macdInd))
.Start();
// Create chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawOwnTrades(area);
}
// Enable position protection with stop-loss
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(StopLoss, UnitTypes.Percent)
);
}
/// <summary>
/// Process each candle and MACD values.
/// </summary>
private void ProcessCandle(ICandleMessage candle, MovingAverageConvergenceDivergenceSignal macdInd)
{
if (candle.State != CandleStates.Finished)
return;
UpdateSentimentScore(candle);
var macdResult = macdInd.Process(candle);
if (!macdInd.IsFormed)
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdResult;
if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (!_hasPreviousMacd)
{
_prevMacd = macd;
_prevSignal = signal;
_hasPreviousMacd = true;
return;
}
// Store previous MACD values for state tracking
var prevMacdOverSignal = _prevMacd > _prevSignal;
var currMacdOverSignal = macd > signal;
// Entry conditions with sentiment filter
if (_cooldownRemaining == 0 && prevMacdOverSignal != currMacdOverSignal && Position == 0)
{
if (currMacdOverSignal && _sentimentScore > Threshold)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (!currMacdOverSignal && _sentimentScore < -Threshold)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
_prevMacd = macd;
_prevSignal = signal;
}
/// <summary>
/// Update sentiment score based on candle data (simulation).
/// In a real implementation, this would fetch data from an external source.
/// </summary>
private void UpdateSentimentScore(ICandleMessage candle)
{
var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var totalSize = candle.HighPrice - candle.LowPrice;
if (totalSize == 0)
return;
var bodyRatio = bodySize / totalSize;
_sentimentScore *= 0.85m;
// Bullish candle with strong body
if (candle.ClosePrice > candle.OpenPrice && bodyRatio > 0.7m)
{
_sentimentScore = Math.Min(_sentimentScore + 0.25m, 1m);
}
// Bearish candle with strong body
else if (candle.ClosePrice < candle.OpenPrice && bodyRatio > 0.7m)
{
_sentimentScore = Math.Max(_sentimentScore - 0.25m, -1m);
}
LogInfo($"Updated sentiment score: {_sentimentScore}");
}
}
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, MovingAverageConvergenceDivergenceSignalValue, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class macd_with_sentiment_filter_strategy(Strategy):
"""
MACD with Sentiment Filter strategy.
Entry condition:
Long: MACD > Signal && Sentiment_Score > Threshold
Short: MACD < Signal && Sentiment_Score < -Threshold
"""
def __init__(self):
super(macd_with_sentiment_filter_strategy, self).__init__()
self._macd_fast = self.Param("MacdFast", 12) \
.SetGreaterThanZero() \
.SetDisplay("MACD Fast", "Fast moving average period for MACD", "MACD Settings")
self._macd_slow = self.Param("MacdSlow", 26) \
.SetGreaterThanZero() \
.SetDisplay("MACD Slow", "Slow moving average period for MACD", "MACD Settings")
self._macd_signal = self.Param("MacdSignal", 9) \
.SetGreaterThanZero() \
.SetDisplay("MACD Signal", "Signal line period for MACD", "MACD Settings")
self._threshold = self.Param("Threshold", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Sentiment Threshold", "Threshold for sentiment filter", "Sentiment Settings")
self._cooldown_bars = self.Param("CooldownBars", 24) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General")
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (%)", "Stop Loss percentage from entry price", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._sentiment_score = 0.0
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_previous_macd = False
self._cooldown_remaining = 0
self._macd_ind = None
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(macd_with_sentiment_filter_strategy, self).OnReseted()
self._prev_macd = 0.0
self._prev_signal = 0.0
self._sentiment_score = 0.0
self._has_previous_macd = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(macd_with_sentiment_filter_strategy, self).OnStarted2(time)
self._macd_ind = MovingAverageConvergenceDivergenceSignal()
self._macd_ind.Macd.ShortMa.Length = int(self._macd_fast.Value)
self._macd_ind.Macd.LongMa.Length = int(self._macd_slow.Value)
self._macd_ind.SignalMa.Length = int(self._macd_signal.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._macd_ind)
self.DrawOwnTrades(area)
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(float(self._stop_loss.Value), UnitTypes.Percent)
)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self.UpdateSentimentScore(candle)
civ = CandleIndicatorValue(self._macd_ind, candle)
civ.IsFinal = True
macd_result = self._macd_ind.Process(civ)
if not self._macd_ind.IsFormed:
return
macd_typed = macd_result
macd_val = macd_typed.Macd
signal_val = macd_typed.Signal
if macd_val is None or signal_val is None:
return
macd = float(macd_val)
signal = float(signal_val)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if not self._has_previous_macd:
self._prev_macd = macd
self._prev_signal = signal
self._has_previous_macd = True
return
prev_macd_over_signal = self._prev_macd > self._prev_signal
curr_macd_over_signal = macd > signal
threshold = float(self._threshold.Value)
cooldown = int(self._cooldown_bars.Value)
if self._cooldown_remaining == 0 and prev_macd_over_signal != curr_macd_over_signal and self.Position == 0:
if curr_macd_over_signal and self._sentiment_score > threshold:
self.BuyMarket()
self._cooldown_remaining = cooldown
elif not curr_macd_over_signal and self._sentiment_score < -threshold:
self.SellMarket()
self._cooldown_remaining = cooldown
self._prev_macd = macd
self._prev_signal = signal
def UpdateSentimentScore(self, candle):
body_size = float(abs(candle.ClosePrice - candle.OpenPrice))
total_size = float(candle.HighPrice - candle.LowPrice)
if total_size == 0:
return
body_ratio = body_size / total_size
self._sentiment_score *= 0.85
if candle.ClosePrice > candle.OpenPrice and body_ratio > 0.7:
self._sentiment_score = min(self._sentiment_score + 0.25, 1.0)
elif candle.ClosePrice < candle.OpenPrice and body_ratio > 0.7:
self._sentiment_score = max(self._sentiment_score - 0.25, -1.0)
self.LogInfo("Updated sentiment score: {0}".format(self._sentiment_score))
def CreateClone(self):
return macd_with_sentiment_filter_strategy()