在 GitHub 上查看
MACD Power 策略
概览
MACD Power 是从 MetaTrader 智能交易顾问移植而来的多周期动量策略。算法在主周期上使用两条线性加权移动平均线(LWMA),结合两组 MACD 指标、高一级周期的动量过滤器以及月线级别的 MACD 趋势过滤器。当短期动量与长期趋势同时指向同一方向时,策略尝试跟随行情的强劲波动。
核心逻辑
- 典型价格 LWMA:快速与慢速 LWMA 使用典型价格 ((High + Low + Close) / 3) 计算。只有当快速 LWMA 位于慢速 LWMA 下方(与原始 EA 相同的前提条件)时才会评估交易信号。
- 双 MACD 过滤:两组 MACD
(12, 26, 1) 与 (6, 13, 1) 必须同时高于零(做多)或低于零(做空)。这正是 MQL 策略中 MacdMAIN1 与 MacdMAIN2 的判断,用于确认短期加速。
- 动量过滤器:动量指标(周期 14)在比主周期更高的周期上计算,例如主图为 15 分钟,则动量在 1 小时级别计算。最近三个动量值与 100 的绝对偏差中至少有一个需要超过阈值
MomentumBuyThreshold 或 MomentumSellThreshold。
- 月线 MACD:月线级别
(12, 26, 9) 的 MACD(对应 EA 中的 MacdMAIN0/MacdSIGNAL0)要求主线高于信号线才能做多,反之则做空,用于保证顺应长期趋势。
仓位管理
- 下单数量:
OrderVolume 控制基础下单量。如果需要反向开仓,策略会在市场单中自动加入反向持仓量,实现一次性反转。
- 止盈止损:
TakeProfitPoints 与 StopLossPoints 以品种点值表示,并通过 Security.PriceStep 转换成价格距离(当未提供步长时使用安全默认值 1)。
- 移动止损:当浮盈达到
TrailingActivationPoints 时启用,止损保持在最高价(多头)或最低价(空头)附近,间距由 TrailingOffsetPoints 控制。
- 保本保护:价格达到
BreakEvenTriggerPoints 后,策略会把保护位移动到“入场价 ± BreakEvenOffsetPoints”。如果价格回撤到该位置,立即平仓。
- 交易次数限制:
MaxTrades 用于限制本次运行内可开启的总交易次数。
参数
| 参数 |
说明 |
默认值 |
CandleType |
主周期蜡烛类型。 |
15 分钟 |
FastMaLength |
快速 LWMA 周期(典型价格)。 |
6 |
SlowMaLength |
慢速 LWMA 周期(典型价格)。 |
85 |
MomentumLength |
高周期动量的计算长度。 |
14 |
MomentumBuyThreshold |
做多时动量与 100 的最小偏差。 |
0.3 |
MomentumSellThreshold |
做空时动量与 100 的最小偏差。 |
0.3 |
TakeProfitPoints |
止盈距离(点)。 |
50 |
StopLossPoints |
止损距离(点)。 |
20 |
TrailingActivationPoints |
启动移动止损所需的盈利(点)。 |
40 |
TrailingOffsetPoints |
移动止损与极值之间的距离(点)。 |
40 |
BreakEvenTriggerPoints |
触发保本保护的盈利(点)。 |
30 |
BreakEvenOffsetPoints |
保本时与入场价的偏移(点)。 |
30 |
MaxTrades |
每次运行允许的最大交易次数。 |
10 |
OrderVolume |
基础下单量。 |
1 |
与 MQL 版本的差异
- 实现使用 StockSharp 的高级 API(
SubscribeCandles, Bind, BindEx),因此只在蜡烛收盘后处理指标,不再依赖逐笔循环。
- 原始代码中的资金止盈、权益回撤保护等功能未移植——在 StockSharp 生态中通常由独立的风险管理模块负责。策略保留了基于点值的止盈止损、移动止损和保本逻辑。
- 邮件、推送提醒以及手动修改挂单的辅助函数被移除,所有下单均通过市场指令完成。
使用建议
- 通过
CandleType 选择主周期。更高周期的动量以及月线 MACD 会根据 GetMomentumCandleType() 中的映射自动确定。
- 根据交易品种的最小变动价位调整
TakeProfitPoints、StopLossPoints 以及相关的风险参数。
- 在回测或实盘中关注
MaxTrades 限制;若需要允许更多连续建仓,可适当调大该值。
- 在图表界面启用显示时,策略会绘制主周期蜡烛与两条 LWMA,方便进行视觉分析。
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;
public class MacdPowerStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public MacdPowerStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class macd_power_strategy(Strategy):
"""
MACD Power: EMA crossover with manual SL/TP via price steps.
"""
def __init__(self):
super(macd_power_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12).SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50).SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200).SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400).SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_power_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(macd_power_strategy, self).OnStarted2(time)
self._fast_ind = ExponentialMovingAverage()
self._fast_ind.Length = self._fast_period.Value
self._slow_ind = ExponentialMovingAverage()
self._slow_ind.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast_ind, self._slow_ind, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
if not self._fast_ind.IsFormed or not self._slow_ind.IsFormed:
self._prev_fast = fast
self._prev_slow = slow
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast
self._prev_slow = slow
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self._stop_loss_points.Value > 0 and close <= self._entry_price - self._stop_loss_points.Value * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
return
if self._take_profit_points.Value > 0 and close >= self._entry_price + self._take_profit_points.Value * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
return
elif self.Position < 0 and self._entry_price > 0:
if self._stop_loss_points.Value > 0 and close >= self._entry_price + self._stop_loss_points.Value * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
return
if self._take_profit_points.Value > 0 and close <= self._entry_price - self._take_profit_points.Value * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
return
if self._prev_fast <= self._prev_slow and fast > slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast < slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return macd_power_strategy()