Money Fixed Risk Strategy is a direct port of the MetaTrader 5 expert advisor Money Fixed Risk.mq5. The original script periodically computes the maximum position size that keeps risk below a fixed percentage of account equity and then opens a market buy protected with symmetric stop-loss and take-profit orders. This StockSharp version preserves the same behaviour using the high-level tick subscription API and risk controls provided by the framework.
The strategy listens to every trade (tick) for the selected security. After a configurable number of ticks it evaluates the current portfolio value, converts the configured stop distance in pips to price units and calculates the largest volume that keeps risk within the specified percentage of equity. If the calculated volume is valid the strategy opens a long market order and assigns both stop-loss and take-profit levels at exactly the stop distance away from the filled price. The stop and target are monitored on each subsequent tick and the position is closed once either boundary is touched.
Data requirements
Tick (trade) data is required because the entry condition counts individual ticks. Candle data is not used.
Correct PriceStep, StepPrice, VolumeStep, MinVolume and optional MaxVolume must be configured for the security so that the position sizing formula matches the broker contract specifications.
How the strategy works
Wait for tick updates through SubscribeTrades().
Track the last traded price and increment an internal counter.
Whenever the tick counter reaches Ticks Interval, reset the counter and:
Determine the pip size from PriceStep and Decimals (5- and 3-digit quotes are automatically scaled by 10).
Convert the configured stop-loss distance from pips to price units.
Determine the current account equity (tries Portfolio.CurrentValue, falls back to CurrentBalance, then BeginValue).
Compute the monetary risk per contract using the stop distance and StepPrice.
Derive the maximum volume that keeps the monetary risk below Risk % of equity and normalise it to the exchange volume step and limits.
If the calculated volume is positive, send a buy market order sized to flatten any existing short exposure and open a new long position.
Record the stop-loss and take-profit prices around the entry price. On every subsequent tick monitor the trade price and close the position if either level is violated.
Parameters
Stop Loss (pips) – stop-loss distance expressed in pips. The take-profit is placed at the same distance in the opposite direction.
Risk % – percentage of portfolio equity risked on each trade.
Ticks Interval – number of ticks to wait before re-evaluating and potentially opening a new position.
All parameters support optimisation and validation (must be greater than zero).
Money management details
Risk amount = Equity * (Risk % / 100).
Stop distance in price units = Stop Loss (pips) * pip size, where pip size equals PriceStep * 10 for 3- and 5-decimal instruments, otherwise PriceStep.
Position size = Risk amount / monetary risk per contract, rounded down to the nearest VolumeStep and constrained by MinVolume/MaxVolume. Orders are skipped when the normalised size is below the minimum volume.
Differences from the original expert advisor
Runs entirely inside StockSharp without calling MetaTrader libraries.
Uses StartProtection() so that platform-level protections remain active.
Relies on the strategy portfolio for current equity information instead of querying terminal balance objects.
Uses continuous tick monitoring to exit positions, removing the need for explicit stop orders in this educational example.
Usage notes
This sample opens only long positions just like the original file. Extend ProcessTrade if short trades are required.
When backtesting ensure that tick data includes enough depth to reach the configured tick interval; otherwise no trades will be triggered.
Because position sizing depends on broker metadata, verify the correctness of PriceStep, StepPrice and volume constraints before running live.
The implementation avoids using indicator collections to respect the conversion guidelines and keeps the logic stateful through private fields.
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>
/// Recreates the Money Fixed Risk expert advisor using StockSharp's high level API.
/// Uses ATR to determine position sizing via risk percentage and opens long positions
/// with symmetric stop-loss and take-profit levels based on ATR.
/// </summary>
public class MoneyFixedRiskStrategy : Strategy
{
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _candleInterval;
private readonly StrategyParam<DataType> _candleType;
private int _candleCounter;
private decimal _stopPrice;
private decimal _takeProfitPrice;
private decimal _entryPrice;
/// <summary>
/// ATR multiplier for stop distance.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// ATR calculation period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Number of candles between position evaluations.
/// </summary>
public int CandleInterval
{
get => _candleInterval.Value;
set => _candleInterval.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MoneyFixedRiskStrategy"/>.
/// </summary>
public MoneyFixedRiskStrategy()
{
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Multiplier for ATR-based stop distance", "Risk")
.SetOptimize(0.5m, 3m, 0.5m);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
.SetOptimize(7, 28, 7);
_candleInterval = Param(nameof(CandleInterval), 10)
.SetGreaterThanZero()
.SetDisplay("Candle Interval", "Candles between position evaluations", "General")
.SetOptimize(5, 30, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for processing", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candleCounter = 0;
_stopPrice = 0m;
_takeProfitPrice = 0m;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
// Manage existing long position
if (Position > 0 && _stopPrice > 0m)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takeProfitPrice)
{
SellMarket(Position);
_stopPrice = 0m;
_takeProfitPrice = 0m;
_entryPrice = 0m;
}
}
// Manage existing short position
if (Position < 0 && _stopPrice > 0m)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takeProfitPrice)
{
BuyMarket(Math.Abs(Position));
_stopPrice = 0m;
_takeProfitPrice = 0m;
_entryPrice = 0m;
}
}
_candleCounter++;
if (_candleCounter < CandleInterval)
return;
_candleCounter = 0;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0)
return;
if (atrValue <= 0m)
return;
var stopDistance = atrValue * AtrMultiplier;
// Alternate between long and short based on price relative to previous entry
var goLong = _entryPrice == 0m || price > _entryPrice;
if (goLong)
{
BuyMarket(Volume);
_entryPrice = price;
_stopPrice = price - stopDistance;
_takeProfitPrice = price + stopDistance;
}
else
{
SellMarket(Volume);
_entryPrice = price;
_stopPrice = price + stopDistance;
_takeProfitPrice = price - stopDistance;
}
}
}