Эта стратегия повторяет эксперта MetaTrader 5 «SimplePivot». Она постоянно сравнивает цену открытия текущей свечи с пивотом
предыдущей свечи и всегда держит позицию в одном направлении. Когда направление меняется, стратегия закрывает текущую позицию и
немедленно открывает новую в противоположную сторону.
Обзор
Режим торговли: Постоянное присутствие в рынке.
Инструменты: Любые инструменты, для которых доступны свечи выбранного таймфрейма.
Таймфреймы: Настраиваются параметром Candle Type (по умолчанию свечи 1 часа).
Ожидание первой завершённой свечи для инициализации расчёта.
Пивот предыдущей свечи равен среднему арифметическому между её максимумом и минимумом.
Максимум и минимум предыдущей свечи сохраняются, чтобы пивот для следующей итерации был готов сразу после закрытия новой
свечи.
Определение направления
Базовое направление — покупка (лонг).
Если текущая свеча открывается ниже максимума предыдущей свечи, но выше рассчитанного пивота, направление меняется на продажу
(шорт).
Если требуемое направление совпадает с последней открытой сделкой, позиция сохраняется без дополнительных действий.
Управление позицией
При смене направления текущая позиция закрывается встречной рыночной заявкой.
После выхода на «нулевую» позицию рыночная заявка объёмом Volume открывает новую сделку в нужном направлении.
На каждой завершённой свече процесс повторяется, поэтому стратегия всегда находится либо в лонге, либо в шорте.
Параметры
Volume: Объём входа для каждой сделки. Тем же объёмом закрывается позиция при смене направления.
Candle Type: Тип свечных данных, используемых для расчёта пивота и сигналов. По умолчанию — часовой таймфрейм, но можно
выбрать любой доступный.
Дополнительные замечания
Логика реагирует только на полностью сформированные свечи (CandleStates.Finished), чтобы избежать повторных сигналов во время
формирования свечи.
Стопы и тейк-профиты не используются; выход происходит исключительно при смене торгового направления.
Поскольку стратегия всегда в рынке, при необходимости дополнительные фильтры риска (лимит просадки, торговые сессии и т.д.)
следует реализовывать отдельно.
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>
/// Simple pivot strategy that flips position based on the previous bar pivot.
/// </summary>
public class SimplePivotStrategy : Strategy
{
private enum TradeDirections
{
None,
Long,
Short,
}
private readonly StrategyParam<DataType> _candleType;
private TradeDirections _lastDirection = TradeDirections.None;
private decimal _previousHigh;
private decimal _previousLow;
private bool _hasPreviousCandle;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public SimplePivotStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "Data");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
protected override void OnReseted()
{
base.OnReseted();
_lastDirection = TradeDirections.None;
_previousHigh = 0m;
_previousLow = 0m;
_hasPreviousCandle = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPreviousCandle)
{
// Collect the very first completed candle as the seed for the pivot calculation.
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_hasPreviousCandle = true;
return;
}
var pivot = (_previousHigh + _previousLow) / 2m;
var desiredDirection = TradeDirections.Long;
// When the new open sits between the previous high and pivot we switch to a short bias.
if (candle.OpenPrice < _previousHigh && candle.OpenPrice > pivot)
desiredDirection = TradeDirections.Short;
if (desiredDirection == _lastDirection && _lastDirection != TradeDirections.None)
{
// Keep the existing position when direction has not changed.
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
return;
}
CloseExistingPosition();
if (desiredDirection == TradeDirections.Long)
{
// Enter a long position when the open is below the pivot.
BuyMarket(Volume);
}
else
{
// Enter a short position when the open is above the pivot zone.
SellMarket(Volume);
}
_lastDirection = desiredDirection;
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
}
private void CloseExistingPosition()
{
if (Position > 0)
{
// Flip from long to flat before opening the opposite direction.
SellMarket(Math.Abs(Position));
_lastDirection = TradeDirections.None;
}
else if (Position < 0)
{
// Flip from short to flat before opening the opposite direction.
BuyMarket(Math.Abs(Position));
_lastDirection = TradeDirections.None;
}
}
}
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
from datatype_extensions import *
from indicator_extensions import *
class simple_pivot_strategy(Strategy):
def __init__(self):
super(simple_pivot_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Type of candles", "Data")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(simple_pivot_strategy, self).OnReseted()
self._prev_high = 0
self._prev_low = 0
self._has_prev = False
self._last_dir = 0 # 1=long, -1=short
def OnStarted2(self, time):
super(simple_pivot_strategy, self).OnStarted2(time)
self._prev_high = 0
self._prev_low = 0
self._has_prev = False
self._last_dir = 0
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
if not self._has_prev:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._has_prev = True
return
pivot = (self._prev_high + self._prev_low) / 2.0
open_price = float(candle.OpenPrice)
desired = 1 # long
if open_price < self._prev_high and open_price > pivot:
desired = -1 # short
if desired == self._last_dir and self._last_dir != 0:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
# Close existing
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
if desired == 1:
self.BuyMarket()
else:
self.SellMarket()
self._last_dir = desired
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def CreateClone(self):
return simple_pivot_strategy()