Fractals Distancia Mínima replica el asesor experto de MetaTrader "Fractals minimum distance" utilizando la API de estrategia de alto nivel de StockSharp. El sistema escanea la serie de velas configurada en busca de patrones fractales de cinco barras estilo Bill Williams. Cada vez que aparece un nuevo fractal confirmado en el desplazamiento de barra de señal especificado, la estrategia mide la brecha entre los fractales de subida y bajada más recientes. Solo se permite una orden de mercado cuando esta distancia supera el umbral requerido expresado en pips.
La conversión mantiene el comportamiento original de cerrar cualquier exposición opuesta inmediatamente antes de revertir. A diferencia de la versión MQL, el tamaño de la posición se toma de la propiedad Volume de la estrategia en lugar de realizar cálculos de riesgo basados en la cuenta. No se envían órdenes de stop-loss ni take-profit, coincidiendo con el experto fuente.
Lógica de señales
Suscribirse al tipo de vela definido por CandleType y construir buffers deslizantes de máximos y mínimos que siempre contengan la barra ubicada SignalBar velas en el pasado junto con dos vecinas en cada lado.
Detectar un fractal superior cuando el máximo de la barra central es estrictamente mayor que los máximos de las dos velas precedentes y las dos siguientes. Detectar un fractal inferior de forma análoga para los mínimos.
Convertir el parámetro DistancePips a una distancia de precio usando el PriceStep del símbolo. Los símbolos con tres o cinco dígitos decimales se ajustan automáticamente para tratar las cotizaciones 0.001/0.00001 como un pip.
Cuando se confirma un fractal superior:
Almacenar el nuevo nivel superior y cerrar las posiciones largas existentes.
Si tanto el último fractal superior como el inferior son conocidos y su diferencia absoluta es al menos el umbral de distancia, enviar una orden de venta de mercado usando Volume.
Cuando se confirma un fractal inferior:
Almacenar el nuevo nivel inferior y cerrar las posiciones cortas existentes.
Si se cumple la condición de distancia, enviar una orden de compra de mercado usando Volume.
Las operaciones se realizan solo después de que se cierra la vela que finaliza el fractal, asegurando que las barras inacabadas nunca desencadenen entradas. La estrategia se basa en IsFormedAndOnlineAndAllowTrading() para evitar colocar órdenes antes de que el entorno esté listo.
Parámetros
Nombre
Descripción
Notas
DistancePips
Espaciado mínimo entre los últimos fractales de subida y bajada medido en pips.
Convertido internamente a unidades de precio usando el tamaño del tick del instrumento.
SignalBar
Número de barras completamente cerradas que deben pasar después de la barra que aloja el fractal.
El valor efectivo mínimo es 2, coincidiendo con la confirmación de dos barras usada por los fractales de Bill Williams.
CandleType
Serie de datos que alimenta los cálculos.
El valor predeterminado es el marco temporal de un minuto; cambiar para trabajar con otras resoluciones.
Volume
Propiedad estándar de la estrategia StockSharp que define el tamaño de la operación.
Reemplaza el dimensionamiento basado en riesgo original del experto MetaTrader.
Gestión de posiciones y diferencias con MQL
Las posiciones siempre se aplanan antes de revertir la dirección, exactamente como lo hacía el helper ClosePositions fuente.
El experto original llamaba a RefreshRates() y realizaba configuraciones explícitas de deslizamiento. Esos aspectos se delegan a la infraestructura de StockSharp en este port.
Las órdenes de stop-loss y take-profit no eran parte de la lógica MQL y permanecen ausentes aquí.
DistancePips usa precisión entera como la entrada ushort, mientras que SignalBar refleja la entrada uchar de MQL.
Dado que StockSharp trabaja con posiciones netas, abrir una orden en dirección opuesta automáticamente voltea la exposición, coincidiendo con el comportamiento de netting de MetaTrader.
Consejos de uso
Comience con el mismo desplazamiento de barra de señal (SignalBar = 3) del código original y calibre el umbral de distancia según la volatilidad del instrumento.
Aumente SignalBar para esperar más velas después de que aparezca un fractal, lo que puede filtrar oscilaciones rápidas.
Combine con gestión de riesgo externa como el helper integrado StartProtection() si se requiere un stop de protección.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fractals minimum distance breakout strategy converted from MetaTrader.
/// </summary>
public class FractalsMinimumDistanceStrategy : Strategy
{
private readonly StrategyParam<int> _distancePips;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevUpperFractal;
private decimal? _prevLowerFractal;
private decimal[] _highs = Array.Empty<decimal>();
private decimal[] _lows = Array.Empty<decimal>();
private int _bufferCount;
private int _windowSize;
private int _signalOffset;
private decimal _pipSize;
public int DistancePips
{
get => _distancePips.Value;
set => _distancePips.Value = value;
}
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public FractalsMinimumDistanceStrategy()
{
_distancePips = Param(nameof(DistancePips), 15)
.SetDisplay("Distance (pips)", "Minimum allowed gap between the last two fractals", "Risk")
;
_signalBar = Param(nameof(SignalBar), 3)
.SetDisplay("Signal bar offset", "How many closed bars ago the fractal must appear", "General")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle type", "Primary candle series used for signals", "Data")
;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevUpperFractal = null;
_prevLowerFractal = null;
_highs = Array.Empty<decimal>();
_lows = Array.Empty<decimal>();
_bufferCount = 0;
_windowSize = 0;
_signalOffset = 0;
_pipSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
InitializeBuffers();
_pipSize = CalculatePipSize();
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void InitializeBuffers()
{
_signalOffset = Math.Max(2, SignalBar);
_windowSize = Math.Max(_signalOffset + 3, 5);
_highs = new decimal[_windowSize];
_lows = new decimal[_windowSize];
_bufferCount = 0;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || _windowSize == 0)
return;
// Shift the rolling buffers to keep the configured number of historical bars.
for (var i = 0; i < _windowSize - 1; i++)
{
_highs[i] = _highs[i + 1];
_lows[i] = _lows[i + 1];
}
// Append the latest candle extremes.
_highs[_windowSize - 1] = candle.HighPrice;
_lows[_windowSize - 1] = candle.LowPrice;
if (_bufferCount < _windowSize)
_bufferCount++;
if (_bufferCount < _windowSize)
return;
var centerIndex = _windowSize - 1 - _signalOffset;
if (centerIndex < 2 || centerIndex > _windowSize - 3)
return;
var high = _highs[centerIndex];
var low = _lows[centerIndex];
var isUpperFractal =
high > _highs[centerIndex - 1] &&
high > _highs[centerIndex - 2] &&
high > _highs[centerIndex + 1] &&
high > _highs[centerIndex + 2];
var isLowerFractal =
low < _lows[centerIndex - 1] &&
low < _lows[centerIndex - 2] &&
low < _lows[centerIndex + 1] &&
low < _lows[centerIndex + 2];
var distanceThreshold = DistancePips * _pipSize;
if (isUpperFractal)
{
_prevUpperFractal = high;
// Close existing long exposure before reversing.
if (Position > 0)
SellMarket();
// Enter a short position if the fractals are far enough apart.
if (ShouldOpenTrade(distanceThreshold))
SellMarket();
}
if (isLowerFractal)
{
_prevLowerFractal = low;
// Close existing short exposure before reversing.
if (Position < 0)
BuyMarket();
// Enter a long position if the fractals are far enough apart.
if (ShouldOpenTrade(distanceThreshold))
BuyMarket();
}
}
private bool ShouldOpenTrade(decimal distanceThreshold)
{
if (Volume <= 0)
return false;
if (_prevUpperFractal is not decimal upper || _prevLowerFractal is not decimal lower)
return false;
var threshold = Math.Abs(distanceThreshold);
return Math.Abs(upper - lower) >= threshold;
}
private decimal CalculatePipSize()
{
var priceStep = Security?.PriceStep;
if (priceStep is not decimal step || step <= 0m)
return 1m;
var decimals = GetDecimalPlaces(step);
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
private static int GetDecimalPlaces(decimal value)
{
var bits = decimal.GetBits(value);
return (bits[3] >> 16) & 0xFF;
}
}
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.Strategies import Strategy
class fractals_minimum_distance_strategy(Strategy):
"""Fractals minimum distance breakout strategy."""
def __init__(self):
super(fractals_minimum_distance_strategy, self).__init__()
self._distance_pips = self.Param("DistancePips", 15) \
.SetDisplay("Distance (pips)", "Minimum allowed gap between fractals", "Risk")
self._signal_bar = self.Param("SignalBar", 3) \
.SetDisplay("Signal bar offset", "How many closed bars ago the fractal must appear", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle type", "Primary candle series used for signals", "Data")
self._prev_upper = None
self._prev_lower = None
self._highs = []
self._lows = []
self._buffer_count = 0
self._window_size = 0
self._signal_offset = 0
self._pip_size = 0.0
@property
def DistancePips(self):
return self._distance_pips.Value
@property
def SignalBar(self):
return self._signal_bar.Value
@property
def CandleType(self):
return self._candle_type.Value
def _calc_pip_size(self):
sec = self.Security
if sec is None or sec.PriceStep is None or float(sec.PriceStep) <= 0:
return 1.0
step = float(sec.PriceStep)
# count decimals
digits = 0
temp = step
while temp > 0 and temp < 1 and digits < 10:
temp *= 10
digits += 1
if digits == 3 or digits == 5:
return step * 10.0
return step
def OnStarted2(self, time):
super(fractals_minimum_distance_strategy, self).OnStarted2(time)
self._signal_offset = max(2, self.SignalBar)
self._window_size = max(self._signal_offset + 3, 5)
self._highs = [0.0] * self._window_size
self._lows = [0.0] * self._window_size
self._buffer_count = 0
self._pip_size = self._calc_pip_size()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished or self._window_size == 0:
return
ws = self._window_size
for i in range(ws - 1):
self._highs[i] = self._highs[i + 1]
self._lows[i] = self._lows[i + 1]
self._highs[ws - 1] = float(candle.HighPrice)
self._lows[ws - 1] = float(candle.LowPrice)
if self._buffer_count < ws:
self._buffer_count += 1
if self._buffer_count < ws:
return
ci = ws - 1 - self._signal_offset
if ci < 2 or ci > ws - 3:
return
high = self._highs[ci]
low = self._lows[ci]
is_upper = (high > self._highs[ci - 1] and high > self._highs[ci - 2] and
high > self._highs[ci + 1] and high > self._highs[ci + 2])
is_lower = (low < self._lows[ci - 1] and low < self._lows[ci - 2] and
low < self._lows[ci + 1] and low < self._lows[ci + 2])
dist_threshold = self.DistancePips * self._pip_size
if is_upper:
self._prev_upper = high
if self.Position > 0:
self.SellMarket()
if self._should_open(dist_threshold):
self.SellMarket()
if is_lower:
self._prev_lower = low
if self.Position < 0:
self.BuyMarket()
if self._should_open(dist_threshold):
self.BuyMarket()
def _should_open(self, threshold):
if self.Volume <= 0:
return False
if self._prev_upper is None or self._prev_lower is None:
return False
return abs(self._prev_upper - self._prev_lower) >= abs(threshold)
def OnReseted(self):
super(fractals_minimum_distance_strategy, self).OnReseted()
self._prev_upper = None
self._prev_lower = None
self._highs = []
self._lows = []
self._buffer_count = 0
self._window_size = 0
self._signal_offset = 0
self._pip_size = 0.0
def CreateClone(self):
return fractals_minimum_distance_strategy()