A Indices Tester Strategy é uma versão direta do MetaTrader 5 consultor especialista "Indices Tester". O sistema concentra-se na negociação de índices intradiários, onde uma única posição longa é aberta durante uma janela de negociação muito estreita. As decisões de negociação dependem puramente de filtros de tempo e limites operacionais:
Um único fluxo de velas configurável aciona o relógio interno da estratégia.
Novas posições só podem ser abertas entre os horários de início e término da sessão configurados.
É permitido um número fixo de negociações por dia, evitando reentradas repetidas.
Todas as posições abertas são fechadas à força num momento de liquidação definido.
A estratégia opera apenas no lado comprado, espelhando o consultor especialista original.
Esta implementação usa o StockSharp API de alto nível, assina dados de velas com SubscribeCandles e lida com decisões de negociação no retorno de chamada ProcessCandle. Não são necessários indicadores, mantendo a lógica enxuta e focada no tempo e nos controles de risco.
Lógica de negociação
Reinicialização diária – a estratégia acompanha o dia de negociação atual. Quando um novo dia começa, todos os contadores são zerados, permitindo uma nova margem de negociação para aquele dia.
Janela de entrada – somente velas com tempo de fechamento estritamente dentro do intervalo [SessionStart, SessionEnd) podem acionar entradas. Isso reproduz as verificações TimeStart e TimeEnd do código original.
Limites de posições e negociações – as entradas serão ignoradas se o número de negociações já abertas durante o dia atual atingir DailyTradeLimit ou se o número de posições abertas simultaneamente exceder MaxOpenPositions.
Envio de ordem – quando todas as condições estão alinhadas, a estratégia envia uma ordem de compra de mercado para TradeVolume unidades. O contador de negociações do dia é incrementado imediatamente após o envio da ordem.
Saída forçada – se uma vela fechar após CloseTime e houver uma posição longa ativa, a estratégia fecha a posição com uma ordem de venda a mercado. Isso reflete a lógica do temporizador ClosePos() da implementação MQL.
A combinação do contador de negociações e do limitador de posições garante que o sistema se comporte como um simples agendador de negociação única por dia por padrão, ao mesmo tempo que permite o ajuste de parâmetros para atividades mais frequentes.
Parâmetros
Nome
Descrição
CandleType
Série de velas primárias que aciona o relógio estratégico (o padrão é velas de 1 minuto).
SessionStart
Hora do dia em que novas negociações podem começar.
SessionEnd
Hora do dia em que novas negociações não são mais permitidas.
CloseTime
Hora do dia em que qualquer posição aberta restante é liquidada.
DailyTradeLimit
Número máximo de entradas permitidas por dia antes da suspensão da negociação.
MaxOpenPositions
Número máximo de posições longas abertas simultaneamente (contadas em unidades de negociação).
TradeVolume
Volume de ordens de mercado utilizado para cada entrada.
Notas e diferenças
StockSharp não expõe tabelas de sessão MetaTrader, portanto, a conversão depende do tempo de troca dos carimbos de data e hora da vela junto com a proteção IsFormedAndOnlineAndAllowTrading().
O consultor especialista original usava temporizadores de segundo nível; esta porta aproveita o fechamento de velas para impulsionar o tempo de entrada e as saídas forçadas, o que é suficiente para janelas de negociação de nível minuto.
As contagens de negociação são redefinidas no início de cada dia de negociação detectada a partir dos horários de fechamento das velas, mantendo o comportamento consistente em diferentes fusos horários, desde que a origem da vela corresponda à bolsa desejada.
Dicas de uso
Certifique-se de que o CandleType configurado corresponda ao mercado que está sendo negociado para que os filtros de tempo se alinhem com a sessão desejada.
Aumente DailyTradeLimit se forem necessárias várias tentativas por dia, por exemplo, ao executar em intervalos de tempo mais curtos.
Defina MaxOpenPositions acima de 1 somente quando o escalonamento parcial em posições for desejado; caso contrário, mantenha o padrão para imitar exatamente o 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()