Daily Trend Reversal
Обзор
Daily Trend Reversal — порт советника MetaTrader 4 dailyTrendReversal_D1. Стратегия ориентирует внутридневную торговлю на текущие дневные уровни открытия, максимума и минимума. Сделки разрешаются только тогда, когда ценовое движение и индикатор Commodity Channel Index (CCI) подтверждают одно направление. Торговля ограничена настраиваемой GMT-сессией, может быть приостановлена после достижения дневной цели по прибыли и умеет немедленно выходить из позиции при смене фильтров на противоположный тренд.
Логика стратегии
Фильтры дневного смещения
- Пошаговый фильтр — до трёх условий проверяют силу дневного импульса:
- Расстояние от текущей цены до дневного экстремума должно превышать заданный риск (в пунктах).
- Расстояние от цены открытия до противоположного экстремума также должно быть больше риска, а текущая цена обязана оставаться в пределах 10 пунктов от дневного открытия.
- (Опционально) Текущая свеча должна закрыться в сторону тренда, пока цена остаётся не далее 10 пунктов от открытия дня.
- Доминирование диапазона — сравнивает длину движения вверх (open → high) и вниз (open → low); преобладающая сторона определяет активный тренд.
- Тренд CCI — три последних завершённых значения CCI должны монотонно расти (для лонга) или падать (для шорта).
Правила входа
- Лонги
- Разрешены только в пределах торгового окна (GMT) и по будням.
- Цена выше дневного открытия, пошаговый фильтр подтверждает восходящее направление, доминирование диапазона указывает вверх, а тренд CCI растущий.
- Позиция открывается лишь тогда, когда текущая чистая позиция нулевая или короткая (при наличии шорта он перекрывается входом в лонг).
- Шорты
- Зеркальные условия: цена ниже открытия, пошаговый фильтр показывает спад, доминирование диапазона — вниз, CCI снижается.
- Открывается при нулевой или длинной позиции.
Правила выхода
- Фиксированные TP/SL — задаются в пунктах относительно цены входа; значение
0отключает соответствующий уровень. - Контроль сессии и времени удержания — после наступления времени закрытия (GMT) или по истечении лимита удержания прибыльные сделки закрываются. Убыточные позиции переводятся в режим безубытка и закрываются при возврате к цене входа.
- Выход по развороту (опционально) — при включении лонг закрывается, когда фильтры переходят в «медвежий» режим (цена ниже открытия, дневной и CCI-тренд вниз). Для шорта условия зеркальны.
- Дневной Profit Stop — суммирует реализованную прибыль с начала дня и текущий плавающий результат. При достижении порога все позиции закрываются, новые входы блокируются до ручного повторного включения.
Параметры
- Auto Trading — разрешает или запрещает автоматическое открытие сделок.
- Reversal Exit — включает мгновенное закрытие при появлении противоположного тренда.
- Trend Steps — количество шагов фильтра (от 1 до 3), необходимых для подтверждения тренда.
- Volume — объём заявок при входе по рынку.
- Take Profit (pips) — расстояние до цели,
0отключает. - Stop Loss (pips) — расстояние до стоп-лосса,
0отключает. - Profit Stop — дневная цель по прибыли (в ценовых единицах);
0— без ограничения. - GMT Diff — разница между временем графика и GMT в часах.
- Start Hour / End Hour — границы торгового окна в часах GMT.
- Closing Hour — час GMT, после которого стратегия закрывает или страхует активные позиции.
- Holding Hours — максимальное время удержания позиции до срабатывания логики сессии.
- Risk (pips) — порог в пунктах, используемый в пошаговом фильтре.
- CCI Period — период индикатора CCI.
- Candle Type — таймфрейм расчётов (по умолчанию 15-минутные свечи).
Дополнительная информация
- Размер пункта определяется автоматически на основе шага цены инструмента; для пяти- и трёхзнаковых валютных пар пороги переводятся в корректные приращения.
- Дневная прибыль пересчитывается при первой свече нового торгового дня — текущее значение PnL фиксируется как базовое.
- В составе пакета представлена только C#-реализация; версия на Python отсутствует.
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>
/// Port of the MetaTrader strategy dailyTrendReversal_D1.
/// Combines daily open/high/low levels with a multi-step filter and CCI trend confirmation.
/// Applies strict session control, optional reversal exits, and a configurable daily profit stop.
/// </summary>
public class DailyTrendReversalStrategy : Strategy
{
private readonly StrategyParam<bool> _enableAutoTrading;
private readonly StrategyParam<bool> _enableReversal;
private readonly StrategyParam<int> _trendSteps;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _profitStop;
private readonly StrategyParam<int> _gmtDiff;
private readonly StrategyParam<int> _gmtStartHour;
private readonly StrategyParam<int> _gmtEndHour;
private readonly StrategyParam<int> _gmtClosingHour;
private readonly StrategyParam<int> _holdingHours;
private readonly StrategyParam<int> _riskPips;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<DataType> _candleType;
private CommodityChannelIndex _cci = null!;
private readonly List<decimal> _cciHistory = new(3);
private decimal _pipSize;
private decimal _tenPips;
private DateTime? _currentDay;
private decimal _dailyOpen;
private decimal _dailyHigh;
private decimal _dailyLow;
private decimal _dayPnLBase;
private bool _tradingSuspended;
private decimal _lastClose;
private DateTimeOffset _lastCandleTime;
private decimal _longEntryPrice;
private DateTimeOffset? _longEntryTime;
private decimal _longTakeProfitPrice;
private decimal _longStopPrice;
private bool _longBreakEvenActive;
private decimal _shortEntryPrice;
private DateTimeOffset? _shortEntryTime;
private decimal _shortTakeProfitPrice;
private decimal _shortStopPrice;
private bool _shortBreakEvenActive;
private DateTimeOffset _lastEntryTime;
private enum TrendDirections
{
Flat,
Up,
Down,
}
/// <summary>
/// Enables automated entries within the trading window.
/// </summary>
public bool EnableAutoTrading
{
get => _enableAutoTrading.Value;
set => _enableAutoTrading.Value = value;
}
/// <summary>
/// Enables closing positions when the opposite trend is confirmed.
/// </summary>
public bool EnableReversal
{
get => _enableReversal.Value;
set => _enableReversal.Value = value;
}
/// <summary>
/// Number of step filters applied to the daily trend evaluation.
/// </summary>
public int TrendSteps
{
get => _trendSteps.Value;
set => _trendSteps.Value = value;
}
/// <summary>
/// Take-profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Daily profit target that halts trading when reached (includes floating PnL).
/// </summary>
public decimal ProfitStop
{
get => _profitStop.Value;
set => _profitStop.Value = value;
}
/// <summary>
/// Difference between chart time and GMT in hours.
/// </summary>
public int GmtDiff
{
get => _gmtDiff.Value;
set => _gmtDiff.Value = value;
}
/// <summary>
/// GMT start hour of the active trading window.
/// </summary>
public int GmtStartHour
{
get => _gmtStartHour.Value;
set => _gmtStartHour.Value = value;
}
/// <summary>
/// GMT end hour for accepting new entries.
/// </summary>
public int GmtEndHour
{
get => _gmtEndHour.Value;
set => _gmtEndHour.Value = value;
}
/// <summary>
/// GMT hour when all trades should be protected or closed.
/// </summary>
public int GmtClosingHour
{
get => _gmtClosingHour.Value;
set => _gmtClosingHour.Value = value;
}
/// <summary>
/// Maximum holding time in hours before forcing exits.
/// </summary>
public int HoldingHours
{
get => _holdingHours.Value;
set => _holdingHours.Value = value;
}
/// <summary>
/// Risk threshold in pips used by the trend step filter.
/// </summary>
public int RiskPips
{
get => _riskPips.Value;
set => _riskPips.Value = value;
}
/// <summary>
/// Length of the Commodity Channel Index indicator.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Candle type that drives the strategy calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="DailyTrendReversalStrategy"/> class.
/// </summary>
public DailyTrendReversalStrategy()
{
_enableAutoTrading = Param(nameof(EnableAutoTrading), true)
.SetDisplay("Auto Trading", "Enable automated entries inside the session", "Trading");
_enableReversal = Param(nameof(EnableReversal), true)
.SetDisplay("Reversal Exit", "Close positions on confirmed opposite trend", "Trading");
_trendSteps = Param(nameof(TrendSteps), 3)
.SetRange(0, 3)
.SetDisplay("Trend Steps", "Number of filters used for daily direction", "Trend Filter");
_takeProfitPips = Param(nameof(TakeProfitPips), 30m)
.SetRange(0m, 1000m)
.SetDisplay("Take Profit (pips)", "Distance to fixed take profit (0 disables)", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 0m)
.SetRange(0m, 1000m)
.SetDisplay("Stop Loss (pips)", "Distance to protective stop loss (0 disables)", "Risk");
_profitStop = Param(nameof(ProfitStop), 100m)
.SetRange(0m, 100000m)
.SetDisplay("Profit Stop", "Daily profit target that pauses trading", "Risk");
_gmtDiff = Param(nameof(GmtDiff), 0)
.SetDisplay("GMT Diff", "Chart time minus GMT in hours", "Session");
_gmtStartHour = Param(nameof(GmtStartHour), 5)
.SetRange(0, 23)
.SetDisplay("Start Hour", "Session start hour in GMT", "Session");
_gmtEndHour = Param(nameof(GmtEndHour), 14)
.SetRange(0, 23)
.SetDisplay("End Hour", "Session end hour for new trades (GMT)", "Session");
_gmtClosingHour = Param(nameof(GmtClosingHour), 18)
.SetRange(0, 23)
.SetDisplay("Closing Hour", "Session close hour for active trades (GMT)", "Session");
_holdingHours = Param(nameof(HoldingHours), 10)
.SetRange(0, 48)
.SetDisplay("Holding Hours", "Maximum holding time for positions", "Risk");
_riskPips = Param(nameof(RiskPips), 30)
.SetRange(0, 1000)
.SetDisplay("Risk (pips)", "Risk filter threshold used by trend steps", "Trend Filter");
_cciPeriod = Param(nameof(CciPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Length of the Commodity Channel Index", "Trend Filter");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cciHistory.Clear();
_pipSize = 0m;
_tenPips = 0m;
_currentDay = null;
_dailyOpen = 0m;
_dailyHigh = 0m;
_dailyLow = 0m;
_dayPnLBase = 0m;
_tradingSuspended = false;
_lastClose = 0m;
_lastCandleTime = default;
_longEntryPrice = 0m;
_longEntryTime = null;
_longTakeProfitPrice = 0m;
_longStopPrice = 0m;
_longBreakEvenActive = false;
_shortEntryPrice = 0m;
_shortEntryTime = null;
_shortTakeProfitPrice = 0m;
_shortStopPrice = 0m;
_shortBreakEvenActive = false;
_lastEntryTime = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = GetPipSize();
_tenPips = 10m * _pipSize;
_dayPnLBase = PnL;
_tradingSuspended = false;
_cci = new CommodityChannelIndex
{
Length = CciPeriod,
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _cci);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
_lastCandleTime = candle.CloseTime;
_lastClose = candle.ClosePrice;
UpdateDailyLevels(candle);
UpdateCciHistory(cciValue);
var riskDistance = Math.Max(0, RiskPips) * _pipSize;
var trend = GetDirectionalTrend(candle, riskDistance);
var rangeTrend = GetRangeTrend();
var cciTrend = GetCciTrend();
ManageExistingPositions(candle, rangeTrend, cciTrend, riskDistance);
HandleProfitStop();
if (!CanOpenPositions(candle))
return;
if (!_cci.IsFormed)
return;
EvaluateEntries(candle, trend, rangeTrend, cciTrend);
}
private void EvaluateEntries(ICandleMessage candle, TrendDirections trend, TrendDirections rangeTrend, TrendDirections cciTrend)
{
if (_lastEntryTime != default && candle.CloseTime - _lastEntryTime < TimeSpan.FromHours(3))
return;
var price = candle.ClosePrice;
if (trend == TrendDirections.Up && rangeTrend == TrendDirections.Up && cciTrend == TrendDirections.Up && price > _dailyOpen && Position <= 0m)
{
var volume = Volume + Math.Max(0m, -Position);
if (volume > 0m)
{
BuyMarket(volume);
_lastEntryTime = candle.CloseTime;
LogInfo($"Enter long at {price} due to daily trend confirmation.");
}
}
if (trend == TrendDirections.Down && rangeTrend == TrendDirections.Down && cciTrend == TrendDirections.Down && price < _dailyOpen && Position >= 0m)
{
var volume = Volume + Math.Max(0m, Position);
if (volume > 0m)
{
SellMarket(volume);
_lastEntryTime = candle.CloseTime;
LogInfo($"Enter short at {price} due to daily trend confirmation.");
}
}
}
private void ManageExistingPositions(ICandleMessage candle, TrendDirections rangeTrend, TrendDirections cciTrend, decimal riskDistance)
{
if (Position > 0m)
{
ManageLongPosition(candle, rangeTrend, cciTrend, riskDistance);
}
else if (Position < 0m)
{
ManageShortPosition(candle, rangeTrend, cciTrend, riskDistance);
}
}
private void ManageLongPosition(ICandleMessage candle, TrendDirections rangeTrend, TrendDirections cciTrend, decimal riskDistance)
{
var price = candle.ClosePrice;
if (_longTakeProfitPrice > 0m && price >= _longTakeProfitPrice)
{
SellMarket(Position);
LogInfo("Long take profit reached.");
return;
}
if (_longStopPrice > 0m && price <= _longStopPrice)
{
SellMarket(Position);
LogInfo("Long stop loss triggered.");
return;
}
var holdingExceeded = HoldingHours > 0 && _longEntryTime is DateTimeOffset longEntry && candle.CloseTime - longEntry >= TimeSpan.FromHours(HoldingHours);
var closingHourReached = GmtClosingHour > 0 && IsAfterOrEqualHour(candle.CloseTime, GmtClosingHour + GmtDiff);
if ((holdingExceeded || closingHourReached) && _longEntryPrice != 0m)
{
if (price > _longEntryPrice)
{
SellMarket(Position);
LogInfo("Long closed with profit due to session or holding limit.");
return;
}
if (!_longBreakEvenActive)
{
_longBreakEvenActive = true;
LogInfo("Long switched to break-even mode due to session or holding limit.");
}
}
if (_longBreakEvenActive && _longEntryPrice != 0m && price >= _longEntryPrice)
{
SellMarket(Position);
_longBreakEvenActive = false;
LogInfo("Long closed at break-even after session limit.");
return;
}
if (EnableReversal && _dailyOpen != 0m)
{
var step1 = TrendSteps >= 0 && price - _dailyLow > riskDistance;
var step2 = TrendSteps >= 2 && _dailyHigh - _dailyOpen >= riskDistance && _dailyOpen - price <= _tenPips;
if (price < _dailyOpen && (step1 || step2) && rangeTrend == TrendDirections.Down && cciTrend == TrendDirections.Down)
{
SellMarket(Position);
LogInfo("Long reversed due to opposite trend confirmation.");
}
}
}
private void ManageShortPosition(ICandleMessage candle, TrendDirections rangeTrend, TrendDirections cciTrend, decimal riskDistance)
{
var price = candle.ClosePrice;
if (_shortTakeProfitPrice > 0m && price <= _shortTakeProfitPrice)
{
BuyMarket(Math.Abs(Position));
LogInfo("Short take profit reached.");
return;
}
if (_shortStopPrice > 0m && price >= _shortStopPrice)
{
BuyMarket(Math.Abs(Position));
LogInfo("Short stop loss triggered.");
return;
}
var holdingExceeded = HoldingHours > 0 && _shortEntryTime is DateTimeOffset shortEntry && candle.CloseTime - shortEntry >= TimeSpan.FromHours(HoldingHours);
var closingHourReached = GmtClosingHour > 0 && IsAfterOrEqualHour(candle.CloseTime, GmtClosingHour + GmtDiff);
if ((holdingExceeded || closingHourReached) && _shortEntryPrice != 0m)
{
if (price < _shortEntryPrice)
{
BuyMarket(Math.Abs(Position));
LogInfo("Short closed with profit due to session or holding limit.");
return;
}
if (!_shortBreakEvenActive)
{
_shortBreakEvenActive = true;
LogInfo("Short switched to break-even mode due to session or holding limit.");
}
}
if (_shortBreakEvenActive && _shortEntryPrice != 0m && price <= _shortEntryPrice)
{
BuyMarket(Math.Abs(Position));
_shortBreakEvenActive = false;
LogInfo("Short closed at break-even after session limit.");
return;
}
if (EnableReversal && _dailyOpen != 0m)
{
var step1 = TrendSteps >= 0 && _dailyHigh - price > riskDistance;
var step2 = TrendSteps >= 2 && _dailyOpen - _dailyLow >= riskDistance && price - _dailyOpen <= _tenPips;
if (price > _dailyOpen && (step1 || step2) && rangeTrend == TrendDirections.Up && cciTrend == TrendDirections.Up)
{
BuyMarket(Math.Abs(Position));
LogInfo("Short reversed due to opposite trend confirmation.");
}
}
}
private void HandleProfitStop()
{
if (ProfitStop <= 0m || _tradingSuspended)
return;
var realized = PnL - _dayPnLBase;
var floating = 0m;
var total = realized + floating;
if (total >= ProfitStop)
{
_tradingSuspended = true;
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
LogInfo($"Trading suspended after reaching daily profit stop of {ProfitStop}.");
}
}
private bool CanOpenPositions(ICandleMessage candle)
{
if (!EnableAutoTrading || _tradingSuspended)
return false;
if (_currentDay is null)
return false;
if (!IsWeekday(candle.OpenTime))
return false;
if (!IsWithinTradingWindow(candle.OpenTime))
return false;
return true;
}
private void UpdateDailyLevels(ICandleMessage candle)
{
var day = candle.OpenTime.Date;
if (_currentDay != day)
{
_currentDay = day;
_dailyOpen = candle.OpenPrice;
_dailyHigh = candle.HighPrice;
_dailyLow = candle.LowPrice;
_dayPnLBase = PnL;
_longBreakEvenActive = false;
_shortBreakEvenActive = false;
}
else
{
_dailyHigh = Math.Max(_dailyHigh, candle.HighPrice);
_dailyLow = Math.Min(_dailyLow, candle.LowPrice);
}
}
private void UpdateCciHistory(decimal cciValue)
{
if (_cci.IsFormed)
{
_cciHistory.Insert(0, cciValue);
if (_cciHistory.Count > 3)
_cciHistory.RemoveAt(_cciHistory.Count - 1);
}
}
private TrendDirections GetCciTrend()
{
if (_cciHistory.Count < 3)
return TrendDirections.Flat;
var current = _cciHistory[0];
var previous = _cciHistory[1];
var older = _cciHistory[2];
if (current >= previous && previous >= older)
return TrendDirections.Up;
if (current <= previous && previous <= older)
return TrendDirections.Down;
return TrendDirections.Flat;
}
private TrendDirections GetDirectionalTrend(ICandleMessage candle, decimal riskDistance)
{
if (_currentDay is null)
return TrendDirections.Flat;
var price = candle.ClosePrice;
if (price > _dailyOpen)
{
var step1 = TrendSteps >= 0 && _dailyHigh - price > riskDistance;
var step2 = TrendSteps >= 2 && _dailyOpen - _dailyLow >= riskDistance && price - _dailyOpen <= _tenPips;
var step3 = TrendSteps >= 3 && price - _dailyOpen <= _tenPips && candle.ClosePrice > candle.OpenPrice;
if (step1 || step2 || step3)
return TrendDirections.Up;
}
else if (price < _dailyOpen)
{
var step1 = TrendSteps >= 0 && price - _dailyLow > riskDistance;
var step2 = TrendSteps >= 2 && _dailyHigh - _dailyOpen >= riskDistance && _dailyOpen - price <= _tenPips;
var step3 = TrendSteps >= 3 && _dailyOpen - price <= _tenPips && candle.ClosePrice < candle.OpenPrice;
if (step1 || step2 || step3)
return TrendDirections.Down;
}
return TrendDirections.Flat;
}
private TrendDirections GetRangeTrend()
{
var upDistance = _dailyHigh - _dailyOpen;
var downDistance = _dailyOpen - _dailyLow;
if (upDistance > downDistance)
return TrendDirections.Up;
if (upDistance < downDistance)
return TrendDirections.Down;
return TrendDirections.Flat;
}
private bool IsWeekday(DateTimeOffset time)
{
var day = time.DayOfWeek;
return day is not DayOfWeek.Saturday and not DayOfWeek.Sunday;
}
private bool IsWithinTradingWindow(DateTimeOffset time)
{
var hour = time.Hour;
var start = NormalizeHour(GmtStartHour + GmtDiff);
var end = NormalizeHour(GmtEndHour + GmtDiff);
if (start == end)
return false;
return start < end ? hour >= start && hour < end : hour >= start || hour < end;
}
private bool IsAfterOrEqualHour(DateTimeOffset time, int targetHour)
{
var hour = time.Hour;
var normalizedTarget = NormalizeHour(targetHour);
return hour >= normalizedTarget;
}
private static int NormalizeHour(int hour)
{
var normalized = hour % 24;
return normalized < 0 ? normalized + 24 : normalized;
}
private decimal GetPipSize()
{
var step = Security.PriceStep ?? 0.0001m;
var decimals = Security.Decimals ?? 0;
if (decimals >= 3)
return step * 10m;
return step > 0m ? step : 0.0001m;
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (Position > 0m)
{
_longEntryPrice = _lastClose;
_longEntryTime = _lastCandleTime;
_longTakeProfitPrice = TakeProfitPips > 0m ? _longEntryPrice + TakeProfitPips * _pipSize : 0m;
_longStopPrice = StopLossPips > 0m ? _longEntryPrice - StopLossPips * _pipSize : 0m;
_longBreakEvenActive = false;
}
else
{
_longEntryTime = null;
_longTakeProfitPrice = 0m;
_longStopPrice = 0m;
if (Position <= 0m)
_longEntryPrice = 0m;
}
if (Position < 0m)
{
_shortEntryPrice = _lastClose;
_shortEntryTime = _lastCandleTime;
_shortTakeProfitPrice = TakeProfitPips > 0m ? _shortEntryPrice - TakeProfitPips * _pipSize : 0m;
_shortStopPrice = StopLossPips > 0m ? _shortEntryPrice + StopLossPips * _pipSize : 0m;
_shortBreakEvenActive = false;
}
else
{
_shortEntryTime = null;
_shortTakeProfitPrice = 0m;
_shortStopPrice = 0m;
if (Position >= 0m)
_shortEntryPrice = 0m;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class daily_trend_reversal_strategy(Strategy):
"""
Port of the MetaTrader strategy dailyTrendReversal_D1.
Combines daily open/high/low levels with a multi-step filter and CCI trend confirmation.
Applies strict session control, optional reversal exits, and a configurable daily profit stop.
"""
FLAT = 0
UP = 1
DOWN = 2
def __init__(self):
super(daily_trend_reversal_strategy, self).__init__()
self._enable_auto_trading = self.Param("EnableAutoTrading", True) \
.SetDisplay("Auto Trading", "Enable automated entries inside the session", "Trading")
self._enable_reversal = self.Param("EnableReversal", True) \
.SetDisplay("Reversal Exit", "Close positions on confirmed opposite trend", "Trading")
self._trend_steps = self.Param("TrendSteps", 3) \
.SetDisplay("Trend Steps", "Number of filters used for daily direction", "Trend Filter")
self._take_profit_pips = self.Param("TakeProfitPips", 30.0) \
.SetDisplay("Take Profit (pips)", "Distance to fixed take profit (0 disables)", "Risk")
self._stop_loss_pips = self.Param("StopLossPips", 0.0) \
.SetDisplay("Stop Loss (pips)", "Distance to protective stop loss (0 disables)", "Risk")
self._profit_stop = self.Param("ProfitStop", 100.0) \
.SetDisplay("Profit Stop", "Daily profit target that pauses trading", "Risk")
self._gmt_diff = self.Param("GmtDiff", 0) \
.SetDisplay("GMT Diff", "Chart time minus GMT in hours", "Session")
self._gmt_start_hour = self.Param("GmtStartHour", 5) \
.SetDisplay("Start Hour", "Session start hour in GMT", "Session")
self._gmt_end_hour = self.Param("GmtEndHour", 14) \
.SetDisplay("End Hour", "Session end hour for new trades (GMT)", "Session")
self._gmt_closing_hour = self.Param("GmtClosingHour", 18) \
.SetDisplay("Closing Hour", "Session close hour for active trades (GMT)", "Session")
self._holding_hours = self.Param("HoldingHours", 10) \
.SetDisplay("Holding Hours", "Maximum holding time for positions", "Risk")
self._risk_pips = self.Param("RiskPips", 30) \
.SetDisplay("Risk (pips)", "Risk filter threshold used by trend steps", "Trend Filter")
self._cci_period = self.Param("CciPeriod", 15) \
.SetDisplay("CCI Period", "Length of the Commodity Channel Index", "Trend Filter")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Primary timeframe for calculations", "General")
self._cci_history = []
self._current_day = None
self._daily_open = 0.0
self._daily_high = 0.0
self._daily_low = 0.0
self._day_pnl_base = 0.0
self._trading_suspended = False
self._last_close = 0.0
self._last_candle_time = None
self._pip_size = 0.0
self._ten_pips = 0.0
self._long_entry_price = 0.0
self._long_entry_time = None
self._long_tp_price = 0.0
self._long_stop_price = 0.0
self._long_break_even = False
self._short_entry_price = 0.0
self._short_entry_time = None
self._short_tp_price = 0.0
self._short_stop_price = 0.0
self._short_break_even = False
self._cci_formed = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(daily_trend_reversal_strategy, self).OnReseted()
self._cci_history = []
self._current_day = None
self._daily_open = 0.0
self._daily_high = 0.0
self._daily_low = 0.0
self._day_pnl_base = 0.0
self._trading_suspended = False
self._last_close = 0.0
self._last_candle_time = None
self._long_entry_price = 0.0
self._long_entry_time = None
self._long_tp_price = 0.0
self._long_stop_price = 0.0
self._long_break_even = False
self._short_entry_price = 0.0
self._short_entry_time = None
self._short_tp_price = 0.0
self._short_stop_price = 0.0
self._short_break_even = False
self._cci_formed = False
def OnStarted2(self, time):
super(daily_trend_reversal_strategy, self).OnStarted2(time)
step = 0.0001
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 0.0001
decimals = 0
if self.Security is not None and self.Security.Decimals is not None:
decimals = int(self.Security.Decimals)
if decimals >= 3:
self._pip_size = step * 10.0
else:
self._pip_size = step if step > 0 else 0.0001
self._ten_pips = 10.0 * self._pip_size
self._day_pnl_base = float(self.PnL)
self._trading_suspended = False
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(cci, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, cci)
self.DrawOwnTrades(area)
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
self._last_candle_time = candle.CloseTime
self._last_close = float(candle.ClosePrice)
cci_value = float(cci_value)
self._update_daily_levels(candle)
self._update_cci_history(cci_value)
risk_distance = max(0, self._risk_pips.Value) * self._pip_size
trend = self._get_directional_trend(candle, risk_distance)
range_trend = self._get_range_trend()
cci_trend = self._get_cci_trend()
self._manage_existing_positions(candle, range_trend, cci_trend, risk_distance)
self._handle_profit_stop()
if not self._can_open_positions(candle):
return
if not self._cci_formed:
return
self._evaluate_entries(candle, trend, range_trend, cci_trend)
def _evaluate_entries(self, candle, trend, range_trend, cci_trend):
price = float(candle.ClosePrice)
if (trend == self.UP and range_trend == self.UP and cci_trend == self.UP
and price > self._daily_open and self.Position <= 0):
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
if (trend == self.DOWN and range_trend == self.DOWN and cci_trend == self.DOWN
and price < self._daily_open and self.Position >= 0):
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def _manage_existing_positions(self, candle, range_trend, cci_trend, risk_distance):
if self.Position > 0:
self._manage_long(candle, range_trend, cci_trend, risk_distance)
elif self.Position < 0:
self._manage_short(candle, range_trend, cci_trend, risk_distance)
def _manage_long(self, candle, range_trend, cci_trend, risk_distance):
price = float(candle.ClosePrice)
if self._long_tp_price > 0 and price >= self._long_tp_price:
self.SellMarket()
return
if self._long_stop_price > 0 and price <= self._long_stop_price:
self.SellMarket()
return
holding_exceeded = False
if self._holding_hours.Value > 0 and self._long_entry_time is not None:
diff = candle.CloseTime - self._long_entry_time
if diff >= TimeSpan.FromHours(self._holding_hours.Value):
holding_exceeded = True
closing_reached = False
if self._gmt_closing_hour.Value > 0:
closing_reached = self._is_after_or_equal_hour(candle.CloseTime, self._gmt_closing_hour.Value + self._gmt_diff.Value)
if (holding_exceeded or closing_reached) and self._long_entry_price != 0:
if price > self._long_entry_price:
self.SellMarket()
return
if not self._long_break_even:
self._long_break_even = True
if self._long_break_even and self._long_entry_price != 0 and price >= self._long_entry_price:
self.SellMarket()
self._long_break_even = False
return
if self._enable_reversal.Value and self._daily_open != 0:
step1 = self._trend_steps.Value >= 0 and (price - self._daily_low > risk_distance)
step2 = self._trend_steps.Value >= 2 and (self._daily_high - self._daily_open >= risk_distance) and (self._daily_open - price <= self._ten_pips)
if price < self._daily_open and (step1 or step2) and range_trend == self.DOWN and cci_trend == self.DOWN:
self.SellMarket()
def _manage_short(self, candle, range_trend, cci_trend, risk_distance):
price = float(candle.ClosePrice)
if self._short_tp_price > 0 and price <= self._short_tp_price:
self.BuyMarket()
return
if self._short_stop_price > 0 and price >= self._short_stop_price:
self.BuyMarket()
return
holding_exceeded = False
if self._holding_hours.Value > 0 and self._short_entry_time is not None:
diff = candle.CloseTime - self._short_entry_time
if diff >= TimeSpan.FromHours(self._holding_hours.Value):
holding_exceeded = True
closing_reached = False
if self._gmt_closing_hour.Value > 0:
closing_reached = self._is_after_or_equal_hour(candle.CloseTime, self._gmt_closing_hour.Value + self._gmt_diff.Value)
if (holding_exceeded or closing_reached) and self._short_entry_price != 0:
if price < self._short_entry_price:
self.BuyMarket()
return
if not self._short_break_even:
self._short_break_even = True
if self._short_break_even and self._short_entry_price != 0 and price <= self._short_entry_price:
self.BuyMarket()
self._short_break_even = False
return
if self._enable_reversal.Value and self._daily_open != 0:
step1 = self._trend_steps.Value >= 0 and (self._daily_high - price > risk_distance)
step2 = self._trend_steps.Value >= 2 and (self._daily_open - self._daily_low >= risk_distance) and (price - self._daily_open <= self._ten_pips)
if price > self._daily_open and (step1 or step2) and range_trend == self.UP and cci_trend == self.UP:
self.BuyMarket()
def _handle_profit_stop(self):
if self._profit_stop.Value <= 0 or self._trading_suspended:
return
realized = float(self.PnL) - self._day_pnl_base
if realized >= self._profit_stop.Value:
self._trading_suspended = True
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
def _can_open_positions(self, candle):
if not self._enable_auto_trading.Value or self._trading_suspended:
return False
if self._current_day is None:
return False
dow = candle.OpenTime.DayOfWeek
from System import DayOfWeek
if dow == DayOfWeek.Saturday or dow == DayOfWeek.Sunday:
return False
if not self._is_within_trading_window(candle.OpenTime):
return False
return True
def _update_daily_levels(self, candle):
day = candle.OpenTime.Date
if self._current_day is None or self._current_day != day:
self._current_day = day
self._daily_open = float(candle.OpenPrice)
self._daily_high = float(candle.HighPrice)
self._daily_low = float(candle.LowPrice)
self._day_pnl_base = float(self.PnL)
self._long_break_even = False
self._short_break_even = False
else:
self._daily_high = max(self._daily_high, float(candle.HighPrice))
self._daily_low = min(self._daily_low, float(candle.LowPrice))
def _update_cci_history(self, cci_value):
if self._cci_formed or len(self._cci_history) >= 2:
self._cci_formed = True
self._cci_history.insert(0, cci_value)
if len(self._cci_history) > 3:
self._cci_history.pop()
def _get_cci_trend(self):
if len(self._cci_history) < 3:
return self.FLAT
current = self._cci_history[0]
previous = self._cci_history[1]
older = self._cci_history[2]
if current >= previous and previous >= older:
return self.UP
if current <= previous and previous <= older:
return self.DOWN
return self.FLAT
def _get_directional_trend(self, candle, risk_distance):
if self._current_day is None:
return self.FLAT
price = float(candle.ClosePrice)
ts = self._trend_steps.Value
if price > self._daily_open:
step1 = ts >= 0 and (self._daily_high - price > risk_distance)
step2 = ts >= 2 and (self._daily_open - self._daily_low >= risk_distance) and (price - self._daily_open <= self._ten_pips)
step3 = ts >= 3 and (price - self._daily_open <= self._ten_pips) and (float(candle.ClosePrice) > float(candle.OpenPrice))
if step1 or step2 or step3:
return self.UP
elif price < self._daily_open:
step1 = ts >= 0 and (price - self._daily_low > risk_distance)
step2 = ts >= 2 and (self._daily_high - self._daily_open >= risk_distance) and (self._daily_open - price <= self._ten_pips)
step3 = ts >= 3 and (self._daily_open - price <= self._ten_pips) and (float(candle.ClosePrice) < float(candle.OpenPrice))
if step1 or step2 or step3:
return self.DOWN
return self.FLAT
def _get_range_trend(self):
up_distance = self._daily_high - self._daily_open
down_distance = self._daily_open - self._daily_low
if up_distance > down_distance:
return self.UP
if up_distance < down_distance:
return self.DOWN
return self.FLAT
def _is_within_trading_window(self, time):
hour = time.Hour
start = self._normalize_hour(self._gmt_start_hour.Value + self._gmt_diff.Value)
end = self._normalize_hour(self._gmt_end_hour.Value + self._gmt_diff.Value)
if start == end:
return False
if start < end:
return hour >= start and hour < end
else:
return hour >= start or hour < end
def _is_after_or_equal_hour(self, time, target_hour):
hour = time.Hour
normalized = self._normalize_hour(target_hour)
return hour >= normalized
def _normalize_hour(self, hour):
normalized = hour % 24
return normalized + 24 if normalized < 0 else normalized
def OnPositionReceived(self, position):
super(daily_trend_reversal_strategy, self).OnPositionReceived(position)
tp_pips = self._take_profit_pips.Value
sl_pips = self._stop_loss_pips.Value
if self.Position > 0:
self._long_entry_price = self._last_close
self._long_entry_time = self._last_candle_time
self._long_tp_price = self._long_entry_price + tp_pips * self._pip_size if tp_pips > 0 else 0.0
self._long_stop_price = self._long_entry_price - sl_pips * self._pip_size if sl_pips > 0 else 0.0
self._long_break_even = False
else:
self._long_entry_time = None
self._long_tp_price = 0.0
self._long_stop_price = 0.0
if self.Position <= 0:
self._long_entry_price = 0.0
if self.Position < 0:
self._short_entry_price = self._last_close
self._short_entry_time = self._last_candle_time
self._short_tp_price = self._short_entry_price - tp_pips * self._pip_size if tp_pips > 0 else 0.0
self._short_stop_price = self._short_entry_price + sl_pips * self._pip_size if sl_pips > 0 else 0.0
self._short_break_even = False
else:
self._short_entry_time = None
self._short_tp_price = 0.0
self._short_stop_price = 0.0
if self.Position >= 0:
self._short_entry_price = 0.0
def CreateClone(self):
return daily_trend_reversal_strategy()