Install
$ agentstack add skill-bencharoenwong-parallax-workflows-parallax-explain-portfolio ✓ 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
Explain Portfolio
When not to use
- Proactive portfolio health check → use /parallax-portfolio-checkup
- Scheduled client meeting prep → use /parallax-client-review
- Hypothetical scenario / news event → use /parallax-scenario-analysis
- Rebalancing trade recommendations → use /parallax-rebalance
- Single stock analysis → use /parallax-should-i-buy
Gotchas
- JIT-load _parallax/parallax-conventions.md for RIC resolution, parallel execution, fallbacks
- JIT-load _parallax/house-view/loader.md FIRST; if active view present, follow §2 (validation), §5 (output rendering), §6 (audit). For attribution, the view provides additional context: a drawdown in a view-tilted-overweight sector is "expected pain from view exposure"; a drawdown in a view-tilted-underweight sector raises "why was this still held?" — surface in the verdict.
- When active view is present, use the view-aware disclaimer per loader.md §5 rule 5; otherwise use the standard disclaimer
- Holdings must be in RIC format with weights summing to ~1.0
- exportpriceseries returns daily OHLCV — use close prices for return calculation
- gettelemetry regimetag and mechanism fields are the key attribution inputs
- Score-vs-price divergence is the core insight — scores stable + price down = transient; scores falling + price down = fundamental
- Score data is weekly (getscoreanalysis) vs daily price data — there may be a ~7 day lag. Acknowledge this gap in the divergence analysis, especially if major news broke after the last score data point.
- The client said a number ("down 4%") — verify it against actual computed returns before attributing
- Cap news calls at top 3 detractors to manage token cost
- quickportfolioscores may fail for concentrated/niche portfolios — fall back to getscoreanalysis (Step 3) as the primary factor source
- getpeersnapshot may return a different company as target (see Convention #2) — extract the queried stock's scores from the peer list, not from the target_company field
- JIT-load
_parallax/white-label/integration-pattern.mdbefore the Pre-Render step. Loader call isload_visual_branding()(7-key visual subset; voice structurally excluded —branding["voice"]raisesKeyError). Apply §5 (Branding Header) and §7 (About This Report) in Output Format.
Reactive performance attribution when a client asks "why am I down?" Decomposes the loss into market/regime, factor/thematic, and stock-specific layers, then determines if the drawdown is fundamental or noise.
Usage
/parallax-explain-portfolio [{"symbol":"AAPL.O","weight":0.25},{"symbol":"JPM.N","weight":0.20},{"symbol":"MSFT.O","weight":0.30},{"symbol":"JNJ.N","weight":0.25}] "I'm down about 4% this month"
The second argument is the client's stated concern — used to anchor the lookback period and validate against actual returns.
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 output rendering.
Step 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. The view does NOT change attribution math (Steps 1-5) — it shapes the Step 6 verdict and the framing of Step 4 stock-specific findings. Specifically: when a top detractor is in a view-OW sector, the loss is "expected from view exposure"; when in a view-UW sector that wasn't trimmed, the loss raises a portfolio-management question.
Step 1 — Measure actual performance
Infer lookback period from the client's statement ("this month" → ~21 trading days, "this week" → 5 days, "this quarter" → ~63 days). Default to 21 days if ambiguous.
Step 1a — Asset-class pre-classification (parallel, MANDATORY)
export_price_series is the equity-only price endpoint; ETFs (SPY, QQQ, IEFA, EWJ, etc.) silently return empty from it and would otherwise be dropped from portfolio-return attribution — a HIGH-IMPACT bias. Before pulling price history, classify each holding:
- For each holding, call
etf_profile()in parallel. - If response is
{"error": "No profile data found", ...}→ treat as equity, route throughexport_price_series. - If response returns an ETF profile (non-error) → treat as ETF, route through
etf_daily_price.
This adds N calls at 1 token each (one per holding) — see _parallax/token-costs.md.
Step 1b — Pull price history (parallel, split by asset class)
Fire ALL of the following in a single tool-call turn:
- For each EQUITY holding →
export_price_series(symbol=, days=, format="json") - For each ETF holding →
etf_daily_price(symbol=, start_date=, end_date=)
Step 1c — Compute attribution + halt rule
Compute:
- Per-holding return over the period (close-to-close)
- Weighted contribution to portfolio return: holding return × weight
- Total portfolio return (sum of weighted contributions)
- Rank holdings by contribution (biggest detractors first)
Halt rule (no silent drops): if any holding returns empty/error from BOTH etf_profile AND its routed endpoint, the skill MUST surface this explicitly:
> ⚠ Cannot compute return for ` — neither exportpriceseries nor etfdailyprice returned data. This holding is **not** included in the attribution below; the reported portfolio return is computed on the remaining %` of weight. Operator decision required: supply prices externally, or remove from the portfolio for this analysis.
Render this banner above the attribution table — never silently zero or skip a holding.
Compare computed return against the client's stated figure. If they diverge significantly (>1%), note the discrepancy.
Step 2 — Attribution layer 1: Market and regime (parallel)
| Tool | Parameters | Purpose | |---|---|---| | get_telemetry | fields: regime_tag, signals, commentary.headline, commentary.mechanism, divergences | Current market regime — is the whole market down? | | list_macro_countries | — | Check coverage for home markets | | 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 for mismatch check. | | quick_portfolio_scores | holdings | Legacy/V1 path only. Do NOT use if PARALLAX_LOADER_V2=1 and view active. |
After Step 2: 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.
After Batch: call macro_analyst with component="tactical" for each home market (cap at 2). This establishes the macro backdrop: is this a market-wide drawdown, sector rotation, or idiosyncratic?
If quick_portfolio_scores fails or returns `):
DRAFT="$(mktemp "${TMPDIR:-/tmp}/explain.XXXXXX")"
cat > "$DRAFT"
REPORT
python3 "/../_parallax/render_gate.py" --skill explain-portfolio ** portfolio attribution`. Logo handling per integration-pattern.md §5: empty path → text only; URL → embed; absolute local (`/` or `~`) → skip embed and append `Logo on file: ` to About This Report.
- **What Happened** (computed portfolio return over the period, compared to client's stated figure; 1-sentence summary of the loss magnitude)
- **Performance Attribution** (table: each holding with return, weighted contribution, and primary driver tag — Market / Factor / Stock-Specific; if view active, add "View Exposure" tag — OW / Neutral / UW per the view)
- **Market & Regime Context** (regime tag, mechanism, 2-3 sentences on what's driving markets; is the broad market down too?)
- **Factor Exposure** (which factor tilts helped/hurt; connection to the current regime; if view active, note whether realized factor exposure matched view's intended tilts)
- **Top Detractors** (for each of top 3: what happened, why, and whether scores agree with the price move; flag whether the holding's view-exposure tag means the loss was expected pain from the view)
- **The Key Question: Noise or Signal?** (score-vs-price divergence summary — portfolio-level verdict with per-holding classification table)
- **What To Do** (conditional advice based on the verdict; if view active, separate "stay-the-course-per-view" actions from "view says trim anyway" actions)
- **About This Report** (always present): one line stating branding state per integration-pattern.md §7 markdown column (render per table; do not collapse). If a logo was skipped per the Branding Header rule, append `Logo on file: ` as a second About This Report line.
Append audit log entry per loader.md §6.
Keep tone calm and explanatory. The client is worried — the output should reduce anxiety with clarity, not amplify it with jargon.
**AI-interaction disclosure (required regardless of view state):** Render `parallax-conventions.md §9.2` immediately above the disclaimer below.
If active view: end with the view-aware disclaimer per loader.md §5 rule 5. Otherwise: render the standard disclaimer verbatim from `parallax-conventions.md` §9.1.
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [bencharoenwong](https://github.com/bencharoenwong)
- **Source:** [bencharoenwong/parallax-workflows](https://github.com/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.