This strategy replicates the MetaTrader "freeman" expert advisor using StockSharp's high level API. It layers multiple market positions while a trend measured by a moving average slope stays aligned with an RSI confirmation. Every entry and exit distance is defined in pips and converted to price units using the instrument tick size so the behaviour matches the original forex implementation.
Trading Logic
Subscribe to a single candle series (configurable timeframe) and update the ATR, moving average and RSI indicators on each finished candle.
Generate a directional signal when:
The moving average slope is positive or negative by comparing the latest value with the previous bar (optional trend filter).
The price is far enough from the moving average to avoid entries directly on the line.
The RSI crosses the upper or lower threshold if the RSI filter is enabled. The MetaTrader logic is kept intact, including the quirk where an RSI sell confirmation returns -11, so activating both filters favours long trades only.
Respect the maximum number of simultaneously open positions. Additional entries in the same direction are allowed only when price has moved against the last fill by the configured pip distance, effectively building a grid.
Every entry uses ATR-based stop-loss and take-profit levels. Trailing stops tighten the protective stop once price moves by the trailing step plus trailing stop distance.
Exits are executed via opposite market orders when the candle range hits the stop, target or trailing level.
Risk Management
ATR multipliers control the fixed stop-loss and take-profit distances. Setting a multiplier to zero disables that protection.
Trailing stops are optional and are defined by two pip parameters: the actual trailing distance and the extra step required before moving the stop again.
The strategy relies on the base Volume property for sizing; no automated money management is applied beyond the position cap.
Parameters
Name
Description
CandleType
Timeframe used for indicator calculations.
MaxPositions
Maximum number of simultaneously open positions (sum of long and short).
DistancePips
Minimum pip distance between consecutive entries in the same direction.
AtrPeriod
Averaging period for the ATR indicator.
AtrStopLossMultiplier
ATR multiplier for the protective stop. 0 disables the stop.
AtrTakeProfitMultiplier
ATR multiplier for the profit target. 0 disables the target.
UseTrendFilter
Enables the moving average slope filter.
DistanceFromMaPips
Minimum pip distance between price and the moving average when the trend filter is active.
MaPeriod, MaShift, MaMethod, MaPriceType
Moving average parameters mirroring the MetaTrader inputs.
UseRsiFilter
Enables the RSI confirmation filter.
RsiLevelUp, RsiLevelDown, RsiPeriod, RsiPriceType
RSI configuration with applied price selection.
TrailingStopPips, TrailingStepPips
Trailing stop distance and step measured in pips.
CurrentBarOffset
Offset applied when reading indicator values, emulating the CurrentBar input from the expert advisor.
Notes
Pip conversion multiplies the instrument PriceStep by 10 when the instrument has 3 or 5 decimal places to reproduce MetaTrader's point-to-pip adjustment.
The strategy uses a netting position model; opposite signals close existing positions before opening trades in the new direction.
Start protection is enabled at launch to guard against unexpected reconnections before any trades are placed.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class FreemanAtrMaRsiGridStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast, _prevSlow;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public FreemanAtrMaRsiGridStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 8).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null; _prevSlow = null;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast; _prevSlow = slow;
if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class freeman_atr_ma_rsi_grid_strategy(Strategy):
def __init__(self):
super(freeman_atr_ma_rsi_grid_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 8) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(freeman_atr_ma_rsi_grid_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(freeman_atr_ma_rsi_grid_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_above and not curr_above and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return freeman_atr_ma_rsi_grid_strategy()