A calculator which tells you how to split your investment amongst your portfolio's assets based on your target asset allocation.
Project description
Rebalance
A calculator that tells you how to split your investment amongst your portfolio's assets based on your target asset allocation.
To use it, install the package and write a portfolio JSON file as described below.
Installation
uv tool install rebalance
If you prefer a traditional package install instead of a uv-managed tool, use:
pip install rebalance
Release images are also published to GHCR:
podman pull ghcr.io/kallegrens/rebalance:v0.5.1
Usage
rebalance portfolios/my_portfolio.json
Set REBALANCE_OBJECTIVE to change the default optimizer objective for both
rebalance and rebalance-monitor. An explicit --objective flag still wins.
Container usage
The container image defaults to running rebalance-monitor /config/portfolio.json.
Mount your portfolio JSON at /config/portfolio.json and pass any additional
monitor flags after the image name.
podman run --rm \
-e REBALANCE_APPRISE_URLS='ntfys://ntfy.example.com/your-secret-topic' \
-v "$(pwd)/portfolios/my_portfolio.json:/config/portfolio.json:ro" \
ghcr.io/kallegrens/rebalance:latest
podman run --rm \
-e REBALANCE_APPRISE_URLS='ntfys://ntfy.example.com/your-secret-topic' \
-v "$(pwd)/portfolios/my_portfolio.json:/config/portfolio.json:ro" \
ghcr.io/kallegrens/rebalance:latest \
--trade-non-triggered
If you prefer a file-based Apprise config, mount it into the container and set
REBALANCE_APPRISE_CONFIG to the mounted path.
The published image is built with uv on top of Astral's official uv base
images and installs the runtime environment from uv.lock using
uv sync --locked --no-dev --no-editable, so deployments stay pinned to the
same fully resolved dependency set that CI validates.
For local rootless Podman builds, the host must also provide newuidmap and
newgidmap plus valid /etc/subuid and /etc/subgid entries for your user.
If Podman fails with exec: "newuidmap": executable file not found in $PATH,
install the host package that provides those binaries, for example
sudo apt-get install uidmap on Debian or Ubuntu.
For a preflighted local build, run:
just container-build
Scheduling the monitor
rebalance-monitor is a single-pass command: it fetches prices, evaluates the
band and leverage rules, sends any Apprise notifications, and exits. Run the
container every 15 minutes from cron, a systemd timer, Podman Quadlet, or a
Kubernetes CronJob. Do not keep the container running continuously unless you
intentionally wrap it in your own loop.
Notifications
rebalance and rebalance-monitor can send notifications through Apprise.
The recommended setup is Apprise in the app, a self-hosted ntfy destination for
iPhone push, and optional e-mail destinations in the same Apprise config.
The notification hooks read configuration in this order:
REBALANCE_APPRISE_URLSfor one or more whitespace-delimited Apprise URLsREBALANCE_APPRISE_CONFIGfor an explicit Apprise config file or URL- Apprise's default config discovery paths such as
~/.config/apprise.yml
If rebalance discovers a default Apprise config on its own and you did not set a
tag override, it sends with the Apprise tag rebalance so a shared global
Apprise config does not accidentally fan out unrelated notifications.
Quick start with a single ntfy destination:
export REBALANCE_APPRISE_URLS='ntfys://ntfy.example.com/your-secret-topic'
rebalance-monitor portfolios/my_portfolio.json
Recommended Apprise config for ntfy plus e-mail fan-out:
# ~/.config/apprise/apprise.conf
rebalance,rebalance-trigger=ntfys://ntfy.example.com/your-secret-topic
rebalance,rebalance-failure=ntfys://ntfy.example.com/your-secret-topic
rebalance,rebalance-failure=mailtos://_?user=alerts@example.com&pass=app-password&smtp=smtp.example.com&from=alerts@example.com&to=you@example.com
Optional routing overrides:
export REBALANCE_NOTIFY_TRIGGER_TAG=rebalance-trigger
export REBALANCE_NOTIFY_FAILURE_TAG=rebalance-failure
Privacy note for iPhone push: if you self-host ntfy and want instant iOS
delivery, ntfy still forwards a minimal poll request to ntfy.sh so Apple can
wake the phone. The full notification body stays on your ntfy server and is
fetched from there over HTTPS.
Band-aware monitoring
The monitor command checks configured volatility bands using the total portfolio
value, including cash. Additions and withdrawals should therefore be represented
in the portfolio cash before running the monitor. Assets use a default band
width of 1.5 sigma, where sigma is the asset's configured annualized
volatility.
Per asset, you can override the default symmetric band width with band_sigma,
or set lower_band_sigma and upper_band_sigma separately for asymmetric
bands.
When a band rebalance is triggered, assets with a 0 target allocation are sold
to zero and their proceeds become buy capacity. New positive-target assets aim
for their JSON target, triggered assets aim for their band tolerance midpoint,
and non-triggered existing assets are frozen by default to avoid unnecessary
trades. Any residual target budget is spread only across tradable positive-target
assets; frozen non-triggered assets stay frozen. If the tradable assets cannot
use all available cash without leaving their bands, the remainder stays as cash.
rebalance-monitor portfolios/my_portfolio.json
rebalance-monitor portfolios/my_portfolio.json --trade-non-triggered
rebalance-monitor portfolios/my_portfolio.json --withdrawal 300000 --json report.json
rebalance-monitor portfolios/my_portfolio.json --max-withdrawal --json report.json
Use --withdrawal AMOUNT to plan a withdrawal in the portfolio common currency.
The monitor sells enough assets to fund the withdrawal and, when Nordnet leverage
is configured, also reserves any credit repayment needed so the projected
post-trade debt remains inside the selected leverage policy. A negative common
currency cash balance without --withdrawal is treated as an already-recorded
withdrawal request; do not use both at the same time. Use --max-withdrawal to
estimate the largest no-new-credit withdrawal that the same planner can fund
while staying inside the policy.
Portfolio file format
Create a JSON file describing your portfolio:
{
"name": "My portfolio",
"selling_allowed": false,
"common_currency": "SEK",
"conversion_cost": 0.25,
"courtage_profile": "nordnet_germany_uk",
"cash": [
{"amount": 3000.0, "currency": "USD"},
{"amount": 200.0, "currency": "CAD"}
],
"assets": [
{"ticker": "XBB.TO", "quantity": 36, "target_allocation": 20, "volatility": 10.0},
{"ticker": "XIC.TO", "quantity": 64, "target_allocation": 20, "volatility": 15.0},
{"ticker": "ITOT", "quantity": 32, "target_allocation": 36, "volatility": 17.5},
{"ticker": "IEFA", "quantity": 8, "target_allocation": 20, "volatility": 17.5, "band_sigma": 2.5},
{"ticker": "IEMG", "quantity": 7, "target_allocation": 4, "volatility": 20.0, "lower_band_sigma": 1.5, "upper_band_sigma": 2.5}
]
}
volatility remains the annualized standard deviation used for band sizing.
If you omit all band sigma fields, the monitor uses 1.5 sigma. Set
band_sigma to override both sides symmetrically, or set lower_band_sigma
and upper_band_sigma to override each side independently.
Set courtage_profile to nordnet_germany_uk to apply the built-in foreign-market
Nordnet Sweden schedule in the portfolio common currency. Use
nordnet_stockholm for Swedish-listed stocks and exchange-traded products
such as Virtune; that profile uses the 1/39/69/99 schedule with Fast modeled
as a flat 99 fee. A per-asset courtage_profile overrides the portfolio
default, which is useful when the same portfolio mixes German or UK listings
with Swedish-listed products. Courtage is added on top of any
conversion_cost FX spread and is shown in verbose rebalance output under
the Courtage, Courtage Fee <CCY>, and FX Fee <CCY> columns. Assets marked
with fractional: true are treated as courtage-free mutual funds.
Nordnet leverage monitoring
rebalance-monitor --json PATH writes a JSON report on every run. When no band
has triggered, the trade rows are empty but the report still includes current
band statuses and leverage diagnostics.
Withdrawal-aware reports include top-level withdrawal_plan and, when requested,
max_withdrawal. Trade reports also include synthetic withdrawal_rows and
financing_rows entries so the external withdrawal and any Nordnet credit
repayment are visible next to the asset trades without being modeled as assets.
Nordnet margin debt is configured explicitly under leverage; do not represent
it as negative cash. Cash remains available for deposits, withdrawals, and normal
rebalancing capacity, while leverage analysis treats margin debt as a separate
liability.
{
"name": "My portfolio",
"common_currency": "SEK",
"leverage": {
"provider": "nordnet",
"margin_debt": {"amount": 370000.0, "currency": "SEK"},
"drawdown_from_ath_pct": 0.0,
"target_leverage": 1.37
},
"assets": [
{
"ticker": "0P00018JII.ST",
"quantity": 109,
"target_allocation": 1.9,
"lending_value": 80.0,
"extended_lending_value": 85.0,
"instrument_type": "fund"
}
]
}
For Nordnet's advanced-portfolio policy, the default target leverage is 1.37x.
The default fallback weighted lending value is 79%, and the tier-1 discount
limit is 40% of lending value, giving the article's fallback borrowing-ratio
ceiling of 31.6%. If assets define lending_value and
extended_lending_value, the monitor computes the current weighted lending value
from actual live holdings instead of using the fallback.
For Portfoljbelaning Plus level 1, the diversification check uses only approved
holdings, meaning positions with a positive ordinary lending_value. The
default caps match Nordnet's published rules: one approved stock or ETF may be
at most 20% of approved holdings, one approved fund may be at most 60%, and
the discount bracket is applied to positions whose ordinary lending_value is
at least 70%. If you want to model level 2 instead, override the JSON config
to use 25/75 issuer caps and a 60% discount limit.
Example output
Ticker Ask Quantity Amount Currency Old allocation New allocation Target allocation
to buy ($) (%) (%) (%)
---------------------------------------------------------------------------------------------------------------
XBB.TO 33.43 30 1002.90 CAD 17.52 19.99 20.00
XIC.TO 24.27 27 655.29 CAD 22.61 20.01 20.00
ITOT 69.38 10 693.80 USD 43.93 35.88 36.00
IEFA 57.65 20 1153.00 USD 9.13 19.88 20.00
IEMG 49.14 0 0.00 USD 6.81 4.24 4.00
Largest discrepancy between the new and the target asset allocation is 0.24 %.
Before making the above purchases, the following currency conversion is required:
1072.88 USD to 1458.19 CAD at a rate of 1.3591.
Remaining cash:
80.32 USD.
0.00 CAD.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rebalance-0.5.1.tar.gz.
File metadata
- Download URL: rebalance-0.5.1.tar.gz
- Upload date:
- Size: 196.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6aaa46334199c02ada936e949e33d5b4971bc980e3f397af0f883765ec8defe6
|
|
| MD5 |
6ba2cf80feddc3d8846d8ce8c7c56e5e
|
|
| BLAKE2b-256 |
9fee3f87b7ea0ed45c4346750a17634e8fa3237fd30d587b7596434b503551ed
|
File details
Details for the file rebalance-0.5.1-py3-none-any.whl.
File metadata
- Download URL: rebalance-0.5.1-py3-none-any.whl
- Upload date:
- Size: 94.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c9387734bc8a4a878e24629d70dcd924fe3480822239c9b8a2db3bae322aad3c
|
|
| MD5 |
9ab4ee66ed81ddfb57db5adc1ec238ed
|
|
| BLAKE2b-256 |
c8ac06ec3d23f017be8868140a21681a2e7389510c4202d71d22ec56bad4e816
|