La Estrategia de Probador de Índices es una adaptación directa del asesor experto MetaTrader 5 "Probador de Índices". El sistema se centra en el comercio de índices intradía, donde se abre una única posición larga durante una ventana de negociación muy estrecha. Las decisiones comerciales se basan únicamente en filtros de tiempo y límites operativos:
Un único flujo de velas configurable impulsa el reloj interno de la estrategia.
Sólo se podrán abrir nuevas posiciones entre las horas de inicio y fin de sesión configuradas.
Se permite un número fijo de operaciones por día, lo que evita reingresos repetidos.
Todas las posiciones abiertas se cierran forzosamente en un momento de liquidación definido.
La estrategia opera únicamente en el lado largo, reflejando al asesor experto original.
Esta implementación utiliza StockSharp API de alto nivel, se suscribe a datos de velas con SubscribeCandles y maneja decisiones comerciales en la devolución de llamada ProcessCandle. No se requieren indicadores, lo que mantiene la lógica ágil y centrada en los controles de tiempo y riesgo.
Lógica de trading
Restablecimiento diario: la estrategia realiza un seguimiento del día de negociación actual. Cuando comienza un nuevo día, todos los contadores se reinician, lo que permite una nueva asignación comercial para ese día.
Ventana de entrada: solo las velas con un tiempo de cierre estrictamente dentro del intervalo [SessionStart, SessionEnd) pueden activar entradas. Esto reproduce las comprobaciones TimeStart y TimeEnd del código original.
Límites de posiciones y operaciones: las entradas se omiten si la cantidad de operaciones ya abiertas durante el día actual ha alcanzado DailyTradeLimit, o si la cantidad de posiciones abiertas simultáneamente excede MaxOpenPositions.
Envío de orden: cuando todas las condiciones se alinean, la estrategia envía una orden de compra de mercado por TradeVolume unidades. El contador de operaciones del día se incrementa inmediatamente después del envío de la orden.
Salida forzada: si una vela se cierra después de CloseTime y hay una posición larga activa, la estrategia cierra la posición con una orden de venta de mercado. Esto refleja la lógica del temporizador ClosePos() de la implementación MQL.
La combinación del contador de operaciones y el limitador de posición garantiza que el sistema se comporte como un simple programador de operaciones por día de forma predeterminada y, al mismo tiempo, permite el ajuste de parámetros para una actividad más frecuente.
Parámetros
Nombre
Descripción
CandleType
Serie de velas primarias que impulsan el reloj de estrategia (por defecto, velas de 1 minuto).
SessionStart
Hora del día en la que se permite iniciar nuevas operaciones.
SessionEnd
Hora del día en la que ya no se permiten nuevas operaciones.
CloseTime
Hora del día en que se liquida cualquier posición abierta restante.
DailyTradeLimit
Número máximo de entradas permitidas por día antes de que se suspenda la negociación.
MaxOpenPositions
Número máximo de posiciones largas abiertas simultáneamente (contadas en unidades comerciales).
TradeVolume
Volumen de orden de mercado utilizado para cada entrada.
Notas y diferencias
StockSharp no expone tablas de sesión MetaTrader, por lo que la conversión se basa en el tiempo de intercambio de las marcas de tiempo de las velas junto con la protección IsFormedAndOnlineAndAllowTrading().
El asesor experto original utilizó cronómetros de segundo nivel; este puerto aprovecha los cierres de velas para impulsar tanto el momento de entrada como las salidas forzadas, lo cual es suficiente para ventanas de negociación a nivel de minutos.
Los recuentos de operaciones se restablecen al comienzo de cada día de negociación detectado a partir de las horas de cierre de las velas, lo que mantiene el comportamiento constante en diferentes zonas horarias siempre que la fuente de la vela coincida con el intercambio deseado.
Consejos de uso
Asegúrese de que el CandleType configurado coincida con el mercado en el que se negocia para que los filtros de tiempo se alineen con la sesión deseada.
Aumente DailyTradeLimit si se requieren varios intentos por día, por ejemplo, cuando se ejecuta en períodos de tiempo más cortos.
Establezca MaxOpenPositions encima de 1 solo cuando se desee un escalado parcial en posiciones; de lo contrario, mantenga el valor predeterminado para imitar exactamente el script MetaTrader.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the MetaTrader expert advisor "Indices Tester".
/// Implements a time filtered long-only session with daily trade and position limits.
/// </summary>
public class IndicesTesterStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<TimeSpan> _sessionStart;
private readonly StrategyParam<TimeSpan> _sessionEnd;
private readonly StrategyParam<TimeSpan> _closeTime;
private readonly StrategyParam<int> _dailyTradeLimit;
private readonly StrategyParam<int> _maxOpenPositions;
private readonly StrategyParam<decimal> _tradeVolume;
private DateTime _currentDay;
private int _tradesOpenedToday;
/// <summary>
/// Candle type used to drive the strategy clock.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Session start time when new long positions may be opened.
/// </summary>
public TimeSpan SessionStart
{
get => _sessionStart.Value;
set => _sessionStart.Value = value;
}
/// <summary>
/// Session end time after which new positions are not allowed.
/// </summary>
public TimeSpan SessionEnd
{
get => _sessionEnd.Value;
set => _sessionEnd.Value = value;
}
/// <summary>
/// Time of day when all active positions are closed.
/// </summary>
public TimeSpan CloseTime
{
get => _closeTime.Value;
set => _closeTime.Value = value;
}
/// <summary>
/// Maximum number of entries that can be opened during a single trading day.
/// </summary>
public int DailyTradeLimit
{
get => _dailyTradeLimit.Value;
set => _dailyTradeLimit.Value = value;
}
/// <summary>
/// Maximum simultaneous long positions measured in trade units.
/// </summary>
public int MaxOpenPositions
{
get => _maxOpenPositions.Value;
set => _maxOpenPositions.Value = value;
}
/// <summary>
/// Order volume submitted with every market entry.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="IndicesTesterStrategy"/> class.
/// </summary>
public IndicesTesterStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe that drives the logic", "General");
_sessionStart = Param(nameof(SessionStart), new TimeSpan(0, 0, 0))
.SetDisplay("Session Start", "Time of day when entries become eligible", "Trading");
_sessionEnd = Param(nameof(SessionEnd), new TimeSpan(23, 0, 0))
.SetDisplay("Session End", "Time of day when new entries stop", "Trading");
_closeTime = Param(nameof(CloseTime), new TimeSpan(23, 30, 0))
.SetDisplay("Close Time", "Time of day used to liquidate open positions", "Risk");
_dailyTradeLimit = Param(nameof(DailyTradeLimit), 1)
.SetGreaterThanZero()
.SetDisplay("Daily Trades", "Maximum number of trades per day", "Risk");
_maxOpenPositions = Param(nameof(MaxOpenPositions), 1)
.SetGreaterThanZero()
.SetDisplay("Open Positions", "Maximum simultaneous long positions", "Risk");
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Market order volume for new positions", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentDay = default;
_tradesOpenedToday = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
// Ignore unfinished candles because the original EA worked on closed data.
if (candle.State != CandleStates.Finished)
return;
var candleTime = candle.CloseTime;
if (_currentDay != candleTime.Date)
{
// Reset the intraday counters on the first candle of a new session.
_currentDay = candleTime.Date;
_tradesOpenedToday = 0;
}
var timeOfDay = candleTime.TimeOfDay;
// Liquidate open positions once the configured close time is reached.
if (Position > 0m && timeOfDay >= CloseTime)
{
SellMarket(Position);
return;
}
// Only evaluate entries strictly inside the trading window.
if (timeOfDay <= SessionStart || timeOfDay >= SessionEnd)
return;
// Respect the daily trade allowance taken from the original EA.
if (_tradesOpenedToday >= DailyTradeLimit)
return;
// Skip entries when the simultaneous position limit would be exceeded.
if (GetOpenPositionCount() >= MaxOpenPositions)
return;
var volume = TradeVolume;
if (volume <= 0m)
return;
// Submit the market order and immediately update the per-day trade counter.
BuyMarket(volume);
_tradesOpenedToday++;
}
private int GetOpenPositionCount()
{
if (Position == 0m)
return 0;
var volume = TradeVolume;
if (volume <= 0m)
return 1;
return (int)Math.Ceiling(Math.Abs(Position) / volume);
}
}
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.Strategies import Strategy
class indices_tester_strategy(Strategy):
def __init__(self):
super(indices_tester_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._session_start = self.Param("SessionStart", TimeSpan(0, 0, 0))
self._session_end = self.Param("SessionEnd", TimeSpan(23, 0, 0))
self._close_time = self.Param("CloseTime", TimeSpan(23, 30, 0))
self._daily_trade_limit = self.Param("DailyTradeLimit", 1)
self._max_open_positions = self.Param("MaxOpenPositions", 1)
self._trade_volume = self.Param("TradeVolume", 0.1)
self._current_day = None
self._trades_opened_today = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def SessionStart(self):
return self._session_start.Value
@SessionStart.setter
def SessionStart(self, value):
self._session_start.Value = value
@property
def SessionEnd(self):
return self._session_end.Value
@SessionEnd.setter
def SessionEnd(self, value):
self._session_end.Value = value
@property
def CloseTime(self):
return self._close_time.Value
@CloseTime.setter
def CloseTime(self, value):
self._close_time.Value = value
@property
def DailyTradeLimit(self):
return self._daily_trade_limit.Value
@DailyTradeLimit.setter
def DailyTradeLimit(self, value):
self._daily_trade_limit.Value = value
@property
def MaxOpenPositions(self):
return self._max_open_positions.Value
@MaxOpenPositions.setter
def MaxOpenPositions(self, value):
self._max_open_positions.Value = value
@property
def TradeVolume(self):
return self._trade_volume.Value
@TradeVolume.setter
def TradeVolume(self, value):
self._trade_volume.Value = value
def OnReseted(self):
super(indices_tester_strategy, self).OnReseted()
self._current_day = None
self._trades_opened_today = 0
def OnStarted2(self, time):
super(indices_tester_strategy, self).OnStarted2(time)
self._current_day = None
self._trades_opened_today = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
candle_time = candle.CloseTime
candle_date = candle_time.Date
if self._current_day is None or self._current_day != candle_date:
self._current_day = candle_date
self._trades_opened_today = 0
time_of_day = candle_time.TimeOfDay
# Liquidate open positions once the configured close time is reached
if self.Position > 0 and time_of_day >= self.CloseTime:
self.SellMarket(self.Position)
return
# Only evaluate entries strictly inside the trading window
if time_of_day <= self.SessionStart or time_of_day >= self.SessionEnd:
return
# Respect the daily trade allowance
if self._trades_opened_today >= self.DailyTradeLimit:
return
# Skip if already have max positions
if self._get_open_position_count() >= self.MaxOpenPositions:
return
volume = self.TradeVolume
if volume <= 0:
return
# Long-only: buy
self.BuyMarket(volume)
self._trades_opened_today += 1
def _get_open_position_count(self):
if self.Position == 0:
return 0
volume = self.TradeVolume
if volume <= 0:
return 1
import math
return int(math.ceil(abs(float(self.Position)) / float(volume)))
def CreateClone(self):
return indices_tester_strategy()