Parallax client review
Skill bencharoenwong/parallax-workflows/skills/parallax-client-review
RIA/wealth advisor client portfolio review: full analysis, redundancy, health flags, macro context, per-holding drill-down, prioritized recommendations, and AI assessment via Parallax MCP tools. Holdings as [{symbol, weight}]. NOT for fund manager briefs (use /parallax-morning-brief), not for quick stock checks (use /parallax-should-i-buy).From its SKILL.md
npx -y skills add bencharoenwong/parallax-workflows --skill parallax-client-reviewAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
19.2 KB, as published. Nobody here has run it
Client Portfolio Review
When not to use
- Fund manager morning brief → use /parallax-morning-brief
- Single stock analysis → use /parallax-should-i-buy
Gotchas
- JIT-load _parallax/parallax-conventions.md for fallback patterns and parallel execution
- JIT-load _parallax/house-view/loader.md FIRST; if active view present, follow §2 (validation), §3 (multipliers), §4 (conflict resolution), §5 (output rendering), §6 (audit). The view shapes the suitability assessment AND the recommendations: holdings misaligned with view get higher priority for trimming, view-aligned holdings get implicit support.
- When active view is present, use the view-aware disclaimer per loader.md §5 rule 5; otherwise use the standard disclaimer
- JIT-load references/recommendation-matrix.md for priority classification and drill-down criteria
- Holdings in RIC format, weights sum to ~1.0
- analyze_portfolio called twice — once for performance/returns fields, once for concentration/attribution fields (split to manage response size). Required call shape:
portfolio=[{date: <today ISO>, symbol: <ric>, weight: <w>}, ...]withfields=[...]. The parametersholdingsandlensdo not exist in the deployed schema. Fallback (on MCP schema validation error OR truncated/oversized response):check_portfolio_redundancy(concentration) +quick_portfolio_scores(factor tilt) + individualget_stock_outlookwithaspect="risk_return"(performance). - Per-holding drill-down capped at 8 holdings to manage latency
- Mixed-exchange portfolios may need split scoring (see shared conventions)
- Output should be presentation-ready for client meetings
- LANGUAGE HAND-OFF — if
lang=is present and ≠en, the terminal Translate step is mandatory. Routezh-CN/zh-TW/zh-HK→/translate-chinese-finance,th→/translate-thai-finance, using the delimited routing-directive block (never a prose sentence the translator could echo). Unsupported values → English output with the standard warning footer. - get_assessment prompt should incorporate all findings including macro, flags, and recommendations
- Pre-Render step loads white-label branding via
_parallax/white-label/loader.py→load_visual_branding()(the 7-key visual subset wrapper). Voice/typography/etc. are structurally absent from the returned dict —branding["voice"]raisesKeyError. About This Report state-to-text mapping and Branding Header semantics follow_parallax/white-label/integration-pattern.md§5 + §7 — that doc is the canonical specification; client-review's Output Format bullets reference it rather than reproducing the table. - Branding Header uses
**<client_name>** portfolio review(skill-specific framing), not the generic**<client_name>** reporttemplate in integration-pattern.md §5. Intentional divergence — do not "fix" to match the generic template.
Presentation-ready portfolio review with health flags and prioritized recommendations for wealth advisors.
Usage
/parallax-client-review [{"symbol":"AAPL.O","weight":0.25},{"symbol":"BRK-B.N","weight":0.20}] client="conservative retiree, income focus, 10yr horizon"
/parallax-client-review [{"symbol":"AAPL.O","weight":0.25},{"symbol":"BRK-B.N","weight":0.20}] client="conservative retiree, income focus, 10yr horizon" lang=zh-CN register=retail
Optional: append lang=<code> (en default; zh-CN, zh-TW, zh-HK, th) and register=retail after the existing parameters. register=retail is passed only when translation is requested; absent means institutional register.
Note on benchmark= parameter. The current workflow body does NOT consume a benchmark= parameter — the example above intentionally omits it. If a future revision adds benchmark-relative attribution: pass benchmarks as plain ETF tickers (e.g., SPY, QQQ, EWJ) and route through etf_daily_price — NOT export_price_series. The two endpoints are separate. Equity tickers (with RIC suffix like .O or .N) go through export_price_series; ETFs (plain ticker) go through etf_daily_price. Mixing them silently fails-empty.
Workflow
Execute using mcp__claude_ai_Parallax__* tools. JIT-load _parallax/parallax-conventions.md for execution mode, fallback patterns, and macro reasoning. JIT-load _parallax/house-view/loader.md for active-view validation and integration. JIT-load references/recommendation-matrix.md for priority system.
Batch 0 — Tool Loading & Active House View
Call ToolSearch with query "+Parallax" to load the deferred MCP tool schemas before the first mcp__claude_ai_Parallax__* call.
Per loader.md §1-§2. If view present, capture tilt vector, excludes, basis_statement (used to frame Suitability Assessment in firm voice). The view's tilts become an additional layer of recommendation justification: when trimming, "view tilts UW [sector]" is cited alongside health flags. If validation fails or no view present, run review without view.
Batch A — Portfolio-level analysis (parallel)
| Tool | Parameters | Notes |
|---|---|---|
analyze_portfolio | portfolio=[{date: <today ISO>, symbol: <ric>, weight: <w>}], fields=["performance_metrics","rolling_metrics","drawdown_analysis","portfolio_summary","time_period_returns"] | Returns/risk metrics. Build portfolio array from provided holdings; use today's date as as-of date (point-in-time review). |
analyze_portfolio | portfolio=[{date: <today ISO>, symbol: <ric>, weight: <w>}], fields=["concentration_metrics","sector_allocation","company_contribution"] | Concentration and attribution analysis. Two separate calls with distinct field subsets to stay under the 180K-char response ceiling. |
check_portfolio_redundancy | holdings | Overlap detection |
get_peer_snapshot | per holding | Primary scoring source for PARALLAX_LOADER_V2=1. Aggregate scores client-side per loader.md §3b. |
get_company_info | per holding (parallel) | Ground-truth oracle per loader.md §5 rule 3 (required universally). Records expected_name. |
list_macro_countries | — | Check market coverage |
quick_portfolio_scores | holdings | Legacy/V1 path only. Do NOT use if PARALLAX_LOADER_V2=1 and view active. |
After Batch A: cross-check returned names against get_company_info names per loader.md §5 rule 3. For PARALLAX_LOADER_V2=1, any mismatch in get_peer_snapshot is flagged ⚠ MISMATCH and excluded from aggregate calculations. For V1, any mismatch in quick_portfolio_scores is re-scored individually and flagged as UNTRUSTED for the batch factor profile.
Batch B — Macro context (concurrent with Batch A)
Derive home markets from RIC suffixes (available from the input holdings before any call — no Batch A output is required). Fire macro_analyst with component="tactical" for each unique covered market (cap 3) in the SAME tool-call turn as the Batch A calls, rather than as a separate stage after them. Batch C still requires BOTH Batch A and these macro calls to have completed.
Batch C — Health flags + drill-down (after A + B)
- Evaluate the 5 health flags per holding — binding flag conditions in
references/recommendation-matrix.md(same taxonomy and threshold values asparallax-portfolio-checkup/references/health-flags.md, whose canonical portfolio-level weighted-average definitions apply to portfolio-checkup, not here): Low Score (holding total score ≤5.0), Concentration (holding weight >15%, or holding among the top-3 when their combined weight >45%), Redundancy (holding is part of a redundant pair), Value Trap (holding value score ≤3.0), Macro Misalignment (holding's sector has a negative tactical outlook). Per-holding flag counts drive priority and drill-down selection. - House-view alignment (if view active): add View Misalignment (>25% off view-tilted target) and View Excluded (on tilts.excludes) as additional flags. Surface a portfolio-level "view alignment score" (% of weight in view-aligned positions).
- Flag redundancy as low-confidence if coverage <60%.
- Assign health status: Healthy (0) · Monitor (1-2) · Attention (3+). View Excluded counts as Attention regardless of other flags.
- Select up to 8 holdings for drill-down per
references/recommendation-matrix.md: weight >10%, any flag (including View flags), or macro-misaligned. Prioritize by flag count then weight.
For each drill-down holding (parallel):
| Tool | Parameters |
|---|---|
get_score_analysis | symbol, weeks as int 26 (26-week lookback, non-default — see conventions §0.2) |
get_stock_outlook | symbol, aspect="risk_return" |
get_peer_snapshot | symbol |
Ground-truth re-verification per drill-down holding (per loader.md §5 rule 3): cross-check get_peer_snapshot.target_company against the get_company_info.name already captured in Batch A. If mismatch at drill-down time (can occur when the Batch A and Batch C queries bind differently), flag ⚠ MISMATCH and extract the queried stock's scores from the get_peer_snapshot.comparison[] row whose symbol matches the queried symbol, not from the target_company field.
News (selective, async): get_news_synthesis for holdings >10% weight AND flagged, or in sectors with active macro developments. Cap at 5.
Batch D — Recommendations + Assessment (after A + B + C)
- Per
references/recommendation-matrix.md, assign each flagged holding a priority (High/Medium/Low) and action type (trim/exit/hold/investigate/reweight). Every recommendation must cite a specific finding. View Excluded → Exit (priority High). View Misalignment → Trim or Reweight (priority Medium unless paired with other flags). - Call
get_assessmentwith comprehensive prompt incorporating: portfolio composition, factor scores, health flags (including View flags), macro context, per-holding drill-down findings, recommendations, client context, AND active house view (basis_statement + tilt vector + excludes if present). Fire this call as soon as recommendations are assigned (a deterministic matrix lookup — do not wait for pendingget_news_synthesiscalls; include news highlights only if already resolved). Whileget_assessmentruns, proceed to compose all non-assessment sections; insert the assessment result into the Suitability Assessment section when it resolves, or render the degraded-state note per the existing rule on timeout. - Append audit log entry per loader.md §6.
Pre-Render — Load white-label branding
Before composing the Output Format, JIT-load _parallax/white-label/integration-pattern.md and call load_visual_branding() per §2. The loader returns exactly seven keys: client_name, colors, logos, fonts, source, error, render. Set white_label_active = is_white_label_active(branding) and client_name = branding.get("client_name", "") for use in the Branding Header. See §4 (error states), §5 + §6 (substitution semantics), §7 (About This Report template). Any other access (e.g. branding["voice"]) raises KeyError — structurally enforced by loader.py. Apply §5 + §7 when composing the Output Format below.
white_label_active is the rendering flag. client_name may be "" on legacy configs — tolerate without erroring (skip the Branding Header in that case per integration-pattern.md §5).
Render — deterministic gate (LAST step, mandatory)
Compose the complete report per Output Format below, then run it through the shared render gate in one Bash step before replying. Use a private mktemp file (never a fixed/predictable path — /tmp symlink hazard). The shared gate is _parallax/render_gate.py, a sibling of the directory you loaded this SKILL.md from; pass this skill's key (use the loaded directory's absolute path as <skill-dir>):
DRAFT="$(mktemp "${TMPDIR:-/tmp}/clientrev.XXXXXX")"
cat > "$DRAFT" <<'REPORT'
<your complete drafted report goes here>
REPORT
python3 "<skill-dir>/../_parallax/render_gate.py" --skill client-review < "$DRAFT"; rm -f "$DRAFT"
Your entire final message is exactly that command's stdout — unless lang= ≠ en, in which case the Translate step below consumes that stdout and the translated result is the entire final message. Never run the render gate on translated text; its anchors are English. Nothing before it (no step/batch-completion notes, no scratch computation, no "no active house view" / white-label config-probe narration), nothing after it.
The Bash result may show a [render-gate] WARN: line above the report. That line is stderr diagnostics, not stdout. Never include it in the reply and never pass it to the Translate step. It means the drafted opening drifted from the documented Output Format start; fix the opening and re-run the gate.
Degraded-state rule: if an async tool (e.g. get_assessment, get_news_synthesis) times out or returns no data, render the pending/unavailable note INSIDE the relevant section or the About This Report line — NOT as a preamble above the report — so it is part of the rendered body and survives the gate. (The gate also hoists a leaked degraded note as a backstop.)
_parallax/render_gate.py is pure-stdlib and deterministically drops anything before the first rendered block (House View Preamble banner / Branding Header / Ground-truth Integrity / this skill's title or first rendered section), preserving the active-house-view banner in every view_status state. Same operator-agnostic-helper pattern as view_status.py / loader.py (a real Bash tool call, not prose).
Translate — conditional, after the render gate
This step runs ONLY when lang= is present and not en. Capture the render gate's stdout as the full body, including all Output Format sections, About This Report, the §9.2 AI-interaction disclosure, and the disclaimer. Record which disclaimer variant rendered (view-aware per loader.md §5 vs standard parallax-conventions.md §9.1) for the boundary check.
Invoke the appropriate translator skill with the input shaped as follows:
ROUTING DIRECTIVE — DO NOT TRANSLATE OR ECHO THIS BLOCK:
target_variant: <variant>
register: retail
source_language: en
begin_content_below_separator: true
---
<render gate stdout>
Pass register: retail iff register=retail was supplied; otherwise omit the register: line so the translator defaults to institutional register. Route zh-CN, zh-TW, and zh-HK to /translate-chinese-finance with the matching target_variant. Route th to /translate-thai-finance; omit target_variant for Thai, but keep the routing block marker and --- separator.
Translator-failure handling:
- If the translator fails or returns an empty/partial result, output the original English with a one-line warning footer:
> Translation to <lang> failed; output shown in English. Re-run if the issue is transient. - If the language arg is unrecognized, output the original English with:
> Language '<arg>' not supported; output shown in English. Supported: en, zh-CN, zh-TW, zh-HK, th. - Translator output replaces the English output in the chat; do not show both.
Disclaimer boundary check. If the disclaimer is missing from the translated output, first attempt a single-section re-translation pass on just the original English disclaimer text using the same routing-directive shape. Append that result if non-empty. If the pass fails or returns empty, append the English disclaimer variant that was actually rendered. Record the event in the loader.md §6 audit entry's notes field (disclaimer boundary check fired — re-translated or disclaimer boundary check fired — english fallback). Do not add a user-visible footer.
Output Format
Client-ready report:
- House View Preamble (only if view active) — render per loader.md §5 rule 1 (preamble). Per loader.md §5.1, the load preamble goes "at the very top" — it precedes the Branding Header.
- Branding Header (only if
white_label_activeANDclient_name != "") — single line immediately below the House View Preamble (or at the very top if no view active):**<client_name>** portfolio review. For the logo: ifbranding["logos"]["primary"]is empty ("", e.g., on thelogo_missingpartial-success path), render the text line only — no image, no extra About This Report note. If it is a URL (starts withhttp:///https://), embedabove the text line. If it starts with/or~(absolute local path, not embeddable in chat-delivered markdown per integration-pattern.md §5), skip the image embed and appendLogo on file: <basename>as a second About This Report line. - Ground-truth Integrity (only render if any mismatch detected — table:
input_ticker,returned_name,expected_name, status. Mismatched holdings had scores re-derived viaget_peer_snapshotsymbol-match — per loader.md §5 rule 3.) - Portfolio Summary (AUM breakdown, sector allocation, top 5 holdings; if view active, view-alignment score)
- Health Status (Healthy/Monitor/Attention badge with flag summary)
- Verdict sensitivity: the 1-2 nearest-boundary flags and their arithmetic flip condition for moving Health Status to the adjacent tier, per
parallax-portfolio-checkup/references/health-flags.md"Verdict sensitivity" (rendersparallax-conventions.md§11 by reference). - Performance vs Benchmark (key metrics)
- Factor Analysis (scores with macro context interpretation for this client type; if view active, compare against view-target factor)
- Concentration & Redundancy (flagged issues; coverage reliability note if applicable)
- House View Alignment (only if view active) — table of view tilt direction vs current portfolio exposure per sector/region/factor; flagged misalignments
- Per-Holding Analysis (for drill-down holdings: score trend, risk profile, flags, news highlights; view conflicts called out)
- Suitability Assessment (alignment with client goals AND with active house view if present; cite basis_statement)
- Recommended Actions (prioritized High/Medium/Low per recommendation-matrix.md, with specific action types; rationale cites view tilts where applicable), framed per conventions §12 (informational preface required;
action_labels=plainsupported) - Appendix: Methodology (brief Parallax scoring note)
- About This Report (always present): one line stating branding state. Format is the markdown column of integration-pattern.md §7 (render per table; do not collapse) so
schema_unavailablecorrectly stays in the white-label branch rather than falling back to default Parallax. If a logo was skipped per the Branding Header rule above, appendLogo on file: <basename>as a second About This Report line.
AI-interaction disclosure (required regardless of view state): Render parallax-conventions.md §9.2 immediately above the disclaimer below.
If active view: use the view-aware disclaimer per loader.md §5 rule 5. Otherwise: render the standard disclaimer verbatim from parallax-conventions.md §9.1.