GitHub で見る
21時間セッションブレイクアウト戦略
この戦略はStockSharp内でMetaTraderの「21hour」エキスパートアドバイザーを再現します。2つの設定可能な取引ウィンドウ中に動作し、保留ストップ注文を使用してレンジの上下でのブレイクアウトを捉えます。各ウィンドウの終わりに、戦略はオープンなエクスポージャーを清算し、アクティブな注文を削除して、毎取引日がクリーンな状態で始まることを保証します。
コアアイデア
- 取引方向は指定されたセッション開始時刻周辺の価格行動によってのみ決定されます。
- 各セッションの始まりに、戦略は現在のアスクを上回るバイストップと現在のビッドを下回るセルストップでマーケットを挟みます。
- ストップ注文が執行されると、反対側は即座にキャンセルされ、固定距離のテイクプロフィット注文が配置されます。
- 設定されたセッション終了時刻に、すべてのポジションがクローズされ、テイクプロフィットに達していなくても、すべての注文がキャンセルされます。
データフロー
- ローソク足: 1分ローソク足(設定可能)はタイムスタンプの提供と毎時スケジュールチェックのトリガーにのみ使用されます。
- 注文帳: レベル1クオートはストップ注文の有効化価格を定義する現在の最良ビッド/アスク値を提供します。
取引ルール
エントリースケジューリング
FirstSessionStartHour(デフォルト サーバー時刻08:00)およびSecondSessionStartHour(デフォルト22:00)に、戦略は:
Ask + StepPoints * PriceStepにバイストップを配置します。
Bid - StepPoints * PriceStepにセルストップを配置します。
- 1つのポジションのみが許可されます。他のセッションが開始したときにすでにポジションが開いている場合、新しいものを配置する前にすべての保留エントリー注文が削除されます。
注文管理
- ストップ注文の1つが執行されると、反対のストップは即座にキャンセルされます。
- テイクプロフィット指値注文は取引方向に応じて
EntryPrice ± TakeProfitPoints * PriceStepに登録されます。
- 注文サイズは
Volumeパラメーター(デフォルト1ロット)で固定されます。
決済ロジック
- テイクプロフィット注文は勝ち取引を自動的にクローズします。
FirstSessionStopHour(デフォルト21:00)およびSecondSessionStopHour(デフォルト23:00)に、戦略は成行でオープンポジションをクローズし、残りのすべての保留注文をキャンセルします。
- ポジションが手動でクローズされた場合、戦略は保留中のテイクプロフィット注文も削除します。
パラメーター
| パラメーター |
デフォルト値 |
説明 |
Volume |
1 |
ストップエントリーとテイクプロフィット決済に使用する注文ボリューム。 |
FirstSessionStartHour |
8 |
最初の取引セッションが始まる時刻(0-23)。 |
FirstSessionStopHour |
21 |
最初のセッションが終わりポジションがクローズされる時刻。 |
SecondSessionStartHour |
22 |
夜間セッションが始まる時刻。最初のセッション後でなければなりません。 |
SecondSessionStopHour |
23 |
2番目のセッションが終わる時刻。最初のセッション停止後でなければなりません。 |
StepPoints |
5 |
最良クオートからエントリーストップ注文までの距離(価格ステップ単位)。 |
TakeProfitPoints |
40 |
エントリー価格とテイクプロフィットリミットの距離(価格ステップ単位)。 |
CandleType |
1分 |
イントラデイスケジュールチェックを動かすローソク足タイプ。 |
すべてのパラメーターは重複するセッションや不可能な時間の組み合わせを避けるために検証されます。
タグと特性
- スタイル: セッションブレイクアウト / 時間ベースのトレンドフォロー。
- 方向: ロングとショート。
- 時間軸: イントラデイ、スケジュール駆動(タイミングのみに1分ローソク足)。
- リスク管理: 固定テイクプロフィット + セッション終了時の強制フラット(ストップロスなし)。
- 市場タイプ: 継続的な取引時間と信頼性の高いクオートを持つFX、インデックス、または任意の銘柄向けに設計されています。
- 複雑さ: 低 – インジケーターなし、純粋に時間と価格ベース。
実装上の注意事項
- 戦略は有効な
Security.PriceStepを必要とします。価格ステップまたはクオートが利用できない場合、注文はスキップされます。
- テイクプロフィットボリュームは利用可能な場合に執行された取引ボリュームを使用し、現在のポジションまたは設定されたボリュームにフォールバックします。
- コードはStockSharpの高レベルAPI(
SubscribeCandles、SubscribeOrderBook、ヘルパーパラメーター、注文ヘルパー)を活用しながら、オリジナルのMQLロジックを反映した英語のインラインコメントを維持しています。
using System;
using System.Linq;
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>
/// 21-hour session breakout strategy. Places simulated stop entries via candle breakout logic.
/// </summary>
public class TwentyOneHourSessionBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _firstSessionStartHour;
private readonly StrategyParam<int> _firstSessionStopHour;
private readonly StrategyParam<decimal> _stepPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal? _sessionOpen;
private decimal _entryPrice;
private bool _inSession;
public int FirstSessionStartHour
{
get => _firstSessionStartHour.Value;
set => _firstSessionStartHour.Value = value;
}
public int FirstSessionStopHour
{
get => _firstSessionStopHour.Value;
set => _firstSessionStopHour.Value = value;
}
public decimal StepPoints
{
get => _stepPoints.Value;
set => _stepPoints.Value = value;
}
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public TwentyOneHourSessionBreakoutStrategy()
{
_firstSessionStartHour = Param(nameof(FirstSessionStartHour), 2)
.SetDisplay("Session Start", "Hour of the trading window start", "Schedule");
_firstSessionStopHour = Param(nameof(FirstSessionStopHour), 20)
.SetDisplay("Session Stop", "Hour of the trading window stop", "Schedule");
_stepPoints = Param(nameof(StepPoints), 40m)
.SetGreaterThanZero()
.SetDisplay("Step Points", "Distance from session open to breakout level", "Orders");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
.SetGreaterThanZero()
.SetDisplay("Take Profit Points", "Take-profit distance", "Orders");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles used to drive the trading schedule", "Data");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sessionOpen = null;
_entryPrice = 0m;
_inSession = false;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.OpenTime.Hour;
var priceStep = Security?.PriceStep ?? 1m;
// Session start: record the open price
if (hour >= FirstSessionStartHour && hour < FirstSessionStopHour)
{
if (!_inSession)
{
_sessionOpen = candle.OpenPrice;
_inSession = true;
}
if (_sessionOpen == null)
return;
var stepOffset = StepPoints * priceStep;
var buyLevel = _sessionOpen.Value + stepOffset;
var sellLevel = _sessionOpen.Value - stepOffset;
// Breakout entry
if (Position == 0)
{
if (candle.HighPrice >= buyLevel)
{
BuyMarket();
_entryPrice = buyLevel;
}
else if (candle.LowPrice <= sellLevel)
{
SellMarket();
_entryPrice = sellLevel;
}
}
// Take profit
if (Position > 0)
{
var tp = _entryPrice + TakeProfitPoints * priceStep;
if (candle.HighPrice >= tp)
{
SellMarket();
_sessionOpen = candle.ClosePrice;
}
}
else if (Position < 0)
{
var tp = _entryPrice - TakeProfitPoints * priceStep;
if (candle.LowPrice <= tp)
{
BuyMarket();
_sessionOpen = candle.ClosePrice;
}
}
}
else
{
// Session end: close position
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_inSession = false;
_sessionOpen = null;
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class twenty_one_hour_session_breakout_strategy(Strategy):
def __init__(self):
super(twenty_one_hour_session_breakout_strategy, self).__init__()
self._first_session_start_hour = self.Param("FirstSessionStartHour", 2)
self._first_session_stop_hour = self.Param("FirstSessionStopHour", 20)
self._step_points = self.Param("StepPoints", 40.0)
self._take_profit_points = self.Param("TakeProfitPoints", 200.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._session_open = None
self._entry_price = 0.0
self._in_session = False
@property
def FirstSessionStartHour(self):
return self._first_session_start_hour.Value
@FirstSessionStartHour.setter
def FirstSessionStartHour(self, value):
self._first_session_start_hour.Value = value
@property
def FirstSessionStopHour(self):
return self._first_session_stop_hour.Value
@FirstSessionStopHour.setter
def FirstSessionStopHour(self, value):
self._first_session_stop_hour.Value = value
@property
def StepPoints(self):
return self._step_points.Value
@StepPoints.setter
def StepPoints(self, value):
self._step_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(twenty_one_hour_session_breakout_strategy, self).OnStarted2(time)
self._session_open = None
self._entry_price = 0.0
self._in_session = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
price_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if price_step <= 0.0:
price_step = 1.0
start_hour = int(self.FirstSessionStartHour)
stop_hour = int(self.FirstSessionStopHour)
if hour >= start_hour and hour < stop_hour:
if not self._in_session:
self._session_open = float(candle.OpenPrice)
self._in_session = True
if self._session_open is None:
return
step_offset = float(self.StepPoints) * price_step
buy_level = self._session_open + step_offset
sell_level = self._session_open - step_offset
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self.Position == 0:
if high >= buy_level:
self.BuyMarket()
self._entry_price = buy_level
elif low <= sell_level:
self.SellMarket()
self._entry_price = sell_level
if self.Position > 0:
tp = self._entry_price + float(self.TakeProfitPoints) * price_step
if high >= tp:
self.SellMarket()
self._session_open = close
elif self.Position < 0:
tp = self._entry_price - float(self.TakeProfitPoints) * price_step
if low <= tp:
self.BuyMarket()
self._session_open = close
else:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._in_session = False
self._session_open = None
def OnReseted(self):
super(twenty_one_hour_session_breakout_strategy, self).OnReseted()
self._session_open = None
self._entry_price = 0.0
self._in_session = False
def CreateClone(self):
return twenty_one_hour_session_breakout_strategy()