A self-hosted portfolio optimizer that turns your market views into concrete position sizes — for stocks and options, using the Kelly criterion.
Every active investor has views: "this reaches $150 within a year", "that one is dead money with a small chance of a squeeze". The hard part is the next step — deciding how much of your portfolio each idea deserves. Bet too small and being right doesn't matter; bet too big and one mistake wipes out years of being right.
kellyfolio makes that sizing decision systematically:
- Write each view as scenarios — a few price points with probabilities: "60% it reaches $150, 25% it stays flat, 15% it drops to $80".
- Pull your real portfolio from Interactive Brokers — stocks, options, cash, multi-currency NAV.
- Simulate thousands of joint outcomes: each view becomes a full return distribution (skew and fat tails included), assets move together per historical correlations, options are re-priced with Black-Scholes.
- Optimize the whole portfolio at once: maximize expected long-run growth
E[ln(1 + w·r)]— the Kelly criterion — under your risk caps (per-position, leverage, per-underlying exposure).
The output is an actionable rebalance plan: target weight, dollar amount and share/contract count for every position, plus the diagnostics to sanity-check it (view-implied vs. market-implied returns, VaR, correlation health). Use it from the CLI or the bundled web UI.
- Views, not forecasts of parameters — each underlying gets a small set of price × probability scenarios at a horizon; μ, σ, skew and kurtosis are all derived from them.
- Shape-aware simulation — scenario skew/kurtosis is matched exactly via Johnson SU/SB transforms (Cornish-Fisher and plain lognormal available), applied per-marginal on top of a historical Gaussian copula with Ledoit-Wolf shrinkage.
- Options as first-class positions — implied vol is reverse-solved from each option's market mark; the optimizer sees the full non-linear payoff distribution, not a delta approximation.
- Fractional Kelly with hard caps —
kelly_fractionscales aggression whilemax_position_weight,max_leverageand the optional delta-adjustedmax_underlying_exposureremain hard, reachable ceilings. - Three view types —
continuous(trend),event(earnings dates, rulings),composite(trend + shocks). - Explainable —
kellyfolio portfolio explain TICKERwalks through every step from a continuous view's scenarios to its time-scaled moments (event/composite views are summarized); the web UI plots the exact fitted density the simulator samples from.
- uv (Python 3.12 managed automatically)
- Node.js 20+ (only for the web UI; CI builds on Node 22)
- An Interactive Brokers account with Flex Web Service enabled (for
pull-account); TWS/IB Gateway running is optional (live spots)
git clone <your-fork-or-clone-url> kellyfolio && cd kellyfolio
uv sync
cp .env.example .env # Windows: copy .env.example .envFill the two required secrets in .env (IBKR Client Portal → Settings → Flex Web Service: enable + generate a token; Settings → Flex Queries: create an Activity Flex Query including Open Positions, Cash Report and Net Asset Value (NAV), note its Query ID):
KELLYFOLIO_FLEX_TOKEN=your-token
KELLYFOLIO_FLEX_QUERY_ID=your-query-idThen:
uv run kellyfolio pull-account # snapshot → data/accounts/YYYY-MM-DD.yaml
uv run kellyfolio portfolio init # scaffold data/views.yaml.template from holdings
cp data/views.yaml.template data/views.yaml # Windows: copy data\views.yaml.template data\views.yaml
# edit data/views.yaml — write your scenarios
uv run kellyfolio portfolio run # solve + print the allocation report
uv run kellyfolio portfolio explain ACME # per-ticker μ/σ/shape derivationportfolio init writes a .template so it never clobbers an existing views.yaml; copy it once, then it is yours to edit (the web Views page edits data/views.yaml directly).
uv run kellyfolio serve # API on http://127.0.0.1:8000
cd web && npm install && npm run dev # UI on http://localhost:5173| Page | What it does |
|---|---|
| Dashboard | Account snapshot (NAV / cash / gross leverage) + positions table; one-click refresh from IBKR |
| Views | Visual views.yaml editor: global config, per-underlying scenarios with a live fitted-density chart, correlation overrides; atomic save with rolling backups |
| Runs | Trigger an optimization, poll status, browse history; allocation table + plain-text report |
| Explain | Step-by-step derivation from scenarios to simulated moments |
| Settings | Effective runtime configuration (read-only; secrets shown as configured / not) |
Three layers, by what the data is:
| What | Where | Committed? |
|---|---|---|
| Secrets + runtime settings (endpoints, ports, data dir) | .env / KELLYFOLIO_* env vars — see .env.example |
template only |
| Your judgement: scenarios, Kelly fraction, caps | data/views.yaml — edited via web UI or by hand |
never (gitignored) |
| Snapshots + run history | data/accounts/, data/runs/ |
never (gitignored) |
views.yaml ──► scenario moments (μ, σ, skew, kurt) per underlying
account.yaml ─► positions, NAV, cash │
▼
price history ──► Ledoit-Wolf correlation ──► correlated Monte Carlo
(Johnson-warped marginals)
│
option marks ──► implied vols ──► Black-Scholes ────▼
re-pricing ──► per-position return matrix
│
▼
SLSQP: max E[ln(1 + w·r)] s.t. caps ──► target weights
See docs/design.md for the full methodology — view types, distribution fitting, the fractional-Kelly box trick, code architecture, known approximations.
Bug reports and focused PRs welcome — see CONTRIBUTING.md for the development setup, test commands and ground rules, and the Architecture section of docs/design.md for how the code is organized.
- The web API is unauthenticated and binds to localhost by default. Do not expose it to untrusted networks — it can read your positions and trigger market-data pulls.
- Secrets live only in
.env(gitignored) and never round-trip through the browser; the Settings page reports them as configured / not configured. data/holds your account snapshots and views — it is gitignored; keep it that way.
kellyfolio is a research and decision-support tool, not investment advice. The Kelly criterion is aggressive and highly sensitive to input errors — garbage views in, confident garbage out. Use fractional Kelly, treat outputs as one input to your own judgement, and understand the model's known approximations (documented in docs/design.md) before trusting it with real money.