Стратегия GPF TCPivotLimit переносит советник MetaTrader 4 gpfTCPivotLimit.mq4 в инфраструктуру StockSharp. Торговля ведётся по часовым свечам с использованием классических дневных уровней Pivot. В начале каждого нового дня стратегия пересчитывает уровни Pivot, три сопротивления (R1–R3) и три поддержки (S1–S3) на основании максимума, минимума и цены закрытия прошлого дня. Далее анализируются две последние закрытые свечи, чтобы определить ложный пробой уровней и открыть сделку в противоположную сторону.
Алгоритм работы
Расчёт Pivot – при смене дня сохраняются данные прошлого дня и вычисляются уровни:
Подтверждение входа – уже в новом дне рассматриваются свечи t-2 и t-1:
Продажа открывается, если свеча t-2 сходила выше выбранного сопротивления (high > уровень либо close ≥ уровень), открылась ниже него, а свеча t-1 закрылась ниже уровня.
Покупка открывается, если свеча t-2 ушла ниже выбранной поддержки (low < уровень либо close ≤ уровень), открылась выше него, а свеча t-1 закрылась выше уровня.
Наборы целей – входной параметр TargetMode полностью повторяет варианты оригинала. Таблица ниже показывает соответствие.
TargetMode
Триггер лонга
Стоп лонга
Цель лонга
Триггер шорта
Стоп шорта
Цель шорта
1
S1
S2
R1
R1
R2
S1
2
S1
S2
R2
R1
R2
S2
3
S2
S3
R1
R2
R3
S1
4
S2
S3
R2
R2
R3
S2
5
S2
S3
R3
R2
R3
S3
Риск-менеджмент – стоп-лосс и тейк-профит проверяются на каждой закрытой свече. Дополнительно реализован трейлинг-стоп: как только плавающая прибыль превышает заданное расстояние, стоп подтягивается вслед за ценой. Опционально позиция закрывается в 23:00 по времени платформы.
Адаптация объёма – параметр MetaTrader isFloatLots соответствует переключателю UseDynamicVolume. При включении лот уменьшается после серии убыточных сделок с учётом DrawdownFactor и RiskPercentage.
Параметры
Имя
Описание
По умолчанию
BaseVolume
Базовый объём рыночных заявок до применения корректировок по риску.
1
UseDynamicVolume
Уменьшает объём после более чем одной убыточной сделки подряд.
false
RiskPercentage
Эталонная доля риска на сделку (аналог MaxR), масштабирует базовый объём.
0.02
DrawdownFactor
Делитель при уменьшении объёма после серии убытков (аналог DcF).
3
TargetMode
Выбор комбинации уровней сопротивления/поддержки (аналог TgtProfit).
1
TrailingPoints
Дистанция трейлинг-стопа в пунктах инструмента; 0 отключает функцию.
30
CloseAtSessionEnd
При значении true позиция закрывается на свече 23:00.
false
LogSignals
Записывает значения Pivot, входы и выходы в лог стратегии вместо e-mail.
false
CandleType
Тип свечей для анализа (по умолчанию часовые).
TimeFrameCandleMessage(1h)
Дополнительные замечания
Заявки всегда рыночные, как и в исходном советнике; отложенные ордера не используются.
Выходы по стопу и цели исполняются рыночными заявками для совместимости с любым коннектором StockSharp.
Для трейлинг-стопа требуется корректно заполненный PriceStep. При отсутствии шага цены трейлинг автоматически отключается.
Флаг отправки e-mail заменён параметром LogSignals, который выводит подробные сообщения в журнал.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class GpfTcpPivotLimitStrategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose; private decimal _prevMid; private bool _hasPrev;
private int _cooldown;
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public GpfTcpPivotLimitStrategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 20).SetDisplay("Channel Period", "Pivot lookback", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 14).SetDisplay("EMA Period", "EMA filter", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = default;
_prevMid = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var highest = new Highest { Length = ChannelPeriod };
var lowest = new Lowest { Length = ChannelPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(highest, lowest, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) return;
var close = candle.ClosePrice;
var mid = (highest + lowest) / 2;
if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }
if (_cooldown > 0)
{
_cooldown--;
_prevClose = close; _prevMid = mid;
return;
}
if (_prevClose <= _prevMid && close > mid && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = 2;
}
else if (_prevClose >= _prevMid && close < mid && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = 2;
}
_prevClose = close; _prevMid = mid;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class gpf_tcp_pivot_limit_strategy(Strategy):
"""
GPF TCP Pivot Limit: Donchian channel midline crossover.
Buys when close crosses above channel midpoint.
Sells when close crosses below channel midpoint.
"""
def __init__(self):
super(gpf_tcp_pivot_limit_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 20) \
.SetDisplay("Channel Period", "Pivot lookback", "Indicators")
self._ema_period = self.Param("EmaPeriod", 14) \
.SetDisplay("EMA Period", "EMA filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(gpf_tcp_pivot_limit_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
self._cooldown = 0
def OnStarted2(self, time):
super(gpf_tcp_pivot_limit_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = self._channel_period.Value
lowest = Lowest()
lowest.Length = self._channel_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, self._process_candle).Start()
def _process_candle(self, candle, highest_val, lowest_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
h = float(highest_val)
l = float(lowest_val)
mid = (h + l) / 2.0
if not self._has_prev:
self._prev_close = close
self._prev_mid = mid
self._has_prev = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_close = close
self._prev_mid = mid
return
if self._prev_close <= self._prev_mid and close > mid and self.Position <= 0:
self.BuyMarket()
self._cooldown = 2
elif self._prev_close >= self._prev_mid and close < mid and self.Position >= 0:
self.SellMarket()
self._cooldown = 2
self._prev_close = close
self._prev_mid = mid
def CreateClone(self):
return gpf_tcp_pivot_limit_strategy()