Diese Strategie ist ein StockSharp High-Level-API-Port des Expert Advisors Aeron JJN Scalper. Sie beobachtet abgeschlossene Kerzen, identifiziert spezifische Zwei-Bar-Umkehrsituationen und platziert simulierte Stop-Orders an der Eröffnung der letzten entgegengesetzten Kerze. Wenn der Markt das gespeicherte Stop-Niveau erreicht, tritt die Strategie mit einer Marktorder ein, wendet ATR-basierte Risikovorgaben an und verwaltet den Trade mit einem pip-basierten Trailing-Stop.
Kernideen:
Die Handelsrichtung wird durch ein bullisches/bärisches Zwei-Kerzen-Umkehrmuster entschieden.
Einstiegsniveaus kommen vom Eröffnungspreis der letzten starken Kerze in der entgegengesetzten Richtung.
Ein ATR(8)-Wert, gemessen auf der Signalkerze, setzt sowohl Stop-Loss- als auch Take-Profit-Abstände.
Die Trailing-Stop-Logik bewegt den Schutzniveau, sobald der Preis um die konfigurierten Pip-Offsets vorgerückt ist.
Ausstehende Niveaus laufen nach der konfigurierten Anzahl von Minuten automatisch ab.
Handelsregeln
Signalerkennung
Nur mit abgeschlossenen Kerzen des konfigurierten Zeitrahmens arbeiten (Standard: 1 Minute).
Pip-Größe aus dem Preisschritt des Instruments berechnen und für 3- oder 5-Dezimal-Preise mit 10 multiplizieren, um das MetaTrader-Pip-Verhalten zu imitieren.
Ein rollendes Fenster der letzten 120 Kerzen zur Suche nach Referenzbars pflegen.
Ein Long-Setup erkennen, wenn:
Die aktuelle Kerze über ihrer Eröffnung schließt (bullisch), und
Die vorherige Kerze bärisch ist mit einer Körpergröße größer als DojiDiff1Pips.
Rückwärts nach der letzten bärischen Kerze suchen, deren Körper DojiDiff2Pips überschreitet; ihr Eröffnungspreis wird zum Buy-Stop-Niveau.
Ein Short-Setup erkennen, wenn:
Die aktuelle Kerze unter ihrer Eröffnung schließt (bärisch), und
Die vorherige Kerze bullisch ist mit einer Körpergröße größer als DojiDiff1Pips.
Rückwärts nach der letzten bullischen Kerze suchen, deren Körper DojiDiff2Pips überschreitet; ihr Eröffnungspreis wird zum Sell-Stop-Niveau.
Neue Setups ignorieren, wenn es bereits ein austehendes Niveau in derselben Richtung gibt, oder wenn der ATR-Wert für die Kerze noch nicht verfügbar ist.
Management ausstehender Niveaus
Das gespeicherte Niveau wird als ausstehende Stop-Order behandelt. Es wird verworfen, wenn der Preis unterhalb (Long) oder oberhalb (Short) des Auslösers bleibt, bis die Ablaufzeit ResetMinutes verstrichen ist.
Wenn der Preis das Niveau auf einer späteren Kerze berührt (Hoch ≥ Kaufniveau oder Tief ≤ Verkaufsniveau), sendet die Strategie eine Marktorder, die so dimensioniert ist, um eine bestehende Exposure umzukehren und Volume Kontrakte hinzuzufügen.
Das Eintreten in eine Long-Position löscht jedes ausstehende Short-Niveau und umgekehrt.
Stop-Loss, Take-Profit und Trailing
Beim Einstieg zeichnet die Strategie den ATR(8)-Wert der Signalkerze auf.
Prüft, ob der Preis den Stop-Loss oder Take-Profit erreicht hat, und schließt mit einer Marktorder, wenn berührt.
Wendet Trailing an, wenn der Preis mindestens TrailingStopPips + TrailingStepPips zugunsten der Position vorgerückt ist. Der neue Stop liegt TrailingStopPips hinter dem letzten Schlusskurs. Der Stop bewegt sich nie rückwärts.
Wird die Position manuell geschlossen, setzt sich der interne Zustand automatisch zurück.
Parameter
Parameter
Standard
Beschreibung
Volume
0.1
Nettopositionsgröße für Einstiege; die Strategie fügt die absolute aktuelle Position hinzu, um die Richtung umzukehren, wenn erforderlich.
TrailingStopPips
5
Basisabstand des Trailing-Stops (in Preiseinheiten umgerechnet).
TrailingStepPips
5
Zusätzlicher Vorschub, der erforderlich ist, bevor der Trailing-Stop wieder bewegt wird.
ResetMinutes
10
Ablaufzeit für ein gespeichertes austehendes Niveau (Minuten).
DojiDiff1Pips
10
Minimale Körpergröße (in Pips) für die Umkehrkerze, die dem Signal vorausgeht.
DojiDiff2Pips
4
Minimale Körpergröße (in Pips) für die Kerze, die als Eintrittsniveaureferenz verwendet wird.
CandleType
1 Minute Zeitrahmen
Für Berechnungen verwendeter Kerzen-Datentyp.
Implementierungshinweise
Die Strategie operiert rein auf abgeschlossenen Kerzen und verwendet speicherinterne Niveaus anstelle von echten Stop-Orders; wenn das Niveau durchbrochen wird, wird sofort eine Marktorder gesendet. Dies spiegelt das ursprüngliche EA-Verhalten innerhalb der StockSharp High-Level-API wider.
ATR(8) wird mit AverageTrueRange berechnet und zwischengespeichert, damit die ursprünglichen Stop-/Ziel-Abstände für jeden Trade konstant bleiben.
Die Pip-Konvertierung reproduziert die MetaTrader-Anpassung für 3- und 5-stellige Kursnotierungen. Fehlt dem Wertpapier PriceStep, wird ein Standardschritt von 1 verwendet.
Bis zu 120 historische Kerzen werden gespeichert, um das ursprüngliche CopyRates-Look-back von 100 Bars mit etwas Sicherheitsmarge zu replizieren.
Kein Python-Port ist für diese Strategie vorgesehen.
Verwendung
Die Strategie an das gewünschte Wertpapier und Portfolio anhängen.
Den Kerzen-Zeitrahmen, Pip-Offsets und ATR-basierte Filter an das Instrument anpassen.
Die Strategie starten; sie wird Signale verfolgen, Marktorders senden, wenn Auslöseniveaus berührt werden, und Ausstiege automatisch verwalten.
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()