Prop Firm Helper 策略
概述
Prop Firm Helper 策略源自 MetaTrader 的 "Prop Firm Helper" 智能交易系统,使用唐奇安通道突破逻辑。策略在价格突破近期高点时挂买入止损单,在跌破近期低点时挂卖出止损单,并在达到挑战目标或触及每日亏损限制后自动停止交易。
交易逻辑
- 根据
Candle Type订阅指定周期的 K 线。 - 计算两个唐奇安通道:
Entry Period/Entry Shift用于识别突破水平。Exit Period/Exit Shift用于跟踪持仓并移动止损。
- 当账户为空仓或持有空头时,在移位后的上轨上方一格挂买入止损单。
- 当账户为空仓或持有多头时,在移位后的下轨下方一格挂卖出止损单。
- 使用平均真实波幅 (
ATR Period) 平滑 trailing 止损的移动频率。 - 如果蜡烛收盘价低于跟踪的下轨则平多仓;若收盘价高于跟踪的上轨则平空仓。
风险管理
Risk Per Trade %按当前资产净值、最小价格变动和每档价格计算下单数量,并按照交易所的最小手数与增量进行取整,限制在最小/最大允许数量内。- 防护性止损使用跟踪通道并叠加 ATR 缓冲,避免频繁修改订单。
Prop Firm 挑战规则
- 勾选
Use Challenge Rules后启用挑战检查。 - 当权益达到
Pass Criteria时停止交易,取消所有挂单并平掉持仓。 - 当当日亏损超过
Daily Loss Limit时立即清空持仓,取消挂单,并在该交易日剩余时间内禁止新订单。每日开始时重新记录基准权益。
参数说明
| 名称 | 描述 |
|---|---|
Entry Period |
突破唐奇安通道的回溯周期。 |
Entry Shift |
计算突破时忽略的已完成 K 线数量。 |
Exit Period |
跟踪唐奇安通道的回溯周期。 |
Exit Shift |
计算 trailing 止损时忽略的已完成 K 线数量。 |
Risk Per Trade % |
每次入场风险占账户权益的百分比。 |
ATR Period |
平滑 trailing 止损的 ATR 周期。 |
Use Challenge Rules |
是否启用 prop firm 挑战限制。 |
Pass Criteria |
达到该权益后停止交易。 |
Daily Loss Limit |
当日允许的最大亏损额。 |
Candle Type |
用于计算的 K 线类型。 |
注意事项
- 策略需要可用的投资组合数据来计算头寸规模和挑战指标。
- 每根完成的 K 线都会重新计算挂单价格并取消旧订单。
- 默认参数复现了原始 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;
}
}
}
import clr
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 prop_firm_helper_strategy(Strategy):
def __init__(self):
super(prop_firm_helper_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._entry_period = self.Param("EntryPeriod", 20)
self._exit_period = self.Param("ExitPeriod", 10)
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 4)
self._entry_highs = []
self._entry_lows = []
self._exit_highs = []
self._exit_lows = []
self._prev_entry_upper = 0.0
self._prev_entry_lower = 0.0
self._has_values = False
self._cooldown_remaining = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EntryPeriod(self):
return self._entry_period.Value
@EntryPeriod.setter
def EntryPeriod(self, value):
self._entry_period.Value = value
@property
def ExitPeriod(self):
return self._exit_period.Value
@ExitPeriod.setter
def ExitPeriod(self, value):
self._exit_period.Value = value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
@SignalCooldownBars.setter
def SignalCooldownBars(self, value):
self._signal_cooldown_bars.Value = value
def OnReseted(self):
super(prop_firm_helper_strategy, self).OnReseted()
self._entry_highs = []
self._entry_lows = []
self._exit_highs = []
self._exit_lows = []
self._prev_entry_upper = 0.0
self._prev_entry_lower = 0.0
self._has_values = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(prop_firm_helper_strategy, self).OnStarted2(time)
self._entry_highs = []
self._entry_lows = []
self._exit_highs = []
self._exit_lows = []
self._prev_entry_upper = 0.0
self._prev_entry_lower = 0.0
self._has_values = False
self._cooldown_remaining = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
entry_period = self.EntryPeriod
exit_period = self.ExitPeriod
self._entry_highs.append(high)
self._entry_lows.append(low)
while len(self._entry_highs) > entry_period:
self._entry_highs.pop(0)
self._entry_lows.pop(0)
self._exit_highs.append(high)
self._exit_lows.append(low)
while len(self._exit_highs) > exit_period:
self._exit_highs.pop(0)
self._exit_lows.pop(0)
if len(self._entry_highs) < entry_period or len(self._exit_highs) < exit_period:
return
entry_upper = max(self._entry_highs)
entry_lower = min(self._entry_lows)
exit_upper = max(self._exit_highs)
exit_lower = min(self._exit_lows)
if not self._has_values:
self._prev_entry_upper = entry_upper
self._prev_entry_lower = entry_lower
self._has_values = True
return
cooldown = self.SignalCooldownBars
# Exit logic
if self.Position > 0 and close < exit_lower:
self.SellMarket()
self._cooldown_remaining = cooldown
self._prev_entry_upper = entry_upper
self._prev_entry_lower = entry_lower
return
elif self.Position < 0 and close > exit_upper:
self.BuyMarket()
self._cooldown_remaining = cooldown
self._prev_entry_upper = entry_upper
self._prev_entry_lower = entry_lower
return
# Entry logic
if self._cooldown_remaining == 0 and self._prev_entry_upper > 0 and close > self._prev_entry_upper and self.Position <= 0:
self.BuyMarket()
self._cooldown_remaining = cooldown
elif self._cooldown_remaining == 0 and self._prev_entry_lower > 0 and close < self._prev_entry_lower and self.Position >= 0:
self.SellMarket()
self._cooldown_remaining = cooldown
self._prev_entry_upper = entry_upper
self._prev_entry_lower = entry_lower
def CreateClone(self):
return prop_firm_helper_strategy()