Стратегия разворота по пин-бару
Пин-бары отображают резкое отвержение цены и могут указывать на краткосрочный разворот. Стратегия измеряет длину хвоста свечи относительно её тела, ища длинные тени, выделяющиеся на фоне недавних ценовых движений. Фильтр скользящей средней помогает торговать по направлению основного тренда.
Тестирование показывает среднегодичную доходность около 82%. Стратегию лучше запускать на фондовом рынке.
При каждом обновлении свечи система вычисляет верхние и нижние тени и сравнивает их с размером тела. Бычий пин-бар с длинной нижней тенью может дать сигнал на покупку, если цена выше скользящей средней. Аналогично медвежий пин-бар с длинной верхней тенью может открыть короткую позицию в нисходящем тренде. Стопы ставятся на фиксированный процент от входа.
Сделка закрывается при появлении противоположного пин-бара против открытой позиции либо при достижении защитного стопа. Совмещение логики пин-бара с трендовым фильтром повышает надёжность, избегая контртрендовых ситуаций.
Детали
- Условия входа: Пин-бар с длинным хвостом и маленькой противоположной тенью, подтверждённый трендом.
- Направление: Лонг и шорт.
- Условия выхода: Противоположный пин-бар или стоп‑лосс.
- Стопы: Да, процентные.
- Значения по умолчанию:
TailToBodyRatio = 2
OppositeTailRatio = 0.5
MAPeriod = 20
CandleType = 15 минут
StopLossPercent = 1
- Фильтры:
- Категория: Паттерн
- Направление: Оба
- Индикаторы: Свечной анализ, MA
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()