Simple MACD
Simple MACD 策略将 MQL5 顾问 Simple_MACD.mq5 迁移到 StockSharp。算法在每根完成的 K 线后评估 MACD 主线的斜率,只要斜率保持不变就持续在该方向上加仓。
概览
- 适用市场:所有提供稳定 K 线数据的市场(外汇、股票、期货等)。
- 核心指标:MACD,使用 12/26 的 EMA 以及 9 周期的信号线。
- 交易思想:比较最近两个完成柱的 MACD 主线值。主线抬升视为动量增强,主线下降视为动量减弱。
- 下单方式:全部为市价单。每次信号都会在反向仓位的平仓量基础上再增加
TradeVolume,完全复刻原始顾问的行为。
转换说明
- MQL5 版本在新柱开始时比较
MACD(1)与MACD(2)。StockSharp 版本在柱子收盘后立即执行同样的比较,因此信号会在下一根柱开启前形成。 - 原代码通过遍历仓位并检查保证金。现在改为使用策略参数和
BuyMarket/SellMarket方法,由平台自动处理仓位与资金。 - StockSharp 使用净仓位,因此不再需要区分多头和空头持仓列表,也无需检测对冲模式。
交易规则
入场与加仓
- 每根完成的蜡烛计算一次 MACD 主线值。
- 保存最近两个主线读数。
- 若
MACD(1) > MACD(2):- 说明斜率向上。下达买单,数量为
TradeVolume + max(0, -Position),即先平掉现有空头,再追加新的多头。
- 说明斜率向上。下达买单,数量为
- 若
MACD(1) < MACD(2):- 说明斜率向下。下达卖单,数量为
TradeVolume + max(0, Position),先平掉现有多头,再追加新的空头。
- 说明斜率向下。下达卖单,数量为
- 若两个值相等,则跳过本次信号。
仓位管理
- 只要 MACD 斜率保持同一方向,每根满足条件的柱子都会再发送一次同向订单。
- 方向发生变化时,会先平仓再建立反向头寸,避免持仓方向相冲突。
- 策略没有内置止损/止盈,风险管理需通过资金控制或额外模块实现。
保护机制
- 在 MACD 指标形成之前不会进行交易。
- 仅处理状态为
CandleStates.Finished的蜡烛,避免在未完成数据上决策。 - 每次交易都会写入日志,记录触发信号的两个 MACD 值,方便回测分析。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
FastPeriod |
12 | MACD 快速 EMA 周期。 |
SlowPeriod |
26 | MACD 慢速 EMA 周期。 |
SignalPeriod |
9 | 信号线周期,保留以兼容原策略设置。 |
TradeVolume |
0.1 | 每次信号额外加仓的数量。 |
CandleType |
1 分钟 | 指标使用的蜡烛类型,可按需要调整。 |
所有参数都通过 Param() 暴露,可用于优化。
可视化
- 策略在有图表的环境中会自动创建价格区域,并绘制 MACD 指标与策略成交点。
- 通过图表可以清晰看到趋势阶段内多次加仓的节奏。
使用建议
- 更适合有明显趋势的市场环境;在震荡行情中可能频繁换向,导致交易成本上升。
- 建议结合资金管理或风险控制模块使用,以弥补缺乏硬性止损的不足。
- 可针对目标品种与周期优化 MACD 周期和
TradeVolume参数。
文件结构
CS/SimpleMacdStrategy.cs—— 策略的 C# 实现。README.md,README_ru.md,README_zh.md—— 三种语言的详细文档。
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simple MACD slope-following strategy converted from MQL5 Simple_MACD.mq5.
/// The strategy evaluates the MACD main line on completed candles and builds positions accordingly.
/// </summary>
public class SimpleMacdStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergence _macd = null!;
private decimal? _previousMacdValue;
private decimal? _prePreviousMacdValue;
private int? _previousSlope;
/// <summary>
/// Fast EMA period used for the MACD main line.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period used for the MACD main line.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Signal EMA period used by the MACD indicator.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Trading volume applied when new orders are sent.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Candle type used to feed the MACD indicator.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize Simple MACD strategy with default parameters.
/// </summary>
public SimpleMacdStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12)
.SetDisplay("MACD Fast Period", "Fast EMA length for MACD calculation", "Indicators")
.SetOptimize(6, 18, 2);
_slowPeriod = Param(nameof(SlowPeriod), 26)
.SetDisplay("MACD Slow Period", "Slow EMA length for MACD calculation", "Indicators")
.SetOptimize(20, 40, 2);
_signalPeriod = Param(nameof(SignalPeriod), 9)
.SetDisplay("MACD Signal Period", "Signal EMA length maintained for compatibility", "Indicators")
.SetOptimize(6, 18, 1);
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetDisplay("Trade Volume", "Order volume used for each signal", "Risk")
.SetOptimize(0.1m, 1m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for MACD calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousMacdValue = null;
_prePreviousMacdValue = null;
_previousSlope = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Configure MACD indicator to match the source MQL strategy settings.
_macd = new MovingAverageConvergenceDivergence
{
ShortMa = { Length = FastPeriod },
LongMa = { Length = SlowPeriod },
};
// Subscribe to candle data and bind the MACD indicator.
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, ProcessCandle)
.Start();
// Prepare visual elements when charts are available.
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
// React only to completed candles to avoid premature decisions.
if (candle.State != CandleStates.Finished)
return;
// Ensure the indicator produced a valid value.
if (!_macd.IsFormed || !macdValue.IsFinal)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var macdLine = macdValue.ToDecimal();
// Accumulate historical MACD values for slope calculations.
if (_previousMacdValue is null)
{
_previousMacdValue = macdLine;
return;
}
if (_prePreviousMacdValue is null)
{
_prePreviousMacdValue = _previousMacdValue;
_previousMacdValue = macdLine;
return;
}
var macdPrev = _previousMacdValue.Value;
var macdPrevPrev = _prePreviousMacdValue.Value;
var currentSlope = macdPrev > macdPrevPrev ? 1 : macdPrev < macdPrevPrev ? -1 : 0;
if (currentSlope == 0)
{
_prePreviousMacdValue = _previousMacdValue;
_previousMacdValue = macdLine;
return;
}
if (_previousSlope == currentSlope)
{
_prePreviousMacdValue = _previousMacdValue;
_previousMacdValue = macdLine;
return;
}
if (currentSlope > 0)
{
// Close shorts and open (or add to) longs when the MACD slope turns positive.
var volumeToBuy = TradeVolume + Math.Max(0m, -Position);
if (volumeToBuy > 0m)
{
BuyMarket(volumeToBuy);
LogInfo($"Bullish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Buying {volumeToBuy}.");
}
}
else
{
// Close longs and open (or add to) shorts when the MACD slope turns negative.
var volumeToSell = TradeVolume + Math.Max(0m, Position);
if (volumeToSell > 0m)
{
SellMarket(volumeToSell);
LogInfo($"Bearish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Selling {volumeToSell}.");
}
}
// Update stored values so the next candle compares the two previous MACD readings.
_previousSlope = currentSlope;
_prePreviousMacdValue = _previousMacdValue;
_previousMacdValue = macdLine;
}
}
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
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergence
from StockSharp.Algo.Strategies import Strategy
class simple_macd_strategy(Strategy):
def __init__(self):
super(simple_macd_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12)
self._slow_period = self.Param("SlowPeriod", 26)
self._signal_period = self.Param("SignalPeriod", 9)
self._trade_volume = self.Param("TradeVolume", 0.1)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(simple_macd_strategy, self).OnReseted()
self._prev_macd = None
self._prev_prev_macd = None
self._prev_slope = None
def OnStarted2(self, time):
super(simple_macd_strategy, self).OnStarted2(time)
self._prev_macd = None
self._prev_prev_macd = None
self._prev_slope = None
self._macd = MovingAverageConvergenceDivergence()
self._macd.ShortMa.Length = self._fast_period.Value
self._macd.LongMa.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.BindEx(self._macd, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, self._macd)
self.DrawOwnTrades(area)
def OnProcess(self, candle, macd_val):
if candle.State != CandleStates.Finished:
return
if not self._macd.IsFormed or not macd_val.IsFinal:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
macd_line = float(macd_val)
if self._prev_macd is None:
self._prev_macd = macd_line
return
if self._prev_prev_macd is None:
self._prev_prev_macd = self._prev_macd
self._prev_macd = macd_line
return
prev = self._prev_macd
prev_prev = self._prev_prev_macd
if prev > prev_prev:
current_slope = 1
elif prev < prev_prev:
current_slope = -1
else:
current_slope = 0
if current_slope == 0:
self._prev_prev_macd = self._prev_macd
self._prev_macd = macd_line
return
if self._prev_slope == current_slope:
self._prev_prev_macd = self._prev_macd
self._prev_macd = macd_line
return
trade_vol = self._trade_volume.Value
if current_slope > 0:
volume_to_buy = trade_vol + max(0, -self.Position)
if volume_to_buy > 0:
self.BuyMarket(volume_to_buy)
else:
volume_to_sell = trade_vol + max(0, self.Position)
if volume_to_sell > 0:
self.SellMarket(volume_to_sell)
self._prev_slope = current_slope
self._prev_prev_macd = self._prev_macd
self._prev_macd = macd_line
def CreateClone(self):
return simple_macd_strategy()