Fractals Distância Mínima replica o consultor especialista do MetaTrader "Fractals minimum distance" usando a API de estratégia de alto nível do StockSharp. O sistema varre a série de candles configurada em busca de padrões fractais de cinco barras no estilo Bill Williams. Cada vez que um novo fractal confirmado aparece no deslocamento da barra de sinal especificada, a estratégia mede a distância entre os fractais de subida e descida mais recentes. Uma ordem de mercado só é permitida quando essa distância excede o limiar necessário expresso em pips.
A conversão mantém o comportamento original de fechar qualquer exposição oposta imediatamente antes de reverter. Ao contrário da versão MQL, o tamanho da posição é retirado da propriedade Volume da estratégia em vez de realizar cálculos de risco baseados na conta. Nenhuma ordem de stop-loss ou take-profit é enviada, correspondendo ao expert de origem.
Lógica de sinais
Assinar o tipo de candle definido por CandleType e construir buffers deslizantes de máximas e mínimas que sempre contenham a barra localizada SignalBar candles no passado junto com dois vizinhos em cada lado.
Detectar um fractal superior quando a máxima da barra central é estritamente maior que as máximas das duas velas anteriores e das duas seguintes. Detectar um fractal inferior de forma análoga para as mínimas.
Converter o parâmetro DistancePips em uma distância de preço usando o PriceStep do símbolo. Símbolos com três ou cinco dígitos decimais são automaticamente ajustados para tratar cotações de 0.001/0.00001 como um pip.
Quando um fractal superior é confirmado:
Armazenar o novo nível superior e fechar as posições compradas existentes.
Se tanto o último fractal superior quanto o inferior forem conhecidos e sua diferença absoluta for pelo menos o limiar de distância, enviar uma ordem de venda a mercado usando Volume.
Quando um fractal inferior é confirmado:
Armazenar o novo nível inferior e fechar as posições vendidas existentes.
Se a condição de distância for satisfeita, enviar uma ordem de compra a mercado usando Volume.
As operações são colocadas apenas após o fechamento do candle que finaliza o fractal, garantindo que barras inacabadas nunca acionem entradas. A estratégia baseia-se em IsFormedAndOnlineAndAllowTrading() para evitar colocar ordens antes que o ambiente esteja pronto.
Parâmetros
Nome
Descrição
Notas
DistancePips
Espaçamento mínimo entre os últimos fractais de subida e descida medido em pips.
Convertido internamente para unidades de preço usando o tamanho do tick do instrumento.
SignalBar
Número de barras completamente fechadas que devem passar após a barra que hospeda o fractal.
O valor efetivo mínimo é 2, correspondendo à confirmação de duas barras usada pelos fractais de Bill Williams.
CandleType
Série de dados que alimenta os cálculos.
O padrão é o período de um minuto; altere para trabalhar com outras resoluções.
Volume
Propriedade padrão da estratégia StockSharp que define o tamanho da operação.
Substitui o dimensionamento baseado em risco original do expert MetaTrader.
Gestão de posições e diferenças em relação ao MQL
As posições são sempre zeradas antes de reverter a direção, exatamente como o helper ClosePositions de origem fazia.
O expert original chamava RefreshRates() e realizava configurações explícitas de slippage. Esses aspectos são delegados à infraestrutura do StockSharp neste port.
Ordens de stop-loss e take-profit não faziam parte da lógica MQL e permanecem ausentes aqui.
DistancePips usa precisão inteira como a entrada ushort, enquanto SignalBar espelha a entrada uchar do MQL.
Como o StockSharp trabalha com posições líquidas, abrir uma ordem na direção oposta vira automaticamente a exposição, correspondendo ao comportamento de netting do MetaTrader.
Dicas de uso
Comece com o mesmo deslocamento de barra de sinal (SignalBar = 3) do código original e calibre o limiar de distância de acordo com a volatilidade do instrumento.
Aumente SignalBar para aguardar mais candles após o aparecimento de um fractal, o que pode filtrar oscilações rápidas.
Combine com gerenciamento de risco externo, como o helper integrado StartProtection(), se um stop de proteção for necessário.
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()