Revised Self Adaptive EA
MetaTrader 5 专家顾问 revised_self_adaptive_ea.mq5 的 StockSharp 高级 API 版本。
策略说明
- 形态识别:检测最近两根已收盘 K 线是否形成吞没形态。看涨信号要求当前 K 线实体为阳线、开盘价低于前一根收盘价且前一根为阴线;看跌信号要求相反条件。实体大小与平均实体比较以过滤噪声。
- 动量过滤:RSI 进入超卖区(看涨)或超买区(看跌)后才允许下单。
- 趋势过滤:短周期简单移动平均必须与进场方向一致,防止逆势交易。
- 风控机制:依据 ATR 计算止损、止盈与可选的移动止损,若价格触发保护位则立即市价平仓。
- 点差与风险限制:当前买卖价差超出阈值或 ATR 止损对应的风险比例超过上限时,信号会被跳过。
主要参数
CandleType:分析所用的 K 线周期(默认 1 小时)。AverageBodyPeriod:计算平均实体长度的样本数。MovingAveragePeriod:趋势过滤所用的 SMA 周期。RsiPeriod、OversoldLevel、OverboughtLevel:RSI 相关设定。AtrPeriod、StopLossAtrMultiplier、TakeProfitAtrMultiplier、TrailingStopAtrMultiplier、UseTrailingStop:ATR 风控相关配置。MaxSpreadPoints:允许的最大点差(以最小跳动单位表示)。MaxRiskPercent:基于 ATR 止损计算的最大可接受风险百分比。TradeVolume:下单手数。
与原始 MQL 的差异
- 原脚本只提供信号检测,本移植补充了移动平均确认以及 ATR 风控模块,以充分利用 MQL 中声明的指标。
- 在 StockSharp 中未实现箭头绘制,仅保留核心交易逻辑。
- 新增风险比例过滤,避免 ATR 止损过大的信号。
使用步骤
- 在 Hydra 或自定义主机中指定投资组合与标的证券。
- 根据交易计划设置参数,特别是 ATR 与点差限制。
- 启动策略后系统将自动订阅 K 线、计算指标并在条件满足时发送市价单。
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()