Estrategia de Oscilaciones de Liquidez
La estrategia de Oscilaciones de Liquidez rastrea los máximos y mínimos pivote recientes para definir resistencia y soporte. Una operación larga ocurre cuando el mínimo cruza por encima del soporte mientras el cierre permanece por debajo de la resistencia. Una operación corta se activa cuando el máximo cruza por debajo de la resistencia mientras el cierre se mantiene por encima del soporte. La gestión de riesgo utiliza un stop loss por debajo/encima del nivel con un buffer y un take profit al doble de esa distancia, generando un riesgo-recompensa de 1:2.
Detalles
- Criterios de entrada:
- Largo: El mínimo cruza por encima del soporte y el cierre < resistencia.
- Corto: El máximo cruza por debajo de la resistencia y el cierre > soporte.
- Largo/Corto: Ambos lados.
- Criterios de salida:
- Stop loss en el nivel o con buffer.
- Take profit a 2× la distancia de riesgo.
- Stops: Stop loss y take profit.
- Valores predeterminados:
Lookback= 5StopLossBuffer= 0.5
- Filtros:
- Categoría: Ruptura
- Dirección: Ambos
- Indicadores: Pivot highs/lows
- Stops: Sí
- Complejidad: Bajo
- Marco temporal: 1h (predeterminado)
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Liquidity Swings Strategy.
/// Uses recent pivot highs/lows as resistance/support levels.
/// Enters on bounce from support/resistance with risk-reward.
/// </summary>
public class LiquiditySwingsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _cooldownBars;
private ExponentialMovingAverage _ema;
private readonly List<decimal> _highBuffer = new();
private readonly List<decimal> _lowBuffer = new();
private decimal _resistance;
private decimal _support;
private decimal _entryPrice;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public LiquiditySwingsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_lookback = Param(nameof(Lookback), 5)
.SetGreaterThanZero()
.SetDisplay("Pivot Lookback", "Pivot detection lookback", "Parameters");
_emaLength = Param(nameof(EmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA trend filter period", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_highBuffer.Clear();
_lowBuffer.Clear();
_resistance = 0;
_support = 0;
_entryPrice = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed)
return;
UpdatePivotLevels(candle);
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
if (_resistance == 0 || _support == 0)
return;
var price = candle.ClosePrice;
// Buy: price near support, bounce up, trend filter (price > ema)
if (price > _support && price < (_support + (_resistance - _support) * 0.3m) && price > emaVal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_entryPrice = price;
_cooldownRemaining = CooldownBars;
}
// Sell: price near resistance, drop, trend filter (price < ema)
else if (price < _resistance && price > (_resistance - (_resistance - _support) * 0.3m) && price < emaVal && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_entryPrice = price;
_cooldownRemaining = CooldownBars;
}
// Exit long at resistance
else if (Position > 0 && price >= _resistance)
{
SellMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
}
// Exit short at support
else if (Position < 0 && price <= _support)
{
BuyMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
}
// Stop loss long: price breaks below support
else if (Position > 0 && price < _support)
{
SellMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
}
// Stop loss short: price breaks above resistance
else if (Position < 0 && price > _resistance)
{
BuyMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
}
}
private void UpdatePivotLevels(ICandleMessage candle)
{
var size = Lookback * 2 + 1;
_highBuffer.Add(candle.HighPrice);
_lowBuffer.Add(candle.LowPrice);
if (_highBuffer.Count > size)
_highBuffer.RemoveAt(0);
if (_lowBuffer.Count > size)
_lowBuffer.RemoveAt(0);
if (_highBuffer.Count == size)
{
var center = Lookback;
var candidate = _highBuffer[center];
var isPivot = true;
for (var i = 0; i < size; i++)
{
if (i == center)
continue;
if (_highBuffer[i] >= candidate)
{
isPivot = false;
break;
}
}
if (isPivot)
_resistance = candidate;
}
if (_lowBuffer.Count == size)
{
var center = Lookback;
var candidate = _lowBuffer[center];
var isPivot = true;
for (var i = 0; i < size; i++)
{
if (i == center)
continue;
if (_lowBuffer[i] <= candidate)
{
isPivot = false;
break;
}
}
if (isPivot)
_support = candidate;
}
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class liquidity_swings_strategy(Strategy):
"""Liquidity Swings Strategy."""
def __init__(self):
super(liquidity_swings_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._lookback = self.Param("Lookback", 5) \
.SetDisplay("Pivot Lookback", "Pivot detection lookback", "Parameters")
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "EMA trend filter period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._ema = None
self._high_buffer = []
self._low_buffer = []
self._resistance = 0.0
self._support = 0.0
self._entry_price = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(liquidity_swings_strategy, self).OnReseted()
self._ema = None
self._high_buffer = []
self._low_buffer = []
self._resistance = 0.0
self._support = 0.0
self._entry_price = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(liquidity_swings_strategy, self).OnStarted2(time)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
if not self._ema.IsFormed:
return
self._update_pivot_levels(candle)
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
if self._resistance == 0.0 or self._support == 0.0:
return
price = float(candle.ClosePrice)
ema_v = float(ema_val)
cooldown = int(self._cooldown_bars.Value)
rng = self._resistance - self._support
if price > self._support and price < (self._support + rng * 0.3) and price > ema_v and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._entry_price = price
self._cooldown_remaining = cooldown
elif price < self._resistance and price > (self._resistance - rng * 0.3) and price < ema_v and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._entry_price = price
self._cooldown_remaining = cooldown
elif self.Position > 0 and price >= self._resistance:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
elif self.Position < 0 and price <= self._support:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
elif self.Position > 0 and price < self._support:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
elif self.Position < 0 and price > self._resistance:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
def _update_pivot_levels(self, candle):
lookback = int(self._lookback.Value)
size = lookback * 2 + 1
self._high_buffer.append(float(candle.HighPrice))
self._low_buffer.append(float(candle.LowPrice))
if len(self._high_buffer) > size:
self._high_buffer.pop(0)
if len(self._low_buffer) > size:
self._low_buffer.pop(0)
if len(self._high_buffer) == size:
center = lookback
candidate = self._high_buffer[center]
is_pivot = True
for i in range(size):
if i == center:
continue
if self._high_buffer[i] >= candidate:
is_pivot = False
break
if is_pivot:
self._resistance = candidate
if len(self._low_buffer) == size:
center = lookback
candidate = self._low_buffer[center]
is_pivot = True
for i in range(size):
if i == center:
continue
if self._low_buffer[i] <= candidate:
is_pivot = False
break
if is_pivot:
self._support = candidate
def CreateClone(self):
return liquidity_swings_strategy()