Стратегия переносит файлы MetaTrader 4 из каталога MQL/8550 (индикатор Pivots и эксперта Pivots_test) в высокоуровневый API Strategy платформы StockSharp. Реализовано исходное поведение: вычисление дневных уровней классических пивотов, поддержание пары противоположных отложенных заявок на центральном уровне и сопровождение каждой позиции фиксированными стоп- и тейк-профитами с трейлинг-стопом.
Расчёт пивотов
Стратегия подписывается на настраиваемый таймфрейм (PivotCandleType, по умолчанию дневные свечи).
После завершения свечи рассчитываются уровни по формулам классических floor-pivot:
Новые уровни активируются с открытия следующей торговой сессии. В этот момент значения выводятся в лог через AddInfoLog (пример: Pivot levels for 2024-04-05: P=1.0924, R1=1.0956, …).
Работа с отложенными заявками
После активации уровней стратегия гарантирует присутствие двух заявок на цене Pivot:
Buy Limit по цене Pivot с защитой SellStop на S2 (стоп-лосс) и SellLimit на R2 (тейк-профит).
Sell Stop по цене Pivot с защитой BuyStop на R2 и BuyLimit на S2.
Регистрация ведётся только через высокоуровневые методы BuyLimit, SellStop, SellLimit, BuyStop. При исполнении заявки пересчитывается средняя цена входа по направлению, отменяются старые защитные ордера и выставляется новая пара стоп/тейк на весь текущий объём – полностью повторяя логику MetaTrader, где каждая позиция использует уровни S2/R2. При срабатывании стопа или тейка вспомогательные ордера очищаются автоматически.
StockSharp работает с нетто-позицией, поэтому встречные сделки взаимоудаляются (в MT4 с хеджингом тикеты существовали отдельно). Это единственное осознанное отличие от оригинального эксперта.
Трейлинг-стоп
Параметр TrailingStopPoints задаёт расстояние в пунктах (умножается на PriceStep).
Для лонга трейлинг активируется, когда цена ушла выше средней входной цены больше чем на указанное расстояние, после чего SellStop подтягивается ближе к рынку.
Для шорта выполняется зеркальная логика с BuyStop.
Обновления трейлинга происходят на основе внутридневного таймфрейма CandleType (по умолчанию 15-минутные свечи).
Параметры
Параметр
Описание
Значение по умолчанию
OrderVolume
Объём каждой отложенной заявки (лоты/контракты).
0.1
TrailingStopPoints
Размер трейлинг-стопа в пунктах (0 отключает механизм).
30
CandleType
Внутридневные свечи для трейлинга и контроля сессии.
Таймфрейм 15 минут
PivotCandleType
Таймфрейм для расчёта пивотов.
Таймфрейм 1 день
LogPivotUpdates
Логировать ли обновления уровней.
true
Все числовые настройки созданы через StrategyParam<T>, поэтому их можно оптимизировать средствами StockSharp.
Логи и диагностика
Обновления уровней выводятся методом AddInfoLog, заменяя комментарии и подписи MT4.
Управление позициями и ордерами реализовано исключительно высокоуровневыми вызовами без прямой работы с буферами или низкоуровневой регистрацией заявок.
Использование
Подключите стратегию к источнику данных, предоставляющему дневные и внутридневные свечи по инструменту.
При необходимости уточните шаг цены инструмента (PriceStep определяется автоматически, резервное значение – 0.0001).
Настройте OrderVolume, TrailingStopPoints и типы свечей в соответствии с исходной конфигурацией MT4.
По запросу Python-версия не создавалась.
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()