Ver en GitHub

Estrategia del revendedor AK-47

Esta estrategia es una conversión del asesor experto MetaTrader 5 "AK-47 Scalper EA" (compilación 44883). Recrea el comportamiento original dentro del marco estratégico de alto nivel StockSharp.

El algoritmo mantiene activa una única orden de parada de venta durante las horas de negociación permitidas. Una vez que se activa la orden, la estrategia adjunta inmediatamente órdenes protectoras de limitación de pérdidas y toma de ganancias. Tanto el precio de la orden pendiente como el stop de protección se ajustan dinámicamente a medida que se mueve el mercado.

Lógica principal

  1. Calcule el tamaño del pip a partir del tamaño del tick del instrumento (los símbolos de 5 dígitos usan pasos de 0,1 pip como en MetaTrader).
  2. Determinar la ventana de negociación. Cuando el filtro de tiempo está habilitado, se permiten entradas solo entre las horas de inicio y finalización configuradas (incluido el inicio, excluyendo el final). Las sesiones nocturnas se apoyan en finalizar alrededor de la medianoche.
  3. Asegúrese de que el diferencial actual en puntos no exceda el límite configurado antes de realizar nuevos pedidos.
  4. Dimensione la posición:
    • Utilice el lote fijo (parámetro Base Lot) o
    • Convierta el Risk Percent configurado del valor de la cartera en lotes (imitando la fórmula MT5) y alinéelo con las restricciones de volumen de intercambio.
  5. Coloque una orden de parada de venta SL/2 pips por debajo de la oferta. La parada de protección está prevista SL/2 pips por encima de la demanda y la toma de ganancias se sitúa TP pips por debajo de la entrada.
  6. Mientras la orden está pendiente, la estrategia la vuelve a registrar continuamente para mantener la brecha de SL/2 pips con respecto a la oferta y actualiza los precios de protección planificados.
  7. Después de la ejecución:
    • Registre una orden buy-stop-stop-loss y una orden buy-limit-take-profit utilizando los precios planificados.
    • En cada cierre de vela, la estrategia sigue el stop manteniéndolo exactamente SL pips por encima de la oferta actual (sin aflojarla nunca).
    • El precio de obtención de beneficios permanece fijo una vez establecido.
  8. Si la posición es plana, todas las órdenes de protección se cancelan y puede comenzar un nuevo ciclo.

Parámetros

Parámetro Descripción
Porcentaje de riesgo de uso Cambie entre lotes fijos y dimensionamiento basado en acciones.
Porcentaje de riesgo Porcentaje aplicado al valor de la cartera al calcular el volumen comercial.
Lote base Tamaño de lote fijo y paso de redondeo para el tamaño de posición.
Detener pérdidas (pips) Distancia entre el precio de entrada y el stop de protección. La compensación de la orden pendiente utiliza la mitad de esta distancia.
Obtener ganancias (pips) Distancia objetivo de beneficio. Establezca en cero para desactivar el objetivo.
Difusión máxima (puntos) Spread máximo permitido (en MetaTrader puntos) para ingresar al mercado.
Usar filtro de tiempo Habilite o deshabilite la restricción de la ventana de negociación.
Hora de inicio/minuto Inicio de la ventana de negociación.
Fin Hora/Minuto Fin de la ventana de negociación.
Tipo de vela Suscripción de vela utilizada para actualizaciones de tiempos y precios.

Notas

  • La estrategia utiliza sólo entradas cortas como el EA original.
  • El seguimiento se realiza en la vela cercana para permanecer sincronizado con la API de alto nivel de StockSharp.
  • Las órdenes de protección se reemplazan mediante llamadas ReRegisterOrder, por lo que el intercambio o el simulador deben admitir el reemplazo de órdenes.
  • Los comentarios gráficos originales de MetaTrader no se reproducen porque las estrategias de StockSharp se basan en el registro en lugar de en los comentarios del terminal.
using System;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "AK-47 Scalper" MetaTrader expert.
/// Sells when price breaks below the low of the previous N candles (breakout scalp),
/// buys when price breaks above the high. Uses ATR for stop distance management.
/// </summary>
public class Ak47ScalperStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrStopMultiplier;

	private AverageTrueRange _atr;
	private decimal _highestHigh;
	private decimal _lowestLow;
	private int _barsCollected;
	private decimal? _entryPrice;
	private Sides? _entrySide;
	private decimal _stopDistance;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int LookbackPeriod
	{
		get => _lookbackPeriod.Value;
		set => _lookbackPeriod.Value = value;
	}

	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	public decimal AtrStopMultiplier
	{
		get => _atrStopMultiplier.Value;
		set => _atrStopMultiplier.Value = value;
	}

	public Ak47ScalperStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_lookbackPeriod = Param(nameof(LookbackPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Lookback", "Number of bars for high/low channel", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for stop distance", "Indicators");

		_atrStopMultiplier = Param(nameof(AtrStopMultiplier), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Stop Mult", "ATR multiplier for stop distance", "Risk");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_atr = new AverageTrueRange { Length = AtrPeriod };
		_highestHigh = 0;
		_lowestLow = decimal.MaxValue;
		_barsCollected = 0;
		_entryPrice = null;
		_entrySide = null;
		_stopDistance = 0;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _atr);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!atrValue.IsFinal)
			return;

		var atrDecimal = atrValue.IsEmpty ? 0m : atrValue.GetValue<decimal>();

		// Build lookback channel
		if (_barsCollected < LookbackPeriod)
		{
			if (candle.HighPrice > _highestHigh)
				_highestHigh = candle.HighPrice;
			if (candle.LowPrice < _lowestLow)
				_lowestLow = candle.LowPrice;
			_barsCollected++;
			return;
		}

		if (!_atr.IsFormed)
		{
			// Keep updating channel
			UpdateChannel(candle);
			return;
		}

		var close = candle.ClosePrice;
		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		_stopDistance = atrDecimal * AtrStopMultiplier;

		// Check stop loss on existing position
		if (_entryPrice != null && _entrySide != null)
		{
			if (_entrySide == Sides.Buy && close <= _entryPrice.Value - _stopDistance)
			{
				SellMarket(Math.Abs(Position));
				_entryPrice = null;
				_entrySide = null;
			}
			else if (_entrySide == Sides.Sell && close >= _entryPrice.Value + _stopDistance)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
				_entrySide = null;
			}
			// Take profit at 2x ATR
			else if (_entrySide == Sides.Buy && close >= _entryPrice.Value + _stopDistance * 1.5m)
			{
				SellMarket(Math.Abs(Position));
				_entryPrice = null;
				_entrySide = null;
			}
			else if (_entrySide == Sides.Sell && close <= _entryPrice.Value - _stopDistance * 1.5m)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
				_entrySide = null;
			}
		}

		// Entry signals: breakout
		if (Position == 0)
		{
			if (close > _highestHigh)
			{
				BuyMarket(volume);
				_entryPrice = close;
				_entrySide = Sides.Buy;
			}
			else if (close < _lowestLow)
			{
				SellMarket(volume);
				_entryPrice = close;
				_entrySide = Sides.Sell;
			}
		}

		UpdateChannel(candle);
	}

	private void UpdateChannel(ICandleMessage candle)
	{
		// Simple rolling update - reset and let it rebuild
		// For simplicity, just use last candle's high/low as reference shifted
		if (candle.HighPrice > _highestHigh)
			_highestHigh = candle.HighPrice;
		else
			_highestHigh = _highestHigh * 0.999m + candle.HighPrice * 0.001m; // slow decay

		if (candle.LowPrice < _lowestLow)
			_lowestLow = candle.LowPrice;
		else
			_lowestLow = _lowestLow * 0.999m + candle.LowPrice * 0.001m; // slow decay
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_atr = null;
		_highestHigh = 0;
		_lowestLow = decimal.MaxValue;
		_barsCollected = 0;
		_entryPrice = null;
		_entrySide = null;
		_stopDistance = 0;

		base.OnReseted();
	}
}