Pin Bar Magic Strategy
Detects bullish and bearish pin bars within a trend defined by a trio of moving averages. Orders are placed at the candle extremes and cancelled after a few bars if not filled. Position size is calculated from an equity risk percentage and ATR-based stop distance.
The method aims to capture sharp reversals at significant support or resistance. It exits positions when the fast and medium EMAs cross in the opposite direction, signalling trend weakness.
Details
- Entry Criteria:
- Long: Fast EMA > Medium EMA > Slow SMA, bullish pin bar piercing one of the averages.
- Short: Fast EMA < Medium EMA < Slow SMA, bearish pin bar piercing one of the averages.
- Exit Criteria:
- Fast EMA crosses the medium EMA in the opposite direction.
- Indicators:
- Slow SMA (period 50)
- Medium EMA (18) and Fast EMA (6)
- ATR (length 14)
- Stops: Position risk = EquityRisk% of account with stop at ATR * multiplier.
- Default Values:
EquityRisk= 3AtrMultiplier= 0.5SlowSmaLength= 50MediumEmaLength= 18FastEmaLength= 6AtrLength= 14CancelEntryBars= 3
- Filters:
- Price action reversal
- Works on 1h candles by default
- Indicators: EMA, SMA, ATR
- Stops: Yes
- Complexity: High
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.
/// Detects pin bar candlestick patterns at EMA/SMA levels in trending markets.
/// Buys on bullish pin bars piercing moving averages in uptrend.
/// Sells on bearish pin bars piercing moving averages in downtrend.
/// </summary>
public class PinBarMagicStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _slowSmaLength;
private readonly StrategyParam<int> _mediumEmaLength;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _cooldownBars;
private SimpleMovingAverage _slowSma;
private ExponentialMovingAverage _mediumEma;
private ExponentialMovingAverage _fastEma;
private int _cooldownRemaining;
public PinBarMagicStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_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");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int SlowSmaLength
{
get => _slowSmaLength.Value;
set => _slowSmaLength.Value = value;
}
public int MediumEmaLength
{
get => _mediumEmaLength.Value;
set => _mediumEmaLength.Value = value;
}
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_slowSma = null;
_mediumEma = null;
_fastEma = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_slowSma = new SimpleMovingAverage { Length = SlowSmaLength };
_mediumEma = new ExponentialMovingAverage { Length = MediumEmaLength };
_fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_slowSma, _mediumEma, _fastEma, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _slowSma);
DrawIndicator(area, _mediumEma);
DrawIndicator(area, _fastEma);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal slowSma, decimal mediumEma, decimal fastEma)
{
if (candle.State != CandleStates.Finished)
return;
if (!_slowSma.IsFormed || !_mediumEma.IsFormed || !_fastEma.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
// Check pin bar patterns
var candleRange = candle.HighPrice - candle.LowPrice;
if (candleRange == 0)
return;
var bullishPinBar = false;
var bearishPinBar = false;
if (candle.ClosePrice > candle.OpenPrice)
{
var lowerWick = candle.OpenPrice - candle.LowPrice;
bullishPinBar = lowerWick > 0.60m * candleRange;
var upperWick = candle.HighPrice - candle.ClosePrice;
bearishPinBar = upperWick > 0.60m * candleRange;
}
else
{
var lowerWick = candle.ClosePrice - candle.LowPrice;
bullishPinBar = lowerWick > 0.60m * candleRange;
var upperWick = candle.HighPrice - candle.OpenPrice;
bearishPinBar = upperWick > 0.60m * candleRange;
}
// Trend conditions - EMA fan
var fanUpTrend = fastEma > mediumEma && mediumEma > slowSma;
var fanDnTrend = fastEma < mediumEma && mediumEma < slowSma;
// Piercing conditions - candle wick pierces through an MA level
var bullPierce = (candle.LowPrice < fastEma && candle.ClosePrice > fastEma) ||
(candle.LowPrice < mediumEma && candle.ClosePrice > mediumEma) ||
(candle.LowPrice < slowSma && candle.ClosePrice > slowSma);
var bearPierce = (candle.HighPrice > fastEma && candle.ClosePrice < fastEma) ||
(candle.HighPrice > mediumEma && candle.ClosePrice < mediumEma) ||
(candle.HighPrice > slowSma && candle.ClosePrice < slowSma);
// Buy: uptrend + bullish pin bar + pierce
if (fanUpTrend && bullishPinBar && bullPierce && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: downtrend + bearish pin bar + pierce
else if (fanDnTrend && bearishPinBar && bearPierce && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: trend reversal (fast crosses below medium)
else if (Position > 0 && fastEma < mediumEma)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: trend reversal (fast crosses above medium)
else if (Position < 0 && fastEma > mediumEma)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class pin_bar_magic_strategy(Strategy):
"""Pin Bar Magic Strategy."""
def __init__(self):
super(pin_bar_magic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._slow_sma_length = self.Param("SlowSmaLength", 50) \
.SetDisplay("Slow SMA Period", "Slow SMA period", "Indicators")
self._medium_ema_length = self.Param("MediumEmaLength", 18) \
.SetDisplay("Medium EMA Period", "Medium EMA period", "Indicators")
self._fast_ema_length = self.Param("FastEmaLength", 6) \
.SetDisplay("Fast EMA Period", "Fast EMA period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._slow_sma = None
self._medium_ema = None
self._fast_ema = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(pin_bar_magic_strategy, self).OnReseted()
self._slow_sma = None
self._medium_ema = None
self._fast_ema = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(pin_bar_magic_strategy, self).OnStarted2(time)
self._slow_sma = SimpleMovingAverage()
self._slow_sma.Length = int(self._slow_sma_length.Value)
self._medium_ema = ExponentialMovingAverage()
self._medium_ema.Length = int(self._medium_ema_length.Value)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = int(self._fast_ema_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._slow_sma, self._medium_ema, self._fast_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._slow_sma)
self.DrawIndicator(area, self._medium_ema)
self.DrawIndicator(area, self._fast_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, slow_sma, medium_ema, fast_ema):
if candle.State != CandleStates.Finished:
return
if not self._slow_sma.IsFormed or not self._medium_ema.IsFormed or not self._fast_ema.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
candle_range = float(candle.HighPrice - candle.LowPrice)
if candle_range == 0:
return
bullish_pin_bar = False
bearish_pin_bar = False
if float(candle.ClosePrice) > float(candle.OpenPrice):
lower_wick = float(candle.OpenPrice - candle.LowPrice)
bullish_pin_bar = lower_wick > 0.60 * candle_range
upper_wick = float(candle.HighPrice - candle.ClosePrice)
bearish_pin_bar = upper_wick > 0.60 * candle_range
else:
lower_wick = float(candle.ClosePrice - candle.LowPrice)
bullish_pin_bar = lower_wick > 0.60 * candle_range
upper_wick = float(candle.HighPrice - candle.OpenPrice)
bearish_pin_bar = upper_wick > 0.60 * candle_range
slow_sma_val = float(slow_sma)
medium_ema_val = float(medium_ema)
fast_ema_val = float(fast_ema)
fan_up_trend = fast_ema_val > medium_ema_val and medium_ema_val > slow_sma_val
fan_dn_trend = fast_ema_val < medium_ema_val and medium_ema_val < slow_sma_val
close = float(candle.ClosePrice)
low = float(candle.LowPrice)
high = float(candle.HighPrice)
bull_pierce = (low < fast_ema_val and close > fast_ema_val) or \
(low < medium_ema_val and close > medium_ema_val) or \
(low < slow_sma_val and close > slow_sma_val)
bear_pierce = (high > fast_ema_val and close < fast_ema_val) or \
(high > medium_ema_val and close < medium_ema_val) or \
(high > slow_sma_val and close < slow_sma_val)
cooldown = int(self._cooldown_bars.Value)
if fan_up_trend and bullish_pin_bar and bull_pierce and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif fan_dn_trend and bearish_pin_bar and bear_pierce and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and fast_ema_val < medium_ema_val:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and fast_ema_val > medium_ema_val:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
def CreateClone(self):
return pin_bar_magic_strategy()