Стратегия перенесена из MetaTrader 5 эксперта «AK-47 Scalper EA» (билд 44883) и реализована на высокоуровневом API StockSharp.
Во время работы стратегия держит один отложенный ордер Sell Stop в разрешённое торговое время. После срабатывания заявка немедленно сопровождается стоп-лоссом и тейк-профитом. Цена отложенного ордера и защитный стоп автоматически подтягиваются вслед за рынком.
Логика работы
Определяется размер пункта на основе минимального шага цены (для пятизнаковых инструментов используется шаг 0.1 пункта как в MT5).
Рассчитывается торговое окно. При включённом фильтре сделки разрешены только между заданными временем начала и окончания; допускается переход через полночь.
Перед постановкой ордера проверяется, что текущий спред в пунктах не превышает максимально допустимое значение.
Объём позиции:
Либо задаётся фиксированным лотом (Base Lot),
Либо вычисляется по доле капитала (Risk Percent), после чего приводится к биржевым ограничениям по минимальному, максимальному объёму и шагу.
Ордер Sell Stop размещается на SL/2 пункта ниже текущего бидa. Планируемый стоп ставится на SL/2 пункта выше аска, тейк — на TP пунктов ниже цены входа.
Пока ордер активен, его цена пере-регистрируется так, чтобы сохранять расстояние SL/2 от бида, а также обновляются запланированные уровни защиты.
После исполнения:
Регистрируются защитные ордера Buy Stop (стоп-лосс) и Buy Limit (тейк-профит) по рассчитанным ранее ценам.
На закрытии каждой свечи стоп переносится на расстояние SL пунктов от текущего бида, никогда не расширяя риск.
Тейк-профит остаётся неизменным.
После закрытия позиции защитные ордера отменяются и цикл начинается заново.
Параметры
Параметр
Описание
Use Risk Percent
Использовать ли процент от капитала для расчёта объёма.
Risk Percent
Процент капитала, используемый при расчёте объёма.
Base Lot
Фиксированный лот и шаг округления для объёма.
Stop Loss (pips)
Расстояние до стоп-лосса; половина этого значения определяет смещение отложенного ордера.
Take Profit (pips)
Расстояние до тейк-профита (0 — отключить).
Max Spread (points)
Максимально допустимый спред в пунктах MetaTrader.
Use Time Filter
Включение ограничения по торговому времени.
Start Hour / Minute
Время начала торгового окна.
End Hour / Minute
Время окончания торгового окна.
Candle Type
Тип свечей, по которым обновляется логика.
Дополнительно
Стратегия торгует только короткие позиции, полностью повторяя оригинальную логику EA.
Подтягивание стопа выполняется после закрытия свечи, что соответствует высокоуровневому API StockSharp.
Для изменения защитных заявок используется ReRegisterOrder; торговая площадка должна поддерживать замену ордеров.
Текстовые комментарии терминала из оригинала не переносились, стратегия использует стандартные журналы StockSharp.
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()