The Coin Flipping Strategy is a literal port of the classic MetaTrader expert advisor that decides whether to buy or sell by simulating a coin toss. Every completed candle triggers a new decision when the strategy is flat, so the system alternates through a continuous series of independent trades. The StockSharp conversion keeps the behaviour intentionally simple: only one position is held at a time and each trade is paired with a symmetric take-profit and stop-loss expressed in pips.
Although the core idea is intentionally naive, the example demonstrates how to translate even very small Expert Advisors into the StockSharp high-level API. The strategy is useful as a teaching aid for wiring up subscriptions, money management helpers and protective orders.
Trading Logic
On strategy start the random number generator is seeded with the current environment tick count, matching the spirit of the original MathSrand(GetTickCount()) call from MQL.
For each finished candle (the default timeframe is 1 minute, but any candle type can be supplied) the strategy checks whether it is allowed to trade and whether no position is currently open.
When flat, the generator produces either 0 or 1. A value of 0 results in a market buy order, while 1 triggers a market sell order. The volume is computed dynamically based on the configured risk percentage and stop-loss distance.
Protective orders created by StartProtection attach a stop-loss and take-profit to every position so the exit management remains automatic.
No other filters are used: every time a position is closed the next candle immediately creates a new trade.
Position Sizing
The StockSharp version reinterprets the lot size formula to work with portfolio values. The risk amount is calculated as Portfolio.CurrentValue * RiskPercent / 100. This capital is divided by the stop-loss distance in price units (pips converted using the security price step) to derive the number of contracts. The helper then rounds the size to the nearest admissible volume step and enforces exchange limits through MinVolume and MaxVolume.
This keeps the spirit of the original code—risking a fixed percentage of equity per trade—while ensuring the order size respects StockSharp security metadata.
Parameters
Parameter
Description
Default
Notes
RiskPercent
Percentage of the portfolio risked on every trade.
2
Increasing this number amplifies the volume; reductions make the orders smaller.
TakeProfitPips
Distance between entry and the take-profit level in pips.
20
Converted to absolute price using the instrument price step and passed to StartProtection.
StopLossPips
Distance between entry and the stop-loss level in pips.
10
Also converted into price units; the same value is used for position sizing.
CandleType
Candle subscription that schedules the decision loop.
1 minute time frame
Any StockSharp candle type can be supplied; higher intervals slow the trading tempo.
Risk Management
StartProtection is launched once during OnStarted with the computed take-profit and stop-loss distances. StockSharp then manages the protective orders automatically, mirroring the OrderSend arguments in the MQL script. Because the strategy only trades when Position == 0, there is no need to manually cancel or resubmit existing orders; the platform cancels the protective orders once the position is closed.
Implementation Notes
Candle processing uses the high-level SubscribeCandles().Bind(...) pattern for clarity and simplicity.
Logging statements describe the chosen direction and volume so that backtests clearly show how the pseudo-random generator behaves.
Volume normalization accounts for VolumeStep, MinVolume, and MaxVolume, ensuring that generated sizes comply with the instrument specification.
The code keeps all comments in English, as required, and mirrors the structure demanded by the repository guidelines.
Usage Notes
Because the trading direction is random, long-term profitability is not expected. Use the strategy for demonstration or testing purposes.
Ensure the portfolio assigned to the strategy has a positive CurrentValue, otherwise the risk calculation returns zero and no trades will be placed.
Adjust the candle type if you prefer the coin toss to occur less frequently (for example, hourly candles) or more often (for example, tick candles).
When optimising, you can explore alternative take-profit and stop-loss distances or lower the risk percentage to keep drawdowns manageable.
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>
/// Randomized coin flipping strategy that alternates between buying and selling based on a pseudo-random generator.
/// Mimics the original MetaTrader expert advisor by opening a single position at a time with symmetric risk controls.
/// </summary>
public class CoinFlippingStrategy : Strategy
{
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<DataType> _candleType;
private Random _random;
private decimal _priceStep;
private decimal _takeProfitDistance;
private decimal _stopLossDistance;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Portfolio share allocated to every trade in percent.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Take profit distance measured in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop loss distance measured in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Candle type used for scheduling trade attempts.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public CoinFlippingStrategy()
{
_riskPercent = Param(nameof(RiskPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Risk %", "Portfolio percentage allocated per trade", "Risk Management")
.SetOptimize(1m, 10m, 1m);
_takeProfitPips = Param(nameof(TakeProfitPips), 5000)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Target distance expressed in pips", "Risk Management")
.SetOptimize(10, 50, 5);
_stopLossPips = Param(nameof(StopLossPips), 3000)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Protective stop distance expressed in pips", "Risk Management")
.SetOptimize(5, 30, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Candle Type", "Candle type used for trade timing", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Reset cached state when the strategy is reset.
_random = null;
_priceStep = 0m;
_takeProfitDistance = 0m;
_stopLossDistance = 0m;
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Seed the pseudo-random generator similarly to the MQL expert.
_random = new Random(System.Environment.TickCount);
// Determine price step information for translating pips into price units.
_priceStep = Security?.PriceStep ?? 1m;
if (_priceStep <= 0m)
_priceStep = 1m;
_takeProfitDistance = TakeProfitPips * _priceStep;
_stopLossDistance = StopLossPips * _priceStep;
// Subscribe to candle data to trigger decision making once per bar.
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
// Only use completed candles to avoid duplicate executions while a bar is forming.
if (candle.State != CandleStates.Finished)
return;
// Check risk management first.
if (Position > 0)
{
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Position);
ResetTargets();
}
else if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
SellMarket(Position);
ResetTargets();
}
}
else if (Position < 0)
{
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetTargets();
}
else if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetTargets();
}
}
// The strategy maintains at most one position at a time.
if (Position != 0)
return;
if (_random == null)
return;
var entryPrice = candle.ClosePrice;
if (entryPrice <= 0m)
return;
var volume = CalculateOrderVolume(entryPrice);
if (volume <= 0m)
return;
var isBuy = _random.Next(0, 2) == 0;
if (isBuy)
{
BuyMarket(volume);
_entryPrice = entryPrice;
_stopPrice = _stopLossDistance > 0m ? entryPrice - _stopLossDistance : null;
_takePrice = _takeProfitDistance > 0m ? entryPrice + _takeProfitDistance : null;
}
else
{
SellMarket(volume);
_entryPrice = entryPrice;
_stopPrice = _stopLossDistance > 0m ? entryPrice + _stopLossDistance : null;
_takePrice = _takeProfitDistance > 0m ? entryPrice - _takeProfitDistance : null;
}
}
private decimal CalculateOrderVolume(decimal entryPrice)
{
var balance = Portfolio?.CurrentValue ?? 0m;
if (balance <= 0m)
return 0m;
var riskAmount = balance * RiskPercent / 100m;
if (riskAmount <= 0m)
return 0m;
var stopDistance = _stopLossDistance;
if (stopDistance <= 0m)
{
stopDistance = StopLossPips * _priceStep;
}
if (stopDistance <= 0m)
return 0m;
// Risk per unit equals the stop distance; divide to get the number of contracts.
var rawVolume = riskAmount / stopDistance;
var volume = NormalizeVolume(rawVolume);
if (volume <= 0m)
{
volume = Volume > 0m ? Volume : 1m;
volume = NormalizeVolume(volume);
}
return volume;
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var step = Security?.VolumeStep;
if (step.HasValue && step.Value > 0m)
{
volume = Math.Floor(volume / step.Value) * step.Value;
}
return volume > 0m ? volume : 1m;
}
private void ResetTargets()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
}
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.Strategies import Strategy
class coin_flipping_strategy(Strategy):
def __init__(self):
super(coin_flipping_strategy, self).__init__()
self._risk_percent = self.Param("RiskPercent", 2.0)
self._take_profit_pips = self.Param("TakeProfitPips", 5000)
self._stop_loss_pips = self.Param("StopLossPips", 3000)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1)))
self._rng = None
self._price_step = 1.0
self._tp_dist = 0.0
self._sl_dist = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RiskPercent(self):
return self._risk_percent.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
def OnStarted2(self, time):
super(coin_flipping_strategy, self).OnStarted2(time)
self._rng = random.Random()
sec = self.Security
self._price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self._price_step <= 0:
self._price_step = 1.0
self._tp_dist = self.TakeProfitPips * self._price_step
self._sl_dist = self.StopLossPips * self._price_step
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self.Position > 0:
if self._stop_price is not None and low <= self._stop_price:
self.SellMarket()
self._reset_targets()
elif self._take_price is not None and high >= self._take_price:
self.SellMarket()
self._reset_targets()
elif self.Position < 0:
if self._stop_price is not None and high >= self._stop_price:
self.BuyMarket()
self._reset_targets()
elif self._take_price is not None and low <= self._take_price:
self.BuyMarket()
self._reset_targets()
if self.Position != 0:
return
if self._rng is None:
return
if close <= 0:
return
is_buy = self._rng.randint(0, 1) == 0
if is_buy:
self.BuyMarket()
self._entry_price = close
self._stop_price = close - self._sl_dist if self._sl_dist > 0 else None
self._take_price = close + self._tp_dist if self._tp_dist > 0 else None
else:
self.SellMarket()
self._entry_price = close
self._stop_price = close + self._sl_dist if self._sl_dist > 0 else None
self._take_price = close - self._tp_dist if self._tp_dist > 0 else None
def _reset_targets(self):
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(coin_flipping_strategy, self).OnReseted()
self._rng = None
self._price_step = 1.0
self._tp_dist = 0.0
self._sl_dist = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def CreateClone(self):
return coin_flipping_strategy()