Patrón de Doble Techo (Double Top Pattern)
El Doble Techo identifica dos picos separados por un número de barras con precios similares. Después de formarse el segundo pico, una vela bajista confirma la reversión.
Las pruebas indican un rendimiento anual promedio de aproximadamente el 58%. Funciona mejor en el mercado de acciones.
La estrategia vende en corto tras la confirmación con un stop por encima de los máximos del patrón, con el objetivo de obtener beneficios de un cambio de tendencia tras el agotamiento de los compradores.
Las posiciones se cierran mediante stop-loss o objetivos discrecionales.
Detalles
- Criterios de entrada: Dos techos dentro de
SimilarityPercentdespués deDistancebarras. - Largo/Corto: Solo cortos.
- Criterios de salida: El precio repunta o stop-loss.
- Stops: Sí.
- Valores predeterminados:
Distance= 5SimilarityPercent= 2.0mCandleType= TimeSpan.FromMinutes(15)StopLossPercent= 1.0m
- Filtros:
- Categoría: Patrón
- Dirección: Solo cortos
- Indicadores: Price Action
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: Sí
- 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>
/// Double Top reversal strategy.
/// Detects two similar tops and enters short on confirmation.
/// Uses SMA for exit signal.
/// </summary>
public class DoubleTopStrategy : Strategy
{
private readonly StrategyParam<int> _distanceParam;
private readonly StrategyParam<decimal> _similarityPercent;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _recentHigh;
private decimal _prevHigh;
private int _barsSinceHigh;
private int _cooldown;
/// <summary>
/// Distance between tops in bars.
/// </summary>
public int Distance
{
get => _distanceParam.Value;
set => _distanceParam.Value = value;
}
/// <summary>
/// Maximum percent difference between two tops.
/// </summary>
public decimal SimilarityPercent
{
get => _similarityPercent.Value;
set => _similarityPercent.Value = value;
}
/// <summary>
/// MA Period for exit.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="DoubleTopStrategy"/>.
/// </summary>
public DoubleTopStrategy()
{
_distanceParam = Param(nameof(Distance), 20)
.SetRange(3, 100)
.SetDisplay("Distance", "Bars between tops", "Pattern");
_similarityPercent = Param(nameof(SimilarityPercent), 1.0m)
.SetRange(0.1m, 5.0m)
.SetDisplay("Similarity %", "Max % diff between tops", "Pattern");
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for exit SMA", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_recentHigh = default;
_prevHigh = default;
_barsSinceHigh = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_recentHigh = 0;
_prevHigh = 0;
_barsSinceHigh = 0;
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
TrackHighs(candle);
return;
}
// Track new highs
if (_recentHigh == 0 || candle.HighPrice > _recentHigh)
{
if (_recentHigh > 0)
_prevHigh = _recentHigh;
_recentHigh = candle.HighPrice;
_barsSinceHigh = 0;
}
else
{
_barsSinceHigh++;
}
if (Position == 0 && _prevHigh > 0 && _barsSinceHigh >= Distance)
{
var priceDiff = Math.Abs((_recentHigh - _prevHigh) / _prevHigh * 100);
if (priceDiff <= SimilarityPercent && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
_recentHigh = 0;
_prevHigh = 0;
}
else if (priceDiff <= SimilarityPercent && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
_recentHigh = 0;
_prevHigh = 0;
}
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
private void TrackHighs(ICandleMessage candle)
{
if (_recentHigh == 0 || candle.HighPrice > _recentHigh)
{
if (_recentHigh > 0)
_prevHigh = _recentHigh;
_recentHigh = candle.HighPrice;
_barsSinceHigh = 0;
}
else
{
_barsSinceHigh++;
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class double_top_strategy(Strategy):
"""
Double Top reversal strategy.
Detects two similar tops and enters short on confirmation.
Uses SMA for exit signal.
"""
def __init__(self):
super(double_top_strategy, self).__init__()
self._distance = self.Param("Distance", 20).SetDisplay("Distance", "Bars between tops", "Pattern")
self._similarity_pct = self.Param("SimilarityPercent", 1.0).SetDisplay("Similarity %", "Max % diff between tops", "Pattern")
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for exit SMA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._recent_high = 0.0
self._prev_high = 0.0
self._bars_since_high = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(double_top_strategy, self).OnReseted()
self._recent_high = 0.0
self._prev_high = 0.0
self._bars_since_high = 0
self._cooldown = 0
def OnStarted2(self, time):
super(double_top_strategy, self).OnStarted2(time)
self._recent_high = 0.0
self._prev_high = 0.0
self._bars_since_high = 0
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _track_highs(self, candle):
high = float(candle.HighPrice)
if self._recent_high == 0 or high > self._recent_high:
if self._recent_high > 0:
self._prev_high = self._recent_high
self._recent_high = high
self._bars_since_high = 0
else:
self._bars_since_high += 1
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
self._track_highs(candle)
return
# Track new highs
high = float(candle.HighPrice)
if self._recent_high == 0 or high > self._recent_high:
if self._recent_high > 0:
self._prev_high = self._recent_high
self._recent_high = high
self._bars_since_high = 0
else:
self._bars_since_high += 1
close = float(candle.ClosePrice)
sv = float(sma_val)
cd = self._cooldown_bars.Value
dist = self._distance.Value
sim = float(self._similarity_pct.Value)
if self.Position == 0 and self._prev_high > 0 and self._bars_since_high >= dist:
price_diff = abs((self._recent_high - self._prev_high) / self._prev_high * 100.0)
if price_diff <= sim and close < sv:
self.SellMarket()
self._cooldown = cd
self._recent_high = 0.0
self._prev_high = 0.0
elif price_diff <= sim and close > sv:
self.BuyMarket()
self._cooldown = cd
self._recent_high = 0.0
self._prev_high = 0.0
elif self.Position > 0 and close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > sv:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return double_top_strategy()