The GPF TCPivotLimit Strategy recreates the MetaTrader 4 expert advisor gpfTCPivotLimit.mq4 inside the StockSharp framework. The system trades on hourly candles and reacts to reversals around classic daily pivot levels. Every new trading day the strategy calculates the pivot, three resistance levels (R1–R3) and three support levels (S1–S3) from the previous day’s high, low and close. As soon as the next day starts, it evaluates the last two completed hourly candles to decide whether price rejected a resistance or support zone and opens a market order in the opposite direction.
Trading Logic
Pivot calculation – when a new daily session begins the strategy stores the previous day’s high, low and close, then computes:
Entry confirmation – with the new day underway the last two closed hourly candles (t-2 and t-1) are inspected.
A short is opened if candle t-2 probed above the selected resistance (high above or close at the level), opened below it, and candle t-1 closed back below the level.
A long is opened if candle t-2 dipped below the selected support (low below or close at the level), opened above it, and candle t-1 closed back above the level.
Target presets – the original expert advisor exposes five profit/stop layouts. The table below shows the exact mapping that is preserved in this port.
TargetMode
Long trigger
Long stop
Long target
Short trigger
Short stop
Short target
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
Risk management – protective stop-loss and take-profit checks run on every completed candle. Optional trailing stop logic emulates the MT4 behaviour: once unrealised profit exceeds the configured distance the stop is moved in favour of the trade. An optional end-of-day exit flattens the position at 23:00 platform time.
Volume adaptation – the MetaTrader input isFloatLots is mirrored by the UseDynamicVolume toggle. When enabled, the position size is reduced after consecutive losing trades, using the DrawdownFactor and RiskPercentage inputs.
Parameters
Name
Description
Default
BaseVolume
Base volume submitted with each market order before risk adjustments.
1
UseDynamicVolume
Reduces the trade size after more than one consecutive loss.
false
RiskPercentage
Reference risk-per-trade ratio used to scale the base volume (MetaTrader MaxR).
0.02
DrawdownFactor
Divisor applied when shrinking the volume after a losing streak (MetaTrader DcF).
3
TargetMode
Selects the resistance/support combination listed above (MetaTrader TgtProfit).
1
TrailingPoints
Trailing-stop distance expressed in instrument points. Set to 0 to disable.
30
CloseAtSessionEnd
When true all positions are closed on the 23:00 candle close.
false
LogSignals
Prints pivot values, entries and exits into the strategy log.
false
CandleType
Candle data type used for analysis (defaults to 1-hour candles).
TimeFrameCandleMessage(1h)
Notes
The strategy issues market orders just like the original EA and does not place pending orders.
Stop-loss and take-profit events are executed with market exits to stay compatible with all StockSharp connectors.
Trailing distances rely on the instrument PriceStep. If the step is missing, the trailing mechanism is automatically disabled.
The email notification flag from the MT4 version is represented by LogSignals, producing log messages instead of emails.
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()