Twenty-Pips-Preiskanalstrategie
Überblick
Die Twenty-Pips-Preiskanalstrategie ist eine Umsetzung des ursprünglichen MetaTrader-Expertenberaters 20 Pips, die einen Preiskanal im Donchian-Stil mit kurzfristigen gleitenden Durchschnittsfiltern kombiniert. Der Algorithmus öffnet Trades nur, wenn die aktuelle Kerze entgegengesetzt zur vorherigen öffnet, filtert die Richtung mit gleitenden Durchschnitten, die auf typischen Preisen berechnet werden, und verwaltet Ausstiege über ein festes Zwanzig-Pip-Ziel, das durch einen dynamischen kanalbasierten Trailing Stop unterstützt wird.
Die StockSharp-Version behält den Geist des ursprünglichen Ansatzes bei und passt die Auftragsverwaltung gleichzeitig an die übergeordnete API-Version an. Marktaufträge werden für Ein- und Ausstiege verwendet, Gewinnziele werden intern überwacht und Stop-Levels werden mit Preiskanalbedingungen nachgebildet.
Handelslogik
Indikatorstapel
- Ein einfacher gleitender Durchschnitt einer Periode des typischen Preises (H+L+C)/3 dient als schnelle Basislinie, die den typischen Preis der vorherigen Kerze widerspiegelt.
- Ein konfigurierbarer langsamer einfacher gleitender Durchschnitt (Standard 20), der auf Schlusskursen berechnet wird, spielt die Rolle des
MA_Low-Filters aus dem EA.
- Höchste und niedrigste Indikatoren mit demselben Zeitraum wie der Preiskanal (Standard 20) emulieren die ursprünglichen benutzerdefinierten Indikatorpuffer.
Eintrittsbedingungen
- Long-Setup: Der vorherige schnelle typische Preis liegt über dem vorherigen langsamen gleitenden Durchschnitt und die aktuelle Kerze öffnet sich unter der vorherigen Eröffnung. Nach einem Verlustgeschäft wird das Volumen mit dem Erholungsfaktor multipliziert (Standard 2). Der Einstiegspreis wird erfasst, um Gewinn und Verlust zu verfolgen.
- Short-Setup: Der vorherige schnelle typische Preis liegt unter dem vorherigen langsamen gleitenden Durchschnitt und die aktuelle Kerze öffnet sich über der vorherigen Eröffnung. Die Volumenskalierung folgt der gleichen Erholungslogik wie bei Long-Trades.
Exit-Management
- Bei der Eröffnung der Position wird ein festes Take-Profit-Ziel in Höhe von
TakeProfitPips multipliziert mit der Preisstufe des Instruments festgelegt.
- Ein kanalgesteuerter Trailing Stop ahmt den ursprünglichen
OrderModify-Aufruf nach. Wenn der vorherige Balken den Preiskanal durchbricht (Verschiebung um zwei Balken aus der MT4-Logik), wird der Schutzstopp zum vorherigen Extrem minus/plus dem nachlaufenden Offset in Pips verschoben. Wenn die nächste Kerze über dieses Extrem hinausgeht, wird die Position sofort zum Eröffnungspreis geschlossen.
- Take-Profit-, Trailing-Stop- und Gap-Exits werden alle über Marktaufträge ausgeführt, während der tatsächliche Exit-Preis verfolgt wird, um die Gewinn-/Verlust-Flagge für die Martingal-Skalierung zu aktualisieren.
Martingale Wiederherstellung
- Nach jeder geschlossenen Verlustposition wird die nächste Einstiegsgröße mit
RecoveryMultiplier multipliziert. Bei gewinnbringenden Geschäften wird die Flagge zurückgesetzt und auf das Basisvolumen zurückgesetzt.
Parameter
| Name |
Beschreibung |
Standard |
CandleType |
Primärer Zeitrahmen, der für Berechnungen verwendet wird. |
1 Stunde Kerzen |
ChannelPeriod |
Lookback-Zeitraum für den Kanal im Donchian-Stil. |
20 |
SlowMaPeriod |
Länge des langsam gleitenden Durchschnittsfilters. |
20 |
TakeProfitPips |
Abstand in Pips für das festgelegte Gewinnziel. |
20 |
TrailingOffsetPips |
Offset, der beim Anziehen des Anschlags auf das vorherige Extrem verwendet wird. |
10 |
RecoveryMultiplier |
Nach einem Verlust angewendeter Volumenmultiplikator. |
2 |
Volume |
Basishandelsvolumen vor Erholungsskalierung. |
0,1 |
Nutzungshinweise
- Die Strategie geht davon aus, dass
Security.PriceStep den Pip-Wert des gehandelten Instruments widerspiegelt. Passen Sie TakeProfitPips und TrailingOffsetPips an, wenn das Symbol eine andere Pip-Definition verwendet.
- Da StockSharp Marktaufträge für Ausstiege verwendet, können Backtests im Vergleich zu den ursprünglichen MT4-Stop- und Limit-Aufträgen einen Slippage aufweisen. Die Logik reproduziert immer noch die gleichen Preisschwellen.
- Die Kanalwerte werden verschoben, um die
iCustom(..., shift=2)-Aufrufe zu emulieren; Beachten Sie dies, wenn Sie das Nachlaufverhalten ändern.
- Der Wiederherstellungsmultiplikator kann auf 1 gesetzt werden, um die Martingal-Skalierung zu deaktivieren.
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>
/// Converted "20 pips" price channel strategy.
/// Uses Donchian channel breakouts with MA filter, trailing stop, and recovery multiplier.
/// </summary>
public class TwentyPipsPriceChannelStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal? _prevChannelUpper;
private decimal? _prevChannelLower;
/// <summary>
/// Candle type to process.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Donchian channel lookback period.
/// </summary>
public int ChannelPeriod
{
get => _channelPeriod.Value;
set => _channelPeriod.Value = value;
}
/// <summary>
/// Slow moving average length.
/// </summary>
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
/// <summary>
/// Stop loss distance in absolute price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take profit distance in absolute price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="TwentyPipsPriceChannelStrategy"/>.
/// </summary>
public TwentyPipsPriceChannelStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary candle type", "General");
_channelPeriod = Param(nameof(ChannelPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Channel Period", "Donchian channel lookback", "Parameters");
_slowMaPeriod = Param(nameof(SlowMaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Slow MA Period", "Slow moving average length", "Parameters");
_stopLoss = Param(nameof(StopLoss), 500m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss distance", "Risk");
_takeProfit = Param(nameof(TakeProfit), 500m)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit distance", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return new[] { (Security, CandleType) };
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_prevChannelUpper = null;
_prevChannelLower = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
var slowMa = new SMA { Length = SlowMaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(slowMa, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, slowMa);
DrawOwnTrades(area);
}
// Use StartProtection for SL/TP
var tp = TakeProfit > 0 ? new Unit(TakeProfit, UnitTypes.Absolute) : null;
var sl = StopLoss > 0 ? new Unit(StopLoss, UnitTypes.Absolute) : null;
StartProtection(tp, sl);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, decimal slowMaValue)
{
if (candle.State != CandleStates.Finished)
return;
// Track highs and lows for manual Donchian channel
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
while (_highs.Count > ChannelPeriod)
_highs.RemoveAt(0);
while (_lows.Count > ChannelPeriod)
_lows.RemoveAt(0);
if (_highs.Count < ChannelPeriod)
{
_prevChannelUpper = null;
_prevChannelLower = null;
return;
}
var channelUpper = _highs.Max();
var channelLower = _lows.Min();
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevChannelUpper = channelUpper;
_prevChannelLower = channelLower;
return;
}
// Channel breakout with MA filter
if (_prevChannelUpper.HasValue && _prevChannelLower.HasValue)
{
// Breakout above the previous channel high -> buy signal
if (candle.ClosePrice > _prevChannelUpper.Value && candle.ClosePrice > slowMaValue && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
}
// Breakout below the previous channel low -> sell signal
else if (candle.ClosePrice < _prevChannelLower.Value && candle.ClosePrice < slowMaValue && Position >= 0)
{
if (Position > 0)
SellMarket(Position);
SellMarket(Volume);
}
}
_prevChannelUpper = channelUpper;
_prevChannelLower = channelLower;
}
}
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, UnitTypes, Unit
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class twenty_pips_price_channel_strategy(Strategy):
"""Donchian channel breakout with SMA filter and StartProtection SL/TP."""
def __init__(self):
super(twenty_pips_price_channel_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 20).SetGreaterThanZero().SetDisplay("Channel Period", "Donchian channel lookback", "Parameters")
self._slow_ma_period = self.Param("SlowMaPeriod", 20).SetGreaterThanZero().SetDisplay("Slow MA Period", "Slow MA length", "Parameters")
self._sl = self.Param("StopLoss", 500).SetNotNegative().SetDisplay("Stop Loss", "SL distance", "Risk")
self._tp = self.Param("TakeProfit", 500).SetNotNegative().SetDisplay("Take Profit", "TP distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(twenty_pips_price_channel_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._prev_upper = None
self._prev_lower = None
def OnStarted2(self, time):
super(twenty_pips_price_channel_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._prev_upper = None
self._prev_lower = None
slow_ma = SimpleMovingAverage()
slow_ma.Length = self._slow_ma_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(slow_ma, self.OnProcess).Start()
sl_val = float(self._sl.Value)
tp_val = float(self._tp.Value)
tp = Unit(tp_val, UnitTypes.Absolute) if tp_val > 0 else None
sl = Unit(sl_val, UnitTypes.Absolute) if sl_val > 0 else None
self.StartProtection(tp, sl)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
def OnProcess(self, candle, slow_ma_val):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
ma_val = float(slow_ma_val)
period = self._channel_period.Value
self._highs.append(high)
self._lows.append(low)
while len(self._highs) > period:
self._highs.pop(0)
while len(self._lows) > period:
self._lows.pop(0)
if len(self._highs) < period:
self._prev_upper = None
self._prev_lower = None
return
ch_upper = max(self._highs)
ch_lower = min(self._lows)
if self._prev_upper is not None and self._prev_lower is not None and self.IsFormedAndOnlineAndAllowTrading():
if close > self._prev_upper and close > ma_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self.BuyMarket(self.Volume)
elif close < self._prev_lower and close < ma_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
self.SellMarket(self.Volume)
self._prev_upper = ch_upper
self._prev_lower = ch_lower
def CreateClone(self):
return twenty_pips_price_channel_strategy()