Deposit-Only Portfolio Rebalancing: Algorithm for Tax-Free Convergence
These articles are AI-generated summaries. Please check the original sources for full details.
Rebalancing a portfolio with only your next deposit (no selling)
Diego, solo developer of Balance, formalizes a rebalancing algorithm using only new deposits. The algorithm avoids selling by distributing a $1,000 contribution proportionally to category gaps, converging portfolios without taxable events.
Why This Matters
Standard portfolio rebalancing requires selling overweight assets, incurring capital gains taxes, brokerage fees, and emotional friction. In practice, this discourages regular rebalancing—investors either let allocations drift dangerously or pay unnecessary costs. Formalizing a deposit-driven algorithm that distributes new funds to underweight categories eliminates those costs entirely, making disciplined rebalancing accessible to anyone making recurring contributions.
Key Insights
- Gap-based distribution: Only positions below their post-deposit target compete for new money, using max(0, target - current) to isolate deficits (Balance, 2026).
- Edge case handling: If total_gap == 0 (all categories at or above target), algorithm splits deposit by target percentages to avoid division by zero (Balance app).
- Market-specific quantization: Stocks buy whole units via int(), crypto buys fractions to 8 decimal places via quantize(), enabling a single engine for stock and Bitcoin portfolios.
- Leftover deployment: A greedy second pass buys additional shares or crypto fractions for the most-underweight category until the deposit is fully used (Balance, 2026).
- Tax-aware sales: For Brazilian investors, avoid_ir_sells flag excludes always-taxed ETFs/REITs (suffix 11) from sale suggestions, selling only tax-exempt stocks under R$20k/month.
Working Examples
Core algorithm: calculates budget per category by distributing deposit proportionally to gaps (target minus current value), ensuring overweight categories get zero budget.
from collections import defaultdict
from decimal import Decimal
def calculate(self, deposit_amount: Decimal) -> dict:
deposit = Decimal(deposit_amount).quantize(Decimal('0.01'))
investments = self._category_investments()
current_total = self.portfolio.total_value
new_total = current_total + deposit
cat_value = defaultdict(lambda: Decimal('0'))
for cat_id, rows in investments.items():
for kind, item in rows:
cat_value[cat_id] += item.current_value
gaps = {}
for cat_id, rows in investments.items():
category = rows[0][1].category
target_value = new_total * category.target_percentage / Decimal('100')
gaps[cat_id] = max(Decimal('0'), target_value - cat_value[cat_id])
total_gap = sum(gaps.values())
cat_budget = {
cat_id: (deposit * gaps[cat_id] / total_gap).quantize(Decimal('0.01'))
for cat_id in investments
}
...
Converts budget to orders: whole shares for stocks, fractions down to 8 decimal places for crypto.
def _buy_suggestion(self, kind, item, budget: Decimal):
if budget <= 0:
return None
if self.portfolio.market == 'CRYPTO':
quantity = (budget / item.current_price).quantize(Decimal('0.00000001'))
else:
quantity = int(budget / item.current_price)
if quantity <= 0:
return None
cost = (quantity * item.current_price).quantize(Decimal('0.01'))
return self._asset_suggestion_row(item, quantity, cost)
Practical Applications
- (Balance / recurring contributors): Use deposit-only rebalancing for monthly infusions to avoid brokerage fees and capital gains taxes; pitfall is ignoring rounding-locks that leave idle cash if not handled with greedy repurposing.
- (Brazilian investors with Balance): Leverage avoid_ir_sells flag to rebalance by selling only tax-exempt assets (stocks under R$20k/month), keeping ETFs/REITs untouched; pitfall is triggering taxable events by selling always-taxed assets without flag.
- (Event: monthly deposit day for any portfolio): Formalize the ‘spreadsheet and guess’ approach into an algorithm that calculates gaps automatically; pitfall is overweight categories drifting so far that deposits alone can’t correct, requiring optional sale enforcement.
References:
Continue reading
Next article
Microsoft and Overture Maps Foundation Unite to Standardize Global Spatial Data
Related Content
scrape-sentinel: A Standard-Library Change Detection Layer for Web Scraping
Vinicius Pereira releases scrape-sentinel, a zero-dependency Python library that detects changes in scraped data, handling key-based matching and atomic snapshots.
4 FastAPI Projects in 2 Weeks: The Hidden Cost of Boilerplate and 15 CI Failures
After 134 Python files and 15 CI failures, a developer reveals the repetitive plumbing wasting hours in FastAPI backends.
The Bug That Taught Me How to Run One Process Per User
A deploy wiped an in-memory registry causing false stoppage reports; duplicating workers and zombie processes exposed critical lifecycle flaws.