/// <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> /// 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> /// 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> /// 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 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> /// 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> /// 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); } }
/// <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); } }