Стратегия представляет собой порт экспертного советника Aeron JJN Scalper на высокоуровневый API StockSharp. Алгоритм анализирует только завершённые свечи, ищет двухсвечные разворотные комбинации и запоминает цену открытия последней сильной свечи в противоположную сторону. Этот уровень рассматривается как отложенный стоп-ордер: при его достижении отправляется рыночная заявка, устанавливаются ATR-ориентиры по стоп-лоссу и тейк-профиту, а дальнейшее сопровождение ведётся с помощью трейлинг-стопа в пунктах.
Ключевые идеи:
Направление сделки определяется двухсвечным разворотом (бычья/медвежья комбинация).
Точка входа — открытие последней «сильной» свечи противоположного цвета.
Значение ATR(8), рассчитанное на свечке сигнала, задаёт расстояние до стоп-лосса и тейк-профита.
Трейлинг-стоп срабатывает, когда цена проходит заданное количество пунктов в прибыльную сторону.
Отложенные уровни автоматически отменяются по истечении заданного времени.
Правила торговли
Поиск сигнала
Используются только закрытые свечи выбранного таймфрейма (по умолчанию 1 минута).
Размер пункта вычисляется из шага цены инструмента; для трёх- и пятизнаковых котировок дополнительно применяется коэффициент 10, как в MetaTrader.
В памяти хранится до 120 последних свечей для поиска подходящих исторических баров.
Сигнал на покупку появляется, если:
текущая свеча закрылась выше открытия;
предыдущая свеча — медвежья, и её тело больше DojiDiff1Pips;
при поиске назад находится ближайшая медвежья свеча с телом больше DojiDiff2Pips, её открытие — уровень стоп-покупки.
Сигнал на продажу формируется зеркально: текущая свеча медвежья, предыдущая — сильная бычья, уровень берётся по открытию последней бычьей свечи с телом больше DojiDiff2Pips.
Новые сигналы игнорируются, если уже есть уровень в ту же сторону или ATR ещё не посчитан.
Управление отложенными уровнями
Сохранённый уровень рассматривается как отложенный ордер и удаляется, если цена не достигает его до истечения ResetMinutes минут.
Если в последующих свечах максимум ≥ уровня покупки (или минимум ≤ уровня продажи), стратегия отправляет рыночную заявку нужного объёма: позиция переворачивается и добавляется Volume лотов.
После открытия позиции противоположный уровень сбрасывается.
Стоп-лосс, тейк-профит и трейлинг
При входе фиксируется значение ATR(8) свечи сигнала:
если цена достигла стоп-уровня или цели, позиция закрывается рыночной заявкой;
когда прибыль превышает TrailingStopPips + TrailingStepPips пунктов, трейлинг-стоп переносится на расстояние TrailingStopPips от текущего закрытия и никогда не отступает назад.
При ручном закрытии позиции внутренние переменные очищаются автоматически.
Параметры
Параметр
Значение по умолчанию
Описание
Volume
0.1
Размер чистой позиции при входе; при развороте добавляется абсолютная величина текущей позиции.
TrailingStopPips
5
Базовое расстояние трейлинг-стопа в пунктах (переводится в цену).
TrailingStepPips
5
Дополнительное движение в пунктах до очередного переноса трейлинга.
ResetMinutes
10
Время жизни отложенного уровня (минуты).
DojiDiff1Pips
10
Минимальное тело предыдущей свечи для подтверждения разворота.
DojiDiff2Pips
4
Минимальное тело свечи, используемой как уровень входа.
CandleType
1 минута
Тип свечей для расчётов.
Особенности реализации
Стратегия работает на уровне завершённых свечей и не выставляет реальные стоп-заявки: при пробое уровня выполняется рыночная сделка, что повторяет логику исходного советника в рамках высокоуровневого API.
ATR(8) рассчитывается индикатором AverageTrueRange, чтобы стопы и цели оставались постоянными для каждой сделки.
Преобразование пунктов повторяет метатрейдеровский подход к трёх- и пятизнаковым котировкам; при отсутствии PriceStep используется значение 1.
В памяти поддерживается до 120 свечей, что немного превышает исходный вызов CopyRates на 100 баров.
Python-версия стратегии не создавалась.
Использование
Подключите стратегию к нужному инструменту и портфелю.
Настройте таймфрейм свечей, параметры точек и ATR под особенности инструмента.
Запустите стратегию — она будет отслеживать сигналы, входить при достижении уровней и сопровождать позиции автоматически.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the Aeron JJN Scalper expert advisor.
/// Detects reversal candle patterns (bullish candle after strong bearish, and vice versa)
/// and enters at breakout of the prior candle's open level.
/// </summary>
public class AeronJjnScalperEaStrategy : Strategy
{
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _bodyMinAtr;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private AverageTrueRange _atr;
private decimal _prevOpen;
private decimal _prevClose;
private bool _hasPrev;
private int _cooldown;
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal BodyMinAtr
{
get => _bodyMinAtr.Value;
set => _bodyMinAtr.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public AeronJjnScalperEaStrategy()
{
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR indicator period", "Indicators");
_bodyMinAtr = Param(nameof(BodyMinAtr), 1.5m)
.SetDisplay("Body Min ATR", "Minimum candle body size as ATR multiple", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle Type", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_hasPrev = false;
_cooldown = 0;
_prevOpen = 0;
_prevClose = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_atr = new AverageTrueRange { Length = AtrLength };
_hasPrev = false;
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_atr, ProcessCandle).Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
{
SavePrev(candle);
return;
}
if (_cooldown > 0)
_cooldown--;
if (_hasPrev && _cooldown == 0 && Position == 0)
{
var prevBody = Math.Abs(_prevClose - _prevOpen);
var minBody = atrVal * BodyMinAtr;
// Bullish candle after strong bearish candle => buy
if (candle.ClosePrice > candle.OpenPrice && _prevClose < _prevOpen && prevBody >= minBody)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Bearish candle after strong bullish candle => sell
else if (candle.ClosePrice < candle.OpenPrice && _prevClose > _prevOpen && prevBody >= minBody)
{
SellMarket();
_cooldown = CooldownBars;
}
}
SavePrev(candle);
}
private void SavePrev(ICandleMessage candle)
{
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
_hasPrev = true;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class aeron_jjn_scalper_ea_strategy(Strategy):
def __init__(self):
super(aeron_jjn_scalper_ea_strategy, self).__init__()
self._atr_length = self.Param("AtrLength", 14)
self._body_min_atr = self.Param("BodyMinAtr", 1.5)
self._cooldown_bars = self.Param("CooldownBars", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev = False
self._cooldown = 0
@property
def AtrLength(self):
return self._atr_length.Value
@property
def BodyMinAtr(self):
return self._body_min_atr.Value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(aeron_jjn_scalper_ea_strategy, self).OnStarted2(time)
self._has_prev = False
self._cooldown = 0
atr = AverageTrueRange()
atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
atr_value = float(atr_val)
if atr_value <= 0:
self._save_prev(candle)
return
if self._cooldown > 0:
self._cooldown -= 1
if self._has_prev and self._cooldown == 0 and float(self.Position) == 0:
prev_body = abs(self._prev_close - self._prev_open)
min_body = atr_value * float(self.BodyMinAtr)
if float(candle.ClosePrice) > float(candle.OpenPrice) and self._prev_close < self._prev_open and prev_body >= min_body:
self.BuyMarket()
self._cooldown = self.CooldownBars
elif float(candle.ClosePrice) < float(candle.OpenPrice) and self._prev_close > self._prev_open and prev_body >= min_body:
self.SellMarket()
self._cooldown = self.CooldownBars
self._save_prev(candle)
def _save_prev(self, candle):
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._has_prev = True
def OnReseted(self):
super(aeron_jjn_scalper_ea_strategy, self).OnReseted()
self._has_prev = False
self._cooldown = 0
self._prev_open = 0.0
self._prev_close = 0.0
def CreateClone(self):
return aeron_jjn_scalper_ea_strategy()