Estrategia Cazadora de Doble Fondo y Doble Techo
Esta estrategia busca patrones de doble fondo y doble techo comparando mínimos y máximos recientes. Un doble fondo ocurre cuando el mínimo más bajo se alcanza dos veces dentro de una ventana de retrospectiva más amplia, mientras que el doble techo utiliza el máximo más alto. Las posiciones largas y cortas se abren en consecuencia y se cierran cuando el precio rompe el extremo opuesto tras formarse un nuevo extremo.
Detalles
- Criterios de entrada:
- Largo: Doble fondo detectado.
- Corto: Doble techo detectado.
- Largo/Corto: Ambas direcciones.
- Criterios de salida:
- Largo: Nuevo máximo por encima del máximo anterior con el precio cayendo por debajo del mínimo anterior.
- Corto: Nuevo mínimo por debajo del mínimo anterior con el precio subiendo por encima del máximo anterior.
- Stops: Ninguno.
- Valores predeterminados:
Length= 100Lookback= 100
- Filtros:
- Categoría: Patrón
- Dirección: Ambos
- Indicadores: Highest, Lowest
- Stops: No
- Complejidad: Básico
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Bajo
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Double Bottom and Top Hunter strategy.
/// The recent window is the previous Length candles and the wider window adds the Lookback candles before them. A double bottom is
/// a candle whose low reaches the lowest low of the older part of the wider window again while the recent candles stayed above it,
/// and that closes back above it; a double top mirrors this with highs. A double bottom goes long and a double top goes short,
/// reversing an opposite position. A long closes once price has made a new high above the recent high and then closes below the
/// recent low; a short closes once price has made a new low below the recent low and then closes above the recent high.
/// </summary>
public class DoubleBottomAndTopHunterStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = [];
private readonly List<decimal> _lows = [];
private bool _newExtremeSinceEntry;
/// <summary>
/// Candles in the recent window.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Older candles that widen the window.
/// </summary>
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DoubleBottomAndTopHunterStrategy()
{
_length = Param(nameof(Length), 100)
.SetGreaterThanZero()
.SetDisplay("Length", "Candles in the recent window", "Pattern");
_lookback = Param(nameof(Lookback), 100)
.SetGreaterThanZero()
.SetDisplay("Lookback", "Older candles that widen the window", "Pattern");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_highs.Clear();
_lows.Clear();
_newExtremeSinceEntry = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var total = Length + Lookback;
if (_highs.Count < total)
{
AddCandle(candle, total);
return;
}
// Oldest first: the first Lookback entries are the older part, the last Length entries the recent one.
var olderHigh = _highs.Take(Lookback).Max();
var olderLow = _lows.Take(Lookback).Min();
var recentHigh = _highs.Skip(Lookback).Max();
var recentLow = _lows.Skip(Lookback).Min();
AddCandle(candle, total);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var doubleBottom = olderLow < recentLow && candle.LowPrice <= olderLow && close > olderLow;
var doubleTop = olderHigh > recentHigh && candle.HighPrice >= olderHigh && close < olderHigh;
if (doubleBottom && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_newExtremeSinceEntry = false;
return;
}
if (doubleTop && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_newExtremeSinceEntry = false;
return;
}
if (Position > 0)
{
if (candle.HighPrice > recentHigh)
_newExtremeSinceEntry = true;
else if (_newExtremeSinceEntry && close < recentLow)
{
SellMarket(Position);
_newExtremeSinceEntry = false;
}
}
else if (Position < 0)
{
if (candle.LowPrice < recentLow)
_newExtremeSinceEntry = true;
else if (_newExtremeSinceEntry && close > recentHigh)
{
BuyMarket(-Position);
_newExtremeSinceEntry = false;
}
}
}
private void AddCandle(ICandleMessage candle, int total)
{
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
while (_highs.Count > total)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class double_bottom_and_top_hunter_strategy(Strategy):
"""
Double Bottom and Top Hunter strategy.
The recent window is the previous Length candles and the wider window adds the Lookback candles before them. A double bottom is
a candle whose low reaches the lowest low of the older part of the wider window again while the recent candles stayed above it,
and that closes back above it; a double top mirrors this with highs. A double bottom goes long and a double top goes short,
reversing an opposite position. A long closes once price has made a new high above the recent high and then closes below the
recent low; a short closes once price has made a new low below the recent low and then closes above the recent high.
"""
def __init__(self):
super(double_bottom_and_top_hunter_strategy, self).__init__()
self._length = self.Param("Length", 100).SetGreaterThanZero().SetDisplay("Length", "Candles in the recent window", "Pattern")
self._lookback = self.Param("Lookback", 100).SetGreaterThanZero().SetDisplay("Lookback", "Older candles that widen the window", "Pattern")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._highs = []
self._lows = []
self._new_extreme_since_entry = False
def OnReseted(self):
super(double_bottom_and_top_hunter_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(double_bottom_and_top_hunter_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _add_candle(self, candle, total):
self._highs.append(candle.HighPrice)
self._lows.append(candle.LowPrice)
while len(self._highs) > total:
self._highs.pop(0)
self._lows.pop(0)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
lookback = self._lookback.Value
total = self._length.Value + lookback
if len(self._highs) < total:
self._add_candle(candle, total)
return
# Oldest first: the first Lookback entries are the older part, the last Length entries the recent one.
older_high = max(self._highs[:lookback])
older_low = min(self._lows[:lookback])
recent_high = max(self._highs[lookback:])
recent_low = min(self._lows[lookback:])
self._add_candle(candle, total)
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
double_bottom = older_low < recent_low and candle.LowPrice <= older_low and close > older_low
double_top = older_high > recent_high and candle.HighPrice >= older_high and close < older_high
if double_bottom and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._new_extreme_since_entry = False
return
if double_top and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._new_extreme_since_entry = False
return
if self.Position > 0:
if candle.HighPrice > recent_high:
self._new_extreme_since_entry = True
elif self._new_extreme_since_entry and close < recent_low:
self.SellMarket(self.Position)
self._new_extreme_since_entry = False
elif self.Position < 0:
if candle.LowPrice < recent_low:
self._new_extreme_since_entry = True
elif self._new_extreme_since_entry and close > recent_high:
self.BuyMarket(-self.Position)
self._new_extreme_since_entry = False
def CreateClone(self):
return double_bottom_and_top_hunter_strategy()