GitHub で見る
グローバルストップロス & 取引時間ウィンドウ戦略
概要
この戦略はMetaTraderのエキスパート Exp_GStopLoss_Tm の動作を再現し、戦略インスタンスが開いたすべての取引の合算結果を監視するリスクオーバーレイを提供する。このモジュール自体はエントリーシグナルを生成しない。代わりに、既存のポジションの損益を追跡し、グローバルストップロスの閾値とオプションの取引セッションウィンドウの両方を適用する。損失が設定された制限を超えるか、市場が許可された時間範囲外に移動した場合、戦略は現在のエクスポージャーを清算し、ブックが再びフラットになるまでそれ以上の取引をブロックする。
トレーディングロジック
- 起動時に、戦略は現在の実現 PnL を基準参照として記録する。これにより、最近のフラット状態に対する浮動利益を測定できる。
- 設定されたローソク足タイプによって生成された完成した各ローソク足がリスクチェックをトリガーする。デフォルトの時間軸はシステムに負担をかけずにティックレベルの監視をエミュレートするために1分である。
- モジュールは現在の戦略 PnL とベース値の差として未実現利益を計算する。戦略が取引ウィンドウ内に留まっている間は正の PnL が無視され、オリジナルのエキスパートアドバイザーに合わせている。
- 損失モードが Percent に設定されている場合、戦略は
Portfolio.CurrentValue から取得したアカウント資本に対する絶対損失率を比較する。Currency モードでは、比較は絶対通貨単位で行われる。
- 損失閾値を超えると、ストップフラグがラッチされ、戦略は次のイテレーションでオープンポジションの決済を開始する。フラグは、ポジションサイズがゼロに戻りベース PnL が更新された後にのみ解除される。
- オプションの取引ウィンドウが有効な場合、リスクチェックはローソク足の終値時刻が許可された区間内にあるかどうかも評価する。ウィンドウは MetaTrader のロジックを反映して、深夜をまたぐイントラデイ・セッションをサポートする。
- ストップフラグがアクティブであるか、セッションフィルターが市場が許可された時間外にあることを検知した場合、モジュールはポジションをフラット化するために反対方向の成行注文を送る。各エグジットの理由を説明する情報ログエントリが記録される。
パラメーター
| 名前 |
説明 |
LossMode |
損失閾値の解釈方法を選択する:現在のアカウント資本のパーセンテージ、または絶対アカウント通貨。 |
StopLoss |
損失閾値の値。パーセンテージモードでは数値がパーセントを表し、通貨モードではアカウント通貨を使用する。 |
UseTimeFilter |
イントラデイ取引ウィンドウを有効にする。無効にすると、戦略は時間フィルターを完全に無視する。 |
StartTime |
UTC での取引ウィンドウの開始(含む)。有効なセッションを定義するために EndTime と連動する。 |
EndTime |
UTC での取引ウィンドウの終了(含まず)。終了時刻が開始時刻より早い場合、深夜をまたぐセッションをサポートする。 |
CandleType |
定期的なリスク評価を駆動するためのローソク足サブスクリプション。デフォルトは1分の時間軸。 |
実装上の注意
- ベース PnL はポジションサイズがゼロに戻るたびに再計算されるため、後続の取引がクリーンなベースで開始される。
- 資本値はライブポートフォリオから取得されるため、パーセンテージモードはアカウント価値の実現済みおよび未実現変化の両方に適応する。
- ソースコードのすべてのコメントはプロジェクトの規則に従って英語で書かれている。
- 利用可能な場合、戦略はデフォルトのチャートエリアにローソク足と自己取引を描画し、テスト中の動作を視覚化する。
使用ガイドライン
- 監視したい銘柄に戦略を添付する。他の戦略からの注文生成は引き続き可能。このモジュールは監視してポジションを決済するだけである。
- リスク許容度に合った損失モードと閾値を設定する。たとえば、
LossMode = Percent と StopLoss = 5 は、現在の資本に対する5%の未実現ドローダウン後にポジションを決済する。
StartTime と EndTime パラメーターを設定して、特定のイントラデイセッションへの取引を制限する。一晩のウィンドウをカバーするには、終了時刻より後の開始時刻を指定する(例:20:00から06:00)。
- バックテストまたはライブセッションを実行する。すべてのポジションがフラット化されると、戦略は自動的にストップフラグをリセットし、その後の取引を監視し続ける。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Global Stop Loss Time strategy (simplified). Trades EMA crossover with
/// session time filter and global stop loss based on drawdown.
/// </summary>
public class GlobalStopLossTimeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaFastLength;
private readonly StrategyParam<int> _emaSlowLength;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaFastLength
{
get => _emaFastLength.Value;
set => _emaFastLength.Value = value;
}
public int EmaSlowLength
{
get => _emaSlowLength.Value;
set => _emaSlowLength.Value = value;
}
public GlobalStopLossTimeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
_emaFastLength = Param(nameof(EmaFastLength), 8)
.SetGreaterThanZero()
.SetDisplay("EMA Fast", "Fast EMA period", "Indicators");
_emaSlowLength = Param(nameof(EmaSlowLength), 21)
.SetGreaterThanZero()
.SetDisplay("EMA Slow", "Slow EMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var emaFast = new ExponentialMovingAverage { Length = EmaFastLength };
var emaSlow = new ExponentialMovingAverage { Length = EmaSlowLength };
decimal prevFast = 0, prevSlow = 0;
var hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(emaFast, emaSlow, (ICandleMessage candle, decimal fastVal, decimal slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevFast = fastVal;
prevSlow = slowVal;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevFast = fastVal;
prevSlow = slowVal;
return;
}
// EMA crossover signals
if (prevFast <= prevSlow && fastVal > slowVal && Position <= 0)
BuyMarket();
else if (prevFast >= prevSlow && fastVal < slowVal && Position >= 0)
SellMarket();
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, emaFast);
DrawIndicator(area, emaSlow);
DrawOwnTrades(area);
}
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class global_stop_loss_time_strategy(Strategy):
def __init__(self):
super(global_stop_loss_time_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candles", "General")
self._ema_fast_length = self.Param("EmaFastLength", 8) \
.SetDisplay("EMA Fast", "Fast EMA period", "Indicators")
self._ema_slow_length = self.Param("EmaSlowLength", 21) \
.SetDisplay("EMA Slow", "Slow EMA period", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaFastLength(self):
return self._ema_fast_length.Value
@property
def EmaSlowLength(self):
return self._ema_slow_length.Value
def OnReseted(self):
super(global_stop_loss_time_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(global_stop_loss_time_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self.EmaFastLength
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self.EmaSlowLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema_fast, ema_slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema_fast)
self.DrawIndicator(area, ema_slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if not self._has_prev:
self._prev_fast = fv
self._prev_slow = sv
self._has_prev = True
return
if self._prev_fast <= self._prev_slow and fv > sv and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fv < sv and self.Position >= 0:
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def CreateClone(self):
return global_stop_loss_time_strategy()