Install
$ agentstack add skill-bencharoenwong-parallax-workflows-parallax-client-review ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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
- JIT-load _parallax/client-policy/policy-loader.md when
policy=is present. Fallback ladder per that file. S1/S2 math via adaptation.py only — never inline. - Policy sections never precede the first render-gate anchor. Render them in the body, after House View Alignment, per policy-loader.md §7 placement rule.
- No
policy=supplied means today's behavior, unchanged. No policy sections, no disclosure line. - 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). Call shape:
portfolio=[{date: , symbol: , weight: }, ...]withfields=[...], subject to the discovered live schema. The date is an as-of date over a price history, not a snapshot key: a deployed connector may need a date early enough that each holding has sufficient prior price history, so today's date can return empty or fail validation. Read the live schema's own description first. When the schema gives no guidance, date every holding at the most recent completed trading day at least 5 calendar days in the past, one date for all holdings, and state the date used in the report — live schema still wins over this default. 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 calls
_parallax/white-label/rm_consumer.py→load_rm_branding_context("portfolio review", audience=). This executable visual-only seam owns source redaction, voice isolation, branding fallback, AND the §13.1 audience-mode resolution — pass the parsedaudience=flag (orNone) into the call and readresolved_audienceoff the returned context; never inspect the branding mapping directly for this. Use itsheader_linesandabout_linesexactly once. The state mapping follows_parallax/white-label/integration-pattern.md§5 + §7. The context'sabout_linesalready carry the §13.4 mode line and any unrecognized-value notice — do not append a second mode line. - Branding Header uses
**** portfolio review(skill-specific framing), not the generic**** 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 policy="/path/to/client-policy.yaml"
/parallax-client-review [{"symbol":"AAPL.O","weight":0.25},{"symbol":"BRK-B.N","weight":0.20}] audience=client_safe
Optional: append lang= (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.
Optional audience= argument: client_safe | internal_analyst; precedence follows parallax-conventions.md §13.1.
Optional: append policy= after the existing parameters. policy= accepts a file path or inline YAML/JSON describing the client's strategic allocation, bands, and tactical budget. Absent means the review runs exactly as today. Do not add an invocation k= flag. The locked spec allows a firm-level default and a per-mandate override only; both live in the policy artifact's adaptation block.
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 0.5 — Client policy load (only if policy= present)
JIT-load _parallax/client-policy/policy-loader.md. Write inline policy input to a private mktemp file first. Then, in one Bash step:
python3 "/../_parallax/client-policy/adaptation.py" --policy "$POLICY" --json
Capture errors, fallback_tier, resolved_k, k_source, policy_hash, and data_quality. Blocking errors mean tier no_policy for the rest of the run; dimension-scoped errors (missing basis, total-basis without equity weight) drop only that dimension to multiplier_fallback — policy-loader.md §2 defines the classes. Render the error list in Policy Data Quality and continue the review unchanged. Exposure-dependent S1/S2 runs after Batch A, in Batch C.
Batch A — Portfolio-level analysis (parallel)
| Tool | Parameters | Notes | |---|---|---| | analyze_portfolio | portfolio=[{date: , symbol: , weight: }], fields=["performance_metrics","rolling_metrics","drawdown_analysis","portfolio_summary","time_period_returns"] | Returns/risk metrics. Build portfolio array from provided holdings; use an as-of date with sufficient prior price history per the Gotchas call-shape note; state the date used. | | analyze_portfolio | portfolio=[{date: , symbol: , weight: }], 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. When analyze_portfolio.sector_allocation also came back, cross-check the two sector breakdowns per Batch C step 3 before rendering either. | | get_peer_snapshot | per holding | Primary scoring source for PARALLAX_LOADER_V2=1. Aggregate scores client-side per loader.md §3b. Distinguish a timeout from an empty response: a timeout leaves the holding unscored, per existing handling. A successful call that carries no data for the listing is a coverage gap, not a timeout — fall back to get_company_info plus get_score_analysis for that holding, label every figure derived this way a profile-derived score, and name every profile-derived holding in the report. Same-scale guard: before ranking or combining a profile-derived score with a peer/batch composite score, cross-check it against the holding's latest get_score_analysis row. If the two agree (same composite field, same 1–10 scale), the profile-derived score is same-scale by construction and renders alongside the composite normally, tagged profile-derived. If they disagree, or only one source returned, do NOT blend a guessed value into the ranking: flag the holding's score unverified — not combined with the composite ranking in Factor Analysis and Per-Holding Analysis instead. | | 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. A successful call with no data for a listing (distinct from a timeout) is the same coverage-gap case as the get_peer_snapshot row above — fall back to get_company_info plus get_score_analysis for that holding, label it a profile-derived score, and apply the same same-scale guard before combining it into any ranking. |
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 /../parallax/coveragecheck.py" --portfolio-sectors "$PORTFOLIOSECTORS" --redundancy-sectors "$REDUNDANCYSECTORS" --holdings "$HOLDINGS"
— and render from its JSON verdict. Tag every--holdingsentry with itssectorfrom theanalyzeportfoliosector mapping (per-holding backstop as usual): the live redundancy payload is aggregate-only, and without sector tags the helper cannot infer absent holdings and resolvesabsentholdingsbasistonotcomputable. Cite the operator-agnostic-helper rationale by reference toparallax/house-view/loader.md§2 ("Why the helper, not inline math") — the same argument applies here: the coverage arithmetic must be identical across operator LLMs. Oncoveragelimited, label the redundancy figures **coverage-limited** in Concentration & Redundancy, name thedivergingsectorsand anyabsentholdings, and prefer theanalyzeportfoliosector weights for every rendered concentration figure.absentholdingsis basis-qualified viaabsentholdingsbasis: when it issectorinference, note that the absent-holdings list is inferred from diverging sectors (the redundancy payload carried no per-holding data) rather than confirmed directly; when it isnotcomputable, render nothing about absent holdings at all — do not claim absence and do not claim confirmed coverage. Onconsistent`, render normally. - 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.
Client policy S1 + S2 (only if policy= present and tier is not no_policy)
- Assemble current sleeve exposures: region from RIC suffixes per
_parallax/parallax-conventions.md§1; sector from the Batch Aanalyze_portfoliosector_allocationblock, withget_peer_snapshot/get_company_infoas a per-holding backstop. Region is therefore classified by listing venue, not by issuer domicile or economic exposure; ADR/fund look-through is not applied in phase 1, and the Policy Data Quality intro states this basis. Renormalize each dimension's weights over MAPPED holdings only and recordcoverageplus everyunmappedholding. Carry each holding'sisinwhen one is available. Payload shape is pinned in policy-loader.md §3; these clauses of it bind the producer and are stated identically inadaptation.py's module docstring and policy-loader.md §3 — one of the three copies that must agree:
basismust be"sleeve"— Phase 1 accepts sleeve-relative exposures only; the producer converts before calling. A missing or non-"sleeve"basisis rejected at the CLI (exit 2, naming the file and the offending value), the same operator-mistake class as a non-object payload; no conversion is implemented in Phase 1.- The whole payload is checked by
validate_exposuresagainst this contract (known dimensions, finite weights in [0, 1], per-dimension sums
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: bencharoenwong
- Source: bencharoenwong/parallax-workflows
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.