Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BeforeMarketOpen() and AfterMarketClose() date rules #8311

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions Common/Scheduling/TimeRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ public ITimeRule Every(TimeSpan interval)
return new FuncTimeRule(name, applicator);
}

/// <summary>
/// Specifies an event should fire at market open +- <paramref name="minutesBeforeOpen"/>
/// </summary>
/// <param name="symbol">The symbol whose market open we want an event for</param>
/// <param name="minutesBeforeOpen">The minutes before market open that the event should fire</param>
/// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
/// <returns>A time rule that fires the specified number of minutes before the symbol's market open</returns>
public ITimeRule BeforeMarketOpen(Symbol symbol, double minutesBeforeOpen = 0, bool extendedMarketOpen = false)
{
return AfterMarketOpen(symbol, minutesBeforeOpen * (-1), extendedMarketOpen);
}

/// <summary>
/// Specifies an event should fire at market open +- <paramref name="minutesAfterOpen"/>
/// </summary>
Expand All @@ -163,7 +175,8 @@ public ITimeRule Every(TimeSpan interval)
public ITimeRule AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false)
{
var type = extendedMarketOpen ? "ExtendedMarketOpen" : "MarketOpen";
var name = Invariant($"{symbol}: {minutesAfterOpen:0.##} min after {type}");
var afterOrBefore = minutesAfterOpen > 0 ? "after" : "before";
var name = Invariant($"{symbol}: {Math.Abs(minutesAfterOpen):0.##} min {afterOrBefore} {type}");
var exchangeHours = GetSecurityExchangeHours(symbol);

var timeAfterOpen = TimeSpan.FromMinutes(minutesAfterOpen);
Expand All @@ -179,6 +192,18 @@ where exchangeHours.IsDateOpen(date, extendedMarketOpen) && marketOpen.Date == d
return new FuncTimeRule(name, applicator);
}

/// <summary>
/// Specifies an event should fire at the market close +- <paramref name="minutesAfterClose"/>
/// </summary>
/// <param name="symbol">The symbol whose market close we want an event for</param>
/// <param name="minutesAfterClose">The time after market close that the event should fire</param>
/// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
/// <returns>A time rule that fires the specified number of minutes after the symbol's market close</returns>
public ITimeRule AfterMarketClose(Symbol symbol, double minutesAfterClose = 0, bool extendedMarketClose = false)
{
return BeforeMarketClose(symbol, minutesAfterClose * (-1), extendedMarketClose);
}

/// <summary>
/// Specifies an event should fire at the market close +- <paramref name="minutesBeforeClose"/>
/// </summary>
Expand All @@ -189,7 +214,8 @@ where exchangeHours.IsDateOpen(date, extendedMarketOpen) && marketOpen.Date == d
public ITimeRule BeforeMarketClose(Symbol symbol, double minutesBeforeClose = 0, bool extendedMarketClose = false)
{
var type = extendedMarketClose ? "ExtendedMarketClose" : "MarketClose";
var name = Invariant($"{symbol}: {minutesBeforeClose:0.##} min before {type}");
var afterOrBefore = minutesBeforeClose > 0 ? "before" : "after";
var name = Invariant($"{symbol}: {Math.Abs(minutesBeforeClose):0.##} min {afterOrBefore} {type}");
var exchangeHours = GetSecurityExchangeHours(symbol);

var timeBeforeClose = TimeSpan.FromMinutes(minutesBeforeClose);
Expand Down
38 changes: 38 additions & 0 deletions Tests/Common/Scheduling/TimeRulesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,25 @@ public void ExtendedMarketOpenNoDeltaForContinuousSchedules()
Assert.AreEqual(6, count);
}

[TestCase(true, 9 - 0.5)]
[TestCase(false, 14.5 - 0.5)]
public void BeforeMarketOpenWithDelta(bool extendedMarketHours, double expectedHour)
{
var rules = GetTimeRules(TimeZones.Utc);
var rule = rules.BeforeMarketOpen(Symbols.SPY, 30, extendedMarketHours);
var times = rule.CreateUtcEventTimes(new[] { new DateTime(2000, 01, 03) });
var type = extendedMarketHours ? "ExtendedMarketOpen" : "MarketOpen";
Assert.AreEqual($"{Symbols.SPY}: 30 min before {type}", rule.Name);

int count = 0;
foreach (var time in times)
{
count++;
Assert.AreEqual(TimeSpan.FromHours(expectedHour), time.TimeOfDay);
}
Assert.AreEqual(1, count);
}

[Test]
public void ExtendedMarketCloseNoDeltaForContinuousSchedules()
{
Expand Down Expand Up @@ -290,6 +309,25 @@ public void ExtendedMarketCloseWithDelta()
Assert.AreEqual(1, count);
}

[TestCase(true, (21 + 4 + .5) % 24)]
[TestCase(false, (21 + .5) % 24)]
public void AfterMarketCloseWithDelta(bool extendedMarketHours, double expectedHour)
{
var rules = GetTimeRules(TimeZones.Utc);
var rule = rules.AfterMarketClose(Symbols.SPY, 30, extendedMarketHours);
var times = rule.CreateUtcEventTimes(new[] { new DateTime(2000, 01, 03) });
var type = extendedMarketHours ? "ExtendedMarketClose" : "MarketClose";
Assert.AreEqual($"{Symbols.SPY}: 30 min after {type}", rule.Name);

int count = 0;
foreach (var time in times)
{
count++;
Assert.AreEqual(TimeSpan.FromHours(expectedHour), time.TimeOfDay);
}
Assert.AreEqual(1, count);
}

[TestCase(0)]
[TestCase(-1)]
public void EveryValidatesTimeSpan(int timeSpanMinutes)
Expand Down
Loading