The Expotest Strategy is a direct StockSharp conversion of the original Expotest.mq5 expert advisor. It trades a single instrument using the Parabolic SAR indicator and a simple martingale-inspired money management rule. The strategy opens only one position at a time and relies on predefined stop-loss and take-profit levels for exits.
Trading Logic
Indicator: Parabolic SAR calculated on the selected candle series. Both the acceleration factor (SarStep) and maximum acceleration (SarMaximum) are configurable.
Entry conditions: When no position is open, the strategy checks the latest closed candle.
If the Parabolic SAR value is below or equal to the close price, a long position is initiated.
If the Parabolic SAR value is above or equal to the close price, a short position is initiated.
Exit conditions: Stop-loss and take-profit levels are placed at a fixed distance from the entry price, measured in price steps. During every new candle the strategy monitors whether the candle range touches either level and closes the position accordingly. The exit type (profit or loss) is remembered for future position sizing decisions.
Position Sizing
Base volume: Defined by the FixedVolume parameter when it is greater than zero. Otherwise the strategy estimates size from the RiskPercent and StopLossPoints values using current portfolio equity. If neither method returns a valid size the default Strategy.Volume is used.
Martingale step: After a losing trade the next position size is doubled compared to the volume of the losing position. A profitable exit resets the multiplier and the next order uses the base volume again.
Configurable Parameters
CandleType – Data type for candle aggregation (time-frame or other candle format).
SarStep – Initial acceleration factor for the Parabolic SAR.
SarMaximum – Maximum acceleration factor for the Parabolic SAR.
StopLossPoints – Stop-loss distance from entry expressed in price steps.
TakeProfitPoints – Take-profit distance from entry expressed in price steps.
RiskPercent – Percentage of portfolio equity to risk per trade when dynamic sizing is enabled.
FixedVolume – Explicit order volume. Set to 0 to enable risk-based sizing.
Additional Notes
The strategy processes only finished candles in order to stay close to the original tick-based MQL implementation while remaining compatible with StockSharp subscriptions.
Protective levels are tracked internally instead of separate stop/limit orders, which keeps the logic transparent and easy to backtest.
Python implementation is intentionally omitted as requested.
namespace StockSharp.Samples.Strategies;
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Parabolic SAR based strategy converted from the Expotest MQL expert advisor.
/// Enters long when SAR is below price, short when SAR is above price.
/// </summary>
public class ExpotestStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _sarStep;
private readonly StrategyParam<decimal> _sarMaximum;
private bool _prevSarBelow;
private bool _initialized;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal SarStep
{
get => _sarStep.Value;
set => _sarStep.Value = value;
}
public decimal SarMaximum
{
get => _sarMaximum.Value;
set => _sarMaximum.Value = value;
}
public ExpotestStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle type for signal generation", "General");
_sarStep = Param(nameof(SarStep), 0.02m)
.SetDisplay("SAR Step", "Acceleration factor for Parabolic SAR", "Indicators");
_sarMaximum = Param(nameof(SarMaximum), 0.2m)
.SetDisplay("SAR Maximum", "Maximum acceleration factor for Parabolic SAR", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSarBelow = false;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSarBelow = false;
_initialized = false;
var sar = new ParabolicSar
{
Acceleration = SarStep,
AccelerationMax = SarMaximum
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sar, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sar);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal sarValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sarBelow = sarValue < candle.ClosePrice;
if (!_initialized)
{
_prevSarBelow = sarBelow;
_initialized = true;
return;
}
// SAR flipped from above to below price => Buy signal
if (sarBelow && !_prevSarBelow && Position <= 0)
{
BuyMarket();
}
// SAR flipped from below to above price => Sell signal
else if (!sarBelow && _prevSarBelow && Position >= 0)
{
SellMarket();
}
_prevSarBelow = sarBelow;
}
}
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 ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class expotest_strategy(Strategy):
def __init__(self):
super(expotest_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle type for signal generation", "General")
self._sar_step = self.Param("SarStep", 0.02) \
.SetDisplay("SAR Step", "Acceleration factor for Parabolic SAR", "Indicators")
self._sar_maximum = self.Param("SarMaximum", 0.2) \
.SetDisplay("SAR Maximum", "Maximum acceleration factor for Parabolic SAR", "Indicators")
self._prev_sar_below = False
self._initialized = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def SarStep(self):
return self._sar_step.Value
@property
def SarMaximum(self):
return self._sar_maximum.Value
def OnReseted(self):
super(expotest_strategy, self).OnReseted()
self._prev_sar_below = False
self._initialized = False
def OnStarted2(self, time):
super(expotest_strategy, self).OnStarted2(time)
self._prev_sar_below = False
self._initialized = False
sar = ParabolicSar()
sar.Acceleration = self.SarStep
sar.AccelerationMax = self.SarMaximum
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(sar, self._on_process) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sar)
self.DrawOwnTrades(area)
def _on_process(self, candle, sar_value):
if candle.State != CandleStates.Finished:
return
sv = float(sar_value)
sar_below = sv < float(candle.ClosePrice)
if not self._initialized:
self._prev_sar_below = sar_below
self._initialized = True
return
if sar_below and not self._prev_sar_below and self.Position <= 0:
self.BuyMarket()
elif not sar_below and self._prev_sar_below and self.Position >= 0:
self.SellMarket()
self._prev_sar_below = sar_below
def CreateClone(self):
return expotest_strategy()