🕵️ SicherheitslückenWhat continuous operational resilience looks like under DORA(09.09.2026 um 17:53 Uhr)
🔧 AI Nachrichten OpenAI seeks tougher AI rules. CIOs may feel the ripple effects(10.09.2026 um 12:11 Uhr)
🔧 AI Nachrichten Mistral valued at €21bn after €3bn Series D funding round(08.09.2026 um 10:19 Uhr)
🪟 Windows TippsWindows XP's Cursor Indicator Is Getting a Windows 11 Refresh(25.08.2026 um 13:00 Uhr)
🕵️ SicherheitslückenWhat continuous operational resilience looks like under DORA(09.09.2026 um 17:53 Uhr)
🔧 AI Nachrichten OpenAI seeks tougher AI rules. CIOs may feel the ripple effects(10.09.2026 um 12:11 Uhr)
🔧 AI Nachrichten Mistral valued at €21bn after €3bn Series D funding round(08.09.2026 um 10:19 Uhr)
🪟 Windows TippsWindows XP's Cursor Indicator Is Getting a Windows 11 Refresh(25.08.2026 um 13:00 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 5 Min Lesezeit
0

Rebalancing a portfolio without selling anything: the contribution algorithm

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

How to split a monthly contribution across several assets to pull a portfolio back toward its targets — without selling anything or paying taxes.




Every long-term investor hits the same wall: you set a target allocation (say 40% stocks, 25% fixed income, 20% REITs, 15% international), the market moves, and three months later your portfolio is lopsided. The classic advice is to rebalance by selling what went up and buying what went down.



The problem: selling triggers taxes, brokerage fees, and — here in Brazil — the headache of filing a DARF. There's a far cheaper way to rebalance if you contribute every month: steer the new money toward the categories that are lagging. Without selling a single share.



This post walks through the algorithm I used for that. It's subtler than it looks.






The naive temptation (which is wrong)



Everyone's first idea is: "I have $1,000 to invest, I'll split it by the targets." 40% goes to stocks, 25% to fixed income, and so on.




CODE
# WRONG
for category in categories:
budget[category] = contribution * category.target_pct / 100






This rebalances nothing — it just perpetuates the current allocation. If your stocks are already at 50% (above the 40% target), throwing another 40% of the contribution at them makes the drift worse.



What you actually want is the opposite: give more money to whoever is furthest behind the target.






The core concept: the gap



For each category, compute how much it should be worth after the contribution, and how far it is from getting there:




CODE
new_total = current_total + contribution

def gap(category):
target_value = new_total * category.target_pct / 100
return max(0, target_value - category.current_value)






The max(0, ...) matters: categories that have already passed the target have a gap of zero — they get nothing from the contribution (and they naturally drift back toward the target as everything else grows around them).



Now distribute the budget proportionally to the gaps, not to the targets:




CODE
total_gap = sum(gap(c) for c in categories)

for category in categories:
budget[category] = contribution * gap(category) / total_gap






That's it — the money flows on its own toward the underweight categories. The further behind a category is, the bigger the slice it gets. When everything is on target, the gaps equalize and the contribution splits proportionally just like the naive case (which, in that case, is the correct behavior).




Edge case: if every category is already at or above target, total_gap == 0 and you'd hit a division by zero. Fall back to distributing by target — there's nothing to correct, just keep the proportions.







The annoying problem: shares are indivisible



So far it's clean arithmetic. Reality breaks it: you don't buy "$213.47 of PETR4". You buy a whole number of shares.




CODE
def buy_suggestion(asset, budget):
quantity = int(budget / asset.price) # rounds down
if quantity <= 0:
return None
cost = quantity * asset.price
return {'ticker': asset.ticker, 'quantity': quantity, 'cost': cost}






That int() throws away the leftovers from each asset. Add it up across a 15-asset portfolio and you can easily leave $150 of the contribution sitting idle. Unacceptable — the user wants to see the money allocated.






Spending the change: the greedy second pass



The fix is a second pass that takes whatever is left and spends it greedily, always on the category with the largest remaining deficit:




CODE
def spend_remaining(remaining, assets, simulated):
while remaining > 0:
best = None
largest_deficit = 0

for asset in assets:
if asset.price > remaining: # can't afford even one unit
continue
deficit = asset.category.target_value - simulated[asset.category]
if deficit > largest_deficit:
largest_deficit = deficit
best = asset

if best is None: # nothing else fits the change
break

remaining -= best.price # buy +1 unit
simulated[best.category] += best.price
record_buy(best, quantity=1)

return remaining






Each loop iteration buys one unit of the asset whose category is furthest behind, updates the simulated deficit, and repeats. It stops when the leftover can't buy even the cheapest share. This squeezes the contribution down to the last possible dollar, without ever breaking the target logic.



(Fixed income is easier: since it's divisible, you can allocate exact cents — no whole-number problem.)






Selling, only when you choose to



Sometimes the drift is too large to fix with contributions alone. Then the user opts in to rebalance by selling. The excess calculation is the mirror image of the gap:




CODE
def sell_suggestions(category):
excess = category.current_value - category.target_value
if excess <= 0:
return []
# sell from each asset proportionally to its weight in the category
sells = []
for asset in category.assets:
slice = excess * (asset.current_value / category.current_value)
qty = int(slice / asset.price)
if qty > 0:
sells.append({'ticker': asset.ticker, 'quantity': qty})
return sells






And since this is Brazil, you can add an avoid_tax_sells=True flag that skips ETFs and REITs (tickers ending in 11, always taxed here) from the sell suggestions — so you rebalance touching only the tax-exempt holdings.






Why it matters



The result is that the investor opens the app, types "I'm going to invest $1,000," and gets a list like:




CODE
Buy   12 BOVA11   × $ 38.50   = $ 462.00
Buy 8 MXRF11 × $ 10.30 = $ 82.40
Add Nubank CD = $ 455.60
Leftover: $ 0.00






Everything allocated, the portfolio closer to its targets, and no taxes paid. That's the difference between "rebalancing" as a tedious quarterly chore and as something that happens on its own with every contribution.






I built this into Balance, an app that helps Brazilian investors keep their portfolio on target by computing exactly these suggestions on every contribution. The full service also handles crypto (8-decimal fractions), multiple markets, and tax reporting — but the heart of it is the algorithm above.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Sam Altman calls GPT-6 Astra rollout ‘messy’ as enterprise users wait for access
1 Quelle
Swiss government explores replacing Microsoft 365 with open-source software
1 Quelle
What continuous operational resilience looks like under DORA
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Rebalancing a portfolio without selling anything: the contribution algorithm

Thematisch verwandte Begriffe: Rebalancing, portfolio, without, selling · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...