Стратегия AdaptiveTrader Pro
Общее описание
AdaptiveTrader Pro — это многотаймфреймовая трендовая стратегия, перенесённая из эксперта MetaTrader 5 AdaptiveTrader_Pro_Final_EA.mq5. Она сочетает RSI, ATR и две скользящие средние, чтобы торговать по направлению доминирующего тренда и одновременно контролировать риски.
Стратегия работает на настраиваемом основном таймфрейме (по умолчанию 5 минут) и подтверждает направление тенденции с помощью скользящей средней на старшем таймфрейме (по умолчанию 1 час). Сигналы на вход формируются при экстремальных значениях RSI, если цена расположена по правильную сторону обеих скользящих средних.
Правила торговли
- Покупка: RSI опускается ниже 30, а цена закрытия свечи находится выше SMA на основном и старшем таймфреймах.
- Продажа: RSI поднимается выше 70, а цена закрытия свечи находится ниже обеих SMA.
- Одна позиция: В рынке поддерживается только одна направленная позиция; перед разворотом противоположная позиция закрывается.
Управление рисками и сделками
- Размер позиции: Рассчитывается автоматически из капитала портфеля, доли риска и дистанции стопа на базе ATR.
- Стопы: Используется плавающий стоп по ATR, который при движении цены в прибыльную сторону подтягивается к точке безубыточности после достижения заданного множителя ATR.
- Частичная фиксация: Доля позиции фиксируется на первом целевом уровне (множитель ATR), остаток управляется трейлинг-стопом.
Параметры
| Параметр | Описание | Значение по умолчанию |
|---|---|---|
MaxRiskPercent |
Процент капитала, рискуемый в одной сделке. | 0.2 |
RsiPeriod |
Период RSI на основном таймфрейме. | 14 |
AtrPeriod |
Период ATR на основном таймфрейме. | 14 |
AtrMultiplier |
Множитель ATR для расчёта изначального стопа. | 1.5 |
TrailingStopMultiplier |
Множитель ATR для трейлинг-стопа. | 1.0 |
TrailingTakeProfitMultiplier |
Множитель ATR для первой цели по прибыли. | 2.0 |
TrendPeriod |
Период SMA на основном таймфрейме. | 20 |
HigherTrendPeriod |
Период SMA на старшем таймфрейме. | 50 |
BreakEvenMultiplier |
Множитель ATR, при котором стоп переносится в безубыток. | 1.5 |
PartialCloseFraction |
Доля позиции, закрываемая на первой цели. | 0.5 |
MaxSpreadPoints |
Максимально допустимый спред в шагах цены для открытия сделок. | 20 |
CandleType |
Тип свечей основного таймфрейма. | 5-минутные свечи |
HigherCandleType |
Тип свечей старшего таймфрейма. | 1-часовые свечи |
Дополнительно
- Стратегия использует высокоуровневый API StockSharp: свечные подписки и привязку индикаторов.
- Контроль спреда выполняется по лучшим котировкам Bid/Ask; если спред превышает лимит, новые сделки не открываются.
- По требованию Python-версия не создаётся.
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Adaptive multi-timeframe strategy converted from the "AdaptiveTrader Pro" expert advisor.
/// Combines RSI, ATR and dual moving averages to align entries with the prevailing trend.
/// Applies risk-based position sizing, partial profit taking, break-even logic and ATR driven trailing stops.
/// </summary>
public class AdaptiveTraderProStrategy : Strategy
{
private readonly StrategyParam<decimal> _maxRiskPercent;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<decimal> _trailingStopMultiplier;
private readonly StrategyParam<decimal> _trailingTakeProfitMultiplier;
private readonly StrategyParam<int> _trendPeriod;
private readonly StrategyParam<int> _higherTrendPeriod;
private readonly StrategyParam<decimal> _breakEvenMultiplier;
private readonly StrategyParam<decimal> _partialCloseFraction;
private readonly StrategyParam<decimal> _maxSpreadPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _higherCandleType;
private decimal? _bestBidPrice;
private decimal? _bestAskPrice;
private decimal _lastHigherTrendValue;
private decimal _entryPrice;
private decimal _entryVolume;
private decimal _entryAtr;
private bool _breakEvenApplied;
private bool _partialTakeProfitDone;
private decimal _trailingStopLevel;
/// <summary>
/// Maximum risk percentage allocated per trade.
/// </summary>
public decimal MaxRiskPercent
{
get => _maxRiskPercent.Value;
set => _maxRiskPercent.Value = value;
}
/// <summary>
/// RSI period used on the main timeframe.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// ATR period used on the main timeframe.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Multiplier applied to ATR for stop-loss sizing.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Multiplier applied to ATR for trailing stop adjustments.
/// </summary>
public decimal TrailingStopMultiplier
{
get => _trailingStopMultiplier.Value;
set => _trailingStopMultiplier.Value = value;
}
/// <summary>
/// Multiplier applied to ATR for the partial take-profit objective.
/// </summary>
public decimal TrailingTakeProfitMultiplier
{
get => _trailingTakeProfitMultiplier.Value;
set => _trailingTakeProfitMultiplier.Value = value;
}
/// <summary>
/// Moving average period used on the main timeframe.
/// </summary>
public int TrendPeriod
{
get => _trendPeriod.Value;
set => _trendPeriod.Value = value;
}
/// <summary>
/// Moving average period used on the higher timeframe.
/// </summary>
public int HigherTrendPeriod
{
get => _higherTrendPeriod.Value;
set => _higherTrendPeriod.Value = value;
}
/// <summary>
/// ATR multiplier that defines when to move the stop to break even.
/// </summary>
public decimal BreakEvenMultiplier
{
get => _breakEvenMultiplier.Value;
set => _breakEvenMultiplier.Value = value;
}
/// <summary>
/// Fraction of the initial position closed at the first target.
/// </summary>
public decimal PartialCloseFraction
{
get => _partialCloseFraction.Value;
set => _partialCloseFraction.Value = value;
}
/// <summary>
/// Maximum allowed spread expressed in price steps.
/// </summary>
public decimal MaxSpreadPoints
{
get => _maxSpreadPoints.Value;
set => _maxSpreadPoints.Value = value;
}
/// <summary>
/// Candle type used on the main timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Candle type used for higher timeframe confirmation.
/// </summary>
public DataType HigherCandleType
{
get => _higherCandleType.Value;
set => _higherCandleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveTraderProStrategy"/> class.
/// </summary>
public AdaptiveTraderProStrategy()
{
_maxRiskPercent = Param(nameof(MaxRiskPercent), 0.2m)
.SetGreaterThanZero()
.SetDisplay("Max Risk %", "Risk percentage applied on each trade", "Risk Management");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of the RSI indicator", "Indicators")
.SetOptimize(8, 20, 1);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Length of the ATR indicator", "Indicators")
.SetOptimize(7, 21, 1);
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Multiplier applied to ATR for stops", "Risk Management")
.SetOptimize(1.0m, 3.0m, 0.5m);
_trailingStopMultiplier = Param(nameof(TrailingStopMultiplier), 3.0m)
.SetGreaterThanZero()
.SetDisplay("Trailing Stop Multiplier", "ATR multiplier for trailing stop", "Risk Management")
.SetOptimize(0.5m, 2.5m, 0.5m);
_trailingTakeProfitMultiplier = Param(nameof(TrailingTakeProfitMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Trailing TP Multiplier", "ATR multiplier for partial profit", "Risk Management")
.SetOptimize(1.0m, 3.0m, 0.5m);
_trendPeriod = Param(nameof(TrendPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Main Trend Period", "SMA length on the main timeframe", "Indicators");
_higherTrendPeriod = Param(nameof(HigherTrendPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Higher Trend Period", "SMA length on the higher timeframe", "Indicators");
_breakEvenMultiplier = Param(nameof(BreakEvenMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Break Even Multiplier", "ATR multiplier that activates break even", "Risk Management");
_partialCloseFraction = Param(nameof(PartialCloseFraction), 0m)
.SetDisplay("Partial Close Fraction", "Fraction of the volume closed at the first target", "Risk Management");
_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 20m)
.SetDisplay("Max Spread (points)", "Maximum allowed spread in price steps", "Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Main Candle Type", "Primary timeframe used for signals", "General");
_higherCandleType = Param(nameof(HigherCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Higher Candle Type", "Confirmation timeframe used for trend", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
if (HigherCandleType != CandleType)
yield return (Security, HigherCandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_entryVolume = 0m;
_entryAtr = 0m;
_breakEvenApplied = false;
_partialTakeProfitDone = false;
_trailingStopLevel = 0m;
_bestBidPrice = null;
_bestAskPrice = null;
_lastHigherTrendValue = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetTradeState();
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var trendMa = new SimpleMovingAverage { Length = TrendPeriod };
var higherTrendMa = new SimpleMovingAverage { Length = HigherTrendPeriod };
var mainSubscription = SubscribeCandles(CandleType);
mainSubscription.Bind(rsi, atr, trendMa, ProcessMainCandle).Start();
var higherSubscription = SubscribeCandles(HigherCandleType);
higherSubscription.Bind(higherTrendMa, ProcessHigherCandle).Start();
}
private void ProcessHigherCandle(ICandleMessage candle, decimal higherTrend)
{
if (candle.State != CandleStates.Finished)
return;
_lastHigherTrendValue = higherTrend;
}
private void ProcessMainCandle(ICandleMessage candle, decimal rsiValue, decimal atrValue, decimal trendValue)
{
if (candle.State != CandleStates.Finished)
return;
//if (!IsFormedAndOnlineAndAllowTrading())
// return;
//if (!_hasHigherTrend)
// return;
//if (!IsSpreadAllowed())
// return;
UpdateTrailingManagement(candle, atrValue);
if (Position != 0m)
return;
if (atrValue <= 0m)
return;
var closePrice = candle.ClosePrice;
if (rsiValue < 45m && closePrice > trendValue)
{
TryEnterLong(closePrice, atrValue);
}
else if (rsiValue > 55m && closePrice < trendValue)
{
TryEnterShort(closePrice, atrValue);
}
}
private void TryEnterLong(decimal entryPrice, decimal atrValue)
{
if (Position < 0m)
{
BuyMarket();
return;
}
BuyMarket();
InitializeTradeState(1, entryPrice, atrValue, Volume > 0 ? Volume : 1m);
}
private void TryEnterShort(decimal entryPrice, decimal atrValue)
{
if (Position > 0m)
{
SellMarket();
return;
}
SellMarket();
InitializeTradeState(-1, entryPrice, atrValue, Volume > 0 ? Volume : 1m);
}
private void UpdateTrailingManagement(ICandleMessage candle, decimal atrValue)
{
if (Position > 0m)
{
var atrForTargets = _entryAtr > 0m ? _entryAtr : atrValue;
var trailingDistance = atrValue * TrailingStopMultiplier;
var candidateStop = candle.ClosePrice - trailingDistance;
if (_trailingStopLevel <= 0m || candidateStop > _trailingStopLevel)
_trailingStopLevel = candidateStop;
if (!_breakEvenApplied && atrForTargets > 0m)
{
var breakEvenTrigger = _entryPrice + atrForTargets * BreakEvenMultiplier;
if (candle.HighPrice >= breakEvenTrigger)
{
_trailingStopLevel = Math.Max(_trailingStopLevel, _entryPrice);
_breakEvenApplied = true;
}
}
if (!_partialTakeProfitDone && PartialCloseFraction > 0m && PartialCloseFraction < 1m && atrForTargets > 0m)
{
var partialTarget = _entryPrice + atrForTargets * TrailingTakeProfitMultiplier;
if (candle.HighPrice >= partialTarget)
{
var desiredVolume = NormalizeVolume(_entryVolume * PartialCloseFraction);
var availableVolume = Math.Max(Position, 0m);
var volumeToClose = Math.Min(availableVolume, desiredVolume);
if (volumeToClose > 0m)
{
SellMarket(volumeToClose);
_partialTakeProfitDone = true;
}
}
}
if (_trailingStopLevel > 0m && candle.LowPrice <= _trailingStopLevel)
{
SellMarket(Math.Max(Position, 0m));
ResetTradeState();
}
}
else if (Position < 0m)
{
var atrForTargets = _entryAtr > 0m ? _entryAtr : atrValue;
var trailingDistance = atrValue * TrailingStopMultiplier;
var candidateStop = candle.ClosePrice + trailingDistance;
if (_trailingStopLevel <= 0m || candidateStop < _trailingStopLevel)
_trailingStopLevel = candidateStop;
if (!_breakEvenApplied && atrForTargets > 0m)
{
var breakEvenTrigger = _entryPrice - atrForTargets * BreakEvenMultiplier;
if (candle.LowPrice <= breakEvenTrigger)
{
_trailingStopLevel = Math.Min(_trailingStopLevel, _entryPrice);
_breakEvenApplied = true;
}
}
if (!_partialTakeProfitDone && PartialCloseFraction > 0m && PartialCloseFraction < 1m && atrForTargets > 0m)
{
var partialTarget = _entryPrice - atrForTargets * TrailingTakeProfitMultiplier;
if (candle.LowPrice <= partialTarget)
{
var desiredVolume = NormalizeVolume(_entryVolume * PartialCloseFraction);
var availableVolume = Math.Max(Math.Abs(Position), 0m);
var volumeToClose = Math.Min(availableVolume, desiredVolume);
if (volumeToClose > 0m)
{
BuyMarket(volumeToClose);
_partialTakeProfitDone = true;
}
}
}
if (_trailingStopLevel > 0m && candle.HighPrice >= _trailingStopLevel)
{
BuyMarket(Math.Abs(Position));
ResetTradeState();
}
}
else
{
ResetTradeState();
}
}
private bool IsSpreadAllowed()
{
if (MaxSpreadPoints <= 0m)
return true;
if (_bestBidPrice is not decimal bid || _bestAskPrice is not decimal ask)
return false;
var step = Security?.PriceStep ?? 1m;
if (step <= 0m)
step = 1m;
var spreadPoints = (ask - bid) / step;
return spreadPoints <= MaxSpreadPoints;
}
// Quote handling removed - not needed for backtest
private void InitializeTradeState(int direction, decimal entryPrice, decimal atrValue, decimal volume)
{
_entryPrice = entryPrice;
_entryVolume = volume;
_entryAtr = atrValue;
_breakEvenApplied = false;
_partialTakeProfitDone = false;
_trailingStopLevel = direction == 1
? entryPrice - atrValue * TrailingStopMultiplier
: entryPrice + atrValue * TrailingStopMultiplier;
}
private void ResetTradeState()
{
_entryPrice = 0m;
_entryVolume = 0m;
_entryAtr = 0m;
_breakEvenApplied = false;
_partialTakeProfitDone = false;
_trailingStopLevel = 0m;
}
private decimal CalculateOrderVolume(decimal atrValue)
{
return Volume > 0 ? Volume : 1m;
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
return Volume > 0 ? Volume : 1m;
return volume;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class adaptive_trader_pro_strategy(Strategy):
def __init__(self):
super(adaptive_trader_pro_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._rsi_period = self.Param("RsiPeriod", 14)
self._atr_period = self.Param("AtrPeriod", 14)
self._trailing_stop_multiplier = self.Param("TrailingStopMultiplier", 3.0)
self._break_even_multiplier = self.Param("BreakEvenMultiplier", 1.5)
self._trend_period = self.Param("TrendPeriod", 20)
self._entry_price = 0.0
self._entry_atr = 0.0
self._break_even_applied = False
self._trailing_stop_level = 0.0
self._direction = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.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 TrailingStopMultiplier(self):
return self._trailing_stop_multiplier.Value
@TrailingStopMultiplier.setter
def TrailingStopMultiplier(self, value):
self._trailing_stop_multiplier.Value = value
@property
def BreakEvenMultiplier(self):
return self._break_even_multiplier.Value
@BreakEvenMultiplier.setter
def BreakEvenMultiplier(self, value):
self._break_even_multiplier.Value = value
@property
def TrendPeriod(self):
return self._trend_period.Value
@TrendPeriod.setter
def TrendPeriod(self, value):
self._trend_period.Value = value
def OnReseted(self):
super(adaptive_trader_pro_strategy, self).OnReseted()
self._entry_price = 0.0
self._entry_atr = 0.0
self._break_even_applied = False
self._trailing_stop_level = 0.0
self._direction = 0
def OnStarted2(self, time):
super(adaptive_trader_pro_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._entry_atr = 0.0
self._break_even_applied = False
self._trailing_stop_level = 0.0
self._direction = 0
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
trend_ma = SimpleMovingAverage()
trend_ma.Length = self.TrendPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, atr, trend_ma, self._process_candle).Start()
def _reset_trade_state(self):
self._entry_price = 0.0
self._entry_atr = 0.0
self._break_even_applied = False
self._trailing_stop_level = 0.0
self._direction = 0
def _process_candle(self, candle, rsi_value, atr_value, trend_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
atr_val = float(atr_value)
trend_val = float(trend_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
trail_mult = float(self.TrailingStopMultiplier)
be_mult = float(self.BreakEvenMultiplier)
# Trailing stop management
if self._direction > 0 and self.Position > 0:
atr_for_targets = self._entry_atr if self._entry_atr > 0 else atr_val
trailing_distance = atr_val * trail_mult
candidate_stop = close - trailing_distance
if self._trailing_stop_level <= 0 or candidate_stop > self._trailing_stop_level:
self._trailing_stop_level = candidate_stop
if not self._break_even_applied and atr_for_targets > 0:
be_trigger = self._entry_price + atr_for_targets * be_mult
if high >= be_trigger:
self._trailing_stop_level = max(self._trailing_stop_level, self._entry_price)
self._break_even_applied = True
if self._trailing_stop_level > 0 and low <= self._trailing_stop_level:
self.SellMarket()
self._reset_trade_state()
return
elif self._direction < 0 and self.Position < 0:
atr_for_targets = self._entry_atr if self._entry_atr > 0 else atr_val
trailing_distance = atr_val * trail_mult
candidate_stop = close + trailing_distance
if self._trailing_stop_level <= 0 or candidate_stop < self._trailing_stop_level:
self._trailing_stop_level = candidate_stop
if not self._break_even_applied and atr_for_targets > 0:
be_trigger = self._entry_price - atr_for_targets * be_mult
if low <= be_trigger:
self._trailing_stop_level = min(self._trailing_stop_level, self._entry_price)
self._break_even_applied = True
if self._trailing_stop_level > 0 and high >= self._trailing_stop_level:
self.BuyMarket()
self._reset_trade_state()
return
else:
if self.Position == 0:
self._reset_trade_state()
if self.Position != 0:
return
if atr_val <= 0:
return
# Entry logic
if rsi_val < 45.0 and close > trend_val:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._entry_atr = atr_val
self._direction = 1
self._trailing_stop_level = close - atr_val * trail_mult
self._break_even_applied = False
elif rsi_val > 55.0 and close < trend_val:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._entry_atr = atr_val
self._direction = -1
self._trailing_stop_level = close + atr_val * trail_mult
self._break_even_applied = False
def CreateClone(self):
return adaptive_trader_pro_strategy()