Fractals Mindestabstand repliziert den MetaTrader Expert Advisor "Fractals minimum distance" mit der High-Level-Strategie-API von StockSharp. Das System durchsucht die konfigurierte Kerzenserie nach Fünf-Balken-Fraktalmustern im Stil von Bill Williams. Jedes Mal, wenn ein neues bestätigtes Fraktal beim angegebenen Signalbalken-Offset erscheint, misst die Strategie den Abstand zwischen den zuletzt aufgetretenen Auf- und Abwärtsfraktalen. Ein Marktauftrag ist nur erlaubt, wenn dieser Abstand den erforderlichen Schwellenwert in Pips überschreitet.
Die Konvertierung behält das ursprüngliche Verhalten bei, jegliche entgegengesetzte Exposition sofort vor der Umkehrung zu schließen. Im Gegensatz zur MQL-Version wird die Positionsgröße aus der Volume-Eigenschaft der Strategie entnommen, anstatt kontobasierte Risikoberechnungen durchzuführen. Es werden keine Stop-Loss- oder Take-Profit-Aufträge gesendet, was dem Quell-Expert entspricht.
Signallogik
Den durch CandleType definierten Kerzentyp abonnieren und rollierende Puffer von Hochs und Tiefs erstellen, die immer die SignalBar Kerzen in der Vergangenheit liegende Kerze sowie zwei Nachbarn auf jeder Seite enthalten.
Ein oberes Fraktal erkennen, wenn das Hoch der Mittelkerze strikt größer ist als die Hochs der zwei vorausgehenden und der zwei folgenden Kerzen. Ein unteres Fraktal analog für Tiefs erkennen.
Den Parameter DistancePips mithilfe des PriceStep des Symbols in eine Preisdistanz umrechnen. Symbole mit drei oder fünf Dezimalstellen werden automatisch angepasst, um 0.001/0.00001-Notierungen als einen Pip zu behandeln.
Wenn ein oberes Fraktal bestätigt wird:
Das neue obere Niveau speichern und bestehende Long-Positionen schließen.
Wenn sowohl das letzte obere als auch das untere Fraktal bekannt sind und ihre absolute Differenz mindestens dem Distanzschwellenwert entspricht, einen Marktverkaufsauftrag mit Volume einreichen.
Wenn ein unteres Fraktal bestätigt wird:
Das neue untere Niveau speichern und bestehende Short-Positionen schließen.
Wenn die Distanzbedingung erfüllt ist, einen Marktkaufauftrag mit Volume einreichen.
Trades werden nur nach dem Schließen der Kerze platziert, die das Fraktal abschließt, um sicherzustellen, dass unfertige Kerzen niemals Einstiege auslösen. Die Strategie basiert auf IsFormedAndOnlineAndAllowTrading(), um das Platzieren von Aufträgen zu vermeiden, bevor die Umgebung bereit ist.
Parameter
Name
Beschreibung
Hinweise
DistancePips
Mindestabstand zwischen dem letzten Auf- und Abwärtsfraktal in Pips gemessen.
Wird intern mithilfe der Tick-Größe des Instruments in Preiseinheiten umgerechnet.
SignalBar
Anzahl der vollständig geschlossenen Kerzen, die nach der das Fraktal enthaltenden Kerze vergehen müssen.
Effektiver Mindestwert ist 2, entsprechend der Zwei-Kerzen-Bestätigung der Bill Williams Fraktale.
CandleType
Datenserie, die die Berechnungen speist.
Standard ist der Ein-Minuten-Zeitrahmen; ändern Sie dies, um mit anderen Auflösungen zu arbeiten.
Volume
Standardmäßige StockSharp-Strategieeigenschaft zur Definition der Handelsgröße.
Ersetzt die ursprüngliche risikobasierte Dimensionierung des MetaTrader-Experten.
Positionsverwaltung und Unterschiede zu MQL
Positionen werden immer vor der Richtungsumkehrung abgebaut, genau wie es der Quell-ClosePositions-Helfer tat.
Der ursprüngliche Experte rief RefreshRates() auf und führte explizite Slippage-Einstellungen durch. Diese Aspekte werden in diesem Port an die StockSharp-Infrastruktur delegiert.
Stop-Loss- und Take-Profit-Aufträge waren nicht Teil der MQL-Logik und fehlen hier weiterhin.
DistancePips verwendet ganzzahlige Präzision wie der ushort-Input, während SignalBar den MQL-uchar-Input widerspiegelt.
Da StockSharp mit Netto-Positionen arbeitet, kehrt das Öffnen einer Order in entgegengesetzter Richtung automatisch die Exposition um, was dem MetaTrader-Netting-Verhalten entspricht.
Verwendungstipps
Beginnen Sie mit demselben Signalbalken-Offset (SignalBar = 3) aus dem Originalcode und kalibrieren Sie den Distanzschwellenwert entsprechend der Volatilität des Instruments.
Erhöhen Sie SignalBar, um mehr Kerzen nach dem Erscheinen eines Fraktals zu warten, was schnelle Schwankungen herausfiltern kann.
Kombinieren Sie mit externem Risikomanagement wie dem integrierten StartProtection()-Helfer, wenn ein Schutz-Stop erforderlich ist.
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()