/// <summary> /// Creates orders in the simulated order book. /// </summary> private static void OrderHandler() { // This thread will create orders in the simulated order book from destination orders placed in the queue. This thread // is necessary due to the locking architecture that prevents accessing the data model during a commit operation (which // is where the event indicating a new or changed destination order originates). while (MarketSimulator.IsBrokerSimulatorThreadRunning) { // This thread will wait here until a destination order is available in the queue. DataModel.DestinationOrderRow destinationOrderRow = MarketSimulator.orderQueue.Dequeue(); // The code that adds an order to the simulated book must have exclusive access to the simulated data model. lock (MarketSimulator.syncRoot) { // The primary data model needs to be accessed also for ancillary data associated with the new order. using (TransactionScope transactionScope = new TransactionScope()) { try { // This context is used to keep track of the locks aquired for the ancillary data. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // Lock the destination order. destinationOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(destinationOrderRow); // Lock the Security. DataModel.SecurityRow securityRow = destinationOrderRow.SecurityRowByFK_Security_DestinationOrder_SecurityId; securityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(securityRow); // The working order row must be locked to examine the flags DataModel.WorkingOrderRow workingOrderRow = destinationOrderRow.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); // Lock the Entity. DataModel.EntityRow entityRow = securityRow.EntityRow; entityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(entityRow); // Calculate the quantity executed on this order (some orders are created with executions, such as crossed orders.) Decimal quantityExecuted = 0.0M; foreach (DataModel.ExecutionRow executionRow in destinationOrderRow.GetExecutionRows()) { executionRow.AcquireReaderLock(dataModelTransaction); dataModelTransaction.AddLock(executionRow); quantityExecuted += executionRow.ExecutionQuantity; } // The simulated order is added to the book. This collects all the information required to simulate the execution of this order. DataSetMarket.OrderRow orderRow = MarketSimulator.dataSetMarket.Order.NewOrderRow(); orderRow.BlotterId = destinationOrderRow.BlotterId; orderRow.DestinationOrderId = destinationOrderRow.DestinationOrderId; orderRow.OrderTypeCode = OrderTypeMap.FromId(destinationOrderRow.OrderTypeId); orderRow.QuantityOrdered = destinationOrderRow.OrderedQuantity; orderRow.QuantityExecuted = quantityExecuted; orderRow.SideCode = SideMap.FromId(destinationOrderRow.SideId); orderRow.Symbol = securityRow.Symbol; orderRow.TimeInForceCode = TimeInForceMap.FromId(destinationOrderRow.TimeInForceId); MarketSimulator.dataSetMarket.Order.AddOrderRow(orderRow); // Adding the first order to the simulated order book will enable the simulation thread to begin processing the orders. The order // simulation thread will continue until the book is filled. if (MarketSimulator.dataSetMarket.Order.Count == 1) { MarketSimulator.orderEvent.Set(); } } catch { } // The locks are no longer required. transactionScope.Complete(); } } } }
/// <summary> /// Simulation of brokers executing destination orders. /// </summary> private static void SimulateBroker() { // This will seed the random number generator. Random random = new Random(DateTime.Now.Millisecond); // An instance of the data model is required to update it. DataModel dataModel = new DataModel(); // This set of claims gives the current thread the authority to update the price table. List <Claim> listClaims = new List <Claim>(); listClaims.Add(new Claim(Teraque.ClaimTypes.Create, Teraque.Resources.Application, Rights.PossessProperty)); listClaims.Add(new Claim(Teraque.ClaimTypes.Update, Teraque.Resources.Application, Rights.PossessProperty)); listClaims.Add(new Claim(Teraque.ClaimTypes.Read, Teraque.Resources.Application, Rights.PossessProperty)); ClaimSet adminClaims = new DefaultClaimSet(null, listClaims); // Thread.CurrentPrincipal = new ClaimsPrincipal(new GenericIdentity("Broker Service"), adminClaims); // Every execution requires a user identifier (the user who created the execution) and the broker who executed the // trade. These values are hard coded at the moment but should be more intelligently assigned in the future. Guid userId = Guid.Empty; Guid brokerId = Guid.Empty; // Operating values are required for the simulation that come from the data model. using (TransactionScope transactionScope = new TransactionScope()) { // The middle tier context allows ADO operations to participate in the transaction. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // The broker for all the executions is hard coded. DataModel.EntityRow brokerRow = DataModel.Entity.EntityKeyExternalId0.Find(new object[] { "ZODIAC SECURITIES" }); brokerRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(brokerRow); brokerId = brokerRow.EntityId; // The user who creates these executions is hard coded. DataModel.EntityRow userRow = DataModel.Entity.EntityKeyExternalId0.Find(new object[] { "ADMINISTRATOR" }); userRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(userRow); userId = userRow.EntityId; // The data model doesn't need to be locked any more. transactionScope.Complete(); } // This thread will run until an external thread sets this property to 'false'. while (MarketSimulator.IsBrokerSimulatorThreadRunning) { // Wait for orders to show up in the simulated order book. MarketSimulator.orderEvent.WaitOne(); // The simulator parameters need to be locked for each batch of simulated price changes. try { Monitor.Enter(MarketSimulator.syncRoot); // This will select a random price in the table and change the price by a random amount. if (MarketSimulator.dataSetMarket.Order.Rows.Count > 0) { // Select a random row from the order book. int orderIndex = random.Next(MarketSimulator.dataSetMarket.Order.Rows.Count); DataSetMarket.OrderRow orderRow = MarketSimulator.dataSetMarket.Order[orderIndex]; if (orderRow.IsBusy) { Thread.Sleep(0); continue; } orderRow.IsBusy = true; // This transaction is required to add the new execution. using (TransactionScope transactionScope = new TransactionScope()) { // The middle tier context allows ADO operations to participate in the transaction. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // 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. int actionTypeIndex = random.Next(Enum.GetValues(typeof(ActionType)).Length); switch ((ActionType)Enum.GetValues(typeof(ActionType)).GetValue(actionTypeIndex)) { case ActionType.Execute: // This creates a random execution of the remaining shares on the order. DateTime dateTime = DateTime.Now; Guid blotterId = orderRow.BlotterId; Decimal quantityLeaves = orderRow.QuantityOrdered - orderRow.QuantityExecuted; Decimal quantityExecuted = quantityLeaves <= 100 ? quantityLeaves : Convert.ToDecimal(random.Next(1, Convert.ToInt32(quantityLeaves / 100.0M))) * 100.0M; DataSetMarket.PriceRow priceRow = MarketSimulator.dataSetMarket.Price.FindByConfigurationIdSymbol("US TICKER", orderRow.Symbol); Decimal executionPrice = 0.0M; if (priceRow != null) { executionPrice = orderRow.SideCode == SideCode.Buy || orderRow.SideCode == SideCode.BuyCover ? priceRow.BidPrice : priceRow.AskPrice; } Guid executionId = Guid.NewGuid(); Guid destinationOrderId = orderRow.DestinationOrderId; // When the order is completed, it is removed from the order book. When the order book is empty, the thread goes to sleep // until another thread puts something in the order queue. orderRow.QuantityExecuted += quantityExecuted; if (orderRow.QuantityOrdered == orderRow.QuantityExecuted) { MarketSimulator.dataSetMarket.Order.RemoveOrderRow(orderRow); if (MarketSimulator.dataSetMarket.Order.Count == 0) { MarketSimulator.orderEvent.Reset(); } } if (quantityExecuted > 0) { Monitor.Exit(MarketSimulator.syncRoot); dataModel.CreateExecution( 0.0M, blotterId, null, brokerId, 0.0M, dateTime, userId, destinationOrderId, StateMap.FromCode(StateCode.Acknowledged), executionId, executionPrice, quantityExecuted, null, null, false, dateTime, userId, null, null, null, null, StateMap.FromCode(StateCode.Acknowledged), 0.0M, 0.0M, 0.0M, 0.0M); Monitor.Enter(MarketSimulator.syncRoot); } break; } // This will commit the changes to the order book. transactionScope.Complete(); } // This allows another thread to work the order. orderRow.IsBusy = false; } } finally { Monitor.Exit(MarketSimulator.syncRoot); } // This allows other threads the chance to run. Thread.Sleep(0); } }