Exp TimeZone Pivots Open System Tm Plus 策略
本策略是将 Exp_TimeZonePivotsOpenSystem_Tm_Plus MQL5 专家顾问完整移植到 StockSharp 高级 API 的版本。代码中重建了 TimeZonePivotsOpenSystem 指标:在每日会话开盘价附近绘制上下两个突破带,并在价格突破后回撤时寻找进场机会。原脚本中的信号延迟、持仓时间限制、方向性平仓和多种资金管理模式全部保留,并以参数形式暴露,方便与原版保持一致。
交易逻辑
- 当新的时间段到达参数
StartHour指定的小时后,记录会话开盘价,并在其上下OffsetPoints(以点数表示)处生成动态通道。 - 如果收盘价 突破上轨:
- 在下一根 K 线(考虑
SignalBar延迟)尝试开多,但仅当当前 K 线已经回到通道内。 - 若启用
SellPosClose,立即平掉空头仓位。
- 在下一根 K 线(考虑
- 如果收盘价 跌破下轨:
- 在下一根 K 线尝试开空,但仅当当前 K 线已经回到通道内。
- 若启用
BuyPosClose,立即平掉多头仓位。
- 通过
TryExecutePendingEntries在新 K 线的首个更新中提交挂起订单,从而与原专家在新柱开始时入场的行为保持一致。
SignalBar 用于控制信号引用的历史柱数:0 代表最新一根收盘柱,1(默认值)表示再额外等待一根柱,以获得额外确认。
仓位管理
- 止损/止盈:
StopLossPoints与TakeProfitPoints以点数表示,根据品种的PriceStep转换为价格距离,并利用蜡烛的最高/最低价进行监控,确保盘中触发也能及时离场。 - 持仓计时:当
TimeTrade为真时,持仓时间超过HoldingMinutes分钟后会被强制平仓,对应原始脚本中的nTime逻辑。 - 反向平仓:若新的突破信号与当前仓位方向相反,并且对应的
BuyPosClose或SellPosClose允许,则立即平仓。
资金管理
MoneyMode 参数对应原始枚举 MarginMode:
Lot:固定手数,取值为MoneyManagement。Balance、FreeMargin:按账户权益或可用保证金的比例下单(MoneyManagement * Equity / Price)。LossBalance、LossFreeMargin:按照止损距离来计算风险敞口,等价于MoneyManagement * Equity / StopDistance。
若 StopLossPoints 设为 0,风险模式会自动退化为按价格比例下单,避免除以零。
参数一览
| 参数 | 说明 | 默认值 |
|---|---|---|
MoneyManagement |
资金管理基数,根据 MoneyMode 计算下单量。 |
0.1 |
MoneyMode |
资金管理方式(Lot、Balance、FreeMargin、LossBalance、LossFreeMargin)。 |
Lot |
StopLossPoints |
止损距离(点)。 | 1000 |
TakeProfitPoints |
止盈距离(点)。 | 2000 |
DeviationPoints |
来自原脚本的滑点设置,当前仅作展示用。 | 10 |
BuyPosOpen / SellPosOpen |
是否允许开多 / 开空。 | true |
BuyPosClose / SellPosClose |
是否允许由反向信号强制平仓。 | true |
TimeTrade |
是否启用最大持仓时间限制。 | true |
HoldingMinutes |
最大持仓时间(分钟)。 | 720 |
OffsetPoints |
上下突破带距离会话开盘价的偏移(点)。 | 200 |
SignalBar |
信号延迟的柱数(0 表示上一根已收盘的 K 线)。 | 1 |
CandleType |
指标计算所用的主时间框架。 | TimeSpan.FromHours(1).TimeFrame() |
StartHour |
会话开盘价所对应的小时(0–23)。 | 0 |
使用建议
- 需要品种提供有效的
PriceStep,否则系统会使用备用值0.0001。 - 由于下单在新柱首个更新时触发,真实成交价将跟随市场,即使与理论开盘价存在滑点也与原专家一致。
- 建议测试时使用 H1 或更低时间框架,以匹配指标的设计假设。
- 调整
SignalBar可以在敏捷度与稳健性之间取舍:0更快,1更稳健。
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()