/// <summary> /// Creates a temporary copy of a model portfolio. /// </summary> /// <param name="modelRow">The original model record.</param> /// <returns>A batch of commands that will create a copy of the original model.</returns> private static ModelBatch CopyModel(ClientMarketData.ModelRow modelRow) { // Create the batch and fill it in with the assembly and type needed for this function. ModelBatch modelBatch = new ModelBatch(); RemoteAssembly remoteAssembly = modelBatch.Assemblies.Add("Service.Core"); RemoteType remoteType = remoteAssembly.Types.Add("Shadows.WebService.Core.Model"); // This method will insert a copy of the original model's header. RemoteMethod insertModel = remoteType.Methods.Add("Insert"); insertModel.Parameters.Add("modelId", DataType.Int, Direction.ReturnValue); insertModel.Parameters.Add("rowVersion", DataType.Long, Direction.Output); insertModel.Parameters.Add("objectTypeCode", modelRow.ObjectRow.ObjectTypeCode); insertModel.Parameters.Add("name", String.Format("Copy of {0}", modelRow.ObjectRow.Name)); insertModel.Parameters.Add("schemeId", modelRow.SchemeId); insertModel.Parameters.Add("algorithmId", modelRow.AlgorithmId); insertModel.Parameters.Add("temporary", true); insertModel.Parameters.Add("description", modelRow.ObjectRow.Description); // For a sector model, copy each of the sector level targets into the destination model. if (modelRow.ModelTypeCode == ModelType.Sector) { // The object Type for this operation. RemoteType sectorTargetType = remoteAssembly.Types.Add("Shadows.WebService.Core.SectorTarget"); // Add the position level target to the model. foreach (ClientMarketData.SectorTargetRow sectorTargetRow in modelRow.GetSectorTargetRows()) { RemoteMethod insertSector = sectorTargetType.Methods.Add("Insert"); insertSector.Parameters.Add("modelId", insertModel.Parameters["modelId"]); insertSector.Parameters.Add("sectorId", sectorTargetRow.SectorId); insertSector.Parameters.Add("percent", sectorTargetRow.Percent); } } // For a position model, copy each of the position level targets into the destination model. if (modelRow.ModelTypeCode == ModelType.Security) { // The object Type for this operation. RemoteType positionTargetType = remoteAssembly.Types.Add("Shadows.WebService.Core.PositionTarget"); // Add the position level target to the model. foreach (ClientMarketData.PositionTargetRow positionTargetRow in modelRow.GetPositionTargetRows()) { RemoteMethod insertSecurity = positionTargetType.Methods.Add("Insert"); insertSecurity.Parameters.Add("modelId", insertModel.Parameters["modelId"]); insertSecurity.Parameters.Add("securityId", positionTargetRow.SecurityId); insertSecurity.Parameters.Add("positionTypeCode", positionTargetRow.PositionTypeCode); insertSecurity.Parameters.Add("percent", positionTargetRow.Percent); } } // Save the reference to the 'modelId' return parameter. modelBatch.ModelIdParameter = insertModel.Parameters["modelId"]; // This batch will create a copy of the original model. return(modelBatch); }
/// <summary> /// Constructs a well formed, but empty, AppraisalDocument. /// </summary> public AppraisalDocument() { // Initialize the members. this.accountRow = null; this.modelRow = null; // Create the root element and add it to the document. this.AppendChild(new AppraisalElement(this)); }
/// <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> /// 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> /// 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> /// 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); } } }
/// <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); } } }
/// <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); } } }
/// <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> /// 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> /// Chooses or creates a model for the appraisal. /// </summary> /// <param name="accountId">The account used to select a model.</param> public static int SelectModel(int accountId) { // The logic in this method will determine if a temporary model is needed and built it. If a temporary model is // required, it will be built using this command batch. In all cases, the appropriate model for the given account will // be returned to the caller. In some cases, a model will be constructed on the fly from the existing values in the // account. These temporary models will use most of the position and trading tables ModelBatch modelBatch = null; try { // Lock the tables Debug.Assert(!ClientMarketData.AreLocksHeld); ClientMarketData.AccountLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.AllocationLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.CurrencyLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.DebtLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.EquityLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.ModelLock.AcquireWriterLock(CommonTimeout.LockWait); ClientMarketData.SectorTargetLock.AcquireWriterLock(CommonTimeout.LockWait); ClientMarketData.PositionTargetLock.AcquireWriterLock(CommonTimeout.LockWait); ClientMarketData.ObjectLock.AcquireWriterLock(CommonTimeout.LockWait); ClientMarketData.ObjectTreeLock.AcquireWriterLock(CommonTimeout.LockWait); ClientMarketData.OrderLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.PriceLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.ProposedOrderLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.SchemeLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.SectorLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.SecurityLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.TaxLotLock.AcquireReaderLock(CommonTimeout.LockWait); ClientMarketData.TransactionTypeLock.AcquireReaderLock(CommonTimeout.LockWait); // Find the account record that is being opened. ClientMarketData.AccountRow accountRow = ClientMarketData.Account.FindByAccountId(accountId); if (accountRow == null) { throw new Exception(String.Format("Account {0} has been deleted", accountId)); } // The objective is to find out whether a 'Self' model must be created from the existing positions, or whether a // an empty or a copy of a model is required to view an account appraisal. The first test is to see whether any model // has been assigned to the account. if (accountRow.IsModelIdNull()) { // This will create an empty position model for the appraisal. modelBatch = Models.CreateEmptyModel(accountRow); } else { // At this point, a model has been assigned to the account. Get the model and find out if a temporary copy // needs to be made. ClientMarketData.ModelRow modelRow = ClientMarketData.Model.FindByModelId(accountRow.ModelId); if (modelRow == null) { throw new Exception(String.Format("Model {0} has been deleted", accountRow.ModelId)); } // A 'self' model is one that requires a calculation of the current positions. if (!modelRow.SectorSelf && !modelRow.SecuritySelf) { // Currently, the existing model is used on an appraisal. Any changes to the model in the appraisal view // will be stored in the persistent model. It may be useful sometime in the future to make a copy of the // model and prompt the user to save it when the appraisal is closed. return(modelRow.ModelId); } else { // Make sure that the account has been assigned a scheme before attempting to build a model from it. if (accountRow.IsSchemeIdNull()) { throw new Exception(String.Format("No scheme has been assigned to account {0}.", accountRow)); } // If the account has a default scheme, make sure it still exists. ClientMarketData.SchemeRow schemeRow = ClientMarketData.Scheme.FindBySchemeId(accountRow.SchemeId); if (schemeRow == null) { throw new ArgumentException("This scheme has been deleted", accountRow.SchemeId.ToString()); } // Create a model based on the current sector totals. if (modelRow.SectorSelf) { modelBatch = Models.CreateSectorSelfModel(accountRow, schemeRow); } // Create a model based on the current position totals. if (modelRow.SecuritySelf) { modelBatch = Models.CreatePositionSelfModel(accountRow, schemeRow); } } } } finally { // Release the table locks. if (ClientMarketData.AccountLock.IsReaderLockHeld) { ClientMarketData.AccountLock.ReleaseReaderLock(); } if (ClientMarketData.AllocationLock.IsReaderLockHeld) { ClientMarketData.AllocationLock.ReleaseReaderLock(); } if (ClientMarketData.CurrencyLock.IsReaderLockHeld) { ClientMarketData.CurrencyLock.ReleaseReaderLock(); } if (ClientMarketData.DebtLock.IsReaderLockHeld) { ClientMarketData.DebtLock.ReleaseReaderLock(); } if (ClientMarketData.EquityLock.IsReaderLockHeld) { ClientMarketData.EquityLock.ReleaseReaderLock(); } if (ClientMarketData.ModelLock.IsWriterLockHeld) { ClientMarketData.ModelLock.ReleaseWriterLock(); } if (ClientMarketData.SectorTargetLock.IsWriterLockHeld) { ClientMarketData.SectorTargetLock.ReleaseWriterLock(); } if (ClientMarketData.PositionTargetLock.IsWriterLockHeld) { ClientMarketData.PositionTargetLock.ReleaseWriterLock(); } if (ClientMarketData.ObjectLock.IsWriterLockHeld) { ClientMarketData.ObjectLock.ReleaseWriterLock(); } if (ClientMarketData.ObjectTreeLock.IsWriterLockHeld) { ClientMarketData.ObjectTreeLock.ReleaseWriterLock(); } if (ClientMarketData.OrderLock.IsReaderLockHeld) { ClientMarketData.OrderLock.ReleaseReaderLock(); } if (ClientMarketData.PriceLock.IsReaderLockHeld) { ClientMarketData.PriceLock.ReleaseReaderLock(); } if (ClientMarketData.ProposedOrderLock.IsReaderLockHeld) { ClientMarketData.ProposedOrderLock.ReleaseReaderLock(); } if (ClientMarketData.SchemeLock.IsReaderLockHeld) { ClientMarketData.SchemeLock.ReleaseReaderLock(); } if (ClientMarketData.SectorLock.IsReaderLockHeld) { ClientMarketData.SectorLock.ReleaseReaderLock(); } if (ClientMarketData.SecurityLock.IsReaderLockHeld) { ClientMarketData.SecurityLock.ReleaseReaderLock(); } if (ClientMarketData.TaxLotLock.IsReaderLockHeld) { ClientMarketData.TaxLotLock.ReleaseReaderLock(); } if (ClientMarketData.TransactionTypeLock.IsReaderLockHeld) { ClientMarketData.TransactionTypeLock.ReleaseReaderLock(); } Debug.Assert(!ClientMarketData.AreLocksHeld); } // At this point, a batch is ready to be sent that will create the model and populate it with target values. The data // structure is an overloaded version of the 'RemoteBatch' class. The 'ModelBatch' contains a member which references // the 'modelId' return value from the creation of the model. This value will be returned to the caller as a reference // to the temporary model. ClientMarketData.Send(modelBatch); // Rethrow a generic error message for the failed model. if (modelBatch.HasExceptions) { throw new Exception("Can't create model."); } // Return the model identifier generated by the server. return((int)modelBatch.ModelIdParameter.Value); }
/// <summary> /// Constructs an AppraisalDocument. /// </summary> /// <param name="accountRow">A record containing the account or fund data.</param> /// <param name="modelRow">A record containing the model that is superimposed on the appraisal data for /// rebalancing.</param> public AppraisalDocument(ClientMarketData.AccountRow accountRow, ClientMarketData.ModelRow modelRow) { // Create a view of the proposed orders that makes it easy to aggregate by position. this.proposedOrderView = new DataView(ClientMarketData.ProposedOrder); this.proposedOrderView.Sort = "[AccountId], [SecurityId], [PositionTypeCode]"; // Create a view of the orders that makes it easy to aggregate by position. this.orderView = new DataView(ClientMarketData.Order); this.orderView.Sort = "[AccountId], [SecurityId], [PositionTypeCode]"; // Create a view of the allocations that makes it easy to aggregate by position. this.allocationView = new DataView(ClientMarketData.Allocation); this.allocationView.Sort = "[AccountId], [SecurityId], [PositionTypeCode]"; // This makes the account and model avaiable to the recursive methods. this.accountRow = accountRow; this.modelRow = modelRow; // Create the root element and add it to the document. AppraisalElement rootElement = new AppraisalElement(this); this.AppendChild(rootElement); // Create and populate the DataSet that represents the outline of the appraisal. This function creates a // set of linked structures that contains only the sectors, securities and positions that will appear in // this document. This driver is built from the bottom up, meaning that we start with the tax lots, // proposed orders, orders and allocations associated with the given account and build the driver up to // the topmost security classification scheme. The alternative -- starting with the classification scheme // and building down until we join with the tax lots, etc, -- turned out to be six times slower. this.appraisal = new Common.Appraisal(accountRow, modelRow, true); // This sector is a catch-all heading for securities not mapped to the given hierarchy. If everything is // mapped, then this sector won't appear in the document. SectorElement unclassifiedSector = null; // Now that the driver is built, we can begin constructing the document. The first section is the // 'Unclassified' sector. This section catches all securities that aren't explicitly mapped to the // hierarchy. This is important because classification schemes are not guaranteed to map every security. // Without this section, those unmapped securities wouldn't appear on the appraisal and wouldn't be // included in the NAV calculation. That is very bad. Note also that we skip over the classification // scheme record during the check. We know that the security classification scheme is at the top of the // hierarchy and won't have any parents. Every other record that doesn't have a parent in the hierarchy // is 'Unclassified'. foreach (AppraisalSet.SecurityRow parentSecurity in this.appraisal.Security) { if (parentSecurity.ObjectRow.GetObjectTreeRowsByFKObjectObjectTreeChildId().Length == 0) { // If the document doesn't have an 'Unclassified' sector yet, then add the sector heading. All // secutiries that are not mapped to the given hierarchy will appear under this catch-all header. if (unclassifiedSector == null) { rootElement.InsertBySortOrder(unclassifiedSector = new SectorElement(this)); } // Attach the each of the unclassified securities to the unclassified sector heading. BuildDocument(unclassifiedSector, parentSecurity.ObjectRow); } } // The report is built recursively. The 'AppraisalSet', constructed above, represents an 'inner join' of the // hierarchy information to the active position information. We'll begin traversing the 'AppraisalSet' from // the top level security: a single node representing the classification scheme. foreach (AppraisalSet.SchemeRow schemeRow in this.appraisal.Scheme) { foreach (AppraisalSet.ObjectTreeRow objectTreeRow in schemeRow.ObjectRow.GetObjectTreeRowsByFKObjectObjectTreeParentId()) { BuildDocument(rootElement, objectTreeRow.ObjectRowByFKObjectObjectTreeChildId); } } }