edgekit.portfolio
Combine independently-validated strategy books into one daily-R stream, then decide how much risk each carries. The unit everywhere is the daily-R series— one strategy's per-day sum of trade R-multiples, indexed by date.
What's inside. Three functions: combine (merge books into one daily-R series), correlation (the pairwise daily-R correlation matrix), and allocation_sweep (sweep the risk-share between two books and pick the return-maximising split under a drawdown budget). Missing days are filled with 0.0— a strategy that didn't trade contributes no R, it is not missing data. Causality lives in sizing.risk_parity (lagged std).
combine#
Combine per-strategy daily-R series into a single book daily-R series, aligned on the union date grid and NaN-free.
combine(books: dict[str, pd.Series], method: str = "risk_parity",
weights: dict[str, float] | None = None) -> pd.Series| Param | Type | Default | Meaning |
|---|---|---|---|
books | dict[str, pd.Series] | — | Name → daily-R series for each validated strategy. |
method | str | "risk_parity" | One of "equal", "fixed", "risk_parity". |
weights | dict[str, float] | None | None | Per-strategy weights (required when method="fixed"; missing → 0). |
method="equal"— unit-weight each (M.sum(axis=1)).method="fixed"— apply theweightsdict; raisesValueErrorif no dict is given.method="risk_parity"— inverse-vol weights, renormalised to sum ton.
Returns a single pd.Series (the book's daily R), indexed by the union of all input dates, with no NaNs.
from edgekit import portfolio
book = portfolio.combine({"trend": r_trend, "breakout": r_breakout}, method="risk_parity")
# fixed weights (e.g. 2x the trend book, mute the breakout sleeve)
book = portfolio.combine({"trend": r_trend, "breakout": r_breakout},
method="fixed", weights={"trend": 2.0, "breakout": 0.0})correlation#
Pairwise daily-R correlation. Symmetric with a 1.0 diagonal.
correlation(books: dict[str, pd.Series]) -> pd.DataFramecorr = portfolio.correlation({"trend": r_trend, "breakout": r_breakout})
print(corr.loc["trend", "breakout"]) # near 0 = genuine diversificationallocation_sweep#
Sweep the risk-share between two books, size each blend to the same drawdown budget, and report the return-maximising split plus the robust risk-parity default. For every share of A in the grid the books are inverse-vol weighted, blended, and sized to dd_budget (with daily_cap binding when it is tighter).
allocation_sweep(book_a: pd.Series, book_b: pd.Series, dd_budget: float,
daily_cap: float | None, account: float, grid=None, cut=None) -> dict| Param | Type | Default | Meaning |
|---|---|---|---|
book_a, book_b | pd.Series | — | The two books' daily-R series. |
dd_budget | float | — | Drawdown budget as a fraction of account (e.g. 0.10 for 10%). |
daily_cap | float | None | — | Daily-loss fraction cap; binds when tighter than dd_budget. |
account | float | — | Account size in dollars. |
grid | iterable | None | None | Shares of A to test (default 0→1 by 0.1). |
cut | date | None | None | If given, fixes 50/50 weights + sizing on the IS window and applies them unchanged OOS. |
Returns a dict:
table— per-share DataFrame with columnsshare, w_a, w_b, dollar_per_r, binding, annual, max_dd_pct, worst_day_pct.best— the return-maximising row.best_share— the share of A at that maximum.risk_parity— the 0.5-share allocation (the robust default).correlation— the A/B correlation.oos(only whencutis passed) —dollar_per_r, is_annual, oos_annual, is_sharpe, oos_sharpe, oos_max_dd_pct.
res = portfolio.allocation_sweep(r_trend, r_breakout, dd_budget=0.10, daily_cap=0.05,
account=100_000, cut="2023-01-01")
print(res["best_share"]) # return-maximising share of the trend book
print(res["risk_parity"]) # robust 50/50 default
print(res["oos"]["oos_annual"]) # what the IS-fixed weights actually did out-of-samplebest_share is chosen in-sample and is therefore optimistic. Pass a cut and read the oos panel: it fixes 50/50 weights and sizing on the in-sample window and reports what they did on unseen data. If oos_annual collapses versus is_annual, the blend does not generalise.See also#
- edgekit.sizing —
risk_parity,size_to_dd, and the risk governors underneath. - edgekit.challenge — run the sized book through a prop-firm evaluation.
- edgekit.validation — validate each book before combining.