Prop Firm Helper Strategy is a Donchian channel breakout system converted from the MetaTrader expert advisor "Prop Firm Helper". The strategy submits stop orders above the recent range for long entries and below the range for short entries. It automatically enforces prop firm challenge rules by stopping trading after the target equity is reached or when the daily loss limit is breached.
Trading Logic
Subscribe to candles defined by the Candle Type parameter.
Calculate two Donchian channels:
Entry Period/Entry Shift to detect breakouts.
Exit Period/Exit Shift to trail open trades.
Place buy stop orders one tick above the shifted upper Donchian high when flat or short.
Place sell stop orders one tick below the shifted lower Donchian low when flat or long.
Use Average True Range smoothing (ATR Period) to decide when to move stop orders forward.
Close long positions if the candle settles below the trailing Donchian low. Close short positions when the candle closes above the trailing Donchian high.
Risk Management
Risk Per Trade % calculates order volume from current portfolio equity, instrument step size and step price. Volume is rounded to the exchange volume step and constrained by minimum/maximum volume.
Protective stop orders trail the position using the exit Donchian channel plus an ATR buffer to avoid excessive order churn.
Prop Firm Challenge Rules
Use Challenge Rules enables challenge checks.
Trading stops once Pass Criteria equity is reached. All orders are cancelled and the position is closed.
Daily drawdowns greater than Daily Loss Limit trigger a full liquidation and disable new orders for the rest of the session. The reference equity resets at the beginning of every trading day.
Parameters
Name
Description
Entry Period
Lookback for breakout Donchian channel.
Entry Shift
Number of finished candles ignored when using the breakout channel.
Exit Period
Lookback for trailing Donchian channel.
Exit Shift
Number of finished candles ignored for trailing stops.
Risk Per Trade %
Percentage of portfolio equity to risk on every entry.
ATR Period
Lookback for ATR filter used when moving stops.
Use Challenge Rules
Enables prop firm challenge conditions.
Pass Criteria
Equity level that stops further trading.
Daily Loss Limit
Allowed daily drawdown before trading halts.
Candle Type
Candle subscription used for calculations.
Notes
The strategy requires a portfolio connection to compute risk based position sizes and challenge metrics.
Orders are cancelled and resubmitted on each finished candle to keep trigger prices aligned with the latest Donchian levels.
Default parameters reproduce the behaviour of the original MetaTrader expert advisor.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy that recreates the MetaTrader "Prop Firm Helper" expert advisor.
/// Uses Donchian channel breakouts for entry signals with market orders.
/// </summary>
public class PropFirmHelperStrategy : Strategy
{
private readonly StrategyParam<int> _entryPeriod;
private readonly StrategyParam<int> _exitPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalCooldownBars;
private DonchianChannels _entryChannel;
private DonchianChannels _exitChannel;
private decimal _entryUpper;
private decimal _entryLower;
private decimal _exitLower;
private decimal _exitUpper;
private decimal _prevEntryUpper;
private decimal _prevEntryLower;
private bool _hasValues;
private decimal _entryPrice;
private int _cooldownRemaining;
/// <summary>
/// Initializes a new instance of <see cref="PropFirmHelperStrategy"/>.
/// </summary>
public PropFirmHelperStrategy()
{
_entryPeriod = Param(nameof(EntryPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Entry Period", "Number of candles used for breakout Donchian channel", "Entries");
_exitPeriod = Param(nameof(ExitPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Exit Period", "Number of candles used for trailing Donchian channel", "Exits");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for Donchian calculations", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 4)
.SetNotNegative()
.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before a new breakout entry", "General");
}
/// <summary>
/// Donchian breakout lookback length.
/// </summary>
public int EntryPeriod
{
get => _entryPeriod.Value;
set => _entryPeriod.Value = value;
}
/// <summary>
/// Donchian trailing lookback length.
/// </summary>
public int ExitPeriod
{
get => _exitPeriod.Value;
set => _exitPeriod.Value = value;
}
/// <summary>
/// Candle type used for indicator subscriptions.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryChannel = null;
_exitChannel = null;
_entryUpper = 0m;
_entryLower = 0m;
_exitLower = 0m;
_exitUpper = 0m;
_prevEntryUpper = 0m;
_prevEntryLower = 0m;
_hasValues = false;
_entryPrice = 0m;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasValues = false;
_cooldownRemaining = 0;
_entryChannel = new DonchianChannels { Length = EntryPeriod };
_exitChannel = new DonchianChannels { Length = ExitPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _entryChannel);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var entryValue = _entryChannel.Process(new CandleIndicatorValue(_entryChannel, candle));
var exitValue = _exitChannel.Process(new CandleIndicatorValue(_exitChannel, candle));
if (!_entryChannel.IsFormed || !_exitChannel.IsFormed)
return;
if (entryValue is not DonchianChannelsValue entryBands || exitValue is not DonchianChannelsValue exitBands)
return;
if (entryBands.UpperBand is not decimal entryUpper || entryBands.LowerBand is not decimal entryLower)
return;
if (exitBands.UpperBand is not decimal exitUpper || exitBands.LowerBand is not decimal exitLower)
return;
_prevEntryUpper = _entryUpper;
_prevEntryLower = _entryLower;
_entryUpper = entryUpper;
_entryLower = entryLower;
_exitUpper = exitUpper;
_exitLower = exitLower;
if (!_hasValues)
{
_hasValues = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
ProcessSignal(candle);
}
private void ProcessSignal(ICandleMessage candle)
{
var close = candle.ClosePrice;
// Exit logic
if (Position > 0 && close < _exitLower)
{
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
return;
}
else if (Position < 0 && close > _exitUpper)
{
BuyMarket(-Position);
_cooldownRemaining = SignalCooldownBars;
return;
}
// Entry logic - breakout above previous entry channel upper
if (_cooldownRemaining == 0 && _prevEntryUpper > 0 && close > _prevEntryUpper && Position <= 0)
{
BuyMarket(Volume + (Position < 0 ? -Position : 0m));
_entryPrice = close;
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && _prevEntryLower > 0 && close < _prevEntryLower && Position >= 0)
{
SellMarket(Volume + (Position > 0 ? Position : 0m));
_entryPrice = close;
_cooldownRemaining = SignalCooldownBars;
}
}
}