Überarbeitetes selbstadaptives EA
Portierung des MetaTrader 5 Expertenberaters revised_self_adaptive_ea.mq5 in das StockSharp High-Level-Strategie-Framework.
Strategieüberblick
Der Algorithmus scannt eine konfigurierbare Kerzenreihe und sucht nach umfassenden Umkehrkonfigurationen, die durch Momentum- und Trendfilter bestätigt werden:
- Mustererkennung – bewertet die letzte geschlossene Kerze im Vergleich zur vorherigen. Ein bullisches Setup erfordert einen grünen Körper, der unter dem vorherigen Schlusskurs öffnet, während die vorherige Kerze bärisch ist. Bei bärischen Setups wird die Spiegellogik angewendet. Kerzenkörper werden mit einem gleitenden Durchschnitt verglichen, um schwache Signale herauszufiltern.
- Momentum-Filter – ein klassischer RSI stellt sicher, dass bullische Trades nur im überverkauften Bereich und bärische Trades im überkauften Bereich ausgelöst werden.
- Trendfilter – ein kurzer einfacher gleitender Durchschnitt muss mit der Handelsrichtung übereinstimmen. Dies verhindert, dass starke Trends ohne Bestätigung verblassen.
- Risikomanagement – ATR-gesteuerte Stop-Loss- und Take-Profit-Level werden für jede neue Position berechnet. Optionale Trailing-Stops verfolgen weiterhin profitable Bewegungen, ohne den Schutz zu verringern. Positionen werden zwangsweise geschlossen, wenn der Preis die Schutzniveaus erreicht.
- Spread- und Risikoschutz – Trades werden übersprungen, wenn der aktuelle Spread den konfigurierten Schwellenwert überschreitet oder wenn der auf ATR basierende Stop ein Risiko darstellen würde, das über dem zulässigen Prozentsatz des Preises liegt.
Parameter
| Name | Beschreibung |
|---|---|
CandleType |
Zur Analyse verwendete Kerzenaggregation. Standardmäßig werden einstündige Balken verwendet. |
AverageBodyPeriod |
Anzahl der Kerzen, die zur Berechnung des Filters für die durchschnittliche Körpergröße verwendet werden. |
MovingAveragePeriod |
Länge des einfachen gleitenden Durchschnitts, der als Richtungsfilter fungiert. |
RsiPeriod |
RSI Länge, die für die Überverkauft-/Überkauft-Bestätigung verwendet wird. |
OversoldLevel |
RSI Schwelle, die erreicht werden muss, bevor eine bullische Umkehr akzeptiert wird. |
OverboughtLevel |
RSI Schwelle, die erreicht werden muss, bevor eine rückläufige Umkehr akzeptiert wird. |
AtrPeriod |
ATR Länge, die für volatilitätsbasierte Schutzabstände verwendet wird. |
StopLossAtrMultiplier |
Auf ATR angewendeter Multiplikationsfaktor für die Stop-Loss-Distanz. |
TakeProfitAtrMultiplier |
Auf ATR angewendeter Multiplikationsfaktor für die Take-Profit-Distanz. |
TrailingStopAtrMultiplier |
ATR Abstand, der von der Trailing-Stop-Logik verwaltet wird. |
UseTrailingStop |
Aktiviert den Trailing-Stop-Supervisor. |
MaxSpreadPoints |
Maximal zulässiger Spread (ausgedrückt in Preisschritten/Pips). Signale werden ignoriert, wenn der Markt breiter ist. |
MaxRiskPercent |
Maximal akzeptables prozentuales Risiko basierend auf dem ATR-Stopp im Verhältnis zum Einstiegspreis. |
TradeVolume |
Basislosgröße, die für Marktaufträge verwendet wird. |
Verhaltenshinweise
- Die Positionen werden abgeflacht, bevor die Richtung umgekehrt wird, um die MetaTrader-Implementierung widerzuspiegeln.
- Die Schutz-Stopp-/Take-Werte werden nach jeder Füllung anhand des letzten ATR-Werts neu berechnet.
- Der Trailing Stop bewegt sich nur in Handelsrichtung und ist deaktiviert, wenn noch keine ATR-Daten verfügbar sind.
- Wenn die Strategie auf einem Instrument ohne zuverlässige Geld-/Briefkurse ausgeführt wird, bleibt der Spread-Filter automatisch inaktiv.
Unterschiede zum Original MQL
Das ursprüngliche Skript beschrieb lediglich die Signalerkennungsroutine. In diesem Port wurden die fehlenden Elemente mithilfe der bereitgestellten Parameter rekonstruiert:
- Bestätigung des gleitenden Durchschnitts hinzugefügt, um das in der MQL-Quelle deklarierte MA-Handle zu verwenden.
- Implementierte ATR-basierte Stop-Loss-, Take-Profit- und Trailing-Stop-Logik unter Verwendung des im ursprünglichen Experten definierten Volatilitäts-Handles.
- Es wurde ein Risikoprozentschutz hinzugefügt, sodass übergroße ATR-Stopps übersprungen werden, anstatt blind ausgeführt zu werden.
- Visualisierungselemente (Diagrammpfeile) wurden weggelassen, da StockSharp-Strategien standardmäßig keine Objekte in Diagrammen zeichnen.
Nutzung
- Hängen Sie die Strategie an ein Portfolio und eine Sicherheit in Hydra oder Ihrem benutzerdefinierten StockSharp-Host an.
- Stellen Sie sicher, dass das Kerzenabonnement dem vorgesehenen Zeitrahmen entspricht (Standard: eine Stunde).
- Passen Sie die Risikoparameter an, um die Volatilität des Instruments widerzuspiegeln.
- Starten Sie die Strategie. Es abonniert automatisch Kerzen, berechnet Indikatoren und platziert Marktaufträge, wenn die Bedingungen erfüllt sind.
namespace StockSharp.Samples.Strategies;
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
/// <summary>
/// Port of the MetaTrader expert revised_self_adaptive_ea.
/// Detects bullish and bearish engulfing patterns confirmed by RSI and a trend moving average.
/// Applies ATR based risk management with optional trailing stop supervision.
/// </summary>
public class RevisedSelfAdaptiveEaStrategy : Strategy
{
private readonly StrategyParam<int> _averageBodyPeriod;
private readonly StrategyParam<int> _movingAveragePeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _volume;
private readonly StrategyParam<decimal> _maxSpreadPoints;
private readonly StrategyParam<decimal> _maxRiskPercent;
private readonly StrategyParam<bool> _useTrailingStop;
private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
private readonly StrategyParam<decimal> _takeProfitAtrMultiplier;
private readonly StrategyParam<decimal> _trailingStopAtrMultiplier;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi = null!;
private SimpleMovingAverage _movingAverage = null!;
private AverageTrueRange _atr = null!;
private SimpleMovingAverage _bodyAverage = null!;
private ICandleMessage _previousCandle;
private decimal _lastAtrValue;
private decimal _averageBodyValue;
private decimal _pipSize;
private bool _pipSizeInitialized;
private decimal? _longEntryPrice;
private decimal? _longStopPrice;
private decimal? _longTakeProfitPrice;
private decimal? _longTrailingStopPrice;
private decimal? _shortEntryPrice;
private decimal? _shortStopPrice;
private decimal? _shortTakeProfitPrice;
private decimal? _shortTrailingStopPrice;
/// <summary>
/// Initializes a new instance of <see cref="RevisedSelfAdaptiveEaStrategy"/>.
/// </summary>
public RevisedSelfAdaptiveEaStrategy()
{
_averageBodyPeriod = Param(nameof(AverageBodyPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Average body period", "Number of candles used to calculate the average body size filter.", "Pattern")
.SetOptimize(2, 10, 1);
_movingAveragePeriod = Param(nameof(MovingAveragePeriod), 2)
.SetGreaterThanZero()
.SetDisplay("MA period", "Simple moving average period used as a directional filter.", "Trend")
.SetOptimize(2, 30, 1);
_rsiPeriod = Param(nameof(RsiPeriod), 6)
.SetGreaterThanZero()
.SetDisplay("RSI period", "Length of the RSI oscillator applied to candle closes.", "Oscillator")
.SetOptimize(3, 30, 1);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR period", "Average True Range period that controls stop distances.", "Risk")
.SetOptimize(7, 50, 1);
_volume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade volume", "Base position size expressed in lots.", "Trading")
.SetOptimize(0.01m, 1m, 0.01m);
_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 20m)
.SetNotNegative()
.SetDisplay("Max spread", "Maximum allowed spread expressed in points.", "Risk");
_maxRiskPercent = Param(nameof(MaxRiskPercent), 10m)
.SetNotNegative()
.SetDisplay("Max risk percent", "Maximum percentage of the portfolio equity accepted per trade.", "Risk");
_useTrailingStop = Param(nameof(UseTrailingStop), true)
.SetDisplay("Use trailing stop", "Enable ATR driven trailing stop supervision.", "Risk");
_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("Stop loss ATR multiplier", "Number of ATRs used to place the protective stop.", "Risk")
.SetOptimize(0.5m, 5m, 0.5m);
_takeProfitAtrMultiplier = Param(nameof(TakeProfitAtrMultiplier), 4m)
.SetNotNegative()
.SetDisplay("Take profit ATR multiplier", "Number of ATRs used to place the profit target.", "Risk")
.SetOptimize(0.5m, 8m, 0.5m);
_trailingStopAtrMultiplier = Param(nameof(TrailingStopAtrMultiplier), 1.5m)
.SetNotNegative()
.SetDisplay("Trailing stop ATR multiplier", "ATR distance maintained by the trailing stop logic.", "Risk")
.SetOptimize(0.5m, 5m, 0.5m);
_oversoldLevel = Param(nameof(OversoldLevel), 40m)
.SetNotNegative()
.SetDisplay("Oversold level", "RSI threshold that confirms bullish reversals.", "Oscillator")
.SetOptimize(10m, 50m, 5m);
_overboughtLevel = Param(nameof(OverboughtLevel), 60m)
.SetNotNegative()
.SetDisplay("Overbought level", "RSI threshold that confirms bearish reversals.", "Oscillator")
.SetOptimize(50m, 90m, 5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle type", "Time frame used for pattern detection.", "General");
}
/// <summary>
/// Rolling period used to evaluate the average candle body size.
/// </summary>
public int AverageBodyPeriod
{
get => _averageBodyPeriod.Value;
set => _averageBodyPeriod.Value = value;
}
/// <summary>
/// Period applied to the simple moving average filter.
/// </summary>
public int MovingAveragePeriod
{
get => _movingAveragePeriod.Value;
set => _movingAveragePeriod.Value = value;
}
/// <summary>
/// RSI length used to confirm overbought and oversold conditions.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Average True Range period that controls risk distances.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Default trade volume.
/// </summary>
public decimal TradeVolume
{
get => _volume.Value;
set => _volume.Value = value;
}
/// <summary>
/// Maximum allowed spread measured in points.
/// </summary>
public decimal MaxSpreadPoints
{
get => _maxSpreadPoints.Value;
set => _maxSpreadPoints.Value = value;
}
/// <summary>
/// Maximum portion of portfolio equity that can be exposed per trade.
/// </summary>
public decimal MaxRiskPercent
{
get => _maxRiskPercent.Value;
set => _maxRiskPercent.Value = value;
}
/// <summary>
/// Enables the ATR based trailing stop controller.
/// </summary>
public bool UseTrailingStop
{
get => _useTrailingStop.Value;
set => _useTrailingStop.Value = value;
}
/// <summary>
/// ATR multiplier used to position the stop loss.
/// </summary>
public decimal StopLossAtrMultiplier
{
get => _stopLossAtrMultiplier.Value;
set => _stopLossAtrMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier used to position the take profit target.
/// </summary>
public decimal TakeProfitAtrMultiplier
{
get => _takeProfitAtrMultiplier.Value;
set => _takeProfitAtrMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier that defines the trailing stop distance.
/// </summary>
public decimal TrailingStopAtrMultiplier
{
get => _trailingStopAtrMultiplier.Value;
set => _trailingStopAtrMultiplier.Value = value;
}
/// <summary>
/// RSI threshold that validates bullish signals.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// RSI threshold that validates bearish signals.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// Candle type consumed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousCandle = null;
_lastAtrValue = 0m;
_averageBodyValue = 0m;
_pipSize = 0m;
_pipSizeInitialized = false;
ResetLongRiskLevels();
ResetShortRiskLevels();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume;
_rsi = new RelativeStrengthIndex
{
Length = RsiPeriod
};
_movingAverage = new SMA
{
Length = MovingAveragePeriod
};
_atr = new AverageTrueRange
{
Length = AtrPeriod
};
_bodyAverage = new SMA
{
Length = AverageBodyPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _movingAverage, _atr, ProcessCandle)
.Start();
StartProtection(null, null);
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
var order = trade.Order;
if (order == null)
return;
if (order.Side == Sides.Buy)
{
if (Position > 0m)
{
// Long exposure established, compute fresh protective levels.
_longEntryPrice = trade.Trade.Price;
InitializeLongRiskLevels(trade.Trade.Price);
}
else if (Position >= 0m)
{
// Short exposure was reduced or closed.
ResetShortRiskLevels();
}
}
else if (order.Side == Sides.Sell)
{
if (Position < 0m)
{
// Short exposure established, compute protective levels.
_shortEntryPrice = trade.Trade.Price;
InitializeShortRiskLevels(trade.Trade.Price);
}
else if (Position <= 0m)
{
// Long exposure was reduced or closed.
ResetLongRiskLevels();
}
}
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (Position == 0m)
{
// Flat state clears pending protective levels.
ResetLongRiskLevels();
ResetShortRiskLevels();
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal maValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_pipSizeInitialized)
InitializePipSize();
_lastAtrValue = atrValue;
UpdateAverageBody(candle);
ManageOpenPositions(candle);
if (!_rsi.IsFormed || !_movingAverage.IsFormed || !_atr.IsFormed)
{
_previousCandle = candle;
return;
}
if (_previousCandle == null)
{
_previousCandle = candle;
return;
}
if (!IsSpreadWithinLimit())
{
_previousCandle = candle;
return;
}
var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var minimumBody = _averageBodyValue > 0m ? _averageBodyValue : 0m;
var bullishEngulfing = candle.ClosePrice > candle.OpenPrice &&
_previousCandle.ClosePrice < _previousCandle.OpenPrice &&
candle.OpenPrice <= _previousCandle.ClosePrice &&
bodySize >= minimumBody;
var bearishEngulfing = candle.ClosePrice < candle.OpenPrice &&
_previousCandle.ClosePrice > _previousCandle.OpenPrice &&
candle.OpenPrice >= _previousCandle.ClosePrice &&
bodySize >= minimumBody;
if (bullishEngulfing && rsiValue <= OversoldLevel && candle.ClosePrice >= maValue)
{
TryOpenLong(candle.ClosePrice);
}
else if (bearishEngulfing && rsiValue >= OverboughtLevel && candle.ClosePrice <= maValue)
{
TryOpenShort(candle.ClosePrice);
}
_previousCandle = candle;
}
private void TryOpenLong(decimal price)
{
if (Position > 0m)
return;
if (ShouldBlockByRisk(price))
return;
if (Position < 0m)
{
// Close the opposing short exposure before flipping direction.
BuyMarket(Math.Abs(Position));
return;
}
var volume = GetTradeVolume();
if (volume <= 0m)
return;
// Market order is used to replicate the MetaTrader execution style.
BuyMarket(volume);
}
private void TryOpenShort(decimal price)
{
if (Position < 0m)
return;
if (ShouldBlockByRisk(price))
return;
if (Position > 0m)
{
// Close the opposing long exposure before flipping direction.
SellMarket(Math.Abs(Position));
return;
}
var volume = GetTradeVolume();
if (volume <= 0m)
return;
SellMarket(volume);
}
private void ManageOpenPositions(ICandleMessage candle)
{
if (Position > 0m)
{
// Manage protective logic for long exposure.
var exitVolume = Position;
if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
{
SellMarket(exitVolume);
return;
}
if (_longTakeProfitPrice.HasValue && candle.HighPrice >= _longTakeProfitPrice.Value)
{
SellMarket(exitVolume);
return;
}
if (UseTrailingStop && _longTrailingStopPrice.HasValue && _lastAtrValue > 0m)
{
var candidate = candle.ClosePrice - _lastAtrValue * TrailingStopAtrMultiplier;
if (candidate > _longTrailingStopPrice.Value)
_longTrailingStopPrice = candidate;
if (candle.LowPrice <= _longTrailingStopPrice.Value)
{
SellMarket(exitVolume);
return;
}
}
}
else if (Position < 0m)
{
// Manage protective logic for short exposure.
var exitVolume = Math.Abs(Position);
if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
{
BuyMarket(exitVolume);
return;
}
if (_shortTakeProfitPrice.HasValue && candle.LowPrice <= _shortTakeProfitPrice.Value)
{
BuyMarket(exitVolume);
return;
}
if (UseTrailingStop && _shortTrailingStopPrice.HasValue && _lastAtrValue > 0m)
{
var candidate = candle.ClosePrice + _lastAtrValue * TrailingStopAtrMultiplier;
if (candidate < _shortTrailingStopPrice.Value)
_shortTrailingStopPrice = candidate;
if (candle.HighPrice >= _shortTrailingStopPrice.Value)
{
BuyMarket(exitVolume);
return;
}
}
}
}
private decimal GetTradeVolume()
{
var volume = TradeVolume;
var security = Security;
if (security == null)
return volume;
var step = security.VolumeStep;
if (step != null && step.Value > 0m)
{
var steps = Math.Max(1m, Math.Round(volume / step.Value, MidpointRounding.AwayFromZero));
volume = steps * step.Value;
}
var minVolume = security.MinVolume;
if (minVolume.HasValue && volume < minVolume.Value)
volume = minVolume.Value;
var maxVolume = security.MaxVolume;
if (maxVolume.HasValue && volume > maxVolume.Value)
volume = maxVolume.Value;
return volume;
}
private bool ShouldBlockByRisk(decimal price)
{
if (MaxRiskPercent <= 0m)
return false;
if (_lastAtrValue <= 0m || StopLossAtrMultiplier <= 0m || price <= 0m)
return false;
var potentialLoss = _lastAtrValue * StopLossAtrMultiplier;
var riskPercent = potentialLoss / price * 100m;
return riskPercent > MaxRiskPercent;
}
private void UpdateAverageBody(ICandleMessage candle)
{
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var value = _bodyAverage.Process(new DecimalIndicatorValue(_bodyAverage, body, candle.OpenTime));
if (value.IsFinal)
_averageBodyValue = value.GetValue<decimal>();
}
private void InitializePipSize()
{
_pipSize = GetPipSize();
_pipSizeInitialized = _pipSize > 0m;
}
private decimal GetPipSize()
{
var security = Security;
if (security == null)
return 0.0001m;
var step = security.PriceStep;
if (step != null && step.Value > 0m)
return step.Value;
var decimals = security.Decimals;
if (decimals.HasValue)
{
var pow = Math.Pow(10, -decimals.Value);
return Convert.ToDecimal(pow);
}
return 0.0001m;
}
private bool IsSpreadWithinLimit()
{
if (MaxSpreadPoints <= 0m)
return true;
var security = Security;
if (security == null)
return true;
var bestBid = GetSecurityValue<decimal?>(Level1Fields.BestBidPrice);
var bestAsk = GetSecurityValue<decimal?>(Level1Fields.BestAskPrice);
if (!bestBid.HasValue || !bestAsk.HasValue || !_pipSizeInitialized || _pipSize <= 0m)
return true;
var spreadPoints = (bestAsk.Value - bestBid.Value) / _pipSize;
return spreadPoints <= MaxSpreadPoints;
}
private void InitializeLongRiskLevels(decimal entryPrice)
{
if (_lastAtrValue <= 0m)
{
ResetLongRiskLevels();
return;
}
_longStopPrice = StopLossAtrMultiplier > 0m ? entryPrice - _lastAtrValue * StopLossAtrMultiplier : null;
_longTakeProfitPrice = TakeProfitAtrMultiplier > 0m ? entryPrice + _lastAtrValue * TakeProfitAtrMultiplier : null;
_longTrailingStopPrice = UseTrailingStop && TrailingStopAtrMultiplier > 0m ? entryPrice - _lastAtrValue * TrailingStopAtrMultiplier : null;
}
private void InitializeShortRiskLevels(decimal entryPrice)
{
if (_lastAtrValue <= 0m)
{
ResetShortRiskLevels();
return;
}
_shortStopPrice = StopLossAtrMultiplier > 0m ? entryPrice + _lastAtrValue * StopLossAtrMultiplier : null;
_shortTakeProfitPrice = TakeProfitAtrMultiplier > 0m ? entryPrice - _lastAtrValue * TakeProfitAtrMultiplier : null;
_shortTrailingStopPrice = UseTrailingStop && TrailingStopAtrMultiplier > 0m ? entryPrice + _lastAtrValue * TrailingStopAtrMultiplier : null;
}
private void ResetLongRiskLevels()
{
_longEntryPrice = null;
_longStopPrice = null;
_longTakeProfitPrice = null;
_longTrailingStopPrice = null;
}
private void ResetShortRiskLevels()
{
_shortEntryPrice = null;
_shortStopPrice = null;
_shortTakeProfitPrice = null;
_shortTrailingStopPrice = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates, Sides, Level1Fields
from StockSharp.Algo.Indicators import RelativeStrengthIndex, SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class revised_self_adaptive_ea_strategy(Strategy):
def __init__(self):
super(revised_self_adaptive_ea_strategy, self).__init__()
self._average_body_period = self.Param("AverageBodyPeriod", 3)
self._moving_average_period = self.Param("MovingAveragePeriod", 2)
self._rsi_period = self.Param("RsiPeriod", 6)
self._atr_period = self.Param("AtrPeriod", 14)
self._volume_param = self.Param("TradeVolume", 1.0)
self._max_spread_points = self.Param("MaxSpreadPoints", 20.0)
self._max_risk_percent = self.Param("MaxRiskPercent", 10.0)
self._use_trailing_stop = self.Param("UseTrailingStop", True)
self._stop_loss_atr_multiplier = self.Param("StopLossAtrMultiplier", 2.0)
self._take_profit_atr_multiplier = self.Param("TakeProfitAtrMultiplier", 4.0)
self._trailing_stop_atr_multiplier = self.Param("TrailingStopAtrMultiplier", 1.5)
self._oversold_level = self.Param("OversoldLevel", 40.0)
self._overbought_level = self.Param("OverboughtLevel", 60.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._rsi = None
self._ma = None
self._atr = None
self._body_average = None
self._previous_candle = None
self._last_atr_value = 0.0
self._average_body_value = 0.0
self._long_entry_price = None
self._long_stop_price = None
self._long_take_profit_price = None
self._long_trailing_stop_price = None
self._short_entry_price = None
self._short_stop_price = None
self._short_take_profit_price = None
self._short_trailing_stop_price = None
@property
def AverageBodyPeriod(self):
return self._average_body_period.Value
@AverageBodyPeriod.setter
def AverageBodyPeriod(self, value):
self._average_body_period.Value = value
@property
def MovingAveragePeriod(self):
return self._moving_average_period.Value
@MovingAveragePeriod.setter
def MovingAveragePeriod(self, value):
self._moving_average_period.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_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 TradeVolume(self):
return self._volume_param.Value
@TradeVolume.setter
def TradeVolume(self, value):
self._volume_param.Value = value
@property
def MaxSpreadPoints(self):
return self._max_spread_points.Value
@MaxSpreadPoints.setter
def MaxSpreadPoints(self, value):
self._max_spread_points.Value = value
@property
def MaxRiskPercent(self):
return self._max_risk_percent.Value
@MaxRiskPercent.setter
def MaxRiskPercent(self, value):
self._max_risk_percent.Value = value
@property
def UseTrailingStop(self):
return self._use_trailing_stop.Value
@UseTrailingStop.setter
def UseTrailingStop(self, value):
self._use_trailing_stop.Value = value
@property
def StopLossAtrMultiplier(self):
return self._stop_loss_atr_multiplier.Value
@StopLossAtrMultiplier.setter
def StopLossAtrMultiplier(self, value):
self._stop_loss_atr_multiplier.Value = value
@property
def TakeProfitAtrMultiplier(self):
return self._take_profit_atr_multiplier.Value
@TakeProfitAtrMultiplier.setter
def TakeProfitAtrMultiplier(self, value):
self._take_profit_atr_multiplier.Value = value
@property
def TrailingStopAtrMultiplier(self):
return self._trailing_stop_atr_multiplier.Value
@TrailingStopAtrMultiplier.setter
def TrailingStopAtrMultiplier(self, value):
self._trailing_stop_atr_multiplier.Value = value
@property
def OversoldLevel(self):
return self._oversold_level.Value
@OversoldLevel.setter
def OversoldLevel(self, value):
self._oversold_level.Value = value
@property
def OverboughtLevel(self):
return self._overbought_level.Value
@OverboughtLevel.setter
def OverboughtLevel(self, value):
self._overbought_level.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(revised_self_adaptive_ea_strategy, self).OnReseted()
self._previous_candle = None
self._last_atr_value = 0.0
self._average_body_value = 0.0
self._reset_long_risk_levels()
self._reset_short_risk_levels()
def _reset_long_risk_levels(self):
self._long_entry_price = None
self._long_stop_price = None
self._long_take_profit_price = None
self._long_trailing_stop_price = None
def _reset_short_risk_levels(self):
self._short_entry_price = None
self._short_stop_price = None
self._short_take_profit_price = None
self._short_trailing_stop_price = None
def OnStarted2(self, time):
super(revised_self_adaptive_ea_strategy, self).OnStarted2(time)
self.Volume = float(self.TradeVolume)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
self._ma = SimpleMovingAverage()
self._ma.Length = self.MovingAveragePeriod
self._atr = AverageTrueRange()
self._atr.Length = self.AtrPeriod
self._body_average = SimpleMovingAverage()
self._body_average.Length = self.AverageBodyPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._ma, self._atr, self._process_candle).Start()
def _update_average_body(self, candle):
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
value = process_float(self._body_average, body, candle.OpenTime, True)
if value.IsFinal:
self._average_body_value = float(to_decimal(value))
def _process_candle(self, candle, rsi_value, ma_value, atr_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
ma_val = float(ma_value)
atr_val = float(atr_value)
self._last_atr_value = atr_val
self._update_average_body(candle)
self._manage_open_positions(candle)
if not self._rsi.IsFormed or not self._ma.IsFormed or not self._atr.IsFormed:
self._previous_candle = candle
return
if self._previous_candle is None:
self._previous_candle = candle
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
prev_close = float(self._previous_candle.ClosePrice)
prev_open = float(self._previous_candle.OpenPrice)
body_size = abs(close - open_p)
minimum_body = self._average_body_value if self._average_body_value > 0 else 0.0
bullish_engulfing = (close > open_p and
prev_close < prev_open and
open_p <= prev_close and
body_size >= minimum_body)
bearish_engulfing = (close < open_p and
prev_close > prev_open and
open_p >= prev_close and
body_size >= minimum_body)
if bullish_engulfing and rsi_val <= float(self.OversoldLevel) and close >= ma_val:
self._try_open_long(close)
elif bearish_engulfing and rsi_val >= float(self.OverboughtLevel) and close <= ma_val:
self._try_open_short(close)
self._previous_candle = candle
def _try_open_long(self, price):
if self.Position > 0:
return
if self.Position < 0:
self.BuyMarket(abs(float(self.Position)))
return
volume = float(self.TradeVolume)
if volume <= 0:
return
self.BuyMarket(volume)
self._initialize_long_risk_levels(price)
def _try_open_short(self, price):
if self.Position < 0:
return
if self.Position > 0:
self.SellMarket(abs(float(self.Position)))
return
volume = float(self.TradeVolume)
if volume <= 0:
return
self.SellMarket(volume)
self._initialize_short_risk_levels(price)
def _manage_open_positions(self, candle):
if self.Position > 0:
exit_volume = float(self.Position)
if self._long_stop_price is not None and float(candle.LowPrice) <= self._long_stop_price:
self.SellMarket(exit_volume)
return
if self._long_take_profit_price is not None and float(candle.HighPrice) >= self._long_take_profit_price:
self.SellMarket(exit_volume)
return
if self.UseTrailingStop and self._long_trailing_stop_price is not None and self._last_atr_value > 0:
candidate = float(candle.ClosePrice) - self._last_atr_value * float(self.TrailingStopAtrMultiplier)
if candidate > self._long_trailing_stop_price:
self._long_trailing_stop_price = candidate
if float(candle.LowPrice) <= self._long_trailing_stop_price:
self.SellMarket(exit_volume)
return
elif self.Position < 0:
exit_volume = abs(float(self.Position))
if self._short_stop_price is not None and float(candle.HighPrice) >= self._short_stop_price:
self.BuyMarket(exit_volume)
return
if self._short_take_profit_price is not None and float(candle.LowPrice) <= self._short_take_profit_price:
self.BuyMarket(exit_volume)
return
if self.UseTrailingStop and self._short_trailing_stop_price is not None and self._last_atr_value > 0:
candidate = float(candle.ClosePrice) + self._last_atr_value * float(self.TrailingStopAtrMultiplier)
if candidate < self._short_trailing_stop_price:
self._short_trailing_stop_price = candidate
if float(candle.HighPrice) >= self._short_trailing_stop_price:
self.BuyMarket(exit_volume)
return
def _initialize_long_risk_levels(self, entry_price):
if self._last_atr_value <= 0:
self._reset_long_risk_levels()
return
sl_mult = float(self.StopLossAtrMultiplier)
tp_mult = float(self.TakeProfitAtrMultiplier)
trail_mult = float(self.TrailingStopAtrMultiplier)
self._long_stop_price = entry_price - self._last_atr_value * sl_mult if sl_mult > 0 else None
self._long_take_profit_price = entry_price + self._last_atr_value * tp_mult if tp_mult > 0 else None
self._long_trailing_stop_price = entry_price - self._last_atr_value * trail_mult if self.UseTrailingStop and trail_mult > 0 else None
def _initialize_short_risk_levels(self, entry_price):
if self._last_atr_value <= 0:
self._reset_short_risk_levels()
return
sl_mult = float(self.StopLossAtrMultiplier)
tp_mult = float(self.TakeProfitAtrMultiplier)
trail_mult = float(self.TrailingStopAtrMultiplier)
self._short_stop_price = entry_price + self._last_atr_value * sl_mult if sl_mult > 0 else None
self._short_take_profit_price = entry_price - self._last_atr_value * tp_mult if tp_mult > 0 else None
self._short_trailing_stop_price = entry_price + self._last_atr_value * trail_mult if self.UseTrailingStop and trail_mult > 0 else None
def CreateClone(self):
return revised_self_adaptive_ea_strategy()