Estratégia de sessão de sexta-feira eletrônica
Visão geral
A estratégia de sessão E-Friday replica o clássico consultor especialista MetaTrader que negocia apenas às sextas-feiras. Observa a vela diária anterior e abre uma posição em uma hora configurada no início da sessão de sexta-feira. A direção é contrária: se o dia anterior fechou abaixo da abertura (vela de baixa), a estratégia compra; se o dia anterior fechou acima de sua abertura (vela de alta), a estratégia vende. As posições são gerenciadas intradiariamente e podem ser fechadas automaticamente após uma hora configurável ou por paradas de proteção.
Regras de negociação
- Colete velas diárias (padrão: 1 dia) para obter a abertura e o fechamento do dia anterior.
- Às sextas-feiras, monitore as velas intradiárias (padrão: 1 minuto) para detectar a hora de entrada configurada.
- Na primeira vela da hora de entrada:
- Opere comprado quando o dia anterior foi de baixa.
- Opere vendido quando o dia anterior foi de alta.
- Ignore a negociação se o dia anterior tiver sido um doji (abertura é igual a fechamento).
- Opcionalmente, feche a posição automaticamente quando a hora de saída configurada for atingida.
- Gerencie saídas usando stop-loss, take-profit e lógica opcional de trailing stop que imita o Expert Advisor original, incluindo a ativação de lucro e limites de trailing step.
Notas de implementação
- Usa StockSharp assinaturas de vela de alto nível para contexto diário e tempo intradiário.
- Converte controles de risco baseados em pontos da versão MQL em compensações de preço absoluto usando a etapa de preço do título.
- Mantém trailing stops no código, atualizando-os em cada vela finalizada e fechando a posição quando os preços extremos são violados.
- Garante apenas uma negociação por sexta-feira, rastreando o estado diário.
- Suporta entradas longas e curtas, respeitando o controle original do número mágico, negociando um único símbolo por instância de estratégia.
Parâmetros
| Nome |
Descrição |
Padrão |
Volume |
Tamanho da negociação em lotes/contratos. |
0.1 |
StopLossPoints |
Distância de stop-loss em etapas de preço (0 desativa). |
75 |
TakeProfitPoints |
Distância de lucro em etapas de preço (0 desativa). |
0 |
HourOpen |
Hora do dia (0-23) para abertura da posição. |
7 |
UseClosePositions |
Habilite o fechamento automático após a hora de saída. |
true |
HourClose |
Hora do dia (0-23) para fechar a posição, se habilitada. |
19 |
UseTrailing |
Habilite ajustes de trailing stop. |
true |
ProfitTrailing |
Exija que o lucro exceda a distância de trilha antes que a trilha seja ativada. |
true |
TrailingStopPoints |
Distância do trailing stop em etapas de preço. |
60 |
TrailingStepPoints |
Pontos adicionais necessários antes de apertar o batente móvel. |
5 |
IntradayCandleType |
Tipo de vela para tempo intradiário (velas padrão de 1 minuto). |
TimeSpan.FromMinutes(1) |
DailyCandleType |
Tipo de vela para detecção diária de sentimento (velas padrão de 1 dia). |
TimeSpan.FromDays(1) |
Dicas de uso
- Alinhe o pregão do instrumento para que o horário de entrada de sexta-feira corresponda à abertura desejada do mercado.
- Ao configurar os valores de stop-loss e trailing, expresse-os nos mesmos "pontos" usados pela etapa de preço do símbolo para reproduzir o comportamento MetaTrader.
- A estratégia é projetada para uma única negociação por sexta-feira. Para negociar vários símbolos, execute instâncias de estratégia separadas por símbolo.
Diferenças do original EA
- Usa dados de fechamento de velas para tomada de decisão, considerando os preços originais pesquisados por tick.
- As saídas de proteção são executadas por meio de ordens de mercado quando as velas indicam que os níveis de parada ou meta foram atingidos dentro do intervalo.
- Os parâmetros de estratégia são expostos por meio do sistema
StrategyParam de StockSharp, suportando otimização e vinculação de UI.
Detalhes de execução: a entrada de sexta é avaliada no primeiro candle intradiário concluído cuja hora de abertura corresponde a HourOpen, não no primeiro tick. É necessário um candle diário de quinta já concluído. Só há uma tentativa de entrada por sexta, mesmo após uma saída de proteção. O encerramento por horário, stop, take profit e trailing usam as máximas e mínimas de candles intradiários concluídos e fecham por ordens a mercado. O trailing avança apenas após o passo definido e nunca é considerado executado retroativamente no mesmo candle.
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()