Estratégia TimeEA
A Estratégia TimeEA replica o consultor especialista original do MetaTrader "TimeEA" dentro do StockSharp. Ela gerencia uma única posição baseando-se exclusivamente na hora do dia: abre em um momento configurado, mantém a posição em uma direção fixa e sai ou em um horário de fechamento programado ou quando os níveis opcionais de stop-loss/take-profit são violados.
Ao contrário dos sistemas baseados em indicadores, esta implementação foca no gerenciamento disciplinado de sessão. Garante apenas uma entrada por dia de trading, limpa a exposição oposta antes de abrir e aplica distâncias mínimas configuráveis para ordens protetoras que imitam as limitações de nível de stop do corretor.
Como funciona
- A estratégia assina uma série de velas configurável (1 minuto por padrão) e avalia apenas as velas concluídas.
- Quando o fechamento de uma vela cruza o Horário de abertura configurado, a estratégia:
- Fecha qualquer posição oposta que ainda possa estar aberta.
- Coloca uma ordem a mercado na direção escolhida (Compra ou Venda) com o volume especificado.
- Registra preços de stop-loss e take-profit em pontos (passos de preço) a partir da entrada, aplicando o multiplicador de distância mínima.
- Durante a sessão, a estratégia monitora as velas:
- Se uma vela toca o nível de stop-loss ou take-profit armazenado, a posição é fechada imediatamente.
- Se a vela cruzar a janela do Horário de fechamento, a posição é nivelada independentemente de lucro ou perda.
- Após fechar a negociação (por stop, alvo ou agenda), a estratégia permanece sem posição até o próximo dia de trading.
Este fluxo reproduz o comportamento "abrir uma vez por dia" da versão do MetaTrader que dependia de comparações TimeCurrent() e Time[0].
Parâmetros
| Nome |
Descrição |
| Open Time |
Hora do dia para abrir a negociação. Aceita HH:MM:SS. |
| Close Time |
Hora do dia para nivelar todas as posições. Pode ser no mesmo dia ou passar para o dia seguinte. |
| Position Type |
Direção da posição (Buy ou Sell). |
| Order Volume |
Quantidade usada ao enviar a ordem a mercado. |
| Stop Loss (points) |
Distância em passos de preço para o stop protetor. Definir como 0 para desativar. |
| Take Profit (points) |
Distância em passos de preço para o alvo de lucro. Definir como 0 para desativar. |
| Minimum Distance Multiplier |
Offset mínimo aplicado tanto ao stop quanto ao alvo (em passos de preço) para emular a verificação original do nível de stop contra o spread. |
| Candle Type |
Série de dados usada para detectar limites de tempo. O padrão são velas de 1 minuto. |
Notas práticas
- Entrada única por dia – Uma vez que o horário de abertura é acionado, a estratégia não reingressará até o próximo dia de calendário mesmo que a posição tenha sido parada antecipadamente.
- Suporte a cruzamento de meia-noite – Tanto os horários de abertura quanto de fechamento podem ser definidos antes ou depois da meia-noite. O auxiliar respeita sessões que continuam após 00:00.
- Gerenciamento de volume – As ordens a mercado respeitam o parâmetro
Order Volume; ajustar para o tamanho do contrato do instrumento selecionado.
- Emulação de nível de stop – O multiplicador de distância mínima garante que stops/alvos fiquem pelo menos um número definido de pontos do ponto de entrada, refletindo a regra original "spread × multiplicador".
- Requisitos de dados – A estratégia depende de velas consistentes para o timing. Usar intervalos de tempo locais da bolsa para evitar a deriva de fuso horário.
- Gestão de risco – Stops e alvos são mantidos internamente; nenhuma ordem OCO do lado do servidor é criada. Quando uma vela cruzar os limiares, a estratégia emite uma ordem a mercado para sair.
Casos de uso
- Automatizar entradas baseadas em sessão (p. ex., abrir posições na abertura de Londres ou Nova York).
- Executar estratégias de viés direcional onde a direção é conhecida de antemão, mas a execução deve seguir um calendário preciso.
- Emular gatilhos de tempo no estilo MetaTrader dentro da API de alto nível do StockSharp sem temporizadores manuais.
Limitações
- O deslizamento é tratado implicitamente por ordens a mercado; não há um parâmetro de desvio separado como no MetaTrader.
- O multiplicador de distância mínima não lê spreads dinâmicos; aplica um amortecedor estático expresso em passos de preço.
- A estratégia assume que apenas um instrumento/segurança é negociado por instância.
Primeiros passos
- Configurar os parâmetros da estratégia no Designer ou via código (horários de abertura/fechamento, direção, volume, distâncias de risco).
- Anexar a estratégia ao instrumento e à fonte de dados desejados.
- Garantir que a série de velas use o mesmo fuso horário que o calendário pretendido.
- Executar a estratégia e monitorar o registro de negociações; sobreposições visuais podem ser habilitadas via
DrawCandles e DrawOwnTrades se desejado.
A lógica está completamente contida em CS/TimeEaStrategy.cs com extensos comentários em linha explicando cada estágio do fluxo de trabalho.
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>
/// Time-based strategy that opens a single directional position at the configured time
/// and closes it at another time or when optional stop/target levels are hit.
/// </summary>
public class TimeEaStrategy : Strategy
{
private readonly StrategyParam<TimeSpan> _openTime;
private readonly StrategyParam<TimeSpan> _closeTime;
private readonly StrategyParam<TimeEaPositionTypes> _openedType;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _minSpreadMultiplier;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _lastEntryDate;
private DateTime? _lastCloseDate;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _takeProfitPrice;
/// <summary>
/// Time of day to open the position.
/// </summary>
public TimeSpan OpenTime
{
get => _openTime.Value;
set => _openTime.Value = value;
}
/// <summary>
/// Time of day to close the position.
/// </summary>
public TimeSpan CloseTime
{
get => _closeTime.Value;
set => _closeTime.Value = value;
}
/// <summary>
/// Direction of the position opened at the scheduled time.
/// </summary>
public TimeEaPositionTypes OpenedType
{
get => _openedType.Value;
set => _openedType.Value = value;
}
/// <summary>
/// Market order volume for opening trades.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Stop loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Minimal distance multiplier applied to stops and targets.
/// </summary>
public int MinSpreadMultiplier
{
get => _minSpreadMultiplier.Value;
set => _minSpreadMultiplier.Value = value;
}
/// <summary>
/// Candle type used to evaluate time windows.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="TimeEaStrategy"/>.
/// </summary>
public TimeEaStrategy()
{
_openTime = Param(nameof(OpenTime), new TimeSpan(1, 0, 0))
.SetDisplay("Open Time", "Time to enter the market", "Scheduling");
_closeTime = Param(nameof(CloseTime), TimeSpan.Zero)
.SetDisplay("Close Time", "Time to exit the market", "Scheduling");
_openedType = Param(nameof(OpenedType), TimeEaPositionTypes.Buy)
.SetDisplay("Position Type", "Direction to maintain", "Trading");
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Quantity for market orders", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Distance in price steps", "Risk");
_minSpreadMultiplier = Param(nameof(MinSpreadMultiplier), 2)
.SetNotNegative()
.SetDisplay("Minimum Distance Multiplier", "Minimal offset applied to stops", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Candles used for scheduling", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastEntryDate = null;
_lastCloseDate = null;
ResetRiskLevels();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
// Use finished candles to evaluate the time windows.
if (candle.State != CandleStates.Finished)
return;
var candleDate = candle.CloseTime.Date;
if (ContainsTime(candle, OpenTime) && _lastEntryDate != candleDate)
{
_lastEntryDate = candleDate;
HandleOpen(candle);
}
if (ContainsTime(candle, CloseTime) && _lastCloseDate != candleDate)
{
_lastCloseDate = candleDate;
if (Position != 0)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
ResetRiskLevels();
}
return;
}
ManageRisk(candle);
}
private void HandleOpen(ICandleMessage candle)
{
// Close opposite exposure before opening a new position.
if (OpenedType == TimeEaPositionTypes.Buy)
{
if (Position < 0)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
ResetRiskLevels();
}
if (Position == 0 && OrderVolume > 0)
{
BuyMarket(OrderVolume);
SetRiskLevels(candle.ClosePrice, true);
}
}
else
{
if (Position > 0)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
ResetRiskLevels();
}
if (Position == 0 && OrderVolume > 0)
{
SellMarket(OrderVolume);
SetRiskLevels(candle.ClosePrice, false);
}
}
}
private void ManageRisk(ICandleMessage candle)
{
// Monitor active position for stop loss and take profit.
if (Position > 0)
{
if (_stopPrice > 0m && candle.LowPrice <= _stopPrice)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
ResetRiskLevels();
return;
}
if (_takeProfitPrice > 0m && candle.HighPrice >= _takeProfitPrice)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
ResetRiskLevels();
}
}
else if (Position < 0)
{
if (_stopPrice > 0m && candle.HighPrice >= _stopPrice)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
ResetRiskLevels();
return;
}
if (_takeProfitPrice > 0m && candle.LowPrice <= _takeProfitPrice)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
ResetRiskLevels();
}
}
}
private void SetRiskLevels(decimal closePrice, bool isLong)
{
_entryPrice = closePrice;
var step = Security?.PriceStep ?? 1m;
var minDistance = Math.Max(MinSpreadMultiplier, 0) * step;
var stopDistance = StopLossPoints > 0 ? Math.Max(StopLossPoints * step, minDistance) : 0m;
var takeDistance = TakeProfitPoints > 0 ? Math.Max(TakeProfitPoints * step, minDistance) : 0m;
// Calculate price levels in the same direction logic as the original Expert Advisor.
if (isLong)
{
_stopPrice = stopDistance > 0m ? closePrice - stopDistance : 0m;
_takeProfitPrice = takeDistance > 0m ? closePrice + takeDistance : 0m;
}
else
{
_stopPrice = stopDistance > 0m ? closePrice + stopDistance : 0m;
_takeProfitPrice = takeDistance > 0m ? closePrice - takeDistance : 0m;
}
}
private void ResetRiskLevels()
{
_entryPrice = 0m;
_stopPrice = 0m;
_takeProfitPrice = 0m;
}
private static bool ContainsTime(ICandleMessage candle, TimeSpan target)
{
var openTime = candle.OpenTime;
var closeTime = candle.CloseTime;
var openSpan = openTime.TimeOfDay;
var closeSpan = closeTime.TimeOfDay;
var crossesMidnight = closeTime.Date > openTime.Date || closeSpan < openSpan;
if (!crossesMidnight)
return target >= openSpan && target <= closeSpan;
var startMinutes = openSpan.TotalMinutes;
var endMinutes = closeSpan.TotalMinutes + TimeSpan.FromDays(1).TotalMinutes;
var targetMinutes = target.TotalMinutes;
if (targetMinutes < startMinutes)
targetMinutes += TimeSpan.FromDays(1).TotalMinutes;
return targetMinutes >= startMinutes && targetMinutes <= endMinutes;
}
/// <summary>
/// Supported position directions.
/// </summary>
public enum TimeEaPositionTypes
{
/// <summary>
/// Open a long position.
/// </summary>
Buy,
/// <summary>
/// Open a short position.
/// </summary>
Sell
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class time_ea_strategy(Strategy):
TYPE_BUY = 0
TYPE_SELL = 1
def __init__(self):
super(time_ea_strategy, self).__init__()
self._open_time = self.Param("OpenTime", TimeSpan(1, 0, 0))
self._close_time = self.Param("CloseTime", TimeSpan.Zero)
self._opened_type = self.Param("OpenedType", self.TYPE_BUY)
self._order_volume = self.Param("OrderVolume", 0.1)
self._stop_loss_points = self.Param("StopLossPoints", 0)
self._take_profit_points = self.Param("TakeProfitPoints", 0)
self._min_spread_multiplier = self.Param("MinSpreadMultiplier", 2)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._last_entry_date = None
self._last_close_date = None
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
@property
def OpenTime(self):
return self._open_time.Value
@property
def CloseTime(self):
return self._close_time.Value
@property
def OpenedType(self):
return self._opened_type.Value
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def MinSpreadMultiplier(self):
return self._min_spread_multiplier.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(time_ea_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
candle_date = candle.CloseTime.Date
if self._contains_time(candle, self.OpenTime) and self._last_entry_date != candle_date:
self._last_entry_date = candle_date
self._handle_open(candle)
if self._contains_time(candle, self.CloseTime) and self._last_close_date != candle_date:
self._last_close_date = candle_date
pos = float(self.Position)
if pos != 0:
if pos > 0:
self.SellMarket(pos)
elif pos < 0:
self.BuyMarket(abs(pos))
self._reset_risk_levels()
return
self._manage_risk(candle)
def _handle_open(self, candle):
pos = float(self.Position)
if self.OpenedType == self.TYPE_BUY:
if pos < 0:
self.BuyMarket(abs(pos))
self._reset_risk_levels()
if float(self.Position) == 0 and float(self.OrderVolume) > 0:
self.BuyMarket(float(self.OrderVolume))
self._set_risk_levels(float(candle.ClosePrice), True)
else:
if pos > 0:
self.SellMarket(pos)
self._reset_risk_levels()
if float(self.Position) == 0 and float(self.OrderVolume) > 0:
self.SellMarket(float(self.OrderVolume))
self._set_risk_levels(float(candle.ClosePrice), False)
def _manage_risk(self, candle):
pos = float(self.Position)
if pos > 0:
if self._stop_price > 0 and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(pos)
self._reset_risk_levels()
return
if self._take_profit_price > 0 and float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket(pos)
self._reset_risk_levels()
elif pos < 0:
if self._stop_price > 0 and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(abs(pos))
self._reset_risk_levels()
return
if self._take_profit_price > 0 and float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket(abs(pos))
self._reset_risk_levels()
def _set_risk_levels(self, close_price, is_long):
self._entry_price = close_price
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if step <= 0:
step = 1.0
min_distance = max(self.MinSpreadMultiplier, 0) * step
stop_dist = max(self.StopLossPoints * step, min_distance) if self.StopLossPoints > 0 else 0.0
take_dist = max(self.TakeProfitPoints * step, min_distance) if self.TakeProfitPoints > 0 else 0.0
if is_long:
self._stop_price = close_price - stop_dist if stop_dist > 0 else 0.0
self._take_profit_price = close_price + take_dist if take_dist > 0 else 0.0
else:
self._stop_price = close_price + stop_dist if stop_dist > 0 else 0.0
self._take_profit_price = close_price - take_dist if take_dist > 0 else 0.0
def _reset_risk_levels(self):
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
def _contains_time(self, candle, target):
open_time = candle.OpenTime
close_time = candle.CloseTime
open_span = open_time.TimeOfDay
close_span = close_time.TimeOfDay
crosses_midnight = close_time.Date > open_time.Date or close_span < open_span
if not crosses_midnight:
return target >= open_span and target <= close_span
start_min = open_span.TotalMinutes
end_min = close_span.TotalMinutes + 1440.0
target_min = target.TotalMinutes
if target_min < start_min:
target_min += 1440.0
return target_min >= start_min and target_min <= end_min
def OnReseted(self):
super(time_ea_strategy, self).OnReseted()
self._last_entry_date = None
self._last_close_date = None
self._reset_risk_levels()
def CreateClone(self):
return time_ea_strategy()