Pipso — это ночная пробойная система, перенесённая из советника MetaTrader Pipso.mq4. Стратегия отслеживает максимум и минимум
предыдущих завершённых свечей и при выходе цены за эти границы немедленно переворачивает позицию: при обновлении максимума
закрываются лонги и открывается шорт, при пробое минимума закрываются шорты и открывается лонг. Дистанция защитного стопа
вычисляется исходя из ширины диапазона, благодаря чему стоп автоматически подстраивается под текущую волатильность.
Алгоритм работы
Подписаться на выбранный таймфрейм (по умолчанию 15 минут) и дождаться формирования истории для индикаторов.
Для каждой новой завершённой свечи рассчитать максимальное и минимальное значение по предыдущим BreakoutPeriod свечам.
Текущая свеча не попадает в расчёт, что повторяет вызов iHighest(..., shift = 1) в оригинальном коде.
Пересчитать расстояние до стоп-лосса как (high - low) * StopLossMultiplier, применяя ограничение MinStopDistance на
минимально допустимую дистанцию.
Поддерживать торговое окно, заданное параметрами SessionStartHour и SessionLengthHours. Если окно переходит через ночь
с пятницы на субботу, оно продлевается на 48 часов, чтобы сделки переживали выходные так же, как в MetaTrader.
При пробое верхней границы диапазона:
Закрыть все активные длинные позиции и, если торговля разрешена, открыть короткую позицию объёмом OrderVolume.
Установить защитный стоп выше цены входа на рассчитанное расстояние.
При пробое нижней границы диапазона:
Закрыть все активные короткие позиции и, если торговля разрешена, открыть длинную позицию объёмом OrderVolume.
Установить защитный стоп ниже цены входа на рассчитанное расстояние.
На каждой завершённой свече проверять защитные стопы. Если минимум свечи опускается ниже стопа лонга или максимум поднимается
выше стопа шорта, позиция немедленно закрывается.
Логика торговой сессии
SessionStartHour задаёт час открытия окна в часах торговой площадки, продолжительность определяется SessionLengthHours.
Если сумма SessionStartHour + SessionLengthHours превышает 24 часа и текущий день — пятница, конец окна переносится вперёд
на двое суток, чтобы новые сделки стали доступны только в понедельник, как и в MQL4.
Вне торгового окна стратегия только фиксирует существующие позиции, но не открывает новые до повторного входа в окно.
Параметры
Имя
Описание
Значение по умолчанию
CandleType
Тип свечей, используемых для расчёта сигналов.
Таймфрейм 15 минут
OrderVolume
Фиксированный объём рыночных заявок.
1
SessionStartHour
Час начала торгового окна.
21
SessionLengthHours
Продолжительность торгового окна в часах.
9
BreakoutPeriod
Количество завершённых свечей, формирующих пробойный диапазон.
36
StopLossMultiplier
Множитель диапазона для расчёта стоп-лосса (значение 3 соответствует исходному SLpp = 300).
3
MinStopDistance
Минимальная дистанция от цены входа до стопа, имитирующая брокерский StopLevel.
0
Примечания
Стратегия работает только с рыночными заявками, целевой прибыли нет: выход осуществляется по стоп-лоссу или по встречному
сигналу.
При смене направления открывается одна рыночная заявка, которая одновременно закрывает старую позицию и открывает новую,
повторяя последовательность OrderClose → OrderSend из исходного советника.
На графике автоматически отображаются линии верхней и нижней границы диапазона, а также все совершённые сделки для удобного
анализа.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Range breakout strategy that uses Highest/Lowest channel.
/// Enters on breakouts above/below the channel and exits on reversion.
/// </summary>
public class PipsoNightBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _breakoutPeriod;
private decimal _entryPrice;
private decimal _prevHighest;
private decimal _prevLowest;
private bool _hasPrev;
public PipsoNightBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_breakoutPeriod = Param(nameof(BreakoutPeriod), 36)
.SetDisplay("Breakout Period", "Period for Highest/Lowest channel.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int BreakoutPeriod
{
get => _breakoutPeriod.Value;
set => _breakoutPeriod.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_prevHighest = 0;
_prevLowest = 0;
_hasPrev = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_prevHighest = 0;
_prevLowest = 0;
_hasPrev = false;
var highest = new Highest { Length = BreakoutPeriod };
var lowest = new Lowest { Length = BreakoutPeriod };
var ema = new ExponentialMovingAverage { Length = BreakoutPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var mid = (highestValue + lowestValue) / 2m;
// Exit conditions
if (Position > 0)
{
// Exit when price reverts to middle or stop-loss
if (close < mid || (_entryPrice > 0 && close < _entryPrice * 0.98m))
{
SellMarket();
}
}
else if (Position < 0)
{
if (close > mid || (_entryPrice > 0 && close > _entryPrice * 1.02m))
{
BuyMarket();
}
}
// Entry conditions: breakout above previous highest or below previous lowest
if (Position == 0 && _hasPrev)
{
if (close > _prevHighest && close > emaValue)
{
_entryPrice = close;
BuyMarket();
}
else if (close < _prevLowest && close < emaValue)
{
_entryPrice = close;
SellMarket();
}
}
_prevHighest = highestValue;
_prevLowest = lowestValue;
_hasPrev = true;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class pipso_night_breakout_strategy(Strategy):
def __init__(self):
super(pipso_night_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._breakout_period = self.Param("BreakoutPeriod", 36).SetDisplay("Breakout Period", "Period for Highest/Lowest channel", "Indicators")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(pipso_night_breakout_strategy, self).OnReseted()
self._entry_price = 0
self._prev_highest = 0
self._prev_lowest = 0
self._has_prev = False
def OnStarted2(self, time):
super(pipso_night_breakout_strategy, self).OnStarted2(time)
self._entry_price = 0
self._prev_highest = 0
self._prev_lowest = 0
self._has_prev = False
highest = Highest()
highest.Length = self._breakout_period.Value
lowest = Lowest()
lowest.Length = self._breakout_period.Value
ema = ExponentialMovingAverage()
ema.Length = self._breakout_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(highest, lowest, ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, highest_val, lowest_val, ema_val):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
mid = (highest_val + lowest_val) / 2.0
if self.Position > 0:
if close < mid or (self._entry_price > 0 and close < self._entry_price * 0.98):
self.SellMarket()
elif self.Position < 0:
if close > mid or (self._entry_price > 0 and close > self._entry_price * 1.02):
self.BuyMarket()
if self.Position == 0 and self._has_prev:
if close > self._prev_highest and close > ema_val:
self._entry_price = close
self.BuyMarket()
elif close < self._prev_lowest and close < ema_val:
self._entry_price = close
self.SellMarket()
self._prev_highest = highest_val
self._prev_lowest = lowest_val
self._has_prev = True
def CreateClone(self):
return pipso_night_breakout_strategy()