Стратегия представляет собой высокоуровневый порт советника MetaTrader 4 из файла MQL/7610/Simplepivot_www_forex-instruments_info.mq4. В оригинале на каждом новом баре берётся его цена открытия и сравнивается с диапазоном предыдущей свечи, после чего советник открывает или переворачивает позицию. В версии для StockSharp та же логика реализована с помощью высокоуровневых вызовов SubscribeCandles, Bind, BuyMarket, SellMarket и ClosePosition.
Пошаговая схема работы:
Ожидание закрытия свечи, чтобы получить её open/high/low.
Вычисление пивота как середины диапазона предыдущего бара.
Если новый бар открылся в нижней половине диапазона либо сделал разрыв вверх, открывается длинная позиция.
Если открытие находится в верхней половине предыдущего диапазона, открывается короткая позиция.
Перед сменой направления текущая позиция закрывается — тем самым сохраняется логика одного тикета, как и в MQL.
В оригинальном советнике нет стоп-лоссов и тейк-профитов, поэтому позиция разворачивается только на следующем сигнале.
Параметры
Имя
Значение по умолчанию
Описание
OrderVolume
1
Объём рыночного ордера при входе.
CandleType
Таймфрейм 1 минута
Тип свечей, которые запрашиваются у источника данных.
Детали торговой логики
Первая полностью сформированная свеча сохраняется и используется как эталон. Пока нет предыдущей свечи, стратегия не торгует.
Если Open < previousHigh и одновременно Open > pivot, выбирается короткое направление.
Во всех остальных случаях выбирается длинное направление (нижняя половина диапазона, равенство пивоту, гэпы вверх/вниз).
Если уже открыта позиция в нужную сторону, сигнал пропускается, чтобы не платить спрэд повторно. Это соответствует проверке prevTradeOp в MQL.
При смене направления вызывается ClosePosition(), после чего отправляется новый рыночный ордер объёмом OrderVolume.
Диапазон предыдущей свечи обновляется значениями текущего бара и используется в следующей итерации.
Управление риском
Алгоритм не содержит защитных стопов и целей по прибыли. Управление риском сводится к настройке объёма OrderVolume или внешним мерам контроля со стороны портфеля/счёта.
Визуализация
При наличии области графика стратегия отображает свечи и собственные сделки, что облегчает проверку корректности разворотов по пивоту в тестах.
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simple pivot-based strategy converted from the MetaTrader expert advisor in MQL/7610.
/// The strategy compares the current candle open with the previous candle range to decide
/// whether the next trade should be long or short.
/// </summary>
public class SimplePivotFlipStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal _previousHigh;
private decimal _previousLow;
private bool _hasPreviousCandle;
/// <summary>
/// Order volume used for market entries.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Candle type used for price subscription.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="SimplePivotFlipStrategy"/> class.
/// </summary>
public SimplePivotFlipStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Market order volume used for entries.", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for pivot calculation.", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousHigh = 0m;
_previousLow = 0m;
_hasPreviousCandle = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPreviousCandle)
{
// Store the first completed candle to build the reference range.
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_hasPreviousCandle = true;
return;
}
// Calculate the pivot as the midpoint of the previous candle range.
var pivot = (_previousHigh + _previousLow) / 2m;
var desiredSide = Sides.Buy;
// If the new candle opens inside the upper half of the previous range we go short.
if (candle.OpenPrice < _previousHigh && candle.OpenPrice > pivot)
desiredSide = Sides.Sell;
// Skip re-entry if we already hold a position in the desired direction.
if ((desiredSide == Sides.Buy && Position > 0) || (desiredSide == Sides.Sell && Position < 0))
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
return;
}
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
if (desiredSide == Sides.Buy)
{
BuyMarket();
}
else
{
SellMarket();
}
// Update reference range for the next candle.
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
}
}
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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class simple_pivot_flip_strategy(Strategy):
"""Simple pivot-based strategy. Compares current candle open with the previous
candle range midpoint. If open is in the upper half of the previous range, sell;
otherwise buy. Closes existing position before reversing."""
def __init__(self):
super(simple_pivot_flip_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Market order volume used for entries", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1))) \
.SetDisplay("Candle Type", "Type of candles used for pivot calculation", "Data")
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous_candle = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def OrderVolume(self):
return self._order_volume.Value
def OnReseted(self):
super(simple_pivot_flip_strategy, self).OnReseted()
self._previous_high = 0.0
self._previous_low = 0.0
self._has_previous_candle = False
def OnStarted2(self, time):
super(simple_pivot_flip_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_price = float(candle.OpenPrice)
if not self._has_previous_candle:
self._previous_high = high
self._previous_low = low
self._has_previous_candle = True
return
# Calculate the pivot as the midpoint of the previous candle range
pivot = (self._previous_high + self._previous_low) / 2.0
# Default to buy; if open is in upper half of previous range, sell
desired_buy = True
if open_price < self._previous_high and open_price > pivot:
desired_buy = False
# Skip re-entry if already holding position in desired direction
if desired_buy and self.Position > 0:
self._previous_high = high
self._previous_low = low
return
if not desired_buy and self.Position < 0:
self._previous_high = high
self._previous_low = low
return
# Close existing position
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
# Open new position
if desired_buy:
self.BuyMarket()
else:
self.SellMarket()
# Update reference range for next candle
self._previous_high = high
self._previous_low = low
def CreateClone(self):
return simple_pivot_flip_strategy()