Skip to content

Commit

Permalink
Merge pull request QuantConnect#656 from QuantConnect/market-hours-ea…
Browse files Browse the repository at this point in the history
…rly-close

Add Early Closes in market hours database
  • Loading branch information
jaredbroad authored Jan 17, 2017
2 parents 24ed701 + 4ade587 commit 45abca8
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 141 deletions.
2 changes: 1 addition & 1 deletion Common/Securities/MarketHoursDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public virtual Entry GetEntry(string market, string symbol, SecurityType securit
// and holidays, but we'll express them in a different time zone
if (overrideTimeZone != null && !entry.ExchangeHours.TimeZone.Equals(overrideTimeZone))
{
return new Entry(overrideTimeZone, new SecurityExchangeHours(overrideTimeZone, entry.ExchangeHours.Holidays, entry.ExchangeHours.MarketHours));
return new Entry(overrideTimeZone, new SecurityExchangeHours(overrideTimeZone, entry.ExchangeHours.Holidays, entry.ExchangeHours.MarketHours, entry.ExchangeHours.EarlyCloses));
}
}

Expand Down
2 changes: 1 addition & 1 deletion Common/Securities/SecurityExchange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public void SetMarketHours(IEnumerable<MarketHoursSegment> marketHoursSegments,
}

// create a new exchange hours instance for the new hours
_exchangeHours = new SecurityExchangeHours(_exchangeHours.TimeZone, _exchangeHours.Holidays, marketHours);
_exchangeHours = new SecurityExchangeHours(_exchangeHours.TimeZone, _exchangeHours.Holidays, marketHours, _exchangeHours.EarlyCloses);
}
}
}
44 changes: 39 additions & 5 deletions Common/Securities/SecurityExchangeHours.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace QuantConnect.Securities
{
/// <summary>
/// Represents the schedule of a security exchange. This includes daily regular and extended market hours
/// as well as holidays
/// as well as holidays and early closes.
/// </summary>
/// <remarks>
/// This type assumes that IsOpen will be called with increasingly future times, that is, the calls should never back
Expand All @@ -33,6 +33,7 @@ public class SecurityExchangeHours
{
private readonly DateTimeZone _timeZone;
private readonly HashSet<long> _holidays;
private readonly Dictionary<DateTime, TimeSpan> _earlyCloses;

// these are listed individually for speed
private readonly LocalMarketHours _sunday;
Expand Down Expand Up @@ -68,6 +69,14 @@ public IReadOnlyDictionary<DayOfWeek, LocalMarketHours> MarketHours
get { return _openHoursByDay; }
}

/// <summary>
/// Gets the early closes for this exchange
/// </summary>
public IReadOnlyDictionary<DateTime, TimeSpan> EarlyCloses
{
get { return _earlyCloses; }
}

/// <summary>
/// Gets a <see cref="SecurityExchangeHours"/> instance that is always open
/// </summary>
Expand All @@ -76,7 +85,8 @@ public static SecurityExchangeHours AlwaysOpen(DateTimeZone timeZone)
var dayOfWeeks = Enum.GetValues(typeof (DayOfWeek)).OfType<DayOfWeek>();
return new SecurityExchangeHours(timeZone,
Enumerable.Empty<DateTime>(),
dayOfWeeks.Select(LocalMarketHours.OpenAllDay).ToDictionary(x => x.DayOfWeek)
dayOfWeeks.Select(LocalMarketHours.OpenAllDay).ToDictionary(x => x.DayOfWeek),
new Dictionary<DateTime, TimeSpan>()
);
}

Expand All @@ -86,10 +96,14 @@ public static SecurityExchangeHours AlwaysOpen(DateTimeZone timeZone)
/// <param name="timeZone">The time zone the dates and hours are represented in</param>
/// <param name="holidayDates">The dates this exchange is closed for holiday</param>
/// <param name="marketHoursForEachDayOfWeek">The exchange's schedule for each day of the week</param>
public SecurityExchangeHours(DateTimeZone timeZone, IEnumerable<DateTime> holidayDates, IReadOnlyDictionary<DayOfWeek, LocalMarketHours> marketHoursForEachDayOfWeek)
/// <param name="earlyCloses">The dates this exchange has an early close</param>
public SecurityExchangeHours(DateTimeZone timeZone, IEnumerable<DateTime> holidayDates, IReadOnlyDictionary<DayOfWeek, LocalMarketHours> marketHoursForEachDayOfWeek,
IReadOnlyDictionary<DateTime, TimeSpan> earlyCloses)
{
_timeZone = timeZone;
_holidays = holidayDates.Select(x => x.Date.Ticks).ToHashSet();
_earlyCloses = earlyCloses.ToDictionary(x => x.Key.Date, x => x.Value);

// make a copy of the dictionary for internal use
_openHoursByDay = new Dictionary<DayOfWeek, LocalMarketHours>(marketHoursForEachDayOfWeek.ToDictionary());

Expand All @@ -110,7 +124,7 @@ public SecurityExchangeHours(DateTimeZone timeZone, IEnumerable<DateTime> holida
/// <returns>True if the exchange is considered open at the specified time, false otherwise</returns>
public bool IsOpen(DateTime localDateTime, bool extendedMarket)
{
if (_holidays.Contains(localDateTime.Date.Ticks))
if (_holidays.Contains(localDateTime.Date.Ticks) || IsTimeAfterEarlyClose(localDateTime))
{
return false;
}
Expand Down Expand Up @@ -138,7 +152,7 @@ public bool IsOpen(DateTime startLocalDateTime, DateTime endLocalDateTime, bool
var end = new DateTime(Math.Min(endLocalDateTime.Ticks, start.Date.Ticks + Time.OneDay.Ticks - 1));
do
{
if (!_holidays.Contains(start.Date.Ticks))
if (!_holidays.Contains(start.Date.Ticks) && !IsTimeAfterEarlyClose(start))
{
// check to see if the market is open
var marketHours = GetMarketHours(start.DayOfWeek);
Expand Down Expand Up @@ -231,6 +245,17 @@ public DateTime GetNextMarketClose(DateTime localDateTime, bool extendedMarket)
var marketHours = GetMarketHours(time.DayOfWeek);
if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
{
TimeSpan earlyCloseTime;
if (_earlyCloses.TryGetValue(time.Date, out earlyCloseTime))
{
var earlyCloseDateTime = time.Date.Add(earlyCloseTime);
if (time < earlyCloseDateTime)
return earlyCloseDateTime;

time = time.Date + Time.OneDay;
continue;
}

var marketCloseTimeOfDay = marketHours.GetMarketClose(time.TimeOfDay, extendedMarket);
if (marketCloseTimeOfDay.HasValue)
{
Expand Down Expand Up @@ -287,5 +312,14 @@ private LocalMarketHours GetMarketHours(DayOfWeek day)
throw new ArgumentOutOfRangeException("day", day, null);
}
}

/// <summary>
/// Helper to determine if the current time is after a market early close
/// </summary>
private bool IsTimeAfterEarlyClose(DateTime localDateTime)
{
TimeSpan earlyCloseTime;
return _earlyCloses.TryGetValue(localDateTime.Date, out earlyCloseTime) && localDateTime.TimeOfDay >= earlyCloseTime;
}
}
}
10 changes: 8 additions & 2 deletions Common/Util/MarketHoursDatabaseJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected override MarketHoursDatabase Create(Type type, JToken token)
public class MarketHoursDatabaseJson
{
/// <summary>
/// The entries in the market hours database, keyed by <see cref="MarketHoursDatabase.Key.ToString"/>
/// The entries in the market hours database, keyed by <see cref="SecurityDatabaseKey"/>
/// </summary>
[JsonProperty("entries")]
public Dictionary<string, MarketHoursDatabaseEntryJson> Entries;
Expand Down Expand Up @@ -155,6 +155,11 @@ public class MarketHoursDatabaseEntryJson
/// </summary>
[JsonProperty("holidays")]
public List<string> Holidays;
/// <summary>
/// Early closes by date
/// </summary>
[JsonProperty("earlyCloses")]
public Dictionary<string, TimeSpan> EarlyCloses = new Dictionary<string, TimeSpan>();

/// <summary>
/// Initializes a new instance of the <see cref="MarketHoursDatabaseEntryJson"/> class
Expand Down Expand Up @@ -193,7 +198,8 @@ public MarketHoursDatabase.Entry Convert()
{ DayOfWeek.Saturday, new LocalMarketHours(DayOfWeek.Saturday, Saturday) }
};
var holidayDates = Holidays.Select(x => DateTime.ParseExact(x, "M/d/yyyy", CultureInfo.InvariantCulture)).ToHashSet();
var exchangeHours = new SecurityExchangeHours(DateTimeZoneProviders.Tzdb[ExchangeTimeZone], holidayDates, hours);
var earlyCloses = EarlyCloses.ToDictionary(x => DateTime.ParseExact(x.Key, "M/d/yyyy", CultureInfo.InvariantCulture), x => x.Value);
var exchangeHours = new SecurityExchangeHours(DateTimeZoneProviders.Tzdb[ExchangeTimeZone], holidayDates, hours, earlyCloses);
return new MarketHoursDatabase.Entry(DateTimeZoneProviders.Tzdb[DataTimeZone], exchangeHours);
}

Expand Down
Loading

0 comments on commit 45abca8

Please sign in to comment.