The Manual EA Strategy is a one-to-one StockSharp high-level API conversion of the MetaTrader 4 expert advisor Manual_EA.mq4 (folder MQL/8159). The original system issues discretionary buy or sell orders whenever the Stochastic oscillator leaves extreme zones. The StockSharp port keeps the same 5-3-3 oscillator configuration, automatically nets existing exposure before placing the next market order, and exposes the common money-management options through strategy parameters.
Trading logic
The strategy subscribes to the CandleType series (default: 15-minute candles) and feeds the close prices into a Stochastic Oscillator configured with:
%K lookback = KPeriod (default 5 bars)
%K slowing = Slowing (default 3 bars)
%D smoothing = DPeriod (default 3 bars)
Signals are evaluated on the final value of the %D (signal) line of each finished candle. Two consecutive readings are compared to detect level crossings.
Long entry – When the previous %D value was below or equal to OversoldLevel (default 10) and the latest value rises above that threshold. The strategy first neutralizes any short exposure and then buys Volume + |short position| by market order.
Short entry – When the previous %D value was above or equal to OverboughtLevel (default 90) and the latest value falls below that threshold. Any existing long position is closed before selling Volume + |long position| at market.
Protective orders are handled via StartProtection. A positive StopLoss and/or TakeProfit (measured in price points) activates automatic risk management. Setting a parameter to 0 disables the corresponding protection.
The port deliberately avoids indicator buffer access patterns and unfinished-candle logic, complying with StockSharp high-level API best practices.
Parameters
Parameter
Description
Default
CandleType
Timeframe (as DataType) used to build candles and drive the oscillator.
15-minute time frame
KPeriod
Lookback length of the Stochastic %K line.
5
DPeriod
Smoothing length of the Stochastic %D signal line.
3
Slowing
Additional smoothing applied to %K before %D is computed.
3
OverboughtLevel
Upper bound that triggers short entries when crossed downward by %D.
90
OversoldLevel
Lower bound that triggers long entries when crossed upward by %D.
10
StopLoss
Distance in price points for the protective stop-loss (0 = disabled).
100
TakeProfit
Distance in price points for the take-profit target (0 = disabled).
100
Volume
Order size sent with each new signal (lots). Existing opposite positions are netted first.
0.1
Additional notes
The strategy uses SubscribeCandles together with BindEx to stream StochasticOscillatorValue updates, ensuring indicator values are final before trading decisions are taken.
Chart visualization automatically plots the selected candle series, the Stochastic oscillator, and own trades when a chart area is available.
Because %D crossings are evaluated on consecutive finished candles, the behaviour matches the MQL implementation that compared MODE_SIGNAL values at shifts 1 and 2.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class ManualEaStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevRsi;
private bool _hasPrev;
private int _cooldown;
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ManualEaStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 9).SetDisplay("RSI Period", "RSI lookback", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA filter", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ema, ProcessCandle).Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal ema)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevRsi = rsi; _hasPrev = true; return; }
if (_cooldown > 0)
{
_cooldown--;
_prevRsi = rsi;
return;
}
if (_prevRsi <= 20 && rsi > 20 && close > ema && Position == 0)
{
BuyMarket();
_cooldown = 2;
}
else if (_prevRsi >= 80 && rsi < 80 && close < ema && Position == 0)
{
SellMarket();
_cooldown = 2;
}
_prevRsi = rsi;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class manual_ea_strategy(Strategy):
def __init__(self):
super(manual_ea_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 9).SetDisplay("RSI Period", "RSI lookback", "Indicators")
self._ema_period = self.Param("EmaPeriod", 20).SetDisplay("EMA Period", "EMA filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_rsi = 0.0; self._has_prev = False; self._cooldown = 0
@property
def rsi_period(self): return self._rsi_period.Value
@property
def ema_period(self): return self._ema_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(manual_ea_strategy, self).OnReseted()
self._prev_rsi = 0.0; self._has_prev = False; self._cooldown = 0
def OnStarted2(self, time):
super(manual_ea_strategy, self).OnStarted2(time)
self._has_prev = False; self._cooldown = 0
rsi = RelativeStrengthIndex(); rsi.Length = self.rsi_period
ema = ExponentialMovingAverage(); ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, ema, self.process_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
def process_candle(self, candle, rsi, ema):
if candle.State != CandleStates.Finished: return
close = float(candle.ClosePrice)
rsi_val = float(rsi); ema_val = float(ema)
if not self._has_prev:
self._prev_rsi = rsi_val; self._has_prev = True; return
if self._cooldown > 0:
self._cooldown -= 1; self._prev_rsi = rsi_val; return
if self._prev_rsi <= 20 and rsi_val > 20 and close > ema_val and self.Position == 0:
self.BuyMarket(); self._cooldown = 2
elif self._prev_rsi >= 80 and rsi_val < 80 and close < ema_val and self.Position == 0:
self.SellMarket(); self._cooldown = 2
self._prev_rsi = rsi_val
def CreateClone(self): return manual_ea_strategy()