Die ChannelEA2-Strategie repliziert den MetaTrader-Expertenberater „ChannelEA2" in StockSharp. Die Strategie erstellt einen Intraday-Preiskanal zwischen den konfigurierten Sitzungsstart- und -endstunden. Wenn die Sitzung endet, platziert sie Stop-Orders über dem Kanalhoch und unterhalb des Kanaltiefs. Jede Stop-Order trägt einen durch den gegenüberliegenden Kanalrand definierten Schutz-Stop-Loss. Der Ansatz zielt darauf ab, Ausbrüche nach einer Konsolidierungsphase während des Sitzungsfensters zu erfassen.
Handelslogik
Bei der ersten abgeschlossenen Kerze, deren Eröffnungszeit BeginHour überschreitet, setzt die Strategie die Sitzung zurück.
Alle offenen Positionen werden mit Market-Orders geschlossen.
Alle aktiven Orders, einschließlich früherer Stop-Einstiege oder Schutz-Stops, werden storniert.
Sitzungshoch und -tief werden mit der ersten Kerze innerhalb der neuen Sitzung initialisiert.
Während der Sitzung (von BeginHour bis EndHour) aktualisieren Hoch und Tief jeder abgeschlossenen Kerze die Kanalgrenzen.
Bei der ersten Kerze, die nach dem Ende der Sitzung (EndHour) eröffnet, berechnet die Strategie:
Eine Kauf-Stop-Order am aufgezeichneten Sitzungshoch plus einem optionalen Buffer in Preisschritten.
Eine Verkauf-Stop-Order am aufgezeichneten Sitzungstief minus demselben Buffer.
Der Stop-Loss für die Kauf-Order ist das Sitzungstief, der Stop-Loss für die Verkauf-Order ist das Sitzungshoch.
Wenn eine Position eröffnet wird, wird die entgegengesetzte Einstiegs-Order storniert und ein Schutz-Stop mit dem gespeicherten Stop-Niveau im Markt registriert.
Orders bleiben bis zum nächsten Sitzungsstart aktiv, wenn alles wieder zurückgesetzt wird.
Parameter
Name
Beschreibung
Standard
BeginHour
Stunde (0-23), bei der die Sitzung zurückgesetzt wird und der Kanal beginnt, Daten zu sammeln.
1
EndHour
Stunde (0-23), bei der Stop-Orders geplant werden. Unterstützt Übernachtsitzungen, wenn BeginHour > EndHour.
10
TradeVolume
Volumen für jede Einstiegs-Order.
1
CandleType
Kerzenserie zum Aufbau des Kanals (Standard 1-Stunden-Kerzen).
1 Stunde
StopBufferMultiplier
Multiplikator des Instrument-Preisschritts als Sicherheitspuffer für Einstiegsaktivierung und Schutz-Stops.
2
Risikomanagement
Die Strategie ruft automatisch StartProtection() auf, um StockSharp unerwartete Positionen verwalten zu lassen.
Schutz-Stop-Orders werden sofort nach dem Erscheinen einer Position übermittelt. Sie werden storniert, wenn die Position auf null zurückgeht.
Stop-Preise werden um StopBufferMultiplier * PriceStep verschoben, um die Mindest-Stop-Abstands-Grenzen der Börse nicht zu verletzen.
Zusätzliche Hinweise
Der Kanalbereich friert ein, sobald die Stop-Orders generiert werden; spätere Kerzen beeinflussen die Einstiegsniveaus bis zum nächsten Sitzungsstart nicht.
Wenn das Instrument kein PriceStep definiert hat, wird der Buffer ignoriert und Orders werden an den genauen Kanalniveaus platziert.
Volumenwerte sind Dezimalzahlen, was fraktionierte Kontrakte oder Lots erlaubt, wenn der Broker dies unterstützt.
Die Strategie zeichnet Kerzen und ausgeführte Trades im Chart-Bereich zur visuellen Nachverfolgung.
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>
/// Breakout strategy that places stop orders at the extremes of the intraday channel.
/// </summary>
public class ChannelEa2Strategy : Strategy
{
private readonly StrategyParam<int> _beginHour;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopBufferMultiplier;
private decimal? _sessionHigh;
private decimal? _sessionLow;
private bool _channelReady;
private decimal? _entryPrice;
private decimal? _stopLossPrice;
/// <summary>
/// Trading session start hour.
/// </summary>
public int BeginHour
{
get => _beginHour.Value;
set => _beginHour.Value = value;
}
/// <summary>
/// Trading session end hour.
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// Order volume.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Candle type used for channel detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of price steps added as a buffer to entry and protective orders.
/// </summary>
public decimal StopBufferMultiplier
{
get => _stopBufferMultiplier.Value;
set => _stopBufferMultiplier.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ChannelEa2Strategy"/> class.
/// </summary>
public ChannelEa2Strategy()
{
_beginHour = Param(nameof(BeginHour), 1)
.SetDisplay("Begin Hour", "Hour when the session resets", "Trading")
.SetOptimize(0, 23, 1);
_endHour = Param(nameof(EndHour), 10)
.SetDisplay("End Hour", "Hour when breakout orders are scheduled", "Trading")
.SetOptimize(0, 23, 1);
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetDisplay("Volume", "Order volume", "Trading")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for the channel", "General");
_stopBufferMultiplier = Param(nameof(StopBufferMultiplier), 2m)
.SetDisplay("Stop Buffer", "Price step multiplier for safety offsets", "Risk")
.SetNotNegative();
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sessionHigh = null;
_sessionLow = null;
_channelReady = false;
_entryPrice = null;
_stopLossPrice = null;
}
/// <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 hour = candle.OpenTime.Hour;
// During channel-building hours, accumulate the range.
if (hour >= BeginHour && hour < EndHour)
{
if (_sessionHigh is null || candle.HighPrice > _sessionHigh)
_sessionHigh = candle.HighPrice;
if (_sessionLow is null || candle.LowPrice < _sessionLow)
_sessionLow = candle.LowPrice;
_channelReady = true;
return;
}
// Outside the channel window, attempt breakout entries.
if (!_channelReady || _sessionHigh is not decimal high || _sessionLow is not decimal low || high <= low)
return;
var buffer = GetPriceBuffer();
// Manage existing positions.
if (Position > 0)
{
if (_stopLossPrice.HasValue && candle.LowPrice <= _stopLossPrice.Value)
{
SellMarket(Position);
_entryPrice = null;
_stopLossPrice = null;
}
return;
}
else if (Position < 0)
{
if (_stopLossPrice.HasValue && candle.HighPrice >= _stopLossPrice.Value)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
_stopLossPrice = null;
}
return;
}
// Long breakout: price exceeds channel high.
if (candle.HighPrice > high + buffer)
{
BuyMarket(TradeVolume > 0 ? TradeVolume : Volume);
_entryPrice = candle.ClosePrice;
_stopLossPrice = low - buffer;
// Reset channel for next session.
_sessionHigh = null;
_sessionLow = null;
_channelReady = false;
return;
}
// Short breakout: price drops below channel low.
if (candle.LowPrice < low - buffer)
{
SellMarket(TradeVolume > 0 ? TradeVolume : Volume);
_entryPrice = candle.ClosePrice;
_stopLossPrice = high + buffer;
_sessionHigh = null;
_sessionLow = null;
_channelReady = false;
}
}
private decimal GetPriceBuffer()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m || StopBufferMultiplier <= 0m)
return 0m;
return step * StopBufferMultiplier;
}
}
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, Math
class channel_ea2_strategy(Strategy):
def __init__(self):
super(channel_ea2_strategy, self).__init__()
self._begin_hour = self.Param("BeginHour", 1)
self._end_hour = self.Param("EndHour", 10)
self._trade_volume = self.Param("TradeVolume", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._stop_buffer_multiplier = self.Param("StopBufferMultiplier", 2.0)
self._session_high = None
self._session_low = None
self._channel_ready = False
self._entry_price = None
self._stop_loss_price = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(channel_ea2_strategy, self).OnStarted2(time)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
if hour >= self._begin_hour.Value and hour < self._end_hour.Value:
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._channel_ready = True
return
if not self._channel_ready or self._session_high is None or self._session_low is None:
return
high = self._session_high
low = self._session_low
if high <= low:
return
buffer = self._get_price_buffer()
if self.Position > 0:
if self._stop_loss_price is not None and float(candle.LowPrice) <= self._stop_loss_price:
self.SellMarket(self.Position)
self._entry_price = None
self._stop_loss_price = None
return
elif self.Position < 0:
if self._stop_loss_price is not None and float(candle.HighPrice) >= self._stop_loss_price:
self.BuyMarket(abs(self.Position))
self._entry_price = None
self._stop_loss_price = None
return
if float(candle.HighPrice) > high + buffer:
vol = self._trade_volume.Value if self._trade_volume.Value > 0 else float(self.Volume)
self.BuyMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._stop_loss_price = low - buffer
self._session_high = None
self._session_low = None
self._channel_ready = False
return
if float(candle.LowPrice) < low - buffer:
vol = self._trade_volume.Value if self._trade_volume.Value > 0 else float(self.Volume)
self.SellMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._stop_loss_price = high + buffer
self._session_high = None
self._session_low = None
self._channel_ready = False
def _get_price_buffer(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0
if step <= 0 or self._stop_buffer_multiplier.Value <= 0:
return 0.0
return step * self._stop_buffer_multiplier.Value
def OnReseted(self):
super(channel_ea2_strategy, self).OnReseted()
self._session_high = None
self._session_low = None
self._channel_ready = False
self._entry_price = None
self._stop_loss_price = None
def CreateClone(self):
return channel_ea2_strategy()