The AMA Trader 2 strategy replicates the averaging workflow of the original MetaTrader expert by Vladimir Karputov. It combines a Kaufman Adaptive Moving Average (AMA) trend filter with a Relative Strength Index (RSI) confirmation block. When price closes above the AMA and the RSI dips into oversold territory the strategy adds long exposure; the symmetric rule applies to short trades when price closes below the AMA while RSI prints an overbought reading. Averaging trades are submitted in fixed lot sizes and can be constrained through risk parameters such as maximum position count, minimum entry spacing, and protective trailing stops.
Market Assumptions
Instrument: Designed for FX/CFD symbols traded with tight spreads, but applicable to any liquid instrument where averaging is acceptable.
Data: Operates on finished time-based candles. The timeframe is configurable through the CandleType parameter (default: 1 minute).
Sessions: Optional intraday window. Trading can be confined to a start/end time in UTC with the UseTimeWindow flag.
Indicators
Kaufman Adaptive Moving Average (AMA) – detects the prevailing trend with configurable fast/slow smoothing constants and averaging length.
Relative Strength Index (RSI) – validates momentum extremes. The number of consecutive RSI readings that must confirm a signal is controlled by StepLength (0 behaves like 1, matching the MQL version).
Trading Logic
Process only finished candles and ensure the strategy is online and allowed to trade.
Apply the optional time filter; skip processing outside the intraday window when enabled.
Update the queue of recent RSI values and compute trailing-stop adjustments for existing exposure.
Long setup: close price above AMA and at least one of the inspected RSI values below RsiLevelDown. If the active long position is losing money, an averaging order is queued before the standard entry, mimicking the "loss recovery" behaviour of the expert advisor. Short signals follow the symmetric rule (RsiLevelUp).
Entries honour MaxPositions, MinStep, and OnlyOnePosition. When CloseOpposite is enabled the strategy first offsets the opposing side and only considers new entries after the flattening trade is confirmed.
Every new position can attach fixed stop-loss/take-profit distances and optionally enable a profit-based trailing stop with activation, distance, and step thresholds.
Risk Management
Fixed lot size: All entries use LotSize, allowing position sizing via the parameter or the hosting portfolio.
Maximum averaging depth: MaxPositions limits how many times exposure can be increased per direction.
Spacing control: MinStep enforces a minimum price distance between consecutive entries, reducing clustering at the same level.
Protective exits: Optional stop-loss, take-profit, and trailing logic replicate the MetaTrader expert's protective toolkit.
Opposite exposure: CloseOpposite forces the strategy to close shorts before opening a long (and vice versa). OnlyOnePosition ensures the strategy never keeps both sides simultaneously.
Parameters
Parameter
Description
CandleType
Candle data type/timeframe used for calculations.
LotSize
Volume for each market order.
RsiLength
RSI averaging period.
StepLength
Number of recent RSI readings inspected (0 → 1).
RsiLevelUp
RSI overbought threshold for short entries.
RsiLevelDown
RSI oversold threshold for long entries.
AmaLength
AMA smoothing length.
AmaFastPeriod
Fast smoothing constant for AMA.
AmaSlowPeriod
Slow smoothing constant for AMA.
StopLoss
Fixed stop distance in price units (0 disables).
TakeProfit
Fixed target distance in price units (0 disables).
TrailingActivation
Profit required to arm the trailing stop (0 disables).
TrailingDistance
Distance maintained by the trailing stop.
TrailingStep
Minimum improvement before the trailing stop is tightened.
MaxPositions
Maximum averaging entries per direction (0 disables).
MinStep
Minimum distance between consecutive entries (0 disables).
CloseOpposite
Close opposite exposure before opening a trade.
OnlyOnePosition
Block new entries whenever any position exists.
UseTimeWindow
Enable intraday start/end time filtering.
StartTime
Session start time (UTC) when the window is enabled.
EndTime
Session end time (UTC) when the window is enabled.
Implementation Notes
High-level API only: candles are subscribed via SubscribeCandles, AMA and RSI are bound with .Bind, and all computations happen in the bound callback without using prohibited indicator getters.
Position accounting mirrors the MQL expert: separate accumulators track long and short volumes/average prices to evaluate unrealized PnL for averaging decisions.
Trailing stops reconfigure the strategy-level stop-loss distance instead of manipulating order queues directly, keeping compatibility with the StockSharp execution model.
Signals are restricted to one execution per bar per side, reproducing the MetaTrader check that prevents duplicate entries on the same candle.
Differences from the MetaTrader Expert
MetaTrader-specific parameters such as magic numbers, deviation, freeze level checks, and tester withdrawal emulation are omitted. The StockSharp environment manages order slippage and fees internally.
Stop/limit prices are calculated from the candle close rather than bid/ask ticks. This matches StockSharp's candle-based workflow.
The original EA uses account margin settings to compute dynamic lot sizes. The port keeps a fixed LotSize, leaving risk-based sizing to the hosting environment.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Adaptive moving average strategy with RSI confirmation.
/// Simplified from the "AMA Trader 2" MetaTrader expert using EMA + RSI.
/// </summary>
public class AmaTrader2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _rsiLevelUp;
private readonly StrategyParam<decimal> _rsiLevelDown;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema;
private decimal? _prevPrice;
private decimal? _prevEma;
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// EMA period.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiLevelUp
{
get => _rsiLevelUp.Value;
set => _rsiLevelUp.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiLevelDown
{
get => _rsiLevelDown.Value;
set => _rsiLevelDown.Value = value;
}
public AmaTrader2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_emaLength = Param(nameof(EmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "Adaptive MA period", "Indicators");
_rsiLevelUp = Param(nameof(RsiLevelUp), 60m)
.SetDisplay("RSI Up", "RSI bullish confirmation threshold", "Signals");
_rsiLevelDown = Param(nameof(RsiLevelDown), 40m)
.SetDisplay("RSI Down", "RSI bearish confirmation threshold", "Signals");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevPrice = null;
_prevEma = null;
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_ema.IsFormed)
{
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
return;
}
if (_prevPrice is null || _prevEma is null)
{
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Price crosses above EMA + RSI confirms bullish
var priceAboveEma = candle.ClosePrice > emaValue;
var prevBelowEma = _prevPrice < _prevEma;
if (priceAboveEma && prevBelowEma && rsiValue > RsiLevelUp)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Price crosses below EMA + RSI confirms bearish
else if (!priceAboveEma && !prevBelowEma && rsiValue < RsiLevelDown)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_ema = null;
_prevPrice = null;
_prevEma = null;
base.OnReseted();
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ama_trader2_strategy(Strategy):
def __init__(self):
super(ama_trader2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._rsi_length = self.Param("RsiLength", 14)
self._ema_length = self.Param("EmaLength", 50)
self._rsi_level_up = self.Param("RsiLevelUp", 60.0)
self._rsi_level_down = self.Param("RsiLevelDown", 40.0)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiLength(self):
return self._rsi_length.Value
@RsiLength.setter
def RsiLength(self, value):
self._rsi_length.Value = value
@property
def EmaLength(self):
return self._ema_length.Value
@EmaLength.setter
def EmaLength(self, value):
self._ema_length.Value = value
@property
def RsiLevelUp(self):
return self._rsi_level_up.Value
@RsiLevelUp.setter
def RsiLevelUp(self, value):
self._rsi_level_up.Value = value
@property
def RsiLevelDown(self):
return self._rsi_level_down.Value
@RsiLevelDown.setter
def RsiLevelDown(self, value):
self._rsi_level_down.Value = value
def OnReseted(self):
super(ama_trader2_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(ama_trader2_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiLength
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ema, self._process_candle).Start()
def _process_candle(self, candle, rsi_value, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi_val = float(rsi_value)
ema_val = float(ema_value)
if self._has_prev:
price_above_ema = close > ema_val
prev_below_ema = self._prev_close < self._prev_ema
if price_above_ema and prev_below_ema and rsi_val > float(self.RsiLevelUp) and self.Position <= 0:
self.BuyMarket()
elif not price_above_ema and not prev_below_ema and rsi_val < float(self.RsiLevelDown) and self.Position >= 0:
self.SellMarket()
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
def CreateClone(self):
return ama_trader2_strategy()