Portfolio construction and rebalance decision skill. In thesis-first Track B, ingests per-ticker deliberation transcripts + optional pm_allocation_memo, then clean-slate portfolio (Phase B) and compare vs current (Phase C). Legacy path: run deliberation inside Phase A. Triggers: orchestrator Phase 7D, daily-delta Phase 7D, standalone rebalance review.
This skill translates research into actionable portfolio positions.
The portfolio manager deliberately separates research-driven conviction from existing position awareness:
config/portfolio.json. The clean-slate portfolio is constructed purely from analyst conviction, macro regime, and theses.This prevents the most common failure mode: anchoring to existing positions and providing rationalizations for the status quo rather than genuine independent analysis.
Load the following (already in session context if running after synthesis):
daily_snapshots.regime (canonical)config/investment-profile.md — risk tolerance (§4), asset preferences (§5), ETF universe (§5D), regime playbook (§6), benchmarks (§8)documents where document_key='digest') — for cross-asset synthesisdocs/research/LIBRARY.md. Load before Phase B. Apply the Black-Litterman conviction-weight table (Section 4.2) for position sizing. Run the Ilmanen 4-quadrant regime check (Section 5.4) before constructing the clean-slate portfolio. Use Kelly ceiling check (Section 4.3) to validate no position exceeds conservative fraction.{{DATE}}: deliberation_session_index (deliberation-transcript-index/{{DATE}}.json) and each listed per-ticker deliberation_transcriptpm_allocation_memo (pm-allocation-memo/{{DATE}}.json) — turnover discipline and target-weight rationale after deliberation (cowork/tasks/portfolio-pm-rebalance.md Phase 6)market_thesis_exploration, thesis_vehicle_map — context for thesis IDs in notesDo NOT load config/portfolio.json yet (except constraint fields if you already need them in Phase B — prefer investment-profile for limits). Portfolio weights stay blind through Phase B.
When deliberation_session_index exists for {{DATE}}:
body.entries[].document_key payload (per-ticker deliberation_transcript).body.final_decisions across transcripts into one resolved summary table (one row per ticker; if duplicates, use the transcript with latest meta.converged === true or the highest round count).pm_allocation_memo when present — use body.target_weights_rationale and body.turnover_discipline as binding guidance for Phase B sizing: the clean-slate book must not contradict deliberation outcomes unless you document an explicit override in the rebalance JSON notes.Announce: "Deliberation ingest complete. [N] tickers from session index."
If no session index exists:
skills/deliberation/SKILL.md completely (single-file or per-ticker outputs).deliberation-transcript/{{DATE}}.json if present.You are building the ideal portfolio from scratch. Do NOT reference the current portfolio weights. Inputs: merged deliberation
final_decisions,pm_allocation_memo(when present), macro regime, thesis context, and risk constraints.
Group analyst recommendations by theme bucket. Check theme-level constraints:
config/investment-profile.md §5)From config/investment-profile.md §4 and config/portfolio.json constraints (not weights):
constraints.max_single_etf_pct (default 100% — no hard cap, but flag any position >25% for PM review)Review the screener-selected candidates from the opportunity screen artifact. If their analyst report recommends >0% weight AND the deliberation resolved them favorably → include in the portfolio. If adding them breaches a theme cap, trim the lowest-conviction existing position first.
Build the full clean-slate target weights as a working table (inline JSON or spreadsheet-style) for use in Phase C only. Do not publish a separate portfolio_recommendation document to Supabase. The only portfolio-layer JSON artifact from this skill is rebalance_decision (Phase C).
NOW you may read
config/portfolio.jsonfor current weights.
Read config/portfolio.json. Extract from positions[]: ticker, weight_pct, entry_date, entry_price_usd, and entry_usdcad.
Note investor_currency (top-level field) — this determines whether FX impact is computed.
Also note any proposed_positions[] from prior agent runs — if any exist, compare against those
too (shows drift between consecutive recommendations).
Respect day-over-day stability: prefer smaller moves when pm_allocation_memo or investment-profile calls for low turnover; flag large single-day shifts for explicit justification in rebalance_decision notes.
For each ticker in the union of (clean-slate portfolio ∪ current portfolio):
delta = recommended_weight - current_weight
| Decision rule | Action |
|---|---|
| delta = 0 | Hold |
| 0 < delta ≤ 4% | Monitor (no action, noted in table) |
| delta ≥ 5% | Add / New Entry |
| -4% ≤ delta < 0 | Monitor |
| delta ≤ -5% | Trim |
| current > 0, recommended = 0 | Exit |
| current = 0, recommended > 0 | New Entry |
Override rule: If a thesis is ❌ Challenged, always flag for action regardless of delta size.
Run this step only when
investor_currencyinconfig/portfolio.jsonis not USD. Skip if allentry_price_usdvalues are null ANDentry_usdcadvalues are null (no baseline).
For each current position, compute:
entry_price_usd is null, use the closing price from the position's entry_date fetched from live data as a best-effort approximation; note the source.entry_usdcad is null, fetch the USD/CAD historical close for entry_date; note the source.Produce this table and include it in the rebalance decision artifact:
| Ticker | Weight% | Entry Price (USD) | Current Price (USD) | USD Rtn% | USD/CAD Entry | USD/CAD Now | FX Effect% | CAD Rtn% |
|---|---|---|---|---|---|---|---|---|
Note: BIL/SHY (USD T-bill ETFs) accumulate USD yield; a weaker USD vs CAD erodes that yield in CAD terms. Quantify this explicitly as an additional investment risk.
Produce the rebalance output as JSON (schema: templates/schemas/rebalance-decision.schema.json).
Include:
action must use the schema enum HOLD, NEW, EXIT, ADD, or TRIM only (never a generic “REBALANCE”). execute_at_open.py maps these to position_events as OPEN (for NEW), EXIT, TRIM, ADD, or HOLD.Run the portfolio validator against the proposed positions to ensure they respect all
constraints from config/investment-profile.md:
./scripts/validate-portfolio.sh --proposed
If any checks fail, adjust the proposed weights before proceeding.
Write the clean-slate recommended weights to the proposed_positions array in config/portfolio.json.
Do NOT modify positions[] — that array reflects actual executed trades and is user-maintained.
"proposed_positions": [
{ "ticker": "IAU", "weight_pct": 20, "as_of": "{{DATE}}", "action": "Hold" }
]
Also update "last_updated_date" and "last_updated_by": "agent".
pm_allocation_memo published when using thesis-first task ordering./scripts/validate-portfolio.sh --proposed)rebalance_decision includes body.proposed_portfolio.positions (and cash_residual_pct when cash is non-zero) so sync_positions_from_rebalance.py can upsert positions on close-outconfig/portfolio.json → proposed_positions[] updatedIf invoked without a fresh session (no Phase outputs from today):
config/portfolio.json (tickers only for analyst roster, Phase A and B stay blinded)This allows standalone portfolio reviews without running the full digest pipeline.