Ver en GitHub

Estrategia de Executer AC

La Estrategia de Executer AC es un port fiel de StockSharp del asesor experto MetaTrader 5 "Executer AC". El EA original opera sobre el Accelerator Oscillator (AC) desarrollado por Bill Williams y combina sus oscilaciones de momentum con un marco fijo de stop/límite y un módulo de trailing stop. Esta conversión mantiene el comportamiento de la versión MQL5 al tiempo que expone parámetros fáciles de usar que se integran con la API de alto nivel de StockSharp.

Lógica de trading

La estrategia opera con velas completadas del marco temporal seleccionado y se basa en los últimos cuatro valores del Accelerator Oscillator:

  • AC[0] – barra completada más reciente (llamada ac[1] en el código original).
  • AC[1], AC[2], AC[3] – valores progresivamente más antiguos usados para la detección de patrones.

El árbol de decisiones es idéntico al EA:

  1. Gestión de posición
    • Las posiciones largas se cierran cuando AC[0] < AC[1] (momentum decreciente).
    • Las posiciones cortas se cierran cuando AC[0] > AC[1] (momentum creciente).
    • Una rutina de trailing stop ajusta dinámicamente el stop protector una vez que el precio supera la distancia configurada más el paso de trailing.
  2. Reglas de entrada cuando está plano
    • Aceleración alcista por encima de cero: si AC[0] > 0 y AC[1] > 0 y AC[0] > AC[1] > AC[2], se emite una compra de mercado.
    • Aceleración bajista por encima de cero: si AC[0] > 0 y AC[1] > 0 y AC[0] < AC[1] < AC[2] < AC[3], se emite una venta de mercado.
    • Aceleración alcista por debajo de cero: si AC[0] < 0 y AC[1] < 0 y AC[0] > AC[1] > AC[2] > AC[3], se emite una compra de mercado.
    • Aceleración bajista por debajo de cero: si AC[0] < 0 y AC[1] < 0 y AC[0] < AC[1] < AC[2], se emite una venta de mercado.
    • Cruces de la línea cero: un cruce descendente (AC[0] > 0 y AC[1] < 0) activa una compra, mientras que un cruce ascendente (AC[0] < 0 y AC[1] > 0) activa una venta.

Las señales se evalúan solo después de confirmar que las velas están completadas, los valores del indicador están formados y el trading está habilitado.

Gestión de riesgos

  • Stop-loss y take-profit: distancias configurables (en pips) convertidas a unidades de precio usando el paso del instrumento. Los stops se recalculan en cada nueva entrada y permanecen sin cambios hasta que se alcancen o sean reemplazados por el trailing stop.
  • Trailing stop: replica la lógica del EA. Cuando el beneficio no realizado supera TrailingStop + TrailingStep (ambos en pips), el precio del stop se mueve a Close - TrailingStop para posiciones largas y Close + TrailingStop para posiciones cortas, exigiendo la mejora requerida antes de cada paso.
  • Protección de posición: se invoca el helper integrado StartProtection() para que StockSharp proteja contra desconexiones inesperadas.

Parámetros

Parámetro Descripción
TradeVolume Volumen de orden usado para todas las entradas de mercado. Se normaliza según el paso de volumen y los límites del instrumento.
StopLossPips Distancia del stop-loss en pips. Un valor de cero deshabilita el stop-loss.
TakeProfitPips Distancia del take-profit en pips. Un valor de cero deshabilita el take-profit.
TrailingStopPips Distancia del trailing stop en pips. Establezca en cero para deshabilitar el trailing.
TrailingStepPips Beneficio adicional mínimo (en pips) requerido antes de mover el trailing stop nuevamente.
CandleType Marco temporal de las velas usadas para calcular el Accelerator Oscillator.

Notas de implementación

  • La normalización de precios respeta tanto el tamaño del tick de la bolsa como los símbolos Forex de tres/cinco dígitos multiplicando el tamaño del punto por diez cuando corresponde.
  • El historial del indicador se mantiene en un buffer de tamaño fijo para replicar las comparaciones originales ac[1] … ac[4] sin recurrir a colecciones costosas o consultas de historial.
  • La estrategia siempre sale antes de evaluar nuevas entradas en la misma vela, coincidiendo con el flujo de control del EA MQL5 donde las instrucciones return previenen la re-entrada inmediata.
  • Los valores del trailing stop actualizan tanto el estado de trailing interno como el precio efectivo del stop usado para las comprobaciones de stop-loss, asegurando consistencia con el comportamiento PositionModify del EA.

Consejos de uso

  1. Elija un marco temporal de velas que coincida con el mercado que opera (el script original se usaba comúnmente en gráficos Forex intradía).
  2. Ajuste las distancias de stop-loss, take-profit y trailing a la volatilidad del instrumento elegido; valores extremadamente ajustados pueden provocar whipsaws frecuentes.
  3. Habilite controles de riesgo en el lado del broker conectado cuando sea posible, ya que la estrategia depende de salidas del lado del software.
  4. Combine con gestión de dinero a nivel de cartera si pretende ejecutar múltiples estrategias simultáneamente.
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;

/// <summary>
/// Executer AC strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ExecuterAcStrategy : 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 ExecuterAcStrategy()
	{
		_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;
	}
}