/// <summary> /// Initializes the server prices with the simulator prices for currency. /// </summary> static void InitializeCurrencyPrice() { // This creates a list of subscribers to the currency price updates. List <WebServiceClient> webServiceClients = new List <WebServiceClient>(); foreach (ClientInfo clientInfo in PriceSimulator.currencyClients) { WebServiceClient webServiceClient = new WebServiceClient(clientInfo.EndpointName); webServiceClient.ClientCredentials.UserName.UserName = clientInfo.UserName; webServiceClient.ClientCredentials.UserName.Password = clientInfo.Password; webServiceClients.Add(webServiceClient); } // The quotes are collected here and sent to the server when we have them all. List <Quote> quotes = new List <Quote>(); // The data model must be locked while we query it for currency prices. lock (MarketData.SyncRoot) { // This provides a view for equity prices DataView currencyView = new DataView(MarketData.Price); currencyView.RowFilter = "SecurityType='CURRENCY'"; // This will segregate the quotes into the different feeds for all the equities in the simulator. foreach (DataRowView dataRowView in currencyView) { MarketDataModel.PriceRow priceRow = dataRowView.Row as MarketDataModel.PriceRow; quotes.Add(new Quote() { AskPrice = priceRow.AskPrice, AskSize = priceRow.AskSize, BidPrice = priceRow.BidPrice, BidSize = priceRow.BidSize, LastPrice = priceRow.LastPrice, LastSize = priceRow.LastSize, Symbol = priceRow.Symbol }); } } // A real-world ticker feed will use a single scheme for identifying the securities. Those schemes are not unique and will often overlap. This // simulates a feed from multiple real-world vendors with different methods of identifying securities. Each organization that has subscrived to // this simluator will get a copy of the batch. foreach (WebServiceClient webServiceClient in webServiceClients) { webServiceClient.UpdateCurrencyPrice(quotes.ToArray()); } }
/// <summary> /// Initializes the server prices with the simulator prices. /// </summary> static void InitializeDebtPrice() { // This creates a list of subscribers to the currency price updates. List <WebServiceClient> webServiceClients = new List <WebServiceClient>(); foreach (ClientInfo clientInfo in PriceSimulator.debtClients) { WebServiceClient webServiceClient = new WebServiceClient(clientInfo.EndpointName); webServiceClient.ClientCredentials.UserName.UserName = clientInfo.UserName; webServiceClient.ClientCredentials.UserName.Password = clientInfo.Password; webServiceClients.Add(webServiceClient); } // The quotes are collected here and sent to the server when we have them all. List <Quote> quotes = new List <Quote>(); // The market data must be locked while we query it for bond prices. lock (MarketData.SyncRoot) { // This provides a view for equity prices DataView debtView = new DataView(MarketData.Price); debtView.RowFilter = "SecurityType='DEBT'"; // This will segregate the quotes into the different feeds for all the equities in the simulator. foreach (DataRowView dataRowView in debtView) { MarketDataModel.PriceRow priceRow = dataRowView.Row as MarketDataModel.PriceRow; quotes.Add(new Quote() { AskPrice = priceRow.AskPrice, AskSize = priceRow.AskSize, BidPrice = priceRow.BidPrice, BidSize = priceRow.BidSize, LastPrice = priceRow.LastPrice, LastSize = priceRow.LastSize, Symbol = priceRow.Symbol }); } } // This will set the prices of the USD securities that use CUSIPs. foreach (WebServiceClient webServiceClient in webServiceClients) { webServiceClient.UpdateCusipPrice(quotes.ToArray()); } }
/// <summary> /// Simulation of price changes in a stock market. /// </summary> static void SimulatePrice() { // This will seed the random number generator. Random random = new Random(DateTime.Now.Millisecond); // This creates a list of subscribers to the equity price updates. List <WebServiceClient> webServiceClients = new List <WebServiceClient>(); foreach (ClientInfo clientInfo in PriceSimulator.equityClients) { WebServiceClient webServiceClient = new WebServiceClient(clientInfo.EndpointName); webServiceClient.ClientCredentials.UserName.UserName = clientInfo.UserName; webServiceClient.ClientCredentials.UserName.Password = clientInfo.Password; webServiceClients.Add(webServiceClient); } // The market data has different securities but we are only interested in simulating price changes for equities here. DataView equityView = null; lock (MarketData.SyncRoot) { equityView = new DataView(MarketData.Price); equityView.RowFilter = "SecurityType='EQUITY'"; } // This thread will run until terminated externally. while (true) { // The price feeds from vendors will rely on their own unique identifiers. This simulator tries to emulate this architecture by providing // different web services that recognize different unique identifiers. This dictionary will segregate the prices by their identifiers. Dictionary <String, List <Quote> > quotes = new Dictionary <String, List <Quote> >() { { "CA TICKER", new List <Quote>() }, { "UK TICKER", new List <Quote>() }, { "US TICKER", new List <Quote>() } }; // In order to minimize the overhead for creating transaction to service the prices, the prices are batched up and processed in a single // transaction. The desired frequence determines the number of prices changed in a single batch since the frequence of the thread is fixed // through constants. Note that the 'EquityFrequency' property is thread safe, so it can be changed from the outside. Int32 batchSize = Convert.ToInt32(PriceSimulator.EquityFrequency * Convert.ToDouble(PriceSimulator.interval) / 1000.0); // The prices are not updated continously because this would be a serious drain on the web services. Instead, the prices are batched up and sent in // bulk. This introduces a latency that is no greater than the PriceSimulator.interval value. for (Int32 index = 0; index < batchSize; index++) { // The market data must be locked while we query and change the prices. However, a batch of quotes is built here which will be sent to the // subscribers after releasing the lock. lock (MarketData.SyncRoot) { // This will select a random price in the table and change the price by a random amount. MarketDataModel.PriceRow priceRow = equityView[random.Next(equityView.Count)].Row as MarketDataModel.PriceRow; // This will simulate a random selection of a price change is quoted. The 'quoteColumn' variable can be used to reference the selected quote // from any price row. Below, a random row will be selected for the price change. Int32 quoteTypeIndex = random.Next(Enum.GetValues(typeof(QuoteType)).Length); switch ((QuoteType)Enum.GetValues(typeof(QuoteType)).GetValue(quoteTypeIndex)) { case QuoteType.Ask: // Move the 'Ask' price. priceRow.AskPrice += Convert.ToDecimal(Math.Round(random.NextDouble() - 0.5, 2)); priceRow.AskSize = Convert.ToDecimal(random.Next(1, 10)); break; case QuoteType.Bid: // Move the 'Bid' price. priceRow.BidPrice += Convert.ToDecimal(Math.Round(random.NextDouble() - 0.5, 2)); priceRow.BidSize = Convert.ToDecimal(random.Next(1, 10)); break; case QuoteType.Last: // Move the 'Last' price. priceRow.LastPrice += Convert.ToDecimal(Math.Round(random.NextDouble() - 0.5, 2)); priceRow.LastSize = Convert.ToDecimal(random.Next(1, 10)); break; } // This will segregate the quotes into the different feeds. This batch of price changes will be sent to each of the subscribing web clients // when we've accumulated the specified number of 'ticks'. quotes[priceRow.Feed].Add(new Quote() { AskPrice = priceRow.AskPrice, AskSize = priceRow.AskSize, BidPrice = priceRow.BidPrice, BidSize = priceRow.BidSize, LastPrice = priceRow.LastPrice, LastSize = priceRow.LastSize, Symbol = priceRow.Symbol }); } } // The web service has trouble with lists so we need to convert the collections to arrays before passing them. There are two different flavors of // updating the subscribers. The first involves hitting the specific web services for the specific simulated feeds. That is, we web service // contains methods for updating United States Ticker prices, Canadian Ticker Prices, United Kingdom Ticker prices. However, each feed would then // require a different web service. While this flavor is useful for shaking out the architecture of the server (and the simulator), it is less // efficient on bandwidth and CPU cycles as this method, which represents a consolidated feed that pull together quotes from different pricing // services and updates the server with one batch. Dictionary <String, Quote[]> serviceQuotes = new Dictionary <String, Quote[]>() { { "CA TICKER", quotes["CA TICKER"].ToArray() }, { "UK TICKER", quotes["UK TICKER"].ToArray() }, { "US TICKER", quotes["US TICKER"].ToArray() } }; // A real-world ticker feed will use a single scheme for identifying the securities. Those schemes are not unique and will often overlap. This // simulates a feed from multiple real-world vendors with different methods of identifying securities that are consolidated into a single batch. // Each organization that has subscrived to this simluator will get a copy of the batch. for (Int32 index = 0; index < webServiceClients.Count; index++) { // This is the next web service that we'll try to update with a batch of quotes. WebServiceClient webServiceClient = webServiceClients[index]; while (true) { try { webServiceClient.UpdatePrice(serviceQuotes); break; } catch (Exception) { // If any exception causes the communication channel to close, then we'll try to reopen it and send the batch of quotes again. if (webServiceClient.State != CommunicationState.Opened) { foreach (ClientInfo clientInfo in PriceSimulator.equityClients) { if (webServiceClient.ClientCredentials.UserName.UserName == webServiceClient.ClientCredentials.UserName.UserName) { webServiceClients.Remove(webServiceClient); webServiceClient = new WebServiceClient(clientInfo.EndpointName); webServiceClient.ClientCredentials.UserName.UserName = clientInfo.UserName; webServiceClient.ClientCredentials.UserName.Password = clientInfo.Password; webServiceClients.Add(webServiceClient); } } } } } } // This allows other threads the chance to run after each batch of price changes has been delivered to the subscribers. Thread.Sleep(PriceSimulator.interval); } }
/// <summary> /// Initializes the server prices with the simulator prices. /// </summary> static void InitializeEquityPrice() { // This creates a list of subscribers to the currency price updates. List <WebServiceClient> webServiceClients = new List <WebServiceClient>(); foreach (ClientInfo clientInfo in PriceSimulator.equityClients) { WebServiceClient webServiceClient = new WebServiceClient(clientInfo.EndpointName); webServiceClient.ClientCredentials.UserName.UserName = clientInfo.UserName; webServiceClient.ClientCredentials.UserName.Password = clientInfo.Password; webServiceClients.Add(webServiceClient); } // The price feeds from vendors will rely on their own unique identifiers. This simulator tries to emulate this architecture by providing different web // services that recognize different unique identifiers. This dictionary will segregate the prices by their identifiers. Dictionary <String, List <Quote> > quotes = new Dictionary <String, List <Quote> >() { { "CA TICKER", new List <Quote>() }, { "UK TICKER", new List <Quote>() }, { "US TICKER", new List <Quote>() } }; // This will lock the market data while we query it for equity prices. lock (MarketData.SyncRoot) { // This provides a view for equity prices DataView equityView = new DataView(MarketData.Price); equityView.RowFilter = "SecurityType='EQUITY'"; // This will segregate the quotes into the different feeds for all the equities in the simulator. foreach (DataRowView dataRowView in equityView) { MarketDataModel.PriceRow priceRow = dataRowView.Row as MarketDataModel.PriceRow; quotes[priceRow.Feed].Add(new Quote() { AskPrice = priceRow.AskPrice, AskSize = priceRow.AskSize, BidPrice = priceRow.BidPrice, BidSize = priceRow.BidSize, LastPrice = priceRow.LastPrice, LastSize = priceRow.LastSize, Symbol = priceRow.Symbol }); } } // The web service has trouble with lists so we need to convert the collections to arrays before passing them. Dictionary <String, Quote[]> serviceQuotes = new Dictionary <String, Quote[]>() { { "CA TICKER", quotes["CA TICKER"].ToArray() }, { "UK TICKER", quotes["UK TICKER"].ToArray() }, { "US TICKER", quotes["US TICKER"].ToArray() } }; // A real-world ticker feed will use a single scheme for identifying the securities. Those schemes are not unique and will often overlap. This // simulates a feed from multiple real-world vendors with different methods of identifying securities. Each organization that has subscrived to // this simluator will get a copy of the batch. foreach (WebServiceClient webServiceClient in webServiceClients) { webServiceClient.UpdatePrice(serviceQuotes); } }
/// <summary> /// Simulates the execution of an order. /// </summary> /// <param name="orderRow">The order to be executed.</param> static void ExecuteOrder(BrokerContext brokerContext, MarketDataModel.OrderRow orderRow) { // Create an execution. MarketDataModel.ExecutionRow executionRow = MarketData.Execution.NewExecutionRow(); executionRow.ExecutionId = Guid.NewGuid(); executionRow.OrderId = orderRow.OrderId; // This execution needs a random number of shares to fill. We can't overcommit the order, so first we need to count up how many shares have already // been executed on this order. Then we can calculated the 'leaves' quantity (the amount remaining to be executed). Decimal executedQuantity = 0.0m; foreach (MarketDataModel.ExecutionRow childExecutionRow in orderRow.GetExecutionRows()) { executedQuantity += childExecutionRow.Quantity; } Decimal leavesQuantity = orderRow.QuantityOrdered - executedQuantity; // This will generate either a random fill or finish the order with an odd lot. executionRow.Quantity = Math.Min(leavesQuantity, brokerContext.Random.Next(1, 100) * 100); // The execution requires a price. The price simulator will provide a bid price or ask price depending on the side of the order. MarketDataModel.PriceRow priceRow = MarketData.Price.FindByFeedSymbol("US TICKER", orderRow.Symbol); SideCode sideCode = (SideCode)orderRow.SideCode; executionRow.Price = 0.0m; if (priceRow != null) { executionRow.Price = sideCode == SideCode.Buy || sideCode == SideCode.BuyCover ? priceRow.BidPrice : priceRow.AskPrice; } // We're record this execution in the broker's book. MarketData.Execution.AddExecutionRow(executionRow); // Now that the order is recorded, we can update the 'leaves' to reflect the execution. leavesQuantity -= executionRow.Quantity; // This message is sent back to the customer to record the execution. Note that we pick the web service to use from a mapping table. This table // associates the name of the firm with a preconfigured web service to communicate with that firm. Message message = new Message(); message.ExecBroker = brokerContext.Symbol; message.Symbol = orderRow.Symbol; message.SenderCompID = orderRow.Source; message.Price = executionRow.Price; message.OrderID = orderRow.OrderId.ToString(); message.OrderStatusCode = leavesQuantity == 0.0M ? OrderStatusCode.Filled : OrderStatusCode.PartiallyFilled; message.CumQty = executionRow.Quantity; message.ClOrdID = orderRow.CustomerOrderId; message.LeavesQty = leavesQuantity; // If this order is filled then remove it from the simulated order book. If the order book is empty, then put the broker threads to sleep. if (leavesQuantity == 0) { MarketData.Order.RemoveOrderRow(orderRow); if (MarketData.Order.Count == 0) { BrokerSimulator.orderEvent.Reset(); } } // This places the message in a queue of messages owned by the current broker that will route the message back to the source of the order. brokerContext.SendReport(message.SenderCompID, message); }