IMACD 狙击手
IMACD Sniper 策略结合 MACD 金叉/死叉、EMA 趋势过滤、成交量确认和强势 K 线。基于近期平均波动的动态止盈与止损。
细节
- 数据:价格 K 线。
- 入场条件:
- 多头:MACD 线向上穿越信号线,价格高于 EMA,MACD 差值超过最小值,两条 MACD 线远离零轴,成交量高于均值,出现强势多头 K 线。
- 空头:MACD 线向下穿越信号线,价格低于 EMA,MACD 差值超过最小值,两条 MACD 线远离零轴,成交量高于均值,出现强势空头 K 线。
- 出场条件:反向 MACD 交叉或达到止盈/止损。
- 止损:基于平均波动的动态止盈与止损。
- 默认参数:
FastLength= 12SlowLength= 26SignalLength= 9MacdDeltaMin= 0.03MacdZeroLimit= 0.05RangeLength= 14RangeMultiplierTp= 4.0RangeMultiplierSl= 1.5EmaLength= 20CandleType= tf(1m)
- 过滤器:
- 类别:趋势
- 方向:多 & 空
- 指标:MACD、EMA、成交量
- 止损:是
- 复杂度:中等
- 时间框架:任意
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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>
/// IMACD Sniper strategy combining MACD crossovers, EMA trend filter,
/// volume confirmation and dynamic take-profit/stop-loss.
/// </summary>
public class ImacdSniperStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<decimal> _macdDeltaMin;
private readonly StrategyParam<decimal> _macdZeroLimit;
private readonly StrategyParam<int> _rangeLength;
private readonly StrategyParam<decimal> _rangeMultiplierTp;
private readonly StrategyParam<decimal> _rangeMultiplierSl;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevMacd;
private decimal _prevSignal;
private decimal _entryPrice;
/// <summary>
/// MACD fast length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// MACD slow length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// MACD signal length.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Minimum MACD delta for entry.
/// </summary>
public decimal MacdDeltaMin
{
get => _macdDeltaMin.Value;
set => _macdDeltaMin.Value = value;
}
/// <summary>
/// Minimum distance from zero line.
/// </summary>
public decimal MacdZeroLimit
{
get => _macdZeroLimit.Value;
set => _macdZeroLimit.Value = value;
}
/// <summary>
/// Length for average range calculation.
/// </summary>
public int RangeLength
{
get => _rangeLength.Value;
set => _rangeLength.Value = value;
}
/// <summary>
/// Take-profit multiplier of the range.
/// </summary>
public decimal RangeMultiplierTp
{
get => _rangeMultiplierTp.Value;
set => _rangeMultiplierTp.Value = value;
}
/// <summary>
/// Stop-loss multiplier of the range.
/// </summary>
public decimal RangeMultiplierSl
{
get => _rangeMultiplierSl.Value;
set => _rangeMultiplierSl.Value = value;
}
/// <summary>
/// EMA length.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ImacdSniperStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast Length", "MACD fast period", "MACD")
.SetOptimize(6, 24, 2);
_slowLength = Param(nameof(SlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow Length", "MACD slow period", "MACD")
.SetOptimize(20, 40, 2);
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal Length", "MACD signal smoothing", "MACD")
.SetOptimize(5, 15, 1);
_macdDeltaMin = Param(nameof(MacdDeltaMin), 0.03m)
.SetGreaterThanZero()
.SetDisplay("Min MACD Delta", "Minimum MACD difference for entry", "Filters")
.SetOptimize(0.01m, 0.1m, 0.01m);
_macdZeroLimit = Param(nameof(MacdZeroLimit), 0.05m)
.SetGreaterThanZero()
.SetDisplay("MACD Zero Limit", "Minimum distance from zero for entry", "Filters")
.SetOptimize(0.01m, 0.1m, 0.01m);
_rangeLength = Param(nameof(RangeLength), 14)
.SetGreaterThanZero()
.SetDisplay("Range Length", "Number of candles for range average", "Risk")
.SetOptimize(5, 30, 1);
_rangeMultiplierTp = Param(nameof(RangeMultiplierTp), 4m)
.SetGreaterThanZero()
.SetDisplay("Range Multiplier TP", "TP multiplier of range", "Risk")
.SetOptimize(1m, 6m, 1m);
_rangeMultiplierSl = Param(nameof(RangeMultiplierSl), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Range Multiplier SL", "SL multiplier of range", "Risk")
.SetOptimize(1m, 3m, 0.5m);
_emaLength = Param(nameof(EmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "Trend")
.SetOptimize(10, 50, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMacd = default;
_prevSignal = default;
_entryPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaLength };
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = FastLength }, LongMa = { Length = SlowLength } },
SignalMa = { Length = SignalLength }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
var macdArea = CreateChartArea();
DrawIndicator(macdArea, macd);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdTyped)
return;
if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
return;
if (emaValue.IsEmpty)
return;
var ema = emaValue.ToDecimal();
var macdDelta = Math.Abs(macd - signal);
var longCondition = _prevMacd != 0 && _prevMacd <= _prevSignal && macd > signal && candle.ClosePrice > ema && macdDelta > MacdDeltaMin;
var shortCondition = _prevMacd != 0 && _prevMacd >= _prevSignal && macd < signal && candle.ClosePrice < ema && macdDelta > MacdDeltaMin;
if (longCondition && Position <= 0)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (shortCondition && Position >= 0)
{
SellMarket();
_entryPrice = candle.ClosePrice;
}
else if (Position > 0 && _prevMacd >= _prevSignal && macd < signal)
{
SellMarket();
}
else if (Position < 0 && _prevMacd <= _prevSignal && macd > signal)
{
BuyMarket();
}
_prevMacd = macd;
_prevSignal = signal;
}
}
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, MovingAverageConvergenceDivergenceSignal, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class imacd_sniper_strategy(Strategy):
"""
IMACD Sniper: MACD crossover with EMA trend filter.
Enters when MACD crosses signal with sufficient delta and price above/below EMA.
"""
def __init__(self):
super(imacd_sniper_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12) \
.SetDisplay("MACD Fast Length", "MACD fast period", "MACD")
self._slow_length = self.Param("SlowLength", 26) \
.SetDisplay("MACD Slow Length", "MACD slow period", "MACD")
self._signal_length = self.Param("SignalLength", 9) \
.SetDisplay("MACD Signal Length", "MACD signal smoothing", "MACD")
self._macd_delta_min = self.Param("MacdDeltaMin", 0.03) \
.SetDisplay("Min MACD Delta", "Minimum MACD difference for entry", "Filters")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA period", "Trend")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_macd = 0.0
self._prev_signal = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(imacd_sniper_strategy, self).OnReseted()
self._prev_macd = 0.0
self._prev_signal = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(imacd_sniper_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._fast_length.Value
macd.Macd.LongMa.Length = self._slow_length.Value
macd.SignalMa.Length = self._signal_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, macd_value, ema_value):
if candle.State != CandleStates.Finished:
return
macd_val = macd_value.Macd
signal_val = macd_value.Signal
if macd_val is None or signal_val is None:
return
if ema_value.IsEmpty:
return
macd = float(macd_val)
signal = float(signal_val)
ema = float(IndicatorHelper.ToDecimal(ema_value))
close = float(candle.ClosePrice)
macd_delta = abs(macd - signal)
delta_min = self._macd_delta_min.Value
long_cond = (self._prev_macd != 0 and self._prev_macd <= self._prev_signal
and macd > signal and close > ema and macd_delta > delta_min)
short_cond = (self._prev_macd != 0 and self._prev_macd >= self._prev_signal
and macd < signal and close < ema and macd_delta > delta_min)
if long_cond and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
elif short_cond and self.Position >= 0:
self.SellMarket()
self._entry_price = close
elif self.Position > 0 and self._prev_macd >= self._prev_signal and macd < signal:
self.SellMarket()
elif self.Position < 0 and self._prev_macd <= self._prev_signal and macd > signal:
self.BuyMarket()
self._prev_macd = macd
self._prev_signal = signal
def CreateClone(self):
return imacd_sniper_strategy()