Pluggable Session Storage | Table of Content | Session Schedule |
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.
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 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.
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 |
---|
If a Session is being registered at the time noted by schedule, the scheduler will connect the Session immediately. |
Unregister(Session) removes the given session from the scheduling services.
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. |
The following steps take place while scheduling a Session:
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 |
---|
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 |
---|
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. |
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".
The following example demonstrates how to configure Trace output to a text file.
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>
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)
The following example demonstrates the basic usage of the scheduling services, as well as the ability to define a Session sequence number reset policy.
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(); } } }
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