GitHub で見る
連続損失戦略で取引を一時停止する
連続損失時の取引一時停止戦略は、MetaTrader 4 エキスパート アドバイザー 「連続損失時の取引一時停止」 のリスク制御ロジックを再現します。元のスクリプトは、最近終了した取引を監視し、マイナスの利益で終了した取引の数を数え、連敗が短期間内にユーザー定義の制限を超えた場合、新規注文を一時停止しました。 StockSharp ポートは、その動作を維持しながら最小運動量エントリ モデルをラップするため、スタンドアロン ストラテジー内で一時停止メカニズムを評価できます。
仕組み
- この戦略は、
CandleType で指定されたタイムフレーム ローソク足をサブスクライブします。完成したローソクが到着するたびに、終値が前の終値と比較されます。それが増加した場合、戦略はロングエントリーを試みます。減少した場合はショートエントリーが検討されます。強気のポジションが弱気のローソク足に面している場合(終値が始値を下回っている場合)、または弱気ポジションが強気のローソク足に面している場合(終値が始値を上回っている場合)、ポジションは終了します。
- クローズされたポジションごとに、ストラテジーの実現利益が検査されます。負けた結果は、連続した負けのみを保存する内部 FIFO リストの終了タイムスタンプをキューに入れます。 MQL ループが損失のない取引に遭遇すると中止されるのと同じように、利益のある出口や損益分岐点の出口はリストを消去します。
- リストが
ConsecutiveLosses 個の項目に達すると、戦略は最も古い損失と最も新しい損失の間の時間差が WithinMinutes 以内であるかどうかをチェックします。そうである場合、取引は最後の終了時刻から PauseMinutes が経過するまで一時停止されます。一時停止中は新しい成行注文は発行されませんが、既存のポジション管理は継続して行われるため、ブックは自然に平坦化することができます。
- 一時停止が終了すると、損失リストがクリアされ、取引が自動的に再開されます。この動作は、永続的な注文履歴スキャンに依存せずに、元の
CheckLastNLossDifference および lastOrderCloseTime 関数を模倣します。
この実装では、StockSharp の高レベルのキャンドル サブスクリプション (SubscribeCandles) と組み込みの損益マネージャーを使用して実現利益を監視します。シンプルなキュー (Queue<DateTimeOffset>) は、冗長な手動履歴走査の禁止を尊重しながら、連敗記録のタイムスタンプをキャプチャします。
パラメーター
| パラメータ |
デフォルト |
説明 |
CandleType |
5分の時間枠 |
単純なモメンタムエントリに使用されるローソク足の集計。 |
OrderVolume |
0.1 |
各エントリー注文とエグジット注文とともに送信されるボリューム (ロット/契約)。 |
ConsecutiveLosses |
3 |
新しい取引が一時停止されるまでに必要な連続負けポジションの数。 |
WithinMinutes |
20 |
連敗の最初と最後の損失の間に許容される最大分数。値をゼロにすると、ウィンドウ チェックが無効になります。 |
PauseMinutes |
20 |
連敗が検出された後の取引停止期間。 |
注意事項
- 損失タイムスタンプのキューは、戦略がフラットで損失を認識したばかりの場合にのみ設定されます。部分的な決済や収益性の高い取引は連続記録を延長せず、誤検知を防ぎます。
- 一時停止タイマーは、終了した各キャンドルに対して評価されます。ストラテジーがアイドル状態の間に
PauseMinutes が経過すると、次のローソク足がすぐに取引のロックを解除します。
- StockSharp バージョンはネッティング ポジションで動作するため、実現された損益の差は
PnLManager.RealizedPnL から導出され、注文ログ全体を再処理することなく、MetaTrader 履歴ルックアップを忠実に反映します。
using System;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Pause Trading On Consecutive Loss" MetaTrader expert.
/// Uses simple momentum entries (close vs previous close) with a pause mechanism
/// that halts trading after consecutive losing trades within a time window.
/// </summary>
public class PauseTradingOnConsecutiveLossStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _consecutiveLosses;
private readonly StrategyParam<int> _pauseBars;
private decimal? _previousClose;
private int _lossStreak;
private int _pauseCountdown;
private decimal _entryPrice;
private Sides? _entryDirection;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int ConsecutiveLosses
{
get => _consecutiveLosses.Value;
set => _consecutiveLosses.Value = value;
}
public int PauseBars
{
get => _pauseBars.Value;
set => _pauseBars.Value = value;
}
public PauseTradingOnConsecutiveLossStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for momentum entries", "General");
_consecutiveLosses = Param(nameof(ConsecutiveLosses), 3)
.SetGreaterThanZero()
.SetDisplay("Consecutive Losses", "Losses before pausing", "Risk");
_pauseBars = Param(nameof(PauseBars), 8)
.SetGreaterThanZero()
.SetDisplay("Pause Bars", "Number of bars to pause after loss streak", "Risk");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousClose = null;
_lossStreak = 0;
_pauseCountdown = 0;
_entryPrice = 0;
_entryDirection = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_previousClose is null)
{
_previousClose = close;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var momentumThreshold = _previousClose.Value * 0.003m;
// Check if we should pause
if (_pauseCountdown > 0)
{
_pauseCountdown--;
_previousClose = close;
return;
}
// Check for exit and track wins/losses
if (Position != 0)
{
var shouldExit = false;
if (Position > 0 && close < _previousClose.Value - momentumThreshold)
shouldExit = true;
else if (Position < 0 && close > _previousClose.Value + momentumThreshold)
shouldExit = true;
if (shouldExit)
{
// Determine if this was a winning or losing trade
var isLoss = false;
if (_entryDirection == Sides.Buy && close < _entryPrice)
isLoss = true;
else if (_entryDirection == Sides.Sell && close > _entryPrice)
isLoss = true;
if (isLoss)
{
_lossStreak++;
if (_lossStreak >= ConsecutiveLosses)
{
_pauseCountdown = PauseBars;
_lossStreak = 0;
}
}
else
{
_lossStreak = 0;
}
// Close position
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
_entryDirection = null;
}
}
// New entry: momentum - close > prev close -> buy, close < prev close -> sell
if (Position == 0 && _entryDirection is null)
{
if (close > _previousClose.Value + momentumThreshold)
{
BuyMarket(volume);
_entryPrice = close;
_entryDirection = Sides.Buy;
}
else if (close < _previousClose.Value - momentumThreshold)
{
SellMarket(volume);
_entryPrice = close;
_entryDirection = Sides.Sell;
}
}
_previousClose = close;
}
/// <inheritdoc />
protected override void OnReseted()
{
_previousClose = null;
_lossStreak = 0;
_pauseCountdown = 0;
_entryPrice = 0;
_entryDirection = null;
base.OnReseted();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class pause_trading_on_consecutive_loss_strategy(Strategy):
def __init__(self):
super(pause_trading_on_consecutive_loss_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60))).SetDisplay("Candle Type", "Timeframe for momentum entries", "General")
self._consecutive_losses = self.Param("ConsecutiveLosses", 3).SetGreaterThanZero().SetDisplay("Consecutive Losses", "Losses before pausing", "Risk")
self._pause_bars = self.Param("PauseBars", 8).SetGreaterThanZero().SetDisplay("Pause Bars", "Number of bars to pause after loss streak", "Risk")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(pause_trading_on_consecutive_loss_strategy, self).OnReseted()
self._previous_close = None
self._loss_streak = 0
self._pause_countdown = 0
self._entry_price = 0
self._entry_direction = None
def OnStarted2(self, time):
super(pause_trading_on_consecutive_loss_strategy, self).OnStarted2(time)
self._previous_close = None
self._loss_streak = 0
self._pause_countdown = 0
self._entry_price = 0
self._entry_direction = None
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
if self._previous_close is None:
self._previous_close = close
return
momentum_threshold = float(self._previous_close) * 0.003
if self._pause_countdown > 0:
self._pause_countdown -= 1
self._previous_close = close
return
if self.Position != 0:
should_exit = False
if self.Position > 0 and close < self._previous_close - momentum_threshold:
should_exit = True
elif self.Position < 0 and close > self._previous_close + momentum_threshold:
should_exit = True
if should_exit:
is_loss = False
if self._entry_direction == "buy" and close < self._entry_price:
is_loss = True
elif self._entry_direction == "sell" and close > self._entry_price:
is_loss = True
if is_loss:
self._loss_streak += 1
if self._loss_streak >= self._consecutive_losses.Value:
self._pause_countdown = self._pause_bars.Value
self._loss_streak = 0
else:
self._loss_streak = 0
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._entry_direction = None
if self.Position == 0 and self._entry_direction is None:
if close > self._previous_close + momentum_threshold:
self.BuyMarket()
self._entry_price = close
self._entry_direction = "buy"
elif close < self._previous_close - momentum_threshold:
self.SellMarket()
self._entry_price = close
self._entry_direction = "sell"
self._previous_close = close
def CreateClone(self):
return pause_trading_on_consecutive_loss_strategy()