Стратегия E-Friday Session
Общее описание
Стратегия E-Friday повторяет классического эксперта MetaTrader, который торгует только по пятницам. Алгоритм анализирует предыдущую дневную свечу и в заданный час пятницы открывает позицию. Направление противоположно движению предыдущего дня: если свеча была медвежьей (закрылась ниже открытия) — совершается покупка, если свеча бычья — совершается продажа. Позиция ведётся внутри дня: её можно закрывать автоматически в заданный час либо по защитным ордерам.
Правила торговли
- Подписываемся на дневные свечи (по умолчанию 1 день), чтобы получить данные об открытии и закрытии предыдущей сессии.
- В пятницу отслеживаем внутридневные свечи (по умолчанию 1 минута), чтобы определить нужный час входа.
- С началом первой свечи выбранного часа:
- Покупаем, если предыдущий день закрылся ниже открытия.
- Продаём, если предыдущий день закрылся выше открытия.
- Пропускаем сигнал, если свеча-дожи (открытие равно закрытию).
- При достижении заданного часа закрытия позиция может быть автоматически закрыта.
- Управление сделкой осуществляется через стоп-лосс, тейк-профит и опциональный трейлинг-стоп, полностью повторяющий логику оригинального эксперта (активация по прибыли и минимальный шаг подтягивания).
Особенности реализации
- Используется высокоуровневый API StockSharp: отдельные подписки на дневные и внутридневные свечи.
- Параметры риска в «пунктах» из MQL переводятся в абсолютные ценовые смещения с учётом минимального шага цены инструмента.
- Трейлинг-стоп ведётся в коде: на каждой завершённой свече проверяются экстремумы и при необходимости позиция закрывается рыночной заявкой.
- Ведётся учёт состояния, чтобы по пятницам открывалась не более одной позиции.
- Поддерживаются длинные и короткие позиции; как и в оригинале, стратегия рассчитана на один инструмент на экземпляр.
Параметры
| Имя |
Описание |
Значение по умолчанию |
Volume |
Объём сделки в лотах/контрактах. |
0.1 |
StopLossPoints |
Дистанция стоп-лосса в шагах цены (0 отключает). |
75 |
TakeProfitPoints |
Дистанция тейк-профита в шагах цены (0 отключает). |
0 |
HourOpen |
Час (0-23), когда открывается позиция. |
7 |
UseClosePositions |
Закрывать ли позицию после заданного часа. |
true |
HourClose |
Час (0-23), после которого позиция закрывается. |
19 |
UseTrailing |
Включить управление трейлинг-стопом. |
true |
ProfitTrailing |
Требовать превышения прибыли над дистанцией трейлинга перед его активацией. |
true |
TrailingStopPoints |
Дистанция трейлинг-стопа в шагах цены. |
60 |
TrailingStepPoints |
Дополнительные шаги перед очередным подтягиванием стопа. |
5 |
IntradayCandleType |
Тип свечей для внутридневного тайминга (по умолчанию 1 минута). |
TimeSpan.FromMinutes(1) |
DailyCandleType |
Тип свечей для анализа предыдущего дня (по умолчанию 1 день). |
TimeSpan.FromDays(1) |
Рекомендации по использованию
- Убедитесь, что торговая сессия инструмента позволяет открыть сделку в выбранный час пятницы.
- Значения стопов и трейлинга задавайте в «пунктах» исходя из минимального шага цены, чтобы максимально приблизиться к поведению MetaTrader.
- Стратегия рассчитана на одну сделку в пятницу. Для торговли несколькими инструментами запустите отдельные экземпляры стратегии.
Отличия от оригинального эксперта
- Решения принимаются по завершённым свечам, тогда как оригинал проверял условия на каждом тике.
- Защитные ордера исполняются рыночными заявками при достижении уровней внутри свечи, поэтому возможны небольшие расхождения во времени выхода.
- Все параметры оформлены через систему
StrategyParam, что позволяет использовать оптимизацию и интеграцию с UI StockSharp.
Уточнение исполнения: вход проверяется на первой завершённой внутридневной свече с часом открытия HourOpen, а не непосредственно на первом тике часа. Обязательно наличие завершённой дневной свечи четверга. За пятницу допустима одна попытка входа, даже если позиция ранее закрыта защитой. Временной выход, стоп, тейк и трейлинг проверяются по экстремумам завершённых внутридневных свечей и исполняются рыночными заявками. Трейлинг переносится только после достижения шага; максимум текущей свечи не используется для фиктивного исполнения стопа в её прошлом.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Takes at most one contrarian position on Friday at the configured hour,
/// using the previous completed daily candle; manages session and price exits.
/// </summary>
public class EFridaySessionStrategy : Strategy
{
private readonly StrategyParam<int> _hourOpen;
private readonly StrategyParam<int> _hourClose;
private readonly StrategyParam<bool> _useClosePositions;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<bool> _useTrailing;
private readonly StrategyParam<bool> _profitTrailing;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<DataType> _intradayCandleType;
private readonly StrategyParam<DataType> _dailyCandleType;
private DateTime? _lastDailyDate;
private decimal _dailyOpen;
private decimal _dailyClose;
private DateTime? _tradedDate;
private decimal? _entryPrice;
private decimal? _trailingLevel;
private decimal _bestPrice;
private Order _entryOrder;
private Order _exitOrder;
public int HourOpen { get => _hourOpen.Value; set => _hourOpen.Value = value; }
public int HourClose { get => _hourClose.Value; set => _hourClose.Value = value; }
public bool UseClosePositions { get => _useClosePositions.Value; set => _useClosePositions.Value = value; }
public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public bool UseTrailing { get => _useTrailing.Value; set => _useTrailing.Value = value; }
public bool ProfitTrailing { get => _profitTrailing.Value; set => _profitTrailing.Value = value; }
public decimal TrailingStopPoints { get => _trailingStopPoints.Value; set => _trailingStopPoints.Value = value; }
public decimal TrailingStepPoints { get => _trailingStepPoints.Value; set => _trailingStepPoints.Value = value; }
public DataType IntradayCandleType { get => _intradayCandleType.Value; set => _intradayCandleType.Value = value; }
public DataType DailyCandleType { get => _dailyCandleType.Value; set => _dailyCandleType.Value = value; }
public EFridaySessionStrategy()
{
Volume = 0.1m;
_hourOpen = Param(nameof(HourOpen), 7).SetRange(0, 23);
_hourClose = Param(nameof(HourClose), 19).SetRange(0, 23);
_useClosePositions = Param(nameof(UseClosePositions), true);
_stopLossPoints = Param(nameof(StopLossPoints), 75m).SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m).SetNotNegative();
_useTrailing = Param(nameof(UseTrailing), true);
_profitTrailing = Param(nameof(ProfitTrailing), true);
_trailingStopPoints = Param(nameof(TrailingStopPoints), 60m).SetNotNegative();
_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m).SetNotNegative();
_intradayCandleType = Param(nameof(IntradayCandleType), TimeSpan.FromMinutes(1).TimeFrame());
_dailyCandleType = Param(nameof(DailyCandleType), TimeSpan.FromDays(1).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, IntradayCandleType), (Security, DailyCandleType)];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_lastDailyDate = null;
_dailyOpen = 0m;
_dailyClose = 0m;
_tradedDate = null;
_entryPrice = null;
_trailingLevel = null;
_bestPrice = 0m;
_entryOrder = null;
_exitOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
if ((Security?.PriceStep ?? 0m) <= 0m)
throw new InvalidOperationException("E-Friday requires a positive PriceStep.");
SubscribeCandles(DailyCandleType).Bind(ProcessDailyCandle).Start();
SubscribeCandles(IntradayCandleType).Bind(ProcessIntradayCandle).Start();
}
private void ProcessDailyCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_lastDailyDate = candle.OpenTime.Date;
_dailyOpen = candle.OpenPrice;
_dailyClose = candle.ClosePrice;
}
private static bool IsPending(Order order) => order is not null &&
order.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessIntradayCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (Position != 0m && !IsPending(_exitOrder))
{
if (UseClosePositions && candle.OpenTime.Hour >= HourClose)
{
ExitPosition("EFriday session close");
return;
}
if (HandleRiskExits(candle))
return;
}
var day = candle.OpenTime.Date;
if (candle.OpenTime.DayOfWeek != DayOfWeek.Friday ||
candle.OpenTime.Hour != HourOpen || _tradedDate == day ||
_lastDailyDate != day.AddDays(-1) ||
Position != 0m || IsPending(_entryOrder) || IsPending(_exitOrder))
return;
if (_dailyClose == _dailyOpen)
return;
var side = _dailyClose < _dailyOpen ? Sides.Buy : Sides.Sell;
var volume = Volume;
if (Security?.VolumeStep is decimal step && step > 0m)
volume = decimal.Floor(volume / step) * step;
if (Security?.MinVolume is decimal min && min > 0m)
volume = Math.Max(volume, min);
if (Security?.MaxVolume is decimal max && max > 0m)
volume = Math.Min(volume, max);
if (volume <= 0m)
return;
_entryOrder = Market(side, volume, "EFriday entry");
_tradedDate = day;
}
private bool HandleRiskExits(ICandleMessage candle)
{
if (_entryPrice is not decimal entry)
return false;
var point = Security?.PriceStep ?? 0m;
var longPosition = Position > 0m;
var stop = StopLossPoints * point;
var take = TakeProfitPoints * point;
if (stop > 0m && (longPosition ? candle.LowPrice <= entry - stop :
candle.HighPrice >= entry + stop))
{
ExitPosition("EFriday stop loss");
return true;
}
if (take > 0m && (longPosition ? candle.HighPrice >= entry + take :
candle.LowPrice <= entry - take))
{
ExitPosition("EFriday take profit");
return true;
}
// Check the previously established trailing level before using the
// latest bar's extremes: intrabar high/low order is not available.
if (_trailingLevel is decimal oldStop &&
(longPosition ? candle.LowPrice <= oldStop : candle.HighPrice >= oldStop))
{
ExitPosition("EFriday trailing stop");
return true;
}
if (!UseTrailing || TrailingStopPoints <= 0m)
return false;
var distance = TrailingStopPoints * point;
var improvement = TrailingStepPoints * point;
_bestPrice = longPosition ? Math.Max(_bestPrice, candle.HighPrice) :
Math.Min(_bestPrice, candle.LowPrice);
var profit = longPosition ? _bestPrice - entry : entry - _bestPrice;
if (ProfitTrailing && profit < distance)
return false;
var next = longPosition ? _bestPrice - distance : _bestPrice + distance;
if (_trailingLevel is not decimal current ||
(longPosition ? next >= current + improvement && next > current :
next <= current - improvement && next < current))
_trailingLevel = next;
return false;
}
private Order Market(Sides side, decimal volume, string comment)
{
var order = new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = volume,
Comment = comment,
};
if (comment == "EFriday entry")
_entryOrder = order;
else
_exitOrder = order;
RegisterOrder(order);
return order;
}
private void ExitPosition(string comment)
{
if (Position == 0m || IsPending(_exitOrder))
return;
_exitOrder = Market(Position > 0m ? Sides.Sell : Sides.Buy, Math.Abs(Position), comment);
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Order == _entryOrder && trade.Trade is not null)
{
_entryPrice = trade.Trade.Price;
_bestPrice = _entryPrice.Value;
_trailingLevel = null;
}
else if (Position == 0m)
{
_entryPrice = null;
_trailingLevel = null;
_bestPrice = 0m;
_exitOrder = null;
}
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import DateTime, DayOfWeek, Decimal, TimeSpan
from StockSharp.Messages import DataType, CandleStates, OrderStates, OrderTypes, Sides
from StockSharp.BusinessEntities import Order
from StockSharp.Algo.Strategies import Strategy
class e_friday_session_strategy(Strategy):
"""One contrarian Friday trade based on the previous completed daily candle."""
def __init__(self):
super(e_friday_session_strategy, self).__init__()
self.Volume = Decimal(0.1)
self._hour_open = self.Param("HourOpen", 7).SetRange(0, 23)
self._hour_close = self.Param("HourClose", 19).SetRange(0, 23)
self._use_close_positions = self.Param("UseClosePositions", True)
self._stop_loss_points = self.Param("StopLossPoints", 75.0).SetNotNegative()
self._take_profit_points = self.Param("TakeProfitPoints", 0.0).SetNotNegative()
self._use_trailing = self.Param("UseTrailing", True)
self._profit_trailing = self.Param("ProfitTrailing", True)
self._trailing_stop_points = self.Param("TrailingStopPoints", 60.0).SetNotNegative()
self._trailing_step_points = self.Param("TrailingStepPoints", 5.0).SetNotNegative()
self._intraday_candle_type = self.Param(
"IntradayCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._daily_candle_type = self.Param(
"DailyCandleType", DataType.TimeFrame(TimeSpan.FromDays(1)))
self._clear_state()
def _clear_state(self):
self._last_daily_date = None
self._daily_open = 0.0
self._daily_close = 0.0
self._traded_date = None
self._entry_price = None
self._trailing_level = None
self._best_price = 0.0
self._entry_order = None
self._exit_order = None
def OnReseted(self):
super(e_friday_session_strategy, self).OnReseted()
self._clear_state()
def GetWorkingSecurities(self):
return [(self.Security, self._intraday_candle_type.Value),
(self.Security, self._daily_candle_type.Value)]
def OnStarted2(self, time):
super(e_friday_session_strategy, self).OnStarted2(time)
self._clear_state()
if float(self.Security.PriceStep or 0) <= 0:
raise ValueError("E-Friday requires a positive PriceStep.")
self.SubscribeCandles(self._daily_candle_type.Value).Bind(self._process_daily).Start()
self.SubscribeCandles(self._intraday_candle_type.Value).Bind(self._process_intraday).Start()
def _process_daily(self, candle):
if candle.State != CandleStates.Finished:
return
self._last_daily_date = candle.OpenTime.Date
self._daily_open = float(candle.OpenPrice)
self._daily_close = float(candle.ClosePrice)
@staticmethod
def _pending(order):
return order is not None and order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_intraday(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position != 0 and not self._pending(self._exit_order):
if (self._use_close_positions.Value and
candle.OpenTime.Hour >= self._hour_close.Value):
self._exit("EFriday session close")
return
if self._handle_risk(candle):
return
day = candle.OpenTime.Date
if (candle.OpenTime.DayOfWeek != DayOfWeek.Friday or
candle.OpenTime.Hour != self._hour_open.Value or
self._traded_date == day or
self._last_daily_date != day.AddDays(-1) or
self.Position != 0 or
self._pending(self._entry_order) or self._pending(self._exit_order)):
return
if self._daily_open == self._daily_close:
return
side = Sides.Buy if self._daily_close < self._daily_open else Sides.Sell
volume = float(self.Volume)
step = float(self.Security.VolumeStep or 0)
if step > 0:
volume = math.floor(volume / step) * step
minimum = float(self.Security.MinVolume or 0)
maximum = float(self.Security.MaxVolume or 0)
if minimum > 0:
volume = max(volume, minimum)
if maximum > 0:
volume = min(volume, maximum)
if volume <= 0:
return
self._entry_order = self._market(side, volume, "EFriday entry")
self._traded_date = day
def _handle_risk(self, candle):
if self._entry_price is None:
return False
entry = self._entry_price
point = float(self.Security.PriceStep)
long_pos = self.Position > 0
stop = float(self._stop_loss_points.Value) * point
take = float(self._take_profit_points.Value) * point
if stop > 0 and (
float(candle.LowPrice) <= entry - stop if long_pos
else float(candle.HighPrice) >= entry + stop):
self._exit("EFriday stop loss")
return True
if take > 0 and (
float(candle.HighPrice) >= entry + take if long_pos
else float(candle.LowPrice) <= entry - take):
self._exit("EFriday take profit")
return True
old_stop = self._trailing_level
if old_stop is not None and (
float(candle.LowPrice) <= old_stop if long_pos
else float(candle.HighPrice) >= old_stop):
self._exit("EFriday trailing stop")
return True
if not self._use_trailing.Value or self._trailing_stop_points.Value <= 0:
return False
distance = float(self._trailing_stop_points.Value) * point
improvement = float(self._trailing_step_points.Value) * point
self._best_price = (max(self._best_price, float(candle.HighPrice)) if long_pos
else min(self._best_price, float(candle.LowPrice)))
profit = (self._best_price - entry if long_pos else entry - self._best_price)
if self._profit_trailing.Value and profit < distance:
return False
next_stop = (self._best_price - distance if long_pos else
self._best_price + distance)
if old_stop is None or (
next_stop >= old_stop + improvement and next_stop > old_stop if long_pos
else next_stop <= old_stop - improvement and next_stop < old_stop):
self._trailing_level = next_stop
return False
def _market(self, side, volume, comment):
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = Decimal(volume)
order.Comment = comment
if comment == "EFriday entry":
self._entry_order = order
else:
self._exit_order = order
self.RegisterOrder(order)
return order
def _exit(self, comment):
if self.Position == 0 or self._pending(self._exit_order):
return
self._exit_order = self._market(
Sides.Sell if self.Position > 0 else Sides.Buy,
abs(float(self.Position)), comment)
def OnOwnTradeReceived(self, trade):
super(e_friday_session_strategy, self).OnOwnTradeReceived(trade)
if trade is not None and trade.Order == self._entry_order and trade.Trade is not None:
self._entry_price = float(trade.Trade.Price)
self._best_price = self._entry_price
self._trailing_level = None
elif self.Position == 0:
self._entry_price = None
self._trailing_level = None
self._best_price = 0.0
self._exit_order = None
def CreateClone(self):
return e_friday_session_strategy()