Skip to content

Instantly share code, notes, and snippets.

@jcayzac
Last active July 27, 2022 10:22
Show Gist options
  • Save jcayzac/43add41238580ceef7012c05ac1e7d93 to your computer and use it in GitHub Desktop.
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.
/**
* 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