Exp TimeZone Pivots Open System Tm Plus Strategy
This strategy is a high level StockSharp port of the Exp_TimeZonePivotsOpenSystem_Tm_Plus expert advisor. It recreates the proprietary TimeZonePivotsOpenSystem indicator that projects two breakout zones around the daily session open and trades the pullbacks that follow a breakout. Every component from the original script—signal delay, time filter, asymmetric exit logic and the money-management presets—was mapped to explicit parameters so the behaviour stays consistent with the MQL5 implementation.
Trading logic
- At the configured
StartHourthe strategy records the session open price. Two dynamic levels are then drawn atOffsetPoints(in points) above and below that anchor. - Whenever a finished candle closes above the upper level the strategy:
- Schedules a long entry to be executed on the next candle (respecting the
SignalBardelay) only if the current bar is no longer above the band. - Closes any open short position immediately if
SellPosCloseis enabled.
- Schedules a long entry to be executed on the next candle (respecting the
- Whenever a finished candle closes below the lower level the strategy:
- Schedules a short entry for the next candle provided the current bar is no longer below the band.
- Closes any open long position immediately if
BuyPosCloseis enabled.
- Entries are executed on the first update of the next candle thanks to
TryExecutePendingEntries. This matches the original expert that delays the order until the new bar begins.
The signal delay parameter SignalBar reproduces the original CopyBuffer shift. A value of 0 reacts to the most recent closed bar, while 1 waits an extra bar before acting, giving additional confirmation.
Order management
- Stop-loss / take-profit – The distances are set in points (
StopLossPoints,TakeProfitPoints) and converted to price using the instrument’s step. Both levels are monitored using candle extremes so intrabar touches trigger an exit. - Time based exit – When
TimeTradeis true the position is force-closed afterHoldingMinutesminutes, mirroring thenTimetimer from the MQL5 code. - Manual closes – Breakout signals of the opposite direction close the running trade if the corresponding
BuyPosCloseorSellPosCloseflag is enabled.
Money management
The MoneyMode parameter reproduces the MarginMode enumeration:
Lot– fixed volume equal toMoneyManagement.BalanceandFreeMargin– use account equity or free margin multiples (MoneyManagement * equity / price).LossBalanceandLossFreeMargin– risk-based sizing that divides the desired capital fraction by the stop distance.
If StopLossPoints is set to zero the risk modes gracefully fall back to price-based sizing.
Parameters
| Parameter | Description | Default |
|---|---|---|
MoneyManagement |
Base coefficient used to size the position depending on MoneyMode. |
0.1 |
MoneyMode |
Position sizing model (Lot, Balance, FreeMargin, LossBalance, LossFreeMargin). |
Lot |
StopLossPoints |
Stop-loss distance expressed in points from the fill price. | 1000 |
TakeProfitPoints |
Take-profit distance expressed in points from the fill price. | 2000 |
DeviationPoints |
Informational parameter kept from the expert (slippage setting in points). | 10 |
BuyPosOpen / SellPosOpen |
Enable or disable long and short entries. | true |
BuyPosClose / SellPosClose |
Allow the opposite breakout to force-close positions. | true |
TimeTrade |
Enable the maximum holding time filter. | true |
HoldingMinutes |
Maximum position lifetime in minutes. | 720 |
OffsetPoints |
Distance of the pivot bands from the session open in points. | 200 |
SignalBar |
Number of bars to delay signal evaluation (0 = last closed bar). | 1 |
CandleType |
Main timeframe used to calculate the indicator. | TimeSpan.FromHours(1).TimeFrame() |
StartHour |
Hour of the day (0-23) that defines the session open price. | 0 |
Usage notes
- The strategy assumes the security provides a valid
PriceStep. If the instrument lacks that metadata a fallback of0.0001is used. - Because entries are triggered on the first update of a new candle, the actual fill price will follow the market at that moment, just like the expert, which may differ from the theoretical open price in fast markets.
- To replicate the original indicator overlay, keep the backtest timeframe at or below H1 as the MQL5 script only operates on hourly or lower periods.
- Set
SignalBarto0for more responsive behaviour or to1(the default) to wait for one extra bar after a breakout.
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>
/// Strategy that replicates the Time Zone Pivots Open System expert from MetaTrader.
/// It follows the session open price and reacts when candles close above or below the
/// upper and lower offset bands while respecting the original money management rules.
/// </summary>
public class ExpTimeZonePivotsOpenSystemTmPlusStrategy : Strategy
{
// Parameters from the original expert controlling size, stops and permissions.
private readonly StrategyParam<decimal> _moneyManagement;
private readonly StrategyParam<MoneyManagementModes> _moneyMode;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _deviationPoints;
private readonly StrategyParam<bool> _allowBuyOpen;
private readonly StrategyParam<bool> _allowSellOpen;
private readonly StrategyParam<bool> _allowBuyClose;
private readonly StrategyParam<bool> _allowSellClose;
private readonly StrategyParam<bool> _useTimeExit;
private readonly StrategyParam<int> _holdingMinutes;
private readonly StrategyParam<decimal> _offsetPoints;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _startHour;
// Rolling buffer that stores recent candle states for the indicator recreation.
private readonly List<ZoneSnapshot> _zoneHistory = new();
// Session level tracking.
private DateTime? _lastSessionDate;
private decimal? _sessionOpenPrice;
private decimal? _upperBand;
private decimal? _lowerBand;
private bool _sessionTradeTaken;
private DateTimeOffset? _lastEntryDate;
// Pending entry scheduling.
private bool _pendingLongEntry;
private bool _pendingShortEntry;
private DateTimeOffset? _longSignalTime;
private DateTimeOffset? _shortSignalTime;
private DateTimeOffset? _lastLongSignalOrigin;
private DateTimeOffset? _lastShortSignalOrigin;
private DateTimeOffset? _currentCandleOpen;
// Position bookkeeping for exit controls.
private DateTimeOffset? _longEntryTime;
private DateTimeOffset? _shortEntryTime;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
private decimal? _longStopPrice;
private decimal? _longTakePrice;
private decimal? _shortStopPrice;
private decimal? _shortTakePrice;
// Cached timeframe of the selected candle series.
private TimeSpan? _timeFrame;
public decimal MoneyManagement
{
get => _moneyManagement.Value;
set => _moneyManagement.Value = value;
}
public MoneyManagementModes MoneyMode
{
get => _moneyMode.Value;
set => _moneyMode.Value = value;
}
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public decimal DeviationPoints
{
get => _deviationPoints.Value;
set => _deviationPoints.Value = value;
}
public bool BuyPosOpen
{
get => _allowBuyOpen.Value;
set => _allowBuyOpen.Value = value;
}
public bool SellPosOpen
{
get => _allowSellOpen.Value;
set => _allowSellOpen.Value = value;
}
public bool BuyPosClose
{
get => _allowBuyClose.Value;
set => _allowBuyClose.Value = value;
}
public bool SellPosClose
{
get => _allowSellClose.Value;
set => _allowSellClose.Value = value;
}
public bool TimeTrade
{
get => _useTimeExit.Value;
set => _useTimeExit.Value = value;
}
public int HoldingMinutes
{
get => _holdingMinutes.Value;
set => _holdingMinutes.Value = value;
}
public decimal OffsetPoints
{
get => _offsetPoints.Value;
set => _offsetPoints.Value = value;
}
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
public ExpTimeZonePivotsOpenSystemTmPlusStrategy()
{
_moneyManagement = Param(nameof(MoneyManagement), 0.1m)
.SetDisplay("Money Management", "Base value used for position sizing", "Trading")
.SetGreaterThanZero();
_moneyMode = Param(nameof(MoneyMode), MoneyManagementModes.Lot)
.SetDisplay("Money Mode", "Position sizing model", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
.SetDisplay("Stop Loss (points)", "Distance from entry to stop loss expressed in points", "Risk")
.SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000m)
.SetDisplay("Take Profit (points)", "Distance from entry to take profit expressed in points", "Risk")
.SetNotNegative();
_deviationPoints = Param(nameof(DeviationPoints), 10m)
.SetDisplay("Allowed Deviation", "Maximum acceptable price deviation for entries", "Risk")
.SetNotNegative();
_allowBuyOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading");
_allowSellOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading");
_allowBuyClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Enable Long Exits", "Allow closing long positions on opposite signals", "Trading");
_allowSellClose = Param(nameof(SellPosClose), true)
.SetDisplay("Enable Short Exits", "Allow closing short positions on opposite signals", "Trading");
_useTimeExit = Param(nameof(TimeTrade), true)
.SetDisplay("Use Time Exit", "Close positions after a fixed holding time", "Risk");
_holdingMinutes = Param(nameof(HoldingMinutes), 720)
.SetDisplay("Holding Minutes", "Maximum position lifetime in minutes", "Risk")
.SetNotNegative();
_offsetPoints = Param(nameof(OffsetPoints), 200m)
.SetDisplay("Offset (points)", "Distance from session open that defines the pivot zones", "Indicator")
.SetNotNegative();
_signalBar = Param(nameof(SignalBar), 1)
.SetDisplay("Signal Bar", "Number of bars to delay the signal evaluation", "Indicator")
.SetNotNegative();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for calculations", "Indicator");
_startHour = Param(nameof(StartHour), 0)
.SetDisplay("Session Start Hour", "Hour of day used to anchor the session open price", "Indicator")
.SetNotNegative()
;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_zoneHistory.Clear();
_lastSessionDate = null;
_sessionOpenPrice = null;
_upperBand = null;
_lowerBand = null;
_pendingLongEntry = false;
_pendingShortEntry = false;
_longSignalTime = null;
_shortSignalTime = null;
_lastLongSignalOrigin = null;
_lastShortSignalOrigin = null;
_currentCandleOpen = null;
_longEntryTime = null;
_shortEntryTime = null;
_longEntryPrice = null;
_shortEntryPrice = null;
_longStopPrice = null;
_longTakePrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
_timeFrame = CandleType.Arg as TimeSpan?;
_sessionTradeTaken = false;
_lastEntryDate = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_timeFrame = CandleType.Arg as TimeSpan?;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
// Enable loss protection guard as required by the framework.
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle)
{
// Detect new candle openings to trigger delayed entries on the first update.
if (_currentCandleOpen != candle.OpenTime)
{
_currentCandleOpen = candle.OpenTime;
TryExecutePendingEntries(candle);
}
// Only finished candles contribute to the indicator logic and trade decisions.
if (candle.State != CandleStates.Finished)
return;
// Refresh the session reference and pre-compute band levels.
UpdateSessionReference(candle);
// Memorise the current candle classification for delayed evaluations.
var snapshot = new ZoneSnapshot
{
State = DetermineState(candle),
OpenTime = candle.OpenTime,
CloseTime = candle.CloseTime
};
_zoneHistory.Insert(0, snapshot);
var maxHistory = Math.Max(5, SignalBar + 3);
while (_zoneHistory.Count > maxHistory)
_zoneHistory.RemoveAt(_zoneHistory.Count - 1);
// Ensure we have enough candles to evaluate the signal and confirmation offsets.
if (_zoneHistory.Count <= SignalBar + 1)
{
ManageStops(candle);
HandleTimeExit(candle.CloseTime);
return;
}
var signalSnapshot = _zoneHistory[SignalBar];
var confirmSnapshot = _zoneHistory[SignalBar + 1];
if (signalSnapshot == null || confirmSnapshot == null)
{
ManageStops(candle);
HandleTimeExit(candle.CloseTime);
return;
}
var closeLong = false;
var closeShort = false;
// Previous candle closed above the upper band – schedule long entry and close shorts.
if (confirmSnapshot.State == ZoneSignals.Above)
{
if (SellPosClose)
closeShort = true;
if (BuyPosOpen && signalSnapshot.State != ZoneSignals.Above && (_lastLongSignalOrigin != confirmSnapshot.CloseTime))
{
_pendingLongEntry = true;
_longSignalTime = confirmSnapshot.CloseTime + (_timeFrame ?? TimeSpan.Zero);
_lastLongSignalOrigin = confirmSnapshot.CloseTime;
}
}
// Previous candle closed below the lower band – schedule short entry and close longs.
else if (confirmSnapshot.State == ZoneSignals.Below)
{
if (BuyPosClose)
closeLong = true;
if (SellPosOpen && signalSnapshot.State != ZoneSignals.Below && (_lastShortSignalOrigin != confirmSnapshot.CloseTime))
{
_pendingShortEntry = true;
_shortSignalTime = confirmSnapshot.CloseTime + (_timeFrame ?? TimeSpan.Zero);
_lastShortSignalOrigin = confirmSnapshot.CloseTime;
}
}
if (closeLong && Position > 0m)
{
SellMarket(Position);
_longEntryTime = null;
_longEntryPrice = null;
_longStopPrice = null;
_longTakePrice = null;
}
if (closeShort && Position < 0m)
{
BuyMarket(Math.Abs(Position));
_shortEntryTime = null;
_shortEntryPrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
ManageStops(candle);
HandleTimeExit(candle.CloseTime);
}
// Execute pending entries once the new candle that should host the trade begins.
private void TryExecutePendingEntries(ICandleMessage candle)
{
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0m)
return;
if (_sessionTradeTaken)
return;
if (_lastEntryDate.HasValue && candle.OpenTime.Date <= _lastEntryDate.Value.Date.AddDays(4))
return;
var opened = false;
if (_pendingLongEntry && BuyPosOpen)
{
if (!_longSignalTime.HasValue || candle.OpenTime >= _longSignalTime.Value)
{
var entryPrice = candle.OpenPrice;
var volume = GetEntryVolume(true, entryPrice);
if (volume > 0m)
{
_longEntryPrice = entryPrice;
BuyMarket(volume);
_pendingLongEntry = false;
_longSignalTime = null;
_sessionTradeTaken = true;
_lastEntryDate = candle.OpenTime;
opened = true;
}
}
}
if (!opened && _pendingShortEntry && SellPosOpen)
{
if (!_shortSignalTime.HasValue || candle.OpenTime >= _shortSignalTime.Value)
{
var entryPrice = candle.OpenPrice;
var volume = GetEntryVolume(false, entryPrice);
if (volume > 0m)
{
_shortEntryPrice = entryPrice;
SellMarket(volume);
_pendingShortEntry = false;
_shortSignalTime = null;
_sessionTradeTaken = true;
_lastEntryDate = candle.OpenTime;
}
}
}
}
// Monitor stop-loss and take-profit levels intrabar using candle extremes.
private void ManageStops(ICandleMessage candle)
{
var volume = Math.Abs(Position);
if (volume <= 0m)
return;
if (Position > 0m)
{
if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
{
SellMarket(volume);
_longEntryTime = null;
_longEntryPrice = null;
_longStopPrice = null;
_longTakePrice = null;
}
else if (_longTakePrice.HasValue && candle.HighPrice >= _longTakePrice.Value)
{
SellMarket(volume);
_longEntryTime = null;
_longEntryPrice = null;
_longStopPrice = null;
_longTakePrice = null;
}
}
else if (Position < 0m)
{
if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
{
BuyMarket(volume);
_shortEntryTime = null;
_shortEntryPrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
else if (_shortTakePrice.HasValue && candle.LowPrice <= _shortTakePrice.Value)
{
BuyMarket(volume);
_shortEntryTime = null;
_shortEntryPrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
}
}
// Implement the time based exit from the MQL5 code.
private void HandleTimeExit(DateTimeOffset time)
{
if (!TimeTrade)
return;
var holdMinutes = HoldingMinutes;
if (holdMinutes <= 0)
return;
var threshold = TimeSpan.FromMinutes(holdMinutes);
var volume = Math.Abs(Position);
if (volume <= 0m)
return;
if (Position > 0m && _longEntryTime.HasValue && time - _longEntryTime.Value >= threshold)
{
SellMarket(volume);
_longEntryTime = null;
_longEntryPrice = null;
_longStopPrice = null;
_longTakePrice = null;
}
else if (Position < 0m && _shortEntryTime.HasValue && time - _shortEntryTime.Value >= threshold)
{
BuyMarket(volume);
_shortEntryTime = null;
_shortEntryPrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
}
// Update the session open reference when the configured hour is reached.
private void UpdateSessionReference(ICandleMessage candle)
{
var openTime = candle.OpenTime;
var currentDate = openTime.Date;
if ((!_lastSessionDate.HasValue || _lastSessionDate.Value != currentDate) && openTime.Hour == StartHour)
{
_sessionOpenPrice = candle.OpenPrice;
_lastSessionDate = currentDate;
_zoneHistory.Clear();
_pendingLongEntry = false;
_pendingShortEntry = false;
_longSignalTime = null;
_shortSignalTime = null;
_lastLongSignalOrigin = null;
_lastShortSignalOrigin = null;
_sessionTradeTaken = false;
}
if (_sessionOpenPrice.HasValue)
{
var step = GetPriceStep();
var offset = OffsetPoints * step;
_upperBand = _sessionOpenPrice + offset;
_lowerBand = _sessionOpenPrice - offset;
}
else
{
_upperBand = null;
_lowerBand = null;
}
}
// Classify the candle relative to the offset bands.
private ZoneSignals DetermineState(ICandleMessage candle)
{
if (!_sessionOpenPrice.HasValue || !_upperBand.HasValue || !_lowerBand.HasValue)
return ZoneSignals.Inside;
if (candle.ClosePrice > _upperBand.Value)
return ZoneSignals.Above;
if (candle.ClosePrice < _lowerBand.Value)
return ZoneSignals.Below;
return ZoneSignals.Inside;
}
// Translate the money management mode into an executable volume.
private decimal GetEntryVolume(bool isLong, decimal price)
{
if (price <= 0m)
return 0m;
var step = GetPriceStep();
var stopDistance = StopLossPoints > 0m ? StopLossPoints * step : 0m;
var capital = Portfolio?.CurrentValue ?? 0m;
var mmValue = MoneyManagement;
switch (MoneyMode)
{
case MoneyManagementModes.Lot:
return mmValue;
case MoneyManagementModes.Balance:
case MoneyManagementModes.FreeMargin:
return capital > 0m ? capital * mmValue / price : 0m;
case MoneyManagementModes.LossBalance:
case MoneyManagementModes.LossFreeMargin:
if (stopDistance > 0m)
return capital > 0m ? capital * mmValue / stopDistance : 0m;
return capital > 0m ? capital * mmValue / price : 0m;
default:
return mmValue;
}
}
// Retrieve the minimum price step for the configured security.
private decimal GetPriceStep()
{
var security = Security;
if (security == null)
return 0.0001m;
if (security.PriceStep > 0m)
return security.PriceStep.Value;
return 0.0001m;
}
// Helper to compute stop-loss and take-profit levels around the fill price.
private decimal? CalculateStopPrice(bool isLong, decimal? entryPrice)
{
if (!entryPrice.HasValue || StopLossPoints <= 0m)
return null;
var distance = StopLossPoints * GetPriceStep();
return isLong ? entryPrice - distance : entryPrice + distance;
}
private decimal? CalculateTakePrice(bool isLong, decimal? entryPrice)
{
if (!entryPrice.HasValue || TakeProfitPoints <= 0m)
return null;
var distance = TakeProfitPoints * GetPriceStep();
return isLong ? entryPrice + distance : entryPrice - distance;
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position > 0m && trade.Order.Side == Sides.Buy)
{
_longEntryTime = trade.Trade.ServerTime;
_longEntryPrice = trade.Trade.Price;
_longStopPrice = CalculateStopPrice(true, _longEntryPrice);
_longTakePrice = CalculateTakePrice(true, _longEntryPrice);
}
else if (Position < 0m && trade.Order.Side == Sides.Sell)
{
_shortEntryTime = trade.Trade.ServerTime;
_shortEntryPrice = trade.Trade.Price;
_shortStopPrice = CalculateStopPrice(false, _shortEntryPrice);
_shortTakePrice = CalculateTakePrice(false, _shortEntryPrice);
}
if (Position == 0m)
{
_longEntryTime = null;
_shortEntryTime = null;
_longEntryPrice = null;
_shortEntryPrice = null;
_longStopPrice = null;
_shortStopPrice = null;
_longTakePrice = null;
_shortTakePrice = null;
}
}
public enum MoneyManagementModes
{
FreeMargin,
Balance,
LossFreeMargin,
LossBalance,
Lot
}
private enum ZoneSignals
{
Inside,
Above,
Below
}
private sealed class ZoneSnapshot
{
public ZoneSignals State { get; init; }
public DateTimeOffset OpenTime { get; init; }
public DateTimeOffset CloseTime { get; init; }
}
}
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, Sides
from StockSharp.Algo.Strategies import Strategy
class exp_time_zone_pivots_open_system_tm_plus_strategy(Strategy):
ZONE_INSIDE = 0
ZONE_ABOVE = 1
ZONE_BELOW = 2
def __init__(self):
super(exp_time_zone_pivots_open_system_tm_plus_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Primary timeframe for calculations", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 1000.0) \
.SetDisplay("Stop Loss (points)", "Distance from entry to stop loss", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 2000.0) \
.SetDisplay("Take Profit (points)", "Distance from entry to take profit", "Risk")
self._offset_points = self.Param("OffsetPoints", 200.0) \
.SetDisplay("Offset (points)", "Distance from session open that defines pivot zones", "Indicator")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Bar", "Number of bars to delay signal evaluation", "Indicator")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Session Start Hour", "Hour of day used to anchor session open price", "Indicator")
self._use_time_exit = self.Param("TimeTrade", True) \
.SetDisplay("Use Time Exit", "Close positions after a fixed holding time", "Risk")
self._holding_minutes = self.Param("HoldingMinutes", 720) \
.SetDisplay("Holding Minutes", "Maximum position lifetime in minutes", "Risk")
self._buy_pos_open = self.Param("BuyPosOpen", True) \
.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading")
self._sell_pos_open = self.Param("SellPosOpen", True) \
.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading")
self._buy_pos_close = self.Param("BuyPosClose", True) \
.SetDisplay("Enable Long Exits", "Allow closing long positions on opposite signals", "Trading")
self._sell_pos_close = self.Param("SellPosClose", True) \
.SetDisplay("Enable Short Exits", "Allow closing short positions on opposite signals", "Trading")
self._zone_history = []
self._last_session_date = None
self._session_open_price = None
self._upper_band = None
self._lower_band = None
self._session_trade_taken = False
self._long_entry_time = None
self._short_entry_time = None
self._long_entry_price = None
self._short_entry_price = None
self._long_stop_price = None
self._long_take_price = None
self._short_stop_price = None
self._short_take_price = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def OffsetPoints(self):
return self._offset_points.Value
@property
def SignalBar(self):
return self._signal_bar.Value
@property
def StartHour(self):
return self._start_hour.Value
@property
def UseTimeExit(self):
return self._use_time_exit.Value
@property
def HoldingMinutes(self):
return self._holding_minutes.Value
@property
def BuyPosOpen(self):
return self._buy_pos_open.Value
@property
def SellPosOpen(self):
return self._sell_pos_open.Value
@property
def BuyPosClose(self):
return self._buy_pos_close.Value
@property
def SellPosClose(self):
return self._sell_pos_close.Value
def OnReseted(self):
super(exp_time_zone_pivots_open_system_tm_plus_strategy, self).OnReseted()
self._zone_history = []
self._last_session_date = None
self._session_open_price = None
self._upper_band = None
self._lower_band = None
self._session_trade_taken = False
self._long_entry_time = None
self._short_entry_time = None
self._long_entry_price = None
self._short_entry_price = None
self._long_stop_price = None
self._long_take_price = None
self._short_stop_price = None
self._short_take_price = None
def OnStarted2(self, time):
super(exp_time_zone_pivots_open_system_tm_plus_strategy, self).OnStarted2(time)
self._zone_history = []
self._last_session_date = None
self._session_open_price = None
self._upper_band = None
self._lower_band = None
self._session_trade_taken = False
self._long_entry_time = None
self._short_entry_time = None
self._long_entry_price = None
self._short_entry_price = None
self._long_stop_price = None
self._long_take_price = None
self._short_stop_price = None
self._short_take_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
self._update_session_reference(candle)
state = self._determine_state(candle)
self._zone_history.insert(0, state)
max_history = max(5, self.SignalBar + 3)
while len(self._zone_history) > max_history:
self._zone_history.pop()
if len(self._zone_history) <= self.SignalBar + 1:
self._manage_stops(candle)
self._handle_time_exit(candle.CloseTime)
return
signal_snapshot = self._zone_history[self.SignalBar]
confirm_snapshot = self._zone_history[self.SignalBar + 1]
close_long = False
close_short = False
if confirm_snapshot == self.ZONE_ABOVE:
if self.SellPosClose:
close_short = True
elif confirm_snapshot == self.ZONE_BELOW:
if self.BuyPosClose:
close_long = True
if close_long and self.Position > 0:
self.SellMarket()
self._long_entry_time = None
self._long_entry_price = None
self._long_stop_price = None
self._long_take_price = None
if close_short and self.Position < 0:
self.BuyMarket()
self._short_entry_time = None
self._short_entry_price = None
self._short_stop_price = None
self._short_take_price = None
self._manage_stops(candle)
self._handle_time_exit(candle.CloseTime)
if self._session_trade_taken:
return
if self.Position != 0:
return
if confirm_snapshot == self.ZONE_ABOVE and signal_snapshot != self.ZONE_ABOVE and self.BuyPosOpen:
self.BuyMarket()
self._session_trade_taken = True
self._long_entry_time = candle.CloseTime
self._long_entry_price = float(candle.ClosePrice)
step = self._get_price_step()
self._long_stop_price = self._long_entry_price - float(self.StopLossPoints) * step if float(self.StopLossPoints) > 0.0 else None
self._long_take_price = self._long_entry_price + float(self.TakeProfitPoints) * step if float(self.TakeProfitPoints) > 0.0 else None
elif confirm_snapshot == self.ZONE_BELOW and signal_snapshot != self.ZONE_BELOW and self.SellPosOpen:
self.SellMarket()
self._session_trade_taken = True
self._short_entry_time = candle.CloseTime
self._short_entry_price = float(candle.ClosePrice)
step = self._get_price_step()
self._short_stop_price = self._short_entry_price + float(self.StopLossPoints) * step if float(self.StopLossPoints) > 0.0 else None
self._short_take_price = self._short_entry_price - float(self.TakeProfitPoints) * step if float(self.TakeProfitPoints) > 0.0 else None
def _manage_stops(self, candle):
if self.Position > 0:
if self._long_stop_price is not None and float(candle.LowPrice) <= self._long_stop_price:
self.SellMarket()
self._long_entry_time = None
self._long_entry_price = None
self._long_stop_price = None
self._long_take_price = None
elif self._long_take_price is not None and float(candle.HighPrice) >= self._long_take_price:
self.SellMarket()
self._long_entry_time = None
self._long_entry_price = None
self._long_stop_price = None
self._long_take_price = None
elif self.Position < 0:
if self._short_stop_price is not None and float(candle.HighPrice) >= self._short_stop_price:
self.BuyMarket()
self._short_entry_time = None
self._short_entry_price = None
self._short_stop_price = None
self._short_take_price = None
elif self._short_take_price is not None and float(candle.LowPrice) <= self._short_take_price:
self.BuyMarket()
self._short_entry_time = None
self._short_entry_price = None
self._short_stop_price = None
self._short_take_price = None
def _handle_time_exit(self, time):
if not self.UseTimeExit:
return
if self.HoldingMinutes <= 0:
return
if self.Position > 0 and self._long_entry_time is not None:
if (time - self._long_entry_time).TotalMinutes >= self.HoldingMinutes:
self.SellMarket()
self._long_entry_time = None
self._long_entry_price = None
self._long_stop_price = None
self._long_take_price = None
elif self.Position < 0 and self._short_entry_time is not None:
if (time - self._short_entry_time).TotalMinutes >= self.HoldingMinutes:
self.BuyMarket()
self._short_entry_time = None
self._short_entry_price = None
self._short_stop_price = None
self._short_take_price = None
def _update_session_reference(self, candle):
open_time = candle.OpenTime
current_date = open_time.Date
if (self._last_session_date is None or self._last_session_date != current_date) and open_time.Hour == self.StartHour:
self._session_open_price = float(candle.OpenPrice)
self._last_session_date = current_date
self._zone_history = []
self._session_trade_taken = False
if self._session_open_price is not None:
step = self._get_price_step()
offset = float(self.OffsetPoints) * step
self._upper_band = self._session_open_price + offset
self._lower_band = self._session_open_price - offset
else:
self._upper_band = None
self._lower_band = None
def _determine_state(self, candle):
if self._session_open_price is None or self._upper_band is None or self._lower_band is None:
return self.ZONE_INSIDE
close = float(candle.ClosePrice)
if close > self._upper_band:
return self.ZONE_ABOVE
if close < self._lower_band:
return self.ZONE_BELOW
return self.ZONE_INSIDE
def _get_price_step(self):
sec = self.Security
if sec is not None and sec.PriceStep is not None:
step = float(sec.PriceStep)
if step > 0.0:
return step
return 0.0001
def CreateClone(self):
return exp_time_zone_pivots_open_system_tm_plus_strategy()