/// <summary> /// Creates an XML Element representing a blockOrder in a execution document. /// </summary> /// <param name="xmlDocument">The destination XML document.</param> /// <param name="blockOrderRow">A blockOrder record.</param> public BlockOrderElement(XmlDocument xmlDocument, ClientMarketData.BlockOrderRow blockOrderRow) : base("BlockOrder", xmlDocument) { // Add the attributes of a blockOrder to this record. AddAttribute("BlockOrderId", blockOrderRow.BlockOrderId.ToString()); // If the security exists, add the basic data to the block information. ClientMarketData.SecurityRow securityRow = blockOrderRow.SecurityRowByFKSecurityBlockOrderSecurityId; AddAttribute("SecurityId", securityRow.SecurityId.ToString()); AddAttribute("SecuritySymbol", securityRow.Symbol); AddAttribute("SecurityName", securityRow.ObjectRow.Name); // It's useful to have the transaction type so we know whether to add fees and commissions or subtract // them from the net calculations. AddAttribute("TransactionTypeCode", blockOrderRow.TransactionTypeCode.ToString()); AddAttribute("TransactionTypeDescription", blockOrderRow.TransactionTypeRow.Description); decimal quantityOrdered = 0.0M; foreach (ClientMarketData.OrderRow orderRow in blockOrderRow.GetOrderRows()) { quantityOrdered += orderRow.Quantity; } AddAttribute("QuantityOrdered", quantityOrdered.ToString()); }
public static void Distribute(int blockOrderId) { // This batch will be filled in with the allocations. RemoteBatch remoteBatch = null; try { // Lock all the tables that we'll reference while building a blotter document. Debug.Assert(!ClientMarketData.AreLocksHeld); ClientMarketData.AllocationLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.BlockOrderLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.ExecutionLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.OrderLock.AcquireReaderLock(CommonTimeout.LockWait); // Find the block order that is to be allocated. ClientMarketData.BlockOrderRow blockOrderRow = ClientMarketData.BlockOrder.FindByBlockOrderId(blockOrderId); if (blockOrderRow == null) { throw new Exception(String.Format("Block Id {0} doesn't exist", blockOrderId)); } // Aggregate the total quantity ordered. This becomes the demoninator for the pro-rata calculation. decimal orderedQuantity = 0.0M; foreach (ClientMarketData.OrderRow orderRow in blockOrderRow.GetOrderRows()) { orderedQuantity += orderRow.Quantity; } // These values will total up all the executions posted against this block order. The pro-rata // allocation will divide all the executions up against the ratio of the quantity ordered against the // total quantity ordered. The price is an average price of all the executions. decimal executedQuantity = 0.0M; decimal executedPrice = 0.0M; decimal executedCommission = 0.0M; decimal executedAccruedInterest = 0.0M; decimal executedUserFee0 = 0.0M; decimal executedUserFee1 = 0.0M; decimal executedUserFee2 = 0.0M; decimal executedUserFee3 = 0.0M; DateTime tradeDate = DateTime.MinValue; DateTime settlementDate = DateTime.MinValue; // Total up all the executions against this block. foreach (ClientMarketData.ExecutionRow executionRow in blockOrderRow.GetExecutionRows()) { executedQuantity += executionRow.Quantity; executedPrice += executionRow.Price * executionRow.Quantity; executedCommission += executionRow.Commission; executedAccruedInterest += executionRow.AccruedInterest; executedUserFee0 += executionRow.UserFee0; executedUserFee1 += executionRow.UserFee1; executedUserFee2 += executionRow.UserFee2; executedUserFee3 += executionRow.UserFee3; tradeDate = executionRow.TradeDate; settlementDate = executionRow.SettlementDate; } // Calculate the average price. decimal averagePrice = Math.Round((executedQuantity > 0.0M) ? executedPrice / executedQuantity : 0.0M, 2); // These values are used to keep track of how much has been allocated. Because of the nature of a // pro-rata allocation, there will be an odd remainder after the allocation is finished. We will // arbitrarily pick the last order in the block order and give it the remainer of the order. To do that, // totals have to be kept of all the allocations that have been created before the last one. decimal allocatedQuantity = 0.0M; decimal allocatedCommission = 0.0M; decimal allocatedAccruedInterest = 0.0M; decimal allocatedUserFee0 = 0.0M; decimal allocatedUserFee1 = 0.0M; decimal allocatedUserFee2 = 0.0M; decimal allocatedUserFee3 = 0.0M; // Put all the allocations in a single batch. remoteBatch = new RemoteBatch(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Core"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Core.Allocation"); // Allocate the order back to the original accounts. Because odd values can be generated when dividing by the // pro-rata value, the last order will get the remaining portions of the execution. int orderCounter = blockOrderRow.GetOrderRows().Length; foreach (ClientMarketData.OrderRow orderRow in blockOrderRow.GetOrderRows()) { decimal quantity; decimal commission; decimal accruedInterest; decimal userFee0; decimal userFee1; decimal userFee2; decimal userFee3; // The last account in the order is arbitrarily given the remaining parts of the execution. if (--orderCounter == 0) { quantity = executedQuantity - allocatedQuantity; commission = executedCommission - allocatedCommission; accruedInterest = executedAccruedInterest = allocatedAccruedInterest; userFee0 = executedUserFee0 - allocatedUserFee0; userFee1 = executedUserFee1 - allocatedUserFee1; userFee2 = executedUserFee2 - allocatedUserFee2; userFee3 = executedUserFee3 - allocatedUserFee3; } else { // Calcuation the proportion of the trade destined for the current order. The proportion is // based on the amount of the original order against the block total. quantity = Math.Round(executedQuantity * orderRow.Quantity / orderedQuantity, 0); commission = Math.Round(executedCommission * orderRow.Quantity / orderedQuantity, 2); accruedInterest = Math.Round(executedAccruedInterest * orderRow.Quantity / orderedQuantity, 2); userFee0 = Math.Round(executedUserFee0 * orderRow.Quantity / orderedQuantity, 2); userFee1 = Math.Round(executedUserFee1 * orderRow.Quantity / orderedQuantity, 2); userFee2 = Math.Round(executedUserFee2 * orderRow.Quantity / orderedQuantity, 2); userFee3 = Math.Round(executedUserFee3 * orderRow.Quantity / orderedQuantity, 2); } // Keep a running total of the amount allocated so far. This will be used to calculate the last order's // portion when the loop has finished. allocatedQuantity += quantity; allocatedCommission += commission; allocatedAccruedInterest += accruedInterest; allocatedUserFee0 += userFee0; allocatedUserFee1 += userFee1; allocatedUserFee2 += userFee2; allocatedUserFee3 += userFee3; // If the allocation has a positive quantity to register, then post it to the server. if (quantity > 0.0M) { // Call the web service to add the new execution. RemoteMethod remoteMethod = remoteType.Methods.Add("Insert"); remoteMethod.Parameters.Add("blockOrderId", orderRow.BlockOrderId); remoteMethod.Parameters.Add("accountId", orderRow.AccountId); remoteMethod.Parameters.Add("securityId", orderRow.SecurityId); remoteMethod.Parameters.Add("settlementId", orderRow.SettlementId); remoteMethod.Parameters.Add("positionTypeCode", orderRow.PositionTypeCode); remoteMethod.Parameters.Add("transactionTypeCode", orderRow.TransactionTypeCode); remoteMethod.Parameters.Add("quantity", quantity); remoteMethod.Parameters.Add("price", averagePrice); remoteMethod.Parameters.Add("commission", commission); remoteMethod.Parameters.Add("accruedInterest", accruedInterest); remoteMethod.Parameters.Add("userFee0", userFee0); remoteMethod.Parameters.Add("userFee1", userFee1); remoteMethod.Parameters.Add("userFee2", userFee2); remoteMethod.Parameters.Add("userFee3", userFee3); remoteMethod.Parameters.Add("tradeDate", tradeDate); remoteMethod.Parameters.Add("settlementDate", settlementDate); remoteMethod.Parameters.Add("createdTime", DateTime.Now); remoteMethod.Parameters.Add("createdLoginId", ClientPreferences.LoginId); remoteMethod.Parameters.Add("modifiedTime", DateTime.Now); remoteMethod.Parameters.Add("modifiedLoginId", ClientPreferences.LoginId); } } } catch (Exception exception) { // This signals that the batch isn't valid and shouldn't be sent. remoteBatch = null; // This will catch all remaining exceptions. Debug.WriteLine(exception.Message); } finally { // Release the locks obtained to produce the blotter report. if (ClientMarketData.AllocationLock.IsReaderLockHeld) { ClientMarketData.AllocationLock.ReleaseReaderLock(); } if (ClientMarketData.BlockOrderLock.IsReaderLockHeld) { ClientMarketData.BlockOrderLock.ReleaseReaderLock(); } if (ClientMarketData.ExecutionLock.IsReaderLockHeld) { ClientMarketData.ExecutionLock.ReleaseReaderLock(); } if (ClientMarketData.OrderLock.IsReaderLockHeld) { ClientMarketData.OrderLock.ReleaseReaderLock(); } Debug.Assert(!ClientMarketData.AreLocksHeld); } // Once the locks are release, the batch can be sent to the server. if (remoteBatch != null) { ClientMarketData.Send(remoteBatch); } }