GitHub で見る
しっかりとしたヘルパー戦略を支える
概要
Prop Firm Helper Strategy は、MetaTrader エキスパート アドバイザー「Prop Firm Helper」から変換された Donchian チャネル ブレイクアウト システムです。この戦略は、ロングエントリーの場合は最近のレンジを上回り、ショートエントリーの場合はレンジを下回るストップオーダーを発行します。目標株価に達した後、または1日の損失制限に違反した場合には取引を停止することで、プロップファームチャレンジルールを自動的に適用します。
取引ロジック
Candle Type パラメータで定義されたキャンドルをサブスクライブします。
- 2 つの Donchian チャネルを計算します。
Entry Period/Entry Shift で吹き出物を検出します。
Exit Period/Exit Shift でオープントレードを追跡します。
- フラットまたはショートの場合、シフトされた上限 Donchian の高値より 1 ティック上に買いストップ注文を出します。
- フラットまたはロングの場合、シフトされた下限 Donchian 安値より 1 ティック下に売りストップ注文を出します。
- Average True Range スムージング (
ATR Period) を使用して、ストップ注文をいつ進めるかを決定します。
- ローソク足が後続安値 Donchian を下回った場合は、ロングポジションを閉じます。ローソク足が後続の高値 Donchian を上回って終了すると、ショートポジションを閉じます。
リスク管理
Risk Per Trade % は、現在のポートフォリオの資本、商品のステップサイズ、ステップ価格から注文量を計算します。ボリュームは交換ボリュームのステップに四捨五入され、最小/最大ボリュームによって制限されます。
- 保護的なストップ注文は、過剰な注文の変動を避けるために、出口 Donchian チャネルと ATR バッファを使用してポジションを追跡します。
プロップファームチャレンジルール
Use Challenge Rules はチャレンジ チェックを有効にします。
Pass Criteria の資産に達すると取引が停止します。すべての注文はキャンセルされ、ポジションはクローズされます。
- 1 日あたりのドローダウンが
Daily Loss Limit を超えると完全な清算がトリガーされ、残りのセッションでは新しい注文が無効になります。参照株式は、取引日ごとの開始時にリセットされます。
パラメーター
| 名前 |
説明 |
Entry Period |
ブレイクアウト Donchian チャネルをルックバックします。 |
Entry Shift |
ブレイクアウトチャネルを使用する場合、完成したローソク足の数は無視されます。 |
Exit Period |
後続の Donchian チャネルをルックバックします。 |
Exit Shift |
トレーリングストップでは無視される完了したローソク足の数。 |
Risk Per Trade % |
すべてのエントリーにおけるリスクに対するポートフォリオの資本の割合。 |
ATR Period |
移動停止時に使用される ATR フィルターのルックバック。 |
Use Challenge Rules |
プロップファームチャレンジ条件を有効にします。 |
Pass Criteria |
さらなる取引を停止する株式レベル。 |
Daily Loss Limit |
取引が停止する前に許容される日次ドローダウン。 |
Candle Type |
計算に使用されるキャンドルのサブスクリプション。 |
注意事項
- この戦略では、リスクベースのポジションサイズとチャレンジメトリクスを計算するためにポートフォリオ接続が必要です。
- 注文は、トリガー価格を最新の Donchian レベルに合わせて維持するために、終了したローソク足ごとにキャンセルされ、再送信されます。
- デフォルトのパラメータは、元の 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()