Pin Bar Magic 策略
在由三条移动平均线定义的趋势中识别多头和空头 Pin Bar。挂单放置于K线极值处,若数个周期内未成交则取消。仓位大小基于账户风险百分比和 ATR 计算的止损距离。
目标是捕捉关键支撑阻力位的快速反转。当快 EMA 与中 EMA 反向交叉时退出,表明趋势减弱。
细节
- 入场条件:
- 多头:快 EMA > 中 EMA > 慢 SMA,且出现向上刺穿均线的看涨 Pin Bar。
- 空头:快 EMA < 中 EMA < 慢 SMA,且出现向下刺穿均线的看跌 Pin Bar。
- 出场条件:
- 快 EMA 与中 EMA 出现反向交叉。
- 指标:
- 慢 SMA(周期50)
- 中 EMA(18)和快 EMA(6)
- ATR(周期14)
- 止损:仓位风险 = EquityRisk% * 账户净值,止损距离 = ATR * Multiplier。
- 默认值:
EquityRisk= 3AtrMultiplier= 0.5SlowSmaLength= 50MediumEmaLength= 18FastEmaLength= 6AtrLength= 14CancelEntryBars= 3
- 过滤:
- 价格行为反转
- 默认周期为1小时K线
- 指标:EMA、SMA、ATR
- 止损:是
- 复杂度:较高
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()