Estrategia de la sesión del viernes electrónico
Descripción general
La estrategia E-Friday Session replica el clásico asesor experto MetaTrader que opera solo los viernes. Observa la vela diaria anterior y abre una posición a una hora configurada al inicio de la sesión del viernes. La dirección es contraria: si el día anterior cerró por debajo de su apertura (vela bajista), la estrategia compra; si el día anterior cerró por encima de su apertura (vela alcista), la estrategia vende. Las posiciones se gestionan intradía y pueden cerrarse automáticamente después de una hora configurable o mediante paradas de protección.
Reglas de trading
- Reúna velas diarias (predeterminado: 1 día) para obtener la apertura y el cierre del día anterior.
- Los viernes, monitoree las velas intradiarias (predeterminado: 1 minuto) para detectar la hora de entrada configurada.
- A la primera vela de la hora de entrada:
- Vaya en largo cuando el día anterior fue bajista.
- Vaya en corto cuando el día anterior fue alcista.
- Evite operar si el día anterior fue un doji (apertura es igual a cierre).
- Opcionalmente cerrar la posición automáticamente una vez alcanzada la hora de salida configurada.
- Administre las salidas utilizando una lógica de stop-loss, take-profit y trailing stop opcional que imita al Asesor Experto original, incluida la activación de ganancias y los umbrales de los pasos finales.
Notas de implementación
- Utiliza StockSharp suscripciones de velas de alto nivel tanto para el contexto diario como para el tiempo intradiario.
- Convierte los controles de riesgo basados en puntos de la versión MQL en compensaciones de precios absolutas utilizando el paso de precio del valor.
- Mantiene trailingstops en el código, actualizándolos en cada vela terminada y cerrando la posición cuando se superan los precios extremos.
- Garantiza solo una operación por viernes mediante el seguimiento del estado diario.
- Admite entradas tanto largas como cortas, respetando la activación de números mágicos original al intercambiar un solo símbolo por instancia de estrategia.
Parámetros
| Nombre |
Descripción |
Predeterminado |
Volume |
Tamaño comercial en lotes/contratos. |
0.1 |
StopLossPoints |
Distancia de stop-loss en pasos de precio (0 desactivaciones). |
75 |
TakeProfitPoints |
Distancia de toma de ganancias en pasos de precio (0 inhabilitaciones). |
0 |
HourOpen |
Hora del día (0-23) para abrir la posición. |
7 |
UseClosePositions |
Habilitar el cierre automático después de la hora de salida. |
true |
HourClose |
Hora del día (0-23) para cerrar la posición si está habilitado. |
19 |
UseTrailing |
Habilite los ajustes del trailing stop. |
true |
ProfitTrailing |
Exija que las ganancias excedan la distancia de seguimiento antes de que se active el seguimiento. |
true |
TrailingStopPoints |
Distancia del trailing stop en pasos de precio. |
60 |
TrailingStepPoints |
Se requieren puntos adicionales antes de apretar el tope final. |
5 |
IntradayCandleType |
Tipo de vela para sincronización intradía (velas predeterminadas de 1 minuto). |
TimeSpan.FromMinutes(1) |
DailyCandleType |
Tipo de vela para la detección de sentimiento diario (velas predeterminadas de 1 día). |
TimeSpan.FromDays(1) |
Consejos de uso
- Alinee la sesión de negociación del instrumento para que la hora de entrada del viernes coincida con la apertura de mercado deseada.
- Al configurar los valores de stop-loss y trailing, expréselos en los mismos "puntos" utilizados por el paso de precio del símbolo para reproducir el comportamiento MetaTrader.
- La estrategia está diseñada para una única operación cada viernes. Para intercambiar múltiples símbolos, ejecute instancias de estrategia separadas por símbolo.
Diferencias con el original EA
- Utiliza datos de cierre de velas para la toma de decisiones, mientras que los precios por tick originales sondeados.
- Las salidas protectoras se ejecutan mediante órdenes de mercado cuando las velas indican que se alcanzaron los niveles objetivo o de parada dentro del intervalo.
- Los parámetros de la estrategia se exponen a través del sistema
StrategyParam de StockSharp, lo que admite la optimización y el enlace de la interfaz de usuario.
Detalles de ejecución: la entrada se evalúa en la primera vela intradía terminada cuya hora de apertura sea HourOpen, no en el primer tick de la hora. Se necesita una vela diaria del jueves cerrada. Solo se permite un intento de entrada por viernes, incluso si se produce una salida protectora. La salida horaria, el stop, el take profit y el trailing se evalúan con los extremos de velas intradía cerradas y se ejecutan mediante órdenes de mercado; el trailing se ajusta solo tras el paso configurado y nunca se considera ejecutado retroactivamente dentro de la misma vela.
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()