Esta estratégia é uma versão StockSharp de alto nível do Expert Advisor do MetaTrader "WOC.0.1.2". Ela ouve atualizações de melhor bid/ask de Nível 1 e procura por séries rápidas de preço no lado do ask. Quando o preço do ask imprime um número configurável de ticks consecutivamente mais altos ou mais baixos dentro de uma janela de tempo limitada, a estratégia abre uma posição de mercado na direção do rompimento. Apenas uma posição pode estar aberta a qualquer momento, o que espelha o comportamento de posição única do código original.
Dados e execução
Dados de mercado: Melhor bid e melhor ask de Nível 1. O algoritmo não requer velas ou indicadores.
Execução: Ordens a mercado. As saídas de proteção são emuladas dentro da estratégia verificando as atualizações de bid/ask.
Lógica de sinal
Rastrear o último preço do ask e medir quantos novos máximos consecutivos (série de alta) ou novos mínimos (série de baixa) foram impressos.
Quando uma série de alta ou baixa atinge SequenceLength, verificar se a duração da série é menor ou igual a SequenceTimeoutSeconds segundos.
Se a série de baixa for mais longa que a de alta, enviar uma ordem de venda; caso contrário, enviar uma ordem de compra. A verificação reproduz a lógica original do MetaTrader onde a série com o contador mais alto define a direção.
Redefinir todos os contadores de série após cada tentativa de entrada para garantir que o próximo sinal comece do zero.
Gerenciamento de posição
Stop inicial: Após uma entrada, a estratégia registra imediatamente um preço de stop-loss que está StopLossTicks passos de preço afastado do bid atual (para comprados) ou do ask (para vendidos).
Stop móvel: Quando o preço se move a favor do trade mais de TrailingStopTicks passos de preço, o stop é ajustado para TrailingStopTicks atrás do último bid/ask, desde que o stop permaneça pelo menos o dobro da distância de trailing afastado do preço atual. Isso reproduz a condição de trailing de dois passos do Expert MQL.
Execução de saída: Quando o bid/ask rastreado cruza o nível de stop armazenado, a posição é fechada via uma ordem a mercado. Após a saída, o estado interno é redefinido para aceitar novas séries.
Gerenciamento de volume
Dois modos de dimensionamento de posição são suportados:
Lote fixo: Usar o parâmetro LotSize como volume de ordem absoluto.
Lotes automáticos: Habilitar UseAutoLotSizing para mapear o saldo da conta para níveis de volume. O saldo é obtido de Portfolio.CurrentValue e recorre a Portfolio.BeginValue se o valor atual não estiver disponível.
Saldo (maior que)
Volume
0 (padrão)
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 – distância do stop-loss medida em passos de preço.
TrailingStopTicks – distância de trailing medida em passos de preço (pode ser zero para desabilitar o trailing).
SequenceLength – número de movimentos consecutivos do ask necessários antes de entrar em um trade.
SequenceTimeoutSeconds – duração máxima da série em segundos.
LotSize – tamanho de ordem fixo usado quando o dimensionamento automático está desabilitado.
UseAutoLotSizing – habilita a tabela de volume baseada em saldo mostrada acima.
Notas de uso
Funciona melhor em instrumentos rápidos onde o melhor ask se atualiza frequentemente; considere testar em feeds de dados em nível de tick.
A estratégia requer contas de hedge porque nunca mantém posições opostas simultaneamente.
Certifique-se de que Security.PriceStep está configurado; caso contrário, os cálculos de stop-loss e trailing recorrem a uma distância de 1 unidade monetária por tick.
Apenas uma posição aberta é suportada por vez, espelhando o comportamento 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()