在 GitHub 上查看

三均线通道策略

概述

三均线通道策略 将 MetaTrader 专家顾问 3MaCross_EA 转换为 StockSharp 的高级 API。策略跟踪三条可配置的移动平均线,当较快的均线向上或向下穿越较慢的均线时开仓。可选的 Donchian 价格通道用于管理离场,对应原脚本使用的 “Price Channel” 指标。

交易逻辑

  • 做多入场:当快速和中速均线同时收盘在慢速均线上方,并且其中任意一条均线在当前柱完成向上穿越慢速均线时触发。
  • 做空入场:当快速和中速均线同时收盘在慢速均线下方,并且其中任意一条均线在当前柱完成向下穿越慢速均线时触发。
  • 离场条件
    • 出现反向交叉信号。
    • 可选 Donchian 通道止损:多头在价格跌破下轨时平仓,空头在价格突破上轨时平仓。
    • 可选固定止盈/止损,按照绝对价格距离计算。

策略仅在柱线收盘后做出决策,与原 EA 的 TradeAtCloseBar 行为一致。一次只持有一个方向的头寸,若出现反向信号,先平掉现有头寸再开新仓。

参数

名称 类型 默认值 说明
FastLength int 2 快速均线的周期。
MediumLength int 4 中速均线的周期。
SlowLength int 30 慢速均线的周期。
ChannelLength int 15 Donchian 通道的窗口长度。
FastType MovingAverageTypeEnum EMA 快速均线采用的算法(SMA、EMA、SMMA、WMA)。
MediumType MovingAverageTypeEnum EMA 中速均线采用的算法。
SlowType MovingAverageTypeEnum EMA 慢速均线采用的算法。
TakeProfit decimal 0 绝对价格单位的止盈距离,0 表示禁用。
StopLoss decimal 0 绝对价格单位的止损距离,0 表示禁用。
UseChannelStop bool true 是否启用 Donchian 通道离场。
CandleType DataType TimeSpan.FromMinutes(1).TimeFrame() 用于计算的蜡烛类型。

说明

  • 所有均线均使用收盘价,可分别配置以对应原 EA 的 FasterModeMediumModeSlowerMode
  • TakeProfitStopLoss 为绝对价差(例如外汇五位报价中 0.0010 约等于 10 点),在柱线收盘时进行判断。
  • 开启 UseChannelStop 时,策略复刻原脚本依赖 Price Channel 指标的自动止损逻辑。
  • 策略会在图表上绘制三条均线、Donchian 通道与交易标记,便于核对信号。
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>
/// Three moving average crossover strategy with channel-based exits.
/// Opens a long position when the fast and medium moving averages cross above the slow moving average and a short position when they cross below.
/// Optionally uses a Donchian channel to set trailing exit levels and supports fixed take-profit and stop-loss distances.
/// </summary>
public class ThreeMaCrossChannelStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _mediumLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _channelLength;
private readonly StrategyParam<MovingAverageTypes> _fastType;
private readonly StrategyParam<MovingAverageTypes> _mediumType;
private readonly StrategyParam<MovingAverageTypes> _slowType;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<bool> _useChannelStop;
private readonly StrategyParam<DataType> _candleType;

private DecimalLengthIndicator _fastMa = null!;
private DecimalLengthIndicator _mediumMa = null!;
private DecimalLengthIndicator _slowMa = null!;
private DonchianChannels _channel = null!;

private bool? _prevFastAboveSlow;
private bool? _prevMediumAboveSlow;
private decimal? _entryPrice;

/// <summary>
/// Length of the fast moving average.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}

/// <summary>
/// Length of the medium moving average.
/// </summary>
public int MediumLength
{
get => _mediumLength.Value;
set => _mediumLength.Value = value;
}

/// <summary>
/// Length of the slow moving average.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}

/// <summary>
/// Period used for the Donchian channel.
/// </summary>
public int ChannelLength
{
get => _channelLength.Value;
set => _channelLength.Value = value;
}

/// <summary>
/// Moving-average type applied to the fast average.
/// </summary>
public MovingAverageTypes FastType
{
get => _fastType.Value;
set => _fastType.Value = value;
}

/// <summary>
/// Moving-average type applied to the medium average.
/// </summary>
public MovingAverageTypes MediumType
{
get => _mediumType.Value;
set => _mediumType.Value = value;
}

/// <summary>
/// Moving-average type applied to the slow average.
/// </summary>
public MovingAverageTypes SlowType
{
get => _slowType.Value;
set => _slowType.Value = value;
}

/// <summary>
/// Take-profit distance measured in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}

/// <summary>
/// Stop-loss distance measured in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}

/// <summary>
/// Enables exits based on Donchian channel boundaries.
/// </summary>
public bool UseChannelStop
{
get => _useChannelStop.Value;
set => _useChannelStop.Value = value;
}

/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}

/// <summary>
/// Initializes a new instance of the <see cref="ThreeMaCrossChannelStrategy"/> class.
/// </summary>
public ThreeMaCrossChannelStrategy()
{
_fastLength = Param(nameof(FastLength), 2)
.SetGreaterThanZero()
.SetDisplay("Fast MA", "Length of the fast moving average", "Moving Averages");

_mediumLength = Param(nameof(MediumLength), 4)
.SetGreaterThanZero()
.SetDisplay("Medium MA", "Length of the medium moving average", "Moving Averages");

_slowLength = Param(nameof(SlowLength), 30)
.SetGreaterThanZero()
.SetDisplay("Slow MA", "Length of the slow moving average", "Moving Averages");

_channelLength = Param(nameof(ChannelLength), 15)
.SetGreaterThanZero()
.SetDisplay("Channel", "Donchian channel lookback period", "Risk Management");

_fastType = Param(nameof(FastType), MovingAverageTypes.EMA)
.SetDisplay("Fast MA Type", "Algorithm used for the fast average", "Moving Averages");

_mediumType = Param(nameof(MediumType), MovingAverageTypes.EMA)
.SetDisplay("Medium MA Type", "Algorithm used for the medium average", "Moving Averages");

_slowType = Param(nameof(SlowType), MovingAverageTypes.EMA)
.SetDisplay("Slow MA Type", "Algorithm used for the slow average", "Moving Averages");

_takeProfit = Param(nameof(TakeProfit), 0m)
.SetDisplay("Take Profit", "Distance to close profitable trades", "Risk Management")

.SetOptimize(0m, 3m, 0.1m);

_stopLoss = Param(nameof(StopLoss), 0m)
.SetDisplay("Stop Loss", "Distance to limit losses", "Risk Management")

.SetOptimize(0m, 3m, 0.1m);

_useChannelStop = Param(nameof(UseChannelStop), true)
.SetDisplay("Channel Exit", "Use Donchian channel boundaries for exits", "Risk Management");

_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used by the strategy", "General");
}

/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}

/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFastAboveSlow = null;
_prevMediumAboveSlow = null;
_entryPrice = null;
}

/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);

_fastMa = CreateMovingAverage(FastType, FastLength);
_mediumMa = CreateMovingAverage(MediumType, MediumLength);
_slowMa = CreateMovingAverage(SlowType, SlowLength);
_channel = new DonchianChannels { Length = ChannelLength };

var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_fastMa, _mediumMa, _slowMa, _channel, ProcessCandle)
.Start();

var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _mediumMa);
DrawIndicator(area, _slowMa);
DrawIndicator(area, _channel);
DrawOwnTrades(area);
}

StartProtection(null, null);
}

private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastVal, IIndicatorValue mediumVal, IIndicatorValue slowVal, IIndicatorValue channelVal)
{
if (candle.State != CandleStates.Finished)
return;

if (!_fastMa.IsFormed || !_mediumMa.IsFormed || !_slowMa.IsFormed)
return;

var fastValue = fastVal.ToDecimal();
var mediumValue = mediumVal.ToDecimal();
var slowValue = slowVal.ToDecimal();

decimal upperBand = 0m, lowerBand = 0m;
if (channelVal is IDonchianChannelsValue dcVal)
{
upperBand = dcVal.UpperBand ?? 0m;
lowerBand = dcVal.LowerBand ?? 0m;
}

var fastAbove = fastValue > slowValue;
var mediumAbove = mediumValue > slowValue;

var fastCrossUp = _prevFastAboveSlow.HasValue && !_prevFastAboveSlow.Value && fastAbove;
var fastCrossDown = _prevFastAboveSlow.HasValue && _prevFastAboveSlow.Value && !fastAbove;
var mediumCrossUp = _prevMediumAboveSlow.HasValue && !_prevMediumAboveSlow.Value && mediumAbove;
var mediumCrossDown = _prevMediumAboveSlow.HasValue && _prevMediumAboveSlow.Value && !mediumAbove;

_prevFastAboveSlow = fastAbove;
_prevMediumAboveSlow = mediumAbove;

if (!IsFormedAndOnlineAndAllowTrading())
return;

var buySignal = fastAbove && mediumAbove && (fastCrossUp || mediumCrossUp);
var sellSignal = !fastAbove && !mediumAbove && (fastCrossDown || mediumCrossDown);

if (Position > 0)
{
var shouldExit = sellSignal;

if (!shouldExit && _entryPrice.HasValue)
{
if (TakeProfit > 0m && candle.ClosePrice - _entryPrice.Value >= TakeProfit)
shouldExit = true;
if (StopLoss > 0m && _entryPrice.Value - candle.ClosePrice >= StopLoss)
shouldExit = true;
}

if (!shouldExit && UseChannelStop && candle.ClosePrice <= lowerBand)
shouldExit = true;

if (shouldExit)
{
SellMarket(Position);
_entryPrice = null;
return;
}
}
else if (Position < 0)
{
var shouldExit = buySignal;

if (!shouldExit && _entryPrice.HasValue)
{
if (TakeProfit > 0m && _entryPrice.Value - candle.ClosePrice >= TakeProfit)
shouldExit = true;
if (StopLoss > 0m && candle.ClosePrice - _entryPrice.Value >= StopLoss)
shouldExit = true;
}

if (!shouldExit && UseChannelStop && candle.ClosePrice >= upperBand)
shouldExit = true;

if (shouldExit)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
return;
}
}

if (buySignal && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
}
else if (sellSignal && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_entryPrice = candle.ClosePrice;
}
}

private static DecimalLengthIndicator CreateMovingAverage(MovingAverageTypes type, int length)
{
return type switch
{
MovingAverageTypes.SMA => new SMA { Length = length },
MovingAverageTypes.EMA => new EMA { Length = length },
MovingAverageTypes.SMMA => new SmoothedMovingAverage { Length = length },
MovingAverageTypes.WMA => new WeightedMovingAverage { Length = length },
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unsupported moving average type."),
};
}

/// <summary>
/// Moving-average algorithms supported by the strategy.
/// Matches the modes available in the original MetaTrader script.
/// </summary>
public enum MovingAverageTypes
{
/// <summary>
/// Simple moving average.
/// </summary>
SMA,

/// <summary>
/// Exponential moving average.
/// </summary>
EMA,

/// <summary>
/// Smoothed moving average.
/// </summary>
SMMA,

/// <summary>
/// Linear weighted moving average.
/// </summary>
WMA,
}
}