Prop Firm Helper — это конвертированный из MetaTrader советник, построенный на пробое канала Дончиана. Стратегия размещает стоп-заявки на покупку выше диапазона и стоп-заявки на продажу ниже диапазона. Дополнительно она контролирует правила проп-фирм: завершает торговлю при достижении целевой доходности или при превышении дневного лимита убытков.
Логика торговли
Подписка на свечи, задаваемые параметром Candle Type.
Расчёт двух каналов Дончиана:
Entry Period/Entry Shift — для поиска уровней пробоя.
Exit Period/Exit Shift — для сопровождения открытых позиций.
Размещение отложенного ордера Buy Stop на один тик выше смещённой верхней границы канала, если нет длинной позиции.
Размещение Sell Stop на один тик ниже смещённой нижней границы, если нет короткой позиции.
Использование индикатора среднего истинного диапазона (ATR Period) для фильтрации обновления защитных стопов.
Закрытие длинной позиции при закрытии свечи ниже нижней границы канала сопровождения. Закрытие короткой — при закрытии выше верхней границы.
Управление рисками
Параметр Risk Per Trade % рассчитывает объём сделки по текущей стоимости портфеля, шагу цены и стоимости шага. Объём приводится к минимальному шагу и ограничивается биржевыми лимитами.
Защитные стопы подтягиваются вдоль канала выхода с учётом буфера ATR, чтобы уменьшить количество перестановок.
Правила проп-фирмы
Use Challenge Rules включает контроль условий испытания.
Достижение Pass Criteria приводит к закрытию всех позиций и отмене заявок.
Если текущая дневная просадка превышает Daily Loss Limit, стратегия закрывает позиции, отменяет заявки и блокирует новые входы до конца дня. Начальное значение капитала пересчитывается в начале каждого торгового дня.
Параметры
Название
Описание
Entry Period
Длина окна для канала пробоя.
Entry Shift
Число завершённых свечей, игнорируемых при определении пробоя.
Exit Period
Длина окна для канала сопровождения.
Exit Shift
Число завершённых свечей, игнорируемых при расчёте стопов.
Risk Per Trade %
Доля капитала под риском на сделку.
ATR Period
Период ATR, применяемый при переносе стопов.
Use Challenge Rules
Включение правил проп-фирмы.
Pass Criteria
Целевое значение капитала для завершения торговли.
Daily Loss Limit
Максимально допустимый дневной убыток.
Candle Type
Тип свечей для расчётов.
Примечания
Для корректной работы необходим доступ к портфелю, чтобы рассчитывать объёмы и отслеживать капитал.
После закрытия каждой свечи стратегия пересчитывает уровни и при необходимости перерегистрирует отложенные заявки.
Значения по умолчанию соответствуют исходному советнику MetaTrader.
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;
}
}
}