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
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).
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.
Asegúrese de que el diferencial actual en puntos no exceda el límite configurado antes de realizar nuevos pedidos.
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.
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.
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.
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.
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();
}
}
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
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class ak47_scalper_strategy(Strategy):
def __init__(self):
super(ak47_scalper_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._lookback_period = self.Param("LookbackPeriod", 5)
self._atr_period = self.Param("AtrPeriod", 14)
self._atr_stop_multiplier = self.Param("AtrStopMultiplier", 1.5)
self._highest_high = 0.0
self._lowest_low = float('inf')
self._bars_collected = 0
self._entry_price = None
self._entry_side = None
self._stop_distance = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def LookbackPeriod(self):
return self._lookback_period.Value
@LookbackPeriod.setter
def LookbackPeriod(self, value):
self._lookback_period.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def AtrStopMultiplier(self):
return self._atr_stop_multiplier.Value
@AtrStopMultiplier.setter
def AtrStopMultiplier(self, value):
self._atr_stop_multiplier.Value = value
def OnReseted(self):
super(ak47_scalper_strategy, self).OnReseted()
self._highest_high = 0.0
self._lowest_low = float('inf')
self._bars_collected = 0
self._entry_price = None
self._entry_side = None
self._stop_distance = 0.0
def OnStarted2(self, time):
super(ak47_scalper_strategy, self).OnStarted2(time)
self._highest_high = 0.0
self._lowest_low = float('inf')
self._bars_collected = 0
self._entry_price = None
self._entry_side = None
self._stop_distance = 0.0
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr_value)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
lookback = self.LookbackPeriod
# Build lookback channel
if self._bars_collected < lookback:
if high > self._highest_high:
self._highest_high = high
if low < self._lowest_low:
self._lowest_low = low
self._bars_collected += 1
return
atr_stop_mult = float(self.AtrStopMultiplier)
self._stop_distance = atr_val * atr_stop_mult
# Check stop loss / take profit on existing position
if self._entry_price is not None and self._entry_side is not None:
if self._entry_side == "buy" and close <= self._entry_price - self._stop_distance:
self.SellMarket()
self._entry_price = None
self._entry_side = None
elif self._entry_side == "sell" and close >= self._entry_price + self._stop_distance:
self.BuyMarket()
self._entry_price = None
self._entry_side = None
elif self._entry_side == "buy" and close >= self._entry_price + self._stop_distance * 1.5:
self.SellMarket()
self._entry_price = None
self._entry_side = None
elif self._entry_side == "sell" and close <= self._entry_price - self._stop_distance * 1.5:
self.BuyMarket()
self._entry_price = None
self._entry_side = None
# Entry signals: breakout
if self.Position == 0:
if close > self._highest_high:
self.BuyMarket()
self._entry_price = close
self._entry_side = "buy"
elif close < self._lowest_low:
self.SellMarket()
self._entry_price = close
self._entry_side = "sell"
# Update channel with slow decay
if high > self._highest_high:
self._highest_high = high
else:
self._highest_high = self._highest_high * 0.999 + high * 0.001
if low < self._lowest_low:
self._lowest_low = low
else:
self._lowest_low = self._lowest_low * 0.999 + low * 0.001
def CreateClone(self):
return ak47_scalper_strategy()