SSL Sell Side Sample
Source code
using System;
using System.Collections.Generic;
using OnixS.Fix;
using System.Globalization;
using System.IO;
using System.CommandLine;
using OnixS.Fix.Fix44;
using System.Runtime.InteropServices;
using NLog.Extensions.Logging;
namespace SslSellSide
{
/// <summary>
/// Waits for incoming connections and processes incoming messages.
/// </summary>
internal static class Acceptor
{
private const ProtocolVersion fixVersion = ProtocolVersion.Fix44;
private static int orderCounter;
private static string GetLicenseStoreFolder()
{
string path = Path.Join(AppContext.BaseDirectory, "../../../../../license");
if (Directory.Exists(path))
return path;
// expecting it run after `dotnet publish` using default paths
return Path.Join(AppContext.BaseDirectory, "../../../../../../license");
}
private static System.Security.SecureString MakeSecureString(string input)
{
System.Security.SecureString result = new();
foreach (char c in input)
result.AppendChar(c);
return result;
}
private static void Run(bool usePemCertificate, bool verifyPeer)
{
Console.WriteLine("SSL Sell Side Sample");
EngineSettings settings = new()
{
SendLogoutOnException = true,
SendLogoutOnInvalidLogon = true, // E.g. to send a Logout when the sequence number of the incoming Logon (A) message is less than expected.
LicenseStore = GetLicenseStoreFolder(),
LoggerProvider = new NLogLoggerProvider()
};
var ssl = settings.Ssl;
ssl.ListenPorts.Add(10451);
ssl.CertificateLocation = usePemCertificate ? "server.pem|server-pk.pem" : "server.p12";
if(verifyPeer)
{
ssl.VerifyPeer = true;
ssl.CaFile = usePemCertificate ? "cert-chain.pem" : "cert-chain.p12";
}
ssl.PrivateKeyPassword = MakeSecureString("OnixS");
Engine.Init(settings);
Engine.Instance.Error += (object sender, EngineErrorEventArgs args) =>
{
Console.WriteLine("Engine Error: " + args);
};
Engine.Instance.Warning += (object sender, EngineWarningEventArgs args) =>
{
Console.WriteLine("Engine Warning: " + args);
};
var dictionary = fixVersion.ToDictionary();
const string senderCompID = "SslSellSide";
const string targetCompID = "SslBuySide";
using Session session = new(senderCompID, targetCompID, dictionary)
{
Encryption = EncryptionMethod.Ssl
};
session.InboundApplicationMessage += OnInboundApplicationMessage;
session.StateChanged += (object sender, SessionStateChangeEventArgs e) =>
{
Console.WriteLine("Session state: " + e.NewState.ToString());
};
session.Error += (object sender, SessionErrorEventArgs e) =>
{
Console.WriteLine("Session Error: " + e);
};
session.Warning += (object sender, SessionWarningEventArgs e) =>
{
Console.WriteLine("Warning: " + e);
};
session.LogonAsAcceptor();
Console.WriteLine("\nAwaiting SSL session-initiator with"
+ "\n SenderCompID (49) = " + targetCompID
// Note: from the counterparty point of view SenderCompID is TargetCompID
+ "\n TargetCompID (56) = " + senderCompID
+ "\n FIX version = " + fixVersion
+ "\non port " + Engine.Instance.Settings.Ssl.ListenPorts[0] + " ...");
while (true)
{
Console.Write("Press 'X' to quit");
// Start a console read operation. Do not display the input.
ConsoleKeyInfo cki = Console.ReadKey(true);
// Announce the name of the key that was pressed .
Console.WriteLine($" Key pressed: {cki.Key}\n");
// Exit if the user pressed the 'X' key.
if (cki.Key == ConsoleKey.X)
break;
}
}
private static void OnInboundApplicationMessage(object sender, InboundMessageEventArgs e)
{
Console.WriteLine("Received application-level message:\n" + e.Message);
try
{
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, ExecType.New));
break;
case 2:
replies.Add(CreateExecutionReport(e.Message, OrdStatus.New, ExecType.New));
replies.Add(CreateExecutionReport(e.Message, OrdStatus.Partial, ExecType.Trade));
break;
case 3:
replies.Add(CreateExecutionReport(e.Message, OrdStatus.New, ExecType.New));
replies.Add(CreateExecutionReport(e.Message, OrdStatus.Filled, ExecType.Trade));
break;
case 4:
replies.Add(CreateExecutionReport(e.Message, OrdStatus.Rejected, ExecType.Rejected));
break;
}
break;
case MsgType.OrderCancelRequest:
replies.Add(CreateCancelledExecutionReport(e.Message));
break;
default:
Message email = new(MsgType.Email, ((Session)sender).Dictionary);
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;
}
var sn = (Session) sender;
foreach (Message reply in replies)
{
reply.Validate();
sn.Send(reply);
Console.WriteLine("Sent to the counterparty:\n" + reply);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception during the processing of the incoming message: " + ex);
}
}
private static Message CreateExecutionReport(Message order, string orderStatus, string executionType)
{
Message report = new(MsgType.ExecutionReport, order.Dictionary);
++orderCounter;
report[Tag.ClOrdID] = order[Tag.ClOrdID];
report[Tag.OrderID] = "OrderID_ " + DateTime.Now.ToString("HHmmss", CultureInfo.InvariantCulture);
report[Tag.ExecID] = "ExecID_" + orderCounter;
report[Tag.OrdStatus] = orderStatus;
report[Tag.ExecType] = executionType;
report[Tag.Symbol] = order[Tag.Symbol];
report[Tag.Side] = order[Tag.Side];
report[Tag.OrdType] = order[Tag.OrdType];
report[Tag.OrderQty] = order[Tag.OrderQty];
report[Tag.LeavesQty] = order[Tag.OrderQty];
report.Set(Tag.CumQty, 0)
.Set(Tag.AvgPx, 100.0);
return report;
}
private static Message CreateCancelledExecutionReport(Message cancelRequest)
{
Message report = new(MsgType.ExecutionReport, cancelRequest.Dictionary);
++orderCounter;
report[Tag.ClOrdID] = cancelRequest[Tag.ClOrdID];
report[Tag.OrigClOrdID] = cancelRequest[Tag.OrigClOrdID];
report[Tag.OrderID] = "OrderID_ " + DateTime.Now.ToString("HHmmss", CultureInfo.InvariantCulture);
report[Tag.ExecID] = "ExecID_" + orderCounter;
report[Tag.OrdStatus] = OrdStatus.Canceled;
report[Tag.ExecType] = ExecType.Canceled;
report[Tag.Symbol] = cancelRequest[Tag.Symbol];
report[Tag.Side] = cancelRequest[Tag.Side];
report[Tag.OrdType] = cancelRequest[Tag.OrdType];
report[Tag.OrderQty] = cancelRequest[Tag.OrderQty];
report.Set(Tag.CumQty, 0)
.Set(Tag.LeavesQty, 0)
.Set(Tag.AvgPx, 0.0);
return report;
}
/// <summary>
/// The main entry point for the application.
/// </summary>
private static int Main(string[] args)
{
try
{
var pemOption = new Option<bool>(
new[] { "--use-pem-certificate", "-p" },
"Option to use PEM certificate instead of default PFX one");
var verifyPeerOption = new Option<bool>(
new[] { "--verify-peer", "-v" },
"Specifies if the server should verify the client certificate");
var rootCommand = new RootCommand { pemOption, verifyPeerOption };
rootCommand.Description = "SSL Sell Side Sample";
rootCommand.TreatUnmatchedTokensAsErrors = true;
rootCommand.SetHandler<bool, bool>(Run, pemOption, verifyPeerOption);
return rootCommand.Invoke(args);
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && ex.Message.Contains("ErrorCode=10013"))
{
Console.WriteLine("The socket is already in use, or access to the port is restricted on this OS. Please try to change the listen port in EngineSettings.ListenPorts to another one.");
Console.WriteLine("You can view a list of which ports are excluded from your user by running this command: 'netsh interface ipv4 show excludedportrange protocol=tcp'");
}
return 1;
}
finally
{
// From https://github.com/NLog/NLog/wiki/Tutorial:
// NET Application running on Mono / Linux are required to stop threads / timers before entering application shutdown phase.
// Failing to do this will cause unhandled exceptions and segmentation faults, and other unpredictable behavior.
NLog.LogManager.Shutdown(); // Flush and close down internal threads and timers
}
}
}
}