MACD Zero Cross 戦略
このシステムは、Moving Average Convergence Divergence (MACD)ヒストグラムがゼロラインに近づくときのモメンタム転換を取引します。ゼロ以下での上昇MACDまたはゼロ以上での下落MACDは潜在的なリバーサルを示します。
テストでは年間平均リターン約136%が示されています。株式市場で最も優れたパフォーマンスを発揮します。
この戦略はMACD線がまだ反対側にある間、ゼロに向かって動くのを待ちます。モメンタムが衰えると、価格のスイングを予測してエントリーします。
MACDがシグナル線をクロスするか、ストップロスが発動されるとトレードを終了します。
詳細
- エントリー条件: MACDがどちらの側からもゼロに向かって動いている。
- ロング/ショート: 両方向。
- エグジット条件: MACDがシグナル線をクロスするかストップ。
- ストップ: はい。
- デフォルト値:
FastPeriod= 12SlowPeriod= 26SignalPeriod= 9StopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: モメンタム
- 方向: 両方
- インジケーター: MACD
- ストップ: はい
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Enters while the MACD line approaches zero, before reaching it.
/// Exits on a signal-line crossing and protects actual fills with a percent stop.
/// </summary>
public class MacdZeroStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private decimal _prevMacd;
private bool _hasPrev;
private decimal _prevSignal;
private Order _pendingOrder;
/// <summary>
/// Fast EMA period for MACD calculation.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period for MACD calculation.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Signal line period for MACD calculation.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Type of candles used for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize the MACD Zero strategy.
/// </summary>
public MacdZeroStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period for MACD", "MACD")
.SetOptimize(8, 16, 2);
_slowPeriod = Param(nameof(SlowPeriod), 26).SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period for MACD", "MACD")
.SetOptimize(15, 30, 2);
_signalPeriod = Param(nameof(SignalPeriod), 9).SetGreaterThanZero()
.SetDisplay("Signal", "Signal line period for MACD", "MACD")
.SetOptimize(7, 12, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
OrderRegistering += order => _pendingOrder = order;
}
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMacd = default;
_hasPrev = default;
_prevSignal = default;
_pendingOrder = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (FastPeriod >= SlowPeriod)
throw new InvalidOperationException("FastPeriod must be below SlowPeriod.");
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
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 macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastPeriod },
LongMa = { Length = SlowPeriod },
},
SignalMa = { Length = SignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before this callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
// Complex values capture formation before processing their inner indicators.
// Check the indicator's post-processing state to seed the first fully formed bar.
if (candle.State != CandleStates.Finished || !macdValue.Indicator.IsFormed || !IsFormedAndOnlineAndAllowTrading())
return;
var value = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (value.Macd is not decimal macd || value.Signal is not decimal signal)
return;
if (!_hasPrev)
{
_hasPrev = true;
_prevMacd = macd;
_prevSignal = signal;
return;
}
if (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
{
_prevMacd = macd;
_prevSignal = signal;
return;
}
var signalCross = _prevMacd <= _prevSignal && macd > signal
|| _prevMacd >= _prevSignal && macd < signal;
// The published exit is a crossing in either direction, not a zero-line exit.
if (Position != 0m && signalCross)
{
if (Position > 0m) SellMarket(Position);
else BuyMarket(Math.Abs(Position));
}
else if (Position == 0m && macd < 0m && macd > _prevMacd)
BuyMarket(Volume);
else if (Position == 0m && macd > 0m && macd < _prevMacd)
SellMarket(Volume);
_prevMacd = macd;
_prevSignal = signal;
}
}
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, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class macd_zero_strategy(Strategy):
"""
Enters while the MACD line approaches zero, before reaching it.
Exits on either signal-line crossing and protects actual fills with a percent stop.
"""
def __init__(self):
super(macd_zero_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period for MACD", "MACD")
self._slow_period = self.Param("SlowPeriod", 26).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period for MACD", "MACD")
self._signal_period = self.Param("SignalPeriod", 9).SetGreaterThanZero().SetDisplay("Signal", "Signal line period for MACD", "MACD")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative() \
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._prev_macd = Decimal.Zero
self._prev_signal = Decimal.Zero
self._has_prev = False
self._pending_order = None
self.OrderRegistering += self._track_pending
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_zero_strategy, self).OnReseted()
self._prev_macd = Decimal.Zero
self._prev_signal = Decimal.Zero
self._has_prev = False
self._pending_order = None
def OnStarted2(self, time):
super(macd_zero_strategy, self).OnStarted2(time)
self._prev_macd = Decimal.Zero
self._prev_signal = Decimal.Zero
self._has_prev = False
self._pending_order = None
if self._fast_period.Value >= self._slow_period.Value:
raise ValueError("FastPeriod must be below SlowPeriod.")
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
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()
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._fast_period.Value
macd.Macd.LongMa.Length = self._slow_period.Value
macd.SignalMa.Length = self._signal_period.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)
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between signal bars.
pass
def _process_candle(self, candle, value):
# Complex values snapshot formation before processing their inner indicators.
if candle.State != CandleStates.Finished or not value.Indicator.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
if value.Macd is None or value.Signal is None:
return
macd = value.Macd
signal = value.Signal
if not self._has_prev:
self._has_prev = True
self._prev_macd = macd
self._prev_signal = signal
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
self._prev_macd = macd
self._prev_signal = signal
return
crossed = (self._prev_macd <= self._prev_signal and macd > signal) or (self._prev_macd >= self._prev_signal and macd < signal)
# The published exit is a crossing in either direction, not a zero-line exit.
if self.Position != 0 and crossed:
if self.Position > 0:
self.SellMarket(self.Position)
else:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and macd < 0 and macd > self._prev_macd:
self.BuyMarket(self.Volume)
elif self.Position == 0 and macd > 0 and macd < self._prev_macd:
self.SellMarket(self.Volume)
self._prev_macd = macd
self._prev_signal = signal
def CreateClone(self):
return macd_zero_strategy()