Estrategia de Distribución Wyckoff
La Distribución Wyckoff es una fase de techo caracterizada por ventas pesadas en los rallies y pruebas de resistencia. El volumen a menudo se expande en los movimientos bajistas y se contrae en los rebotes, sugiriendo que los grandes intereses están liquidando posiciones.
Las pruebas indican un rendimiento anual promedio de aproximadamente 64%. Funciona mejor en el mercado de divisas.
Esta estrategia vende en corto cuando el precio rompe hacia abajo desde el rango de distribución, anticipando una caída sostenida.
Un stop justo por encima del rango protege contra falsos rompimientos, y las posiciones se cierran si el precio regresa a la parte superior de la estructura.
Detalles
- Criterios de entrada: señal de indicador
- Largo/Corto: Ambos
- Criterios de salida: stop-loss o señal opuesta
- Stops: Sí, basados en porcentaje
- Valores predeterminados:
CandleType= 15 minuteStopLoss= 2%
- Filtros:
- Categoría: Seguimiento de tendencia
- Dirección: Ambos
- Indicadores: Volume, Price
- 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>
/// Strategy based on Wyckoff Distribution pattern.
/// Detects narrowing ranges near extremes (distribution/accumulation),
/// then enters on upthrust/spring confirmation with MA filter.
/// Uses bar-based cooldown to control trade frequency.
/// </summary>
public class WyckoffDistributionStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _rangePeriod;
private readonly StrategyParam<int> _cooldownBars;
private SimpleMovingAverage _ma;
private Highest _highest;
private Lowest _lowest;
private decimal _prevMa;
private decimal _prevClose;
private int _narrowCount;
private int _barsSinceEntry;
private decimal _entryPrice;
private int _holdBars;
/// <summary>
/// Candle type and timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// MA period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Highest/Lowest period.
/// </summary>
public int RangePeriod
{
get => _rangePeriod.Value;
set => _rangePeriod.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public WyckoffDistributionStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetDisplay("MA Period", "SMA period", "Indicators")
.SetRange(10, 50);
_rangePeriod = Param(nameof(RangePeriod), 20)
.SetDisplay("Range Period", "Highest/Lowest period", "Indicators")
.SetRange(10, 50);
_cooldownBars = Param(nameof(CooldownBars), 800)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(10, 2000);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma = default;
_highest = default;
_lowest = default;
_prevMa = 0;
_prevClose = 0;
_narrowCount = 0;
_barsSinceEntry = 0;
_entryPrice = 0;
_holdBars = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_barsSinceEntry = CooldownBars; // allow immediate first trade
_ma = new SimpleMovingAverage { Length = MaPeriod };
_highest = new Highest { Length = RangePeriod };
_lowest = new Lowest { Length = RangePeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ma, _highest, _lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal ma, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var range = highest - lowest;
if (range <= 0 || _prevMa == 0)
{
_prevMa = ma;
_prevClose = close;
return;
}
_barsSinceEntry++;
var candleRange = candle.HighPrice - candle.LowPrice;
var isNarrow = candleRange < range * 0.35m;
// Track consecutive narrow-range candles
if (isNarrow)
_narrowCount++;
else
_narrowCount = 0;
// Exit logic: hold for minimum bars, then exit on MA cross
if (Position != 0 && _holdBars > 0)
{
_holdBars--;
}
if (Position > 0 && _holdBars == 0)
{
if (close < ma)
{
SellMarket();
_barsSinceEntry = 0;
}
}
else if (Position < 0 && _holdBars == 0)
{
if (close > ma)
{
BuyMarket();
_barsSinceEntry = 0;
}
}
// Entry logic: only when no position and sufficient cooldown
if (Position == 0 && _barsSinceEntry >= CooldownBars && _narrowCount >= 2)
{
var nearTop = close > lowest + range * 0.55m;
var nearBottom = close < highest - range * 0.55m;
// Upthrust (short): price near top after consolidation, bearish candle below MA
if (nearTop && close < candle.OpenPrice && close < ma)
{
SellMarket();
_entryPrice = close;
_barsSinceEntry = 0;
_narrowCount = 0;
_holdBars = 20;
}
// Spring (long): price near bottom after consolidation, bullish candle above MA
else if (nearBottom && close > candle.OpenPrice && close > ma)
{
BuyMarket();
_entryPrice = close;
_barsSinceEntry = 0;
_narrowCount = 0;
_holdBars = 20;
}
}
_prevMa = ma;
_prevClose = close;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class wyckoff_distribution_strategy(Strategy):
"""
Strategy based on Wyckoff Distribution pattern.
Detects narrowing ranges near extremes (distribution/accumulation),
then enters on upthrust/spring confirmation with MA filter.
Uses bar-based cooldown to control trade frequency.
"""
def __init__(self):
super(wyckoff_distribution_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "SMA period", "Indicators")
self._range_period = self.Param("RangePeriod", 20).SetDisplay("Range Period", "Highest/Lowest period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 800).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._prev_ma = 0.0
self._prev_close = 0.0
self._narrow_count = 0
self._bars_since_entry = 0
self._entry_price = 0.0
self._hold_bars = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(wyckoff_distribution_strategy, self).OnReseted()
self._prev_ma = 0.0
self._prev_close = 0.0
self._narrow_count = 0
self._bars_since_entry = 0
self._entry_price = 0.0
self._hold_bars = 0
def OnStarted2(self, time):
super(wyckoff_distribution_strategy, self).OnStarted2(time)
self._bars_since_entry = self._cooldown_bars.Value # allow immediate first trade
self._prev_ma = 0.0
self._prev_close = 0.0
self._narrow_count = 0
self._entry_price = 0.0
self._hold_bars = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
highest = Highest()
highest.Length = self._range_period.Value
lowest = Lowest()
lowest.Length = self._range_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, highest, lowest, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val, highest_val, lowest_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ma = float(ma_val)
highest = float(highest_val)
lowest = float(lowest_val)
rng = highest - lowest
if rng <= 0 or self._prev_ma == 0:
self._prev_ma = ma
self._prev_close = close
return
self._bars_since_entry += 1
candle_range = float(candle.HighPrice) - float(candle.LowPrice)
is_narrow = candle_range < rng * 0.35
# Track consecutive narrow-range candles
if is_narrow:
self._narrow_count += 1
else:
self._narrow_count = 0
cd = self._cooldown_bars.Value
# Exit logic: hold for minimum bars, then exit on MA cross
if self.Position != 0 and self._hold_bars > 0:
self._hold_bars -= 1
if self.Position > 0 and self._hold_bars == 0:
if close < ma:
self.SellMarket()
self._bars_since_entry = 0
elif self.Position < 0 and self._hold_bars == 0:
if close > ma:
self.BuyMarket()
self._bars_since_entry = 0
# Entry logic: only when no position and sufficient cooldown
if self.Position == 0 and self._bars_since_entry >= cd and self._narrow_count >= 2:
near_top = close > lowest + rng * 0.55
near_bottom = close < highest - rng * 0.55
# Upthrust (short): price near top after consolidation, bearish candle below MA
if near_top and candle.ClosePrice < candle.OpenPrice and close < ma:
self.SellMarket()
self._entry_price = close
self._bars_since_entry = 0
self._narrow_count = 0
self._hold_bars = 20
# Spring (long): price near bottom after consolidation, bullish candle above MA
elif near_bottom and candle.ClosePrice > candle.OpenPrice and close > ma:
self.BuyMarket()
self._entry_price = close
self._bars_since_entry = 0
self._narrow_count = 0
self._hold_bars = 20
self._prev_ma = ma
self._prev_close = close
def CreateClone(self):
return wyckoff_distribution_strategy()