Pin Bar Magic-Strategie
Erkennt bullische und bärische Pin Bars innerhalb eines Trends, der durch ein Trio von gleitenden Durchschnitten definiert wird. Aufträge werden an den Kerzenextremen platziert und nach einigen Kerzen storniert, wenn sie nicht ausgeführt werden. Die Positionsgröße wird aus einem prozentualen Eigenkapitalrisiko und dem ATR-basierten Stop-Abstand berechnet.
Die Methode zielt darauf ab, scharfe Umkehrungen an bedeutenden Unterstützungs- oder Widerstandsniveaus zu erfassen. Positionen werden beendet, wenn die schnelle und mittlere EMA in die entgegengesetzte Richtung kreuzen, was eine Trendschwäche signalisiert.
Details
- Einstiegskriterien:
- Long: Schnelle EMA > Mittlere EMA > Langsame SMA, bullischer Pin Bar, der eine der Durchschnitte durchsticht.
- Short: Schnelle EMA < Mittlere EMA < Langsame SMA, bärischer Pin Bar, der eine der Durchschnitte durchsticht.
- Ausstiegskriterien:
- Schnelle EMA kreuzt die mittlere EMA in die entgegengesetzte Richtung.
- Indikatoren:
- Langsame SMA (Periode 50)
- Mittlere EMA (18) und schnelle EMA (6)
- ATR (Länge 14)
- Stops: Positionsrisiko = EquityRisk% des Kontos mit Stop bei ATR * Multiplikator.
- Standardwerte:
EquityRisk= 3AtrMultiplier= 0.5SlowSmaLength= 50MediumEmaLength= 18FastEmaLength= 6AtrLength= 14CancelEntryBars= 3
- Filter:
- Kursaktions-Umkehr
- Funktioniert standardmäßig auf 1-Stunden-Kerzen
- Indikatoren: EMA, SMA, ATR
- Stops: Ja
- Komplexität: Hoch
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Pin Bar Magic Strategy.
/// A bullish pin bar whose tail pierces one of the averages while Fast EMA > Medium EMA > Slow SMA arms a buy stop at the
/// pin bar high; a bearish pin bar in the opposite fan arms a sell stop at its low. An entry not triggered within
/// CancelEntryBars candles is cancelled. The stop sits ATR * AtrMultiplier from the entry and the size risks EquityRisk percent
/// of the account on that distance. Positions close when the fast EMA crosses the medium EMA against them.
/// </summary>
public class PinBarMagicStrategy : Strategy
{
private const decimal PinBarWickRatio = 0.66m;
private readonly StrategyParam<decimal> _equityRisk;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _slowSmaLength;
private readonly StrategyParam<int> _mediumEmaLength;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<int> _cancelEntryBars;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevFast;
private decimal? _prevMedium;
private decimal? _pendingLevel;
private decimal _pendingStopDistance;
private decimal _pendingVolume;
private int _pendingSide;
private int _pendingBarsLeft;
private decimal? _stopPrice;
/// <summary>
/// Percent of the account risked per trade.
/// </summary>
public decimal EquityRisk
{
get => _equityRisk.Value;
set => _equityRisk.Value = value;
}
/// <summary>
/// ATR multiplier of the stop distance.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Slow SMA period.
/// </summary>
public int SlowSmaLength
{
get => _slowSmaLength.Value;
set => _slowSmaLength.Value = value;
}
/// <summary>
/// Medium EMA period.
/// </summary>
public int MediumEmaLength
{
get => _mediumEmaLength.Value;
set => _mediumEmaLength.Value = value;
}
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// Candles after which an untriggered entry is cancelled.
/// </summary>
public int CancelEntryBars
{
get => _cancelEntryBars.Value;
set => _cancelEntryBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public PinBarMagicStrategy()
{
_equityRisk = Param(nameof(EquityRisk), 3m)
.SetGreaterThanZero()
.SetDisplay("Equity Risk %", "Percent of the account risked per trade", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 0.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR multiplier of the stop distance", "Risk");
_slowSmaLength = Param(nameof(SlowSmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("Slow SMA Period", "Slow SMA period", "Indicators");
_mediumEmaLength = Param(nameof(MediumEmaLength), 18)
.SetGreaterThanZero()
.SetDisplay("Medium EMA Period", "Medium EMA period", "Indicators");
_fastEmaLength = Param(nameof(FastEmaLength), 6)
.SetGreaterThanZero()
.SetDisplay("Fast EMA Period", "Fast EMA period", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Indicators");
_cancelEntryBars = Param(nameof(CancelEntryBars), 3)
.SetGreaterThanZero()
.SetDisplay("Cancel Entry Bars", "Candles after which an untriggered entry is cancelled", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_prevFast = null;
_prevMedium = null;
_pendingLevel = null;
_pendingStopDistance = 0m;
_pendingVolume = 0m;
_pendingSide = 0;
_pendingBarsLeft = 0;
_stopPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var slowSma = new SimpleMovingAverage { Length = SlowSmaLength };
var mediumEma = new ExponentialMovingAverage { Length = MediumEmaLength };
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(slowSma, mediumEma, fastEma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, slowSma);
DrawIndicator(area, mediumEma);
DrawIndicator(area, fastEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue slowValue, IIndicatorValue mediumValue, IIndicatorValue fastValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!slowValue.IsFormed || !mediumValue.IsFormed || !fastValue.IsFormed || !atrValue.IsFormed)
return;
var slow = slowValue.GetValue<decimal>();
var medium = mediumValue.GetValue<decimal>();
var fast = fastValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var prevFast = _prevFast;
var prevMedium = _prevMedium;
_prevFast = fast;
_prevMedium = medium;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var crossDown = prevFast is decimal pf1 && prevMedium is decimal pm1 && pf1 >= pm1 && fast < medium;
var crossUp = prevFast is decimal pf2 && prevMedium is decimal pm2 && pf2 <= pm2 && fast > medium;
// Exits of the open position: the ATR stop or the fast/medium cross against it.
var position = Position;
if (position > 0 && ((_stopPrice is decimal longStop && candle.LowPrice <= longStop) || crossDown))
{
SellMarket(position);
_stopPrice = null;
position = 0m;
}
else if (position < 0 && ((_stopPrice is decimal shortStop && candle.HighPrice >= shortStop) || crossUp))
{
BuyMarket(-position);
_stopPrice = null;
position = 0m;
}
// A pending stop entry triggers when the candle trades through its level.
if (_pendingLevel is decimal level)
{
if (_pendingSide > 0 && candle.HighPrice >= level)
{
BuyMarket(_pendingVolume + Math.Max(0m, -position));
position = _pendingVolume;
_stopPrice = level - _pendingStopDistance;
ClearPending();
}
else if (_pendingSide < 0 && candle.LowPrice <= level)
{
SellMarket(_pendingVolume + Math.Max(0m, position));
position = -_pendingVolume;
_stopPrice = level + _pendingStopDistance;
ClearPending();
}
else if (--_pendingBarsLeft <= 0)
{
ClearPending();
}
}
var range = candle.HighPrice - candle.LowPrice;
if (range <= 0m)
return;
var bodyLow = Math.Min(candle.OpenPrice, candle.ClosePrice);
var bodyHigh = Math.Max(candle.OpenPrice, candle.ClosePrice);
var bullishPinBar = bodyLow - candle.LowPrice > PinBarWickRatio * range;
var bearishPinBar = candle.HighPrice - bodyHigh > PinBarWickRatio * range;
var fanUp = fast > medium && medium > slow;
var fanDown = fast < medium && medium < slow;
var bullPierce = Pierces(candle.LowPrice, candle, fast, true) || Pierces(candle.LowPrice, candle, medium, true) || Pierces(candle.LowPrice, candle, slow, true);
var bearPierce = Pierces(candle.HighPrice, candle, fast, false) || Pierces(candle.HighPrice, candle, medium, false) || Pierces(candle.HighPrice, candle, slow, false);
var stopDistance = atr * AtrMultiplier;
if (stopDistance <= 0m)
return;
if (fanUp && bullishPinBar && bullPierce && position <= 0)
ArmEntry(1, candle.HighPrice, stopDistance);
else if (fanDown && bearishPinBar && bearPierce && position >= 0)
ArmEntry(-1, candle.LowPrice, stopDistance);
}
private static bool Pierces(decimal extreme, ICandleMessage candle, decimal average, bool bullish)
=> bullish
? extreme < average && candle.OpenPrice > average && candle.ClosePrice > average
: extreme > average && candle.OpenPrice < average && candle.ClosePrice < average;
private void ArmEntry(int side, decimal level, decimal stopDistance)
{
var volume = CalculateVolume(stopDistance);
if (volume <= 0m)
return;
_pendingSide = side;
_pendingLevel = level;
_pendingStopDistance = stopDistance;
_pendingVolume = volume;
_pendingBarsLeft = CancelEntryBars;
}
private void ClearPending()
{
_pendingLevel = null;
_pendingSide = 0;
_pendingBarsLeft = 0;
}
private decimal CalculateVolume(decimal stopDistance)
{
var equity = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
if (equity <= 0m)
return Volume;
var volume = equity * EquityRisk / 100m / stopDistance;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m)
volume = Math.Floor(volume / step) * step;
if (Security?.MaxVolume is decimal max && max > 0m && volume > max)
volume = max;
return volume;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
PIN_BAR_WICK_RATIO = 0.66
class pin_bar_magic_strategy(Strategy):
"""
Pin Bar Magic Strategy.
A bullish pin bar whose tail pierces one of the averages while Fast EMA > Medium EMA > Slow SMA arms a buy stop at the
pin bar high; a bearish pin bar in the opposite fan arms a sell stop at its low. An entry not triggered within
CancelEntryBars candles is cancelled. The stop sits ATR * AtrMultiplier from the entry and the size risks EquityRisk percent
of the account on that distance. Positions close when the fast EMA crosses the medium EMA against them.
"""
def __init__(self):
super(pin_bar_magic_strategy, self).__init__()
self._equity_risk = self.Param("EquityRisk", 3.0).SetGreaterThanZero().SetDisplay("Equity Risk %", "Percent of the account risked per trade", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 0.5).SetGreaterThanZero().SetDisplay("ATR Multiplier", "ATR multiplier of the stop distance", "Risk")
self._slow_sma_length = self.Param("SlowSmaLength", 50).SetGreaterThanZero().SetDisplay("Slow SMA Period", "Slow SMA period", "Indicators")
self._medium_ema_length = self.Param("MediumEmaLength", 18).SetGreaterThanZero().SetDisplay("Medium EMA Period", "Medium EMA period", "Indicators")
self._fast_ema_length = self.Param("FastEmaLength", 6).SetGreaterThanZero().SetDisplay("Fast EMA Period", "Fast EMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Indicators")
self._cancel_entry_bars = self.Param("CancelEntryBars", 3).SetGreaterThanZero().SetDisplay("Cancel Entry Bars", "Candles after which an untriggered entry is cancelled", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_fast = None
self._prev_medium = None
self._pending_level = None
self._pending_stop_distance = 0.0
self._pending_volume = 0.0
self._pending_side = 0
self._pending_bars_left = 0
self._stop_price = None
def OnReseted(self):
super(pin_bar_magic_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(pin_bar_magic_strategy, self).OnStarted2(time)
self._reset_state()
slow_sma = SimpleMovingAverage()
slow_sma.Length = self._slow_sma_length.Value
medium_ema = ExponentialMovingAverage()
medium_ema.Length = self._medium_ema_length.Value
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(slow_sma, medium_ema, fast_ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, slow_sma)
self.DrawIndicator(area, medium_ema)
self.DrawIndicator(area, fast_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, slow_value, medium_value, fast_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not slow_value.IsFormed or not medium_value.IsFormed or not fast_value.IsFormed or not atr_value.IsFormed:
return
slow = float(slow_value.GetValue[Decimal](None))
medium = float(medium_value.GetValue[Decimal](None))
fast = float(fast_value.GetValue[Decimal](None))
atr = float(atr_value.GetValue[Decimal](None))
prev_fast = self._prev_fast
prev_medium = self._prev_medium
self._prev_fast = fast
self._prev_medium = medium
if not self.IsFormedAndOnlineAndAllowTrading():
return
cross_down = prev_fast is not None and prev_medium is not None and prev_fast >= prev_medium and fast < medium
cross_up = prev_fast is not None and prev_medium is not None and prev_fast <= prev_medium and fast > medium
open_price = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
position = float(self.Position)
# Exits of the open position: the ATR stop or the fast/medium cross against it.
if position > 0 and ((self._stop_price is not None and low <= self._stop_price) or cross_down):
self.SellMarket(self.Position)
self._stop_price = None
position = 0.0
elif position < 0 and ((self._stop_price is not None and high >= self._stop_price) or cross_up):
self.BuyMarket(-self.Position)
self._stop_price = None
position = 0.0
# A pending stop entry triggers when the candle trades through its level.
if self._pending_level is not None:
level = self._pending_level
if self._pending_side > 0 and high >= level:
self.BuyMarket(Decimal(self._pending_volume + max(0.0, -position)))
position = self._pending_volume
self._stop_price = level - self._pending_stop_distance
self._clear_pending()
elif self._pending_side < 0 and low <= level:
self.SellMarket(Decimal(self._pending_volume + max(0.0, position)))
position = -self._pending_volume
self._stop_price = level + self._pending_stop_distance
self._clear_pending()
else:
self._pending_bars_left -= 1
if self._pending_bars_left <= 0:
self._clear_pending()
rng = high - low
if rng <= 0:
return
body_low = min(open_price, close)
body_high = max(open_price, close)
bullish_pin_bar = body_low - low > PIN_BAR_WICK_RATIO * rng
bearish_pin_bar = high - body_high > PIN_BAR_WICK_RATIO * rng
fan_up = fast > medium and medium > slow
fan_down = fast < medium and medium < slow
bull_pierce = any(low < ma and open_price > ma and close > ma for ma in (fast, medium, slow))
bear_pierce = any(high > ma and open_price < ma and close < ma for ma in (fast, medium, slow))
stop_distance = atr * float(self._atr_multiplier.Value)
if stop_distance <= 0:
return
if fan_up and bullish_pin_bar and bull_pierce and position <= 0:
self._arm_entry(1, high, stop_distance)
elif fan_down and bearish_pin_bar and bear_pierce and position >= 0:
self._arm_entry(-1, low, stop_distance)
def _arm_entry(self, side, level, stop_distance):
volume = self._calculate_volume(stop_distance)
if volume <= 0:
return
self._pending_side = side
self._pending_level = level
self._pending_stop_distance = stop_distance
self._pending_volume = volume
self._pending_bars_left = self._cancel_entry_bars.Value
def _clear_pending(self):
self._pending_level = None
self._pending_side = 0
self._pending_bars_left = 0
def _calculate_volume(self, stop_distance):
equity = 0.0
if self.Portfolio is not None:
if self.Portfolio.CurrentValue is not None:
equity = float(self.Portfolio.CurrentValue)
elif self.Portfolio.BeginValue is not None:
equity = float(self.Portfolio.BeginValue)
if equity <= 0:
return float(self.Volume)
volume = equity * float(self._equity_risk.Value) / 100.0 / stop_distance
security = self.Security
step = float(security.VolumeStep) if security is not None and security.VolumeStep is not None else 0.0
if step > 0:
volume = math.floor(volume / step) * step
# Keep the volume an exact multiple of the step after the float round trip.
volume = round(volume, max(0, -int(math.floor(math.log10(step)))))
if security is not None and security.MaxVolume is not None and float(security.MaxVolume) > 0 and volume > float(security.MaxVolume):
volume = float(security.MaxVolume)
return volume
def CreateClone(self):
return pin_bar_magic_strategy()