This strategy is a high-level StockSharp port of the Aeron JJN Scalper expert advisor. It watches finished candles, identifies specific two-bar reversal situations, and places simulated stop orders at the open of the most recent opposite candle. When the market reaches the stored stop level the strategy enters with a market order, applies ATR-based risk targets, and manages the trade with a pip-based trailing stop.
Key ideas:
Trade direction is decided by a bullish/bearish two-candle reversal pattern.
Entry levels come from the open price of the last strong candle in the opposite direction.
An ATR(8) value measured on the signal bar sets both stop-loss and take-profit distances.
Trailing stop logic moves the protective level once price advances by the configured pip offsets.
Pending levels automatically expire after the configured number of minutes.
Trading rules
Signal detection
Work only with finished candles from the configured timeframe (default: 1 minute).
Compute pip size from the security price step and multiply by 10 for 3 or 5 decimal pricing to mimic MetaTrader pip behaviour.
Maintain a rolling window of the last 120 candles to search for reference bars.
Detect a long setup when:
The current candle closes above its open (bullish), and
The previous candle is bearish with body size greater than DojiDiff1Pips.
Search backwards for the latest bearish candle whose body exceeds DojiDiff2Pips; its open price becomes the buy stop level.
Detect a short setup when:
The current candle closes below its open (bearish), and
The previous candle is bullish with body size greater than DojiDiff1Pips.
Search backwards for the latest bullish candle whose body exceeds DojiDiff2Pips; its open price becomes the sell stop level.
Ignore new setups if there is already a pending level in the same direction, or if the ATR value for the candle is not yet available.
Pending level management
The stored level is treated as a pending stop order. It is discarded if price remains below (long) or above (short) the trigger until the expiration time ResetMinutes elapses.
When price touches the level on a later candle (high ≥ buy level or low ≤ sell level), the strategy sends a market order sized to flip any existing exposure and add Volume contracts.
Entering a long position clears any outstanding short level and vice versa.
Stop-loss, take-profit, and trailing
Upon entry the strategy records the ATR(8) value from the signal candle.
Checks whether price reached the stop-loss or take-profit and exits with a market order if touched.
Applies trailing when price has moved at least TrailingStopPips + TrailingStepPips in favour of the position. The new stop sits TrailingStopPips behind the latest close. The stop never moves backwards.
If the position is closed manually the internal state resets automatically.
Parameters
Parameter
Default
Description
Volume
0.1
Net position size used for entries; the strategy adds the absolute current position to flip direction when required.
TrailingStopPips
5
Base trailing stop distance (converted to price units).
TrailingStepPips
5
Extra advance required before moving the trailing stop again.
ResetMinutes
10
Expiration time for a stored pending level (minutes).
DojiDiff1Pips
10
Minimum body size (in pips) for the reversal candle that precedes the signal.
DojiDiff2Pips
4
Minimum body size (in pips) for the candle used as the entry reference level.
CandleType
1 minute time frame
Candle data type used for calculations.
Implementation notes
The strategy operates purely on finished candles and uses in-memory levels instead of real stop orders; when the level is breached a market order is sent immediately. This mirrors the original EA behaviour within the StockSharp high-level API.
ATR(8) is computed with AverageTrueRange and cached so that the original stop/take distances remain constant for each trade.
The pip conversion reproduces the MetaTrader adjustment for 3- and 5-digit quotes. If the security lacks PriceStep, a default step of 1 is used.
Up to 120 historical candles are stored to replicate the original CopyRates look-back of 100 bars with some safety margin.
No Python port is provided for this strategy.
Usage
Attach the strategy to the desired security and portfolio.
Adjust the candle timeframe, pip offsets, and ATR-based filters to suit the instrument.
Start the strategy; it will track signals, submit market orders when trigger levels are touched, and manage exits automatically.
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()