GitHub で見る
PriceChannel シグナル v2 戦略
概要
PriceChannel Signal v2 は、変更された Donchian チャネルを中心に構築されたトレンド追跡ブレイクアウト システムです。オリジナルの MQL5 エキスパート アドバイザーは、チャネル トレンドの推移、価格がバンドを押し戻されたときのオプションのリエントリー条件、同じレンジから導出される保護的な出口レベルを監視します。 StockSharp ポートは元の動作を維持します。一度に 1 つのポジションを取引し、完了したローソク足にのみ反応し、日中ウィンドウに制限できます。
取引ロジック
- Donchian チャンネルの高値と低値は、設定された
ChannelPeriod に基づいて計算されます。
- 生の範囲は 2 つの乗数によってシフトされます。
- リスクファクター – エントリーバンドをチャネル中央値に向かって圧縮します。
- 終了レベル – 終了をトリガーする内側バンドの 2 番目のペアを構築します。
- トレンド状態が維持されます。
- 終値が上部エントリーバンドを上抜けると、トレンドは強気になります。
- 終値が下限エントリーバンドを下回ると、トレンドは弱気になります。
- それ以外の場合は、以前の傾向が維持されます。
- その状態から生成される信号:
- ロングエントリー – トレンドが弱気から強気に反転します。
- ショートエントリー – トレンドが強気から弱気に反転します。
- ロングリエントリー – オプション。トレンドがすでに強気である間に価格が上限バンドを上回って終了します。
- ショートリエントリー – オプション。トレンドがすでに弱気である間に、価格は下限バンドを下回って終了します。
- ロングエグジット – オプション。価格は前のバーで強気エグジットバンドを上回った後、その下で終了します。
- ショートエグジット – オプション。価格は前の足で弱気エグジットバンドを下回った後、その上で終了します。
- バーごと、方向ごとに 1 つのオーダーのみが許可されます。この戦略は、別のポジションがすでにアクティブである場合、新しいポジションをオープンすることを拒否します。
- 日中時間フィルターが有効な場合、構成されたウィンドウ外では上記の信号はすべて無視されます。
パラメーター
| パラメータ |
説明 |
ChannelPeriod |
価格チャネルと出口バンドの計算に使用される Donchian のルックバック長。 |
RiskFactor |
エントリーバンドのシフト (0 ~ 10)。値を低くすると帯域が広がり、値を高くすると帯域が狭くなります。 |
ExitLevel |
出口バンドのシフト。エントリ範囲内に収まるようにするには、RiskFactor より大きくする必要があります。 |
UseReEntry |
価格がアクティブバンドを通過して押し戻されたときに、リエントリー取引が可能になります。 |
UseExitSignals |
内側の保護バンドに基づいて終了ロジックを有効にします。 |
CandleType |
ローソク足を構築し、インジケーターを実行するために使用される時間枠。 |
UseTimeControl |
日中取引ウィンドウを切り替えます。 |
StartHour / StartMinute |
時間制御がアクティブな場合の取引ウィンドウの包括的な開始。 |
EndHour / EndMinute |
時間制御がアクティブな場合の取引ウィンドウの排他的な終了。 |
入退場のルール
- ロングエントリー: トレンドが強気またはリエントリー条件に反転し、現在位置はフラットで、バーは許容時間枠内にあります。
- ショート入力: トレンドが弱気またはショートリエントリー条件に反転し、現在位置は横ばいで、バーは許容時間枠内にあります。
- Exit Long:
UseExitSignals が有効になっており、終値は前のバーでExit バンドを上回った後、下に落ちます。
- エグジットショート:
UseExitSignals が有効で、終値は前の足でエグジットバンドを下回った後、エグジットバンドを上回ります。
追加の注意事項
- この戦略は成行注文で機能し、ポジションをピラミッド化するものではありません。
- インジケーター値は、バー内再描画を避けるために、完成したローソク足でのみ処理されます。
- 明示的に指定しない場合、ボリュームはデフォルトで 1 コントラクトになります。
- 時間制御は元の EA の動作に従います。終了時刻は排他的であり、午前 0 時をまたぐラップがサポートされています。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Price Channel Signal v2 strategy that reacts to Donchian channel breakouts.
/// </summary>
public class PriceChannelSignalV2Strategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _highHistory = new();
private readonly Queue<decimal> _lowHistory = new();
private int _previousTrend;
private decimal? _previousClose;
/// <summary>
/// Channel lookback length.
/// </summary>
public int ChannelPeriod
{
get => _channelPeriod.Value;
set => _channelPeriod.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize a new instance of <see cref="PriceChannelSignalV2Strategy"/>.
/// </summary>
public PriceChannelSignalV2Strategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Channel Period", "Donchian lookback used for Price Channel", "Price Channel");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for Price Channel", "General");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousTrend = 0;
_previousClose = null;
_highHistory.Clear();
_lowHistory.Clear();
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;
if (_highHistory.Count < ChannelPeriod)
{
EnqueueCandle(candle);
return;
}
var highs = _highHistory.ToArray();
var lows = _lowHistory.ToArray();
var channelHigh = GetMax(highs);
var channelLow = GetMin(lows);
var range = channelHigh - channelLow;
if (range <= 0m)
{
_previousClose = candle.ClosePrice;
EnqueueCandle(candle);
return;
}
var mid = (channelHigh + channelLow) / 2m;
// Update trend state based on channel breakout
var trend = _previousTrend;
if (candle.ClosePrice > channelHigh + range * 0.05m)
trend = 1;
else if (candle.ClosePrice < channelLow - range * 0.05m)
trend = -1;
var volume = Volume;
if (volume <= 0)
volume = 1;
// Trend reversal signals
var changedPosition = false;
if (trend > 0 && _previousTrend <= 0)
{
if (Position <= 0)
{
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
changedPosition = true;
}
}
else if (trend < 0 && _previousTrend >= 0)
{
if (Position >= 0)
{
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
changedPosition = true;
}
}
// Exit on mid-line cross
if (!changedPosition && Position > 0 && _previousClose is decimal pc1 && pc1 >= mid && candle.ClosePrice < mid)
{
SellMarket(Math.Abs(Position));
}
else if (!changedPosition && Position < 0 && _previousClose is decimal pc2 && pc2 <= mid && candle.ClosePrice > mid)
{
BuyMarket(Math.Abs(Position));
}
_previousTrend = trend;
_previousClose = candle.ClosePrice;
EnqueueCandle(candle);
}
private void EnqueueCandle(ICandleMessage candle)
{
_highHistory.Enqueue(candle.HighPrice);
_lowHistory.Enqueue(candle.LowPrice);
while (_highHistory.Count > ChannelPeriod)
_highHistory.Dequeue();
while (_lowHistory.Count > ChannelPeriod)
_lowHistory.Dequeue();
}
private static decimal GetMax(IEnumerable<decimal> values)
{
var max = decimal.MinValue;
foreach (var value in values)
{
if (value > max)
max = value;
}
return max;
}
private static decimal GetMin(IEnumerable<decimal> values)
{
var min = decimal.MaxValue;
foreach (var value in values)
{
if (value < min)
min = value;
}
return min;
}
/// <inheritdoc />
protected override void OnReseted()
{
_previousTrend = 0;
_previousClose = null;
_highHistory.Clear();
_lowHistory.Clear();
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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class price_channel_signal_v2_strategy(Strategy):
def __init__(self):
super(price_channel_signal_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._channel_period = self.Param("ChannelPeriod", 20)
self._high_history = []
self._low_history = []
self._prev_trend = 0
self._prev_close = 0.0
self._has_prev_close = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def ChannelPeriod(self):
return self._channel_period.Value
@ChannelPeriod.setter
def ChannelPeriod(self, value):
self._channel_period.Value = value
def OnReseted(self):
super(price_channel_signal_v2_strategy, self).OnReseted()
self._high_history = []
self._low_history = []
self._prev_trend = 0
self._prev_close = 0.0
self._has_prev_close = False
def OnStarted2(self, time):
super(price_channel_signal_v2_strategy, self).OnStarted2(time)
self._high_history = []
self._low_history = []
self._prev_trend = 0
self._prev_close = 0.0
self._has_prev_close = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
period = self.ChannelPeriod
if len(self._high_history) < period:
self._high_history.append(high)
self._low_history.append(low)
self._prev_close = close
self._has_prev_close = True
return
channel_high = max(self._high_history)
channel_low = min(self._low_history)
ch_range = channel_high - channel_low
if ch_range <= 0:
self._prev_close = close
self._high_history.append(high)
self._low_history.append(low)
while len(self._high_history) > period:
self._high_history.pop(0)
while len(self._low_history) > period:
self._low_history.pop(0)
return
mid = (channel_high + channel_low) / 2.0
trend = self._prev_trend
if close > channel_high + ch_range * 0.05:
trend = 1
elif close < channel_low - ch_range * 0.05:
trend = -1
changed_position = False
if trend > 0 and self._prev_trend <= 0:
if self.Position <= 0:
self.BuyMarket()
changed_position = True
elif trend < 0 and self._prev_trend >= 0:
if self.Position >= 0:
self.SellMarket()
changed_position = True
# Exit on mid-line cross
if not changed_position and self._has_prev_close:
if self.Position > 0 and self._prev_close >= mid and close < mid:
self.SellMarket()
elif self.Position < 0 and self._prev_close <= mid and close > mid:
self.BuyMarket()
self._prev_trend = trend
self._prev_close = close
self._has_prev_close = True
self._high_history.append(high)
self._low_history.append(low)
while len(self._high_history) > period:
self._high_history.pop(0)
while len(self._low_history) > period:
self._low_history.pop(0)
def CreateClone(self):
return price_channel_signal_v2_strategy()