/// <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> /// 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> /// 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> /// Truncates the Execution table. /// </summary> internal static void Truncate() { // We need a transaction in order to scan the Execution table. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // We can't modify a list while iterating through it, so we need to collect all the Execution records in this list. Then when we start to // destroy them, we won't disturb the iterator. List <DestroyInfo> destroyInfoList = new List <DestroyInfo>(); try { // A reader lock is required to protect the table iterators while we collect all the Executions. DataModel.DataLock.EnterReadLock(); // This will collect all the Executions. foreach (DataModel.ExecutionRow executionRow in DataModel.Execution) { executionRow.AcquireReaderLock(dataModelTransaction); dataModelTransaction.AddLock(executionRow); destroyInfoList.Add(new DestroyInfo(new Object[] { executionRow.ExecutionId }, executionRow.RowVersion)); } } finally { // We don't need to protect the Execution table iterators any longer. DataModel.DataLock.ExitReadLock(); } // At this point we've collected all the Executions in the table. This will call the internal method to destroy them. foreach (DestroyInfo destroyInfo in destroyInfoList) { DataModel.TenantDataModel.DestroyExecution(destroyInfo.Key, destroyInfo.RowVersion); } }
/// <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> /// Gets the user's unique identifier. /// </summary> /// <returns>The user's unique identifier.</returns> public static Guid GetUserId() { // Get the current transaction and the user's identity principal. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; OrganizationPrincipal organizationPrincipal = Thread.CurrentPrincipal as OrganizationPrincipal; // Try to find the user in the user tables. DataModel.UserRow userRow = DataModel.User.UserKeyDistinguishedName.Find(organizationPrincipal.DistinguishedName); if (userRow == null) { Log.Error(String.Format("Invalid Login request for {0}", organizationPrincipal.DistinguishedName)); throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("User", new Object[] { organizationPrincipal.DistinguishedName })); } // If a record is found in the User table that matches the thread's identity, then lock it for the duration of the transaction and insure that it wasn't // deleted between the time it was found and the time it was locked. userRow.AcquireReaderLock(dataModelTransaction); dataModelTransaction.AddLock(userRow); if (userRow.RowState == DataRowState.Detached) { throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("User", new Object[] { organizationPrincipal.DistinguishedName })); } // This is the user's internal identity to the data model. return(userRow.UserId); }
/// <summary> /// Gets the StatusId from the StatusCode. /// </summary> internal static Guid FromCode(DataModelTransaction dataModelTransaction, StatusCode statusCode) { DataModel.StatusRow statusRow = DataModel.Status.StatusKeyStatusCode.Find(statusCode); statusRow.AcquireReaderLock(dataModelTransaction); if (statusRow.RowState != System.Data.DataRowState.Detached) { throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("Status", new Object[] { statusCode })); } return(statusRow.StatusId); }
/// <summary> /// Gets the sum total of all the destination orders. /// </summary> /// <param name="dataModelTransaction">The data model transaction.</param> /// <param name="workingOrderRow">The working order row.</param> /// <returns>The aggregate quantity of destination orders for the specified working order.</returns> internal static Decimal GetDestinationOrderQuantity(DataModelTransaction dataModelTransaction, DataModel.WorkingOrderRow workingOrderRow) { // Aggregate the destination orders. Decimal destinationOrderQuantity = 0.0m; foreach (DataModel.DestinationOrderRow destinationOrderRow in workingOrderRow.GetDestinationOrderRows()) { destinationOrderRow.AcquireReaderLock(dataModelTransaction); destinationOrderQuantity += destinationOrderRow.OrderedQuantity; } return(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> /// 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 OnDestinationOrderDelete(DataModel.DestinationOrderRow destinationOrderRow) { // A transaction is needed to handle the change. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // 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. Note // that on a deletion, we don't have easy access to the original working order, so we need to extract it from the deleted record. StatusCode previousStatusCode = (StatusCode)destinationOrderRow[DataModel.DestinationOrder.StatusCodeColumn, DataRowVersion.Original]; StatusCode currentStatusCode = StatusCode.Deleted; Guid workingOrderId = (Guid)destinationOrderRow[DataModel.DestinationOrder.WorkingOrderIdColumn, DataRowVersion.Original]; DataModel.WorkingOrderRow workingOrderRow = DataModel.WorkingOrder.WorkingOrderKey.Find(new Object[] { workingOrderId }); if (workingOrderRow != null) { statusChangeMap[previousStatusCode][currentStatusCode](workingOrderRow); } }
/// <summary> /// Handles a change to the DestinationOrder table. /// </summary> /// <param name="sender">The object that originated the event.</param> /// <param name="e">The event data.</param> static void OnDestinationOrderRowChanged(object sender, DataModel.DestinationOrderRowChangeEventArgs e) { // This will turn new DestinationOrder records into orders that can be sent to a destination for execution. if (e.Action == DataRowAction.Add) { // The current transaction is going to be needed to lock records. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // Create a new message for the order we're going to build from the DestinationOrder. Message message = new Message(); // The market execution engine will need to know the source firm so it knows how to route the order back. OrganizationPrincipal organizationPrincipal = Thread.CurrentPrincipal as OrganizationPrincipal; message.SenderCompID = organizationPrincipal.Organization; // Copy the basic properties of the DestinationOrder into the message. DataModel.DestinationOrderRow destinationOrderRow = e.Row; message.ClOrdID = destinationOrderRow.DestinationOrderId.ToString(); message.OrderQty = destinationOrderRow.OrderedQuantity; message.OrdType = destinationOrderRow.OrderTypeCode; message.SideCode = destinationOrderRow.SideCode; message.TimeInForceCode = destinationOrderRow.TimeInForceCode; // Get the symbol to use as a security identifier. DataModel.SecurityRow securityRow = destinationOrderRow.SecurityRowByFK_Security_DestinationOrder_SecurityId; securityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(securityRow); if (securityRow.RowState == DataRowState.Detached) { throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("Security", new Object[] { destinationOrderRow.SecurityId })); } message.Symbol = securityRow.Symbol; // This will put the new order in a queue. The DestinatonThread will pull it out, batch it up and send it to the destination to be executed. lock (MarketEngine.syncRoot) { MarketEngine.messageQueue.Enqueue(message); if (messageQueue.Count == 1) { MarketEngine.orderEvent.Set(); } } } }
/// <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> /// Gets the sum total of all the executions. /// </summary> /// <param name="dataModelTransaction">The data model transaction.</param> /// <param name="workingOrderRow">The working order row.</param> /// <returns>The aggregate quantity of executed orders for the specified working order.</returns> internal static Decimal GetExecutionQuantity(DataModelTransaction dataModelTransaction, DataModel.WorkingOrderRow workingOrderRow) { // Aggregate the executed orders. Decimal executionQuantity = 0.0m; foreach (DataModel.DestinationOrderRow destinationOrderRow in workingOrderRow.GetDestinationOrderRows()) { destinationOrderRow.AcquireReaderLock(dataModelTransaction); if (destinationOrderRow.StatusCode != StatusCode.Canceled) { foreach (DataModel.ExecutionRow executionRow in destinationOrderRow.GetExecutionRows()) { executionRow.AcquireReaderLock(dataModelTransaction); executionQuantity += executionRow.ExecutionQuantity; } } } // This is the aggregate quantity executed for the given working order. return(executionQuantity); }
/// <summary> /// The sum total of the quantities of all the source orders in a given working order. /// </summary> /// <param name="dataModelTransaction"></param> /// <param name="workingOrderRow">A working order row.</param> /// <returns>The total quantity of all the source orders associated with the working order.</returns> internal static Decimal GetSourceOrderQuantity(DataModelTransaction dataModelTransaction, DataModel.WorkingOrderRow workingOrderRow) { // This will aggregate all the source order quantities. Note that the rows are kept locked for the duration of the transaction. This guarantees // the integrity of the aggregate values. Decimal sourceOrderQuantity = 0.0m; foreach (DataModel.SourceOrderRow sourceOrderRow in workingOrderRow.GetSourceOrderRows()) { try { sourceOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); sourceOrderQuantity += sourceOrderRow.OrderedQuantity; } finally { sourceOrderRow.ReleaseLock(dataModelTransaction.TransactionId); } } // This is the sum total of all the source orders in the given working order. return(sourceOrderQuantity); }
/// <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(); } } } }
/// <summary> /// Updates prices using a United States ticker. /// </summary> /// <param name="symbolIndex">The index to use to find the symbol.</param> /// <param name="symbolKey">The symbol key.</param> /// <param name="currencyIndex">The index to use to find the currency.</param> /// <param name="currencyKey">The currency key.</param> /// <param name="quote">The quote used to update the price.</param> static void UpdatePriceBySymbolCurrency( DataModel.IEntityIndex symbolIndex, Object[] symbolKey, DataModel.IEntityIndex currencyIndex, Object[] currencyKey, Quote quote) { // The current transaction and the target data model is extracted from the thread. DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; TenantDataModel tenantDataSet = dataModelTransaction.TenantDataModel; // This will find the currency of the quote. DataModel.EntityRow currencyEntityRow = currencyIndex.Find(currencyKey); if (currencyEntityRow == null) { throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("Entity", currencyKey)); } currencyEntityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(currencyEntityRow); if (currencyEntityRow.RowState == System.Data.DataRowState.Detached) { throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("Entity", currencyKey)); } Guid currencyId = currencyEntityRow.EntityId; // This will find the security using the external identifier. DataModel.EntityRow securityEntityRow = symbolIndex.Find(symbolKey); if (securityEntityRow == null) { throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("Entity", symbolKey)); } securityEntityRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(securityEntityRow); if (securityEntityRow.RowState == System.Data.DataRowState.Detached) { throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("Entity", symbolKey)); } Guid securityId = securityEntityRow.EntityId; // If the price record exists, then update it. If it doesn't exist then create it. Object[] priceKey = new Object[] { securityId, currencyId }; DataModel.PriceRow priceRow = DataModel.Price.PriceKey.Find(priceKey); if (priceRow == null) { tenantDataSet.CreatePrice( quote.AskPrice, quote.AskSize, quote.BidPrice, quote.BidSize, null, currencyId, null, quote.LastPrice, quote.LastSize, null, null, null, securityId, null, null); } else { priceRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(priceRow); if (priceRow.RowState == System.Data.DataRowState.Detached) { throw new FaultException <RecordNotFoundFault>(new RecordNotFoundFault("Price", priceKey)); } tenantDataSet.UpdatePrice( quote.AskPrice, quote.AskSize, quote.BidPrice, quote.BidSize, null, currencyId, null, quote.LastPrice, quote.LastSize, null, null, null, priceKey, priceRow.RowVersion, securityId, null, null); } }
/// <summary> /// Simulation of price changes in a stock market. /// </summary> private static void SimulatePrice() { // This will seed the random number generator. Random random = new Random(DateTime.Now.Millisecond); // 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.ToArray()); // Thread.CurrentPrincipal = new Teraque.ClaimsPrincipal(new GenericIdentity("Price Service"), adminClaims); // This thread will run until an external thread sets this property to 'false'. while (MarketSimulator.IsPriceSimulatorThreadRunning) { // The price changes are generated in batches since there is a modest overhead to creating each transaction and applying it to the shared data // model. using (TransactionScope transactionScope = new TransactionScope()) { // The middle tier context allows ADO operations to participate in the transaction. DataModel dataModel = new DataModel(); DataModelTransaction dataModelTransaction = DataModel.CurrentTransaction; // The simulator parameters need to be locked for each batch of simulated price changes. lock (MarketSimulator.syncRoot) { // The prices are not updated continously because it is trying to simulate a communication interface where a batch of prices is updated // periodically. for (int tick = 0; tick < MarketSimulator.batchSize; tick++) { // This will select a random price in the table and change the price by a random amount. int priceIndex = random.Next(MarketSimulator.dataSetMarket.Price.Rows.Count); DataSetMarket.PriceRow priceRow = MarketSimulator.dataSetMarket.Price[priceIndex]; // 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 quoteTypeIndex = random.Next(Enum.GetValues(typeof(QuoteType)).Length); switch ((QuoteType)Enum.GetValues(typeof(QuoteType)).GetValue(quoteTypeIndex)) { case QuoteType.Ask: priceRow.AskPrice += Convert.ToDecimal(Math.Round(random.NextDouble() - 0.5, 2)); priceRow.AskSize += Convert.ToDecimal(random.Next(1, 10)); break; case QuoteType.Bid: priceRow.BidPrice += Convert.ToDecimal(Math.Round(random.NextDouble() - 0.5, 2)); priceRow.BidSize += Convert.ToDecimal(random.Next(1, 10)); break; case QuoteType.Last: priceRow.LastPrice += Convert.ToDecimal(Math.Round(random.NextDouble() - 0.5, 2)); priceRow.LastSize += Convert.ToDecimal(random.Next(1, 10)); break; } // This does the work of updating the price table using the external identifiers and the newly generated prices. dataModel.CreatePriceEx(priceRow.AskPrice, priceRow.AskSize, priceRow.BidPrice, priceRow.BidSize, 0.0M, priceRow.ConfigurationId, 0.0M, priceRow.LastPrice, priceRow.LastSize, 0.0M, 0.0M, 0.0M, new object[] { priceRow.Currency }, new object[] { priceRow.Symbol }, 0.0M, 0.0M); } // This will commit the changes to the price table. transactionScope.Complete(); } } // This allows other threads the chance to run. Thread.Sleep(MarketSimulator.interval); } }
/// <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); } }