Sell Side Windows Service Sample
Source code
using Microsoft.Extensions.Hosting;
using OnixS.Fix;
using OnixS.Fix.Fix44;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif
using System.Threading;
using System.Threading.Tasks;
using NLog.Extensions.Logging;
namespace SellSideWindowsService
{
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public class SellSideWindowsService : BackgroundService
{
private int OrderCounter;
private const ProtocolVersion fixVersion = ProtocolVersion.Fix44;
Session sn;
void OnInboundApplicationMsg(object sender, InboundMessageEventArgs e)
{
ReportEvent("Incoming application-level message:\n" + e.Message);
List<Message> replies = new();
switch (e.Message.Type)
{
case MsgType.NewOrderSingle:
Random rnd = new();
int x = rnd.Next(1, 5);
switch (x)
{
case 1:
replies.Add(CreateExecutionReport(e.Message, OrdStatus.New));
break;
case 2:
replies.Add(CreateExecutionReport(e.Message, OrdStatus.New));
replies.Add(CreateExecutionReport(e.Message, OrdStatus.Partial));
break;
case 3:
replies.Add(CreateExecutionReport(e.Message, OrdStatus.New));
replies.Add(CreateExecutionReport(e.Message, OrdStatus.Filled));
break;
case 4:
replies.Add(CreateExecutionReport(e.Message, OrdStatus.Rejected));
break;
}
break;
case MsgType.OrderCancelRequest:
replies.Add(CreateCancelledExecutionReport(e.Message));
break;
default:
Message email = new(MsgType.Email, fixVersion);
email[Tag.EmailThreadID] = "SellSide reply";
email[Tag.EmailType] = EmailType.New;
email[Tag.Subject] = "Message was received";
email[Tag.EmailType] = EmailType.New;
Group group = email.SetGroup(Tag.NoLinesOfText, 1);
group.Set(Tag.Text, 0, "The message with MsgType=" + e.Message[Tag.MsgType] + " was received");
replies.Add(email);
break;
}
Session sn = (Session)sender;
foreach (Message reply in replies)
{
reply.Validate();
sn.Send(reply);
}
}
private Message CreateExecutionReport(Message order, string status)
{
Message execReport = new(MsgType.ExecutionReport, fixVersion);
++OrderCounter;
execReport[Tag.ClOrdID] = order[Tag.ClOrdID];
execReport[Tag.OrderID] = "OrderID_ " + DateTime.Now.ToString("HHmmss", CultureInfo.InvariantCulture);
execReport[Tag.ExecID] = "ExecID_" + OrderCounter;
execReport[Tag.OrdStatus] = status;
execReport[Tag.ExecType] = status;
execReport[Tag.Symbol] = order[Tag.Symbol];
execReport[Tag.Side] = order[Tag.Side];
execReport[Tag.OrdType] = order[Tag.OrdType];
execReport[Tag.OrderQty] = order[Tag.OrderQty];
execReport[Tag.CumQty] = "0";
execReport[Tag.LeavesQty] = order[Tag.OrderQty];
execReport[Tag.AvgPx] = "100.0";
return execReport;
}
private Message CreateCancelledExecutionReport(Message cancelRequest)
{
Message execReport = new(MsgType.ExecutionReport, fixVersion);
++OrderCounter;
execReport[Tag.ClOrdID] = cancelRequest[Tag.ClOrdID];
execReport[Tag.OrigClOrdID] = cancelRequest[Tag.OrigClOrdID];
execReport[Tag.OrderID] = "OrderID_ " + DateTime.Now.ToString("HHmmss", CultureInfo.InvariantCulture);
execReport[Tag.ExecID] = "ExecID_" + OrderCounter;
execReport[Tag.OrdStatus] = OrdStatus.Canceled;
execReport[Tag.ExecType] = ExecType.Canceled;
execReport[Tag.Symbol] = cancelRequest[Tag.Symbol];
execReport[Tag.Side] = cancelRequest[Tag.Side];
execReport[Tag.OrdType] = cancelRequest[Tag.OrdType];
execReport[Tag.OrderQty] = cancelRequest[Tag.OrderQty];
execReport[Tag.CumQty] = "0";
execReport[Tag.LeavesQty] = "0";
execReport[Tag.AvgPx] = "0.0";
return execReport;
}
void OnStateChange(object sender, SessionStateChangeEventArgs e)
{
ReportEvent("New session state: " + e.NewState);
}
static void ReportEvent(string msg)
{
EventLog.WriteEntry("FixClientAsServiceSample", msg, EventLogEntryType.Information);
}
public override Task StartAsync(CancellationToken cancellationToken)
{
if (!cancellationToken.IsCancellationRequested)
{
try
{
EngineSettings settings = new();
settings.SendLogoutOnException = true;
settings.SendLogoutOnInvalidLogon = true; // E.g. to send a Logout when the sequence number of the incoming Logon (A) message is less than expected.
settings.ListenPorts.Add(10450);
//settings.LicenseStore = @"<the absolute path to the folder that contains the license file>";
//settings.Dictionary = @"<the absolute path to XML file with the description of FIX dictionary>";
settings.LoggerProvider = new NLogLoggerProvider();
Engine.Init(settings);
const string SenderCompID = "SellSide";
const string TargetCompID = "BuySide";
sn = new Session(SenderCompID, TargetCompID, fixVersion);
sn.InboundApplicationMessage += OnInboundApplicationMsg;
sn.StateChanged += OnStateChange;
sn.LogonAsAcceptor();
ReportEvent("FixClientAsServiceSample started");
}
catch (Exception ex)
{
EventLog.WriteEntry("FixClientAsServiceSample", ex.ToString(), EventLogEntryType.Error);
}
}
return base.StartAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
try
{
if (sn != null)
{
sn.Logout();
sn = null;
}
if (Engine.IsInitialized)
Engine.Shutdown();
}
catch (Exception ex)
{
EventLog.WriteEntry("FixClientAsServiceSample", ex.ToString(), EventLogEntryType.Error);
}
return base.StopAsync(cancellationToken);
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
}
}
Host Run
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif
namespace SellSideWindowsService
{
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(string[] args)
{
using IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = ".NET SellSide Service";
})
.ConfigureServices(services =>
{
services.AddHostedService<SellSideWindowsService>();
})
.Build();
host.RunAsync().Wait();
}
}
}