StockSharp port of the MetaTrader 4 expert advisor SimpleTrade.mq4 (a.k.a. "neroTrade").
Designed for single-symbol trading on the timeframe configured through the CandleType parameter.
Always maintains at most one open position and flips direction at the open of every new bar.
Trading Logic
Each time a new candle becomes active, the strategy compares the candle's opening price with the opening price of the candle that is LookbackBars periods older.
If the new open is strictly higher than the historical reference, all existing positions are closed and a fresh long market order with TradeVolume lots is submitted.
Otherwise (open is equal or lower) the strategy closes any existing positions and opens a short market position of the same size.
The StopLossPoints parameter mirrors the original EA's stop setting. When both the security's PriceStep and StopLossPoints are available, the strategy converts the value into an absolute distance and forwards it to StartProtection, letting StockSharp maintain the protective stop-loss orders automatically.
Candle opens are tracked using the high-level candle subscription API. Finished candles populate the history list, while the active candle triggers the decision once per bar.
Parameters
Parameter
Description
Default
TradeVolume
Base order size expressed in lots. Must be positive.
1
StopLossPoints
Protective stop distance in instrument points. Set to 0 to disable the automatic stop-loss.
120
LookbackBars
Number of bars used for the open-price comparison. A value of 3 reproduces Open[0] vs Open[3] from the original code.
3
CandleType
Timeframe (as a DataType) from which candles are requested. Controls when new signals appear.
1 hour timeframe
Implementation Notes
Uses the high-level SubscribeCandles(...).Bind(...) workflow, so the strategy remains lightweight and reacts to both historical and live candles.
StartProtection is invoked once during OnStarted. Ensure the connected security provides PriceStep; otherwise the stop-loss distance cannot be translated into absolute prices.
Because all trades are entered with market orders at the start of each bar, slippage handling is delegated to the trading venue and there is no additional slippage parameter.
The historical open buffer keeps only a small rolling window (LookbackBars + 5 values) to avoid unnecessary memory usage.
No Python port is supplied; the CS/ directory contains the only implementation.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// StockSharp port of the MetaTrader "SimpleTrade" expert advisor.
/// Compares the opening price of the current bar with the bar from several periods ago and flips the position accordingly.
/// </summary>
public class SimpleTradeFlipStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<int> _lookbackBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _openHistory = new();
private int _cooldown;
/// <summary>
/// Initializes a new instance of the <see cref="SimpleTradeFlipStrategy"/> class.
/// </summary>
public SimpleTradeFlipStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order size in lots", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 120m)
.SetNotNegative()
.SetDisplay("Stop-Loss Points", "Protective stop distance expressed in instrument points", "Risk");
_lookbackBars = Param(nameof(LookbackBars), 10)
.SetGreaterThanZero()
.SetDisplay("Lookback Bars", "Number of bars used for the open price comparison", "Signals");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe used for signal calculations", "General");
}
/// <summary>
/// Order size submitted with each entry.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Stop-loss distance in instrument points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = Math.Max(0m, value);
}
/// <summary>
/// Number of historical bars used for the open price comparison.
/// </summary>
public int LookbackBars
{
get => _lookbackBars.Value;
set => _lookbackBars.Value = Math.Max(1, value);
}
/// <summary>
/// Candle type that defines the working timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_openHistory.Clear();
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 0m;
Unit stopLossUnit = null;
if (StopLossPoints > 0m && step > 0m)
stopLossUnit = new Unit(StopLossPoints * step, UnitTypes.Absolute);
StartProtection(null, stopLossUnit);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Store the open price for future comparisons.
_openHistory.Add(candle.OpenPrice);
var maxHistory = Math.Max(LookbackBars + 5, 5);
if (_openHistory.Count > maxHistory)
_openHistory.RemoveRange(0, _openHistory.Count - maxHistory);
var lookback = LookbackBars;
if (_openHistory.Count <= lookback)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var volume = TradeVolume;
if (volume <= 0m)
return;
var currentOpen = candle.OpenPrice;
var referenceOpen = _openHistory[^(lookback + 1)];
// Only trade on clear directional difference
var diff = currentOpen - referenceOpen;
if (Math.Abs(diff) < currentOpen * 0.001m)
return;
if (diff > 0 && Position <= 0)
{
if (Position < 0m)
BuyMarket(Math.Abs(Position));
BuyMarket(volume);
_cooldown = 5;
}
else if (diff < 0 && Position >= 0)
{
if (Position > 0m)
SellMarket(Position);
SellMarket(volume);
_cooldown = 5;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
class simple_trade_flip_strategy(Strategy):
def __init__(self):
super(simple_trade_flip_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 1.0) \
.SetDisplay("Trade Volume", "Order size in lots", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 120.0) \
.SetDisplay("Stop-Loss Points", "Protective stop distance in instrument points", "Risk")
self._lookback_bars = self.Param("LookbackBars", 10) \
.SetDisplay("Lookback Bars", "Number of bars used for open price comparison", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Primary timeframe used for signal calculations", "General")
self._open_history = []
self._cooldown = 0
@property
def TradeVolume(self):
return self._trade_volume.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def LookbackBars(self):
return self._lookback_bars.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(simple_trade_flip_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
ps = self.Security.PriceStep if self.Security is not None else None
step = float(ps) if ps is not None and float(ps) > 0 else 0.0
sl_pts = float(self.StopLossPoints)
sl = Unit(sl_pts * step, UnitTypes.Absolute) if sl_pts > 0 and step > 0 else None
self.StartProtection(None, sl)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self._open_history.append(float(candle.OpenPrice))
max_history = max(self.LookbackBars + 5, 5)
if len(self._open_history) > max_history:
self._open_history = self._open_history[-max_history:]
lookback = int(self.LookbackBars)
if len(self._open_history) <= lookback:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown > 0:
self._cooldown -= 1
return
volume = float(self.TradeVolume)
if volume <= 0:
return
current_open = float(candle.OpenPrice)
reference_open = self._open_history[-(lookback + 1)]
diff = current_open - reference_open
if abs(diff) < current_open * 0.001:
return
if diff > 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(volume)
self._cooldown = 5
elif diff < 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
self.SellMarket(volume)
self._cooldown = 5
def OnReseted(self):
super(simple_trade_flip_strategy, self).OnReseted()
self._open_history = []
self._cooldown = 0
def CreateClone(self):
return simple_trade_flip_strategy()