Diese Strategie ist ein StockSharp High-Level-Port des MetaTrader Expert Advisors "WOC.0.1.2". Sie hört auf Level-1-Best-Bid/Ask-Updates und sucht nach schnellen Preisserien auf der Ask-Seite. Wenn der Ask-Preis eine konfigurierbare Anzahl aufeinanderfolgender höherer oder niedrigerer Ticks innerhalb eines begrenzten Zeitfensters druckt, öffnet die Strategie eine Marktposition in Ausbruchsrichtung. Nur eine Position kann zu einem Zeitpunkt offen sein, was das Einzelpositions-Verhalten des ursprünglichen Codes widerspiegelt.
Daten und Ausführung
Marktdaten: Level-1-Best-Bid und Best-Ask. Der Algorithmus benötigt keine Kerzen oder Indikatoren.
Ausführung: Marktorders. Schutzausstiege werden innerhalb der Strategie durch Überprüfung von Bid/Ask-Updates emuliert.
Signallogik
Den letzten Ask-Preis verfolgen und messen, wie viele aufeinanderfolgende neue Hochs (Up-Streak) oder neue Tiefs (Down-Streak) gedruckt wurden.
Wenn ein Up-Streak oder Down-Streak SequenceLength erreicht, prüfen, dass die Streak-Dauer kleiner oder gleich SequenceTimeoutSeconds Sekunden ist.
Wenn der Down-Streak länger als der Up-Streak ist, eine Verkaufsorder senden; sonst eine Kauforder. Die Prüfung reproduziert die ursprüngliche MetaTrader-Logik, bei der der Streak mit dem höchsten Zähler die Richtung definiert.
Alle Streak-Zähler nach jedem Eintrittversuch zurücksetzen, um sicherzustellen, dass das nächste Signal von vorne beginnt.
Positionsmanagement
Initialer Stop: Nach einem Einstieg zeichnet die Strategie sofort einen Stop-Loss-Preis auf, der StopLossTicks Preisschritte vom aktuellen Bid (für Longs) oder Ask (für Shorts) entfernt ist.
Trailing Stop: Wenn sich der Preis um mehr als TrailingStopTicks Preisschritte zugunsten des Trades bewegt, wird der Stop auf TrailingStopTicks hinter dem letzten Bid/Ask gestrafft, solange der Stop mindestens doppelt so weit wie der Trailing-Abstand vom aktuellen Preis entfernt bleibt. Dies reproduziert die zweistufige Trailing-Bedingung aus dem MQL-Expert.
Exit-Ausführung: Wenn das verfolgte Bid/Ask das gespeicherte Stop-Level kreuzt, wird die Position über eine Marktorder geschlossen. Nach dem Exit wird der interne Zustand zurückgesetzt, um neue Streaks zu akzeptieren.
Volumenverwaltung
Zwei Positionsgrößen-Modi werden unterstützt:
Festes Los: Den Parameter LotSize als absolutes Ordervolumen verwenden.
Auto-Lots: UseAutoLotSizing aktivieren, um das Kontosaldo auf Volumentiers zu mappen. Das Saldo wird aus Portfolio.CurrentValue entnommen und fällt auf Portfolio.BeginValue zurück, wenn der aktuelle Wert nicht verfügbar ist.
Saldo (größer als)
Volumen
0 (Standard)
LotSize
200
0.04
300
0.05
400
0.06
500
0.07
600
0.08
700
0.09
800
0.10
900
0.20
1 000
0.30
2 000
0.40
3 000
0.50
4 000
0.60
5 000
0.70
6 000
0.80
7 000
0.90
8 000
1.00
9 000
2.00
10 000
3.00
11 000
4.00
12 000
5.00
13 000
6.00
14 000
7.00
15 000
8.00
20 000
9.00
30 000
10.00
40 000
11.00
50 000
12.00
60 000
13.00
70 000
14.00
80 000
15.00
90 000
16.00
100 000
17.00
110 000
18.00
120 000
19.00
130 000
20.00
Parameter
StopLossTicks – Stop-Loss-Abstand gemessen in Preisschritten.
TrailingStopTicks – Trailing-Abstand gemessen in Preisschritten (kann null sein, um Trailing zu deaktivieren).
SequenceLength – Anzahl der aufeinanderfolgenden Ask-Bewegungen, die vor dem Einstieg in einen Trade erforderlich sind.
SequenceTimeoutSeconds – maximale Dauer des Streaks in Sekunden.
LotSize – feste Ordergröße, die verwendet wird, wenn automatische Losgröße deaktiviert ist.
UseAutoLotSizing – aktiviert die oben gezeigte saldobasierte Volumentabelle.
Verwendungshinweise
Funktioniert am besten bei schnellen Instrumenten, bei denen der Best-Ask häufig aktualisiert wird; erwägen Sie das Testen auf Tick-Level-Datenfeeds.
Die Strategie erfordert Hedging-Konten, da sie niemals gleichzeitig entgegengesetzte Positionen hält.
Stellen Sie sicher, dass Security.PriceStep konfiguriert ist; sonst fallen Stop-Loss- und Trailing-Berechnungen auf einen Abstand von 1 Währungseinheit pro Tick zurück.
Es wird nur eine offene Position gleichzeitig unterstützt, was das ursprüngliche MQL-Verhalten widerspiegelt.
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>
/// Momentum strategy based on WOC 0.1.2 concept.
/// Detects consecutive candle close runs in one direction and enters on breakout.
/// Uses ATR-based stop loss and trailing stop.
/// </summary>
public class Woc012Strategy : Strategy
{
private readonly StrategyParam<int> _sequenceLength;
private readonly StrategyParam<decimal> _stopLossAtrMult;
private readonly StrategyParam<decimal> _trailingAtrMult;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private int _upCount;
private int _downCount;
private decimal _entryPrice;
private decimal? _stopPrice;
public int SequenceLength { get => _sequenceLength.Value; set => _sequenceLength.Value = value; }
public decimal StopLossAtrMult { get => _stopLossAtrMult.Value; set => _stopLossAtrMult.Value = value; }
public decimal TrailingAtrMult { get => _trailingAtrMult.Value; set => _trailingAtrMult.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Woc012Strategy()
{
_sequenceLength = Param(nameof(SequenceLength), 6)
.SetGreaterThanZero()
.SetDisplay("Sequence Length", "Consecutive bars in same direction to trigger entry", "Signals");
_stopLossAtrMult = Param(nameof(StopLossAtrMult), 1.5m)
.SetGreaterThanZero()
.SetDisplay("SL ATR Mult", "Stop loss as ATR multiple", "Risk");
_trailingAtrMult = Param(nameof(TrailingAtrMult), 1.0m)
.SetGreaterThanZero()
.SetDisplay("Trail ATR Mult", "Trailing stop as ATR multiple", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR calculation length", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_upCount = 0;
_downCount = 0;
_entryPrice = 0;
_stopPrice = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
// Track consecutive direction
if (_prevClose > 0)
{
if (close > _prevClose)
{
_upCount++;
_downCount = 0;
}
else if (close < _prevClose)
{
_downCount++;
_upCount = 0;
}
else
{
_upCount = 0;
_downCount = 0;
}
}
_prevClose = close;
// Manage existing position
if (Position != 0)
{
if (Position > 0)
{
// Trail up
var trail = close - TrailingAtrMult * atr;
if (_stopPrice == null || trail > _stopPrice)
_stopPrice = trail;
if (close <= _stopPrice)
{
SellMarket(Math.Abs(Position));
_stopPrice = null;
_entryPrice = 0;
return;
}
}
else
{
// Trail down
var trail = close + TrailingAtrMult * atr;
if (_stopPrice == null || trail < _stopPrice)
_stopPrice = trail;
if (close >= _stopPrice)
{
BuyMarket(Math.Abs(Position));
_stopPrice = null;
_entryPrice = 0;
return;
}
}
}
// Entry: consecutive sequence completed
if (_upCount >= SequenceLength && Position <= 0)
{
var vol = Volume + Math.Abs(Position);
BuyMarket(vol);
_entryPrice = close;
_stopPrice = close - StopLossAtrMult * atr;
_upCount = 0;
}
else if (_downCount >= SequenceLength && Position >= 0)
{
var vol = Volume + Math.Abs(Position);
SellMarket(vol);
_entryPrice = close;
_stopPrice = close + StopLossAtrMult * atr;
_downCount = 0;
}
}
}
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
from StockSharp.Algo.Indicators import AverageTrueRange
class woc012_strategy(Strategy):
def __init__(self):
super(woc012_strategy, self).__init__()
self._sequence_length = self.Param("SequenceLength", 6) \
.SetGreaterThanZero() \
.SetDisplay("Sequence Length", "Consecutive bars in same direction to trigger entry", "Signals")
self._stop_loss_atr_mult = self.Param("StopLossAtrMult", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("SL ATR Mult", "Stop loss as ATR multiple", "Risk")
self._trailing_atr_mult = self.Param("TrailingAtrMult", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Trail ATR Mult", "Trailing stop as ATR multiple", "Risk")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "ATR calculation length", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._up_count = 0
self._down_count = 0
self._entry_price = 0.0
self._stop_price = None
@property
def SequenceLength(self):
return self._sequence_length.Value
@SequenceLength.setter
def SequenceLength(self, value):
self._sequence_length.Value = value
@property
def StopLossAtrMult(self):
return self._stop_loss_atr_mult.Value
@StopLossAtrMult.setter
def StopLossAtrMult(self, value):
self._stop_loss_atr_mult.Value = value
@property
def TrailingAtrMult(self):
return self._trailing_atr_mult.Value
@TrailingAtrMult.setter
def TrailingAtrMult(self, value):
self._trailing_atr_mult.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(woc012_strategy, self).OnStarted2(time)
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(atr, self.process_candle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
atr = float(atr_val)
if self._prev_close > 0:
if close > self._prev_close:
self._up_count += 1
self._down_count = 0
elif close < self._prev_close:
self._down_count += 1
self._up_count = 0
else:
self._up_count = 0
self._down_count = 0
self._prev_close = close
if self.Position != 0:
if self.Position > 0:
trail = close - float(self.TrailingAtrMult) * atr
if self._stop_price is None or trail > self._stop_price:
self._stop_price = trail
if close <= self._stop_price:
self.SellMarket(abs(self.Position))
self._stop_price = None
self._entry_price = 0.0
return
else:
trail = close + float(self.TrailingAtrMult) * atr
if self._stop_price is None or trail < self._stop_price:
self._stop_price = trail
if close >= self._stop_price:
self.BuyMarket(abs(self.Position))
self._stop_price = None
self._entry_price = 0.0
return
if self._up_count >= self.SequenceLength and self.Position <= 0:
vol = self.Volume + abs(self.Position)
self.BuyMarket(vol)
self._entry_price = close
self._stop_price = close - float(self.StopLossAtrMult) * atr
self._up_count = 0
elif self._down_count >= self.SequenceLength and self.Position >= 0:
vol = self.Volume + abs(self.Position)
self.SellMarket(vol)
self._entry_price = close
self._stop_price = close + float(self.StopLossAtrMult) * atr
self._down_count = 0
def OnReseted(self):
super(woc012_strategy, self).OnReseted()
self._prev_close = 0.0
self._up_count = 0
self._down_count = 0
self._entry_price = 0.0
self._stop_price = None
def CreateClone(self):
return woc012_strategy()