La Estrategia controlada por tiempo FitFul 13 es una versión StockSharp del MetaTrader 4 asesor experto "FitFul_13". La estrategia construye una escalera de pivote semanal (PP, R0.5, R1, R1.5, R2, R2.5, R3 y los niveles de soporte correspondientes) utilizando el máximo, el mínimo y el cierre de la semana anterior. Las decisiones comerciales se toman en el plazo principal (predeterminado, 1 hora) y, opcionalmente, se confirman en un plazo más rápido (predeterminado, 15 minutos). Se permiten nuevas posiciones solo en minutos intradiarios específicos para imitar el comportamiento original de EA.
Lógica de señal
Cálculo de pivote semanal
Al cierre de cada vela semanal, se recalcula la escalera de pivote.
Los precios de limitación de pérdidas y toma de ganancias se compensan con respecto a los niveles base mediante una distancia configurable expresada en puntos de precio.
Condiciones de plazo principal
La última vela primaria completada debe ser alcista para buscar entradas largas o bajista para buscar entradas cortas.
La vela primaria anterior debe abarcar uno de los niveles de pivote (abrir por debajo y cerrar por arriba para largos, abrir por arriba y cerrar por debajo para cortos).
Condiciones de plazo de confirmación
Si la vela de confirmación actual es alcista, los mínimos de las dos velas de confirmación anteriores deben perforar y cerrar por encima del mismo nivel de pivote para confirmar una señal larga.
Si la vela de confirmación actual es bajista, los máximos de las dos velas de confirmación anteriores deben perforar y cerrar por debajo de un nivel de pivote para confirmar una señal corta.
Tiempo de entrada
Se realiza una operación solo cuando el minuto de apertura de la vela primaria terminada es igual a uno de los cuatro minutos configurados (0, 15, 30 o 45 por defecto).
La exposición neta está limitada por MaxNetPositions × Volume para emular la restricción de "tres órdenes abiertas como máximo" de la versión MetaTrader.
Gestión de riesgos
Paradas y objetivos: a cada posición se le asigna un límite de pérdidas y toma de ganancias derivado de un pivote inmediatamente después de la entrada.
Parada dinámica: una vez que el precio avanza en la cantidad de puntos configurada, la parada sigue la dirección comercial.
Tiempo máximo de retención: las operaciones rentables se cierran una vez que el tiempo de retención excede la duración configurada (48 horas de forma predeterminada).
Regla fija de los viernes: los viernes, cualquier posición abierta se cierra entre los minutos configurados de la hora especificada (predeterminado de 21:50 a 21:59).
Parámetros
Nombre
Descripción
PrimaryCandleType
Plazo utilizado para las verificaciones cruzadas del pivote principal.
ConfirmationCandleType
Plazo más rápido que valida las reacciones de pivote.
Volume
Volumen neto de órdenes de mercado.
MaxNetPositions
Exposición máxima medida en múltiplos de Volume.
OffsetPoints
Distancia de precio-punto aplicada a paradas y objetivos alrededor de cada pivote.
TrailingStopPoints
Distancia del trailing stop en puntos de precio.
CloseAfter
Tiempo máximo de tenencia para posiciones rentables.
CloseHour, CloseMinuteFrom, CloseMinuteTo
Ventana horaria del viernes para salidas forzadas.
EntryMinute0..3
Minutos permitidos (dentro de cada hora) para abrir nuevas posiciones.
Notas
La conversión mantiene la dependencia del EA original de la escalera dinámica de la semana anterior y de las ventanas de ejecución de un cuarto de hora.
La administración del dinero se ha simplificado: el parámetro StockSharp Volume controla el tamaño del pedido directamente en lugar de volver a implementar el cálculo dinámico del lote desde MetaTrader.
Todos los comentarios dentro del código están escritos en inglés, como lo exigen las pautas del proyecto.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// FitFul 13 Time Gated strategy - channel midpoint crossover.
/// Buys when close crosses above the midpoint of Highest/Lowest channel.
/// Sells when close crosses below the midpoint.
/// </summary>
public class FitFul13TimeGatedStrategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevMid;
private bool _hasPrev;
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public FitFul13TimeGatedStrategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 13)
.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 13)
.SetDisplay("EMA Period", "EMA trend filter", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevClose = 0m; _prevMid = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var highest = new Highest { Length = ChannelPeriod };
var lowest = new Lowest { Length = ChannelPeriod };
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ema, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest, decimal ema)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var mid = (highest + lowest) / 2;
if (!_hasPrev)
{
_prevClose = close;
_prevMid = mid;
_hasPrev = true;
return;
}
// Cross above midpoint with EMA confirmation
if (_prevClose <= _prevMid && close > mid && close > ema && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Cross below midpoint with EMA confirmation
else if (_prevClose >= _prevMid && close < mid && close < ema && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevClose = close;
_prevMid = mid;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class fit_ful13_time_gated_strategy(Strategy):
def __init__(self):
super(fit_ful13_time_gated_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 13) \
.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators")
self._ema_period = self.Param("EmaPeriod", 13) \
.SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
@property
def channel_period(self):
return self._channel_period.Value
@property
def ema_period(self):
return self._ema_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fit_ful13_time_gated_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(fit_ful13_time_gated_strategy, self).OnStarted2(time)
self._has_prev = False
highest = Highest()
highest.Length = self.channel_period
lowest = Lowest()
lowest.Length = self.channel_period
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, ema, self.process_candle).Start()
def process_candle(self, candle, highest, lowest, ema):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high_val = float(highest)
low_val = float(lowest)
ema_val = float(ema)
mid = (high_val + low_val) / 2.0
if not self._has_prev:
self._prev_close = close
self._prev_mid = mid
self._has_prev = True
return
if self._prev_close <= self._prev_mid and close > mid and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close >= self._prev_mid and close < mid and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_mid = mid
def CreateClone(self):
return fit_ful13_time_gated_strategy()