Pinbar-Umkehr-Strategie
Pinbars heben plötzliche Preisablehnungen hervor und können kurzfristige Wendepunkte signalisieren. Diese Strategie misst die Länge des Kerzendochts relativ zu seinem Körper und sucht nach langen Schatten, die aus der jüngsten Preisbewegung herausragen. Ein gleitender Durchschnittsfilter hilft, in Richtung des zugrunde liegenden Trends zu handeln.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 82%. Sie funktioniert am besten auf dem Aktienmarkt.
Bei jeder Kerzenaktualisierung berechnet das System obere und untere Schatten und vergleicht sie mit der Körpergröße. Ein bullischer Pinbar mit langem unterem Docht kann einen Long-Einstieg auslösen, wenn der Preis über dem gleitenden Durchschnitt liegt. Ebenso kann ein bärischer Pinbar mit einem ausgedehnten oberen Docht eine Short-Position in einem Abwärtstrend einleiten. Stops werden auf einen festen Prozentsatz vom Einstieg gesetzt.
Der Trade wird geschlossen, wenn ein entgegengesetzter Pinbar gegen die offene Position erscheint oder der schützende Stop erreicht wird. Die Kombination der Pinbar-Logik mit einem Trendfilter verbessert die Zuverlässigkeit durch Vermeidung kontrend-Setups.
Details
- Einstiegskriterien: Pinbar mit langem Docht und kleinem entgegengesetzten Schatten, durch Trend bestätigt.
- Long/Short: Beide.
- Ausstiegskriterien: Entgegengesetzter Pinbar oder Stop-Loss.
- Stops: Ja, prozentbasiert.
- Standardwerte:
TailToBodyRatio = 2
OppositeTailRatio = 0.5
MAPeriod = 20
CandleType = 15 minute
StopLossPercent = 1
- Filter:
- Kategorie: Muster
- Richtung: Beide
- Indikatoren: Candlestick, MA
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Intraday
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
using System;
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>
/// Enters in the SMA-aligned direction of a formed pinbar and exits on the opposite pinbar or stop.
/// </summary>
public class PinbarReversalStrategy : Strategy
{
private readonly StrategyParam<decimal> _tailToBodyRatio;
private readonly StrategyParam<decimal> _oppositeTailRatio;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private Order _pendingOrder;
public decimal TailToBodyRatio { get => _tailToBodyRatio.Value; set => _tailToBodyRatio.Value = value; }
public decimal OppositeTailRatio { get => _oppositeTailRatio.Value; set => _oppositeTailRatio.Value = value; }
public int MAPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public PinbarReversalStrategy()
{
_tailToBodyRatio = Param(nameof(TailToBodyRatio), 2m).SetRange(1m, 10m)
.SetDisplay("Tail/Body Ratio", "Minimum dominant shadow relative to the body", "Pattern");
_oppositeTailRatio = Param(nameof(OppositeTailRatio), 0.5m).SetRange(0m, 2m)
.SetDisplay("Opposite Tail Ratio", "Maximum opposite shadow relative to the body", "Pattern");
_maPeriod = Param(nameof(MAPeriod), 20).SetGreaterThanZero()
.SetDisplay("MA Period", "Close SMA trend filter", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Pinbar and MA timeframe", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
OrderRegistering += order => _pendingOrder = order;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pendingOrder = null;
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var sma = new SimpleMovingAverage { Length = MAPeriod };
var candles = SubscribeCandles(CandleType);
candles.Bind(sma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candles);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native actual-fill protection evaluates executable quotes between signal candles.
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished || !IsFormedAndOnlineAndAllowTrading() ||
_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
if (body <= 0m)
return;
var lower = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;
var upper = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
var bullish = lower >= body * TailToBodyRatio && upper <= body * OppositeTailRatio;
var bearish = upper >= body * TailToBodyRatio && lower <= body * OppositeTailRatio;
if (Position > 0m && bearish)
SellMarket(Position);
else if (Position < 0m && bullish)
BuyMarket(Math.Abs(Position));
else if (Position == 0m && bullish && candle.ClosePrice > smaValue)
BuyMarket(Volume);
else if (Position == 0m && bearish && candle.ClosePrice < smaValue)
SellMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class pinbar_reversal_strategy(Strategy):
"""
Enters in the SMA-aligned direction of a pinbar and exits on the opposite pinbar or stop.
"""
def __init__(self):
super(pinbar_reversal_strategy, self).__init__()
self._tail_to_body_ratio = self.Param("TailToBodyRatio", 2.0).SetRange(1.0, 10.0).SetDisplay("Tail/Body Ratio", "Minimum dominant shadow relative to the body", "Pattern")
self._opposite_tail_ratio = self.Param("OppositeTailRatio", 0.5).SetRange(0.0, 2.0).SetDisplay("Opposite Tail Ratio", "Maximum opposite shadow relative to the body", "Pattern")
self._ma_period = self.Param("MAPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Close SMA trend filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Pinbar and MA timeframe", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative().SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._pending_order = None
self.OrderRegistering += self._track_pending
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
def OnReseted(self):
super(pinbar_reversal_strategy, self).OnReseted()
self._pending_order = None
def OnStarted2(self, time):
super(pinbar_reversal_strategy, self).OnStarted2(time)
self._pending_order = None
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
candles = self.SubscribeCandles(self.candle_type)
candles.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native actual-fill protection evaluates executable quotes between signal candles.
pass
def _process_candle(self, candle, sma):
if candle.State != CandleStates.Finished or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
body = Math.Abs(candle.ClosePrice - candle.OpenPrice)
if body <= Decimal(0):
return
lower = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice
upper = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice)
tail_ratio = Decimal(self._tail_to_body_ratio.Value)
opposite_ratio = Decimal(self._opposite_tail_ratio.Value)
bullish = lower >= body * tail_ratio and upper <= body * opposite_ratio
bearish = upper >= body * tail_ratio and lower <= body * opposite_ratio
if self.Position > 0 and bearish:
self.SellMarket(self.Position)
elif self.Position < 0 and bullish:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and bullish and candle.ClosePrice > sma:
self.BuyMarket(self.Volume)
elif self.Position == 0 and bearish and candle.ClosePrice < sma:
self.SellMarket(self.Volume)
def CreateClone(self):
return pinbar_reversal_strategy()