/// <summary> /// Handles a change to a filled state. /// </summary> /// <param name="workingOrderRow">The parent working order.</param> static void OnFilledAction(DataModel.WorkingOrderRow workingOrderRow) { // A transaction is needed to handle the change. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // This will lock the WorkingOrderRow while we examine it. workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); if (workingOrderRow.RowState == DataRowState.Detached) { return; } // This will mark the working order as filled when the quantity executed is the same as the quantity ordered. The only exception to this is when ng // the workiorder is in an error state. if (workingOrderRow.StatusCode != StatusCode.Error) { Decimal quantityOrdered = WorkingOrderService.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); Decimal quantityExecuted = WorkingOrderService.GetExecutionQuantity(dataModelTransaction, workingOrderRow); if (quantityOrdered == quantityExecuted) { WorkingOrderService.UpdateWorkingOrderStatus(workingOrderRow, StatusCode.Filled); } } }
/// <summary> /// Handles a change to a cancelled state. /// </summary> /// <param name="workingOrderRow">The parent working order.</param> static void OnCanceledAction(DataModel.WorkingOrderRow workingOrderRow) { // A transaction is needed to handle the change. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // This will lock the WorkingOrderRow while we examine it. workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); if (workingOrderRow.RowState == DataRowState.Detached) { return; } // When a destination order is canceled we will return the order to its previous state (as determined by aggregating the executed and destination // quantities). The only exception to this is when the working order is in the error state. if (workingOrderRow.StatusCode != StatusCode.Error) { Decimal quantityExecuted = WorkingOrderService.GetExecutionQuantity(dataModelTransaction, workingOrderRow); Decimal quantityOrdered = WorkingOrderService.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); if (quantityExecuted == 0.0M && workingOrderRow.StatusCode != StatusCode.New) { WorkingOrderService.UpdateWorkingOrderStatus(workingOrderRow, StatusCode.New); } if (0.0M < quantityExecuted && quantityExecuted < quantityOrdered && workingOrderRow.StatusCode != StatusCode.PartiallyFilled) { WorkingOrderService.UpdateWorkingOrderStatus(workingOrderRow, StatusCode.PartiallyFilled); } } }
/// <summary> /// Validates the source order row when it is deleted. /// </summary> /// <param name="sourceOrderRow">The source order row that was deleted.</param> static void OnSourceOrderDelete(DataModel.SourceOrderRow sourceOrderRow) { // We'll need to add several rows to the transaction as we validate the source order deletion. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // There is no implicit locking mechanism for deleted rows, so we need to lock the record manually as we access the parent working order of the deleted // source order. DataModel.WorkingOrderRow workingOrderRow = null; try { DataModel.DataLock.EnterReadLock(); workingOrderRow = ((DataModel.WorkingOrderRow)(sourceOrderRow.GetParentRow(DataModel.SourceOrder.WorkingOrderSourceOrderRelation, DataRowVersion.Original))); } finally { DataModel.DataLock.ExitReadLock(); } workingOrderRow.AcquireReaderLock(dataModelTransaction); // This will insure that the quantity ordered doesn't fall below the quantity placed with brokers and exchanges. Decimal sourceOrderQuantity = WorkingOrderService.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); Decimal destinationOrderQuantity = WorkingOrderService.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } }
/// <summary> /// Validates the source order row when it changes. /// </summary> /// <param name="sourceOrderRow">The source order row that was changed.</param> static void OnSourceOrderChange(DataModel.SourceOrderRow sourceOrderRow) { // We can't allow the quantity of source order shares to drop below the quantity of destination order shares. We only need to check for this condition // when the quantity of the source order record has changed. Decimal originalQuantity = (Decimal)sourceOrderRow[DataModel.SourceOrder.OrderedQuantityColumn, DataRowVersion.Original]; Decimal currentQuantity = (Decimal)sourceOrderRow[DataModel.SourceOrder.OrderedQuantityColumn, DataRowVersion.Current]; if (originalQuantity < currentQuantity) { // We'll need to lock several records in order to check source order and destination order totals. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // Get the working order associated with this source order and lock it. DataModel.WorkingOrderRow workingOrderRow = sourceOrderRow.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction); // Now aggregate the source order quantities and the destination order quantities. Throw an exception if the source orders are less than the // destination orders (we can't have less quantity ordered than we've placed with brokers and exchanges). Decimal sourceOrderQuantity = WorkingOrderService.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); Decimal destinationOrderQuantity = WorkingOrderService.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } } }
/// <summary> /// Handler for validating Destination Order records. /// </summary> /// <param name="sender">The object that originated the event.</param> /// <param name="e">The event arguments.</param> static void OnDestinationOrderAdd(DataModel.DestinationOrderRow destinationOrderRow) { // A transaction is needed to handle the change. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; DataModel.WorkingOrderRow workingOrderRow = destinationOrderRow.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction); Decimal sourceOrderQuantity = WorkingOrderService.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); Decimal destinationOrderQuantity = WorkingOrderService.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } }
/// <summary> /// Handles a change to a error state. /// </summary> /// <param name="workingOrderRow">The parent working order.</param> static void OnClearErrorAction(DataModel.WorkingOrderRow workingOrderRow) { // A transaction is needed to handle the change. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // This will lock the WorkingOrderRow while we examine it. workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); if (workingOrderRow.RowState == DataRowState.Detached) { return; } // The error status is cleared only when none of the sibling destination orders has an error. Boolean isErrorStatus = false; foreach (DataModel.DestinationOrderRow siblingOrderRow in workingOrderRow.GetDestinationOrderRows()) { siblingOrderRow.AcquireReaderLock(dataModelTransaction); if (siblingOrderRow.StatusCode == StatusCode.Error) { isErrorStatus = true; break; } } // If none of the siblings has an error, the we're going to set the working order's status to what it was before the error occurred. if (!isErrorStatus) { Decimal quantityExecuted = WorkingOrderService.GetExecutionQuantity(dataModelTransaction, workingOrderRow); Decimal quantityOrdered = WorkingOrderService.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); if (quantityExecuted == 0.0M && workingOrderRow.StatusCode != StatusCode.New) { WorkingOrderService.UpdateWorkingOrderStatus(workingOrderRow, StatusCode.New); } if (0.0M < quantityExecuted && quantityExecuted < quantityOrdered && workingOrderRow.StatusCode != StatusCode.PartiallyFilled) { WorkingOrderService.UpdateWorkingOrderStatus(workingOrderRow, StatusCode.PartiallyFilled); } if (quantityExecuted == quantityOrdered && workingOrderRow.StatusCode != StatusCode.Filled) { WorkingOrderService.UpdateWorkingOrderStatus(workingOrderRow, StatusCode.Filled); } } }
/// <summary> /// Handles a change to a error state. /// </summary> /// <param name="workingOrderRow">The parent working order.</param> static void OnSetErrorAction(DataModel.WorkingOrderRow workingOrderRow) { // A transaction is needed to handle the change. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // This will lock the WorkingOrderRow while we examine it. workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); if (workingOrderRow.RowState == DataRowState.Detached) { return; } // This logic is simple enough, set to an error state if we're not already there. if (workingOrderRow.StatusCode != StatusCode.Error) { WorkingOrderService.UpdateWorkingOrderStatus(workingOrderRow, StatusCode.Error); } }
/// <summary> /// Handles a change to a partially executed state. /// </summary> /// <param name="workingOrderRow">The parent working order.</param> static void OnPartialAction(DataModel.WorkingOrderRow workingOrderRow) { // A transaction is needed to handle the change. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // This will lock the WorkingOrderRow while we examine it. workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); if (workingOrderRow.RowState == DataRowState.Detached) { return; } // The working order is considered partially filled if any of its destination orders are partially filled. The only exception to this is when the // working order is in an error state. if (workingOrderRow.StatusCode != StatusCode.Error && workingOrderRow.StatusCode != StatusCode.PartiallyFilled) { WorkingOrderService.UpdateWorkingOrderStatus(workingOrderRow, StatusCode.PartiallyFilled); } }
/// <summary> /// Handler for validating Destination Order records. /// </summary> /// <param name="sender">The object that originated the event.</param> /// <param name="e">The event arguments.</param> static void OnDestinationOrderChange(DataModel.DestinationOrderRow destinationOrderRow) { // A transaction is needed to handle the change. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // If the quantity has changed then we need to make sure that the quantity sent to a destination (broker, exchange, etc.) is not less than the amount // ordered. That is, we can't accept a change that leaves us overcommited with a destination. Decimal originalQuantity = (Decimal)destinationOrderRow[DataModel.DestinationOrder.OrderedQuantityColumn, DataRowVersion.Original]; Decimal currentQuantity = (Decimal)destinationOrderRow[DataModel.DestinationOrder.OrderedQuantityColumn, DataRowVersion.Current]; if (originalQuantity < currentQuantity) { // We need to aggregate at the working order, so we need to lock the working order while we do our sums. DataModel.WorkingOrderRow workingOrderRow = destinationOrderRow.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); // This will aggregate the source and destination orders and throw an exception if this transaction would leave us overcommitted with the // destination. Decimal sourceOrderQuantity = WorkingOrderService.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); Decimal destinationOrderQuantity = WorkingOrderService.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } } // If the StatusCode of the destination order has changed then find the right handler for the state change and go execute the business logic for this // change. The 'statusChangeMap' is a two dimensional Dictionary (hash table) using the previous and current states to find the right handler. StatusCode previousStatusCode = (StatusCode)destinationOrderRow[DataModel.DestinationOrder.StatusCodeColumn, DataRowVersion.Original]; StatusCode currentStatusCode = destinationOrderRow.StatusCode; if (previousStatusCode != currentStatusCode) { statusChangeMap[previousStatusCode][currentStatusCode](destinationOrderRow.WorkingOrderRow); } }
/// <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(); } } } }