多时间框架 MACD
多时间框架 MACD 将当前时间框架和更高时间框架的 MACD 信号结合。只有当两个时间框架在均线交叉或零线穿越上达成一致时才入场。
详情
- 数据:两个时间框架的价格蜡烛。
- 入场条件:
- 做多:基于
Entry参数,默认是两个时间框架同时出现看涨交叉。 - 做空:与做多相反。
- 做多:基于
- 出场条件:反向信号或移动止损。
- 止损:可选的移动止损。
- 默认值:
FastLength= 12SlowLength= 26SignalLength= 9CandleType= tf(5)HigherCandleType= tf(1h)ShowCurrentTimeframe= trueShowHigherTimeframe= trueEntry= CrossoverUseTrailingStop= falseTrailingStopPercent= 2
- 过滤器:
- 类别:趋势
- 方向:多头和空头
- 指标:MACD
- 止损:是
- 复杂度:中等
- 时间框架:多时间框架 (5m/1h)
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public enum MultiTimeframeMacdEntry
{
Crossover,
ZeroLine,
}
/// <summary>
/// MACD agreement strategy across working and higher timeframes.
/// </summary>
public class MultiTimeframeMacdStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _higherCandleType;
private readonly StrategyParam<bool> _showCurrentTimeframe;
private readonly StrategyParam<bool> _showHigherTimeframe;
private readonly StrategyParam<MultiTimeframeMacdEntry> _entry;
private readonly StrategyParam<bool> _useTrailingStop;
private readonly StrategyParam<decimal> _trailingStopPercent;
private MacdFrame _current;
private MacdFrame _higher;
private int _lastCombined;
private decimal? _bestPrice;
public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DataType HigherCandleType { get => _higherCandleType.Value; set => _higherCandleType.Value = value; }
public bool ShowCurrentTimeframe { get => _showCurrentTimeframe.Value; set => _showCurrentTimeframe.Value = value; }
public bool ShowHigherTimeframe { get => _showHigherTimeframe.Value; set => _showHigherTimeframe.Value = value; }
public MultiTimeframeMacdEntry Entry { get => _entry.Value; set => _entry.Value = value; }
public bool UseTrailingStop { get => _useTrailingStop.Value; set => _useTrailingStop.Value = value; }
public decimal TrailingStopPercent { get => _trailingStopPercent.Value; set => _trailingStopPercent.Value = value; }
public MultiTimeframeMacdStrategy()
{
_fastLength = Param(nameof(FastLength), 12).SetGreaterThanZero();
_slowLength = Param(nameof(SlowLength), 26).SetGreaterThanZero();
_signalLength = Param(nameof(SignalLength), 9).SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame());
_higherCandleType = Param(nameof(HigherCandleType), TimeSpan.FromHours(1).TimeFrame());
_showCurrentTimeframe = Param(nameof(ShowCurrentTimeframe), true);
_showHigherTimeframe = Param(nameof(ShowHigherTimeframe), true);
_entry = Param(nameof(Entry), MultiTimeframeMacdEntry.Crossover);
_useTrailingStop = Param(nameof(UseTrailingStop), false);
_trailingStopPercent = Param(nameof(TrailingStopPercent), 2m).SetGreaterThanZero();
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, HigherCandleType)];
protected override void OnReseted()
{
base.OnReseted();
_current = null;
_higher = null;
_lastCombined = 0;
_bestPrice = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_current = new MacdFrame(FastLength, SlowLength, SignalLength);
_higher = new MacdFrame(FastLength, SlowLength, SignalLength);
var currentSubscription = SubscribeCandles(CandleType);
currentSubscription.Bind(candle =>
{
if (candle.State != CandleStates.Finished)
return;
_current.Process(candle.ClosePrice);
if (ApplyTrailing(candle))
return;
EvaluateAgreement();
}).Start();
var higherSubscription = SubscribeCandles(HigherCandleType);
higherSubscription.Bind(candle =>
{
if (candle.State != CandleStates.Finished)
return;
_higher.Process(candle.ClosePrice);
EvaluateAgreement();
}).Start();
if (ShowCurrentTimeframe)
{
var area = CreateChartArea();
if (area != null)
DrawCandles(area, currentSubscription);
}
if (ShowHigherTimeframe)
{
var area = CreateChartArea();
if (area != null)
DrawCandles(area, higherSubscription);
}
}
private void EvaluateAgreement()
{
if (!_current.IsReady || !_higher.IsReady)
return;
var current = _current.Direction(Entry);
var higher = _higher.Direction(Entry);
var combined = current != 0 && current == higher ? current : 0;
if (combined == 0)
{
_lastCombined = 0;
return;
}
if (combined == _lastCombined)
return;
if (combined > 0 && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_bestPrice = null;
}
else if (combined < 0 && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_bestPrice = null;
}
_lastCombined = combined;
}
private bool ApplyTrailing(ICandleMessage candle)
{
if (!UseTrailingStop || Position == 0)
return false;
if (Position > 0)
{
_bestPrice = _bestPrice is decimal best ? Math.Max(best, candle.HighPrice) : candle.HighPrice;
var stop = _bestPrice.Value * (1m - TrailingStopPercent / 100m);
if (candle.LowPrice <= stop)
{
SellMarket(Math.Abs(Position));
_bestPrice = null;
return true;
}
}
else
{
_bestPrice = _bestPrice is decimal best ? Math.Min(best, candle.LowPrice) : candle.LowPrice;
var stop = _bestPrice.Value * (1m + TrailingStopPercent / 100m);
if (candle.HighPrice >= stop)
{
BuyMarket(Math.Abs(Position));
_bestPrice = null;
return true;
}
}
return false;
}
private sealed class MacdFrame(int fastLength, int slowLength, int signalLength)
{
private readonly int _fastLength = fastLength;
private readonly int _slowLength = slowLength;
private readonly int _signalLength = signalLength;
private decimal? _fast;
private decimal? _slow;
private decimal? _signal;
private int _count;
public decimal Macd { get; private set; }
public decimal Signal { get; private set; }
public bool IsReady => _count >= _slowLength + _signalLength;
public void Process(decimal price)
{
_count++;
_fast = Ema(_fast, price, _fastLength);
_slow = Ema(_slow, price, _slowLength);
Macd = _fast.Value - _slow.Value;
_signal = Ema(_signal, Macd, _signalLength);
Signal = _signal.Value;
}
public int Direction(MultiTimeframeMacdEntry mode)
{
if (!IsReady)
return 0;
var value = mode == MultiTimeframeMacdEntry.Crossover ? Macd - Signal : Macd;
return value > 0m ? 1 : value < 0m ? -1 : 0;
}
private static decimal Ema(decimal? previous, decimal value, int length)
{
if (previous is null)
return value;
var alpha = 2m / (length + 1m);
return previous.Value + alpha * (value - previous.Value);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class _macd_frame:
def __init__(self, fast_length, slow_length, signal_length):
self.fast_length = fast_length
self.slow_length = slow_length
self.signal_length = signal_length
self.fast = None
self.slow = None
self.signal = None
self.macd = Decimal.Zero
self.count = 0
@staticmethod
def ema(previous, value, length):
if previous is None:
return value
alpha = Decimal(2) / Decimal(length + 1)
return previous + alpha * (value - previous)
def process(self, price):
self.count += 1
self.fast = self.ema(self.fast, price, self.fast_length)
self.slow = self.ema(self.slow, price, self.slow_length)
self.macd = self.fast - self.slow
self.signal = self.ema(self.signal, self.macd, self.signal_length)
def ready(self):
return self.count >= self.slow_length + self.signal_length
def direction(self, mode):
if not self.ready():
return 0
value = self.macd - self.signal if mode.lower() == "crossover" else self.macd
return 1 if value > 0 else (-1 if value < 0 else 0)
class multi_timeframe_macd_strategy(Strategy):
def __init__(self):
super(multi_timeframe_macd_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12).SetGreaterThanZero()
self._slow_length = self.Param("SlowLength", 26).SetGreaterThanZero()
self._signal_length = self.Param("SignalLength", 9).SetGreaterThanZero()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._higher_candle_type = self.Param("HigherCandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._show_current = self.Param("ShowCurrentTimeframe", True)
self._show_higher = self.Param("ShowHigherTimeframe", True)
self._entry = self.Param("Entry", "Crossover")
self._use_trailing = self.Param("UseTrailingStop", False)
self._trailing_percent = self.Param("TrailingStopPercent", 2.0).SetGreaterThanZero()
self._current = None
self._higher = None
self._last_combined = 0
self._best_price = None
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value), (self.Security, self._higher_candle_type.Value)]
def OnReseted(self):
super(multi_timeframe_macd_strategy, self).OnReseted()
self._current = None
self._higher = None
self._last_combined = 0
self._best_price = None
def OnStarted2(self, time):
super(multi_timeframe_macd_strategy, self).OnStarted2(time)
fast = int(self._fast_length.Value)
slow = int(self._slow_length.Value)
signal = int(self._signal_length.Value)
self._current = _macd_frame(fast, slow, signal)
self._higher = _macd_frame(fast, slow, signal)
def on_current(candle):
if candle.State != CandleStates.Finished:
return
self._current.process(candle.ClosePrice)
if self._apply_trailing(candle):
return
self._evaluate()
def on_higher(candle):
if candle.State != CandleStates.Finished:
return
self._higher.process(candle.ClosePrice)
self._evaluate()
current_subscription = self.SubscribeCandles(self._candle_type.Value)
current_subscription.Bind(on_current).Start()
higher_subscription = self.SubscribeCandles(self._higher_candle_type.Value)
higher_subscription.Bind(on_higher).Start()
if self._show_current.Value:
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, current_subscription)
if self._show_higher.Value:
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, higher_subscription)
def _evaluate(self):
if not self._current.ready() or not self._higher.ready():
return
mode = str(self._entry.Value)
current = self._current.direction(mode)
higher = self._higher.direction(mode)
combined = current if current != 0 and current == higher else 0
if combined == 0:
self._last_combined = 0
return
if combined == self._last_combined:
return
if combined > 0 and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
self._best_price = None
elif combined < 0 and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
self._best_price = None
self._last_combined = combined
def _apply_trailing(self, candle):
if not bool(self._use_trailing.Value) or self.Position == 0:
return False
pct = Decimal(self._trailing_percent.Value) / Decimal(100)
if self.Position > 0:
high = candle.HighPrice
self._best_price = high if self._best_price is None else max(self._best_price, high)
stop = self._best_price * (Decimal(1) - pct)
if candle.LowPrice <= stop:
self.SellMarket(Math.Abs(self.Position))
self._best_price = None
return True
else:
low = candle.LowPrice
self._best_price = low if self._best_price is None else min(self._best_price, low)
stop = self._best_price * (Decimal(1) + pct)
if candle.HighPrice >= stop:
self.BuyMarket(Math.Abs(self.Position))
self._best_price = None
return True
return False
def CreateClone(self):
return multi_timeframe_macd_strategy()