MACD Stochastic 2 策略
该策略使用 StockSharp 高级 API 复刻 MetaTrader 中的“MACD Stochastic 2”专家逻辑。通过 MACD 主线的三根 K 线形态与随机指标 Stochastic 结合,识别接近超卖或超买区域的动量反转。同时为多空方向分别设置止损、止盈,并提供可选的点(pip)单位追踪止损。
概览
- 通过参数
CandleType指定任意品种和时间框架。 - 仅依据 MACD 主线判断局部高低点,信号线和柱状图可用于图表展示。
- 入场时要求 Stochastic %K 低于 20(做多)或高于 80(做空)。
- 点值计算遵循原版 EA:取合约的
PriceStep,若价格精度为 3 或 5 位小数则额外乘以 10。
交易逻辑
多头入场
- 当前及前两根已完成 K 线的 MACD 主线值全部小于 0。
- 当前 MACD 值大于上一根,上一根小于前两根(形成局部底部)。
- Stochastic %K 低于 20(超卖)。
- 若存在空头仓位则先平仓,随后在
Position <= 0时开多。
空头入场
- 当前及前两根已完成 K 线的 MACD 主线值全部大于 0。
- 当前 MACD 值小于上一根,上一根大于前两根(形成局部顶部)。
- Stochastic %K 高于 80(超买)。
- 若存在多头仓位则先平仓,随后在
Position >= 0时开空。
仓位管理
- 固定止损 / 止盈: 多空方向分别配置点数距离,设置为 0 可关闭对应防护。
- 追踪止损: 启用后,当价格推进超过设定距离时生效;只有当盈利超过追踪步长时才上调/下调止损,减少频繁修改。
- 反向信号: 出现反向条件时先平掉当前仓位,再用设定手数开立新的反向仓位。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
TradeVolume |
1 |
新仓下单量。 |
StopLossBuyPips |
50 |
多头止损点数(0 表示关闭)。 |
StopLossSellPips |
50 |
空头止损点数(0 表示关闭)。 |
TakeProfitBuyPips |
50 |
多头止盈点数(0 表示关闭)。 |
TakeProfitSellPips |
50 |
空头止盈点数(0 表示关闭)。 |
TrailingStopPips |
0 |
追踪止损距离(点)。0 表示禁用。 |
TrailingStepPips |
5 |
更新追踪止损所需的最小盈利点数;启用追踪时必须为正。 |
MacdFastPeriod |
12 |
MACD 快速 EMA 长度。 |
MacdSlowPeriod |
26 |
MACD 慢速 EMA 长度。 |
MacdSignalPeriod |
9 |
MACD 信号线平滑周期。 |
StochasticKPeriod |
5 |
Stochastic %K 回溯周期。 |
StochasticDPeriod |
3 |
Stochastic %D 平滑周期。 |
StochasticSlowing |
3 |
Stochastic %K 额外平滑长度。 |
CandleType |
1 小时周期 |
指标计算所用的 K 线类型(时间框架)。 |
说明
- Pip 计算方式:
pip = PriceStep,若报价精度为 3 或 5 位小数则乘以 10,与原始脚本保持一致。 - Stochastic 阈值 20/80 在代码中写成常量,需要自定义时可直接修改源代码。
- 策略仅在完整收盘的 K 线上做出决策,行为与 MetaTrader 的收盘执行一致。
使用步骤
- 启动前配置交易标的、
CandleType和下单量。 - 根据波动率调整止损、止盈和追踪止损参数。
- 需要优化时,可借助 StockSharp 优化器搜索 MACD 与 Stochastic 参数组合。
- 若界面存在图表区域,策略会自动绘制 K 线、MACD、Stochastic 以及成交标记,便于监控。
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>
/// MACD and stochastic based swing strategy.
/// Buys when MACD turns up in negative zone + stochastic is oversold.
/// Sells when MACD turns down in positive zone + stochastic is overbought.
/// </summary>
public class MacdStochastic2Strategy : Strategy
{
private readonly StrategyParam<decimal> _oversoldThreshold;
private readonly StrategyParam<decimal> _overboughtThreshold;
private readonly StrategyParam<int> _macdFastPeriod;
private readonly StrategyParam<int> _macdSlowPeriod;
private readonly StrategyParam<int> _stochasticKPeriod;
private readonly StrategyParam<int> _stochasticDPeriod;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<DataType> _candleType;
private decimal _macdPrev1;
private decimal _macdPrev2;
private int _macdCount;
public decimal OversoldThreshold { get => _oversoldThreshold.Value; set => _oversoldThreshold.Value = value; }
public decimal OverboughtThreshold { get => _overboughtThreshold.Value; set => _overboughtThreshold.Value = value; }
public int MacdFastPeriod { get => _macdFastPeriod.Value; set => _macdFastPeriod.Value = value; }
public int MacdSlowPeriod { get => _macdSlowPeriod.Value; set => _macdSlowPeriod.Value = value; }
public int StochasticKPeriod { get => _stochasticKPeriod.Value; set => _stochasticKPeriod.Value = value; }
public int StochasticDPeriod { get => _stochasticDPeriod.Value; set => _stochasticDPeriod.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MacdStochastic2Strategy()
{
_oversoldThreshold = Param(nameof(OversoldThreshold), 20m)
.SetDisplay("Oversold", "Stochastic oversold threshold", "Stochastic");
_overboughtThreshold = Param(nameof(OverboughtThreshold), 80m)
.SetDisplay("Overbought", "Stochastic overbought threshold", "Stochastic");
_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA length for MACD", "MACD");
_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA length for MACD", "MACD");
_stochasticKPeriod = Param(nameof(StochasticKPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Stochastic K", "Lookback for %K", "Stochastic");
_stochasticDPeriod = Param(nameof(StochasticDPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic D", "Smoothing for %D", "Stochastic");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit in price units", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macdPrev1 = 0m;
_macdPrev2 = 0m;
_macdCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var macd = new MovingAverageConvergenceDivergence(
new ExponentialMovingAverage { Length = MacdSlowPeriod },
new ExponentialMovingAverage { Length = MacdFastPeriod });
var rsi = new RelativeStrengthIndex { Length = StochasticKPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, (candle, rsiValue) =>
{
if (candle.State != CandleStates.Finished)
return;
// Process MACD manually
var macdResult = macd.Process(candle.ClosePrice, candle.CloseTime, true);
if (!macd.IsFormed)
return;
var macdValue = macdResult.ToDecimal();
_macdCount++;
if (_macdCount < 3)
{
_macdPrev2 = _macdPrev1;
_macdPrev1 = macdValue;
return;
}
var macd0 = macdValue;
var macd1 = _macdPrev1;
var macd2 = _macdPrev2;
// Buy: MACD in negative zone turning up + stochastic oversold
var longSignal = macd0 < 0m && macd1 < 0m && macd2 < 0m &&
macd0 > macd1 && macd1 < macd2 &&
rsiValue < OversoldThreshold;
// Sell: MACD in positive zone turning down + stochastic overbought
var shortSignal = macd0 > 0m && macd1 > 0m && macd2 > 0m &&
macd0 < macd1 && macd1 > macd2 &&
rsiValue > OverboughtThreshold;
if (longSignal && Position <= 0)
BuyMarket();
else if (shortSignal && Position >= 0)
SellMarket();
_macdPrev2 = _macdPrev1;
_macdPrev1 = macdValue;
})
.Start();
StartProtection(
new Unit(TakeProfit, UnitTypes.Absolute),
new Unit(StopLoss, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import (
RelativeStrengthIndex,
MovingAverageConvergenceDivergence,
ExponentialMovingAverage,
)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class macd_stochastic2_strategy(Strategy):
def __init__(self):
super(macd_stochastic2_strategy, self).__init__()
self._oversold_threshold = self.Param("OversoldThreshold", 20.0)
self._overbought_threshold = self.Param("OverboughtThreshold", 80.0)
self._macd_fast_period = self.Param("MacdFastPeriod", 12)
self._macd_slow_period = self.Param("MacdSlowPeriod", 26)
self._stochastic_k_period = self.Param("StochasticKPeriod", 5)
self._stochastic_d_period = self.Param("StochasticDPeriod", 3)
self._stop_loss = self.Param("StopLoss", 1000.0)
self._take_profit = self.Param("TakeProfit", 2000.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._macd_prev1 = 0.0
self._macd_prev2 = 0.0
self._macd_count = 0
self._macd_ind = None
@property
def OversoldThreshold(self):
return self._oversold_threshold.Value
@OversoldThreshold.setter
def OversoldThreshold(self, value):
self._oversold_threshold.Value = value
@property
def OverboughtThreshold(self):
return self._overbought_threshold.Value
@OverboughtThreshold.setter
def OverboughtThreshold(self, value):
self._overbought_threshold.Value = value
@property
def MacdFastPeriod(self):
return self._macd_fast_period.Value
@MacdFastPeriod.setter
def MacdFastPeriod(self, value):
self._macd_fast_period.Value = value
@property
def MacdSlowPeriod(self):
return self._macd_slow_period.Value
@MacdSlowPeriod.setter
def MacdSlowPeriod(self, value):
self._macd_slow_period.Value = value
@property
def StochasticKPeriod(self):
return self._stochastic_k_period.Value
@StochasticKPeriod.setter
def StochasticKPeriod(self, value):
self._stochastic_k_period.Value = value
@property
def StochasticDPeriod(self):
return self._stochastic_d_period.Value
@StochasticDPeriod.setter
def StochasticDPeriod(self, value):
self._stochastic_d_period.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(macd_stochastic2_strategy, self).OnStarted2(time)
self._macd_prev1 = 0.0
self._macd_prev2 = 0.0
self._macd_count = 0
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.MacdSlowPeriod
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.MacdFastPeriod
self._macd_ind = MovingAverageConvergenceDivergence(slow_ema, fast_ema)
rsi = RelativeStrengthIndex()
rsi.Length = self.StochasticKPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self.ProcessCandle).Start()
self.StartProtection(
Unit(self.TakeProfit, UnitTypes.Absolute),
Unit(self.StopLoss, UnitTypes.Absolute))
def ProcessCandle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
macd_result = process_float(self._macd_ind, candle.ClosePrice, candle.OpenTime, True)
if not self._macd_ind.IsFormed:
return
macd_value = float(macd_result)
rsi_val = float(rsi_value)
self._macd_count += 1
if self._macd_count < 3:
self._macd_prev2 = self._macd_prev1
self._macd_prev1 = macd_value
return
macd0 = macd_value
macd1 = self._macd_prev1
macd2 = self._macd_prev2
long_signal = (macd0 < 0.0 and macd1 < 0.0 and macd2 < 0.0
and macd0 > macd1 and macd1 < macd2
and rsi_val < float(self.OversoldThreshold))
short_signal = (macd0 > 0.0 and macd1 > 0.0 and macd2 > 0.0
and macd0 < macd1 and macd1 > macd2
and rsi_val > float(self.OverboughtThreshold))
if long_signal and self.Position <= 0:
self.BuyMarket()
elif short_signal and self.Position >= 0:
self.SellMarket()
self._macd_prev2 = self._macd_prev1
self._macd_prev1 = macd_value
def OnReseted(self):
super(macd_stochastic2_strategy, self).OnReseted()
self._macd_prev1 = 0.0
self._macd_prev2 = 0.0
self._macd_count = 0
def CreateClone(self):
return macd_stochastic2_strategy()