/// <summary> /// Execute the remote batch and catch any batch errors. /// </summary> /// <param name="remoteBatch"></param> public static void Send(RemoteBatch remoteBatch) { // The top level window of the application is needed as the owner of the error message dialog box that will pop up to // process the errors. System.Windows.Forms.Control activeForm = Form.ActiveForm; System.Windows.Forms.Control topLevelControl = activeForm == null ? null : activeForm.TopLevelControl; try { // Execute the remote batch. Execute(remoteBatch); } catch (BatchException batchException) { // Display each error from the batch until the user hits the 'Cancel' button, or until they are all displayed. foreach (RemoteException remoteException in batchException.Exceptions) { DialogResult dialogResult = MessageBox.Show(topLevelControl, remoteException.Message, "Quasar Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Error); if (dialogResult == DialogResult.Cancel) { break; } } } catch (Exception exception) { // Write the error and stack trace out to the debug listener Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace)); } }
/// <summary> /// Sends a batch of commands to the server and processes the results. /// </summary> private static void SendBatch() { // Create a new web client that will serve as the connection point to the server. WebClient webClient = new WebClient(); webClient.Timeout = TableLoader.timeout; // Call the web services to execute the command batch. remoteBatch.Merge(webClient.Execute(remoteBatch)); // Any error during the batch will terminate the execution of the remaining records in the data file. if (remoteBatch.HasExceptions) { // Display each error on the console. foreach (RemoteException remoteException in remoteBatch.Exceptions) { Console.WriteLine(remoteException.Message); } // This will signal the error exit from this loader. hasErrors = true; } // Clearing out the batch indicates that a new header should be created for the next set of commands. remoteBatch = null; remoteTransaction = null; remoteAssembly = null; remoteType = null; }
/// <summary> /// Load and execute a command batch. /// </summary> /// <param name="rootNode">The root node of the Xml Data structure that contains the command.</param> private static void LoadCommand(XmlNode rootNode) { // Read each of the transactions and send them to the server. foreach (XmlNode transactionNode in rootNode.SelectNodes("transaction")) { // Ignore Comment Nodes. if (transactionNode.NodeType == XmlNodeType.Comment) { continue; } // Each transaction in the batch is handled as a unit. That is, everything between the start and end <Transaction> // tags will be executed or rolled back as a unit. remoteBatch = new RemoteBatch(); remoteTransaction = remoteBatch.Transactions.Add(); remoteAssembly = null; remoteType = null; // The <Assembly> tag specifies the name of an assembly where the object and methods are found. foreach (XmlNode assemblyNode in transactionNode.SelectNodes("assembly")) { LoadAssembly(assemblyNode, 0); } // Once the entire transaction has been loaded, send it off to the server. SendBatch(); } // If the batch file doesn't contain explicit transactions, then implicit ones are assumed and the processing of the file // continues with the 'assembly' nodes. XmlNodeList assemblyNodes = rootNode.SelectNodes("assembly"); if (assemblyNodes.Count != 0) { // This will tell the 'LoadAssembly' to determine the size of the batch from the number of records processed. remoteBatch = null; remoteTransaction = null; remoteAssembly = null; remoteType = null; // If an assembly is included outside of a Transaction, then the transaction is implicit and the size of the // transaction is the 'batchSize' parameter. foreach (XmlNode assemblyNode in assemblyNodes) { // Load the assebly node ninto the batch. LoadAssembly(assemblyNode, batchSize); // There will be records in the batch when the above method returns (unless there are exactly as many records // in the batch as the batch size). This will send the remaining records to the server. if (remoteBatch != null) { SendBatch(); } } } }
/// <summary> /// Returns a set of orders that will achieve the targets specified by the model. /// </summary> /// <param name="accountRow">The account or parent account to be rebalanced.</param> /// <param name="modelRow">The target percentages to use for rebalancing.</param> /// <returns>A Dataset of new, updated and deleted orders.</returns> public static RemoteBatch Rebalance(ClientMarketData.AccountRow accountRow, ClientMarketData.ModelRow modelRow) { // The orders to insert, update and delete orders to achieve the target percentages will be put in this DataSet. RemoteBatch remoteBatch = new RemoteBatch(); RemoteTransaction remoteTransaction = remoteBatch.Transactions.Add(); // Rebalance the parent account and all it's children. SelectedSecurity.RecurseAccounts(remoteBatch, remoteTransaction, accountRow, modelRow); // This is the sucessful result of rebalancing. return(remoteBatch); }
/// <summary> /// Creates a block order. /// </summary> /// <param name="configurationId">Defines which external fields are used to identify an object.</param> /// <param name="blotterId">The destination blotter for the trade.</param> /// <param name="securityId">The security.</param> /// <param name="transactionType">The type of transaction.</param> public BlockOrder(Blotter blotter, Security security, TransactionType transactionType) { // Create a block order on the server. RemoteBatch remoteBatch = new RemoteBatch(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Trading"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Trading.BlockOrder"); RemoteMethod remoteMethod = remoteType.Methods.Add("Insert"); remoteMethod.Parameters.Add("blockOrderId", DataType.Int, Direction.ReturnValue); remoteMethod.Parameters.Add("blotterId", blotter.BlotterId); remoteMethod.Parameters.Add("securityId", security.SecurityId); remoteMethod.Parameters.Add("settlementId", security.SettlementId); remoteMethod.Parameters.Add("transactionTypeCode", (int)transactionType); ClientMarketData.Execute(remoteBatch); // Now that the block order is created, construct the in-memory version of the record. int blockOrderId = (int)remoteMethod.Parameters["blockOrderId"].Value; try { // Lock the tables. Debug.Assert(!ClientMarketData.AreLocksHeld); ClientMarketData.BlockOrderLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.ObjectLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.SecurityLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.BlockOrderRow blockOrderRow = ClientMarketData.BlockOrder.FindByBlockOrderId(blockOrderId); if (blockOrderRow == null) throw new Exception(String.Format("BlockOrder {0} doesn't exist", blockOrderId)); this.blockOrderId = blockOrderRow.BlockOrderId; this.security = Security.Make(blockOrderRow.SecurityRowByFKSecurityBlockOrderSecurityId.SecurityId); this.settlement = Security.Make(blockOrderRow.SecurityRowByFKSecurityBlockOrderSettlementId.SecurityId); this.transactionType = (TransactionType)blockOrderRow.TransactionTypeCode; } finally { // Release the table locks. if (ClientMarketData.BlockOrderLock.IsReaderLockHeld) ClientMarketData.BlockOrderLock.ReleaseReaderLock(); if (ClientMarketData.ObjectLock.IsReaderLockHeld) ClientMarketData.ObjectLock.ReleaseReaderLock(); if (ClientMarketData.SecurityLock.IsReaderLockHeld) ClientMarketData.SecurityLock.ReleaseReaderLock(); Debug.Assert(!ClientMarketData.AreLocksHeld); } }
public static void Flush() { if (CommandBatch.remoteBatch.RemoteMethod.Rows.Count > 0) { RemoteBatch currentBatch = null; lock (typeof(CommandBatch)) { currentBatch = CommandBatch.remoteBatch; CommandBatch.remoteBatch = new RemoteBatch(); } new WorkerThread(new ThreadHandler(FlushThread), "Command Batch Execution", currentBatch); } }
/// <summary> /// Executes a block order. /// </summary> /// <param name="configurationId">Defines which external fields are used to identify an object.</param> /// <param name="brokerId">The destination broker for the order.</param> /// <param name="tif">Specifies a time limit on the order.</param> /// <param name="quantity">The number of units to be traded.</param> /// <param name="pricedAt">Specifies how the order is to be priced.</param> /// <param name="limitPrice">A limit price for the order.</param> public void Execute(Broker broker, TIF tif, decimal quantity, PricedAt pricedAt, decimal limitPrice) { RemoteBatch remoteBatch = new RemoteBatch(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Trading"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Trading.BlockOrder"); RemoteMethod remoteMethod = remoteType.Methods.Add("Execute"); remoteMethod.Parameters.Add("blockOrderId", this.blockOrderId); remoteMethod.Parameters.Add("brokerId", broker.BrokerId); remoteMethod.Parameters.Add("timeInForceCode", (int)tif); remoteMethod.Parameters.Add("quantity", quantity); remoteMethod.Parameters.Add("orderTypeCode", (int)pricedAt); remoteMethod.Parameters.Add("price1", limitPrice); ClientMarketData.Execute(remoteBatch); }
public Restriction(string restrictionId, Severity severity, Approval approval, string description) { RemoteBatch remoteBatch = new RemoteBatch(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Core"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Core.Restriction"); RemoteMethod remoteMethod = remoteType.Methods.Add("Insert"); remoteMethod.Parameters.Add("internalRestrictionId", DataType.Int, Direction.ReturnValue); remoteMethod.Parameters.Add("severity", (int)severity); remoteMethod.Parameters.Add("approval", (int)approval); remoteMethod.Parameters.Add("description", description); remoteMethod.Parameters.Add("userId0", restrictionId); ClientMarketData.Send(remoteBatch); Initialize((int)remoteMethod.Parameters["internalRestrictionId"].Value); }
/// <summary> /// Returns a set of orders that will achieve the targets specified by the model. /// </summary> /// <param name="accountRow">The account or parent account to be rebalanced.</param> /// <param name="modelRow">The target percentages to use for rebalancing.</param> /// <returns>A Dataset of new, updated and deleted orders.</returns> public static RemoteBatch Rebalance(ClientMarketData.AccountRow accountRow, ClientMarketData.ModelRow modelRow) { // The orders to insert, update and delete orders to achieve the target percentages will be put in this // DataSet. RemoteBatch remoteBatch = new RemoteBatch(); RemoteTransaction remoteTransaction = remoteBatch.Transactions.Add(); // The outline of the appraisal will be needed to make calculations based on a position, that is a security, // account, position type combination. Note that we're also including all the model securities in the // outline. This triggers a rebalance if a security exists in the model, but doesn't exist yet in the // appraisal. AppraisalSet appraisalSet = new Appraisal(accountRow, modelRow, true); // Rebalance the parent account and all it's children. Security.RecurseAccounts(remoteBatch, remoteTransaction, appraisalSet, accountRow, modelRow); // This is the sucessful result of rebalancing. return(remoteBatch); }
/// <summary> /// Returns a set of orders that will achieve the targets specified by the model. /// </summary> /// <param name="accountRow">The account or parent account to be rebalanced.</param> /// <param name="modelRow">The target percentages to use for rebalancing.</param> /// <returns>A Dataset of new, updated and deleted orders.</returns> public static RemoteBatch Rebalance(ClientMarketData.AccountRow accountRow, ClientMarketData.ModelRow modelRow) { // Make sure the scheme still exists in the in-memory database. We need it to rebalance the appraisal. ClientMarketData.SchemeRow schemeRow; if ((schemeRow = ClientMarketData.Scheme.FindBySchemeId(modelRow.SchemeId)) == null) { throw new ArgumentException("Scheme doesn't exist in the ClientMarketData", modelRow.SchemeId.ToString()); } // The final result of this method is a command batch that can be sent to the server. RemoteBatch remoteBatch = new RemoteBatch(); RemoteTransaction remoteTransaction = remoteBatch.Transactions.Add(); // Rebalance the parent account and all it's children. RecurseAccounts(remoteBatch, remoteTransaction, accountRow, modelRow, schemeRow); // The sucessful result of rebalancing. return(remoteBatch); }
/// <summary> /// Adds an order to a block order. /// </summary> /// <param name="configurationId">Defines which external fields are used to identify an object.</param> /// <param name="accountId">The destination account for the order.</param> /// <param name="tif">Specifies a time limit on the order.</param> /// <param name="quantity">The number of units to be traded.</param> /// <returns>An internal identifier used to track the order.</returns> public int AddOrder(Account account, TIF tif, decimal quantity) { RemoteBatch remoteBatch = new RemoteBatch(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Trading"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Trading.Order"); RemoteMethod remoteMethod = remoteType.Methods.Add("Insert"); remoteMethod.Parameters.Add("orderId", DataType.Int, Direction.ReturnValue); remoteMethod.Parameters.Add("blockOrderId", this.blockOrderId); remoteMethod.Parameters.Add("accountId", account.AccountId); remoteMethod.Parameters.Add("securityId", this.Security.SecurityId); remoteMethod.Parameters.Add("settlementId", this.Settlement.SecurityId); remoteMethod.Parameters.Add("timeInForceCode", (int)tif); remoteMethod.Parameters.Add("transactionTypeCode", (int)this.transactionType); remoteMethod.Parameters.Add("orderTypeCode", (int)PricedAt.Market); remoteMethod.Parameters.Add("quantity", quantity); ClientMarketData.Execute(remoteBatch); return (int)remoteMethod.Parameters["orderId"].Value; }
/// <summary> /// Recursively creates instructions to delete proposed order of the given position. /// </summary> /// <param name="remoteBatch">The object type containing the method to delete the order relationship.</param> /// <param name="remoteTransaction">Groups several commands into a unit for execution.</param> /// <param name="accountRow">An account record, used to select proposed order records.</param> private static void Delete(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, ClientMarketData.AccountRow accountRow, ClientMarketData.SecurityRow securityRow, int positionTypeCode) { // Run through each of the proposed orders in this account and create a record to have them deleted. object[] key = new object[] { accountRow.AccountId, securityRow.SecurityId, positionTypeCode }; foreach (DataRowView dataRowView in MarketData.ProposedOrder.UKProposedOrderAccountIdSecurityIdPositionTypeCode.FindRows(key)) { // This is used to reference the current proposed order that matches the position criteria. ClientMarketData.ProposedOrderRow proposedOrderRow = (ClientMarketData.ProposedOrderRow)dataRowView.Row; // Child proposed orders aren't deleted directly, they can only be deleted when the parent is deleted. The best // example of this is cash. An account can have both child cash (related to an equity trade) or parent cash (cash // added directly to the account with no offsetting trade). If a reqest is made to delete cash, only the parent // cash should be deleted. The account will appear to have a cash balance until the equity attached to the child // cash is deleted. if (!Relationship.IsChildProposedOrder(proposedOrderRow)) { Delete(remoteBatch, remoteTransaction, proposedOrderRow); } } }
/// <summary> /// Creates a batch command to delete a proposed order and it's links. /// </summary> /// <param name="remoteBatch"></param> /// <param name="remoteTransaction"></param> /// <param name="parentProposedOrder"></param> public static void Delete(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, ClientMarketData.ProposedOrderRow parentProposedOrder) { // These define the assembly and the types within those assemblies that will be used to create the proposed orders on // the middle tier. RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Core"); RemoteType proposedOrderType = remoteAssembly.Types.Add("Shadows.WebService.Core.ProposedOrder"); RemoteType proposedOrderTreeType = remoteAssembly.Types.Add("Shadows.WebService.Core.ProposedOrderTree"); // Proposed orders have a hierarchy. For example, orders for equities will be linked to foreach (ClientMarketData.ProposedOrderTreeRow proposedOrderTree in parentProposedOrder.GetProposedOrderTreeRowsByFKProposedOrderProposedOrderTreeParentId()) { // Create a command to delete the relationship between the parent and child. RemoteMethod deleteRelation = proposedOrderTreeType.Methods.Add("Delete"); deleteRelation.Transaction = remoteTransaction; deleteRelation.Parameters.Add("rowVersion", proposedOrderTree.RowVersion); deleteRelation.Parameters.Add("parentId", proposedOrderTree.ParentId); deleteRelation.Parameters.Add("childId", proposedOrderTree.ChildId); // This relatioship will give us access to the child proposed order. ClientMarketData.ProposedOrderRow childProposedOrder = proposedOrderTree.ProposedOrderRowByFKProposedOrderProposedOrderTreeChildId; // Create a command to delete the child proposed order. RemoteMethod deleteChild = proposedOrderType.Methods.Add("Delete"); deleteChild.Transaction = remoteTransaction; deleteChild.Parameters.Add("rowVersion", childProposedOrder.RowVersion); deleteChild.Parameters.Add("proposedOrderId", childProposedOrder.ProposedOrderId); } // Create a command to delete the parent proposed order. RemoteMethod deleteParent = proposedOrderType.Methods.Add("Delete"); deleteParent.Transaction = remoteTransaction; deleteParent.Parameters.Add("rowVersion", parentProposedOrder.RowVersion); deleteParent.Parameters.Add("proposedOrderId", parentProposedOrder.ProposedOrderId); }
private static void Update(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, ClientMarketData.ProposedOrderRow parentProposedOrder, decimal quantityInstruction) { // These define the assembly and the types within those assemblies that will be used to create the proposed orders on // the middle tier. RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Core"); RemoteType proposedOrderType = remoteAssembly.Types.Add("Shadows.WebService.Core.ProposedOrder"); ClientMarketData.AccountRow accountRow = parentProposedOrder.AccountRow; ClientMarketData.SecurityRow securityRow = parentProposedOrder.SecurityRowByFKSecurityProposedOrderSecurityId; // This will turn the signed quantity into an absolute quantity and a transaction code (e.g. -1000 is turned into a // SELL of 1000 shares). decimal parentQuantity = Math.Abs(quantityInstruction); int parentTransactionTypeCode = TransactionType.Calculate(securityRow.SecurityTypeCode, parentProposedOrder.PositionTypeCode, quantityInstruction); // The time in force first comes from the user preferences, next, account settings and finally defaults to a day // orders. int timeInForceCode = !ClientPreferences.IsTimeInForceCodeNull() ? ClientPreferences.TimeInForceCode : !accountRow.IsTimeInForceCodeNull() ? accountRow.TimeInForceCode : TimeInForce.DAY; // The destination blotter comes first from the user preferences, second from the account preferences, and finally uses // the auto-routing logic. int blotterId = !ClientPreferences.IsBlotterIdNull() ? ClientPreferences.BlotterId : !accountRow.IsBlotterIdNull() ? accountRow.BlotterId : TradingSupport.AutoRoute(securityRow, parentQuantity); // Create a command to update the proposed order. RemoteMethod updateParent = proposedOrderType.Methods.Add("Update"); updateParent.Transaction = remoteTransaction; updateParent.Parameters.Add("rowVersion", parentProposedOrder.RowVersion); updateParent.Parameters.Add("proposedOrderId", parentProposedOrder.ProposedOrderId); updateParent.Parameters.Add("accountId", parentProposedOrder.AccountId); updateParent.Parameters.Add("securityId", parentProposedOrder.SecurityId); updateParent.Parameters.Add("settlementId", parentProposedOrder.SettlementId); updateParent.Parameters.Add("blotterId", blotterId); updateParent.Parameters.Add("positionTypeCode", parentProposedOrder.PositionTypeCode); updateParent.Parameters.Add("transactionTypeCode", parentTransactionTypeCode); updateParent.Parameters.Add("timeInForceCode", timeInForceCode); updateParent.Parameters.Add("orderTypeCode", OrderType.Market); updateParent.Parameters.Add("quantity", parentQuantity); foreach (ClientMarketData.ProposedOrderTreeRow proposedOrderTree in parentProposedOrder.GetProposedOrderTreeRowsByFKProposedOrderProposedOrderTreeParentId()) { ClientMarketData.ProposedOrderRow childProposedOrder = proposedOrderTree.ProposedOrderRowByFKProposedOrderProposedOrderTreeChildId; // If this is the settlement part of the order, then adjust the quantity. if (childProposedOrder.SecurityId == parentProposedOrder.SettlementId) { // The settlement security is needed for the calculation of the cash impact of this trade. ClientMarketData.CurrencyRow currencyRow = MarketData.Currency.FindByCurrencyId(childProposedOrder.SettlementId); decimal marketValue = parentQuantity * securityRow.QuantityFactor * Price.Security(currencyRow, securityRow) * securityRow.PriceFactor * TransactionType.GetCashSign(parentTransactionTypeCode); decimal childQuantity = Math.Abs(marketValue); int childTransactionTypeCode = TransactionType.Calculate(securityRow.SecurityTypeCode, parentProposedOrder.PositionTypeCode, marketValue); // Create a command to update the proposed order. RemoteMethod updateChild = proposedOrderType.Methods.Add("Update"); updateChild.Transaction = remoteTransaction; updateChild.Parameters.Add("rowVersion", childProposedOrder.RowVersion); updateChild.Parameters.Add("proposedOrderId", childProposedOrder.ProposedOrderId); updateChild.Parameters.Add("accountId", childProposedOrder.AccountId); updateChild.Parameters.Add("securityId", childProposedOrder.SecurityId); updateChild.Parameters.Add("settlementId", childProposedOrder.SettlementId); updateChild.Parameters.Add("blotterId", blotterId); updateChild.Parameters.Add("positionTypeCode", parentProposedOrder.PositionTypeCode); updateChild.Parameters.Add("transactionTypeCode", childTransactionTypeCode); updateChild.Parameters.Add("timeInForceCode", timeInForceCode); updateChild.Parameters.Add("orderTypeCode", OrderType.Market); updateChild.Parameters.Add("quantity", childQuantity); } } }
/// <summary> /// Load up an assembly section of a batch. /// </summary> /// <param name="assemblyNode"></param> private static void LoadAssembly(XmlNode assemblyNode, int batchSize) { // Ignore Comment Nodes. if (assemblyNode.NodeType == XmlNodeType.Comment) { return; } // Add an Assembly specifier to the batch. This essentially describes the DLL where the methods are found. string assemblyName = assemblyNode.Attributes["name"].Value; // Each assembly can have one or more Objects (or Types) which can be instantiated. The batch command // processing can call static methods belonging to the instantiated object. foreach (XmlNode typeNode in assemblyNode.SelectNodes("type")) { // Ignore Comment Nodes. if (typeNode.NodeType == XmlNodeType.Comment) { continue; } // Loading the database involves creating a batch of commands and sending them off as a transaction. // This gives the server a chance to pipeline a large chunk of processing, without completely locking // up the server for the entire set of data. This will construct a header for the command batch which // gives information about which assembly contains the class that is used to load the data. string typeName = typeNode.Attributes["name"].Value; // Attach each method and it's parameters to the command batch. foreach (XmlNode methodNode in typeNode.SelectNodes("method")) { // Ignore Comment Nodes. if (methodNode.NodeType == XmlNodeType.Comment) { continue; } // The 'remoteBatch' will be cleared after it is sent to the server. Make sure a batch exists before adding // methods and parameters to it. if (remoteBatch == null) { // Loading the database involves creating a batch of commands and sending them off as a transaction. This // gives the server a chance to pipeline a large chunk of processing, without completely locking up the // server for the entire set of data. This will create a batch for the next bunch of commands. remoteBatch = new RemoteBatch(); remoteTransaction = remoteBatch.Transactions.Add(); // This counts the number of records placed in the batch. batchCounter = 0; } if (remoteAssembly == null || remoteAssembly.Name != assemblyName) { remoteAssembly = remoteBatch.Assemblies.Add(assemblyName); } if (remoteType == null || remoteType.Name != typeName) { remoteType = remoteAssembly.Types.Add(typeName); } // Each method is part of the transaction defined by the tagged outline structure of the input // file. RemoteMethod remoteMethod = remoteType.Methods.Add(methodNode.Attributes["name"].Value); remoteMethod.Transaction = remoteTransaction; // Load each of the parameters into the method structure. foreach (XmlNode parameterNode in methodNode.ChildNodes) { // Ignore Comment Nodes. if (parameterNode.NodeType == XmlNodeType.Comment) { continue; } // Add the next parameter to the method. remoteMethod.Parameters.Add(parameterNode.Name, parameterNode.InnerText); } // One more record for the grand total, one more record for the number in the current batch. recordCounter++; batchCounter++; // This will check to see if it's time to send the batch. A batch is sent when the 'batchSize' has been // reached, or if the last record has just been converted into a command. if (batchSize != 0 && recordCounter % batchSize == 0) { SendBatch(); } } } }
/// <summary> /// Fills the OrderForm table with instructions to create, delete or update proposed orders. /// </summary> /// <param name="accountId">Identifiers the destination account of the proposed order.</param> /// <param name="securityId">Identifies the security being trade.</param> /// <param name="positionTypeCode">Identifies the long or short position of the trade.</param> /// <param name="settlementId"></param> /// <param name="proposedQuantity">The signed (relative) quantity of the trade.</param> public static void Create(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, ClientMarketData.AccountRow accountRow, ClientMarketData.SecurityRow securityRow, int positionTypeCode, decimal proposedQuantity) { // If the proposed quantity is to be zero, we'll delete all proposed orders for this position in the parent and // descendant accounts. Otherwise, a command batch will be created to clear any child proposed orders and create or // update a proposed order for the parent account. if (proposedQuantity == 0.0M) { ProposedOrder.Delete(remoteBatch, remoteTransaction, accountRow, securityRow, positionTypeCode); } else { // The strategy here is to cycle through all the existing proposed orders looking for any that match the account // id, security id and position type of the new order. If none is found, we create a new order. If one is found, // we modify it for the new quantity. Any additional proposed orders are deleted. This flag lets us know if any // existing proposed orders match the position attributes. bool firstTime = true; // Cycle through each of the proposed orders in the given account looking for a matching position. object[] key = new object[] { accountRow.AccountId, securityRow.SecurityId, positionTypeCode }; foreach (DataRowView dataRowView in ClientMarketData.ProposedOrder.UKProposedOrderAccountIdSecurityIdPositionTypeCode.FindRows(key)) { // This is used to reference the current proposed order that matches the position criteria. ClientMarketData.ProposedOrderRow parentProposedOrderRow = (ClientMarketData.ProposedOrderRow)dataRowView.Row; // This check is provided for currency-like assets. There may be many proposed orders for currency // transactions that are used to settle other trades. The user can also enter currency orders directly into // the appraisal. Any manual deposits or withdrawls should not impact settlement orders. This check will skip // any trade that is linked to another order. if (Shadows.Quasar.Common.Relationship.IsChildProposedOrder(parentProposedOrderRow)) { continue; } // Recycle the first proposed order that matches the position criteria. Any additional proposed orders for the // same account, security, position type will be deleted. if (firstTime) { // Any proposed orders found after this one will be deleted. This variable will also indicate that an // existing proposed order was recycled. After the loop is run on this position, a new order will be // created if an existing order couldn't be recycled. firstTime = false; // Create the command to update this proposed order. Update(remoteBatch, remoteTransaction, parentProposedOrderRow, proposedQuantity); } else { // Any order that isn't recycled is considered to be redundant. That is, this order has been superceded by // the recycled order. Clearing any redundant orders makes the operation more intuitive: the user knows // that the only order on the books is the one they entered. They don't have to worry about artifacts from // other operations. Delete(remoteBatch, remoteTransaction, parentProposedOrderRow); } } // This will create a new proposed order if an existing one couldn't be found above for recycling. if (firstTime == true) { Insert(remoteBatch, remoteTransaction, accountRow, securityRow, positionTypeCode, proposedQuantity); } } }
static int Main(string[] args) { // If this flag is set during the processing of the file, the program will exit with an error code. bool hasErrors = false; try { // Defaults assemblyName = "Service.External"; namespaceName = "Shadows.WebService.External"; externalConfigurationCode = "DEFAULT"; argumentState = ArgumentState.FileName; // Parse the command line for arguments. foreach (string argument in args) { // Decode the current argument into a state. if (argument == "-c") { argumentState = ArgumentState.ConfigurationCode; continue; } if (argument == "-i") { argumentState = ArgumentState.FileName; continue; } if (argument == "-id") { argumentState = ArgumentState.StylesheetId; continue; } if (argument == "-t") { argumentState = ArgumentState.StylesheetTypeCode; continue; } // The parsing state will determine which variable is read next. switch (argumentState) { // Read the command line argument into the proper variable based on the parsing state. case ArgumentState.ConfigurationCode: externalConfigurationCode = argument; break; case ArgumentState.StylesheetTypeCode: stylesheetTypeCode = argument; break; case ArgumentState.FileName: fileName = argument; break; case ArgumentState.Name: name = argument; break; case ArgumentState.StylesheetId: StylesheetId = argument; break; } // The default state for the parser is to look for a file name. argumentState = ArgumentState.FileName; } // If no file name was specified, we return an error. if (fileName == null) { throw new Exception("Usage: Loader.Stylesheet -i <FileName>"); } // Load up an XML document with the contents of the file specified on the command line. XmlDocument stylesheet = new XmlDocument(); stylesheet.Load(fileName); // The XML document has several nodes that need to be read -- and then removed -- that contain attributes of the // stylesheet. These nodes can be found easily using the XSL Path functions which need a namespace manager to sort // out the tag prefixes. XmlNamespaceManager namespaceManager = new XmlNamespaceManager(stylesheet.NameTable); namespaceManager.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform"); namespaceManager.AddNamespace("sss", "urn:schemas-shadows-com:shadows:stylesheet"); // The spreadhseet source has several nodes which contain information about how the data in the XML document should // be loaded into the server, such as the stylesheet identifier, the name and the stylesheet style. They are found // at this node. After these information nodes have been read, they are removed from the stylesheet source. XmlNode stylesheetNode = stylesheet.SelectSingleNode("xsl:stylesheet", namespaceManager); if (stylesheetNode == null) { throw new Exception("Syntax Error: missing stylesheet declaration."); } // Find the StylesheetId node. XmlNode StylesheetIdNode = stylesheetNode.SelectSingleNode("sss:stylesheetId", namespaceManager); if (StylesheetIdNode == null) { throw new Exception("Syntax Error: missing StylesheetId declaration."); } // Find the StylesheetStyle node. XmlNode stylesheetTypeCodeNode = stylesheetNode.SelectSingleNode("sss:stylesheetTypeCode", namespaceManager); if (stylesheetTypeCodeNode == null) { throw new Exception("Syntax Error: missing StylesheetStyle declaration."); } // Find the name node. XmlNode nameNode = stylesheetNode.SelectSingleNode("sss:name", namespaceManager); if (nameNode == null) { throw new Exception("Syntax Error: missing name declaration."); } // Extract the data from the XML nodes. StylesheetId = StylesheetIdNode.InnerText; stylesheetTypeCode = stylesheetTypeCodeNode.InnerText; name = nameNode.InnerText; // Remove the stylesheet nodes from the XSL spreadsheet before loading it into the server. stylesheetNode.RemoveChild(StylesheetIdNode); stylesheetNode.RemoveChild(stylesheetTypeCodeNode); stylesheetNode.RemoveChild(nameNode); // Create a command to load the stylesheet from the data loaded from the file. RemoteBatch remoteBatch = new RemoteBatch(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add(assemblyName); RemoteType remoteType = remoteAssembly.Types.Add(string.Format("{0}.{1}", namespaceName, "Stylesheet")); RemoteMethod remoteMethod = remoteType.Methods.Add("Load"); remoteMethod.Parameters.Add("StylesheetId", StylesheetId); remoteMethod.Parameters.Add("stylesheetTypeCode", stylesheetTypeCode); remoteMethod.Parameters.Add("name", name); remoteMethod.Parameters.Add("text", stylesheet.InnerXml); // Create a new web client that will serve as the connection point to the server and call the web services to // execute the command batch. WebClient webClient = new WebClient(); remoteBatch.Merge(webClient.Execute(remoteBatch)); // Display the each of the exceptions and set a global flag that shows that there was an exception to the normal // execution. if (remoteBatch.HasExceptions) { foreach (RemoteException exception in remoteBatch.Exceptions) { Console.WriteLine(String.Format("{0}: {1}", remoteMethod.Parameters["StylesheetId"].Value, exception.Message)); } hasErrors = true; } } catch (Exception exception) { // Show the system error and exit with an error. Console.WriteLine(exception.Message); hasErrors = true; } // Any errors will cause an abnormal exit. if (hasErrors) { return(1); } // Display the template that was loaded and exit with a successful code. Console.WriteLine(String.Format("{0} Stylesheet: {1}, Loaded", DateTime.Now.ToString("u"), name)); return(0); }
/// <summary> /// Rebalances an AppraisalModelSet to sector targets. The model is applied to the aggregate market value of the /// account and it's children. /// </summary> /// <param name="accountId">The parent account to be rebalanced.</param> /// <param name="modelId">The sector model to be used.</param> /// <returns>A set of proposed orders.</returns> public static RemoteBatch Rebalance(ClientMarketData.AccountRow accountRow, ClientMarketData.ModelRow modelRow) { // Make sure the scheme still exists in the in-memory database. We need it to rebalance to calculate // sector totals. ClientMarketData.SchemeRow schemeRow; if ((schemeRow = ClientMarketData.Scheme.FindBySchemeId(modelRow.SchemeId)) == null) { throw new ArgumentException("Scheme doesn't exist in the ClientMarketData", modelRow.SchemeId.ToString()); } // All the market values need to be normalized to a single currency so the sectors can be aggregated. This // value is made available to all methods through a member rather than passed on the stack. ClientMarketData.CurrencyRow currencyRow = accountRow.CurrencyRow; // The final result of this method is a command batch that can be sent to the server. RemoteBatch remoteBatch = new RemoteBatch(); RemoteTransaction remoteTransaction = remoteBatch.Transactions.Add(); // Calculate the total market value for the appraisal and all the sub-accounts. This will be the denominator // in all calculations involving sector percentages. This feature makes a 'Merge' rebalancer different from a // 'Wrap' rebalance. The 'Wrap' uses the sub-account's market value as the denominator when calculating // sector market values. decimal accountMarketValue = MarketValue.Calculate(accountRow.CurrencyRow, accountRow, MarketValueFlags.EntirePosition | MarketValueFlags.IncludeChildAccounts); // The outline of the appraisal will be needed to make calculations based on a position, that is a security, // account, position type combination grouped by a security classification scheme. AppraisalSet appraisalSet = new Appraisal(accountRow, schemeRow, true); // By cycling through all the immediate children of the scheme record, we'll have covered the top-level // sectors in this appraisal. foreach (AppraisalSet.SchemeRow driverScheme in appraisalSet.Scheme) { foreach (AppraisalSet.ObjectTreeRow driverTree in driverScheme.ObjectRow.GetObjectTreeRowsByFKObjectObjectTreeParentId()) { foreach (AppraisalSet.SectorRow driverSector in driverTree.ObjectRowByFKObjectObjectTreeChildId.GetSectorRows()) { // The appraisal set collects the ids of the records used. We need to look up the actual sector // record from the ClientMarketData in order to search through it and aggregate sub-sectors and // securities. ClientMarketData.SectorRow sectorRow = ClientMarketData.Sector.FindBySectorId(driverSector.SectorId); // Get the market value of the top-level sector, including all subaccounts and all positions. decimal actualSectorMarketValue = MarketValue.Calculate(currencyRow, accountRow, sectorRow, MarketValueFlags.EntirePosition | MarketValueFlags.IncludeChildAccounts); // This will find the model percentage of the current top-level sector. If the sector wasn't // specified in the model, assume a value of zero, which would indicate that we're to sell the // entire sector. ClientMarketData.SectorTargetRow sectorTargetRow = ClientMarketData.SectorTarget.FindByModelIdSectorId(modelRow.ModelId, driverSector.SectorId); decimal targetPercent = sectorTargetRow == null ? 0.0M : sectorTargetRow.Percent; // The target market value is calculated from the model percentage and the actual aggregate // account market value. decimal targetSectorMarketValue = accountMarketValue * targetPercent; // Now that we have a target to shoot for, recursively descend into the structure calculating // propsed orders. RecurseSectors(remoteBatch, remoteTransaction, currencyRow, modelRow, driverSector.ObjectRow, actualSectorMarketValue, targetSectorMarketValue); } } } // This object holds a complete set of proposed orders to achieve the sector targets in the model. return(remoteBatch); }
/// <summary> /// Event handler for changing the name of a label. /// </summary> /// <param name="sender">The window control that generated the 'LabelEdit' event.</param> /// <param name="e">Event Parameters used to control the actions taken by the event handler.</param> private void treeView_AfterLabelEdit(object sender, System.Windows.Forms.NodeLabelEditEventArgs e) { // The TreeView has a bug in it: if you leave the edit mode without typing anything, the returned text of the control // will be an empty string. Since we don't want to bother the server or the user with this nonsense, we'll filter out // the possiblity here. if (e.Label == null) { e.CancelEdit = true; return; } // Extract the object's properties from the node. ObjectNode objectNode = (ObjectNode)e.Node; // This command batch is constructed below and sent to the server for execution. RemoteBatch remoteBatch = new RemoteBatch(); // This will insure that table locks are cleaned up. try { // Lock the table Debug.Assert(!ClientMarketData.AreLocksHeld); ClientMarketData.ObjectLock.AcquireReaderLock(CommonTimeout.LockWait); // Find the object in the data model and make sure it still exists. ClientMarketData.ObjectRow objectRow = ClientMarketData.Object.FindByObjectId(objectNode.ObjectId); if (objectRow == null) { throw new Exception("This object has been deleted."); } // Construct a command to rename the object. RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Core"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Core.Object"); RemoteMethod remoteMethod = remoteType.Methods.Add("Update"); remoteMethod.Parameters.Add("rowVersion", objectRow.RowVersion); remoteMethod.Parameters.Add("objectId", objectRow.ObjectId); remoteMethod.Parameters.Add("name", e.Label); } catch (Exception exception) { // Write the error and stack trace out to the debug listener Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace)); // Cancel the tree operation if we can't execute the command. The text in the tree control will revert to the // previous value. e.CancelEdit = true; } finally { // Release table locks. if (ClientMarketData.ObjectLock.IsReaderLockHeld) { ClientMarketData.ObjectLock.ReleaseReaderLock(); } Debug.Assert(!ClientMarketData.AreLocksHeld); } // If the command batch was built successfully, then execute it. If any part of it should fail, cancel the edit and // display the server errors. if (remoteBatch != null) { try { // Call the web server to rename the object on the database. Note that this method must be called when there are // no locks to prevent deadlocking. That is why it appears in it's own 'try/catch' block. ClientMarketData.Execute(remoteBatch); } catch (BatchException batchException) { // Undo the editing action. This will restore the name of the object to what it was before the operation. e.CancelEdit = true; // Display each error in the batch. foreach (RemoteException remoteException in batchException.Exceptions) { MessageBox.Show(remoteException.Message, "Quasar Error"); } } } }
/// <summary> /// Creates a command in a RemoteBatch structure to insert a proposed order. /// </summary> /// <param name="remoteBatch"></param> /// <param name="remoteTransaction"></param> /// <param name="accountRow"></param> /// <param name="securityRow"></param> /// <param name="positionTypeCode"></param> /// <param name="quantityInstruction"></param> private static void Insert(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, ClientMarketData.AccountRow accountRow, ClientMarketData.SecurityRow securityRow, int positionTypeCode, decimal quantityInstruction) { // These define the assembly and the types within those assemblies that will be used to create the proposed orders on // the middle tier. RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Core"); RemoteType proposedOrderType = remoteAssembly.Types.Add("Shadows.WebService.Core.ProposedOrder"); RemoteType proposedOrderTreeType = remoteAssembly.Types.Add("Shadows.WebService.Core.ProposedOrderTree"); // Find the default settlement for this order. int settlementId = Shadows.Quasar.Common.Security.GetDefaultSettlementId(securityRow); // As a convention between the rebalancing section and the order generation, the parentQuantity passed into this method // is a signed value where the negative values are treated as 'Sell' instructions and the positive values meaning // 'Buy'. This will adjust the parentQuantity so the trading methods can deal with an unsigned value, which is more // natural for trading. decimal parentQuantity = Math.Abs(quantityInstruction); // This will turn the signed parentQuantity into an absolute parentQuantity and a transaction code (e.g. -1000 is // turned into a SELL of 1000 shares). int parentTransactionTypeCode = TransactionType.Calculate(securityRow.SecurityTypeCode, positionTypeCode, quantityInstruction); // The time in force first comes from the user preferences, next, account settings and finally defaults to a day // orders. int timeInForceCode = !ClientPreferences.IsTimeInForceCodeNull() ? ClientPreferences.TimeInForceCode : !accountRow.IsTimeInForceCodeNull() ? accountRow.TimeInForceCode : TimeInForce.DAY; // The destination blotter comes first from the user preferences, second from the account preferences, and finally uses // the auto-routing logic. int parentBlotterId = ClientPreferences.IsBlotterIdNull() ? (accountRow.IsBlotterIdNull() ? TradingSupport.AutoRoute(securityRow, parentQuantity) : accountRow.BlotterId) : ClientPreferences.BlotterId; // Create a command to delete the relationship between the parent and child. RemoteMethod insertParent = proposedOrderType.Methods.Add("Insert"); insertParent.Transaction = remoteTransaction; insertParent.Parameters.Add("proposedOrderId", DataType.Int, Direction.ReturnValue); insertParent.Parameters.Add("blotterId", parentBlotterId); insertParent.Parameters.Add("accountId", accountRow.AccountId); insertParent.Parameters.Add("securityId", securityRow.SecurityId); insertParent.Parameters.Add("settlementId", settlementId); insertParent.Parameters.Add("positionTypeCode", positionTypeCode); insertParent.Parameters.Add("transactionTypeCode", parentTransactionTypeCode); insertParent.Parameters.Add("timeInForceCode", timeInForceCode); insertParent.Parameters.Add("orderTypeCode", OrderType.Market); insertParent.Parameters.Add("quantity", parentQuantity); // Now it's time to create an order for the settlement currency. if (securityRow.SecurityTypeCode == SecurityType.Equity || securityRow.SecurityTypeCode == SecurityType.Debt) { // The underlying currency is needed for the market value calculations. ClientMarketData.CurrencyRow currencyRow = MarketData.Currency.FindByCurrencyId(settlementId); decimal marketValue = parentQuantity * securityRow.QuantityFactor * Price.Security(currencyRow, securityRow) * securityRow.PriceFactor * TransactionType.GetCashSign(parentTransactionTypeCode); // The stragegy for handling the settlement currency changes is to calculate the old market value, calculate the // new market value, and add the difference to the running total for the settlement currency of this security. The // new market value is the impact of the trade that was just entered. int childTransactionTypeCode = TransactionType.Calculate(securityRow.SecurityTypeCode, positionTypeCode, marketValue); decimal childQuantity = Math.Abs(marketValue); // The destination blotter comes first from the user preferences, second from the account preferences, and finally // uses the auto-routing logic. int childBlotterId = ClientPreferences.IsBlotterIdNull() ? (accountRow.IsBlotterIdNull() ? TradingSupport.AutoRoute(currencyRow.SecurityRow, childQuantity) : accountRow.BlotterId) : ClientPreferences.BlotterId; // Fill in the rest of the fields and the defaulted fields for this order. Create a command to delete the // relationship between the parent and child. RemoteMethod insertChild = proposedOrderType.Methods.Add("Insert"); insertChild.Transaction = remoteTransaction; insertChild.Parameters.Add("proposedOrderId", DataType.Int, Direction.ReturnValue); insertChild.Parameters.Add("blotterId", childBlotterId); insertChild.Parameters.Add("accountId", accountRow.AccountId); insertChild.Parameters.Add("securityId", settlementId); insertChild.Parameters.Add("settlementId", settlementId); insertChild.Parameters.Add("transactionTypeCode", childTransactionTypeCode); insertChild.Parameters.Add("positionTypeCode", positionTypeCode); insertChild.Parameters.Add("timeInForceCode", timeInForceCode); insertChild.Parameters.Add("orderTypeCode", OrderType.Market); insertChild.Parameters.Add("quantity", childQuantity); RemoteMethod insertRelation = proposedOrderTreeType.Methods.Add("Insert"); insertRelation.Transaction = remoteTransaction; insertRelation.Parameters.Add("parentId", insertParent.Parameters["proposedOrderId"]); insertRelation.Parameters.Add("childId", insertChild.Parameters["proposedOrderId"]); } }
static int Main(string[] args) { // If this flag is set during the processing of the file, the program will exit with an error code. bool hasErrors = false; try { // Defaults batchSize = 100; assemblyName = "Service.External"; nameSpaceName = "Shadows.WebService.External"; // The command line parser is driven by different states that are triggered by the flags read. Unless a flag has been // read, the command line parser assumes that it's reading the file name from the command line. argumentState = ArgumentState.FileName; // Parse the command line for arguments. foreach (string argument in args) { // Decode the current argument into a state change (or some other action). if (argument == "-a") { argumentState = ArgumentState.Assembly; continue; } if (argument == "-b") { argumentState = ArgumentState.BatchSize; continue; } if (argument == "-n") { argumentState = ArgumentState.NameSpace; continue; } if (argument == "-i") { argumentState = ArgumentState.FileName; continue; } // The parsing state will determine which variable is read next. switch (argumentState) { case ArgumentState.Assembly: assemblyName = argument; break; case ArgumentState.BatchSize: batchSize = Convert.ToInt32(argument); break; case ArgumentState.FileName: fileName = argument; break; case ArgumentState.NameSpace: nameSpaceName = argument; break; } // The default state is to look for the input file name on the command line. argumentState = ArgumentState.FileName; } // Throw a usage message back at the user if no file name was given. if (fileName == null) { throw new Exception("Usage: Loader.Algorithm -i <FileName>"); } // Open up the file containing all the broker. BrokerReader brokerReader = new BrokerReader(fileName); // Create a new web client that will serve as the connection point to the server. WebClient webClient = new WebClient(); // Loading the database involves creating a batch of commands and sending them off as a transaction. This gives // the server a chance to pipeline a large chunk of processing, without completely locking up the server for the // entire set of data. This will construct a header for the command batch which gives information about which // assembly contains the class that is used to load the data. RemoteBatch remoteBatch = new RemoteBatch(); RemoteTransaction remoteTransaction = remoteBatch.Transactions.Add(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add(assemblyName); RemoteType remoteType = remoteAssembly.Types.Add(string.Format("{0}.{1}", nameSpaceName, "Broker")); // Read the file until an EOF is reached. while (true) { // This counter keeps track of the number of records sent. When the batch is full, it's sent to the server to be // executed as a single transaction. int batchCounter = 0; // Read the next broker from the input stream. A 'null' is returned when we've read past the end of file. Broker broker = brokerReader.ReadBroker(); if (broker != null) { // Construct a call to the 'Load' method to populate the broker record. RemoteMethod remoteMethod = remoteType.Methods.Add("Load"); remoteMethod.Transaction = remoteTransaction; remoteMethod.Parameters.Add("brokerId", broker.Symbol); remoteMethod.Parameters.Add("name", broker.Name); remoteMethod.Parameters.Add("symbol", broker.Symbol); } // This will check to see if it's time to send the batch. A batch is sent when the 'batchSize' has been // reached, or if the last record has just been converted into a command. if (++batchCounter % batchSize == 0 || broker == null) { // Call the web services to execute the command batch. remoteBatch.Merge(webClient.Execute(remoteBatch)); // Any error during the batch will terminate the execution of the remaining records in the data file. if (remoteBatch.HasExceptions) { // Display each error on the console. foreach (RemoteMethod remoteMethod in remoteBatch.Methods) { foreach (RemoteException remoteException in remoteMethod.Exceptions) { Console.WriteLine(String.Format("{0}: {1}", remoteMethod.Parameters["brokerId"].Value, remoteException.Message)); } } // This will signal the error exit from this loader. hasErrors = true; } // If the end of file was reached, break out of the loop and exit the application. if (broker == null) { break; } // After each batch has been send, check to see if there are additional records to be send. If so, // regenerate the remote batch and set up the header. remoteBatch = new RemoteBatch(); remoteTransaction = remoteBatch.Transactions.Add(); remoteAssembly = remoteBatch.Assemblies.Add(assemblyName); remoteType = remoteAssembly.Types.Add(string.Format("{0}.{1}", nameSpaceName, "Broker")); } } } catch (Exception exception) { // Show the system error and exit with an error. Console.WriteLine(exception.Message); hasErrors = true; } // Any errors will cause an abnormal exit. if (hasErrors) { return(1); } // Write a status message when a the file is loaded successfully. Console.WriteLine(String.Format("{0} Data: Brokers, Loaded", DateTime.Now.ToString("u"))); // If we reached here, the file was imported without issue. return(0); }
/// <summary> /// Recursively calculates proposed orders for a sector. /// </summary> /// <param name="sector">Gives the current sector (sector) for the calculation.</param> private static void RecurseSectors(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, ClientMarketData.CurrencyRow currencyRow, ClientMarketData.ModelRow modelRow, AppraisalSet.ObjectRow driverObject, decimal actualSectorMarketValue, decimal targetSectorMarketValue) { // Run through each of the positions in the sector and calculate the current percentage of the position within // the sector. We're going to keep this percentage as we rebalance to the new sector market value. foreach (AppraisalSet.SecurityRow driverSecurity in driverObject.GetSecurityRows()) { foreach (AppraisalSet.PositionRow driverPosition in driverSecurity.GetPositionRows()) { // We need to know what kind of security we're dealing with when calculating market values and quantities // below. ClientMarketData.SecurityRow securityRow = ClientMarketData.Security.FindBySecurityId(driverSecurity.SecurityId); // In this rebalancing operation, the cash balance is dependant on the securities bought and sold. When // stocks are bought or sold below, they will impact the underlying currency. We can not balance to a // currency target directly. if (securityRow.SecurityTypeCode == SecurityType.Currency) { continue; } // Calculate the proposed orders for each account. The fraction of the security within the sector will // stay the same, even though the sector may increase or decrease with respect to the total market value. foreach (AppraisalSet.AccountRow driverAccount in driverPosition.GetAccountRows()) { // The underlying currency is needed for the market value calculations. ClientMarketData.AccountRow accountRow = ClientMarketData.Account.FindByAccountId(driverAccount.AccountId); // Sector rebalancing keeps the percentage of a security within the sector constant. Only the overall // percentage of the sector with respect to the NAV changes. To accomplish this, we first calculate // the percentage of the security within the sector before we rebalance the sector. decimal actualPositionMarketValue = MarketValue.Calculate(currencyRow, accountRow, securityRow, driverPosition.PositionTypeCode, MarketValueFlags.EntirePosition); // Calculate the target market value as a percentage of the entire sector (use zero if the sector has // no market value to prevent divide by zero errors). decimal targetPositionMarketValue = (actualSectorMarketValue == 0) ? 0.0M : actualPositionMarketValue * targetSectorMarketValue / actualSectorMarketValue; // The target proposed orders market value keeps the percentage of the position constant while // changing the overall sector percentage. decimal proposedMarketValue = targetPositionMarketValue - MarketValue.Calculate(currencyRow, accountRow, securityRow, driverPosition.PositionTypeCode, MarketValueFlags.ExcludeProposedOrder); // Calculate the quantity needed to hit the target market value and round it according to the // model. Note that the market values and prices are all denominated in the currency of the // parent account. Also note the quantityFactor is needed for the proper quantity calculation. decimal proposedQuantity = proposedMarketValue / (Price.Security(currencyRow, securityRow) * securityRow.PriceFactor * securityRow.QuantityFactor); // If we have an equity, round to the model's lot size. if (securityRow.SecurityTypeCode == SecurityType.Equity) { proposedQuantity = Math.Round(proposedQuantity / modelRow.EquityRounding, 0) * modelRow.EquityRounding; } // A debt generally needs to be rounded to face. if (securityRow.SecurityTypeCode == SecurityType.Debt) { proposedQuantity = Math.Round(proposedQuantity / modelRow.DebtRounding, 0) * modelRow.DebtRounding; } // Have the Order Form Builder object construct an order based on the quantity we've calcuated from // the market value. This method will fill in the defaults needed for a complete proposed order. ProposedOrder.Create(remoteBatch, remoteTransaction, accountRow, securityRow, driverAccount.PositionTypeCode, proposedQuantity); } } } // Recurse into each of the sub-sectors. This allows us to rebalance with any number of levels to the // hierarchy. Eventually, we will run across a sector with security positions in it and end up doing some // real work. foreach (AppraisalSet.ObjectTreeRow driverTree in driverObject.GetObjectTreeRowsByFKObjectObjectTreeParentId()) { SectorMerge.RecurseSectors(remoteBatch, remoteTransaction, currencyRow, modelRow, driverTree.ObjectRowByFKObjectObjectTreeChildId, actualSectorMarketValue, targetSectorMarketValue); } }
/// <summary> /// Recursively rebalances an account and all it's children. /// </summary> /// <param name="accountRow">The parent account to be rebalanced.</param> private static void RecurseAccounts(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, AppraisalSet appraisalSet, ClientMarketData.AccountRow accountRow, ClientMarketData.ModelRow modelRow) { // The base currency of the account is used to cacluate market values. ClientMarketData.CurrencyRow currencyRow = ClientMarketData.Currency.FindByCurrencyId(accountRow.CurrencyId); // Calculate the total market value for the appraisal. This will be the denominator in all calculations involving // portfolio percentages. decimal accountMarketValue = MarketValue.Calculate(currencyRow, accountRow, MarketValueFlags.EntirePosition); // Cycle through all the positions of the appraisal using the current account and calculate the size and direction of // the trade needed to bring it to the model's target percent. foreach (AppraisalSet.SecurityRow driverSecurity in appraisalSet.Security) { // We need to reference the security row in the ClientMarketData to price this item. ClientMarketData.SecurityRow securityRow = ClientMarketData.Security.FindBySecurityId(driverSecurity.SecurityId); // In this rebalancing operation, the cash balance is dependant on the securities bought and sold. The assumption // is made that we won't implicitly add or remove cash to accomplish the reblancing operation. When stocks are // bought or sold below, they will impact the underlying currency. A cash target can be reached by setting all the // other percentages up properly. As long as the total percentage in a model is 100%, the proper cash target will // be calculated. We don't have to do anything with this asset type. if (securityRow.SecurityTypeCode == SecurityType.Currency) { continue; } // This section will calculate the difference in between the actual and target market values for each // position and create orders that will bring the account to the targeted percentages. foreach (AppraisalSet.PositionRow driverPosition in driverSecurity.GetPositionRows()) { // Calculate the proposed quantity needed to bring this asset/account combination to the percentage given by // the model. First, find the target percent. If it's not there, we assume a target of zero (meaning sell all // holdings). ClientMarketData.PositionTargetRow positionTargetRow = ClientMarketData.PositionTarget.FindByModelIdSecurityIdPositionTypeCode(modelRow.ModelId, securityRow.SecurityId, driverPosition.PositionTypeCode); decimal targetPositionPercent = positionTargetRow == null ? 0.0M : positionTargetRow.Percent; // The market value of this trade will be the target market value less the current market value of // this position (without including the existing proposed orders in the current market value // calculation). decimal targetPositionMarketValue = targetPositionPercent * accountMarketValue; decimal actualPositionMarketValue = MarketValue.Calculate(currencyRow, accountRow, securityRow, driverPosition.PositionTypeCode, MarketValueFlags.ExcludeProposedOrder); decimal proposedMarketValue = targetPositionMarketValue - actualPositionMarketValue; // Calculate the quantity needed to hit the target market value and round it according to the model. Note that // the market values and prices are all denominated in the currency of the parent account. Also note the // quantityFactor is needed for the proper quantity calculation. decimal price = Price.Security(currencyRow, securityRow); decimal proposedQuantity = price == 0.0M ? 0.0M : proposedMarketValue / (price * securityRow.QuantityFactor); // If we have an equity, round to the model's lot size. Common values are 100 and 1. if (securityRow.SecurityTypeCode == SecurityType.Equity) { proposedQuantity = Math.Round(proposedQuantity / modelRow.EquityRounding, 0) * modelRow.EquityRounding; } // A debt generally needs to be rounded to face. if (securityRow.SecurityTypeCode == SecurityType.Debt) { proposedQuantity = Math.Round(proposedQuantity / modelRow.DebtRounding, 0) * modelRow.DebtRounding; } // Have the Order Form Builder object construct an order based on the new proposed quantity. This method will // fill in the defaults needed for a complete Proposed Order. It will also create an deposit or widthdrawal // from an account to cover the transaction. ProposedOrder.Create(remoteBatch, remoteTransaction, accountRow, securityRow, driverPosition.PositionTypeCode, proposedQuantity); } } // Now that we've rebalanced the parent account, cycle through all the children accounts and rebalance them. foreach (ClientMarketData.ObjectTreeRow objectTreeRow in accountRow.ObjectRow.GetObjectTreeRowsByFKObjectObjectTreeParentId()) { foreach (ClientMarketData.AccountRow childAccount in objectTreeRow.ObjectRowByFKObjectObjectTreeChildId.GetAccountRows()) { Security.RecurseAccounts(remoteBatch, remoteTransaction, appraisalSet, childAccount, modelRow); } } }
private void DragDropHandler(params object[] arguments) { ObjectNode toNode = (ObjectNode)arguments[0]; ObjectNode fromNode = (ObjectNode)arguments[1]; ObjectNode childNode = (ObjectNode)arguments[2]; // Make sure the user has selected a valid source and destination for the operation. It's illegal to move the node // 1. To the root of the tree // 2. From the root of the tree if (toNode == null || fromNode == null) { return; } // Don't allow for circular references. try { // Lock the tables needed for this operation. Debug.Assert(!ClientMarketData.AreLocksHeld); ClientMarketData.ObjectLock.AcquireReaderLock(CommonTimeout.LockWait); // It's critical that circular references aren't created, either by accident or design. First, find the object // record associated with the destination node. ClientMarketData.ObjectRow parentRow = ClientMarketData.Object.FindByObjectId(toNode.ObjectId); if (parentRow == null) { throw new Exception("This object has been deleted"); } // This is the object that is being dragged. Find the row ClientMarketData.ObjectRow childRow = ClientMarketData.Object.FindByObjectId(childNode.ObjectId); if (childRow == null) { throw new Exception("This object has been deleted"); } if (Shadows.Quasar.Common.Relationship.IsChildObject(childRow, parentRow)) { MessageBox.Show(this.TopLevelControl, "This would create a circular references.", "Quasar Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } catch (Exception exception) { // Write the error and stack trace out to the debug listener Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace)); } finally { // Release table locks. if (ClientMarketData.ObjectLock.IsReaderLockHeld) { ClientMarketData.ObjectLock.ReleaseReaderLock(); } Debug.Assert(!ClientMarketData.AreLocksHeld); } // Any commands created below will be constructed in this object and sent to the server for execution. RemoteBatch remoteBatch = new RemoteBatch(); // Change the default model of an account. When the destination is an account group, account or sub account and the // source is a model, a command will be constructed to change the default model. if (toNode.ObjectTypeCode == ObjectType.Account && dragNode.ObjectTypeCode == ObjectType.Model) { try { // Lock the tables needed for this operation. Debug.Assert(!ClientMarketData.AreLocksHeld); ClientMarketData.AccountLock.AcquireReaderLock(CommonTimeout.LockWait); // Find the account used, throw an error if it's been deleted. ClientMarketData.AccountRow accountRow; if ((accountRow = ClientMarketData.Account.FindByAccountId(toNode.ObjectId)) == null) { throw new Exception("This account has been deleted"); } // Construct a command to change the default model associated with the account. RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Core"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Core.Account"); RemoteMethod remoteMethod = remoteType.Methods.Add("Update"); remoteMethod.Parameters.Add("rowVersion", accountRow.RowVersion); remoteMethod.Parameters.Add("accountId", accountRow.AccountId); remoteMethod.Parameters.Add("modelId", dragNode.ObjectId); } catch (Exception exception) { // Write the error and stack trace out to the debug listener Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace)); } finally { // Release table locks. if (ClientMarketData.AccountLock.IsReaderLockHeld) { ClientMarketData.AccountLock.ReleaseReaderLock(); } Debug.Assert(!ClientMarketData.AreLocksHeld); } } else { // If we made it here, the drag-and-drop is interpreted as a command to move a child from one parent to another. try { // Lock the tables needed for this operation. Debug.Assert(!ClientMarketData.AreLocksHeld); ClientMarketData.ObjectTreeLock.AcquireReaderLock(CommonTimeout.LockWait); // Extract the primary identifiers from the user interface nodes. int fromId = fromNode.ObjectId; int toId = toNode.ObjectId; int childId = dragNode.ObjectId; // Find the object in the data model and make sure it still exists. ClientMarketData.ObjectTreeRow objectTreeRow = ClientMarketData.ObjectTree.FindByParentIdChildId(fromNode.ObjectId, dragNode.ObjectId); if (objectTreeRow == null) { throw new Exception("This relationship has been deleted by someone else."); } // Moving a child object from one parent to another must be accomplished as a transaction. Otherwise, an // orhpan object will be created if the operation fails midway through. RemoteTransaction remoteTransaction = remoteBatch.Transactions.Add(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Core"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Core.ObjectTree"); // Construct a command delete the old parent relation. RemoteMethod deleteObjectTree = remoteType.Methods.Add("Delete"); deleteObjectTree.Transaction = remoteTransaction; deleteObjectTree.Parameters.Add("rowVersion", objectTreeRow.RowVersion); deleteObjectTree.Parameters.Add("parentId", fromId); deleteObjectTree.Parameters.Add("childId", childId); // Construct a command insert a new parent relation. RemoteMethod insertObjectTree = remoteType.Methods.Add("Insert"); insertObjectTree.Transaction = remoteTransaction; insertObjectTree.Parameters.Add("parentId", toId); insertObjectTree.Parameters.Add("childId", childId); } catch (Exception exception) { // Write the error and stack trace out to the debug listener Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace)); } finally { // Release table locks. if (ClientMarketData.ObjectTreeLock.IsReaderLockHeld) { ClientMarketData.ObjectTreeLock.ReleaseReaderLock(); } Debug.Assert(!ClientMarketData.AreLocksHeld); } } // If the command batch was built successfully, then execute it. if (remoteBatch != null) { try { // Call the web server to rename the object on the database. Note that this method must be called when there are // no locks to prevent deadlocking. That is why it appears in it's own 'try/catch' block. ClientMarketData.Execute(remoteBatch); } catch (BatchException batchException) { // Display each error in the batch. foreach (RemoteException remoteException in batchException.Exceptions) { MessageBox.Show(remoteException.Message, "Quasar Error"); } } } }
/// <summary> /// Execute a command batch and synchronizes the client data model with the newer records from the server. /// </summary> public static void Execute(RemoteBatch remoteBatch) { // This 'try' block will insure that the mutual exclusion locks are released. try { // Maure sure only one thread at a time tries to refresh the data model. ClientMarketData.refreshMutex.WaitOne(); // IMPORTANT CONCEPT: The rowVersion keeps track of the state of the client in-memory database. The server returns // a value for us to use on the next cycle. If, for any reason, the merge of the data on the client should fail, // we won't use the new value on the next cycle. That is, we're going to keep on asking for the same incremental // set of records until we've successfully merged them. This guarantees that the client and server databases stay // consistent with each other. DateTime timeStamp = ClientMarketData.timeStamp; long rowVersion = ClientMarketData.rowVersion; // IMPORTANT CONCEPT: Executing the 'Reconcile' Web Service with a rowVersion returns to the client a DataSet with // only records that are the same age or younger than the rowVersion. This reduces the traffic on the network to // include only the essential records. This set also contains the deleted records. When the DataSet that is // returned from this Web Service is merged with the current data, the client will be reconciled with the server as // of the 'rowVersion' returned from the server. DataSet resultSet; WebClient webClient = new WebClient(); DataSet dataSet = webClient.ExecuteAndReconcile(ref timeStamp, ref rowVersion, remoteBatch, out resultSet); remoteBatch.Merge(resultSet); // If the time stamps are out of sync, then the server has reset since our last refresh (or this is the first time // refreshing the data mdoel). This will reset the client side of the data model so that, after this refresh, the // client and the server will be in sync again. if (ClientMarketData.timeStamp != timeStamp) { ClientMarketData.rowVersion = 0; ClientMarketData.timeStamp = timeStamp; ClientMarketData.Clear(); } // Optimization: Don't merge the results if there's nothing to merge. if (rowVersion > ClientMarketData.rowVersion) { // IMPORTANT CONCEPT: This broadcast can be used to set up conditions for the data event handlers. Often, // optimizing algorithms will be used to consolidate the results of a merge. This will allow the event driven // logic to clear previous results and set up initial states for handling the bulk update of data. ClientMarketData.OnBeginMerge(typeof(ClientMarketData)); // This 'try' block will insure that the locks are released if there's an error. try { // IMPORTANT CONCEPT: Make sure that there aren't any nested locks. Table locks have to be aquired in the // same order for every thread that uses the shared data model. It's possible that the current thread may // have locked table 'B' if the preamble, then try to lock table 'A' during the processing of a command // batch. If another thread aquires the lock on table 'A' and blocks on table 'B', we will have a // deadlock. This is designed to catch situations where the 'Execute' method is called while tables are // already locked. Debug.Assert(!ClientMarketData.AreLocksHeld); // IMPORTANT CONCEPT: Since the results will still be on the server if the client misses a refresh cycle, // we take the attitude that this update process doesn't have to wait for locks. That is, if we can't get // all the tables locked quickly, we'll just wait until the next refresh period to get the results. This // effectively prevents deadlocking on the client. Make sure all the tables are locked before populating // them. foreach (TableLock tableLock in ClientMarketData.TableLocks) { tableLock.AcquireWriterLock(ClientTimeout.LockWait); } // IMPORANT CONCEPT: Once all the write locks have been obtained, we can merge the results. This will // trigger the events associated with the tables for updated and deleted rows. Also, if // "AcceptChanges" is invoked for a DataSet or a Table, every single record in the DataSet or DataTable // will be "Committed", even though they are unchanged. This is a VERY inefficient operation. To get // around this, an ArrayList of modified records is constructed during the Merge operation. After the // merge, only the new records are committed. ClientMarketData.mergedRows.Clear(); ClientMarketData.Merge(dataSet); for (int index = 0; index < ClientMarketData.mergedRows.Count; index++) { ((DataRow)ClientMarketData.mergedRows[index]).AcceptChanges(); } // If the merge operation was successful, then we can use the new rowVersion for the next cycle. Any // exception before this point will result in a request of the same set of data becaue the rowVersion was // never updated. ClientMarketData.rowVersion = rowVersion; } catch (ConstraintException) { // Write out the exact location of the error. foreach (DataTable dataTable in ClientMarketData.Tables) { foreach (DataRow dataRow in dataTable.Rows) { if (dataRow.HasErrors) { Console.WriteLine("Error in '{0}': {1}", dataRow.Table.TableName, dataRow.RowError); } } } } catch (Exception exception) { // Write the error and stack trace out to the debug listener Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace)); } finally { // No matter what happens above, we need to release the locks acquired above. foreach (TableLock tableLock in ClientMarketData.TableLocks) { if (tableLock.IsWriterLockHeld) { tableLock.ReleaseWriterLock(); } } } // IMPORTANT CONCEPT: When the merge is complete and the tables are unlocked, this will broadcast an event // which allows optimization code to consolidate the results, examine the changed values and update reports // based on the changed data. ClientMarketData.OnEndMerge(typeof(ClientMarketData)); } } finally { // Other threads can now request a refresh of the data model. ClientMarketData.refreshMutex.ReleaseMutex(); } // Throw a specialized exception if the server returned any errors in the RemoteBatch structure. if (remoteBatch.HasExceptions) { throw new BatchException(remoteBatch); } }
static CommandBatch() { CommandBatch.remoteBatch = new RemoteBatch(); }
/// <summary> /// Rebalances an account to the sector targets, then recursively rebalances the children accounts. /// </summary> /// <param name="orderFormBuilder">A collection of orders.</param> /// <param name="accountRow">The parent account to be rebalanced.</param> /// <param name="modelRow">The model containing the sector targets.</param> /// <param name="schemeRow">The outline scheme used to define the sector contents.</param> private static void RecurseAccounts(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, ClientMarketData.AccountRow accountRow, ClientMarketData.ModelRow modelRow, ClientMarketData.SchemeRow schemeRow) { // All the market values of all the securities in this account are normalized to a single currency so they can // be aggregated. ClientMarketData.CurrencyRow currencyRow = ClientMarketData.Currency.FindByCurrencyId(accountRow.CurrencyId); // Calculate the total market value for the appraisal without including child accounts. This is a 'Wrap' // rebalancing, so we're only concerned with what's in this account. The account's market value will be the // denominator in all calculations involving sector percentages. decimal accountMarketValue = MarketValue.Calculate(currencyRow, accountRow, MarketValueFlags.EntirePosition); // The outline of the appraisal will be needed to make market value calculations based on a sector. Note that // we're not including the child accounts in the outline. Wrap rebalancing works only on a single account at // a time. AppraisalSet appraisalSet = new Appraisal(accountRow, schemeRow, false); // By cycling through all the immediate children of the scheme record, we'll have covered the top-level // sectors in this appraisal. foreach (AppraisalSet.SchemeRow driverScheme in appraisalSet.Scheme) { foreach (AppraisalSet.ObjectTreeRow driverTree in driverScheme.ObjectRow.GetObjectTreeRowsByFKObjectObjectTreeParentId()) { foreach (AppraisalSet.SectorRow driverSector in driverTree.ObjectRowByFKObjectObjectTreeChildId.GetSectorRows()) { // Find the sectors row record that corresponds to the current sector in the appraisal set. ClientMarketData.SectorRow sectorRow = ClientMarketData.Sector.FindBySectorId(driverSector.SectorId); // Get the market value of the top-level sector, including all sub-sectors and all positions // belonging to only the current account. decimal actualSectorMarketValue = MarketValue.Calculate(currencyRow, accountRow, sectorRow, MarketValueFlags.EntirePosition); // This will find the model percentage of the current top-level sector. If the sector wasn't // specified in the model, assume a value of zero, which would indicate that we're to sell the // entire sector. ClientMarketData.SectorTargetRow sectorTargetRow = ClientMarketData.SectorTarget.FindByModelIdSectorId(modelRow.ModelId, driverSector.SectorId); decimal targetPercent = (sectorTargetRow == null) ? 0.0M : sectorTargetRow.Percent; // The sector's target market value is calculated from the model percentage and the current // account market value. This is placed in a member variable so it's available to the methods // when we recurse. decimal targetSectorMarketValue = accountMarketValue * targetPercent; // Now that we have a sector target to shoot for, recursively descend into the structure // calculating proposed orders. SectorWrap.RecurseSectors(remoteBatch, remoteTransaction, modelRow, driverSector, actualSectorMarketValue, targetSectorMarketValue); } } } // Now that we've rebalanced the parent account, cycle through all the children accounts and rebalance them. foreach (ClientMarketData.ObjectTreeRow objectTreeRow in accountRow.ObjectRow.GetObjectTreeRowsByFKObjectObjectTreeParentId()) { foreach (ClientMarketData.AccountRow childAccount in objectTreeRow.ObjectRowByFKObjectObjectTreeChildId.GetAccountRows()) { SectorWrap.RecurseAccounts(remoteBatch, remoteTransaction, childAccount, modelRow, schemeRow); } } }
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); } }
/// <summary> /// Recursively calculates proposed orders for a sector. /// </summary> /// <param name="sector">Gives the current sector (sector) for the calculation.</param> private static void RecurseSectors(RemoteBatch remoteBatch, RemoteTransaction remoteTransaction, ClientMarketData.ModelRow modelRow, AppraisalSet.SectorRow driverSector, decimal actualSectorMarketValue, decimal targetSectorMarketValue) { // The main idea here is to keep the ratio of the security to the sector constant, while changing the market // value of the sector. Scan each of the securities belonging to this sector. foreach (AppraisalSet.ObjectTreeRow objectTreeRow in driverSector.ObjectRow.GetObjectTreeRowsByFKObjectObjectTreeParentId()) { // Cycle through each of the securities in the sector. We're going to keep the ratio of the security the // same as we target a different sector total. foreach (AppraisalSet.SecurityRow driverSecurity in objectTreeRow.ObjectRowByFKObjectObjectTreeChildId.GetSecurityRows()) { foreach (AppraisalSet.PositionRow driverPosition in driverSecurity.GetPositionRows()) { // We need to reference the security record for calculating proposed orders and the market value // of the trade. ClientMarketData.SecurityRow securityRow = ClientMarketData.Security.FindBySecurityId(driverSecurity.SecurityId); // In this rebalancing operation, the cash balance is dependant on the securities bought and // sold. When stocks are bought or sold below, they will impact the underlying currency. A cash // target can be reached by setting all the other percentages up properly. As long as the total // percentage in a model is 100%, the proper cash target will be calculated. We don't have to do // anything with this asset type. if (securityRow.SecurityTypeCode == SecurityType.Currency) { continue; } // The ratio of the security within the sector will stay constant, even though the sector may // increase or decrease with the target in the model. Note that there's only one account in the // 'Accounts' table of the driver because this is a 'Wrap' operation. foreach (AppraisalSet.AccountRow driverAccount in driverPosition.GetAccountRows()) { // Find the account associated with the driver record. ClientMarketData.AccountRow accountRow = ClientMarketData.Account.FindByAccountId(driverAccount.AccountId); // The market value of all the securities are normalized to the base currency of the account // so they can be aggregated. ClientMarketData.CurrencyRow currencyRow = ClientMarketData.Currency.FindByCurrencyId(accountRow.CurrencyId); // Sector rebalancing keeps the percentage of a security within the sector constant. Only the // overall percentage of the sector with respect to the NAV changes. The first step in this // rebalancing operation is to calculate the market value of the given position. decimal actualPositionMarketValue = MarketValue.Calculate(currencyRow, accountRow, securityRow, driverPosition.PositionTypeCode, MarketValueFlags.EntirePosition); // The target market value operation keeps the percentage of the position constant while // changing the overall sector percentage. decimal targetPositionMarketValue = (actualSectorMarketValue == 0) ? 0.0M : actualPositionMarketValue * targetSectorMarketValue / actualSectorMarketValue; // Calculate the market value of an order that will achieve the target. Note that we're not // including the existing proposed orders in the market value, but we did include them when // calculating the account's market value. This allows us to put in what-if orders that will // impact the market value before we do the rebalancing. decimal proposedMarketValue = targetPositionMarketValue - MarketValue.Calculate(currencyRow, accountRow, securityRow, driverPosition.PositionTypeCode, MarketValueFlags.ExcludeProposedOrder); // Calculate the quantity needed to hit the target market value and round it according to the // model. Note that the market values and prices are all denominated in the currency of the // parent account. Also note the quantityFactor is needed for the proper quantity // calculation. decimal proposedQuantity = proposedMarketValue / (Price.Security(currencyRow, securityRow) * securityRow.QuantityFactor); // If we have an equity, round to the model's lot size. if (securityRow.SecurityTypeCode == SecurityType.Equity) { proposedQuantity = Math.Round(proposedQuantity / modelRow.EquityRounding, 0) * modelRow.EquityRounding; } // A debt generally needs to be rounded to face. if (securityRow.SecurityTypeCode == SecurityType.Debt) { proposedQuantity = Math.Round(proposedQuantity / modelRow.DebtRounding, 0) * modelRow.DebtRounding; } // Have the OrderForm object construct an order based on the quantity we've calcuated // from the market value. This will fill in the defaults for the order and translate the // signed quantities into transaction codes. ProposedOrder.Create(remoteBatch, remoteTransaction, accountRow, securityRow, driverAccount.PositionTypeCode, proposedQuantity); } } } // Recurse into each of the sub-sectors. This allows us to rebalance with any number of levels to the // hierarchy. Eventually, we will run across a sector with security positions in it and end up doing some // real work. foreach (AppraisalSet.SectorRow childSector in objectTreeRow.ObjectRowByFKObjectObjectTreeChildId.GetSectorRows()) { SectorWrap.RecurseSectors(remoteBatch, remoteTransaction, modelRow, childSector, actualSectorMarketValue, targetSectorMarketValue); } } }
public void Initialize(Account account, Security security, TransactionType transactionType, TIF tif, PricedAt pricedAt, decimal quantity, object price1, object price2) { // Create a block order on the server. RemoteBatch remoteBatch = new RemoteBatch(); RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add("Service.Trading"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Trading.Order"); RemoteMethod remoteMethod = remoteType.Methods.Add("Insert"); remoteMethod.Parameters.Add("orderId", DataType.Int, Direction.ReturnValue); remoteMethod.Parameters.Add("accountId", account.AccountId); remoteMethod.Parameters.Add("securityId", security.SecurityId); remoteMethod.Parameters.Add("settlementId", security.SettlementId); remoteMethod.Parameters.Add("transactionTypeCode", (int)transactionType); remoteMethod.Parameters.Add("timeInForceCode", (int)tif); remoteMethod.Parameters.Add("orderTypeCode", (int)pricedAt); remoteMethod.Parameters.Add("quantity", quantity); remoteMethod.Parameters.Add("price1", price1 == null ? (object)DBNull.Value : price1); remoteMethod.Parameters.Add("price2", price2 == null ? (object)DBNull.Value : price2); ClientMarketData.Execute(remoteBatch); // Now that the block order is created, construct the in-memory version of the record. int orderId = (int)remoteMethod.Parameters["orderId"].Value; try { // Lock the tables. Debug.Assert(!ClientMarketData.AreLocksHeld); ClientMarketData.AccountLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.OrderLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.ObjectLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.SecurityLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.OrderRow orderRow = ClientMarketData.Order.FindByOrderId(orderId); if (orderRow == null) { throw new Exception(String.Format("Order {0} doesn't exist", orderId)); } Initialize(orderRow); } finally { // Release the table locks. if (ClientMarketData.AccountLock.IsReaderLockHeld) { ClientMarketData.AccountLock.ReleaseReaderLock(); } if (ClientMarketData.OrderLock.IsReaderLockHeld) { ClientMarketData.OrderLock.ReleaseReaderLock(); } if (ClientMarketData.ObjectLock.IsReaderLockHeld) { ClientMarketData.ObjectLock.ReleaseReaderLock(); } if (ClientMarketData.SecurityLock.IsReaderLockHeld) { ClientMarketData.SecurityLock.ReleaseReaderLock(); } Debug.Assert(!ClientMarketData.AreLocksHeld); } }