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