Skip to content

Commit

Permalink
Implemented Weekdays setting (daily sessions only in specified days)
Browse files Browse the repository at this point in the history
 issue connamara#844

(originally @MichalAssuri's commit 65f675d from
PR connamara#845, but amended andexpanded by @gbirchmeier)
  • Loading branch information
MichalUssuri authored and gbirchmeier committed May 23, 2024
1 parent 37ba497 commit 3fb3892
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 55 deletions.
101 changes: 74 additions & 27 deletions QuickFIXn/SessionSchedule.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;

namespace QuickFix
{
Expand All @@ -12,6 +13,9 @@ public class SessionSchedule
public DayOfWeek? StartDay { get; }
public DayOfWeek? EndDay { get; }

private readonly bool _isWeekdaysSession;
private readonly HashSet<DayOfWeek> _weekdays;

public bool NonStopSession { get; }

public bool UseLocalTime { get; }
Expand All @@ -28,9 +32,7 @@ public class SessionSchedule
public bool IsNewSession(DateTime oldtimeUtc, DateTime testtimeUtc)
{
if (NonStopSession)
{
return false;
}

if (oldtimeUtc.Kind != DateTimeKind.Utc)
throw new ArgumentException("Only UTC time is supported", nameof(oldtimeUtc));
Expand Down Expand Up @@ -72,6 +74,9 @@ public bool IsSessionTime(DateTime utc)

DateTime adjusted = AdjustUtcDateTime(utc);

if (_isWeekdaysSession)
return CheckWeekdays(adjusted);

return WeeklySession ? CheckDay(adjusted) : CheckTime(adjusted.TimeOfDay);
}

Expand All @@ -83,7 +88,7 @@ public bool IsSessionTime(DateTime utc)
public DateTime NextEndTime(DateTime utc)
{
if (NonStopSession)
throw new NotSupportedException("NonStopSession");
throw new InvalidOperationException("NonStopSession is set; this statement should be unreachable");

TimeSpan vEndTime = EndTime ?? throw new QuickFix.ConfigError("EndTime is null");

Expand All @@ -93,7 +98,14 @@ public DateTime NextEndTime(DateTime utc)
DateTime d = AdjustUtcDateTime(utc);
DateTime end = DateTime.MinValue;

if (WeeklySession)

if (_isWeekdaysSession)
{
end = new DateTime(d.Year, d.Month, d.Day, vEndTime.Hours, vEndTime.Minutes, vEndTime.Seconds, d.Kind);
if (DateTime.Compare(d, end) > 0) // d is later than end
end = end.AddDays(1);
}
else if (WeeklySession)
{
end = new DateTime(d.Year, d.Month, d.Day, vEndTime.Hours, vEndTime.Minutes, vEndTime.Seconds, d.Kind);
while (end.DayOfWeek != EndDay)
Expand All @@ -114,12 +126,12 @@ public DateTime NextEndTime(DateTime utc)
/// <summary>
/// return true if time falls within StartTime/EndTime
/// </summary>
/// <param name="time"></param>
/// <param name="dt"></param>
/// <returns></returns>
private bool CheckDay(DateTime time)
private bool CheckDay(DateTime dt)
{
if (NonStopSession)
throw new InvalidOperationException("NonStopSession is set -- this should never be called.");
throw new InvalidOperationException("NonStopSession is set; this statement should be unreachable");

DayOfWeek vStartDay = StartDay ?? throw new QuickFix.ConfigError("StartDay is null");
DayOfWeek vEndDay = EndDay ?? throw new QuickFix.ConfigError("EndDay is null");
Expand All @@ -128,31 +140,31 @@ private bool CheckDay(DateTime time)

if (vStartDay < vEndDay)
{
if (time.DayOfWeek < vStartDay || time.DayOfWeek > vEndDay)
if (dt.DayOfWeek < vStartDay || dt.DayOfWeek > vEndDay)
return false;

if (time.DayOfWeek < vEndDay)
return (vStartDay < time.DayOfWeek) || (vStartTime.CompareTo(time.TimeOfDay) <= 0);
if (dt.DayOfWeek < vEndDay)
return (vStartDay < dt.DayOfWeek) || (vStartTime.CompareTo(dt.TimeOfDay) <= 0);

return (time.DayOfWeek < vEndDay) || (vEndTime.CompareTo(time.TimeOfDay) >= 0);
return (dt.DayOfWeek < vEndDay) || (vEndTime.CompareTo(dt.TimeOfDay) >= 0);
}

if (vEndDay < vStartDay)
{
if (vEndDay < time.DayOfWeek && time.DayOfWeek < vStartDay)
if (vEndDay < dt.DayOfWeek && dt.DayOfWeek < vStartDay)
return false;

if (time.DayOfWeek < vStartDay)
return (time.DayOfWeek < vEndDay) || (vEndTime.CompareTo(time.TimeOfDay) >= 0);
if (dt.DayOfWeek < vStartDay)
return (dt.DayOfWeek < vEndDay) || (vEndTime.CompareTo(dt.TimeOfDay) >= 0);

return (time.DayOfWeek > vStartDay) || (vStartTime.CompareTo(time.TimeOfDay) <= 0);
return (dt.DayOfWeek > vStartDay) || (vStartTime.CompareTo(dt.TimeOfDay) <= 0);
}

//start day must be same as end day
if (vStartTime >= vEndTime)
return time.DayOfWeek != vStartDay || CheckTime(time.TimeOfDay);
return dt.DayOfWeek != vStartDay || CheckTime(dt.TimeOfDay);

return time.DayOfWeek == vStartDay && CheckTime(time.TimeOfDay);
return dt.DayOfWeek == vStartDay && CheckTime(dt.TimeOfDay);
}

/// <summary>
Expand All @@ -170,29 +182,64 @@ private bool CheckTime(TimeSpan time)

if (vStartTime.CompareTo(vEndTime) < 0)
{
return (time.CompareTo(vStartTime) >= 0 &&
time.CompareTo(vEndTime) <= 0);
return time.CompareTo(vStartTime) >= 0 &&
time.CompareTo(vEndTime) <= 0;
}

if (vStartTime.CompareTo(vEndTime) > 0)
{
return time.CompareTo(vStartTime) >= 0 ||
time.CompareTo(vEndTime) <= 0;
return time.CompareTo(vStartTime) >= 0 ||
time.CompareTo(vEndTime) <= 0;
}

private bool CheckWeekdays(DateTime dt)
{
if (NonStopSession)
throw new InvalidOperationException("NonStopSession is set; this statement should be unreachable");

TimeSpan vStartTime = StartTime ?? throw new QuickFix.ConfigError("StartTime is null");
TimeSpan vEndTime = EndTime ?? throw new QuickFix.ConfigError("EndTime is null");

TimeSpan tod = dt.TimeOfDay;

// session spans midnight
if (vStartTime.CompareTo(vEndTime) < 0) {
return _weekdays.Contains(dt.DayOfWeek) &&
tod.CompareTo(vStartTime) >= 0 &&
tod.CompareTo(vEndTime) < 0;
}

return true;
// session doesn't span midnight
if (tod.CompareTo(vEndTime) >= 0 && tod.CompareTo(vStartTime) < 0)
return false;
var targetDay = tod.CompareTo(vStartTime) >= 0
? dt.DayOfWeek
: PreviousDay(dt.DayOfWeek);
return _weekdays.Contains(targetDay);
}

private DayOfWeek PreviousDay(DayOfWeek d) {
return d == DayOfWeek.Sunday
? DayOfWeek.Saturday
: d - 1;
}

/// <summary>
/// </summary>
/// <param name="settings"></param>
public SessionSchedule(QuickFix.SettingsDictionary settings)
{
if (settings.Has(SessionSettings.NON_STOP_SESSION))
if (settings.Has(SessionSettings.NON_STOP_SESSION)) {
NonStopSession = settings.GetBool(SessionSettings.NON_STOP_SESSION);

if (NonStopSession)
return;
}

if (settings.Has(SessionSettings.WEEKDAYS))
{
_isWeekdaysSession = true;
if (settings.Has(SessionSettings.START_DAY) || settings.Has(SessionSettings.END_DAY) )
throw new ConfigError("StartDay/EndDay are not compatible with 'Weekdays' setting");

_weekdays = settings.GetDays(SessionSettings.WEEKDAYS);
}

if (!settings.Has(SessionSettings.START_DAY) && settings.Has(SessionSettings.END_DAY))
throw new QuickFix.ConfigError("EndDay used without StartDay");
Expand Down
1 change: 1 addition & 0 deletions QuickFIXn/SessionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class SessionSettings
public const string TIME_ZONE = "TimeZone";
public const string START_DAY = "StartDay";
public const string END_DAY = "EndDay";
public const string WEEKDAYS = "Weekdays";
public const string START_TIME = "StartTime";
public const string END_TIME = "EndTime";
public const string HEARTBTINT = "HeartBtInt";
Expand Down
32 changes: 32 additions & 0 deletions QuickFIXn/SettingsDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,37 @@ public bool IsBoolPresentAndTrue(string key) {
return Has(key) && GetBool(key);
}

// TODO: unify this func's switch with the one in GetDay
public HashSet<DayOfWeek> GetDays(string key)
{
string[] weekdayNameArray = GetString(key).Split(",");
var result = new HashSet<DayOfWeek>(weekdayNameArray.Length);
foreach (var weekDayName in weekdayNameArray)
{
string abbr = weekDayName.Trim().Substring(0, 2).ToUpper();
switch (abbr)
{
case "SU": result.Add(DayOfWeek.Sunday);
break;
case "MO": result.Add(DayOfWeek.Monday);
break;
case "TU": result.Add(DayOfWeek.Tuesday);
break;
case "WE": result.Add(DayOfWeek.Wednesday);
break;
case "TH": result.Add(DayOfWeek.Thursday);
break;
case "FR": result.Add(DayOfWeek.Friday);
break;
case "SA": result.Add(DayOfWeek.Saturday);
break;
default: throw new ConfigError("Illegal value " + weekDayName.Trim() + " for " + key);
}
}

return result;
}

public DayOfWeek GetDay(string key) {
string abbr = GetString(key).Substring(0, 2).ToUpperInvariant();
return abbr switch
Expand Down Expand Up @@ -199,6 +230,7 @@ public void SetBool(string key, bool val)
SetString(key, BoolConverter.Convert(val));
}

// TODO: this func is only used by tests! Get it out of here
public void SetDay(string key, DayOfWeek val)
{
switch(val)
Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ What's New
* Also refactor the heck out of DateTimeConverter & tests: many functions renamed/deprecated
* #847 - remove setting MillisecondsInTimeStamp (gbirchmeier)
* Use TimestampPrecision instead (same as QF/j)
* #844 - implement "Weekdays" setting (MichalUssuri/gbirchmeier)

**Non-breaking changes**
* #400 - added DDTool, a C#-based codegen, and deleted Ruby-based generator (gbirchmeier)
Expand Down
Loading

0 comments on commit 3fb3892

Please sign in to comment.