This strategy is a conversion of the MetaTrader 5 expert advisor "AK-47 Scalper EA" (build 44883). It recreates the original behaviour inside the StockSharp high-level strategy framework.
The algorithm keeps a single sell stop order active during the allowed trading hours. Once the order is triggered the strategy immediately attaches protective stop-loss and take-profit orders. Both the pending order price and the protective stop are tightened dynamically as the market moves.
Core Logic
Calculate the pip size from the instrument tick size (5-digit symbols use 0.1 pip steps just like in MetaTrader).
Determine the trading window. When the time filter is enabled, entries are allowed only between the configured start and end times (inclusive of the start, exclusive of the end). Overnight sessions are supported by wrapping around midnight.
Make sure the current spread in points does not exceed the configured limit before placing new orders.
Size the position:
Either use the fixed lot (Base Lot parameter), or
Convert the configured Risk Percent of the portfolio value into lots (mimicking the MT5 formula) and align it with the exchange volume constraints.
Place a sell stop order SL/2 pips below the bid. The protective stop is planned SL/2 pips above the ask and the take profit sits TP pips below the entry.
While the order is pending the strategy continually re-registers it to keep the SL/2 pip gap to the bid and updates the planned protective prices.
After execution:
Register a buy-stop stop-loss order and a buy-limit take-profit order using the planned prices.
On every candle close the strategy trails the stop by keeping it exactly SL pips above the current bid (never loosening it).
The take-profit price stays fixed once set.
If the position is flat all protective orders are cancelled and a new cycle can start.
Parameters
Parameter
Description
Use Risk Percent
Switch between fixed lots and equity-based sizing.
Risk Percent
Percentage applied to the portfolio value when calculating the trade volume.
Base Lot
Fixed lot size and rounding step for the position sizing.
Stop Loss (pips)
Distance between the entry price and the protective stop. The pending order offset uses half of this distance.
Take Profit (pips)
Profit target distance. Set to zero to disable the target.
Max Spread (points)
Maximum allowed spread (in MetaTrader points) to enter the market.
Use Time Filter
Enable or disable the trading window restriction.
Start Hour / Minute
Beginning of the trading window.
End Hour / Minute
End of the trading window.
Candle Type
Candle subscription used for timing and price updates.
Notes
The strategy uses only short entries just like the original EA.
Trailing is performed on candle close to stay in sync with the StockSharp high-level API.
Protective orders are replaced via ReRegisterOrder calls, so the exchange or simulator must support order replacement.
The original graphical comments from MetaTrader are not reproduced because StockSharp strategies rely on logging instead of terminal comments.
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()