Scalping de Cuadrícula Fractal Adaptativa
El Scalping de Cuadrícula Fractal Adaptativa coloca órdenes limitadas alrededor de pivotes fractales recientes utilizando el ATR para la distancia. La tendencia se define mediante una media móvil simple. Cuando la volatilidad supera un umbral, se colocan límites de compra por debajo de los mínimos fractales en tendencias alcistas y límites de venta por encima de los máximos fractales en tendencias bajistas. Las salidas se producen en el nivel de cuadrícula opuesto o con un stop trailing basado en ATR.
Detalles
- Criterios de entrada: ATR por encima del umbral con el precio relativo a la SMA; límite de compra en el mínimo fractal menos el multiplicador ATR o límite de venta en el máximo fractal más el multiplicador ATR.
- Largo/Corto: Ambas direcciones.
- Criterios de salida: Nivel de cuadrícula opuesto o stop basado en fractales.
- Stops: Sí.
- Valores predeterminados:
AtrLength= 14SmaLength= 50GridMultiplierHigh= 2.0mGridMultiplierLow= 0.5mTrailStopMultiplier= 0.5mVolatilityThreshold= 1.0mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Scalping
- Dirección: Ambos
- Indicadores: Fractal, ATR, SMA
- 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>
/// Adaptive Fractal Grid Scalping strategy.
/// Five-bar fractals mark the grid. While ATR is above VolatilityThreshold the strategy keeps a buy limit at the last fractal low minus
/// ATR * GridMultiplierLow when the close is above the SMA, or a sell limit at the last fractal high plus the same distance when it is
/// below. A filled long exits at the opposite grid level (fractal high plus ATR * GridMultiplierHigh) or by a trailing stop
/// ATR * TrailStopMultiplier behind the best price; shorts mirror this.
/// </summary>
public class AdaptiveFractalGridScalpingStrategy : Strategy
{
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<decimal> _gridMultiplierHigh;
private readonly StrategyParam<decimal> _gridMultiplierLow;
private readonly StrategyParam<decimal> _trailStopMultiplier;
private readonly StrategyParam<decimal> _volatilityThreshold;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = [];
private readonly List<decimal> _lows = [];
private decimal? _fractalHigh;
private decimal? _fractalLow;
private Order _entryOrder;
private decimal? _target;
private decimal? _trailStop;
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// Period of the trend SMA.
/// </summary>
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
/// <summary>
/// ATR multiplier of the opposite grid level used as the target.
/// </summary>
public decimal GridMultiplierHigh
{
get => _gridMultiplierHigh.Value;
set => _gridMultiplierHigh.Value = value;
}
/// <summary>
/// ATR multiplier of the entry grid level.
/// </summary>
public decimal GridMultiplierLow
{
get => _gridMultiplierLow.Value;
set => _gridMultiplierLow.Value = value;
}
/// <summary>
/// ATR multiplier of the trailing stop distance.
/// </summary>
public decimal TrailStopMultiplier
{
get => _trailStopMultiplier.Value;
set => _trailStopMultiplier.Value = value;
}
/// <summary>
/// ATR level above which the grid is active.
/// </summary>
public decimal VolatilityThreshold
{
get => _volatilityThreshold.Value;
set => _volatilityThreshold.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdaptiveFractalGridScalpingStrategy()
{
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Indicators");
_smaLength = Param(nameof(SmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("SMA Length", "Period of the trend SMA", "Indicators");
_gridMultiplierHigh = Param(nameof(GridMultiplierHigh), 2.0m)
.SetNotNegative()
.SetDisplay("Grid Multiplier High", "ATR multiplier of the opposite grid level used as the target", "Grid");
_gridMultiplierLow = Param(nameof(GridMultiplierLow), 0.5m)
.SetNotNegative()
.SetDisplay("Grid Multiplier Low", "ATR multiplier of the entry grid level", "Grid");
_trailStopMultiplier = Param(nameof(TrailStopMultiplier), 0.5m)
.SetNotNegative()
.SetDisplay("Trail Stop Multiplier", "ATR multiplier of the trailing stop distance", "Risk");
_volatilityThreshold = Param(nameof(VolatilityThreshold), 1.0m)
.SetNotNegative()
.SetDisplay("Volatility Threshold", "ATR level above which the grid is active", "Grid");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var atr = new AverageTrueRange { Length = AtrLength };
var sma = new SimpleMovingAverage { Length = SmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ResetState()
{
_highs.Clear();
_lows.Clear();
_fractalHigh = null;
_fractalLow = null;
_entryOrder = null;
_target = null;
_trailStop = null;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateFractals(candle);
if (!atrValue.IsFormed || !smaValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var atr = atrValue.ToDecimal();
var sma = smaValue.ToDecimal();
var close = candle.ClosePrice;
if (Position != 0)
{
CancelEntry();
ManagePosition(candle, atr);
return;
}
_target = null;
_trailStop = null;
// A pending order that is still being registered is left alone until its state is known.
if (_entryOrder != null && _entryOrder.State is OrderStates.None or OrderStates.Pending)
return;
CancelEntry();
if (atr <= VolatilityThreshold)
return;
var offset = atr * GridMultiplierLow;
if (close > sma && _fractalLow is decimal low)
_entryOrder = BuyLimit(RoundPrice(low - offset), Volume);
else if (close < sma && _fractalHigh is decimal high)
_entryOrder = SellLimit(RoundPrice(high + offset), Volume);
}
private void ManagePosition(ICandleMessage candle, decimal atr)
{
var trailDistance = atr * TrailStopMultiplier;
if (Position > 0)
{
_target ??= _fractalHigh is decimal fh ? fh + atr * GridMultiplierHigh : null;
var newStop = candle.HighPrice - trailDistance;
_trailStop = _trailStop is decimal s ? Math.Max(s, newStop) : newStop;
if ((_target is decimal t && candle.HighPrice >= t) || candle.ClosePrice <= _trailStop)
SellMarket(Position);
}
else
{
_target ??= _fractalLow is decimal fl ? fl - atr * GridMultiplierHigh : null;
var newStop = candle.LowPrice + trailDistance;
_trailStop = _trailStop is decimal s ? Math.Min(s, newStop) : newStop;
if ((_target is decimal t && candle.LowPrice <= t) || candle.ClosePrice >= _trailStop)
BuyMarket(-Position);
}
}
private void UpdateFractals(ICandleMessage candle)
{
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > 5)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count < 5)
return;
// The middle bar is a fractal when it beats the two bars on each side.
var midHigh = _highs[2];
if (midHigh > _highs[0] && midHigh > _highs[1] && midHigh > _highs[3] && midHigh > _highs[4])
_fractalHigh = midHigh;
var midLow = _lows[2];
if (midLow < _lows[0] && midLow < _lows[1] && midLow < _lows[3] && midLow < _lows[4])
_fractalLow = midLow;
}
private void CancelEntry()
{
if (_entryOrder != null && _entryOrder.State == OrderStates.Active)
CancelOrder(_entryOrder);
_entryOrder = null;
}
private decimal RoundPrice(decimal price)
{
var step = Security.PriceStep ?? 0m;
return step > 0 ? Math.Round(price / step) * step : price;
}
}
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, OrderStates
from StockSharp.Algo.Indicators import AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class adaptive_fractal_grid_scalping_strategy(Strategy):
"""
Adaptive Fractal Grid Scalping strategy.
Five-bar fractals mark the grid. While ATR is above VolatilityThreshold the strategy keeps a buy limit at the last fractal low minus
ATR * GridMultiplierLow when the close is above the SMA, or a sell limit at the last fractal high plus the same distance when it is
below. A filled long exits at the opposite grid level (fractal high plus ATR * GridMultiplierHigh) or by a trailing stop
ATR * TrailStopMultiplier behind the best price; shorts mirror this.
"""
def __init__(self):
super(adaptive_fractal_grid_scalping_strategy, self).__init__()
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period", "Indicators")
self._sma_length = self.Param("SmaLength", 50).SetGreaterThanZero().SetDisplay("SMA Length", "Period of the trend SMA", "Indicators")
self._grid_multiplier_high = self.Param("GridMultiplierHigh", 2.0).SetNotNegative().SetDisplay("Grid Multiplier High", "ATR multiplier of the opposite grid level used as the target", "Grid")
self._grid_multiplier_low = self.Param("GridMultiplierLow", 0.5).SetNotNegative().SetDisplay("Grid Multiplier Low", "ATR multiplier of the entry grid level", "Grid")
self._trail_stop_multiplier = self.Param("TrailStopMultiplier", 0.5).SetNotNegative().SetDisplay("Trail Stop Multiplier", "ATR multiplier of the trailing stop distance", "Risk")
self._volatility_threshold = self.Param("VolatilityThreshold", 1.0).SetNotNegative().SetDisplay("Volatility Threshold", "ATR level above which the grid is active", "Grid")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).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._fractal_high = None
self._fractal_low = None
self._entry_order = None
self._target = None
self._trail_stop = None
def OnReseted(self):
super(adaptive_fractal_grid_scalping_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(adaptive_fractal_grid_scalping_strategy, self).OnStarted2(time)
self._reset_state()
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sma = SimpleMovingAverage()
sma.Length = self._sma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, 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 _process_candle(self, candle, atr_value, sma_value):
if candle.State != CandleStates.Finished:
return
self._update_fractals(candle)
if not atr_value.IsFormed or not sma_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
atr = atr_value.GetValue[Decimal](None)
sma = sma_value.GetValue[Decimal](None)
close = candle.ClosePrice
if self.Position != 0:
self._cancel_entry()
self._manage_position(candle, atr)
return
self._target = None
self._trail_stop = None
# A pending order that is still being registered is left alone until its state is known.
if self._entry_order is not None and self._entry_order.State != OrderStates.Active and self._entry_order.State != OrderStates.Done and self._entry_order.State != OrderStates.Failed:
return
self._cancel_entry()
if atr <= Decimal(self._volatility_threshold.Value):
return
offset = atr * Decimal(self._grid_multiplier_low.Value)
if close > sma and self._fractal_low is not None:
self._entry_order = self.BuyLimit(self._round_price(self._fractal_low - offset), self.Volume)
elif close < sma and self._fractal_high is not None:
self._entry_order = self.SellLimit(self._round_price(self._fractal_high + offset), self.Volume)
def _manage_position(self, candle, atr):
trail_distance = atr * Decimal(self._trail_stop_multiplier.Value)
grid_high = Decimal(self._grid_multiplier_high.Value)
if self.Position > 0:
if self._target is None and self._fractal_high is not None:
self._target = self._fractal_high + atr * grid_high
new_stop = candle.HighPrice - trail_distance
self._trail_stop = new_stop if self._trail_stop is None else max(self._trail_stop, new_stop)
if (self._target is not None and candle.HighPrice >= self._target) or candle.ClosePrice <= self._trail_stop:
self.SellMarket(self.Position)
else:
if self._target is None and self._fractal_low is not None:
self._target = self._fractal_low - atr * grid_high
new_stop = candle.LowPrice + trail_distance
self._trail_stop = new_stop if self._trail_stop is None else min(self._trail_stop, new_stop)
if (self._target is not None and candle.LowPrice <= self._target) or candle.ClosePrice >= self._trail_stop:
self.BuyMarket(-self.Position)
def _update_fractals(self, candle):
self._highs.append(candle.HighPrice)
self._lows.append(candle.LowPrice)
if len(self._highs) > 5:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < 5:
return
# The middle bar is a fractal when it beats the two bars on each side.
h = self._highs
if h[2] > h[0] and h[2] > h[1] and h[2] > h[3] and h[2] > h[4]:
self._fractal_high = h[2]
l = self._lows
if l[2] < l[0] and l[2] < l[1] and l[2] < l[3] and l[2] < l[4]:
self._fractal_low = l[2]
def _cancel_entry(self):
if self._entry_order is not None and self._entry_order.State == OrderStates.Active:
self.CancelOrder(self._entry_order)
self._entry_order = None
def _round_price(self, price):
step = self.Security.PriceStep
if step is None or step <= 0:
return price
return Math.Round(price / step) * step
def CreateClone(self):
return adaptive_fractal_grid_scalping_strategy()