Esta estrategia transfiere los MetaTrader 4 archivos ubicados en MQL/8550 (el indicador Pivotes y el asesor experto Pivots_test que lo acompaña) al Strategy API de alto nivel de StockSharp. Mantiene el comportamiento original de calcular los niveles diarios de pivote de piso, organizar un par de órdenes pendientes opuestas en el pivote central y administrar cada posición resultante con un stop-loss fijo, una toma de ganancias y un stop dinámico.
Cálculo de pivote
La estrategia se suscribe a un plazo de pivote configurable (PivotCandleType, diario de forma predeterminada).
Cada vez que finaliza una vela de ese período de tiempo, deriva los niveles clásicos de pivote de piso de los precios OHLC del día anterior:
R3 = 2 × Pivot + High − 2 × Low y S3 = 2 × Pivot − (2 × High − Low)
Los niveles se activan al comienzo de la siguiente sesión. Cuando esto sucede, la estrategia registra los valores a través de AddInfoLog (por ejemplo: Pivot levels for 2024-04-05: P=1.0924, R1=1.0956, …).
Flujo de trabajo de pedidos pendientes
Una vez que los niveles de pivote están activos, la estrategia garantiza continuamente que existan dos órdenes pendientes al precio de pivote:
Límite de compra @ Pivot con protección posterior al llenado SellStop (stop-loss) en S2 y SellLimit (take-profit) en R2.
Sell Stop @ Pivot con protección posterior al llenado BuyStop en R2 y BuyLimit en S2.
Todos los pedidos se envían a través de los métodos auxiliares de alto nivel BuyLimit, SellStop, SellLimit y BuyStop. Si se ejecuta una orden, el código recalcula el precio de entrada promedio para esa dirección, cancela las órdenes de protección existentes y envía un nuevo par stop/límite que cubre todo el volumen abierto (reflejando el comportamiento MetaTrader donde cada posición hereda la misma protección S2/R2). Si se ejecuta la parada de protección o la toma de ganancias, los ayudantes relacionados se borran automáticamente.
La estrategia utiliza una única posición neta, por lo que los rellenos opuestos se compensarán entre sí (a diferencia de la cobertura basada en tickets de MetaTrader). Ésta es la única desviación intencionada del experto original.
Lógica de parada dinámica
TrailingStopPoints define la distancia en puntos indicadores (multiplicada por el instrumento PriceStep).
Para posiciones largas, el trailing stop se activa una vez que el precio se ha movido más de esa distancia por encima de la entrada promedio. Luego, el SellStop protector se acerca al mercado.
Para posiciones cortas se aplica la lógica espejo, bajando el BuyStop a medida que el precio se mueve favorablemente.
Las actualizaciones finales son impulsadas por la serie intradiaria seleccionada hasta CandleType (velas de 15 minutos de forma predeterminada).
Parámetros
Parámetro
Descripción
Predeterminado
OrderVolume
Volumen de cada orden pendiente (lotes/contratos).
0.1
TrailingStopPoints
Distancia del trailing stop en puntos. 0 desactiva la lógica de seguimiento.
30
CandleType
Serie de velas intradiarias utilizadas para seguir y mantener el cronograma de la sesión.
15m período de tiempo
PivotCandleType
Marco de tiempo utilizado para calcular los niveles de pivote diarios.
1D período de tiempo
LogPivotUpdates
Cuando true, los niveles de pivote se escriben en el registro de estrategia cada vez que cambian.
true
Todos los parámetros numéricos están expuestos a través de StrategyParam<T> para que puedan optimizarse dentro de la infraestructura StockSharp.
Registro y diagnóstico
Las actualizaciones dinámicas se enrutan a través de AddInfoLog, que reemplaza la salida MetaTrader Comment/ObjectSetText.
La gestión de órdenes de protección, el manejo de posiciones y la lógica de seguimiento dependen únicamente de los ayudantes de alto nivel de StockSharp; no se utilizan registros de órdenes de bajo nivel ni buffers de indicadores.
Notas de uso
Adjunte la estrategia a un conector que proporcione velas tanto diarias como intradiarias para el valor elegido.
Ajuste el paso del instrumento si es necesario (PriceStep se detecta automáticamente; el respaldo es 0.0001).
Opcionalmente, ajuste OrderVolume, TrailingStopPoints o los tipos de velas para que coincidan con la configuración MT4 original.
No se proporciona ninguna versión de Python para este puerto según lo 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()