Portfolio allocation
The portfolio manager decides how much weight to give each symbol, using a constraint solver (Google OR-Tools) rather than ad-hoc rules. It maximizes total expected score subject to hard constraints.
quantred allocate --scanner demo_volume --symbols NVDA,META,TSLA,AMD \
--capital 100000 --max-positions 5 --max-weight 0.25
Requires the portfolio extra (see optional extras).
What it solves
Each scanned symbol becomes a candidate with a score (here, its trailing return — a transparent, swappable factor) and a price. The solver chooses weights to maximize the score-weighted allocation subject to:
- invest at most 100% of capital,
- hold at most
--max-positionsnames, - cap any single name at
--max-weight.
Example output:
SYMBOL WEIGHT DOLLARS SHARES
NVDA 25.0% 25,000.00 190
META 25.0% 25,000.00 51
TSLA 20.0% 20,000.00 80
Choosing the factor
The CLI scores by trailing return, but the allocator accepts any score. Swap in momentum, inverse volatility, a model's expected return, or signal strength — see Portfolio (engineering) for the model and how to change the scoring factor.
Mean-variance construction (--objective utility)
The default allocate is a scalar-score sizer. --objective utility instead builds
the risk-adjusted, cost-aware portfolio from a strategy's alpha and the
covariance Σ — maximizing αᵀw − λ·wᵀΣw − cost(Δw) at a target tracking
error. It is a read-only research proposal (it places no orders):
quantred allocate --objective utility \
--strategy demo_trend --symbols NVDA,AAPL,META,AMD,TSLA,GOOG,MSFT,AMZN \
--as-of 2025-06-01 --target-te 0.04 --max-names 20 --capital 1000000
Portfolio for 'demo_trend' as of 2025-06-01 (target TE 4%, cost-aware)
IR* 0.81 predicted TE 3.9% predicted IR 0.74 transfer coef 0.91 turnover 18.0%
net active return 3.62%/yr (round-trip) = gross 3.85% − round-trip cost 0.23%
this rebalance: turnover cost 0.11%/yr one-way (linear 0.08% + √-impact 0.03%); one-way net 3.74%
capacity ≈ $340,000,000 (where √-impact erases the alpha)
SYMBOL WEIGHT DOLLARS SHARES
NVDA 25.0% 250,000.00 190
AMD 18.0% 180,000.00 142
...
Read it as: IR* is the best information ratio achievable from these alphas and
this Σ, before cost; the transfer coefficient is how much of it survives your
constraints and cost (tighten --max-names/--max-weight, or trade a costlier
book, and watch it fall); predicted IR ≈ TC · IR*. net active return is
the headline, net of a round-trip cost haircut on the held book; the one-way
line below it is the detail — exactly what this rebalance's turnover cost, split
into linear (commission + spread) and square-root impact. See
Portfolio construction (engineering) for
the math.
The capacity line is a number only when one was solved for. When the expected
gross alpha is not positive, or does not cover cost even at the smallest capital
searched, or still covers it at the largest, the line says which of those happened
in words (the payload carries capacity_status and a null capacity_capital) -
never $0 or a search bound that reads as a measured capital.
Cost-aware is the default — with --capital set, the objective carries a
name-specific linear turnover cost and a square-root market-impact term, and a
no-trade band emerges from each name's own cost (cheap, liquid names trade
freely; expensive ones need a clearer signal to move). Pass --gross-objective
to drop cost from the objective (it's still reported ex-post) — useful for
seeing how much the cost-aware solve actually bought you. --holding-period
(years, default 1/12) sets the amortization horizon for the in-objective cost.
--neutralize-factors builds the book from factor-neutral alphas (bare flag =
market,volatility,size; momentum kept as a deliberate return tilt) — see
Ranking by alpha for the semantics and the
honesty warning when exposures are unavailable.
Benchmark-relative construction
--benchmark-holdings equal (or a symbol,weight CSV/JSON file) makes the
benchmark a genuine portfolio, not just a beta/vol return series. Tracking
error, alpha neutralization, and the transfer coefficient all move into active
space (w_a = w − w_B):
quantred allocate --objective utility --strategy demo_trend \
--symbols NVDA,AAPL,META,AMD,TSLA,GOOG,MSFT,AMZN --as-of 2025-06-01 \
--benchmark-holdings equal --benchmark-premium 0.05
The report adds active beta, residual risk (the ψ² = β_a²σ_B² + ω² split),
and a consensus-returns block — the reverse-optimized expected returns for
which w_B is itself mean-variance optimal, so you can see what your alphas are
really deviations from. Without --benchmark-holdings everything stays
cash-relative, byte-for-byte unchanged. See
Portfolio construction — benchmark-relative.
Long/short (--book market-neutral)
The long-only box forces every unattractive name to a forced underweight it
can't relax below zero. --book market-neutral relaxes the box to
[−short-max-weight, max-weight] and the budget to Σw = 0; a gross-leverage
cap is then mandatory (an unconstrained long/short book on a noisy Σ is a
leverage machine):
quantred allocate --objective utility --strategy demo_trend \
--symbols NVDA,AAPL,META,AMD,TSLA,GOOG,MSFT,AMZN --as-of 2025-06-01 \
--book market-neutral --gross-leverage 2.0 --short-max-weight 0.25
The report adds the dollar-neutral residual, realized gross leverage vs the
cap, and the short book's borrow carry. --longshort-report solves the same
alphas/Σ/cost both ways and prices the long-only constraint directly: the
measured IR shrinkage, both transfer coefficients, and the long-only book's
incidental size tilt. See
Portfolio construction — long/short.
Conditional risk (--conditional)
--conditional ewma or --conditional har conditions Σ's volatilities on
recent history before the solve, so --target-te is measured against current
risk rather than a flat trailing-window average. Default off — see
Estimating risk — conditional risk for the evidence
gate that decides whether it's worth turning on for your data.
Black–Litterman (--posterior bl)
--posterior bl blends the refined alphas with the reverse-optimized consensus
prior, so names outside your alpha's coverage get a real, correlation-propagated
posterior instead of being silently excluded:
quantred allocate --objective utility --strategy demo_trend \
--symbols NVDA,AAPL,META,AMD,TSLA,GOOG,MSFT,AMZN --as-of 2025-06-01 \
--benchmark-holdings equal --posterior bl --posterior-t-eff 60
--posterior-t-eff is required (pass the effective_t a prior info call
measured); --tau overrides the pinned τ for sensitivity. Default off until
validated out-of-sample. See
Portfolio construction — Black–Litterman.
Multi-period trading
--policy aim replaces the myopic "jump to this period's optimum" with a
partial-adjustment policy: alphas are discounted for how fast they'll decay
before the book finishes trading into them, and the book moves a derived
fraction κ of the gap each rebalance instead of all of it:
quantred allocate --objective utility --strategy demo_trend \
--symbols NVDA,AAPL,META,AMD,TSLA,GOOG,MSFT,AMZN --as-of 2025-06-01 \
--capital 1000000 --policy aim
policy 'aim': κ 0.893 (derived 0.893) trading half-life 0.8 rebalances φ 0.000/rebalance discount 1.00
decay half-life inf bars (upper CI bound inf, fit R² 0.16, used conservatively)
--trade-rate overrides the derived κ directly. Default off — decide
whether it's worth turning on with the net-of-cost A/B:
quantred info --policy-ab --strategy demo_trend \
--symbols NVDA,AAPL,META,AMD,TSLA,GOOG,MSFT,AMZN --start 2024-01-01 --end 2024-12-31
which walks the same alpha book forward under the myopic policy and the aim policy and reports which one actually won net of cost — not a preference. See Multi-period trading (engineering) for the derivation.
From a source checkout (contributors)
The commands above are the installed quantred command. In a checkout, run them as
uv run python main.py <command> .... The Makefile shortcut for this page is make allocate. Extras are installed with uv sync --inexact --extra <name>
(see optional extras).