├── OrderMatchingEngine.sln ├── OrderMatchingEngine.suo ├── OrderMatchingEngine ├── Exchange │ ├── Exchange.cs │ └── Market.cs ├── OrderBook │ ├── EquityOrder.cs │ ├── Instrument.cs │ ├── Order.cs │ ├── OrderBook.cs │ ├── OrderProcessing │ │ ├── DedicatedThreadsOrderProcessor.cs │ │ ├── OrderProcessor.cs │ │ ├── SynchronousOrderProcessor.cs │ │ └── ThreadPooledOrderProcessor.cs │ ├── Orders.cs │ ├── Stats │ │ ├── Statistic.cs │ │ └── Statistics.cs │ ├── Trade.cs │ └── Trades.cs ├── OrderMatchingEngine.csproj ├── OrderMatchingEngineTests │ └── UnitTests │ │ └── TradesTests.cs └── Properties │ └── AssemblyInfo.cs ├── OrderMatchingEngineTests ├── Nunit │ └── NUnit-2.5.10.11092 │ │ └── bin │ │ └── net-2.0 │ │ └── nunit.framework.dll ├── OrderMatchingEngineTests.csproj ├── Properties │ └── AssemblyInfo.cs └── UnitTests │ ├── BuyOrdersTests.cs │ ├── MarketTests.cs │ ├── OrderBookTests.cs │ ├── OrderMatchingTests.cs │ ├── OrderProcessorTests.cs │ ├── SellOrdersTests.cs │ └── TradesTests.cs └── README /OrderMatchingEngine.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderMatchingEngine", "OrderMatchingEngine\OrderMatchingEngine.csproj", "{8675A129-DCDB-4340-A9C3-534017B3D207}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderMatchingEngineTests", "OrderMatchingEngineTests\OrderMatchingEngineTests.csproj", "{E68060A2-24C7-475C-B8A0-1415C1244E53}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {8675A129-DCDB-4340-A9C3-534017B3D207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {8675A129-DCDB-4340-A9C3-534017B3D207}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {8675A129-DCDB-4340-A9C3-534017B3D207}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {8675A129-DCDB-4340-A9C3-534017B3D207}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {E68060A2-24C7-475C-B8A0-1415C1244E53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {E68060A2-24C7-475C-B8A0-1415C1244E53}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {E68060A2-24C7-475C-B8A0-1415C1244E53}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {E68060A2-24C7-475C-B8A0-1415C1244E53}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /OrderMatchingEngine.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eneiand/OrderMatchingEngine/ded44cc6c95c5d77e70f7b137a46aaaf1e7b4667/OrderMatchingEngine.suo -------------------------------------------------------------------------------- /OrderMatchingEngine/Exchange/Exchange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OrderMatchingEngine.Exchange 5 | { 6 | internal class Exchange 7 | { 8 | private readonly Dictionary m_Markets; 9 | 10 | public Exchange(IDictionary markets) 11 | { 12 | if (markets == null) throw new ArgumentNullException("markets"); 13 | 14 | m_Markets = new Dictionary(markets); 15 | } 16 | 17 | public Market this[Market.MarketName marketName] 18 | { 19 | get 20 | { 21 | if (marketName == null) throw new ArgumentNullException("marketName"); 22 | 23 | Market market; 24 | 25 | if (m_Markets.TryGetValue(marketName, out market)) 26 | return market; 27 | else 28 | throw new MarketNotOnThisExchangeException(); 29 | } 30 | } 31 | 32 | public class MarketNotOnThisExchangeException : Exception 33 | { 34 | public MarketNotOnThisExchangeException() 35 | : base("market is not on this exchange") 36 | { 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /OrderMatchingEngine/Exchange/Market.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Timers; 4 | using OrderMatchingEngine.OrderBook; 5 | using OrderMatchingEngine.OrderBook.OrderProcessing; 6 | using OrderMatchingEngine.OrderBook.Stats; 7 | 8 | namespace OrderMatchingEngine.Exchange 9 | { 10 | public class Market 11 | { 12 | private readonly Dictionary m_OrderBooks; 13 | private readonly List m_Timers; 14 | private bool m_TimersStarted; 15 | private readonly Object m_Locker = new Object(); 16 | 17 | public Market(IDictionary orderBooks, IEnumerable timers) 18 | { 19 | if (orderBooks == null) throw new ArgumentNullException("orderBooks"); 20 | 21 | m_OrderBooks = new Dictionary(orderBooks); 22 | m_Timers = new List(timers); 23 | m_Timers.ForEach(t => t.Stop()); 24 | } 25 | 26 | public Market(IDictionary orderBooks) 27 | { 28 | if (orderBooks == null) throw new ArgumentNullException("orderBooks"); 29 | 30 | m_OrderBooks = new Dictionary(orderBooks); 31 | m_Timers = new List {OrdersPerSecondPriority()}; 32 | } 33 | 34 | private Timer OrdersPerSecondPriority() 35 | { 36 | var numOrdersPerSecondMonitor = new Timer {AutoReset = true, Enabled = false, Interval = 1000}; 37 | numOrdersPerSecondMonitor.Elapsed += delegate 38 | { 39 | var ob = new List(); 40 | foreach (var orderBook in m_OrderBooks) 41 | { 42 | ob.Add(orderBook.Value); 43 | } 44 | 45 | PrioritiseOrderBooks(ob, 46 | (x, y) => 47 | -1* 48 | x.Statistics[Statistics.Stat.NumOrders]. 49 | Value.CompareTo( 50 | y.Statistics[ 51 | Statistics.Stat.NumOrders]. 52 | Value)); 53 | ResetStats(ob); 54 | }; 55 | return numOrdersPerSecondMonitor; 56 | } 57 | 58 | private static void ResetStats(List ob) 59 | { 60 | ob.ForEach(o => o.Statistics[Statistics.Stat.NumOrders].Reset()); 61 | } 62 | 63 | public static void PrioritiseOrderBooks(List orderBooks, 64 | Comparison orderBookComparer, //reads as "sort the OrderBooks by orderBookComparer 65 | int dedicatedThreadsPercentage = 10, // the top 10% of the sorted OrderBooks should have dedicated threads, 66 | int threadPooledPercentage = 20) // the next 20% of the OrderBooks use the ThreadPool 67 | // and the remaining 70% will be synchronouss" 68 | { 69 | if (orderBooks == null) throw new ArgumentNullException("orderBooks"); 70 | if (orderBookComparer == null) throw new ArgumentNullException("orderBookComparer"); 71 | 72 | orderBooks.Sort(orderBookComparer); 73 | 74 | for (int i = 0; i < orderBooks.Count; ++i) 75 | { 76 | decimal percentageOfBooks = ((i + 1) / (decimal)orderBooks.Count) * 100m; 77 | OrderBook.OrderBook oBook = orderBooks[i]; 78 | int limitForThreadPooled = dedicatedThreadsPercentage + threadPooledPercentage; 79 | 80 | if (percentageOfBooks <= dedicatedThreadsPercentage && !(oBook.OrderProcessingStrategy is DedicatedThreadsOrderProcessor)) 81 | oBook.OrderProcessingStrategy = new DedicatedThreadsOrderProcessor(oBook.BuyOrders, oBook.SellOrders, 82 | oBook.Trades); 83 | else if (percentageOfBooks <= limitForThreadPooled && !(oBook.OrderProcessingStrategy is ThreadPooledOrderProcessor)) 84 | oBook.OrderProcessingStrategy = new ThreadPooledOrderProcessor(oBook.BuyOrders, oBook.SellOrders, 85 | oBook.Trades); 86 | else if (!(oBook.OrderProcessingStrategy is SynchronousOrderProcessor)) 87 | oBook.OrderProcessingStrategy = new SynchronousOrderProcessor(oBook.BuyOrders, oBook.SellOrders, 88 | oBook.Trades); 89 | } 90 | } 91 | 92 | public OrderBook.OrderBook this[Instrument instrument] 93 | { 94 | get 95 | { 96 | if (instrument == null) throw new ArgumentNullException("instrument"); 97 | 98 | OrderBook.OrderBook orderBook; 99 | 100 | if (m_OrderBooks.TryGetValue(instrument, out orderBook)) 101 | return orderBook; 102 | else 103 | throw new InstrumentNotInThisMarketException(); 104 | } 105 | } 106 | 107 | public void SubmitOrder(Order order) 108 | { 109 | if (order == null) throw new ArgumentNullException("order"); 110 | 111 | OrderBook.OrderBook orderBook = this[order.Instrument]; 112 | StartTimersOnFirstOrder(); 113 | orderBook.InsertOrder(order); 114 | } 115 | 116 | private void StartTimersOnFirstOrder() 117 | { 118 | lock (m_Locker) 119 | { 120 | if (!m_TimersStarted) 121 | { 122 | m_TimersStarted = true; 123 | m_Timers.ForEach(t => t.Start()); 124 | } 125 | } 126 | } 127 | 128 | public class MarketName 129 | { 130 | public MarketName(String name) 131 | { 132 | if (String.IsNullOrEmpty(name.Trim())) throw new ArgumentNullException("name"); 133 | 134 | Name = name; 135 | } 136 | 137 | public String Name { get; private set; } 138 | } 139 | 140 | public class InstrumentNotInThisMarketException : Exception 141 | { 142 | public InstrumentNotInThisMarketException() 143 | : base("instrument does not have an orderbook in this market") 144 | { 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/EquityOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OrderMatchingEngine.OrderBook 4 | { 5 | public class EquityOrder : Order 6 | { 7 | public EquityOrder(Instrument instrument, OrderTypes orderType, BuyOrSell buySell, Decimal price, 8 | UInt64 quantity) 9 | : base(instrument, orderType, buySell, price, quantity) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/Instrument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OrderMatchingEngine.OrderBook 4 | { 5 | [Serializable] 6 | public class Instrument 7 | { 8 | public String Symbol { get; private set; } 9 | 10 | public Instrument(String symbol) 11 | { 12 | if (String.IsNullOrEmpty(symbol)) throw new ArgumentNullException("symbol"); 13 | 14 | Symbol = symbol; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace OrderMatchingEngine.OrderBook 5 | { 6 | public abstract class Order 7 | { 8 | public enum BuyOrSell 9 | { 10 | Buy, 11 | Sell 12 | } 13 | 14 | public enum OrderTypes 15 | { 16 | GoodUntilCancelled, 17 | GoodUntilDate, 18 | ImmediateOrCancel, 19 | LimitPrice, 20 | MarketPrice, 21 | StopLoss 22 | } 23 | 24 | private static Int64 GlobalOrderId; 25 | private UInt64 m_Quantity; 26 | private readonly Object m_Locker = new object(); 27 | 28 | protected Order() 29 | { 30 | Id = Interlocked.Increment(ref GlobalOrderId); 31 | CreationTime = DateTime.Now; 32 | } 33 | 34 | protected Order(Instrument instrument, OrderTypes orderType, BuyOrSell buySell, Decimal price, UInt64 quantity) 35 | : this() 36 | { 37 | if (instrument == null) throw new ArgumentNullException("instrument"); 38 | if (quantity <= 0) throw new ArgumentException("order cannot be created with quantity less than or equal to 0", "quantity"); 39 | if (price <= 0) throw new ArgumentException("price cannot be less than or equal to 0", "price"); 40 | 41 | Instrument = instrument; 42 | OrderType = orderType; 43 | BuySell = buySell; 44 | Price = price; 45 | Quantity = quantity; 46 | } 47 | 48 | public BuyOrSell BuySell { get; private set; } 49 | public OrderTypes OrderType { get; private set; } 50 | public Decimal Price { get; private set; } 51 | 52 | public UInt64 Quantity 53 | { 54 | get { lock (m_Locker) return m_Quantity; } 55 | set { lock (m_Locker) m_Quantity = value; } 56 | } 57 | 58 | public Instrument Instrument { get; private set; } 59 | public DateTime CreationTime { get; private set; } 60 | public Int64 Id { get; private set; } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/OrderBook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using OrderMatchingEngine.Exchange; 4 | using OrderMatchingEngine.OrderBook.OrderProcessing; 5 | using OrderMatchingEngine.OrderBook.Stats; 6 | 7 | namespace OrderMatchingEngine.OrderBook 8 | { 9 | public class OrderBook 10 | { 11 | private OrderProcessor m_OrderProcessingStrategy; 12 | 13 | private readonly Object m_Locker = new Object(); 14 | 15 | public Instrument Instrument { get; private set; } 16 | public BuyOrders BuyOrders { get; private set; } 17 | public SellOrders SellOrders { get; private set; } 18 | public Trades Trades { get; private set; } 19 | public Statistics Statistics { get; private set; } 20 | 21 | public OrderProcessor OrderProcessingStrategy 22 | { 23 | get { return m_OrderProcessingStrategy; } 24 | set 25 | { 26 | lock (m_Locker) 27 | { 28 | var dedicatedThreadOrderProcessor = m_OrderProcessingStrategy as DedicatedThreadsOrderProcessor; 29 | 30 | if (dedicatedThreadOrderProcessor != null) 31 | dedicatedThreadOrderProcessor.Stop(); 32 | 33 | m_OrderProcessingStrategy = value; 34 | } 35 | } 36 | } 37 | 38 | public OrderBook(Instrument instrument, BuyOrders buyOrders, SellOrders sellOrders, Trades trades, 39 | OrderProcessor orderProcessingStrategy) 40 | { 41 | if (instrument == null) throw new ArgumentNullException("instrument"); 42 | if (buyOrders == null) throw new ArgumentNullException("buyOrders"); 43 | if (sellOrders == null) throw new ArgumentNullException("sellOrders"); 44 | if (trades == null) throw new ArgumentNullException("trades"); 45 | if (orderProcessingStrategy == null) throw new ArgumentNullException("orderProcessingStrategy"); 46 | if (!(instrument == buyOrders.Instrument && instrument == sellOrders.Instrument)) 47 | throw new ArgumentException("instrument does not match buyOrders and sellOrders instrument"); 48 | 49 | Instrument = instrument; 50 | BuyOrders = buyOrders; 51 | SellOrders = sellOrders; 52 | Trades = trades; 53 | OrderProcessingStrategy = orderProcessingStrategy; 54 | Statistics = new Statistics(); 55 | } 56 | 57 | public OrderBook(Instrument instrument) 58 | : this(instrument, new BuyOrders(instrument), new SellOrders(instrument), new Trades(instrument)) 59 | { 60 | } 61 | 62 | public OrderBook(Instrument instrument, BuyOrders buyOrders, SellOrders sellOrders, Trades trades) 63 | : this( 64 | instrument, buyOrders, sellOrders, trades, new SynchronousOrderProcessor(buyOrders, sellOrders, trades)) 65 | { 66 | } 67 | 68 | public void InsertOrder(Order order) 69 | { 70 | if (order == null) throw new ArgumentNullException("order"); 71 | if (order.Instrument != Instrument) 72 | throw new OrderIsNotForThisBookException(); 73 | 74 | OrderReceived(); 75 | 76 | //the strategy can change at runtime so lock here and in OrderProcessingStrategy property 77 | lock (m_Locker) 78 | OrderProcessingStrategy.InsertOrder(order); 79 | } 80 | 81 | private void OrderReceived() 82 | { 83 | var numOrders = Statistics[Statistics.Stat.NumOrders]; 84 | numOrders++; 85 | } 86 | 87 | public class OrderIsNotForThisBookException : Exception 88 | { 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/OrderProcessing/DedicatedThreadsOrderProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Threading; 3 | 4 | namespace OrderMatchingEngine.OrderBook.OrderProcessing 5 | { 6 | public class DedicatedThreadsOrderProcessor : OrderProcessor 7 | { 8 | private readonly Thread m_Thread; 9 | private readonly BlockingCollection m_PendingOrders = new BlockingCollection(); 10 | 11 | public DedicatedThreadsOrderProcessor(BuyOrders buyOrders, SellOrders sellOrders, Trades trades) 12 | : base(buyOrders, sellOrders, trades) 13 | { 14 | m_Thread = new Thread(ProcessOrders); 15 | m_Thread.Start(); 16 | } 17 | 18 | 19 | private void ProcessOrders() 20 | { 21 | foreach (Order order in m_PendingOrders.GetConsumingEnumerable()) 22 | ProcessOrder(order); 23 | } 24 | 25 | public void Stop() 26 | { 27 | m_PendingOrders.CompleteAdding(); 28 | } 29 | 30 | public override void InsertOrder(Order order) 31 | { 32 | m_PendingOrders.Add(order); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/OrderProcessing/OrderProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OrderMatchingEngine.OrderBook.OrderProcessing 5 | { 6 | public abstract class OrderProcessor 7 | { 8 | public delegate bool OrderMatcher(Order order, Orders orders, Trades trades); 9 | 10 | public static bool TryMatchOrder(Order order, Orders orders, Trades trades) 11 | { 12 | List candidateOrders = order.BuySell == Order.BuyOrSell.Buy 13 | ? new List(orders.FindAll(o => o.Price <= order.Price)) 14 | : new List(orders.FindAll(o => o.Price >= order.Price)); 15 | if (candidateOrders.Count == 0) 16 | return false; 17 | 18 | foreach (Order candidateOrder in candidateOrders) 19 | { 20 | //once an order has been filled completely our job is done 21 | if (order.Quantity == 0) 22 | break; 23 | 24 | ulong quantity = (candidateOrder.Quantity >= order.Quantity 25 | ? order.Quantity 26 | : candidateOrder.Quantity); 27 | 28 | candidateOrder.Quantity -= quantity; 29 | order.Quantity -= quantity; 30 | 31 | if (candidateOrder.Quantity == 0) 32 | orders.Remove(candidateOrder); 33 | 34 | trades.AddTrade(new Trade(order.Instrument, quantity, candidateOrder.Price)); 35 | } 36 | return true; 37 | } 38 | 39 | protected BuyOrders m_BuyOrders; 40 | protected SellOrders m_SellOrders; 41 | protected Trades m_Trades; 42 | 43 | public OrderMatcher TryMatchBuyOrder { get; set; } 44 | public OrderMatcher TryMatchSellOrder { get; set; } 45 | 46 | 47 | public OrderProcessor(BuyOrders buyOrders, SellOrders sellOrders, Trades trades, 48 | OrderMatcher tryMatchBuyOrder, OrderMatcher tryMatchSellOrder) 49 | { 50 | m_BuyOrders = buyOrders; 51 | m_SellOrders = sellOrders; 52 | m_Trades = trades; 53 | TryMatchBuyOrder = tryMatchBuyOrder; 54 | TryMatchSellOrder = tryMatchSellOrder; 55 | } 56 | 57 | public OrderProcessor(BuyOrders buyOrders, SellOrders sellOrders, Trades trades) 58 | : this(buyOrders, sellOrders, trades, 59 | TryMatchOrder, 60 | TryMatchOrder) 61 | { 62 | } 63 | 64 | 65 | public abstract void InsertOrder(Order order); 66 | 67 | protected void ProcessOrder(Order order) 68 | { 69 | switch (order.BuySell) 70 | { 71 | case Order.BuyOrSell.Buy: 72 | TryMatchBuyOrder(order, m_SellOrders, m_Trades); 73 | if (order.Quantity > 0) 74 | m_BuyOrders.Insert(order); 75 | break; 76 | case Order.BuyOrSell.Sell: 77 | TryMatchSellOrder(order, m_BuyOrders, m_Trades); 78 | if (order.Quantity > 0) 79 | m_SellOrders.Insert(order); 80 | break; 81 | default: 82 | throw new ArgumentOutOfRangeException(); 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/OrderProcessing/SynchronousOrderProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace OrderMatchingEngine.OrderBook.OrderProcessing 2 | { 3 | public class SynchronousOrderProcessor : OrderProcessor 4 | { 5 | public SynchronousOrderProcessor(BuyOrders buyOrders, SellOrders sellOrders, Trades trades) 6 | : base(buyOrders, sellOrders, trades) 7 | { 8 | } 9 | 10 | public override void InsertOrder(Order order) 11 | { 12 | ProcessOrder(order); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/OrderProcessing/ThreadPooledOrderProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace OrderMatchingEngine.OrderBook.OrderProcessing 4 | { 5 | public class ThreadPooledOrderProcessor : OrderProcessor 6 | { 7 | public ThreadPooledOrderProcessor(BuyOrders buyOrders, SellOrders sellOrders, Trades trades) 8 | : base(buyOrders, sellOrders, trades) 9 | { 10 | } 11 | 12 | public override void InsertOrder(Order order) 13 | { 14 | ThreadPool.QueueUserWorkItem((o) => ProcessOrder(order)); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/Orders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace OrderMatchingEngine.OrderBook 6 | { 7 | public abstract class Orders : IEnumerable 8 | { 9 | public Instrument Instrument { get; private set; } 10 | 11 | private readonly List m_Orders = new List(); 12 | private readonly Object m_Locker = new object(); 13 | private readonly Comparison m_OrderSorter; 14 | 15 | protected Orders(Instrument instrument, Comparison orderSorter) 16 | { 17 | if (instrument == null) throw new ArgumentNullException("instrument"); 18 | if (orderSorter == null) throw new ArgumentNullException("orderSorter"); 19 | 20 | Instrument = instrument; 21 | m_OrderSorter = orderSorter; 22 | } 23 | 24 | public void Insert(Order order) 25 | { 26 | if (order == null) throw new ArgumentNullException("order"); 27 | if (!OrderIsForThisList(order)) 28 | throw new ArgumentException("order is not valid for this Orders instance", "order"); 29 | 30 | 31 | lock (m_Locker) 32 | { 33 | m_Orders.Add(order); 34 | m_Orders.Sort(m_OrderSorter); 35 | } 36 | } 37 | 38 | public void Remove(Order order) 39 | { 40 | if (order == null) throw new ArgumentNullException("order"); 41 | if (!OrderIsForThisList(order)) 42 | throw new ArgumentException("order is not valid for this Orders instance", "order"); 43 | 44 | lock (m_Locker) 45 | m_Orders.Remove(order); 46 | } 47 | 48 | private bool OrderIsForThisList(Order order) 49 | { 50 | return order.Instrument == Instrument && OrderIsCorrectType(order); 51 | } 52 | 53 | protected abstract bool OrderIsCorrectType(Order order); 54 | 55 | protected static int EarliestTimeFirst(Order x, Order y) 56 | { 57 | return x.CreationTime.CompareTo(y.CreationTime); 58 | } 59 | 60 | protected static int HighestPriceFirst(Order x, Order y) 61 | { 62 | return -1*x.Price.CompareTo(y.Price); 63 | } 64 | 65 | 66 | public IEnumerable FindAll(Predicate filter) 67 | { 68 | List foundOrders; 69 | lock (m_Locker) 70 | foundOrders = m_Orders.FindAll(filter); 71 | 72 | return foundOrders; 73 | } 74 | 75 | public IEnumerator GetEnumerator() 76 | { 77 | List ordersCopy; 78 | lock (m_Locker) 79 | { 80 | ordersCopy = new List(m_Orders); 81 | } 82 | return ordersCopy.GetEnumerator(); 83 | } 84 | 85 | IEnumerator IEnumerable.GetEnumerator() 86 | { 87 | return GetEnumerator(); 88 | } 89 | 90 | public Order this[int i] 91 | { 92 | get 93 | { 94 | lock (m_Locker) 95 | return m_Orders[i]; 96 | } 97 | } 98 | } 99 | 100 | public class BuyOrders : Orders 101 | { 102 | private static int HighestPriceEarliestTimePriority(Order x, Order y) 103 | { 104 | int priceComp = HighestPriceFirst(x, y); 105 | 106 | if (priceComp == 0) 107 | return EarliestTimeFirst(x, y); 108 | 109 | return priceComp; 110 | } 111 | 112 | 113 | public BuyOrders(Instrument instrument) : base(instrument, HighestPriceEarliestTimePriority) 114 | { 115 | } 116 | 117 | protected override bool OrderIsCorrectType(Order order) 118 | { 119 | return order.BuySell == Order.BuyOrSell.Buy; 120 | } 121 | } 122 | 123 | public class SellOrders : Orders 124 | { 125 | private static int LowestPriceEarliestTimePriority(Order x, Order y) 126 | { 127 | int priceComp = (-1*HighestPriceFirst(x, y)); 128 | 129 | if (priceComp == 0) 130 | return EarliestTimeFirst(x, y); 131 | 132 | return priceComp; 133 | } 134 | 135 | 136 | public SellOrders(Instrument instrument) : base(instrument, LowestPriceEarliestTimePriority) 137 | { 138 | } 139 | 140 | protected override bool OrderIsCorrectType(Order order) 141 | { 142 | return order.BuySell == Order.BuyOrSell.Sell; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/Stats/Statistic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace OrderMatchingEngine.OrderBook.Stats 5 | { 6 | public class Statistic 7 | { 8 | private long m_Value = 0; 9 | private readonly Object m_Locker = new Object(); 10 | 11 | public long Value { get { lock (m_Locker) return m_Value; }} 12 | 13 | public void Reset() 14 | { 15 | lock (m_Locker) 16 | m_Value = 0; 17 | } 18 | 19 | public static Statistic operator++(Statistic stat) 20 | { 21 | Interlocked.Increment(ref stat.m_Value); 22 | return stat; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/Stats/Statistics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OrderMatchingEngine.OrderBook.Stats 5 | { 6 | public class Statistics 7 | { 8 | public enum Stat 9 | { 10 | NumOrders 11 | } 12 | 13 | private readonly Dictionary m_Stats; 14 | 15 | public Statistics(IDictionary stats) 16 | { 17 | if (stats == null) throw new ArgumentNullException("stats"); 18 | m_Stats = new Dictionary(stats); 19 | } 20 | 21 | public Statistics() : this(new Dictionary(){{Stat.NumOrders, new Statistic()} }) 22 | {} 23 | 24 | public Statistic this[Stat stat] 25 | { 26 | get { return m_Stats[stat]; } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/Trade.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading; 4 | 5 | namespace OrderMatchingEngine.OrderBook 6 | { 7 | public class Trade 8 | { 9 | private static Int64 GlobalTradeId; 10 | 11 | public Trade(Instrument instrument, UInt64 quantity, Decimal price) 12 | : this() 13 | { 14 | if (instrument == null) throw new ArgumentNullException("instrument"); 15 | if(quantity <= 0) throw new ArgumentException("a trade cannot be created with a quantity cannot less than or equal to 0", "quantity"); 16 | if (price <= 0) throw new ArgumentException("a trade cannot be created with a price cannot less than or equal to 0", "price"); 17 | 18 | Instrument = instrument; 19 | Quantity = quantity; 20 | Price = price; 21 | } 22 | 23 | private Trade() 24 | { 25 | Id = Interlocked.Increment(ref GlobalTradeId); 26 | CreationTime = DateTime.Now; 27 | } 28 | 29 | public Instrument Instrument { get; private set; } 30 | public UInt64 Quantity { get; private set; } 31 | public Decimal Price { get; private set; } 32 | public Int64 Id { get; private set; } 33 | public DateTime CreationTime { get; private set; } 34 | 35 | public override string ToString() 36 | { 37 | var s = new StringBuilder(Instrument.Symbol); 38 | s.AppendFormat(" {0} {1} ", Quantity, Price); 39 | return s.ToString(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderBook/Trades.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace OrderMatchingEngine.OrderBook 6 | { 7 | public class Trades 8 | { 9 | public Instrument Instrument { get; private set; } 10 | public TradeProcessor TradeProcessingStrategy { get; set; } 11 | private readonly Object m_Locker = new Object(); 12 | 13 | public Trades(Instrument instrument, TradeProcessor tradeProcessingStrategy) 14 | { 15 | Instrument = instrument; 16 | TradeProcessingStrategy = tradeProcessingStrategy; 17 | } 18 | 19 | public Trades(Instrument instrument) 20 | : this(instrument, new InMemoryTradeProcessor()) 21 | { 22 | } 23 | 24 | public void AddTrade(Trade trade) 25 | { 26 | if (trade == null) throw new ArgumentNullException("trade"); 27 | 28 | if (trade.Instrument != Instrument) 29 | throw new TradeIsNotForThisInstrumentException(); 30 | 31 | lock (m_Locker) 32 | TradeProcessingStrategy.Add(trade); 33 | } 34 | 35 | public class TradeIsNotForThisInstrumentException : Exception 36 | { 37 | } 38 | 39 | public abstract class TradeProcessor 40 | { 41 | public abstract void Add(Trade trade); 42 | } 43 | 44 | public class StreamWritingTradeProcessor : TradeProcessor 45 | { 46 | public StreamWriter Writer { get; private set; } 47 | 48 | public StreamWritingTradeProcessor(StreamWriter writer) 49 | { 50 | Writer = writer; 51 | } 52 | 53 | public override void Add(Trade trade) 54 | { 55 | Writer.Write(trade.ToString()); 56 | } 57 | } 58 | 59 | public class InMemoryTradeProcessor : TradeProcessor 60 | { 61 | private readonly List m_Trades = new List(); 62 | private readonly Object m_Locker = new object(); 63 | 64 | public override void Add(Trade trade) 65 | { 66 | lock (m_Locker) 67 | m_Trades.Add(trade); 68 | } 69 | 70 | public List Trades 71 | { 72 | get 73 | { 74 | var tradesCopy = new List(m_Trades); 75 | return tradesCopy; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderMatchingEngine.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {8675A129-DCDB-4340-A9C3-534017B3D207} 9 | Library 10 | Properties 11 | OrderMatchingEngine 12 | OrderMatchingEngine 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 69 | -------------------------------------------------------------------------------- /OrderMatchingEngine/OrderMatchingEngineTests/UnitTests/TradesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using NUnit.Framework; 7 | using OrderMatchingEngine; 8 | using OrderMatchingEngine.OrderBook; 9 | 10 | namespace OrderMatchingEngineTests.UnitTests 11 | { 12 | [TestFixture] 13 | class TradesTests 14 | { 15 | private Trades m_Trades; 16 | private Instrument m_Instrument; 17 | 18 | [SetUp] 19 | public void Init() 20 | { 21 | m_Instrument = new Instrument("MSFT"); 22 | m_Trades = new Trades(m_Instrument); 23 | } 24 | 25 | [Test] 26 | public void AddTradeTest() 27 | { 28 | var trade = new Trade(m_Instrument, 100UL, 100.10M); 29 | m_Trades.AddTrade(trade); 30 | 31 | Assert.That(((Trades.InMemoryTradeProcessor)m_Trades.TradeProcessingStrategy).Trades[0], Is.EqualTo(trade)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /OrderMatchingEngine/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("OrderMatchingEngine")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("OrderMatchingEngine")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("bbd30782-07f7-47b4-8d79-d4dc8d3f0864")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/Nunit/NUnit-2.5.10.11092/bin/net-2.0/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eneiand/OrderMatchingEngine/ded44cc6c95c5d77e70f7b137a46aaaf1e7b4667/OrderMatchingEngineTests/Nunit/NUnit-2.5.10.11092/bin/net-2.0/nunit.framework.dll -------------------------------------------------------------------------------- /OrderMatchingEngineTests/OrderMatchingEngineTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {E68060A2-24C7-475C-B8A0-1415C1244E53} 9 | Library 10 | Properties 11 | OrderMatchingEngineTests 12 | OrderMatchingEngineTests 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | False 36 | Nunit\NUnit-2.5.10.11092\bin\net-2.0\nunit.framework.dll 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {8675A129-DCDB-4340-A9C3-534017B3D207} 59 | OrderMatchingEngine 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("OrderMatchingEngineTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("OrderMatchingEngineTests")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0ad32d3d-ea51-4f06-9c52-03362470c268")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/UnitTests/BuyOrdersTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using NUnit.Framework; 5 | using OrderMatchingEngine.OrderBook; 6 | 7 | namespace OrderMatchingEngineTests.UnitTests 8 | { 9 | [TestFixture] 10 | internal class BuyOrdersTests 11 | { 12 | private BuyOrders m_BuyOrders; 13 | private Instrument m_Instrument; 14 | 15 | [SetUp] 16 | public void Init() 17 | { 18 | m_Instrument = new Instrument("MSFT"); 19 | m_BuyOrders = new BuyOrders(m_Instrument); 20 | 21 | for (int i = 0, j = 10; i < 10; ++i, ++j) 22 | { 23 | Thread.Sleep(2); 24 | m_BuyOrders.Insert(new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, 25 | Order.BuyOrSell.Buy, 5, (ulong)j)); 26 | } 27 | 28 | for (int i = 0, j = 10; i < 10; ++i, ++j) 29 | { 30 | m_BuyOrders.Insert(new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, 31 | Order.BuyOrSell.Buy, 5, (ulong)j)); 32 | } 33 | } 34 | 35 | [Test] 36 | public void CorrectOrderPriorityTest() 37 | { 38 | var sortedOrders = new List(m_BuyOrders); 39 | 40 | for (int i = 0; i < sortedOrders.Count - 1; ++i) 41 | { 42 | Order thisOrder = sortedOrders[i]; 43 | Order nextOrder = sortedOrders[i + 1]; 44 | 45 | Assert.That(thisOrder.Price > nextOrder.Price || 46 | (thisOrder.Price == nextOrder.Price && (thisOrder.CreationTime <= nextOrder.CreationTime))); 47 | } 48 | } 49 | 50 | [Test] 51 | public void WrongOrderTypeThrowsException() 52 | { 53 | var order = new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Sell, 5, 10ul); 54 | 55 | Assert.Throws(() => m_BuyOrders.Insert(order)); 56 | } 57 | 58 | [Test] 59 | public void WrongInstrumentThrowsException() 60 | { 61 | var order = new EquityOrder(new Instrument("WRONG"), Order.OrderTypes.GoodUntilCancelled, 62 | Order.BuyOrSell.Buy, 5, 10ul); 63 | 64 | Assert.Throws(() => m_BuyOrders.Insert(order)); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/UnitTests/MarketTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using NUnit.Framework; 5 | using OrderMatchingEngine.Exchange; 6 | using OrderMatchingEngine.OrderBook; 7 | using OrderMatchingEngine.OrderBook.Stats; 8 | 9 | namespace OrderMatchingEngineTests.UnitTests 10 | { 11 | [TestFixture] 12 | internal class MarketTests 13 | { 14 | private Market m_Market; 15 | private Instrument m_Instrument; 16 | private OrderBook m_OrderBook; 17 | 18 | [SetUp] 19 | public void Setup() 20 | { 21 | m_Instrument = new Instrument("MSFT"); 22 | m_OrderBook = new OrderBook(m_Instrument); 23 | var orderBooks = new Dictionary(); 24 | orderBooks[m_Instrument] = m_OrderBook; 25 | m_Market = new Market(orderBooks); 26 | } 27 | 28 | [Test] 29 | public void SubmitBuyOrderTest() 30 | { 31 | var buyOrder = new EquityOrder(m_Instrument, Order.OrderTypes.LimitPrice, Order.BuyOrSell.Buy, 100, 100); 32 | m_Market.SubmitOrder(buyOrder); 33 | 34 | Assert.That(m_Market[m_Instrument].BuyOrders[0], Is.EqualTo(buyOrder)); 35 | } 36 | 37 | [Test] 38 | public void SubmitSellOrderTest() 39 | { 40 | var sellOrder = new EquityOrder(m_Instrument, Order.OrderTypes.LimitPrice, Order.BuyOrSell.Sell, 100, 100); 41 | m_Market.SubmitOrder(sellOrder); 42 | 43 | Assert.That(m_Market[m_Instrument].SellOrders[0], Is.EqualTo(sellOrder)); 44 | } 45 | 46 | [Test] 47 | public void TrySubmitAnOrderForTheWrongInstrument() 48 | { 49 | Assert.Throws( 50 | () => 51 | m_Market.SubmitOrder(new EquityOrder(new Instrument("XXXX"), Order.OrderTypes.GoodUntilCancelled, 52 | Order.BuyOrSell.Buy, 100, 100))); 53 | } 54 | 55 | [Test] 56 | public void SubmitMultipleOrdersConcurrently() 57 | { 58 | var orders = new List(Orders()); 59 | 60 | 61 | foreach (Order order in orders) 62 | { 63 | Order order1 = order; 64 | ThreadPool.QueueUserWorkItem((e) => m_Market.SubmitOrder(order1)); 65 | } 66 | 67 | while (m_OrderBook.SellOrders.Count() != 2 || m_OrderBook.BuyOrders.Count() != 2) ; 68 | 69 | Assert.That(orders[0], Is.EqualTo(m_OrderBook.BuyOrders[1])); 70 | Assert.That(orders[1], Is.EqualTo(m_OrderBook.BuyOrders[0])); 71 | Assert.That(orders[2], Is.EqualTo(m_OrderBook.SellOrders[0])); 72 | Assert.That(orders[3], Is.EqualTo(m_OrderBook.SellOrders[1])); 73 | } 74 | 75 | 76 | [Test] 77 | public void OrderBooksPrioritiserTest() 78 | { 79 | var orderBooks = new List(); 80 | 81 | for (int i = 0; i < 10; ++i) 82 | { 83 | var book = new OrderBook(new Instrument("" + i)); 84 | for (int j = 0; j < i; ++j) 85 | { 86 | Statistic stat = book.Statistics[Statistics.Stat.NumOrders]; 87 | ++stat; 88 | } 89 | orderBooks.Add(book); 90 | } 91 | 92 | Market.PrioritiseOrderBooks(orderBooks, 93 | (x, y) => -1*x.Statistics[Statistics.Stat.NumOrders].Value.CompareTo( 94 | y.Statistics[Statistics.Stat.NumOrders].Value)); 95 | 96 | for(int i = 1; i < orderBooks.Count; ++i) 97 | { 98 | Assert.That(orderBooks[i].Statistics[Statistics.Stat.NumOrders].Value, Is.LessThanOrEqualTo(orderBooks[i-1].Statistics[Statistics.Stat.NumOrders].Value)); 99 | } 100 | } 101 | 102 | private IEnumerable Orders() 103 | { 104 | for (int i = 1; i < 3; ++i) 105 | yield return 106 | new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Buy, i, 10ul); 107 | 108 | for (int i = 3; i < 5; ++i) 109 | yield return 110 | new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Sell, i, 10ul); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/UnitTests/OrderBookTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NUnit.Framework; 3 | using OrderMatchingEngine.OrderBook; 4 | using OrderMatchingEngine.OrderBook.OrderProcessing; 5 | 6 | namespace OrderMatchingEngineTests.UnitTests 7 | { 8 | [TestFixture] 9 | internal class OrderBookTests 10 | { 11 | private OrderBook m_OrderBook; 12 | private Instrument m_Instrument; 13 | private BuyOrders m_BuyOrders; 14 | private SellOrders m_SellOrders; 15 | private Trades m_Trades; 16 | private List m_Orders; 17 | 18 | [SetUp] 19 | public void Init() 20 | { 21 | m_Instrument = new Instrument("MSFT"); 22 | m_BuyOrders = new BuyOrders(m_Instrument); 23 | m_SellOrders = new SellOrders(m_Instrument); 24 | m_Trades = new Trades(m_Instrument); 25 | 26 | m_OrderBook = new OrderBook(m_Instrument, m_BuyOrders, m_SellOrders, m_Trades); 27 | 28 | m_Orders = new List 29 | { 30 | new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Buy, 100, 100), 31 | new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilDate, Order.BuyOrSell.Sell, 110, 100) 32 | }; 33 | } 34 | 35 | [Test] 36 | public void InsertOrderTest() 37 | { 38 | foreach (Order order in m_Orders) 39 | m_OrderBook.InsertOrder(order); 40 | 41 | Assert.That(m_OrderBook.BuyOrders[0], Is.EqualTo(m_Orders[0])); 42 | Assert.That(m_OrderBook.SellOrders[0], Is.EqualTo(m_Orders[1])); 43 | } 44 | 45 | [Test, Timeout(500)] 46 | public void InsertOrderPoolTest() 47 | { 48 | m_OrderBook.OrderProcessingStrategy = new ThreadPooledOrderProcessor(m_BuyOrders, m_SellOrders, 49 | m_Trades); 50 | 51 | foreach (Order order in m_Orders) 52 | m_OrderBook.InsertOrder(order); 53 | 54 | List buys; 55 | List sells; 56 | 57 | do 58 | { 59 | buys = new List(m_BuyOrders); 60 | sells = new List(m_SellOrders); 61 | } 62 | while (buys.Count == 0 || sells.Count == 0); //spin wait for thread pool 63 | 64 | Assert.That(m_OrderBook.BuyOrders[0], Is.EqualTo(m_Orders[0])); 65 | Assert.That(m_OrderBook.SellOrders[0], Is.EqualTo(m_Orders[1])); 66 | } 67 | 68 | [Test, Timeout(500)] 69 | public void InsertOrderDedicatedThreadTest() 70 | { 71 | m_OrderBook.OrderProcessingStrategy = new DedicatedThreadsOrderProcessor(m_BuyOrders, m_SellOrders, 72 | m_Trades); 73 | 74 | foreach (Order order in m_Orders) 75 | m_OrderBook.InsertOrder(order); 76 | 77 | List buys; 78 | List sells; 79 | 80 | do 81 | { 82 | buys = new List(m_BuyOrders); 83 | sells = new List(m_SellOrders); 84 | } 85 | while (buys.Count == 0 || sells.Count == 0); //spin wait for thread pool 86 | 87 | Assert.That(m_OrderBook.BuyOrders[0], Is.EqualTo(m_Orders[0])); 88 | Assert.That(m_OrderBook.SellOrders[0], Is.EqualTo(m_Orders[1])); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/UnitTests/OrderMatchingTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using NUnit.Framework; 3 | using OrderMatchingEngine.OrderBook; 4 | using OrderMatchingEngine.OrderBook.OrderProcessing; 5 | 6 | namespace OrderMatchingEngineTests.UnitTests 7 | { 8 | [TestFixture] 9 | internal class OrderMatchingTests 10 | { 11 | private EquityOrder m_BuyOrder, m_SellOrder; 12 | private Instrument m_Instrument; 13 | private SellOrders m_SellOrders; 14 | private BuyOrders m_BuyOrders; 15 | private Trades m_Trades; 16 | private Trades.InMemoryTradeProcessor m_TradeProcessor; 17 | 18 | [SetUp] 19 | public void Init() 20 | { 21 | m_Instrument = new Instrument("GOOG"); 22 | m_BuyOrder = new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Buy, 100M, 23 | 100ul); 24 | m_SellOrder = new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Sell, 90, 25 | 100ul); 26 | m_SellOrders = new SellOrders(m_Instrument); 27 | m_BuyOrders = new BuyOrders(m_Instrument); 28 | m_SellOrders.Insert(m_SellOrder); 29 | m_BuyOrders.Insert(m_BuyOrder); 30 | m_Trades = new Trades(m_Instrument); 31 | m_TradeProcessor = m_Trades.TradeProcessingStrategy as Trades.InMemoryTradeProcessor; 32 | } 33 | 34 | [Test] 35 | public void MatchBuyOrderTest() 36 | { 37 | ulong buyQuantity = m_BuyOrder.Quantity; 38 | 39 | Assert.True(OrderProcessor.TryMatchOrder(m_BuyOrder, m_SellOrders, m_Trades)); 40 | Trade trade = m_TradeProcessor.Trades[0]; 41 | 42 | Assert.That(trade.Instrument, Is.EqualTo(m_Instrument)); 43 | Assert.That(trade.Price, Is.EqualTo(m_SellOrder.Price)); 44 | Assert.That(trade.Quantity, Is.EqualTo(buyQuantity)); 45 | 46 | Assert.That(m_SellOrders.Count() == 0); 47 | } 48 | 49 | [Test] 50 | public void OneBuyLotsOfPotentialSellsOrderTest() 51 | { 52 | ulong buyQuantity = m_BuyOrder.Quantity; 53 | 54 | m_SellOrders.Insert(new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Sell, 95, 100)); 55 | m_SellOrders.Insert(new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Sell, 95, 100)); 56 | 57 | Assert.True(OrderProcessor.TryMatchOrder(m_BuyOrder, m_SellOrders, m_Trades)); 58 | Trade trade = m_TradeProcessor.Trades[0]; 59 | 60 | Assert.That(m_TradeProcessor.Trades.Count, Is.EqualTo(1)); 61 | 62 | Assert.That(trade.Instrument, Is.EqualTo(m_Instrument)); 63 | Assert.That(trade.Price, Is.EqualTo(m_SellOrder.Price)); 64 | Assert.That(trade.Quantity, Is.EqualTo(buyQuantity)); 65 | 66 | Assert.That(m_SellOrders.Count() == 2); 67 | Assert.That(!m_SellOrders.Contains(m_SellOrder)); 68 | } 69 | 70 | [Test] 71 | public void MatchSellOrderTest() 72 | { 73 | ulong sellQuantity = m_SellOrder.Quantity; 74 | 75 | Assert.True(OrderProcessor.TryMatchOrder(m_SellOrder, m_BuyOrders, m_Trades)); 76 | Trade trade = m_TradeProcessor.Trades[0]; 77 | 78 | Assert.That(trade.Instrument, Is.EqualTo(m_Instrument)); 79 | Assert.That(trade.Price, Is.EqualTo(m_BuyOrder.Price)); 80 | Assert.That(trade.Quantity, Is.EqualTo(sellQuantity)); 81 | 82 | Assert.That(m_BuyOrders.Count() == 0); 83 | } 84 | 85 | [Test] 86 | public void NoMatchTest() 87 | { 88 | Assert.False( 89 | OrderProcessor.TryMatchOrder( 90 | new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Sell, 91 | m_BuyOrder.Price + 10, 100ul), 92 | m_BuyOrders, m_Trades)); 93 | Assert.That(m_TradeProcessor.Trades.Count, Is.EqualTo(0)); 94 | Assert.That(m_BuyOrders.Count() == 1); 95 | } 96 | 97 | [Test] 98 | public void BigBuyMatchesMultipleSellsTest() 99 | { 100 | var secondSellOrder = new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, 101 | Order.BuyOrSell.Sell, 95, 100); 102 | m_SellOrders.Insert(secondSellOrder); 103 | m_BuyOrder.Quantity = 150; 104 | Assert.True(OrderProcessor.TryMatchOrder(m_BuyOrder, m_SellOrders, m_Trades)); 105 | 106 | Assert.That(m_TradeProcessor.Trades.Count, Is.EqualTo(2)); 107 | Assert.That(m_BuyOrder.Quantity, Is.EqualTo(0)); 108 | Assert.That(secondSellOrder.Quantity, Is.EqualTo(50)); 109 | Assert.That(m_SellOrders[0] == secondSellOrder && m_SellOrders.Count() == 1); 110 | } 111 | 112 | [Test] 113 | public void BigSellMatchesMultipleBuysTest() 114 | { 115 | var secondBuyOrder = new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, 116 | Order.BuyOrSell.Buy, 90, 100); 117 | m_BuyOrders.Insert(secondBuyOrder); 118 | m_SellOrder.Quantity = 150; 119 | Assert.True(OrderProcessor.TryMatchOrder(m_SellOrder, m_BuyOrders, m_Trades)); 120 | 121 | Assert.That(m_TradeProcessor.Trades.Count, Is.EqualTo(2)); 122 | Assert.That(m_BuyOrder.Quantity == 0); 123 | Assert.That(secondBuyOrder.Quantity == 50); 124 | Assert.That(m_BuyOrders[0] == secondBuyOrder && m_BuyOrders.Count() == 1); 125 | } 126 | 127 | [Test] 128 | public void LittleSellMatchesPartialBuyTest() 129 | { 130 | m_SellOrder.Quantity = 50; 131 | Assert.True(OrderProcessor.TryMatchOrder(m_SellOrder, m_BuyOrders, m_Trades)); 132 | 133 | Assert.That(m_TradeProcessor.Trades.Count, Is.EqualTo(1)); 134 | Assert.That(m_BuyOrder.Quantity == 50); 135 | 136 | Assert.That(m_BuyOrders[0] == m_BuyOrder && m_BuyOrders.Count() == 1); 137 | Assert.That(m_SellOrder.Quantity == 0); 138 | } 139 | 140 | [Test] 141 | public void LittleBuyMatchesPartialSellTest() 142 | { 143 | m_BuyOrder.Quantity = 50; 144 | Assert.True(OrderProcessor.TryMatchOrder(m_BuyOrder, m_SellOrders, m_Trades)); 145 | 146 | Assert.That(m_TradeProcessor.Trades.Count, Is.EqualTo(1)); 147 | Assert.That(m_SellOrder.Quantity == 50); 148 | 149 | Assert.That(m_SellOrders[0] == m_SellOrder && m_SellOrders.Count() == 1); 150 | Assert.That(m_BuyOrder.Quantity == 0); 151 | } 152 | 153 | [Test] 154 | public void BigBuyMatchesPartialSellTest() 155 | { 156 | m_BuyOrder.Quantity = 150; 157 | Assert.True(OrderProcessor.TryMatchOrder(m_BuyOrder, m_SellOrders, m_Trades)); 158 | 159 | Assert.That(m_TradeProcessor.Trades.Count, Is.EqualTo(1)); 160 | Assert.That(m_SellOrder.Quantity == 0); 161 | 162 | Assert.That(m_BuyOrders[0] == m_BuyOrder && m_BuyOrders.Count() == 1); 163 | Assert.That(m_SellOrders.Count() == 0); 164 | 165 | Assert.That(m_BuyOrder.Quantity == 50); 166 | } 167 | 168 | [Test] 169 | public void BigSellMatchesPartialBuyTest() 170 | { 171 | m_SellOrder.Quantity = 150; 172 | Assert.True(OrderProcessor.TryMatchOrder(m_SellOrder, m_BuyOrders, m_Trades)); 173 | 174 | Assert.That(m_TradeProcessor.Trades.Count, Is.EqualTo(1)); 175 | Assert.That(m_BuyOrder.Quantity == 0); 176 | 177 | Assert.That(m_SellOrders[0] == m_SellOrder && m_SellOrders.Count() == 1); 178 | Assert.That(m_BuyOrders.Count() == 0); 179 | 180 | Assert.That(m_SellOrder.Quantity == 50); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/UnitTests/OrderProcessorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | using OrderMatchingEngine.OrderBook; 7 | using OrderMatchingEngine.OrderBook.OrderProcessing; 8 | 9 | namespace OrderMatchingEngineTests.UnitTests 10 | { 11 | [TestFixture] 12 | class OrderProcessorTests 13 | { 14 | private Instrument m_Instrument; 15 | private OrderBook m_OrderBook; 16 | 17 | [SetUp] 18 | public void Init() 19 | { 20 | m_Instrument = new Instrument("GOOG"); 21 | m_OrderBook = new OrderBook(m_Instrument); 22 | } 23 | 24 | [Test] 25 | public void SwitchingOrderProcessingStrategiesTest() 26 | { 27 | List orders = new List(Orders()); 28 | 29 | for (int i = 0; i < 40; ++i) 30 | m_OrderBook.InsertOrder(orders[i]); 31 | 32 | m_OrderBook.OrderProcessingStrategy = new ThreadPooledOrderProcessor(m_OrderBook.BuyOrders, m_OrderBook.SellOrders, m_OrderBook.Trades); 33 | 34 | for (int i = 40; i < 80; ++i) 35 | m_OrderBook.InsertOrder(orders[i]); 36 | 37 | m_OrderBook.OrderProcessingStrategy = new DedicatedThreadsOrderProcessor(m_OrderBook.BuyOrders, m_OrderBook.SellOrders, m_OrderBook.Trades); 38 | 39 | for (int i = 80; i < 120; ++i) 40 | m_OrderBook.InsertOrder(orders[i]); 41 | 42 | m_OrderBook.OrderProcessingStrategy = new ThreadPooledOrderProcessor(m_OrderBook.BuyOrders, m_OrderBook.SellOrders, m_OrderBook.Trades); 43 | 44 | for (int i = 120; i < 160; ++i) 45 | m_OrderBook.InsertOrder(orders[i]); 46 | 47 | m_OrderBook.OrderProcessingStrategy = new SynchronousOrderProcessor(m_OrderBook.BuyOrders, m_OrderBook.SellOrders, m_OrderBook.Trades); 48 | 49 | for (int i = 160; i < orders.Count; ++i) 50 | m_OrderBook.InsertOrder(orders[i]); 51 | 52 | while (m_OrderBook.SellOrders.Count() != 100 || m_OrderBook.BuyOrders.Count() != 100) ; 53 | 54 | Assert.That(m_OrderBook.BuyOrders.Count(), Is.EqualTo(100)); 55 | Assert.That(m_OrderBook.SellOrders.Count(), Is.EqualTo(100)); 56 | } 57 | 58 | 59 | private IEnumerable Orders(int numBuyOrders = 100, int numSellOrders = 100) 60 | { 61 | for (int i = 1; i <= numBuyOrders; ++i) 62 | yield return new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Buy, 100m, 100ul); 63 | 64 | for (int i = 1; i <= numSellOrders; ++i) 65 | yield return new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Sell, 110m, 110); 66 | 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/UnitTests/SellOrdersTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using NUnit.Framework; 5 | using OrderMatchingEngine.OrderBook; 6 | 7 | namespace OrderMatchingEngineTests.UnitTests 8 | { 9 | [TestFixture] 10 | internal class SellOrdersTests 11 | { 12 | private SellOrders m_SellOrders; 13 | private Instrument m_Instrument; 14 | 15 | [SetUp] 16 | public void Init() 17 | { 18 | m_Instrument = new Instrument("MSFT"); 19 | m_SellOrders = new SellOrders(m_Instrument); 20 | 21 | for (int i = 0, j = 10; i < 10; ++i, ++j) 22 | { 23 | Thread.Sleep(2); 24 | m_SellOrders.Insert(new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, 25 | Order.BuyOrSell.Sell, 5, (ulong)j)); 26 | } 27 | 28 | for (int i = 0, j = 10; i < 10; ++i, ++j) 29 | { 30 | m_SellOrders.Insert(new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, 31 | Order.BuyOrSell.Sell, 5, (ulong)j)); 32 | } 33 | } 34 | 35 | [Test] 36 | public void CorrectOrderPriorityTest() 37 | { 38 | var sortedOrders = new List(m_SellOrders); 39 | 40 | for (int i = 0; i < sortedOrders.Count - 1; ++i) 41 | { 42 | Order thisOrder = sortedOrders[i]; 43 | Order nextOrder = sortedOrders[i + 1]; 44 | 45 | Assert.That(thisOrder.Price < nextOrder.Price || 46 | (thisOrder.Price == nextOrder.Price && (thisOrder.CreationTime <= nextOrder.CreationTime))); 47 | } 48 | } 49 | 50 | [Test] 51 | public void WrongOrderTypeThrowsException() 52 | { 53 | var order = new EquityOrder(m_Instrument, Order.OrderTypes.GoodUntilCancelled, Order.BuyOrSell.Buy, 10, 10ul); 54 | 55 | Assert.Throws(() => m_SellOrders.Insert(order)); 56 | } 57 | 58 | [Test] 59 | public void WrongInstrumentThrowsException() 60 | { 61 | var order = new EquityOrder(new Instrument("WRONG"), Order.OrderTypes.GoodUntilCancelled, 62 | Order.BuyOrSell.Sell, 10, 10ul); 63 | 64 | Assert.Throws(() => m_SellOrders.Insert(order)); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /OrderMatchingEngineTests/UnitTests/TradesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using NUnit.Framework; 7 | using OrderMatchingEngine; 8 | using OrderMatchingEngine.OrderBook; 9 | 10 | namespace OrderMatchingEngineTests.UnitTests 11 | { 12 | [TestFixture] 13 | class TradesTests 14 | { 15 | private Trades m_Trades; 16 | private Instrument m_Instrument; 17 | 18 | [SetUp] 19 | public void Init() 20 | { 21 | m_Instrument = new Instrument("MSFT"); 22 | m_Trades = new Trades(m_Instrument); 23 | } 24 | 25 | [Test] 26 | public void AddTradeTest() 27 | { 28 | var trade = new Trade(m_Instrument, 100UL, 100.10M); 29 | m_Trades.AddTrade(trade); 30 | 31 | Assert.That(((Trades.InMemoryTradeProcessor)m_Trades.TradeProcessingStrategy).Trades[0], Is.EqualTo(trade)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eneiand/OrderMatchingEngine/ded44cc6c95c5d77e70f7b137a46aaaf1e7b4667/README --------------------------------------------------------------------------------