Time EA Strategy
The Time EA Strategy replicates the original MetaTrader "TimeEA" expert advisor inside StockSharp. It manages a single position based exclusively on the time of day: it opens at a configured moment, keeps the position in a fixed direction, and exits either at a scheduled closing time or once optional stop-loss / take-profit levels are breached.
Unlike indicator-driven systems, this implementation focuses on disciplined session management. It ensures only one entry per trading day, cleans up opposite exposure before opening, and enforces configurable minimum distances for protective orders to mimic broker stop-level limitations.
How It Works
- The strategy subscribes to a configurable candle series (1-minute by default) and evaluates only completed candles.
- When the close of a candle crosses the configured Open Time, the strategy:
- Closes any opposite position that might still be open.
- Places a market order in the chosen direction (Buy or Sell) with the specified volume.
- Records stop-loss and take-profit prices in points (price steps) from the entry, applying the minimal distance multiplier.
- Throughout the session the strategy monitors candles:
- If a candle touches the stored stop-loss or take-profit level, the position is immediately closed.
- If the candle crosses the Close Time window, the position is flattened regardless of profit or loss.
- After closing the trade (by stop, target, or schedule) the strategy remains flat until the next trading day.
This flow reproduces the "open once per day" behavior of the MetaTrader version that relied on TimeCurrent() and Time[0] comparisons.
Parameters
| Name |
Description |
| Open Time |
Time of day to open the trade. Accepts HH:MM:SS. |
| Close Time |
Time of day to flatten all positions. Can be the same day or spill into the next day. |
| Position Type |
Direction of the position (Buy or Sell). |
| Order Volume |
Quantity used when submitting the market order. |
| Stop Loss (points) |
Distance in price steps for the protective stop. Set to 0 to disable. |
| Take Profit (points) |
Distance in price steps for the profit target. Set to 0 to disable. |
| Minimum Distance Multiplier |
Minimal offset applied to both stop and target (in price steps) to emulate the original stop-level check against spread. |
| Candle Type |
Data series used to detect time boundaries. Default is 1-minute candles. |
Practical Notes
- Single Entry Per Day – Once the open time fires, the strategy will not re-enter until the next calendar day even if the position was stopped out early.
- Cross-Midnight Support – Both open and close times can be set before or after midnight. The helper respects sessions that continue past 00:00.
- Volume Handling – Market orders respect the
Order Volume parameter; adjust to the contract size of the selected instrument.
- Stop-Level Emulation – The minimal distance multiplier ensures that stops/targets stay at least a defined number of points away from the entry, mirroring the original "spread × multiplier" rule.
- Data Requirements – The strategy relies on consistent candles for timing. Use exchange-local timeframes to avoid timezone drift.
- Risk Management – Stops and targets are maintained internally; no server-side OCO orders are created. When a candle crosses the thresholds, the strategy issues a market order to exit.
Use Cases
- Automating session-based entries (e.g., opening positions at the London or New York open).
- Running directional bias strategies where direction is known in advance but execution must follow a precise schedule.
- Emulating MetaTrader-style time triggers inside the StockSharp high-level API without manual timers.
Limitations
- Slippage is handled implicitly by market orders; there is no separate deviation parameter as in MetaTrader.
- The minimal distance multiplier does not read dynamic spreads; it enforces a static cushion expressed in price steps.
- The strategy assumes only one instrument/security is traded per instance.
Getting Started
- Configure the strategy parameters in Designer or via code (open/close times, direction, volume, risk distances).
- Attach the strategy to the desired security and data source.
- Ensure the candle series uses the same timezone as the intended schedule.
- Run the strategy and monitor the trade log; visual overlays can be enabled via
DrawCandles and DrawOwnTrades if desired.
The logic is fully contained in CS/TimeEaStrategy.cs with extensive inline comments explaining each stage of the workflow.
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()