MACD 信号策略
该策略基于 MACD 线与其信号线的差值进行交易。 当该差值突破基于 ATR 的阈值时开仓,反向突破时平仓。 策略使用以 tick 为单位的移动止损和固定止盈。
细节
- 入场条件:
- 多头:MACD - Signal 上穿
ATR * Level。 - 空头:MACD - Signal 下穿
-ATR * Level。
- 多头:MACD - Signal 上穿
- 方向:双向。
- 出场条件:
- 反向突破阈值。
- 止损/止盈:
- 固定 tick 止盈。
- 可选移动止损。
- 指标:
- MACD(可调节 fast、slow、signal 周期)。
- ATR(200) 用于计算阈值。
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD signal strategy with ATR-based threshold and trailing stop.
/// Opens positions when MACD crosses the signal line beyond an ATR-adjusted level.
/// </summary>
public class MacdSignalStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfitTicks;
private readonly StrategyParam<decimal> _trailingStopTicks;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<decimal> _atrLevel;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _signalEma;
private AverageTrueRange _atr;
private decimal _prevDelta;
private bool _hasPrevDelta;
/// <summary>
/// Take profit in ticks.
/// </summary>
public decimal TakeProfitTicks
{
get => _takeProfitTicks.Value;
set => _takeProfitTicks.Value = value;
}
/// <summary>
/// Trailing stop in ticks.
/// </summary>
public decimal TrailingStopTicks
{
get => _trailingStopTicks.Value;
set => _trailingStopTicks.Value = value;
}
/// <summary>
/// Fast EMA period for MACD.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period for MACD.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Signal line period for MACD.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for MACD threshold.
/// </summary>
public decimal AtrLevel
{
get => _atrLevel.Value;
set => _atrLevel.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MacdSignalStrategy"/>.
/// </summary>
public MacdSignalStrategy()
{
_takeProfitTicks = Param(nameof(TakeProfitTicks), 10m)
.SetDisplay("Take Profit", "Take profit in ticks", "Risk Management");
_trailingStopTicks = Param(nameof(TrailingStopTicks), 25m)
.SetDisplay("Trailing Stop", "Trailing stop in ticks", "Risk Management");
_fastPeriod = Param(nameof(FastPeriod), 9)
.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 15)
.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 8)
.SetDisplay("Signal", "Signal line period for MACD", "Indicators");
_atrLevel = Param(nameof(AtrLevel), 0.01m)
.SetDisplay("ATR Level", "ATR multiplier for threshold", "Logic");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).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();
_signalEma = null;
_atr = null;
_prevDelta = 0m;
_hasPrevDelta = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
_signalEma = new ExponentialMovingAverage { Length = SignalPeriod };
_atr = new() { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 1m;
StartProtection(
takeProfit: new Unit(TakeProfitTicks * step, UnitTypes.Absolute),
stopLoss: TrailingStopTicks > 0 ? new Unit(TrailingStopTicks * step, UnitTypes.Absolute) : null,
isStopTrailing: TrailingStopTicks > 0);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastEma, decimal slowEma)
{
if (candle.State != CandleStates.Finished)
return;
var atrValue = _atr.Process(candle);
var delta = fastEma - slowEma;
var signal = _signalEma.Process(delta, candle.CloseTime, true).ToDecimal();
if (!atrValue.IsFinal || !_signalEma.IsFormed)
return;
delta -= signal;
var rr = atrValue.ToDecimal() * AtrLevel;
if (!_hasPrevDelta)
{
_prevDelta = delta;
_hasPrevDelta = true;
return;
}
var prevDelta = _prevDelta;
_prevDelta = delta;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position <= 0 && delta > rr && prevDelta <= rr)
{
BuyMarket(Volume + Math.Abs(Position));
}
else if (Position >= 0 && delta < -rr && prevDelta >= -rr)
{
SellMarket(Volume + Math.Abs(Position));
}
else if (Position > 0 && delta < 0)
{
SellMarket(Math.Abs(Position));
}
else if (Position < 0 && delta > 0)
{
BuyMarket(Math.Abs(Position));
}
}
}
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 System import Decimal
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class macd_signal_strategy(Strategy):
def __init__(self):
super(macd_signal_strategy, self).__init__()
self._take_profit_ticks = self.Param("TakeProfitTicks", 10.0)
self._trailing_stop_ticks = self.Param("TrailingStopTicks", 25.0)
self._fast_period = self.Param("FastPeriod", 9)
self._slow_period = self.Param("SlowPeriod", 15)
self._signal_period = self.Param("SignalPeriod", 8)
self._atr_level = self.Param("AtrLevel", 0.01)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._signal_ema = None
self._atr = None
self._prev_delta = 0.0
self._has_prev_delta = False
@property
def TakeProfitTicks(self):
return self._take_profit_ticks.Value
@TakeProfitTicks.setter
def TakeProfitTicks(self, value):
self._take_profit_ticks.Value = value
@property
def TrailingStopTicks(self):
return self._trailing_stop_ticks.Value
@TrailingStopTicks.setter
def TrailingStopTicks(self, value):
self._trailing_stop_ticks.Value = value
@property
def FastPeriod(self):
return self._fast_period.Value
@FastPeriod.setter
def FastPeriod(self, value):
self._fast_period.Value = value
@property
def SlowPeriod(self):
return self._slow_period.Value
@SlowPeriod.setter
def SlowPeriod(self, value):
self._slow_period.Value = value
@property
def SignalPeriod(self):
return self._signal_period.Value
@SignalPeriod.setter
def SignalPeriod(self, value):
self._signal_period.Value = value
@property
def AtrLevel(self):
return self._atr_level.Value
@AtrLevel.setter
def AtrLevel(self, value):
self._atr_level.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(macd_signal_strategy, self).OnStarted2(time)
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
self._signal_ema = ExponentialMovingAverage()
self._signal_ema.Length = self.SignalPeriod
self._atr = AverageTrueRange()
self._atr.Length = 14
self._prev_delta = 0.0
self._has_prev_delta = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, self.ProcessCandle).Start()
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
tp_ticks = float(self.TakeProfitTicks)
sl_ticks = float(self.TrailingStopTicks)
tp_unit = Unit(tp_ticks * step, UnitTypes.Absolute)
sl_unit = Unit(sl_ticks * step, UnitTypes.Absolute) if sl_ticks > 0 else None
is_trailing = sl_ticks > 0
self.StartProtection(tp_unit, sl_unit, is_trailing)
def ProcessCandle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
atr_input = CandleIndicatorValue(self._atr, candle)
atr_result = self._atr.Process(atr_input)
fast_f = float(fast_val)
slow_f = float(slow_val)
delta_val = fast_f - slow_f
signal_result = process_float(self._signal_ema, Decimal(delta_val), candle.CloseTime, True)
signal_f = float(signal_result)
if not atr_result.IsFinal or not self._signal_ema.IsFormed:
return
delta_val -= signal_f
atr_val = float(atr_result)
atr_level = float(self.AtrLevel)
rr = atr_val * atr_level
if not self._has_prev_delta:
self._prev_delta = delta_val
self._has_prev_delta = True
return
prev_delta = self._prev_delta
self._prev_delta = delta_val
if not self.IsFormedAndOnlineAndAllowTrading():
return
pos = float(self.Position)
vol = float(self.Volume)
if pos <= 0 and delta_val > rr and prev_delta <= rr:
self.BuyMarket(vol + abs(pos))
elif pos >= 0 and delta_val < -rr and prev_delta >= -rr:
self.SellMarket(vol + abs(pos))
elif pos > 0 and delta_val < 0:
self.SellMarket(abs(pos))
elif pos < 0 and delta_val > 0:
self.BuyMarket(abs(pos))
def OnReseted(self):
super(macd_signal_strategy, self).OnReseted()
self._signal_ema = None
self._atr = None
self._prev_delta = 0.0
self._has_prev_delta = False
def CreateClone(self):
return macd_signal_strategy()