Skip to content

Commit

Permalink
Message Queue and basic matching
Browse files Browse the repository at this point in the history
  • Loading branch information
leboeuf committed Apr 23, 2016
1 parent 1dea6b2 commit 7d34e88
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 12 deletions.
6 changes: 4 additions & 2 deletions OrderBook.Core/Model/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ namespace OrderBook.Core.Model
{
public class Order
{
public DateTime Timestamp { get; set; }
public string Symbol { get; set; }
public OrderSide OrderSide { get; set; }
public int Quantity { get; set; }
public int Price { get; set; } // scaled by 10000 (i.e. $34.25 => 342500)
public DateTime Timestamp { get; set; }
public Guid Reference { get; set; } // Used to uniquely identify an order, e.g. to notify when an order is executed

public Order(OrderSide orderSide, int quantity, int price)
public Order(string symbol, OrderSide orderSide, int quantity, int price)
{
Symbol = symbol;
OrderSide = orderSide;
Quantity = quantity;
Price = price;
Expand Down
5 changes: 4 additions & 1 deletion OrderBook.Core/Model/OrderBook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ public class OrderBook
{
public List<Order> Orders { get; set; }


public OrderBook()
{
Orders = new List<Order>();
}
}
}
30 changes: 30 additions & 0 deletions OrderBook.Core/Model/ProcessedOrder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using OrderBook.Core.Model.Enums;

namespace OrderBook.Core.Model
{
public class ProcessedOrder
{
public string Symbol { get; set; }
public OrderSide OrderSide { get; set; }
public int Quantity { get; set; }
public int Price { get; set; }
public DateTime TimestampReceived { get; set; }
public DateTime TimestampExecuted { get; set; }
public Guid Reference { get; set; }

public static ProcessedOrder CreateFromOrder(Order order)
{
return new ProcessedOrder
{
Symbol = order.Symbol,
OrderSide = order.OrderSide,
Price = order.Price,
Quantity = order.Quantity,
Reference = order.Reference,
TimestampReceived = order.Timestamp,
TimestampExecuted = DateTime.UtcNow
};
}
}
}
1 change: 1 addition & 0 deletions OrderBook.Core/OrderBook.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<Compile Include="Model\Enums\OrderSide.cs" />
<Compile Include="Model\Order.cs" />
<Compile Include="Model\OrderBook.cs" />
<Compile Include="Model\ProcessedOrder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Expand Down
1 change: 1 addition & 0 deletions OrderBook.Server/OrderBook.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Messaging" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
Expand Down
102 changes: 94 additions & 8 deletions OrderBook.Server/Program.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,106 @@
using OrderBook.Core.Model;
using OrderBook.Core.Model.Enums;
using System.Messaging;
using System.Collections.Generic;
using System.Linq;
using System;

namespace OrderBook.Server
{
class Program
{
private const string MESSAGE_QUEUE_RX_NAME = @".\Private$\OrderBookServer_OrdersQueue";
private const string MESSAGE_QUEUE_TX_NAME = @".\Private$\OrderBookServer_ProcessedOrdersQueue";
private static MessageQueue _rxMessageQueue;
private static MessageQueue _txMessageQueue;

private static readonly IReadOnlyList<string> AvailableSymbols = new List<string> { "TEST", "TEST1", "TEST2" };
private static Dictionary<string, Core.Model.OrderBook> _orderBooks;

static void Main(string[] args)
{
var orderBookForSomeStock = new Core.Model.OrderBook();
orderBookForSomeStock.Orders.Add(new Order(OrderSide.Sell, 100, 32));

// wait for orders
// try matching
// put in order book if not matched
// delete from orders if matched and broadcast the trade to everyone
// at the end of trading day, close orders depending on "good til x"
InitializeOrderBooks();
InitializeMessageQueues();

while (true)
{
// TODO:
// if (current time is in market closed time)
// Cleanup "good til x" orders: refer to the Duration section in http://apps.tmx.com/en/trading/order_types/
// block on WaitForMarketOpen()

var message = _rxMessageQueue.Receive();
ProcessOrder((Order)message.Body);
}
}

private static void InitializeOrderBooks()
{
_orderBooks = new Dictionary<string, Core.Model.OrderBook>();

// TODO: load orderbooks from disk

foreach (var symbol in AvailableSymbols)
{
_orderBooks.Add(symbol, new Core.Model.OrderBook());
}
}

private static void InitializeMessageQueues()
{
CreateOrOpenQueue(ref _rxMessageQueue, MESSAGE_QUEUE_RX_NAME);
CreateOrOpenQueue(ref _txMessageQueue, MESSAGE_QUEUE_TX_NAME);
}

private static void CreateOrOpenQueue(ref MessageQueue messageQueue, string queueName)
{
if (MessageQueue.Exists(queueName))
{
messageQueue = new MessageQueue(queueName);
}
else
{
messageQueue = MessageQueue.Create(queueName);
}

messageQueue.Formatter = new BinaryMessageFormatter();
}

private static void ProcessOrder(Order order)
{
// Update timestamp to server time
order.Timestamp = DateTime.UtcNow;

// Try to math the order
var orderBook = _orderBooks[order.Symbol];
var matches = orderBook.Orders
.Where(bookOrder => bookOrder.OrderSide != order.OrderSide
&& (order.OrderSide == OrderSide.Buy ? bookOrder.Price <= order.Price : bookOrder.Price >= order.Price)
&& bookOrder.Quantity == order.Quantity) // TODO: temporary (remove when handle partial fills)
.OrderBy(bookOrder => bookOrder.Price)
.ThenBy(bookOrder => bookOrder.Timestamp)
.ToList();

// TODO: handle partial fills (execute some trades and put the rest of the order in the queue)

if (matches.Any())
{
// Delete from orders if matched and broadcast the trade to everyone
var match = matches.First();
orderBook.Orders.Remove(match);
BroadcastProcessedOrder(ProcessedOrder.CreateFromOrder(match));
BroadcastProcessedOrder(ProcessedOrder.CreateFromOrder(order));
}
else
{
// Put in order book if not matched
orderBook.Orders.Add(order);
}
}

private static void BroadcastProcessedOrder(ProcessedOrder order)
{
_txMessageQueue.Send(order);
}
}
}
13 changes: 12 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# OrderBook

A C# price-time order book matching engine.
A C# price-time order book matching engine.

## Prerequisites

OrderBook.Server and OrderBook.ServerEndpoint projects make use of *Microsoft Message Que (MSMQ) Server*. To enable this Windows feature, run `OptionalFeatures` at a command prompt and look for MSQM.

## Architecture

OrderBook.Server contains the OrderBook (the list of orders). Clients connect to OrderBook.ServerEndpoint, which transmits orders to the OrderBook.Server and broadcast notifications to clients. Clients never connect directly to OrderBook.Server for security and load concerns.

The OrderBook.Server queue expects to recieve messages of type `Order` and sends messages of type `ProcessedOrder`.

0 comments on commit 7d34e88

Please sign in to comment.