The Exp Amstell Strategy is a grid trading system converted from the original MetaTrader 4 expert advisor exp_Amstell.mq4. It continuously places buy and sell market orders whenever price travels a configurable number of points away from the most recent fill. Every individual trade is managed independently: once the market moves by the specified take-profit distance, the strategy sends an offsetting order to capture the profit for that single layer.
Unlike momentum-driven systems, Exp Amstell remains active at all times. It does not wait for indicator confirmations and instead accumulates positions on both sides of the book as the market oscillates. This behaviour makes it highly sensitive to the chosen point distances and to the size of each order.
Trading Logic
Tick-based processing. The strategy subscribes to level1 quotes and reacts to every change in best bid and best ask, just like the start() function in the original MQL code.
Independent long and short stacks. Buy orders are allowed when no long trades are open or when the ask price has dropped by at least the re-entry distance from the latest long entry. Sell orders use the symmetrical condition on the bid price.
Per-trade take profit. Each open layer is tracked separately. When the bid (for longs) or ask (for shorts) advances by the configured take-profit points, the strategy closes only that layer with a market order. Other layers remain untouched.
FIFO emulation. Executed trades are recorded in FIFO order to reproduce the ticket-based accounting that MetaTrader applies to hedged positions. This guarantees that partial fills reduce the oldest outstanding layer first.
Netted portfolio awareness. StockSharp maintains net positions. If a new buy order offsets an open short layer, the strategy removes that short from its synthetic stack before recording the remainder as a new long position.
Parameters
Name
Type
Default
Description
TradeVolume
decimal
0.1
Volume of every market order that opens a new grid layer.
TakeProfitPoints
int
30
Distance in MetaTrader points that must be covered by price before an individual layer is closed.
ReentryDistancePoints
int
10
Minimal point distance from the latest entry before adding another order on the same side.
The strategy automatically converts points into actual price steps using the instrument’s PriceStep. Five-digit and three-digit quotes receive the MetaTrader-specific multiplier so that 1 point equals 0.0001 (or 0.01 for JPY-style symbols).
Implementation Notes
Level1 data is sufficient; no candle subscription is required. The strategy declares this by overriding GetWorkingSecurities() and requesting (Security, DataType.Level1).
StartProtection() is invoked during OnStarted to guarantee that the runner closes any leftover position if the strategy stops unexpectedly.
All comments inside the C# file remain in English, matching the project guidelines.
Because StockSharp uses netted positions, the port cannot keep opposing buys and sells open simultaneously. When both sides trade at the same time the newer order will flatten the existing exposure before creating a fresh layer.
Usage Tips
Calibrate the point distances. Smaller distances create denser grids that can overtrade in volatile markets. Larger distances reduce activity but increase drawdown per layer.
Size orders prudently. Grid systems accumulate exposure quickly. Test conservative volumes in the Designer/Backtester before switching to live trading.
Consider manual risk controls. The original expert has no global stop-loss. Combine the strategy with portfolio-level protections to cap tail risk.
Monitor execution quality. The algorithm assumes that market orders fill near the best bid/ask. Slippage directly affects achieved take-profit distances.
Source
Converted from MQL/9027/exp_Amstell.mq4.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Exp Amstell: Grid-style strategy that scales into positions
/// on ATR-based price movements and closes on profit targets.
/// </summary>
public class ExpAmstellStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<int> _emaLength;
private decimal _entryPrice;
private decimal _prevEma;
private int _gridCount;
public ExpAmstellStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for grid distance.", "Indicators");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "EMA period for trend.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_prevEma = 0;
_gridCount = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_prevEma = 0;
_gridCount = 0;
var atr = new AverageTrueRange { Length = AtrLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0 || _prevEma == 0)
{
_prevEma = emaVal;
return;
}
var close = candle.ClosePrice;
// Grid exit: take profit at 1.5 ATR or stop at 3 ATR
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 1.5m)
{
SellMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (close <= _entryPrice - atrVal * 3m)
{
SellMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (_gridCount < 3 && close <= _entryPrice - atrVal)
{
// Scale in: add to position on pullback
_entryPrice = (_entryPrice + close) / 2m;
_gridCount++;
BuyMarket();
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 1.5m)
{
BuyMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (close >= _entryPrice + atrVal * 3m)
{
BuyMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (_gridCount < 3 && close >= _entryPrice + atrVal)
{
_entryPrice = (_entryPrice + close) / 2m;
_gridCount++;
SellMarket();
}
}
// Entry: EMA direction determines initial direction
if (Position == 0)
{
if (close > emaVal && emaVal > _prevEma)
{
_entryPrice = close;
_gridCount = 0;
BuyMarket();
}
else if (close < emaVal && emaVal < _prevEma)
{
_entryPrice = close;
_gridCount = 0;
SellMarket();
}
}
_prevEma = emaVal;
}
}
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 AverageTrueRange, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_amstell_strategy(Strategy):
"""
Grid-style strategy that scales into positions on ATR-based price movements.
Enters based on EMA direction, scales in on pullbacks, exits on ATR-based TP/SL.
"""
def __init__(self):
super(exp_amstell_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for grid distance", "Indicators")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA period for trend", "Indicators")
self._entry_price = 0.0
self._prev_ema = 0.0
self._grid_count = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_amstell_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_ema = 0.0
self._grid_count = 0
def OnStarted2(self, time):
super(exp_amstell_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._prev_ema = 0.0
self._grid_count = 0
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, atr_val, ema_val):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr_val)
ema_val = float(ema_val)
if atr_val <= 0 or self._prev_ema == 0:
self._prev_ema = ema_val
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + atr_val * 1.5:
self.SellMarket()
self._entry_price = 0.0
self._grid_count = 0
elif close <= self._entry_price - atr_val * 3.0:
self.SellMarket()
self._entry_price = 0.0
self._grid_count = 0
elif self._grid_count < 3 and close <= self._entry_price - atr_val:
self._entry_price = (self._entry_price + close) / 2.0
self._grid_count += 1
self.BuyMarket()
elif self.Position < 0:
if close <= self._entry_price - atr_val * 1.5:
self.BuyMarket()
self._entry_price = 0.0
self._grid_count = 0
elif close >= self._entry_price + atr_val * 3.0:
self.BuyMarket()
self._entry_price = 0.0
self._grid_count = 0
elif self._grid_count < 3 and close >= self._entry_price + atr_val:
self._entry_price = (self._entry_price + close) / 2.0
self._grid_count += 1
self.SellMarket()
if self.Position == 0:
if close > ema_val and ema_val > self._prev_ema:
self._entry_price = close
self._grid_count = 0
self.BuyMarket()
elif close < ema_val and ema_val < self._prev_ema:
self._entry_price = close
self._grid_count = 0
self.SellMarket()
self._prev_ema = ema_val
def CreateClone(self):
return exp_amstell_strategy()