AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Parallax Cio Letter Prep

skill-bencharoenwong-parallax-workflows-parallax-cio-letter-prep · by bencharoenwong

Monthly CIO letter prep pack for fund managers: period attribution via daily contribution analysis, top contributors / detractors with evidence-backed drivers, macro snapshot, news themes, and a conditional forward-outlook. Output is Word .docx for the CIO to edit and send. Provide current_portfolio, prior_portfolio, trade_log, period_start, period_end. Symbols in RIC format. NOT for individual s…

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-bencharoenwong-parallax-workflows-parallax-cio-letter-prep

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-bencharoenwong-parallax-workflows-parallax-cio-letter-prep)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
yesterday

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Parallax Cio Letter Prep? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

CIO Letter Prep Pack

When not to use

  • Single stock analysis → use /parallax-should-i-buy or /parallax-deep-dive
  • Daily fund manager brief → use /parallax-morning-brief
  • RIA / wealth-advisor client review → use /parallax-client-review
  • Reactive drawdown attribution after a single event → use /parallax-explain-portfolio
  • Backtesting a strategy → use /backtest
  • Period start more than 365 days before today → exportpriceseries trailing-window cannot reach it; split into quarterly windows or adjust start date

Gotchas

  • JIT-load _parallax/parallax-conventions.md for RIC, symbol cross-validation, parallel exec, fallbacks, news async, macro reasoning, and disclaimer
  • JIT-load _parallax/house-view/loader.md ONLY if house_view is supplied AND forward-outlook will render. House view is scoped to the forward-outlook section ONLY — retrospective sections (header, attribution, contributors, detractors, trade narrative, macro, news) are always view-agnostic. No view → omit forward-outlook entirely; never retrofit view language onto retrospective prose.
  • Attribution method is "daily contribution analysis" (NOT "time-weighted" / TWR — that's a different industry term for return calculation, and conflating them confuses practitioners). Math: daily weights from prior_portfolio + trade_log, sum (dailyweight × dailyreturn) per holding. Naive weight × period_return is silently wrong when positions change mid-period.
  • company_contribution[].contribution_pct is a share of total portfolio P&L, NOT a return contribution. The rows sum to 1.0. A value of 0.64 means "this holding produced 64% of the period's total P&L" — never "this holding contributed 64% of return". Rendering it as bps directly overstates by a factor of 1 / total_return. Return contribution in bps is contribution_pct × portfolio_summary.total_return × 10000.
  • Sign comes from the converted value, never from raw contribution_pct. In a losing period the portfolio's total P&L is negative, so every losing holding has a positive contribution_pct (negative ÷ negative). Ranking or splitting contributors from detractors on the raw field puts the biggest loser at the top of the contributors table and suppresses the detractors table entirely. The conversion above restores the correct sign in every case, because total_return carries the period's sign.
  • Server-side numbers are canonical. analyze_portfolio.company_contribution produces the LP-facing numbers; scripts/contribution.py is the local reconciliation audit. If |local − server| > 25 bps on total return, halt rendering with an audit-failed error. The 25-bp tolerance accommodates known rebalance-date convention skew between Parallax server-side and local math; tighter agreement would require aligning conventions in v2.
  • scripts/contribution.py enforces a 1-bp inner reconciliation gate (|sum(contributions) − portfolio_total_return| > 1bpReconciliationError). On gate fire, surface the diff and abort. Never catch-and-discard.
  • prior_portfolio and trade_log are HARD-REQUIRED. Reject incomplete invocations with the accepted-shape examples in Inputs. No silent degradation to position-only contribution.
  • daily_prices MUST be total-return prices (dividends reinvested). export_price_series returns TR-adjusted closes; do not mix in raw closes from any other source.
  • Per-mover fan-out cap: 5 contributors + 5 detractors = 10 names max. Do not exceed.
  • Soft cap on holdings: 40. Auto-truncate to top 40 by current weight; surface "Holdings 41-N (X% weight) excluded from per-position analysis". Daily contribution math runs over the FULL set; only per-mover fan-out is truncated.
  • macro_analyst parameter is market (not country). Cap macro coverage at 3 markets.
  • Period start must be within the trailing 365-day window from today (\export_price_series\ limit — trailing from today, no start/end anchor). For periods starting >365 days ago, reject with a v2-roadmap note.
  • Output is Word .docx ONLY via the docx skill chain. Markdown is an intermediate artifact, never the deliverable.
  • Materiality tiers for excluded holdings: ≤5% total → in-line "Coverage gap" note above contributors table; >5% → high-visibility WARNING banner at top; any single holding >10% → reject the attribution section entirely.
  • Driver-field fallback hierarchy: (1) news event with date from get_news_synthesis, (2) factor score change from get_score_analysis, (3) sector movement from analyze_portfolio sector_contribution / sector_allocation (this skill never calls get_peer_snapshot, and analyze_portfolio returns no peer data), (4) default phrase ("Price appreciation in line with [sector / market]" or "Multiple expansion / contraction"). Never render an empty driver slot.
  • Empty contributor / detractor side: if all holdings have positive (negative) converted return contribution, render the populated table only; suppress the empty side rather than rendering "No detractors" prose.
  • Single-holding portfolio: top-5 collapses to top-1 (or top-N for N

Where `inputs.json` provides the structured inputs in the shapes below. The CIO reviews, edits in Word, and sends to LPs. The skill never sends or stores anything externally.

## Inputs

| Field | Required? | Shape | Notes |
|---|---|---|---|
| `current_portfolio` | yes | `[{"symbol":"AAPL.O","weight":0.25}, ...]` | RIC format. Weights sum to ~1.0. |
| `prior_portfolio` | **yes** | `[{"symbol":"AAPL.O","weight":0.20}, ...]` | Period-start snapshot. Hard-required — anchor for trades. |
| `trade_log` | **yes** | `[{"symbol":"NVDA.O","action":"enter","date":"2026-04-15","weight_delta":0.05}, ...]` | Chronologically ordered. Actions ∈ {`add`, `trim`, `enter`, `exit`}. Same-date weight_deltas should sum to zero across symbols. Hard-required — without it, the math is silently wrong when positions change mid-period. |
| `period_start` | yes | ISO `YYYY-MM-DD` | Inclusive. |
| `period_end` | yes | ISO `YYYY-MM-DD` | Inclusive. Period length must be ≤ 365 days. |
| `benchmark` | optional | `"SPY.N"` or RIC | If supplied, attribution snapshot includes excess return. |
| `length_target` | optional | `"short"` (≤ 600 words) \| `"standard"` (≤ 1000) \| `"long"` (≤ 1500) | Default: `"standard"`. Affects narrative density, not table content. |
| `house_view` | optional | view manifest per `_parallax/house-view/loader.md` | Forward-outlook section ONLY. |

If `prior_portfolio` or `trade_log` is missing, reject with a clear error and an accepted-shape example.

## Workflow

JIT-load `_parallax/parallax-conventions.md` for execution-mode, RIC resolution, symbol cross-validation, fallbacks, news async, and macro reasoning. JIT-load `_parallax/house-view/loader.md` ONLY if `house_view` is supplied AND the forward-outlook section will render.

### Batch 0 — Tool loading + house view check

1. Call `ToolSearch` with query `"+Parallax"` to load deferred MCP tool schemas.
2. Validate inputs: weights sum to 1.0 (within 1e-3), period ≤ 365 days, trade_log chronologically ordered, weight_delta sums per date balance to zero, no duplicate symbols.
3. If portfolio has > 40 holdings, truncate to top 40 by current weight and record the truncated tail's combined weight for the excluded-holdings note. Daily contribution math still runs over the FULL set; only per-mover fan-out is truncated.
4. If `house_view` is supplied, load it per loader.md §1-§2. If validation fails, treat as no view (omit forward-outlook). If no `house_view`, skip the forward-outlook section entirely.

### Batch A — Period analytics fan-out (parallel)

Fire all rows below in a single tool-call turn. Every row is independent. Cross-validate each holding's identity per the `analyze_portfolio` row of the conventions §2 identity table: compare the per-row `name` in `latest_holdings[]` and `company_contribution[]` against `get_company_info.name` for the same `ric`, normalizing both sides first per §2 step 2 — `Apple Inc` and `Apple Inc.` are the same company and must not flag. Flag any mismatch that survives normalization and exclude mismatched holdings from aggregate factor calculations. A holding with a row in neither `latest_holdings[]` nor `company_contribution[]`, or whose `get_company_info` call fails or returns empty, has no name pair to compare and therefore cannot be identity-checked — record it as UNCHECKED and list it under coverage gaps rather than treating a missing comparison as a pass.

| Tool | Parameters | Notes |
|---|---|---|
| `mcp__claude_ai_Parallax__get_telemetry` | fields: regime_tag, signals, commentary.headline, commentary.mechanism, divergences | Market regime context for the period header. |
| `mcp__claude_ai_Parallax__analyze_portfolio` | Construct the `portfolio` array as one entry at `period_start` carrying `prior_portfolio` weights, plus one entry per distinct trade date in `trade_log` carrying the cumulative-post-trade weights as of that date. The final entry's weights MUST equal `current_portfolio`. `start_date=period_start`, `end_date=period_end`, `benchmark=`, `fields=["portfolio_summary","performance_metrics","drawdown_analysis","portfolio_scores","concentration_metrics","company_contribution","sector_contribution","sector_allocation","time_period_returns","latest_holdings"]`. For the common single-rebalance case (one trade date `D`), the array has 2 entries: `[{date: period_start, ...prior}, {date: D, ...current}]`. | **Single multi-date call.** Server-side `company_contribution` is canonical; current+prior factor exposures via `latest_holdings` + `sector_allocation` over time. `scripts/contribution.py` runs as the reconciliation audit (Batch B step 1). |
| `mcp__claude_ai_Parallax__export_price_series` | `symbol=`, `days=` | One call per holding (parallel). Returns TR-adjusted closes. Trailing window anchored to period_start (today must be within 365 days of period_start); slice fetched prices to [period_start, period_end] before passing to contribution.py. Fires for the FULL holdings set (input to local audit) — Batch B fan-out is the truncated set, not this. |
| `mcp__claude_ai_Parallax__get_company_info` | `symbol=` — **one call per holding, all fanned out in parallel within this batch** | Ground-truth name oracle for the identity gate above. FREE, instant. Per-holding only — do NOT consolidate into a comma-joined call: comma-joined calls fail-empty on partial coverage, so a single unresolved RIC silently zeroes the whole batch, leaving the identity gate with nothing to compare and passing every holding. Per-holding calls also mean the single-symbol `GET_COMPANY_INFO_SCHEMA` and its mock model the response this skill actually receives. |
| `mcp__claude_ai_Parallax__check_portfolio_redundancy` | `holdings=current_portfolio` | Surfaced under coverage gaps if low coverage; otherwise informs trade-narrative quality. |

Asset-class scope: this skill assumes equity legs only (single-stock fund-manager portfolios). `export_price_series` is the correct call for every holding. If a holding resolves to ETF, branch via `etf_daily_price` per the equity-branch convention; equity is the default Pre-classification gate.

If `export_price_series` fails for a holding, mark that holding as price-unavailable and apply materiality-tier handling after Batch B's contribution math.

### Batch B — Top movers + macro fan-out (after Batch A's contribution math)

**Step 1 — Reconciliation audit.** Server-side `analyze_portfolio.company_contribution` provides the canonical LP-facing numbers. Run `scripts/contribution.py` locally as a cross-check:

```bash
# Illustrative — template-fill from JSON inputs at runtime, do not copy {...} literally
python -c "
import json, sys
sys.path.insert(0, 'skills/parallax-cio-letter-prep/scripts')
from contribution import daily_contribution, ReconciliationError
result = daily_contribution(
    prior_portfolio=PRIOR_PORTFOLIO,        # dict from prior_portfolio input
    current_portfolio=CURRENT_PORTFOLIO,    # dict from current_portfolio input
    trade_log=TRADES,                       # list from trade_log input
    daily_prices=DAILY_PRICES,              # built from Batch A export_price_series results
    period_start=PERIOD_START,
    period_end=PERIOD_END,
)
print(json.dumps(result))
"

Compare local result['portfolio_total_return'] against server-side analyze_portfolio.portfolio_summary.total_return:

  • |local − server| ≤ 25 bps → render server-side numbers in the pack; note "local audit passed (diff {N} bps)" in About This Report.
  • |local − server| > 25 bps → halt rendering with "Reconciliation audit failed: local {X} bps vs server {Y} bps (diff {Z} bps exceeds 25-bp tolerance). Investigate before sending to LPs."

The 25-bp tolerance accommodates known rebalance-date convention skew (Parallax server-side rebalances ON date D; contribution.py applies trades AFTER D's close). For monthly periods the typical divergence is ~10-25 bps; values outside this band signal a real bug in inputs, prices, or trade log.

The script's own 1-bp inner gate (ReconciliationError on |sum(contributions) − portfolio_total_return| > 1bp) still fires as the inner safety net.

Step 2 — Materiality tiers based on holdings excluded from contribution due to missing prices:

| Total excluded weight | Action | |---|---| | 0% 5% | High-visibility WARNING banner at the top of the doc: "WARNING: [X.X%] of the portfolio was excluded from contribution analysis due to missing data. Key performance drivers may be missing from this report." | | Any single holding > 10% weight | Reject the attribution section entirely with an error pointing to the missing symbol. |

Step 3 — Convert each server-side company_contribution row to return contribution first: contrib_bps = contribution_pct × portfolio_summary.total_return × 10000. Rank on that converted value, not on raw contribution_pct — see the basis and sign gotchas above. Select the top 5 contributors (positive contrib_bps) and bottom 5 detractors (negative contrib_bps). If a side has fewer than 5 same-signed holdings, render only what exists (suppress empty side).

Step 4 — Fan out per-mover and macro calls in parallel:

| Tool | Calls | Notes | |---|---|---| | mcp__claude_ai_Parallax__get_assessment | × ≤ 10 | Async (~30-90s). AI synthesis used for "why" prose. | | mcp__claude_ai_Parallax__get_score_analysis | × ≤ 10 | Weekly factor score history — input to driver fallback step 2. | | mcp__claude_ai_Parallax__get_news_synthesis | × ≤ 10 | Async (~30-90s). Primary input to driver fallback step 1. | | mcp__claude_ai_Parallax__macro_analyst | × ≤ 3 markets | Parameter is market (not country). Pick relevant markets per conventions §6. Component default — call once per market; the summary call returns all components inline. |

Per conventions §5, the async tools should not block render assembly; if they have not resolved by render time, leave a [news pending] placeholder and complete on resolution.

Pre-Render — Load white-label branding

Load _parallax/white-label/integration-pattern.md and apply §2 (Loading the branding) verbatim. The loader call is load_visual_branding() (NOT load_client_branding()); the wrapper returns the 7-key visual subset and structurally excludes voice. Compute white_label_active per §2.

If white_label_active is True, the render in Batch C applies the docx substitution table at integration-pattern.md §6. Logo path (when present) is inserted at the cover-page header per §6 (left-aligned, ≤1.5 inch height). Semantic colors (cg-green-700, cg-red-700, cg-amber-*) are NEVER overridden per §1.

If white_label_active is False, the Default brand palette table in Output Format below applies; no cover-page logo is inserted. On logo_missing (partial-success path per integration-pattern.md §4), palette and fonts still apply; only the cover-page logo is skipped.

About This Report line wording follows i

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.