Exp Zeitzonenpivots-Offenem-System Tm Plus Strategie
Diese Strategie ist ein High-Level-StockSharp-Port des Expert Advisors Exp_TimeZonePivotsOpenSystem_Tm_Plus. Er recreiert den proprietären TimeZonePivotsOpenSystem-Indikator, der zwei Ausbruchszonen rund um das tägliche Sessions-Open projiziert und die Pullbacks handelt, die einem Ausbruch folgen. Jede Komponente des ursprünglichen Skripts—Signalverzögerung, Zeitfilter, asymmetrische Ausstiegslogik und die Geldmanagement-Voreinstellungen—wurde auf explizite Parameter abgebildet, damit das Verhalten mit der MQL5-Implementierung konsistent bleibt.
Handelslogik
- Zur konfigurierten
StartHourzeichnet die Strategie den Session-Eröffnungspreis auf. Zwei dynamische Niveaus werden dann beiOffsetPoints(in Punkten) über und unter diesem Anker gezeichnet. - Immer wenn eine abgeschlossene Kerze über dem oberen Niveau schließt, ist die Strategie:
- Plant einen Long-Einstieg, der auf der nächsten Kerze ausgeführt wird (unter Beachtung der
SignalBar-Verzögerung), nur wenn der aktuelle Balken nicht mehr über der Band liegt. - Schließt jede offene Short-Position sofort, wenn
SellPosCloseaktiviert ist.
- Plant einen Long-Einstieg, der auf der nächsten Kerze ausgeführt wird (unter Beachtung der
- Immer wenn eine abgeschlossene Kerze unter dem unteren Niveau schließt, ist die Strategie:
- Plant einen Short-Einstieg für die nächste Kerze, sofern der aktuelle Balken nicht mehr unter der Band liegt.
- Schließt jede offene Long-Position sofort, wenn
BuyPosCloseaktiviert ist.
- Einstiege werden beim ersten Update der nächsten Kerze dank
TryExecutePendingEntriesausgeführt. Dies entspricht dem ursprünglichen Experten, der die Order bis zum Beginn der neuen Kerze verzögert.
Der Signalverzögerungsparameter SignalBar reproduziert den ursprünglichen CopyBuffer-Shift. Ein Wert von 0 reagiert auf den zuletzt geschlossenen Balken, während 1 einen zusätzlichen Balken wartet, bevor gehandelt wird, was zusätzliche Bestätigung gibt.
Order-Management
- Stop-Loss / Take-Profit – Die Abstände werden in Punkten (
StopLossPoints,TakeProfitPoints) angegeben und unter Verwendung des Instrumentenschritts in Preis umgerechnet. Beide Niveaus werden anhand der Kerzenhochs/-tiefs überwacht, sodass intrabar-Berührungen einen Ausstieg auslösen. - Zeitbasierter Ausstieg – Wenn
TimeTradewahr ist, wird die Position nachHoldingMinutesMinuten zwangsliquidiert, was dennTime-Timer aus dem MQL5-Code spiegelt. - Manuelle Schließungen – Ausbruchssignale in entgegengesetzter Richtung schließen den laufenden Trade, wenn das entsprechende
BuyPosClose- oderSellPosClose-Flag aktiviert ist.
Geldmanagement
Der MoneyMode-Parameter reproduziert die MarginMode-Aufzählung:
Lot– festes Volumen gleichMoneyManagement.BalanceundFreeMargin– verwenden Kontoeigenkapital- oder freie-Margin-Vielfache (MoneyManagement * Eigenkapital / Preis).LossBalanceundLossFreeMargin– risikobasierte Größenbestimmung, die den gewünschten Kapitalanteil durch den Stop-Abstand teilt.
Wenn StopLossPoints auf null gesetzt ist, fallen die Risikomodi auf preisbasierte Größenbestimmung zurück.
Parameter
| Parameter | Beschreibung | Standard |
|---|---|---|
MoneyManagement |
Basiskoeffizient zur Positionsgrößenbestimmung abhängig von MoneyMode. |
0.1 |
MoneyMode |
Positionsgrößenmodell (Lot, Balance, FreeMargin, LossBalance, LossFreeMargin). |
Lot |
StopLossPoints |
Stop-Loss-Abstand in Punkten vom Ausführungspreis. | 1000 |
TakeProfitPoints |
Take-Profit-Abstand in Punkten vom Ausführungspreis. | 2000 |
DeviationPoints |
Informativer Parameter aus dem Experten (Slippage-Einstellung in Punkten). | 10 |
BuyPosOpen / SellPosOpen |
Long- und Short-Einstiege aktivieren oder deaktivieren. | true |
BuyPosClose / SellPosClose |
Dem entgegengesetzten Ausbruch erlauben, Positionen zwangsweise zu schließen. | true |
TimeTrade |
Den maximalen Haltezeit-Filter aktivieren. | true |
HoldingMinutes |
Maximale Positionslebensdauer in Minuten. | 720 |
OffsetPoints |
Abstand der Pivot-Bänder vom Session-Open in Punkten. | 200 |
SignalBar |
Anzahl der Balken zur Verzögerung der Signalauswertung (0 = letzter geschlossener Balken). | 1 |
CandleType |
Haupt-Zeitrahmen zur Berechnung des Indikators. | TimeSpan.FromHours(1).TimeFrame() |
StartHour |
Stunde des Tages (0-23), die den Session-Eröffnungspreis definiert. | 0 |
Verwendungshinweise
- Die Strategie setzt voraus, dass das Wertpapier einen gültigen
PriceStepbereitstellt. Wenn dem Instrument diese Metadaten fehlen, wird ein Fallback von0.0001verwendet. - Da Einstiege beim ersten Update einer neuen Kerze ausgelöst werden, folgt der tatsächliche Ausführungspreis dem Markt zu diesem Zeitpunkt, genau wie beim Experten, was in schnellen Märkten vom theoretischen Eröffnungspreis abweichen kann.
- Um die ursprüngliche Indikator-Überlagerung zu replizieren, halten Sie den Backtest-Zeitrahmen bei oder unter H1, da das MQL5-Skript nur auf stündlichen oder niedrigeren Perioden arbeitet.
- Setzen Sie
SignalBarauf0für reaktiveres Verhalten oder auf1(Standard), um nach einem Ausbruch eine extra Kerze zu warten.
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()