The Random Bias Trader Strategy emulates the MetaTrader "random trader" expert advisor using StockSharp's high-level API.
At every finished candle the strategy flips a virtual coin and opens a position in that direction when no trade is active.
Stop-loss and take-profit levels are derived either from ATR(10) or from a fixed pip distance and sized by the reward-to-risk ratio.
Position size is computed from the configured risk percentage and automatically capped by the instrument volume limits.
An optional breakeven trigger can move the stop-loss to the entry price once a specified pip gain is reached.
Details
Data: One candle subscription defined by CandleType.
Entry Criteria:
Long: No open position, coin toss returns long. Entry price equals the latest close.
Short: No open position, coin toss returns short. Entry price equals the latest close.
Exit Criteria:
Stop-loss: Distance equals LossPipDistance × pip size or LossAtrMultiplier × ATR(10) depending on LossType.
Take-profit: Stop distance multiplied by RewardRiskRatio.
Breakeven: When enabled, move stop to entry after BreakevenDistancePips gain.
Stops: Dynamic stop-loss and take-profit per trade, breakeven stop optional.
Default Values:
CandleType = 1 minute timeframe
RewardRiskRatio = 2.0
LossType = Pip
LossAtrMultiplier = 5.0
LossPipDistance = 20 pips
RiskPercentPerTrade = 1%
UseBreakeven = Enabled
BreakevenDistancePips = 10 pips
UseMaxMargin = Enabled
Filters:
Category: Randomized trend-neutral
Direction: Both, determined per flip
Indicators: ATR(10) (optional)
Complexity: Beginner
Risk level: Medium, depends on stop sizing
Notes
When the risk-based volume becomes too small, the strategy optionally falls back to the maximum tradable volume.
Stop and target levels are rounded to the instrument price step before orders are placed.
Breakeven logic keeps only one position open at any time, mirroring the original MetaTrader logic.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Random trade generator with ATR-based risk management.
/// Opens a random long or short position on each candle when flat,
/// with stop loss and take profit based on ATR.
/// </summary>
public class RandomBiasTraderStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _rewardRiskRatio;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _atrPeriod;
private Random _random;
private decimal _entryPrice;
private int _direction; // 1=long, -1=short, 0=flat
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal RewardRiskRatio
{
get => _rewardRiskRatio.Value;
set => _rewardRiskRatio.Value = value;
}
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public RandomBiasTraderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle type", "Data");
_rewardRiskRatio = Param(nameof(RewardRiskRatio), 3m)
.SetDisplay("Reward/Risk", "Reward to risk ratio", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 3m)
.SetDisplay("ATR Multiplier", "ATR multiplier for stop distance", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "ATR indicator period", "Risk");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_random = new Random(42);
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (atrValue <= 0)
return;
var stopDistance = atrValue * AtrMultiplier;
var takeDistance = stopDistance * RewardRiskRatio;
var close = candle.ClosePrice;
// Check exit for existing position
if (_direction > 0)
{
if (close >= _entryPrice + takeDistance || close <= _entryPrice - stopDistance)
{
SellMarket();
_direction = 0;
}
return;
}
else if (_direction < 0)
{
if (close <= _entryPrice - takeDistance || close >= _entryPrice + stopDistance)
{
BuyMarket();
_direction = 0;
}
return;
}
// Open new random position
if (_random.Next(4) != 0)
return;
if (_random.Next(2) == 0)
{
BuyMarket();
_entryPrice = close;
_direction = 1;
}
else
{
SellMarket();
_entryPrice = close;
_direction = -1;
}
}
/// <inheritdoc />
protected override void OnReseted()
{
_random = null;
_entryPrice = 0;
_direction = 0;
base.OnReseted();
}
}
import clr
import random
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
from StockSharp.Algo.Strategies import Strategy
class random_bias_trader_strategy(Strategy):
def __init__(self):
super(random_bias_trader_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._reward_risk_ratio = self.Param("RewardRiskRatio", 3.0)
self._atr_multiplier = self.Param("AtrMultiplier", 3.0)
self._atr_period = self.Param("AtrPeriod", 14)
self._rng = None
self._entry_price = 0.0
self._direction = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RewardRiskRatio(self):
return self._reward_risk_ratio.Value
@RewardRiskRatio.setter
def RewardRiskRatio(self, value):
self._reward_risk_ratio.Value = value
@property
def AtrMultiplier(self):
return self._atr_multiplier.Value
@AtrMultiplier.setter
def AtrMultiplier(self, value):
self._atr_multiplier.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
def OnReseted(self):
super(random_bias_trader_strategy, self).OnReseted()
self._rng = None
self._entry_price = 0.0
self._direction = 0
def OnStarted2(self, time):
super(random_bias_trader_strategy, self).OnStarted2(time)
self._rng = random.Random(42)
self._entry_price = 0.0
self._direction = 0
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr_value)
if atr_val <= 0:
return
close = float(candle.ClosePrice)
atr_mult = float(self.AtrMultiplier)
rr_ratio = float(self.RewardRiskRatio)
stop_distance = atr_val * atr_mult
take_distance = stop_distance * rr_ratio
# Check exit for existing position
if self._direction > 0:
if close >= self._entry_price + take_distance or close <= self._entry_price - stop_distance:
self.SellMarket()
self._direction = 0
return
elif self._direction < 0:
if close <= self._entry_price - take_distance or close >= self._entry_price + stop_distance:
self.BuyMarket()
self._direction = 0
return
# Open new random position
if self._rng.randint(0, 3) != 0:
return
if self._rng.randint(0, 1) == 0:
self.BuyMarket()
self._entry_price = close
self._direction = 1
else:
self.SellMarket()
self._entry_price = close
self._direction = -1
def CreateClone(self):
return random_bias_trader_strategy()