MACD Fixed PSAR 策略
概述
该策略为 MetaTrader 智能交易系统 EA_MACD_FixedPSAR 的 C# 版本。策略通过 MACD 金叉/死叉配合 EMA 趋势过滤来捕捉趋势反转,同时提供固定距离追踪止损与类抛物线 SAR 追踪止损两种风险管理选项。所有阈值均以点数(pips)配置,并根据品种的最小跳动点自动转换为价格单位。
指标
MovingAverageConvergenceDivergenceSignal(12, 26, 9)提供 MACD 线与信号线。ExponentialMovingAverage(默认 26)作为短期趋势过滤器。
交易逻辑
- 入场
- 做多:MACD 在零轴下方向上穿越信号线,MACD 绝对值超过“开仓阈值”,且 EMA 较上一根 K 线走高。
- 做空:MACD 在零轴上方向下穿越信号线,MACD 绝对值超过“开仓阈值”,且 EMA 较上一根 K 线走低。
- 离场
- MACD 在相反方向的回调超过“平仓阈值”。
- 以点数定义的止盈与止损触发。
- 可选的追踪止损:
- Fixed:保持与最新收盘价的固定距离。
- Fixed PSAR:模拟原始 MQL 版本使用的逐步抛物线 SAR 调整方法。
参数
| 名称 | 说明 |
|---|---|
Volume |
订单使用的交易量。 |
TakeProfitPips |
止盈距离(点)。 |
StopLossPips |
止损距离(点)。 |
TrailMode |
追踪止损模式(None、Fixed、FixedPsar)。 |
TrailingStopPips |
固定追踪模式的距离。 |
PsarStep |
PSAR 模式的初始加速因子。 |
PsarMaximum |
PSAR 模式的最大加速因子。 |
MacdOpenLevelPips |
触发开仓所需的 MACD 最小幅度(点)。 |
MacdCloseLevelPips |
触发平仓所需的 MACD 最小幅度(点)。 |
TrendPeriod |
EMA 趋势过滤器的周期。 |
CandleType |
用于计算指标的 K 线类型。 |
注意事项
- 点数会根据合约的
PriceStep自动转换,并包含三/五位小数品种的特殊调整,以贴合 MetaTrader 的处理方式。 - 追踪止损只在完整收盘的 K 线上更新,以避免噪音导致的提前离场。
- 若图表区域可用,策略会绘制行情 K 线、两个指标以及交易标记。
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>
/// Simplified from "EA_MACD_FixedPSAR" MetaTrader expert.
/// Combines MACD histogram crossover with EMA trend filter.
/// Buys when MACD histogram goes positive and price above EMA.
/// Sells when MACD histogram goes negative and price below EMA.
/// </summary>
public class MacdFixedPsarStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<int> _trendPeriod;
private MovingAverageConvergenceDivergence _macd;
private ExponentialMovingAverage _trendEma;
private readonly Queue<decimal> _macdHistory = new();
private decimal? _prevHistogram;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
public int TrendPeriod
{
get => _trendPeriod.Value;
set => _trendPeriod.Value = value;
}
public MacdFixedPsarStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for MACD calculations", "General");
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Period", "Signal line smoothing period", "Indicators");
_trendPeriod = Param(nameof(TrendPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Trend EMA", "Trend filter EMA period (computed as SMA)", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHistogram = null;
_macdHistory.Clear();
_macd = new MovingAverageConvergenceDivergence
{
ShortMa = { Length = FastPeriod },
LongMa = { Length = SlowPeriod },
};
_trendEma = new ExponentialMovingAverage { Length = TrendPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_macd, _trendEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macd);
DrawIndicator(area, _trendEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal macdValue, decimal trendValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_macd.IsFormed || !_trendEma.IsFormed)
return;
var close = candle.ClosePrice;
// Compute signal line manually
_macdHistory.Enqueue(macdValue);
if (_macdHistory.Count > SignalPeriod)
_macdHistory.Dequeue();
if (_macdHistory.Count < SignalPeriod)
return;
decimal signalSum = 0;
var history = _macdHistory.ToArray();
foreach (var v in history)
signalSum += v;
var signal = signalSum / history.Length;
var histogram = macdValue - signal;
if (_prevHistogram is null)
{
_prevHistogram = histogram;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var crossUp = _prevHistogram.Value <= 0 && histogram > 0;
var crossDown = _prevHistogram.Value >= 0 && histogram < 0;
if (crossUp && close > trendValue)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossDown && close < trendValue)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevHistogram = histogram;
}
/// <inheritdoc />
protected override void OnReseted()
{
_macd = null;
_trendEma = null;
_prevHistogram = null;
_macdHistory.Clear();
base.OnReseted();
}
}
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 MovingAverageConvergenceDivergence, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class macd_fixed_psar_strategy(Strategy):
def __init__(self):
super(macd_fixed_psar_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._fast_period = self.Param("FastPeriod", 20)
self._slow_period = self.Param("SlowPeriod", 50)
self._signal_period = self.Param("SignalPeriod", 12)
self._trend_period = self.Param("TrendPeriod", 60)
self._macd_history = []
self._prev_histogram = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.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 TrendPeriod(self):
return self._trend_period.Value
@TrendPeriod.setter
def TrendPeriod(self, value):
self._trend_period.Value = value
def OnReseted(self):
super(macd_fixed_psar_strategy, self).OnReseted()
self._macd_history = []
self._prev_histogram = None
def OnStarted2(self, time):
super(macd_fixed_psar_strategy, self).OnStarted2(time)
self._macd_history = []
self._prev_histogram = None
macd = MovingAverageConvergenceDivergence()
macd.ShortMa.Length = self.FastPeriod
macd.LongMa.Length = self.SlowPeriod
trend_ema = ExponentialMovingAverage()
trend_ema.Length = self.TrendPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(macd, trend_ema, self._process_candle).Start()
def _process_candle(self, candle, macd_value, trend_value):
if candle.State != CandleStates.Finished:
return
macd_val = float(macd_value)
trend_val = float(trend_value)
close = float(candle.ClosePrice)
signal_period = self.SignalPeriod
self._macd_history.append(macd_val)
while len(self._macd_history) > signal_period:
self._macd_history.pop(0)
if len(self._macd_history) < signal_period:
return
signal = sum(self._macd_history) / signal_period
histogram = macd_val - signal
if self._prev_histogram is None:
self._prev_histogram = histogram
return
cross_up = self._prev_histogram <= 0 and histogram > 0
cross_down = self._prev_histogram >= 0 and histogram < 0
if cross_up and close > trend_val:
if self.Position <= 0:
self.BuyMarket()
elif cross_down and close < trend_val:
if self.Position >= 0:
self.SellMarket()
self._prev_histogram = histogram
def CreateClone(self):
return macd_fixed_psar_strategy()