Ver no GitHub

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

  1. Colete velas diárias (padrão: 1 dia) para obter a abertura e o fechamento do dia anterior.
  2. Às sextas-feiras, monitore as velas intradiárias (padrão: 1 minuto) para detectar a hora de entrada configurada.
  3. 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).
  4. Opcionalmente, feche a posição automaticamente quando a hora de saída configurada for atingida.
  5. 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;
		}
	}
}