Estrategia de Reversión Pinbar
Los Pinbars destacan rechazos repentinos del precio y pueden señalar puntos de inflexión a corto plazo. Esta estrategia mide la longitud de la mecha de la vela en relación con su cuerpo, buscando sombras largas que sobresalgan de la acción reciente del precio. Un filtro de media móvil ayuda a operar en la dirección de la tendencia subyacente.
Las pruebas indican un rendimiento anual promedio de aproximadamente el 82%. Funciona mejor en el mercado de acciones.
Durante cada actualización de vela, el sistema calcula las sombras superiores e inferiores y las compara con el tamaño del cuerpo. Un Pinbar alcista con una mecha inferior larga puede activar una entrada larga si el precio está por encima de la media móvil. Del mismo modo, un Pinbar bajista con una cola superior extendida puede iniciar una posición corta en una tendencia bajista. Los stops se colocan a un porcentaje fijo desde la entrada.
La operación se cierra cuando aparece un Pinbar opuesto contra la posición abierta o cuando se alcanza el stop protector. Combinar la lógica del Pinbar con un filtro de tendencia mejora la fiabilidad al evitar configuraciones contratendencia.
Detalles
- Criterios de entrada: Pinbar con cola larga y sombra opuesta pequeña, confirmado por la tendencia.
- Largo/Corto: Ambos.
- Criterios de salida: Pinbar opuesto o stop-loss.
- Stops: Sí, basado en porcentaje.
- Valores predeterminados:
TailToBodyRatio = 2
OppositeTailRatio = 0.5
MAPeriod = 20
CandleType = 15 minute
StopLossPercent = 1
- Filtros:
- Categoría: Patrón
- Dirección: Ambos
- Indicadores: Candlestick, MA
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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()