Trading Client Sample
An interactive console application to send orders and receive responses.
Source code
using System;
using System.Globalization;
using OnixS.Cme.ILink3;
using OnixS.SimpleBinaryEncoding;
namespace TradingClientSample
{
internal class TradingClient
{
private static Session CreateSession(SessionSettings settings, string segmentId, ulong uuid, string customKey)
{
if (0 == StringComparer.CurrentCultureIgnoreCase.Compare(segmentId, "CGW"))
return new CgwSession(settings, SessionStorageType.FileBasedStorage, uuid, customKey);
return new Session(settings, int.Parse(segmentId, CultureInfo.InvariantCulture), SessionStorageType.FileBasedStorage, uuid, customKey);
}
public TradingClient()
{
failover = Settings.FaultTolerance;
CreateSessions();
ConstructMenu();
}
public void Run()
{
menu.ProcessRequests();
}
private void ConstructMenu()
{
menu
.Add("Connect Sessions.", "Sending Negotiation/Establishment.. ", EstablishConnection)
.Add("Review session status.", "Viewing the status of sessions.. ", OnViewSessions)
.Add("Review sent orders and their status. ", "Viewing sent orders and their status.. ", OnViewOrders)
.Add("Create and send New Order. ", "Creating new order.. ", OnSendNewOrder)
.Add("Submit Order Modify Request. ", "Order Modify Request.. ", OnOrderModifyRequest)
.Add("Submit Order Cancel Request. ", "Order Cancel Request.. ", OnOrderCancelRequest)
.Add("Submit Order Mass Action Request. ", "Order Mass Action Request.. ", OnOrderMassActionRequest)
.Add("Submit Order Mass Status Request. ", "Order Mass Status Request.. ", OnMassStatusRequest)
.Add("Submit SecurityDefinitionRequest. ", "Creating SecurityDefinition.. ", OnSecurityDefinitionRequest)
.Add("Submit RequestForQuote. ", "Creating RFQ.. ", OnRequestForQuote)
.Add("Submit New Order Cross. ", "Creating Cross Order.. ", OnCrossOrder)
.Add("Disconnect all sessions", "Closing connections...", Disconnect)
.Add("Reset Sequence Numbers", "Reset Sequence Numbers", ResetSequenceNumbers)
.Add("Close the connection and exit. ", "Exiting application.. ", OnExit)
;
}
private bool CheckSessionConnected()
{
if (!connected)
Screen.Info("Session is not connected. Please establish the connection first.");
return connected;
}
private void SubscribeSession(Session session)
{
session.InboundApplicationMessage += OnInboundApplicationMessage;
session.InboundSessionMessage += OnInboundSessionMessage;
session.StateChanged += OnStateChanged;
session.Failover += OnFailover;
session.Error += (object sender, SessionErrorEventArgs e) => Screen.Error(e.Description);
session.Warning += (object sender, SessionWarningEventArgs e) => Screen.Warning(e.Description);
}
private void CreateSessions()
{
Screen.Info("Creating the primary iLink3 Session...");
SessionSettings settings = new()
{
AccessKey = Settings.AccessKey,
FirmId = Settings.FirmId,
KeepAliveInterval = Settings.KeepAliveInterval,
LicenseStore = "../../../../../license|../../../../license|.",
SecretKey = Settings.SecretKey,
SessionId = Settings.SessionId,
TradingSystemName = Settings.TradingSystemName,
TradingSystemVendor = Settings.TradingSystemVendor,
TradingSystemVersion = Settings.TradingSystemVersion,
};
primarySession = CreateSession(settings, Settings.MarketSegment, Session.UndefinedUuid, "");
SubscribeSession(primarySession);
if (Settings.InSeqNum > 0)
primarySession.InSeqNum = Settings.InSeqNum;
if (Settings.OutSeqNum > 0)
primarySession.OutSeqNum = Settings.OutSeqNum;
if (failover)
{
Screen.Info("Creating the backup iLink3 Session...");
backupSession = CreateSession(settings, Settings.MarketSegment, primarySession.SessionId.Uuid, "Backup");
backupSession.FaultToleranceIndicator = FaultToleranceIndicator.Backup;
SubscribeSession(backupSession);
}
messageFactory = new MessageFactory(primarySession.CreateEncoder(), Settings.SessionId);
}
private void OnInboundApplicationMessage(object sender, InboundMessageEventArgs e)
{
var message = e.Message;
Screen.Info("Message received from counterparty: ", message.ToString());
if(message.Contains(Tag.PossRetransFlag) && (1 == message.GetByte(Tag.PossRetransFlag)))
return;
if (!message.Contains(Tag.ClOrdID))
return;
var order = FindByOrderId(e.Message);
switch (message.TemplateID)
{
case TemplateId.ExecutionReportNew:
SetCommonFields(ref order, e.Message);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
break;
case TemplateId.ExecutionReportModify:
SetCommonFields(ref order, e.Message);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.LeavesQty = message.GetUnsignedInteger(Tag.LeavesQty);
order.CumQty = message.GetUnsignedInteger(Tag.CumQty);
break;
case TemplateId.ExecutionReportCancel:
SetCommonFields(ref order, e.Message);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.OrderQty = message.GetUnsignedInteger(Tag.OrderQty);
order.CumQty = message.GetUnsignedInteger(Tag.CumQty);
break;
case TemplateId.ExecutionReportReject:
SetCommonFields(ref order, e.Message);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
break;
case TemplateId.ExecutionReportStatus:
SetCommonFields(ref order, e.Message);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.LeavesQty = message.GetUnsignedInteger(Tag.LeavesQty);
order.CumQty = message.GetUnsignedInteger(Tag.CumQty);
break;
case TemplateId.ExecutionReportTradeOutright:
SetCommonFields(ref order, e.Message);
order.OrderStatus = message.GetEnum<OrderStatus>(Tag.OrdStatus);
order.LeavesQty = message.GetUnsignedInteger(Tag.LeavesQty);
order.CumQty = message.GetUnsignedInteger(Tag.CumQty);
order.LastPx = message.GetDecimal(Tag.LastPx);
break;
case TemplateId.ExecutionReportTradeSpread:
SetCommonFields(ref order, e.Message);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.LeavesQty = message.GetUnsignedInteger(Tag.LeavesQty);
order.CumQty = message.GetUnsignedInteger(Tag.CumQty);
order.LastPx = message.GetDecimal(Tag.LastPx);
break;
case TemplateId.ExecutionReportTradeSpreadLeg:
order.OrderId = message.GetUnsignedLong(Tag.OrderID);
order.SecurityId = message.GetInteger(Tag.SecurityID);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.CumQty = message.GetUnsignedInteger(Tag.CumQty);
order.LastPx = message.GetDecimal(Tag.LastPx);
break;
case TemplateId.ExecutionReportElimination:
SetCommonFields(ref order, e.Message);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.LeavesQty = message.GetUnsignedInteger(Tag.LeavesQty);
break;
case TemplateId.ExecutionReportTradeAddendumOutright:
order.OrderId = message.GetUnsignedLong(Tag.OrderID);
order.SecurityId = message.GetInteger(Tag.SecurityID);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.LastPx = message.GetDecimal(Tag.LastPx);
order.TransactTime = message.GetUnsignedLong(Tag.TransactTime);
break;
case TemplateId.ExecutionReportTradeAddendumSpread:
order.OrderId = message.GetUnsignedLong(Tag.OrderID);
order.SecurityId = message.GetInteger(Tag.SecurityID);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.LastPx = message.GetDecimal(Tag.LastPx);
order.TransactTime = message.GetUnsignedLong(Tag.TransactTime);
break;
case TemplateId.ExecutionReportTradeAddendumSpreadLeg:
order.OrderId = message.GetUnsignedLong(Tag.OrderID);
order.SecurityId = message.GetInteger(Tag.SecurityID);
order.OrderStatus = (OrderStatus)message.GetChar(Tag.OrdStatus);
order.LastPx = message.GetDecimal(Tag.LastPx);
order.TransactTime = message.GetUnsignedLong(Tag.TransactTime);
break;
}
Screen.Info("Order changed: " + order.ToString());
}
private static void OnInboundSessionMessage(object sender, InboundMessageEventArgs e)
{
Screen.Info("Message received from counterparty: ", e.Message.ToString());
}
private void OnStateChanged(object sender, SessionStateChangeEventArgs e)
{
Screen.Info("Session state changed to ", e.NewState.ToString());
connected = e.NewState == SessionState.Established;
}
private void OnFailover(object sender, FailoverEventArgs e)
{
var session = (Session)sender;
bool isPrimarySession = session == primarySession;
Screen.Info("Changing the session role of " + (isPrimarySession ? "primary" : "backup") +
" session to " + e.FaultToleranceIndicator.ToString() + "...");
if (e.FaultToleranceIndicator == FaultToleranceIndicator.Primary)
{
if (session == backupSession)
{
backupSession.InSeqNum = primarySession.InSeqNum;
backupSession.OutSeqNum = primarySession.OutSeqNum;
primarySession.FaultToleranceIndicator = FaultToleranceIndicator.Backup;
}
else
{
primarySession.InSeqNum = backupSession.InSeqNum;
primarySession.OutSeqNum = backupSession.OutSeqNum;
backupSession.FaultToleranceIndicator = FaultToleranceIndicator.Backup;
}
}
}
private CommandExecutionStatus EstablishConnection()
{
if (!connected)
{
try
{
primarySession.Connect(Settings.Host, Settings.Port);
}
catch (Exception ex)
{
Screen.Info("Cannot connect to the primary host: ", ex.ToString());
// If the FaultTolerance is used and the primary iLink3 session is out of service, then we try to conne
if (!failover)
throw;
}
// 3. If the FaultTolerance is used, then we connect to the backup iLink3 session to activate the fail-over logic.
if (failover)
{
try
{
backupSession.Connect(Settings.BackupHost, Settings.BackupPort);
}
catch (Exception ex)
{
Screen.Info("Cannot connect to the backup host: ", ex.ToString());
throw;
}
}
Screen.Info("Done");
}
else
{
Screen.Info("Connection is already established.");
}
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus Disconnect()
{
if (failover)
{
if (null != backupSession && backupSession.State != SessionState.Disconnected)
{
Screen.Info("Disconnecting from backup counterparty.. ");
backupSession.Disconnect();
}
}
if (null != primarySession && primarySession.State != SessionState.Disconnected)
{
Screen.Info("Disconnecting from primary counterparty.. ");
primarySession.Disconnect();
}
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnViewOrders()
{
ListViewer.OutputItems(orderBook.GetEntries(), 10);
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnSendNewOrder()
{
if (!CheckSessionConnected())
return CommandExecutionStatus.ContinueExecution;
Screen.Info("Sending new order to counterparty...");
var order = NewOrder();
var msg = messageFactory.GetNewOrderSingle(order);
Send(msg);
orderBook.Store(order);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnOrderModifyRequest()
{
if (!CheckSessionConnected())
return CommandExecutionStatus.ContinueExecution;
var ordersInBook = orderBook.GetEntries();
if (ordersInBook.Count == 0)
{
Screen.Info("No orders found");
return CommandExecutionStatus.ContinueExecution;
}
ListViewer.OutputItems(ordersInBook, 10);
Screen.Out("Enter the index of the order to be modified");
int orderIndex = int.Parse(Screen.GetInput());
var order = ordersInBook[orderIndex - 1];
// Changing OrderQty
Screen.Info("Enter new OrderQty (or Enter to skip): ");
order.OrderQty = Screen.GetInput(order.OrderQty);
// Changing Price
Screen.Info("Enter new Price mantissa (or Enter to skip): ");
order.Price = Screen.GetInput(order.Price);
var msg = messageFactory.GetModifyRequest(order);
Send(msg);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnOrderCancelRequest()
{
if (!CheckSessionConnected())
return CommandExecutionStatus.ContinueExecution;
var ordersInBook = orderBook.GetEntries();
if (ordersInBook.Count == 0)
{
Screen.Info("No orders found");
return CommandExecutionStatus.ContinueExecution;
}
ListViewer.OutputItems(ordersInBook, 10);
Screen.Out("Enter the index of the order to be canceled");
int orderIndex = int.Parse(Screen.GetInput());
var order = ordersInBook[orderIndex - 1];
var msg = messageFactory.GetCancelRequest(order);
Send(msg);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnCrossOrder()
{
if (!CheckSessionConnected())
return CommandExecutionStatus.ContinueExecution;
Order buySideOrder = new() { Side = Side.Buy };
Screen.Info("Enter Price mantissa (default = " + Constants.DefaultPriceMantissa + "): ");
buySideOrder.Price = Screen.GetInput(Constants.DefaultPriceMantissa) * Constants.DefaultPriceExponent;
Screen.Info("Enter SecurityId (default=" + Constants.DefaultSecurityId + "): ");
buySideOrder.SecurityId = Screen.GetInput(Constants.DefaultSecurityId);
Screen.Info("Enter Quantity (default = 100): ");
buySideOrder.OrderQty = Screen.GetInput(100u);
Order sellSideOrder = new() {
OrderQty = buySideOrder.OrderQty,
Price = buySideOrder.Price,
SecurityId = buySideOrder.SecurityId,
Side = Side.Sell,
};
var msg = messageFactory.GetNewOrderCross(buySideOrder, sellSideOrder);
Send(msg);
orderBook.Store(buySideOrder)
.Store(sellSideOrder);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnViewSessions()
{
Screen.Info("Fail-over is " + (failover ? "on." : "off."));
Screen.Info("Primary iLink3 session: ", primarySession + ", State=" + primarySession.State + ".");
if (failover)
Screen.Info("Backup iLink3 session: ", backupSession + ", State=" + backupSession.State + ".");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnOrderMassActionRequest()
{
if (!CheckSessionConnected())
return CommandExecutionStatus.ContinueExecution;
Screen.Info("Sending Order Mass Action Request to counterparty.. ");
Screen.Info("Enter MassActionScope (1=Instrument (default), 9=Market Segment ID, 10=Group Code): ");
var massActionScope = Screen.GetInputEnum<MassActionScope>(MassActionScope.Instrument);
Screen.Info("Enter " + massActionScope + " : ");
string traits = Screen.GetInput();
var msg = messageFactory.GetOrderMassActionRequest(massActionScope, traits);
Send(msg);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnMassStatusRequest()
{
if (!CheckSessionConnected())
return CommandExecutionStatus.ContinueExecution;
Screen.Info("Sending Mass Status Request to counterparty.. ");
Screen.Info("Enter massStatusReqType (1 = Instrument (default), 3 = Group Code, 100 = Market Segment : ");
var massStatusReqType = Screen.GetInputEnum(MassStatusReqTyp.Instrument);
Screen.Info("Enter " + massStatusReqType + " : ");
var traits = Screen.GetInput();
var msg = messageFactory.GetOrderMassStatusRequest(massStatusReqType, traits);
Send(msg);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnSecurityDefinitionRequest()
{
if (!CheckSessionConnected())
return CommandExecutionStatus.ContinueExecution;
Screen.Info("Sending Security Definition Request to counterparty...");
var msg = messageFactory.GetSecurityDefinitionRequest();
Send(msg);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnRequestForQuote()
{
if (!CheckSessionConnected())
return CommandExecutionStatus.ContinueExecution;
Screen.Info("Sending Request For Quote to counterparty.. ");
Screen.Info("Enter Qty (default = 100):");
var qti = Screen.GetInput(100u);
Screen.Info("Enter Side ( 1 = Buy (default), 2 = Sell, 8 = Cross): ");
var side = Screen.GetInputEnum(RFQSide.Buy);
Screen.Info("Enter SecurityId (default=" + Constants.DefaultSecurityId + "): ");
var securityId = Screen.GetInput(Constants.DefaultSecurityId);
var msg = messageFactory.GetRequestForQuote(qti, side, securityId);
Send(msg);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private CommandExecutionStatus OnExit()
{
return CommandExecutionStatus.TerminateExecution;
}
private CommandExecutionStatus ResetSequenceNumbers()
{
if (null != primarySession && primarySession.State == SessionState.Disconnected)
{
if(primarySession.Negotiated)
primarySession.Reset(false);
}
else
Screen.Info("Operation can be performed only when the sessions are disconnected");
if (failover && backupSession.State == SessionState.Disconnected && backupSession.Negotiated)
backupSession.Reset(false);
Screen.Info("Done");
return CommandExecutionStatus.ContinueExecution;
}
private void Send(IMessage msg)
{
if (!CheckSessionConnected())
return;
if (failover)
{
if (FaultToleranceIndicator.Primary == primarySession.FaultToleranceIndicator)
primarySession.Send(msg);
else if (FaultToleranceIndicator.Primary == backupSession.FaultToleranceIndicator)
backupSession.Send(msg);
else
Screen.Info("Cannot send an iLink3 message because there is not a session with the primary role.");
}
else
primarySession.Send(msg);
}
private static Order NewOrder()
{
Order order = new();
Screen.Info("Enter SecurityId (default=" + Constants.DefaultSecurityId + "): ");
order.SecurityId = Screen.GetInput(Constants.DefaultSecurityId);
Screen.Info(
"Enter OrderType(40) (1-Market order, 2 - Limit order (default), 3 - Stop order, 4 - Stop - Limit order, K - Market - Limit order): ");
order.OrdType = Screen.GetInputEnumChar(OrderTypeReq.Limit);
if (order.OrdType == OrderTypeReq.Limit || order.OrdType == OrderTypeReq.StopLimit)
{
Screen.Info("Enter Price mantissa (default = " + Constants.DefaultPriceMantissa + "): ");
order.Price = Screen.GetInput(Constants.DefaultPriceMantissa) * Constants.DefaultPriceExponent;
}
if (order.OrdType == OrderTypeReq.StopLimit || order.OrdType == OrderTypeReq.StopWithProtection)
{
Screen.Info("Enter StopPx: ");
order.StopPx = Screen.GetInput<Decimal>();
}
Screen.Info("Enter OrderQuantity (default = 100): ");
order.OrderQty = Screen.GetInput(100u);
Screen.Info("Enter MinQty or skip");
order.MinQty = Screen.GetInput(Order.UInt32NullValue);
Screen.Info("Enter DisplayQty or skip");
order.DisplayQty = Screen.GetInput(Order.UInt32NullValue);
Screen.Info(
"Enter TimeInForce (0 - Day (default), 1 - Good Till Cancel, 3 - Fill and Kill, 6 - Good Till Date): ");
order.TimeInForce = Screen.GetInputEnum(TimeInForce.Day);
if (order.TimeInForce == TimeInForce.GoodTillDate)
{
Screen.Info("Enter Expire date YYYYMMDD: ");
order.ExpireDate = Screen.GetInput<ushort>();
}
Screen.Info("Enter Side (1 - Buy (default), 2 - Sell): ");
order.Side = Screen.GetInputEnum(Side.Buy);
return order;
}
private Order FindByOrderId(IMessage report)
{
var orderId = report.GetString(Tag.ClOrdID);
var order = orderBook.Find(orderId);
if (!order.HasValue)
{
throw new Exception(
$"Cannot process received Execution Report." +
$" No order was sent with ClOrdID (tag = 11) equal to '{orderId}' in the current session.");
}
return order.Value;
}
private static void SetCommonFields(ref Order order, IMessage message)
{
order.OrderId = message.GetUnsignedLong(Tag.OrderID);
order.OrderQty = message.GetUnsignedInteger(Tag.OrderQty);
order.SecurityId = message.GetInteger(Tag.SecurityID);
order.TransactTime = message.GetUnsignedLong(Tag.TransactTime);
if (message.Contains(Tag.ExpireDate))
{
order.ExpireDate = message.GetUnsignedShort(Tag.ExpireDate);
}
if (message.Contains(Tag.TimeInForce))
{
order.TimeInForce = message.GetEnum<TimeInForce>(Tag.TimeInForce);
}
}
private bool connected = false;
private MessageFactory messageFactory;
private readonly Book orderBook = new();
private readonly bool failover = false;
private readonly Menu menu = new();
private Session backupSession;
private Session primarySession;
}
}