Shooting-Star-Muster (Shooting Star Pattern)
Die Shooting-Star-Kerze erscheint oft nach einem Anstieg und warnt vor einer Umkehr. Diese Strategie sucht nach einem langen oberen Docht im Verhältnis zum Kerzenkörper und kaum einem unteren Docht.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 67 %. Die Strategie eignet sich am besten für den Aktienmarkt.
Wenn eine Bestätigung erforderlich ist, muss die nächste Kerze tiefer schließen, bevor eine Short-Position eingegangen wird. Andernfalls kann der Trade sofort eingegangen werden. Stops werden oberhalb des Musthochs platziert.
Details
- Einstiegskriterien: Shooting Star erkannt und Bestätigung, falls aktiviert.
- Long/Short: Nur Short.
- Ausstiegskriterien: Stop-Loss oder diskretionärer Ausstieg.
- Stops: Ja.
- Standardwerte:
ShadowToBodyRatio= 2.0mCandleType= TimeSpan.FromMinutes(15)StopLossPercent= 1.0mConfirmationRequired= true
- Filter:
- Kategorie: Muster
- Richtung: Nur Short
- Indikatoren: Candlestick
- 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.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Short-only shooting star after three rising closes, optionally confirmed by the next candle.
/// Protects above the pattern high with executable best asks and finished-bar fallback.
/// </summary>
public class ShootingStarStrategy : Strategy
{
private readonly StrategyParam<decimal> _shadowToBodyRatio;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _confirmationRequired;
private readonly Queue<decimal> _priorCloses = new();
private (decimal High, decimal Close)? _candidate;
private decimal? _patternStop;
private Order _entryOrder;
private Order _exitOrder;
public decimal ShadowToBodyRatio { get => _shadowToBodyRatio.Value; set => _shadowToBodyRatio.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public bool ConfirmationRequired { get => _confirmationRequired.Value; set => _confirmationRequired.Value = value; }
public ShootingStarStrategy()
{
_shadowToBodyRatio = Param(nameof(ShadowToBodyRatio), 2m).SetGreaterThanZero()
.SetDisplay("Shadow/body ratio", "Minimum upper-shadow to real-body ratio", "Pattern");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for shooting-star pattern", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetRange(0m, 99m)
.SetDisplay("Stop above high (%)", "Percent buffer above the star high; zero places it at the high.", "Protection");
_confirmationRequired = Param(nameof(ConfirmationRequired), true)
.SetDisplay("Confirmation required", "Wait for the next candle to close below the star close.", "Pattern");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_priorCloses.Clear();
_candidate = null;
_patternStop = null;
_entryOrder = _exitOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
var asks = new Subscription(DataType.Level1, Security);
asks.MarketData.BuildField = Level1Fields.BestAskPrice;
SubscribeLevel1(asks).Bind(ProcessAsk).Start();
var candles = SubscribeCandles(CandleType);
candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candles);
DrawOwnTrades(area);
}
}
private static bool IsPending(Order order)
=> order is not null && order.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessAsk(Level1ChangeMessage message)
{
if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
CheckStop(ask);
}
private void CheckStop(decimal executableAsk)
{
if (Position < 0m && _patternStop is decimal stop && executableAsk >= stop && !IsPending(_exitOrder))
{
_exitOrder = BuyMarket(Math.Abs(Position));
_candidate = null;
}
}
private void EnterShort(decimal patternHigh)
{
_patternStop = patternHigh * (1m + StopLossPercent / 100m);
_entryOrder = SellMarket(Volume);
_candidate = null;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var advance = _priorCloses.Count == 3;
if (advance)
{
var closes = _priorCloses.ToArray();
advance = closes[0] < closes[1] && closes[1] < closes[2];
_priorCloses.Dequeue();
}
_priorCloses.Enqueue(candle.ClosePrice);
if (Position < 0m)
{
CheckStop(candle.HighPrice);
return;
}
if (IsPending(_entryOrder) || IsPending(_exitOrder))
return;
if (_candidate is { } star)
{
_candidate = null;
if (candle.ClosePrice < star.Close && IsFormedAndOnlineAndAllowTrading())
{
EnterShort(star.High);
return;
}
}
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var upper = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
var lower = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;
if (!advance || body <= 0m || upper < body * ShadowToBodyRatio || lower > body * 0.5m ||
!IsFormedAndOnlineAndAllowTrading())
return;
if (ConfirmationRequired)
_candidate = (candle.HighPrice, candle.ClosePrice);
else
EnterShort(candle.HighPrice);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Level1Fields, OrderStates
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class shooting_star_strategy(Strategy):
"""
Short-only shooting star after three rising closes, optionally confirmed next bar.
Protects above the pattern high with executable asks and finished-bar fallback.
"""
def __init__(self):
super(shooting_star_strategy, self).__init__()
self._shadow_to_body_ratio = self.Param("ShadowToBodyRatio", 2.0).SetGreaterThanZero().SetDisplay("Shadow/body ratio", "Minimum upper-shadow to real-body ratio", "Pattern")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Timeframe for shooting-star pattern", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetRange(0.0, 99.0).SetDisplay("Stop above high (%)", "Percent buffer above the star high; zero places it at the high.", "Protection")
self._confirmation_required = self.Param("ConfirmationRequired", True).SetDisplay("Confirmation required", "Wait for the next candle to close below the star close.", "Pattern")
self._clear_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _clear_state(self):
self._prior_closes = []
self._candidate = None
self._pattern_stop = None
self._entry_order = None
self._exit_order = None
def OnReseted(self):
super(shooting_star_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(shooting_star_strategy, self).OnStarted2(time)
self._clear_state()
asks = Subscription(DataType.Level1, self.Security)
asks.MarketData.BuildField = Level1Fields.BestAskPrice
self.SubscribeLevel1(asks).Bind(self._process_ask).Start()
candles = self.SubscribeCandles(self.candle_type)
candles.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawOwnTrades(area)
def _is_pending(self, order):
return order is not None and order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_ask(self, message):
if message.Changes.ContainsKey(Level1Fields.BestAskPrice):
ask = message.Changes[Level1Fields.BestAskPrice]
if ask is not None and ask > Decimal(0):
self._check_stop(ask)
def _check_stop(self, executable_ask):
if self.Position < 0 and self._pattern_stop is not None and executable_ask >= self._pattern_stop and not self._is_pending(self._exit_order):
self._exit_order = self.BuyMarket(Math.Abs(self.Position))
self._candidate = None
def _enter_short(self, pattern_high):
self._pattern_stop = pattern_high * (Decimal(1) + Decimal(self._stop_loss_percent.Value) / Decimal(100))
self._entry_order = self.SellMarket(self.Volume)
self._candidate = None
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
advance = len(self._prior_closes) == 3
if advance:
advance = self._prior_closes[0] < self._prior_closes[1] < self._prior_closes[2]
self._prior_closes.pop(0)
self._prior_closes.append(candle.ClosePrice)
if self.Position < 0:
self._check_stop(candle.HighPrice)
return
if self._is_pending(self._entry_order) or self._is_pending(self._exit_order):
return
if self._candidate is not None:
star_high, star_close = self._candidate
self._candidate = None
if candle.ClosePrice < star_close and self.IsFormedAndOnlineAndAllowTrading():
self._enter_short(star_high)
return
body = Math.Abs(candle.ClosePrice - candle.OpenPrice)
upper = candle.HighPrice - max(candle.OpenPrice, candle.ClosePrice)
lower = min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice
if not advance or body <= Decimal(0) or upper < body * Decimal(self._shadow_to_body_ratio.Value) or lower > body * Decimal(0.5) or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._confirmation_required.Value:
self._candidate = (candle.HighPrice, candle.ClosePrice)
else:
self._enter_short(candle.HighPrice)
def CreateClone(self):
return shooting_star_strategy()