Ver en GitHub

Estrategia de tres canales cruzados MA

Descripción general

La Estrategia de tres canales cruzados MA convierte el Expert Advisor de MetaTrader 3MaCross_EA en la API de alto nivel de StockSharp. Monitorea tres promedios móviles configurables y abre operaciones cuando los promedios más rápidos cruzan el más lento. Opcionalmente se utiliza un canal de precios Donchian para gestionar las salidas, imitando fielmente el EA original que hacía referencia al indicador "Canal de precios".

Lógica de trading

  • Entrada larga: Se genera cuando las medias móviles rápida y media cierran por encima de la media móvil lenta y cualquiera de las dos medias móviles más rápidas cruza por encima de la lenta en la barra actual.
  • Entrada corta: Se activa cuando los promedios móviles rápido y medio cierran por debajo del promedio móvil lento y cualquiera de los dos promedios móviles más rápidos cruza por debajo del lento.
  • Posición de salida:
    • Señal de cruce opuesta.
    • Parada de canal Donchian opcional: las posiciones largas se cierran si el precio cae por debajo de la banda inferior; Las posiciones cortas se cierran si el precio sube por encima de la banda superior.
    • Distancias fijas opcionales de toma de ganancias o stop loss medidas en unidades de precio absoluto.

La estrategia siempre espera a que se completen las velas, coincidiendo con el comportamiento TradeAtCloseBar del script original. Sólo se mantiene una posición direccional a la vez; cuando aparece una señal contra una posición existente, la operación actual se cierra antes de que se abra una nueva.

Parámetros

Nombre Tipo Predeterminado Descripción
FastLength int 2 Mirando hacia atrás para ver la media móvil rápida.
MediumLength int 4 Mirando hacia atrás para la media móvil media.
SlowLength int 30 Mirando hacia atrás para ver el promedio móvil lento.
ChannelLength int 15 Donchian ventana de canal utilizada para salidas basadas en canales.
FastType MovingAverageTypeEnum EMA Algoritmo de media móvil aplicado al promedio rápido (SMA, EMA, SMMA, WMA).
MediumType MovingAverageTypeEnum EMA Algoritmo de media móvil aplicado a la media media.
SlowType MovingAverageTypeEnum EMA Algoritmo de media móvil aplicado a la media lenta.
TakeProfit decimal 0 Objetivo de beneficio en unidades de precio absoluto. Establezca en 0 para desactivar.
StopLoss decimal 0 Límite de pérdida en unidades de precio absoluto. Establezca en 0 para desactivar.
UseChannelStop bool true Habilita las salidas de canal Donchian.
CandleType DataType TimeSpan.FromMinutes(1).TimeFrame() Tipo de vela utilizada para los cálculos.

Notas

  • Todos los promedios móviles utilizan precios de cierre y se pueden configurar individualmente para que coincidan con las opciones FasterMode, MediumMode y SlowerMode del EA original.
  • TakeProfit y StopLoss utilizan distancias de precios absolutas (por ejemplo, 0.0010 corresponde a 10 pips en un símbolo Forex de 5 dígitos). Se evalúan en el cierre de velas, replicando la gestión de cierre de barras del EA.
  • Cuando UseChannelStop está habilitado, la estrategia reproduce el comportamiento automático de stop-loss que se basaba en el indicador personalizado Price Channel.
  • La estrategia dibuja los tres promedios móviles, el canal Donchian y los marcadores comerciales en el gráfico para confirmación visual.
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,
}
}