Esta estrategia es una versión StockSharp de alto nivel del Expert Advisor de MetaTrader "WOC.0.1.2". Escucha actualizaciones de mejor bid/ask de Nivel 1 y busca rachas de precio rápidas en el lado del ask. Cuando el precio del ask imprime un número configurable de ticks consecutivamente más altos o más bajos dentro de una ventana de tiempo limitada, la estrategia abre una posición de mercado en la dirección de la ruptura. Solo puede haber una posición abierta en cualquier momento, lo que refleja el comportamiento de posición única del código original.
Datos y ejecución
Datos de mercado: Mejor bid y mejor ask de Nivel 1. El algoritmo no requiere velas ni indicadores.
Ejecución: Órdenes de mercado. Las salidas de protección se emulan dentro de la estrategia comprobando las actualizaciones de bid/ask.
Lógica de señal
Rastrear el último precio del ask y medir cuántos nuevos máximos consecutivos (racha alcista) o nuevos mínimos (racha bajista) han sido impresos.
Cuando una racha alcista o bajista alcanza SequenceLength, comprobar que la duración de la racha es menor o igual a SequenceTimeoutSeconds segundos.
Si la racha bajista es más larga que la alcista, enviar una orden de venta; de lo contrario, enviar una orden de compra. La verificación reproduce la lógica original de MetaTrader donde la racha con el contador más alto define la dirección.
Restablecer todos los contadores de rachas después de cada intento de entrada para asegurarse de que la siguiente señal comience desde cero.
Gestión de posición
Stop inicial: Después de una entrada, la estrategia registra inmediatamente un precio de stop-loss que está StopLossTicks pasos de precio alejado del bid actual (para largos) o del ask (para cortos).
Stop móvil: Cuando el precio se mueve a favor del trade más de TrailingStopTicks pasos de precio, el stop se ajusta a TrailingStopTicks detrás del último bid/ask, siempre que el stop permanezca al menos el doble de la distancia de trailing alejado del precio actual. Esto reproduce la condición de trailing de dos pasos del Expert MQL.
Ejecución de salida: Cuando el bid/ask rastreado cruza el nivel de stop almacenado, la posición se cierra mediante una orden de mercado. Después de la salida, el estado interno se restablece para aceptar nuevas rachas.
Gestión de volumen
Se admiten dos modos de dimensionamiento de posición:
Lote fijo: Usar el parámetro LotSize como volumen de orden absoluto.
Lotes automáticos: Habilitar UseAutoLotSizing para mapear el saldo de la cuenta a niveles de volumen. El saldo se toma de Portfolio.CurrentValue y recurre a Portfolio.BeginValue si el valor actual no está disponible.
Saldo (mayor que)
Volumen
0 (predeterminado)
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
Parámetros
StopLossTicks – distancia del stop-loss medida en pasos de precio.
TrailingStopTicks – distancia de trailing medida en pasos de precio (puede ser cero para deshabilitar el trailing).
SequenceLength – número de movimientos consecutivos del ask requeridos antes de entrar en un trade.
SequenceTimeoutSeconds – duración máxima de la racha en segundos.
LotSize – tamaño de orden fijo usado cuando el dimensionamiento automático está deshabilitado.
UseAutoLotSizing – habilita la tabla de volumen basada en saldo mostrada arriba.
Notas de uso
Funciona mejor en instrumentos rápidos donde el mejor ask se actualiza con frecuencia; considere probar con feeds de datos a nivel de tick.
La estrategia requiere cuentas de cobertura porque nunca mantiene posiciones opuestas simultáneamente.
Asegúrese de que Security.PriceStep esté configurado; de lo contrario, los cálculos de stop-loss y trailing recurren a una distancia de 1 unidad monetaria por tick.
Solo se admite una posición abierta a la vez, reflejando el comportamiento MQL original.
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()