forwardPluggable Session Storage   Table of ContentSession Scheduleforward
Scheduling Sessions for Automatic Connection

In real life FIX Connections of Sessions occur at regular intervals. A lot of trading systems have public schedules which declare time frames for trading. The Session class exposes members for the FIX Connections basic handling. It also provides users with automatic reconnection facility in case of connection failure. However, there is no way in the Session functionality to maintain Connections on a systematic basis.

Sessions Scheduler

To satisfy the needs of real-life trading schedules, the OnixS .NET Framework FIX Engine offers the Sessions Scheduler. This service will automatically connect certain FIX sessions to the counterparty at the beginning of the trading day, as well as disconnect at the end of a day. It is also possible to keep a FIX Session connected for an entire trading week and disconnect it at the end of the last trading day.

The Sessions Scheduler is available as stand-alone extension to OnixS .NET Framework FIX Engine since this functionality is not considered directly a part of the formal FIX Session behavior.

Scheduler class

Scheduler is a workhorse class of the Sessions Scheduler component. This class exposes a simple but comprehensive API to handle connections by schedules of any complexity.

Registering a Session for Automatic Connection

Register(Session, SessionSchedule, SessionConnectionSettings) member schedules a session for automatic logon and logout according to the specified session time, which can be delivered to the Scheduler as an instance of the appropriate class, or as a name of the predefined preset. The parameter of the type SessionConnectionSettings specifies the role of a Session (either Acceptor or Initiator) and primary connection parameters like host and port, which the Session will use to establish the FIX Connection.

Note Note
If a Session is being registered at the time noted by schedule, the scheduler will connect the Session immediately.
Removing a Session from Scheduling Services

Unregister(Session) removes the given session from the scheduling services.

Note Note
Unregister(Session) doesn't disconnect an active session from the trading system. Therefore, if a session has been already connected by the scheduler, it remains connected after unregistering.
Important Behavioral Aspects

The following steps take place while scheduling a Session:

  • If a session is registered during the time covered by specified schedule, the scheduler will connect a session immediately.
  • If a session is registered after logout and before logon time, no attempts will be made to connect the session until the next logon time.
  • If a session is registered after logout and before logon time, and the session is in active state, it will be disconnected immediately.
  • In case of regular logon failure, the scheduler tries to logon again in accordance with its own reconnection settings (ReconnectAttempts and ReconnectInterval values).
  • In case of an active connection break by some network issues, the FIX Engine tries to restore the connection in accordance with the Engine configuration (Reconnect.Attempts and Reconnect.Interval values).
  • In case of a normal disconnect of an active connection by Logout messages exchange, during the activity time, the scheduler tries to logon again immediately without an interval.
  • If a session is connected within reconnection attempts defined by Engine configuration, no error will be reported by the scheduler.
  • If a session is being retried to connect within reconnection attempts and logout time is reached, a scheduler will stop trying to connect the session and no error will be reported.
  • If a session remains disconnected after all the attempts, the Error event will be raised, and registered error handlers will be invoked.
  • Once the Error event is raised by the scheduler, all attempts to logon the disconnected session will be suspended till the next activity (logon) time.

Handling Scheduling Warnings and Errors

Scheduler exposes Error event to get notified about errors in session scheduling. This event is not linked with session errors. Scheduler raises notifications only about scheduling-related errors like inability to connect session with a specified amount of time.

Note Note
Once the Error event is raised by the scheduler, all attempts to logon disconnected session will be suspended till the next activity (logon) time.

Warning event is exposed by the Scheduler to provide its users with more information about the scheduling flow. This kind of information is usually related to non-critical errors which occur while scheduling a session.

Note Note
In case of failure at logon, the scheduler will raise the Warning event and will try to perform logon again in accordance with its own reconnection settings (ReconnectAttempts and ReconnectInterval values). Once all attempts are performed and a session remains disconnected, Scheduler will fire the Error event and will stop all subsequent attempts to bring a session to a connected state until the next logon time.
Scheduling log

Scheduler uses the standard Trace output. User can configure trace via the application configuration file or from code. A name of the trace switch, which a scheduler uses, is "FIXForge.NET.FIX.Scheduling".

Example

The following example demonstrates how to configure Trace output to a text file.

C#
Stream traceFile = File.Create(Path.Combine(Engine.Instance.Settings.LogDirectory, "TraceLog.txt"));

// Create a new text writer using the output stream, and add it to the trace listeners.
TextWriterTraceListener textListener = new TextWriterTraceListener(traceFile);
Trace.Listeners.Add(textListener);
         <?xml version="1.0" encoding="utf-8" ?>
  <configuration>
    <system.diagnostics>
  <switches>
    <!-- This switch controls general messages.
        In order to receive general trace messages, change the value to the appropriate level.
         "0" gives nothing
         "1" gives error messages,
        "2" gives error and warning messages,
         "3" gives error, warning and info messages,
         "4" gives error, warning, info and verbose messages.
         -->
    <add name="FIXForge.NET.FIX.Scheduling" value="4"/>
  </switches>
  <!-- autoflush=false would decrease the system load -->
  <trace autoflush="true">
    <listeners>
    <remove name="Default"/>
    <add name="textWriterTraceListener" type="System.Diagnostics.TextWriterTraceListener"
        initializeData="MsgStorage\TraceLog.txt"/>
    </listeners>
  </trace>
  </system.diagnostics>
</configuration>
VB
Dim traceFile As Stream = File.Create(Path.Combine(Engine.Instance.Settings.LogDirectory, "TraceLog.txt"))

' Create a new text writer using the output stream, and add it to the trace listeners.
Dim textListener As TextWriterTraceListener = New TextWriterTraceListener(traceFile)
Trace.Listeners.Add(textListener)
Example

The following example demonstrates the basic usage of the scheduling services, as well as the ability to define a Session sequence number reset policy.

C#
class Sample : IDisposable
{
    // Dumps errors from a scheduler to the console.
    class ErrorReporter
    {
        private readonly Scheduler scheduler_;

        public ErrorReporter(Scheduler scheduler)
        {
            (scheduler_ = scheduler).Error += OnSchedulerError;
        }

        private void OnSchedulerError(
            object scheduler, SessionErrorEventArgs args)
        {
            Console.WriteLine(
                "Scheduler reported error for session {0}: {1}",
                args.Session, args.Reason);
        }
    }

    // Dumps session state changes to the console.
    class SessionStateChangeDetector
    {
        private readonly Session session_;

        public SessionStateChangeDetector(Session session)
        {
            (session_ = session).StateChangeEvent += OnStateChange;
        }

        private void OnStateChange(
            object session,
            Session.StateChangeEventArgs args)
        {
            Console.WriteLine(
                "Session {0} changed its state from {1} to {2}.",
                session, args.PrevState, args.NewState);
        }
    }

    // Samples configuration parameters.

    const string acceptorSideId_ = "Acceptor";
    const string initiatorSideId_ = "Initiator";

    const ProtocolVersion protocol_ = ProtocolVersion.FIX44;
    const bool seqNumberLogonPolicy_ = true;

    private Scheduler scheduler_;
    private ErrorReporter errorReporter_;

    private Session acceptor_;
    private Session initiator_;

    // Sample schedules only initiator, therefore, its
    // state will be traced to make sure the scheduler works.
    private SessionStateChangeDetector initiatorStateTracer_;

    public Sample()
    {
        ConstructScheduler();
        ConstructSessions();
    }

    private void ConstructSessions()
    {
        acceptor_ = new Session(
            acceptorSideId_, initiatorSideId_,
            protocol_,
            seqNumberLogonPolicy_);

        initiator_ = new Session(
            initiatorSideId_, acceptorSideId_,
            protocol_,
            seqNumberLogonPolicy_);

        initiatorStateTracer_ =
            new SessionStateChangeDetector(
            initiator_);
    }

    private void ConstructScheduler()
    {
        scheduler_ = new Scheduler();
        errorReporter_ = new ErrorReporter(scheduler_);
    }

    ~Sample()
    {
        CleanUp();
    }

    public void Dispose()
    {
        CleanUp();
        GC.SuppressFinalize(this);
    }

    private void CleanUp()
    {
        scheduler_.Dispose();

        initiator_.Dispose();
        acceptor_.Dispose();
    }

    // Carries the sample out.
    public void Run()
    {
        // Acceptor is being maintained manually.
        // Therefore, reset of sequence numbers
        // must be done manually before logon.
        acceptor_.ResetLocalSequenceNumbers();

        // Comment this call if you want to check
        // how the scheduler notifies about errors.
        acceptor_.LogonAsAcceptor();

        SessionSchedule scheduleForInitiator =
            ConstructShortTimeActivitySchedule();

        // Let's play the party..

        scheduler_.Register(
            initiator_,
            scheduleForInitiator,
            new InitiatorConnectionSettings("localhost", 4500));

        Console.WriteLine(
            "Session {0} scheduled for automatic connection.",
            initiator_);

        // Since a session is initially in disconnected state,
        // we can't simply wait for logout. Instead, we have
        // to give time for the scheduler to activate the session.
        // The best and simplest approach is to wait till time
        // of logout, as it's specified in the constructed schedule.

        PauseUntil(
            scheduleForInitiator.LogoutTimes[
            (int)DateTime.Now.DayOfWeek]);

        // Make sure that initiator is shutdown.
        WaitUntilLogout(initiator_);

        // Don't forget that once initiator disconnects from
        // the acceptor, the acceptor starts waiting for the next logon.
        // Since we manage it manually, we have to call logout.

        acceptor_.Logout();
        WaitUntilLogout(acceptor_);
    }

    // Constructs schedule with short time activity in 30 seconds.
    // Also logon time is delayed for a couple of seconds to demonstrate
    // that scheduler will connect a session only when logon event occurs.
    private static SessionSchedule
        ConstructShortTimeActivitySchedule()
    {
        const int logonDelayInSeconds = 5;
        const int sessionDurationInSeconds = 30;

        TimeSpan logonTime =
            DateTime.Now.TimeOfDay.Add(
            new TimeSpan(0, 0, logonDelayInSeconds));

        TimeSpan logoutTime = logonTime.Add(
            new TimeSpan(0, 0, sessionDurationInSeconds));

        return new SessionSchedule(
            DayOfWeek.Sunday,
            DayOfWeek.Saturday,
            logonTime,
            logoutTime,
            SessionDuration.Day,
            SequenceNumberResetPolicy.Daily);
    }

    private static void PauseUntil(TimeSpan timeOfDay)
    {
        System.Threading.Thread.Sleep(
            timeOfDay.Subtract(DateTime.Now.TimeOfDay));
    }

    private static void WaitUntilLogout(Session session)
    {
        const int oneSecondPause = 1000;

        while (session.State != Session.SessionState.DISCONNECTED)
            System.Threading.Thread.Sleep(oneSecondPause);
    }
}

static class Program
{
    private static void Initialize()
    {
        const int connectionPort = 4500;

        Engine.Init(connectionPort);

        Engine.Instance.Settings.ReconnectAttempts = 2;
        Engine.Instance.Settings.ReconnectInterval = 1;
    }

    private static void CleanUp()
    {
        if (Engine.IsInitialized())
            Engine.Instance.Shutdown();
    }

    public static void Main(String[] args)
    {
        try
        {
            Initialize();

            using (Sample sample = new Sample())
            {
                sample.Run();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine();

            Console.WriteLine(
                "Error while executing sample: {0}",
                ex);
        }
        finally
        {
            CleanUp();
        }
    }
}
VB
Class Sample

    Implements IDisposable

    ' Dumps errors from scheduler to the console.
    Class ErrorReporter

        Private ReadOnly scheduler_ As Scheduler

        Public Sub New(ByVal scheduler As Scheduler)
            scheduler_ = scheduler
            AddHandler scheduler_.Error, AddressOf OnSchedulerError
        End Sub

        Private Sub OnSchedulerError(ByVal scheduler As Object, ByVal args As SessionErrorEventArgs)
            Console.WriteLine("Scheduler reported error for session {0}: {1}", args.Session, args.Reason)
        End Sub
    End Class

    ' Dumps session state changes to the console.
    Class SessionStateChangeDetector

        Private ReadOnly session_ As Session

        Public Sub New(ByRef session As Session)
            session_ = session
            AddHandler session_.StateChangeEvent, AddressOf OnStateChange
        End Sub

        Private Sub OnStateChange(ByVal session As Object, ByVal args As Session.StateChangeEventArgs)
            Console.WriteLine("Session {0} changed its state from {1} to {2}.", session, args.PrevState, args.NewState)
        End Sub
    End Class

    ' Samples configuration parameters.

    Const acceptorSideId_ As String = "Acceptor"
    Const initiatorSideId_ As String = "Initiator"

    Const protocol_ As ProtocolVersion = ProtocolVersion.FIX44
    Const seqNumberLogonPolicy_ As Boolean = True

    Private scheduler_ As Scheduler
    Private errorReporter_ As ErrorReporter

    Private acceptor_ As Session
    Private initiator_ As Session

    ' Sample schedules only initiator, therefore its
    ' state will be traced to make sure scheduler works.
    Private initiatorStateTracer_ As SessionStateChangeDetector

    Public Sub Sample()
        ConstructScheduler()
        ConstructSessions()
    End Sub

    Private Sub ConstructSessions()

        acceptor_ = New Session(acceptorSideId_, initiatorSideId_, protocol_, seqNumberLogonPolicy_)
        initiator_ = New Session(initiatorSideId_, acceptorSideId_, protocol_, seqNumberLogonPolicy_)
        initiatorStateTracer_ = New SessionStateChangeDetector(initiator_)
    End Sub

    Private Sub ConstructScheduler()
        scheduler_ = New Scheduler()
        errorReporter_ = New ErrorReporter(scheduler_)
    End Sub

    Protected Overrides Sub Finalize()
        Try
            CleanUp()
        Finally
            MyBase.Finalize()
        End Try
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        CleanUp()
        GC.SuppressFinalize(Me)
    End Sub

    Private Sub CleanUp()
        scheduler_.Dispose()

        initiator_.Dispose()
        acceptor_.Dispose()
    End Sub

    ' Carries the sample out.
    Public Sub Run()
        ' Acceptor is being maintained manually.
        ' Therefore, reset of local sequence numbers
        ' must be done manually before logon.
        acceptor_.ResetLocalSequenceNumbers()

        ' Comment this call if you want to check
        ' how scheduler notifies about errors.
        acceptor_.LogonAsAcceptor()

        Dim scheduleForInitiator As SessionSchedule = ConstructShortTimeActivitySchedule()

        ' Let's play the party..

        scheduler_.Register(initiator_, scheduleForInitiator, New InitiatorConnectionSettings("localhost", 4500))

        Console.WriteLine("Session {0} scheduled for automatic connection.", initiator_)

        ' Since a session is initially in disconnected state,
        ' we can't simply wait for logout. Instead, we have
        ' to give time for a scheduler to activate the session.
        ' The best and simplest approach is to wait till time
        ' of logout, as it's specified in constructed schedule.

        PauseUntil(scheduleForInitiator.LogoutTimes(CInt(DateTime.Now.DayOfWeek)))

        ' Make sure that initiator is shutdown.
        WaitUntilLogout(initiator_)

        ' Don't forget that once initiator disconnects from
        ' the acceptor, the acceptor starts waiting for the next logon.
        ' Since we manage it manually, we have to call logout.

        acceptor_.Logout()
        WaitUntilLogout(acceptor_)
    End Sub

    ' Constructs schedule with a short time activity in 30 seconds.
    ' Also, logon time is delayed for a couple of seconds to demonstrate
    ' that scheduler will connect the session only when logon event occurs.
    Private Shared Function ConstructShortTimeActivitySchedule() As SessionSchedule
        Const logonDelayInSeconds As Integer = 5
        Const sessionDurationInSeconds As Integer = 30

        Dim logonTime As TimeSpan = DateTime.Now.TimeOfDay.Add(New TimeSpan(0, 0, logonDelayInSeconds))

        Dim logoutTime As TimeSpan = logonTime.Add(New TimeSpan(0, 0, sessionDurationInSeconds))

        Return New SessionSchedule(DayOfWeek.Sunday, DayOfWeek.Saturday, logonTime, logoutTime, SessionDuration.Day, SequenceNumberResetPolicy.Daily)
    End Function

    Private Shared Sub PauseUntil(ByVal timeOfDay As TimeSpan)
        System.Threading.Thread.Sleep(timeOfDay.Subtract(DateTime.Now.TimeOfDay))
    End Sub

    Private Shared Sub WaitUntilLogout(ByVal session__1 As Session)
        Const oneSecondPause As Integer = 1000

        While session__1.State <> Session.SessionState.DISCONNECTED
            System.Threading.Thread.Sleep(oneSecondPause)
        End While
    End Sub

End Class

Class Program
    Private Shared Sub Initialize()
        Const connectionPort As Integer = 4500

        Engine.Init(connectionPort)

        Engine.Instance.Settings.ReconnectAttempts = 2
        Engine.Instance.Settings.ReconnectInterval = 1
    End Sub

    Private Shared Sub CleanUp()
        If Engine.IsInitialized() Then
            Engine.Instance.Shutdown()
        End If
    End Sub

    Public Shared Sub Main(ByVal args As [String]())
        Try
            Initialize()

            Using sample As New Sample()
                sample.Run()
            End Using
        Catch ex As Exception
            Console.WriteLine()

            Console.WriteLine("Error while executing sample: {0}", ex)
        Finally
            CleanUp()
        End Try
    End Sub

End Class
Content