Esta estratégia transporta os MetaTrader 4 arquivos localizados em MQL/8550 (o indicador Pivots e o consultor especialista Pivots_test que o acompanha) para o Strategy API de alto nível de StockSharp. Ele mantém o comportamento original de calcular os níveis diários de pivô mínimo, organizando um par de ordens pendentes opostas no pivô central e gerenciando cada posição resultante com um stop-loss, take-profit e stop móvel fixos.
Cálculo dinâmico
A estratégia assina um período de pivô configurável (PivotCandleType, diariamente por padrão).
Sempre que uma vela desse período termina, ela deriva os níveis clássicos de pivô dos preços OHLC do dia anterior:
R3 = 2 × Pivot + High − 2 × Low e S3 = 2 × Pivot − (2 × High − Low)
Os níveis ficam ativos no início da próxima sessão. Quando isso acontece a estratégia registra os valores através de AddInfoLog (por exemplo: Pivot levels for 2024-04-05: P=1.0924, R1=1.0956, …).
Fluxo de trabalho de pedidos pendentes
Uma vez ativos os níveis de pivô, a estratégia garante continuamente a existência de duas ordens pendentes ao preço de pivô:
Limite de compra @ Pivot com proteção pós-preenchimento SellStop (stop-loss) em S2 e SellLimit (take-profit) em R2.
Sell Stop @ Pivot com proteção pós-preenchimento BuyStop em R2 e BuyLimit em S2.
Todos os pedidos são enviados por meio dos métodos auxiliares de alto nível BuyLimit, SellStop, SellLimit e BuyStop. Se uma ordem for preenchida, o código recalcula o preço médio de entrada para aquela direção, cancela as ordens de proteção existentes e envia um novo par stop/limit que cobre todo o volume aberto (espelhando o comportamento MetaTrader onde cada posição herda a mesma proteção S2/R2). Se o stop de proteção ou o take-profit forem executados, os auxiliares relacionados serão compensados automaticamente.
A estratégia usa uma única posição líquida, portanto, preenchimentos opostos se compensarão (ao contrário da cobertura baseada em tickets de MetaTrader). Este é o único desvio intencional do perito original.
Lógica de parada móvel
TrailingStopPoints define a distância em pontos indicadores (multiplicada pelo instrumento PriceStep).
Para posições longas, o trailing stop é ativado quando o preço se move mais do que aquela distância acima da entrada média. O protetor SellStop é então movido para mais perto do mercado.
Para posições curtas, a lógica de espelho se aplica, diminuindo o BuyStop à medida que o preço se move favoravelmente.
As atualizações finais são orientadas pela série intradiária selecionada por meio de CandleType (velas de 15 minutos por padrão).
Parâmetros
Parâmetro
Descrição
Padrão
OrderVolume
Volume de cada ordem pendente (lotes/contratos).
0.1
TrailingStopPoints
Distância de parada final em pontos. 0 desativa a lógica final.
30
CandleType
Série de velas intradiárias usadas para rastrear e manter a programação da sessão.
15m prazo
PivotCandleType
Período usado para calcular os níveis de pivô diários.
1D prazo
LogPivotUpdates
Quando true, os níveis pivô são gravados no registro de estratégia sempre que mudam.
true
Todos os parâmetros numéricos são expostos por meio de StrategyParam<T> para que possam ser otimizados dentro da infraestrutura StockSharp.
Registro e diagnóstico
As atualizações dinâmicas são roteadas por meio de AddInfoLog, que substitui a saída MetaTrader Comment/ObjectSetText.
O gerenciamento de ordens de proteção, o tratamento de posições e a lógica de rastreamento dependem exclusivamente dos ajudantes de alto nível de StockSharp; nenhum registro de pedido de baixo nível ou buffers de indicador são usados.
Notas de uso
Anexe a estratégia a um conector que forneça velas diárias e intradiárias para o título escolhido.
Ajuste a etapa do instrumento, se necessário (PriceStep é detectado automaticamente; o substituto é 0.0001).
Opcionalmente, ajuste OrderVolume, TrailingStopPoints ou os tipos de velas para corresponder à configuração original do MT4.
Nenhuma versão do Python é fornecida para esta porta conforme solicitado.
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>
/// Strategy that calculates classic floor pivot levels from daily candles and trades
/// breakouts around the central pivot. Goes long on close above pivot, short on close below.
/// Uses S2/R2 as stop/target levels.
/// </summary>
public class PivotsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private decimal _pivotLevel;
private decimal _r1, _r2, _s1, _s2;
private decimal? _previousClose;
private decimal? _entryPrice;
private bool _pivotReady;
private readonly List<decimal> _dailyHighs = new();
private readonly List<decimal> _dailyLows = new();
private readonly List<decimal> _dailyCloses = new();
private DateTime _currentDay;
private decimal _dayHigh;
private decimal _dayLow;
private decimal _dayClose;
public PivotsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal generation", "General");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_pivotLevel = 0m;
_r1 = _r2 = _s1 = _s2 = 0m;
_previousClose = null;
_entryPrice = null;
_pivotReady = false;
_dailyHighs.Clear();
_dailyLows.Clear();
_dailyCloses.Clear();
_currentDay = DateTime.MinValue;
_dayHigh = 0m;
_dayLow = decimal.MaxValue;
_dayClose = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var candleDay = candle.OpenTime.Date;
// Track daily OHLC
if (candleDay != _currentDay)
{
if (_currentDay != DateTime.MinValue && _dayHigh > 0m)
{
// Previous day completed, calculate pivots
var high = _dayHigh;
var low = _dayLow;
var close = _dayClose;
_pivotLevel = (high + low + close) / 3m;
_r1 = 2m * _pivotLevel - low;
_s1 = 2m * _pivotLevel - high;
_r2 = _pivotLevel + (high - low);
_s2 = _pivotLevel - (high - low);
_pivotReady = true;
}
_currentDay = candleDay;
_dayHigh = candle.HighPrice;
_dayLow = candle.LowPrice;
_dayClose = candle.ClosePrice;
}
else
{
if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
_dayClose = candle.ClosePrice;
}
if (!_pivotReady)
{
_previousClose = candle.ClosePrice;
return;
}
if (_previousClose is null)
{
_previousClose = candle.ClosePrice;
return;
}
// Manage open positions
if (Position > 0)
{
// Exit long at R2 (take profit) or S1 (stop loss)
if (candle.HighPrice >= _r2 || candle.LowPrice <= _s1)
{
SellMarket(Math.Abs(Position));
_entryPrice = null;
}
}
else if (Position < 0)
{
// Exit short at S2 (take profit) or R1 (stop loss)
if (candle.LowPrice <= _s2 || candle.HighPrice >= _r1)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
}
}
// Entry signals based on pivot cross
if (Position == 0)
{
var crossAbovePivot = _previousClose.Value <= _pivotLevel && candle.ClosePrice > _pivotLevel;
var crossBelowPivot = _previousClose.Value >= _pivotLevel && candle.ClosePrice < _pivotLevel;
if (crossAbovePivot)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (crossBelowPivot)
{
SellMarket();
_entryPrice = candle.ClosePrice;
}
}
_previousClose = candle.ClosePrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, DateTime
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
class pivots_strategy(Strategy):
def __init__(self):
super(pivots_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for signal generation", "General")
self._pivot_level = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._previous_close = None
self._entry_price = None
self._pivot_ready = False
self._current_day = None
self._day_high = 0.0
self._day_low = float('inf')
self._day_close = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(pivots_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = 2
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
candle_day = candle.OpenTime.Date
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._current_day is None or candle_day != self._current_day:
if self._current_day is not None and self._day_high > 0:
h = self._day_high
l = self._day_low
c = self._day_close
self._pivot_level = (h + l + c) / 3.0
self._r1 = 2.0 * self._pivot_level - l
self._s1 = 2.0 * self._pivot_level - h
self._r2 = self._pivot_level + (h - l)
self._s2 = self._pivot_level - (h - l)
self._pivot_ready = True
self._current_day = candle_day
self._day_high = high
self._day_low = low
self._day_close = close
else:
if high > self._day_high:
self._day_high = high
if low < self._day_low:
self._day_low = low
self._day_close = close
if not self._pivot_ready:
self._previous_close = close
return
if self._previous_close is None:
self._previous_close = close
return
if self.Position > 0:
if high >= self._r2 or low <= self._s1:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = None
elif self.Position < 0:
if low <= self._s2 or high >= self._r1:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = None
if self.Position == 0:
cross_above_pivot = self._previous_close <= self._pivot_level and close > self._pivot_level
cross_below_pivot = self._previous_close >= self._pivot_level and close < self._pivot_level
if cross_above_pivot:
self.BuyMarket()
self._entry_price = close
elif cross_below_pivot:
self.SellMarket()
self._entry_price = close
self._previous_close = close
def OnReseted(self):
super(pivots_strategy, self).OnReseted()
self._pivot_level = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._previous_close = None
self._entry_price = None
self._pivot_ready = False
self._current_day = None
self._day_high = 0.0
self._day_low = float('inf')
self._day_close = 0.0
def CreateClone(self):
return pivots_strategy()