チャネル EA 指値注文戦略
概要
- 起源: MetaTrader 5 エキスパート
ChannelEA1.mq5から変換。 - 目的: ユーザー定義の 2 つの時間の間でイントラデイ価格チャネルを監視し、そのウィンドウの終わりに指値注文をキューに入れる。
- アプローチ: 戦略はセッション中に観察された最高値と最安値を追跡し、チャネルの反対側に向かう潜在的な反転を取引するために対称的な指値注文を配置します。
この戦略は、日次レンジが確立されると平均回帰を示す銘柄に適しています。設計上、ネッティング口座で動作します:約定した売り指値注文は、新しいショートを開く前に既存のロングポジションを閉じます(逆も同様)。
パラメーター
| 名前 | デフォルト | 説明 |
|---|---|---|
BeginHour |
1 |
イントラデイレンジ追跡が始まる時間(0-23)。この時間に戦略は未決注文をキャンセルしポジションを閉じます。 |
EndHour |
10 |
累積レンジが評価され、新しい指値注文が配置される時間(0-23)。オーバーナイトセッションをサポート:BeginHour > EndHour の場合、セッションは深夜をまたぎます。 |
OrderVolume |
1 |
すべての未決注文に適用されるボリューム。 |
CandleType |
1 時間タイムフレーム |
チャネルを構築するために使用されるローソク足シリーズ。StockSharp でサポートされている任意のタイムフレームに切り替えられます。 |
トレードロジック
- セッション処理
- 戦略はローソク足のタイムスタンプを使用して
BeginHourおよびEndHourパラメーターからセッションの開始と終了のタイムスタンプを導きます。BeginHour > EndHourの場合、終了は翌日に移されます。 - 閉じる時間が開始境界に達する最初の完成したローソク足で、戦略はすべてのアクティブな注文をキャンセルし、オープンポジションを閉じ、セッション統計をリセットします。
- 戦略はローソク足のタイムスタンプを使用して
- チャネル構築
- セッションウィンドウ内に開く時間があるローソク足のみがレンジに貢献します。戦略はセッションの最高値と最安値を追跡し、貢献するローソク足の数をカウントします。
- 有効なレンジを形成するには少なくとも 2 つの完成したローソク足が必要で、元の MQL5 エキスパートの動作(
n > 2条件)を反映しています。
- セッション終了時の注文配置
- 完成したローソク足が終了境界を越えた場合、戦略はレンジが形成されており安値が高値を厳密に下回っていることを確認します。
- 次に 2 つの未決注文を配置します:
OrderVolumeボリュームで記録されたセッション安値にBuyLimit。- 同じボリュームで記録されたセッション高値に
SellLimit。
- 注文は次のセッションが始まるまでアクティブのままです。戦略はネッティング口座で実行されるため、これらの注文はエントリーと出口の両方として機能します:例えば、
SellLimitは新しいショートを確立する前にセッション高値で既存のロングポジションを閉じます。
- 次のセッションの準備
- 次の開始境界で、戦略は残っているポジションを閉じ、新しいチャネルを測定する前に残った未決注文を削除します。
追加注意事項
- 明示的なストップロスは設定されていません。リスク管理はポジションサイジング、手動の上書き、または外部の保護ロジックを通じて制御する必要があります。
- ロジックは元の EA 動作に合わせるために完成したローソク足(
CandleStates.Finished)のみを使用します。 - セッション境界は取引所/現地時間で評価されるため、データフィードとサーバーのタイムゾーンが期待と一致していることを確認してください。
- 最適化する際は、取引時間とローソク足の期間の両方を考慮してください;記録されたレンジは選択したタイムフレームに依存するため、戦略はその組み合わせに敏感です。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Channel trading strategy that places limit orders at the end of the monitored session.
/// </summary>
public class ChannelEaLimitsStrategy : Strategy
{
private readonly StrategyParam<int> _beginHour;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private DateTimeOffset _sessionStart;
private DateTimeOffset _sessionEnd;
private decimal _sessionHigh;
private decimal _sessionLow;
private int _barsInSession;
private DateTimeOffset? _prevCandleClose;
private bool _ordersPlaced;
private bool _needsSessionReset;
private bool _tradeTaken;
/// <summary>
/// Initializes a new instance of the <see cref="ChannelEaLimitsStrategy"/> class.
/// </summary>
public ChannelEaLimitsStrategy()
{
_beginHour = Param(nameof(BeginHour), 1)
.SetDisplay("Begin Hour", "Hour when session tracking starts (0-23)", "Session")
.SetRange(0, 23);
_endHour = Param(nameof(EndHour), 10)
.SetDisplay("End Hour", "Hour when limit orders are placed (0-23)", "Session")
.SetRange(0, 23);
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetDisplay("Order Volume", "Volume for each limit order", "Trading")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used to build the session channel", "General");
}
/// <summary>
/// Hour when session tracking starts.
/// </summary>
public int BeginHour
{
get => _beginHour.Value;
set => _beginHour.Value = value;
}
/// <summary>
/// Hour when the strategy places new pending orders.
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// Volume per limit order.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Working candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sessionStart = DateTimeOffset.MinValue;
_sessionEnd = DateTimeOffset.MinValue;
_sessionHigh = decimal.MinValue;
_sessionLow = decimal.MaxValue;
_barsInSession = 0;
_prevCandleClose = null;
_ordersPlaced = false;
_needsSessionReset = false;
_tradeTaken = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
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 closeTime = candle.CloseTime;
var sessionStart = CalculateSessionStart(closeTime);
if (_sessionStart != sessionStart)
{
_sessionStart = sessionStart;
_sessionEnd = CalculateSessionEnd(_sessionStart);
ResetSessionState();
}
if (_needsSessionReset)
{
// Close any open position at session reset
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_needsSessionReset = false;
}
if (candle.OpenTime >= _sessionStart && candle.OpenTime < _sessionEnd)
{
var high = candle.HighPrice;
var low = candle.LowPrice;
if (_sessionHigh == decimal.MinValue || high > _sessionHigh)
_sessionHigh = high;
if (_sessionLow == decimal.MaxValue || low < _sessionLow)
_sessionLow = low;
_barsInSession++;
}
// After session ends, trade breakouts of the channel
if (_ordersPlaced && !_tradeTaken && _barsInSession >= 2 && _sessionLow < _sessionHigh)
{
if (Position == 0)
{
// Buy when price touches session low, sell when it touches session high
if (candle.LowPrice <= _sessionLow)
{
BuyMarket(OrderVolume);
_tradeTaken = true;
}
else if (candle.HighPrice >= _sessionHigh)
{
SellMarket(OrderVolume);
_tradeTaken = true;
}
}
}
if (!_ordersPlaced && _prevCandleClose.HasValue)
{
var previousClose = _prevCandleClose.Value;
if (previousClose < _sessionEnd && closeTime >= _sessionEnd)
{
if (_barsInSession >= 2 && _sessionLow < _sessionHigh)
{
_ordersPlaced = true;
}
}
}
_prevCandleClose = closeTime;
}
private void ResetSessionState()
{
_sessionHigh = decimal.MinValue;
_sessionLow = decimal.MaxValue;
_barsInSession = 0;
_ordersPlaced = false;
_needsSessionReset = true;
_tradeTaken = false;
}
private DateTimeOffset CalculateSessionStart(DateTimeOffset time)
{
var offset = time.Offset;
var day = new DateTimeOffset(time.Date, offset);
var start = day.AddHours(BeginHour);
var startHour = TimeSpan.FromHours(BeginHour);
if (BeginHour <= EndHour)
{
if (time < start)
start = start.AddDays(-1);
}
else
{
if (time.TimeOfDay < startHour)
start = start.AddDays(-1);
}
return start;
}
private DateTimeOffset CalculateSessionEnd(DateTimeOffset sessionStart)
{
var offset = sessionStart.Offset;
var day = new DateTimeOffset(sessionStart.Date, offset);
var end = day.AddHours(EndHour);
if (EndHour <= BeginHour || end <= sessionStart)
end = end.AddDays(1);
return end;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan
class channel_ea_limits_strategy(Strategy):
def __init__(self):
super(channel_ea_limits_strategy, self).__init__()
self._begin_hour = self.Param("BeginHour", 1)
self._end_hour = self.Param("EndHour", 10)
self._order_volume = self.Param("OrderVolume", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._session_high = None
self._session_low = None
self._bars_in_session = 0
self._prev_candle_close = None
self._orders_placed = False
self._needs_session_reset = False
self._trade_taken = False
self._session_start = None
self._session_end = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(channel_ea_limits_strategy, self).OnStarted2(time)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _calc_session_start(self, close_time):
begin = self._begin_hour.Value
end = self._end_hour.Value
day = close_time.Date
start = day.AddHours(begin)
if begin <= end:
if close_time < start:
start = start.AddDays(-1)
else:
if close_time.TimeOfDay.TotalHours < begin:
start = start.AddDays(-1)
return start
def _calc_session_end(self, session_start):
end_hour = self._end_hour.Value
begin_hour = self._begin_hour.Value
day = session_start.Date
end = day.AddHours(end_hour)
if end_hour <= begin_hour or end <= session_start:
end = end.AddDays(1)
return end
def _reset_session(self):
self._session_high = None
self._session_low = None
self._bars_in_session = 0
self._orders_placed = False
self._needs_session_reset = True
self._trade_taken = False
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close_time = candle.CloseTime
session_start = self._calc_session_start(close_time)
if self._session_start is None or self._session_start != session_start:
self._session_start = session_start
self._session_end = self._calc_session_end(session_start)
self._reset_session()
if self._needs_session_reset:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._needs_session_reset = False
open_time = candle.OpenTime
if open_time >= self._session_start and open_time < self._session_end:
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self._session_high is None or h > self._session_high:
self._session_high = h
if self._session_low is None or lo < self._session_low:
self._session_low = lo
self._bars_in_session += 1
if self._orders_placed and not self._trade_taken and self._bars_in_session >= 2:
if self._session_low is not None and self._session_high is not None and self._session_low < self._session_high:
if self.Position == 0:
if float(candle.LowPrice) <= self._session_low:
self.BuyMarket(self._order_volume.Value)
self._trade_taken = True
elif float(candle.HighPrice) >= self._session_high:
self.SellMarket(self._order_volume.Value)
self._trade_taken = True
if not self._orders_placed and self._prev_candle_close is not None:
if self._prev_candle_close < self._session_end and close_time >= self._session_end:
if self._bars_in_session >= 2 and self._session_low is not None and self._session_high is not None and self._session_low < self._session_high:
self._orders_placed = True
self._prev_candle_close = close_time
def OnReseted(self):
super(channel_ea_limits_strategy, self).OnReseted()
self._session_high = None
self._session_low = None
self._bars_in_session = 0
self._prev_candle_close = None
self._orders_placed = False
self._needs_session_reset = False
self._trade_taken = False
self._session_start = None
self._session_end = None
def CreateClone(self):
return channel_ea_limits_strategy()