Last active
July 27, 2022 10:22
-
-
Save jcayzac/43add41238580ceef7012c05ac1e7d93 to your computer and use it in GitHub Desktop.
Google App Script function implementing buy-and-hold rebalancing by splitting a cash inflow into the most meaningful contributions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Implements buy-and-hold rebalancing by splitting some cash inflow into the | |
* most meaningful contributions: | |
* | |
* The buckets whose valuations have fallen the most behind their target weight | |
* are contributed to first. Then, contributions to the buckets with the largest | |
* inflow from the previous step are prioritized, as to minimize the number of | |
* transactions and thus transaction fees. | |
* | |
* @param values Range with the current valuation of the portfolio's constituents. | |
* @param targets Target weights of the constituents. | |
* @param cash Amount to contribute. | |
* @returns How much to contribute, for each constituent. | |
*/ | |
function CONTRIBUTE(values, targets, cash) { | |
// Sanitize input. | |
values = values.map(x => Number.parseFloat(x)) | |
targets = targets.map(x => Number.parseFloat(x)) | |
cash = Number.parseFloat(cash) | |
// Total value of the portfolio before adding cash. | |
const preTotal = values.reduce((total, value) => total + value, 0) | |
// Total value of the portfolio after adding cash. | |
const postTotal = preTotal + cash | |
// Sort current valuations by how far behind they are from their target. | |
const thirsty = values | |
.map((value, index) => [ index, Math.max(0, preTotal * targets[index] - value) ]) | |
.sort((a, b) => b[1]-a[1]) | |
// Fill the most thirsty buckets first. | |
const contributions = [] | |
for (const [index, cashThirst] of thirsty) { | |
const contribution = Math.min(cashThirst, cash) | |
contributions[index] = [index, contribution] | |
cash -= contribution | |
} | |
// Sort buckets so that those with the largest pending contribution are at the top. | |
// We'll add money to those first, in order to maximize the transaction amount per | |
// unit and thus decrease transaction costs. | |
contributions.sort((a, b) => b[1] - a[1]) | |
// Compute contributions for the target portfolio value with the remaining cash. | |
return contributions.map(([index, pendingContribution]) => { | |
const target = postTotal * targets[index] | |
const current = (values[index] + pendingContribution) | |
const contribution = Math.max(0, Math.min(target - current, cash)) | |
const finalContribution = pendingContribution + contribution | |
cash -= contribution | |
return [index, finalContribution] | |
}).reduce((o, [i, v]) => { o[i]=v; return o }, []) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment