Asteria Investment Office — Task-Solving Skill
Environment & Data Precedence
Single source of truth: The remote Asteria API at GDPEVO_ENV_BASE_URL (set per session). All portfolio, bond, issuer, index, policy, allocation, and signal data lives there. The root GET / enumerates available endpoints.
Stale-data rule: Every local payload (desk requests, memos, committee packets, stale snapshots) may contain outdated marks, quantities, or preferences. The API environment is the current book of record. When local data conflicts with API data, the API wins. The data_precedence field in relevant templates should be "current_environment_over_stale_payload" whenever the API is used to resolve or override local numbers.
as_of_date: Always pulled from the current portfolio record on the API (/api/portfolios/<id> → as_of_date). Across the 2026-05 environment this is "2026-05-29". Use it for every output's as_of_date field.
API Data Model Quick Reference
| Endpoint |
Key fields |
Used by tasks |
GET /api/catalog |
All IDs (portfolios, bonds, indices, issuers, policies, opportunity sets) |
Orientation |
GET /api/policies |
Credit constraints (HY cap, duration band, issuer limit, subsector min), correlation thresholds (high 0.8, low 0.2), allocation mapping (view/conviction thresholds), policy IDs |
All |
GET /api/portfolios/<id> |
Holdings (instrument_id, quantity_usd_m, sleeve), market_value_usd_m, constraint_policy_id, as_of_date |
1,2,4,5 |
GET /api/instruments/bonds |
instrument_id, issuer_id, rating_bucket (IG/HY), yield_to_maturity_pct, modified_duration_years, energy_linked (bool), candidate (bool), sector, subsector, recommended_theme_tags |
1,4,5 |
GET /api/issuers |
issuer_id, watchlist (bool), rating_bucket, sector, subsector, credit_outlook |
1,4 |
GET /api/indices |
index_id, display_name, region, frequency, level_start_date, level_end_date |
2,5 |
GET /api/index-levels |
Per-index array of {date, level} ordered chronologically, 12 monthly points from 2025-05-30 to 2026-04-30 |
2,5 |
GET /api/market/energy |
Commodity signals with score, direction, signal_id |
1 |
GET /api/allocation/opportunity-sets |
opportunity_set, asset_class, sub_asset_class, display_order |
3,5 |
GET /api/allocation/prior-views |
opportunity_set, quarter, previous_quarter, view, conviction |
3,5 |
GET /api/macro-signals |
opportunity_set, quarter, score, rationale_code, drivers |
3,5 |
Task-Type Workflows
Type 1: Credit Trade Strategy (train_001 — PF-EN-ALTA)
Goal: Select N BUY/SELL tickets meeting notional, eligibility, and constraint requirements while improving carry.
Step-by-step:
GET /api/portfolios/<id> → current holdings, MV, constraint_policy_id, as_of_date
GET /api/policies → read the constraint block matching the portfolio's constraint_policy_id for HY cap, duration band, issuer limit, subsector min
GET /api/instruments/bonds → filter to eligible candidates (candidate: true, plus any sector/theme filters from the request)
GET /api/issuers → cross-reference watchlist status; never BUY a watchlisted issuer's bonds
GET /api/market/energy (if energy-linked) → thematic support for sales positioning
- Select bonds, compute post-trade metrics, run constraint checks, fill template
Post-trade metric formulas (all market-value-weighted):
total_market_value_usd_m = current MV + sum(new notionals), rounded to 2 decimals
hy_allocation_pct = (sum of HY-rated holdings post-trade) / post_trade_MV × 100, rounded to 2 decimals
weighted_modified_duration_years = Σ(holding_qty × bond_duration) / post_trade_MV, rounded to 2 decimals
weighted_yield_to_maturity_pct = Σ(holding_qty × bond_ytm) / post_trade_MV, rounded to 2 decimals
Constraint checks (all boolean):
hy_cap_pass: post_trade_hy_pct ≤ max_hy_allocation_pct (20%)
duration_band_pass: post_trade_duration ∈ [duration_band_years[0], duration_band_years[1]] ([3.0, 5.0])
selected_issuer_diversification_pass: the selected bonds are from at least 2 distinct issuers, AND no single issuer's post-trade total exceeds issuer_concentration_limit_pct (12%) of post-trade MV
selected_subsector_diversification_pass: the selected bonds span at least subsector_min_count_for_diversified (2) distinct subsectors
watchlist_avoidance_pass: none of the selected BUY bonds belong to a watchlisted issuer
Sales positioning: Match to template enum. For energy-income pitches, "multi_asset_income" + theme from market signals (e.g., "lng_export_tailwind" if LNG scores highest).
Trade ordering: Sort by instrument_id ascending within each action group, or as specified by the template.
Type 2: Equity Correlation Review (train_002 — PF-INT-NEXVEN)
Goal: Compute Pearson correlations across an index universe and identify concentration/diversification signals.
Step-by-step:
GET /api/portfolios/<id> → holdings, constraint_policy_id
GET /api/policies → correlation thresholds (correlation_high_threshold: 0.8, correlation_low_threshold: 0.2)
GET /api/index-levels → for each index in the review universe, extract the 12 monthly level values
- Compute monthly simple returns, then all pairwise Pearson correlations
Return calculation (CRITICAL — use simple returns, NOT log returns):
For each index, for t = 1..11:
r_t = (level_t - level_{t-1}) / level_{t-1}
This yields 11 return observations per index (12 levels → 11 returns).
Pearson correlation:
r_xy = Σ((x_i - x̄)(y_i - ȳ)) / sqrt(Σ(x_i - x̄)² × Σ(y_i - ȳ)²)
Round to 3 decimal places for all correlation outputs.
Extreme pairs:
highest_positive: the pair with the largest (most positive) correlation value
lowest: the pair with the smallest (most negative) correlation value
- For each, output
pair_id (list of 2 index IDs, alphabetically sorted) and correlation
Concentration analysis:
china_asia_dependence_flag: true if China / AC Asia Pac ex JP correlation exceeds correlation_high_threshold (0.8)
primary_code: "CHINA_ASIA_DEPENDENCE" if flag is true and this is the primary concern; "GLOBAL_DEVELOPED_OVERLAP" if developed-market pairs dominate; "NO_MATERIAL_CONCENTRATION" otherwise
high_threshold_breached: true if ANY pair exceeds 0.8
Diversification candidates: Among the allowed candidate set, select indices whose pairwise correlations are ≤ correlation_low_threshold (0.2) with the concentration source. Sort alphabetically.
Index/pair ordering: Always sort index IDs alphabetically within pairs and lists.
Type 3: Allocation View Refresh (train_003 — CIO Q2 2026)
Goal: Derive active allocation views from macro signal scores using policy thresholds.
Step-by-step:
GET /api/allocation/opportunity-sets → asset_class for each opportunity set
GET /api/macro-signals → filter to quarter: "Q2_2026" entries; extract score and rationale_code for each requested opportunity set
GET /api/allocation/prior-views → filter to quarter: <target_quarter> entries (these are the views set in the prior quarter for the target quarter); extract view as the prior view
GET /api/policies → allocation_mapping thresholds
View derivation from signal score (use allocation_mapping.view_score_thresholds):
| Condition |
View |
| score ≥ OW_min (0.35) |
"OW" |
| score ≤ UW_max (-0.35) |
"UW" |
| -0.35 < score < 0.35 |
"N" |
Change vs. prior quarter (use allocation_mapping.view_rank: OW=1, N=0, UW=-1):
| Condition |
Change |
| current_rank > prior_rank |
"UP" |
| current_rank < prior_rank |
"DOWN" |
| current_rank == prior_rank |
"UNCHANGED" |
Conviction from abs(score) (use allocation_mapping.conviction_thresholds):
| abs(score) |
Conviction |
| ≥ HIGH_abs_min (0.7) |
"HIGH" |
| ≥ MEDIUM_abs_min (0.35) |
"MEDIUM" |
| < LOW_abs_below (0.35) |
"LOW" |
Rationale code: Copy the rationale_code from the macro-signal record for that opportunity set. This is a direct lookup, not derived.
Risk overlay: Select overlay_code and primary_action based on the aggregate pattern of views. Common patterns:
- Many UW credit views + OW duration views →
"CREDIT_RISK_REDUCTION" / "trim_credit_beta"
- OW on duration, mixed equities →
"DURATION_QUALITY_TILT" / "tilt_to_duration_quality"
- No strong tilt →
"NO_OVERLAY" / "hold_policy_weights"
rationale_codes for the overlay: list the most relevant rationale codes from the macro signals that support the overlay choice, in business priority order (highest priority first).
Row ordering: Follow the request payload's focus_opportunity_sets order exactly.
Policy ID: Use the policy_id from the allocation_mapping block in /api/policies.
Type 4: Fixed-Income Risk Rebalance (train_004 — PF-FI-LUMEN)
Goal: Rotate out of HY/watchlist positions and into eligible IG candidates while meeting reduction targets and duration constraints.
Step-by-step:
GET /api/portfolios/<id> → current holdings, MV, constraint_policy_id
GET /api/policies → read the matching constraint block for HY cap, duration band, target HY reduction
GET /api/instruments/bonds → current holdings' details + candidate pool
GET /api/issuers → watchlist cross-reference
- Design SELL/BUY rotation, compute post-trade metrics, fill template
Rotation design rules:
- SELL: target HY-rated holdings and/or watchlisted-issuer bonds. Use the portfolio's actual current quantities (from API), not stale payload quantities.
- BUY: select from
candidate: true bonds that are IG-rated and NOT from watchlisted issuers. Match the portfolio's sector/style.
- SELL and BUY notional amounts should balance (or be sized to meet the HY reduction target).
hy_reduction_pct_points = pre_trade_hy_pct − post_trade_hy_pct, rounded to 2 decimals.
Post-trade watchlist exposure: Sum of all post-trade holdings from watchlisted issuers. If all watchlisted bonds are sold, this is 0.0.
Trade ordering: SELL before BUY, then by instrument_id ascending within each action group.
Constraint flags:
hy_cap_pass: post_trade_hy_pct ≤ max_hy_allocation_pct
duration_band_pass: post_trade_duration ∈ [band_low, band_high]
target_hy_reduction_met: hy_reduction_pct_points ≥ target_hy_reduction_pct (4.0 for risk-reduction policy)
watchlist_exposure_cleared: post_trade_watchlist_exposure_usd_m == 0.0 (or negligible)
Watchlist handling output:
watchlist_sell_ids: instrument_ids of sold watchlisted bonds, sorted ascending
buys_avoid_watchlist: true if all BUY tickets are from non-watchlisted issuers
risk_note_code: Pick the enum value that best describes the primary risk trade-off in the rotation.
Type 5: Multi-Asset Committee JSON (train_005 — PF-MA-HELIO)
Goal: Combine equity correlation findings with active allocation views into a single committee decision file.
Step-by-step:
GET /api/portfolios/<id> → holdings, policy_id
GET /api/index-levels → for the 4-index subset (EM, China, India, LatAm), compute Pearson correlations (same Type 2 method)
GET /api/macro-signals → for the requested opportunity sets (Emerging Markets, India, Latin America, USD), get scores and rationale codes
GET /api/allocation/prior-views → prior views for the requested sets
GET /api/policies → thresholds
Correlation summary (2 items, in order):
highest_concentration: the pair with the highest positive correlation among the 4-index subset → indicates concentration risk
best_diversifier: the pair with the lowest (most negative) correlation → indicates diversification benefit
Target sleeve actions (ordered by opportunity_set as specified):
- Derive from both correlation signals AND allocation views
"trim" for concentrated/overweight positions; "add" for diversifying/underweight positions; "hold" for neutral; "hedge" for high-concentration positions needing offset; "monitor" for borderline cases; "rotate" for replacing one exposure with another
Allocation views: Same Type 3 derivation but includes signal_score (raw score, rounded to 3 decimals) and prior_view (from prior-views endpoint).
rebalance_trigger: The primary reason for action — pick the enum that matches the dominant signal.
portfolio_risk_concentration_flag: true if any correlation in the subset exceeds the high threshold (0.8).
next_step: The committee's decision based on constraint checks and risk flags.
Precision & Rounding Rules
All numeric output fields must match the precision declared in the answer template:
| Field type |
Typical precision |
Example |
| Market values, notionals |
1 decimal (USD M) |
4.0, 68.0 |
| Portfolio MV |
2 decimals (USD M) |
68.00 |
| HY allocation % |
2 decimals |
13.24 |
| Duration (years) |
2 decimals |
3.28 |
| YTM % |
2 decimals |
5.80 |
| Correlation |
3 decimals |
0.974 |
| Signal score |
3 decimals |
0.732 |
| HY reduction (pp) |
2 decimals |
4.00 |
Use standard rounding (round half to even, or round(value, N) in Python). Do not truncate.
Sorting & Ordering Conventions
- Index IDs in pairs: Always alphabetical ascending (e.g.,
["IDX_CHINA", "IDX_LATAM"])
- Index lists: Alphabetical ascending by index_id
- Trade lists: When template says "SELL before BUY, then instrument_id ascending within each action" — follow that exactly
- Allocation rows: Follow the request payload's
focus_opportunity_sets order — preserve the input sequence, do not re-sort
- Correlation summary items: Follow the template's
item_order array
- Watchlist sell IDs: Ascending instrument_id order
- Overlay rationale codes: Business priority order (highest priority first)
Common Pitfalls
- Using stale payload quantities as current holdings. Always fetch current quantities from
/api/portfolios/<id>. Local payloads often contain stale snapshots.
- Log returns instead of simple returns. The spec says "monthly simple returns" — use
(P₁-P₀)/P₀, never ln(P₁/P₀).
- Confusing signal score sign with view direction. A negative score below -0.35 maps to UW; a positive score above 0.35 maps to OW. The sign of the score IS the direction signal.
- Watchlist check via bond tags only. Always cross-reference
/api/issuers → watchlist: true. The WATCHLIST_RISK theme tag on a bond is a hint, but the issuer record is authoritative.
- Filtering out non-energy-linked bonds for non-energy portfolios. Only apply
energy_linked: true filter when the task explicitly requires energy-linked bonds (train_001). Other credit tasks use the full bond universe.
- Wrong policy block. Match the portfolio's
constraint_policy_id to the correct block in /api/policies. Different portfolios use different policies with different thresholds.
- Incorrect observation count. With 12 monthly index levels, there are 11 simple returns. Report
return_observations: 11.
- Missing the
candidate filter. Only bonds with candidate: true can be selected for BUY trades. Held bonds may have candidate: false.
- Overlooking issuer concentration from existing holdings. When checking
selected_issuer_diversification_pass, account for the TOTAL post-trade exposure per issuer (existing + new), not just the new tickets.
- Using the wrong quarter's prior views. Filter prior-views by
quarter: <target_quarter> to get the views that were set in the prior quarter for the current target quarter.
- Rounding before all calculations are done. Keep full precision through intermediate steps; only round final output values.
- Not using the answer_template.json structure. Every task provides an
answer_template.json — match its exact key names, types, enums, and nesting.
Output Conventions
- Return only the JSON object (unless the task explicitly allows narrative). No markdown wrapping, no commentary outside the JSON.
- All
required keys in the template must be present, even if their value is an empty list.
- Boolean fields use JSON
true/false (not strings).
- String enum values must match the allowed values exactly, including case and underscores.
- The
portfolio_id field is always a required string matching the portfolio being analyzed.
1---2name: self-attempt-03-483description: Asteria Investment Office — Task-Solving Skill4---5# Asteria Investment Office — Task-Solving Skill67## Environment & Data Precedence89**Single source of truth**: The remote Asteria API at `GDPEVO_ENV_BASE_URL` (set per session). All portfolio, bond, issuer, index, policy, allocation, and signal data lives there. The root `GET /` enumerates available endpoints.1011**Stale-data rule**: Every local payload (desk requests, memos, committee packets, stale snapshots) may contain outdated marks, quantities, or preferences. The API environment is the current book of record. When local data conflicts with API data, the API wins. The `data_precedence` field in relevant templates should be `"current_environment_over_stale_payload"` whenever the API is used to resolve or override local numbers.1213**as_of_date**: Always pulled from the current portfolio record on the API (`/api/portfolios/<id>` → `as_of_date`). Across the 2026-05 environment this is `"2026-05-29"`. Use it for every output's `as_of_date` field.1415---1617## API Data Model Quick Reference1819| Endpoint | Key fields | Used by tasks |20|---|---|---|21| `GET /api/catalog` | All IDs (portfolios, bonds, indices, issuers, policies, opportunity sets) | Orientation |22| `GET /api/policies` | Credit constraints (HY cap, duration band, issuer limit, subsector min), correlation thresholds (high 0.8, low 0.2), allocation mapping (view/conviction thresholds), policy IDs | All |23| `GET /api/portfolios/<id>` | Holdings (instrument_id, quantity_usd_m, sleeve), market_value_usd_m, constraint_policy_id, as_of_date | 1,2,4,5 |24| `GET /api/instruments/bonds` | instrument_id, issuer_id, rating_bucket (IG/HY), yield_to_maturity_pct, modified_duration_years, energy_linked (bool), candidate (bool), sector, subsector, recommended_theme_tags | 1,4,5 |25| `GET /api/issuers` | issuer_id, watchlist (bool), rating_bucket, sector, subsector, credit_outlook | 1,4 |26| `GET /api/indices` | index_id, display_name, region, frequency, level_start_date, level_end_date | 2,5 |27| `GET /api/index-levels` | Per-index array of {date, level} ordered chronologically, 12 monthly points from 2025-05-30 to 2026-04-30 | 2,5 |28| `GET /api/market/energy` | Commodity signals with score, direction, signal_id | 1 |29| `GET /api/allocation/opportunity-sets` | opportunity_set, asset_class, sub_asset_class, display_order | 3,5 |30| `GET /api/allocation/prior-views` | opportunity_set, quarter, previous_quarter, view, conviction | 3,5 |31| `GET /api/macro-signals` | opportunity_set, quarter, score, rationale_code, drivers | 3,5 |3233---3435## Task-Type Workflows3637### Type 1: Credit Trade Strategy (train_001 — PF-EN-ALTA)3839**Goal**: Select N BUY/SELL tickets meeting notional, eligibility, and constraint requirements while improving carry.4041**Step-by-step**:421. `GET /api/portfolios/<id>` → current holdings, MV, constraint_policy_id, as_of_date432. `GET /api/policies` → read the constraint block matching the portfolio's `constraint_policy_id` for HY cap, duration band, issuer limit, subsector min443. `GET /api/instruments/bonds` → filter to eligible candidates (`candidate: true`, plus any sector/theme filters from the request)454. `GET /api/issuers` → cross-reference `watchlist` status; never BUY a watchlisted issuer's bonds465. `GET /api/market/energy` (if energy-linked) → thematic support for sales positioning476. Select bonds, compute post-trade metrics, run constraint checks, fill template4849**Post-trade metric formulas** (all market-value-weighted):50- `total_market_value_usd_m` = current MV + sum(new notionals), rounded to 2 decimals51- `hy_allocation_pct` = (sum of HY-rated holdings post-trade) / post_trade_MV × 100, rounded to 2 decimals52- `weighted_modified_duration_years` = Σ(holding_qty × bond_duration) / post_trade_MV, rounded to 2 decimals53- `weighted_yield_to_maturity_pct` = Σ(holding_qty × bond_ytm) / post_trade_MV, rounded to 2 decimals5455**Constraint checks** (all boolean):56- `hy_cap_pass`: post_trade_hy_pct ≤ max_hy_allocation_pct (20%)57- `duration_band_pass`: post_trade_duration ∈ [duration_band_years[0], duration_band_years[1]] ([3.0, 5.0])58- `selected_issuer_diversification_pass`: the selected bonds are from at least 2 distinct issuers, AND no single issuer's post-trade total exceeds `issuer_concentration_limit_pct` (12%) of post-trade MV59- `selected_subsector_diversification_pass`: the selected bonds span at least `subsector_min_count_for_diversified` (2) distinct subsectors60- `watchlist_avoidance_pass`: none of the selected BUY bonds belong to a watchlisted issuer6162**Sales positioning**: Match to template enum. For energy-income pitches, `"multi_asset_income"` + theme from market signals (e.g., `"lng_export_tailwind"` if LNG scores highest).6364**Trade ordering**: Sort by `instrument_id` ascending within each action group, or as specified by the template.6566---6768### Type 2: Equity Correlation Review (train_002 — PF-INT-NEXVEN)6970**Goal**: Compute Pearson correlations across an index universe and identify concentration/diversification signals.7172**Step-by-step**:731. `GET /api/portfolios/<id>` → holdings, constraint_policy_id742. `GET /api/policies` → correlation thresholds (`correlation_high_threshold: 0.8`, `correlation_low_threshold: 0.2`)753. `GET /api/index-levels` → for each index in the review universe, extract the 12 monthly level values764. Compute monthly simple returns, then all pairwise Pearson correlations7778**Return calculation** (CRITICAL — use simple returns, NOT log returns):79```80For each index, for t = 1..11:81 r_t = (level_t - level_{t-1}) / level_{t-1}82```83This yields 11 return observations per index (12 levels → 11 returns).8485**Pearson correlation**:86```87r_xy = Σ((x_i - x̄)(y_i - ȳ)) / sqrt(Σ(x_i - x̄)² × Σ(y_i - ȳ)²)88```89Round to **3 decimal places** for all correlation outputs.9091**Extreme pairs**:92- `highest_positive`: the pair with the largest (most positive) correlation value93- `lowest`: the pair with the smallest (most negative) correlation value94- For each, output `pair_id` (list of 2 index IDs, alphabetically sorted) and `correlation`9596**Concentration analysis**:97- `china_asia_dependence_flag`: true if China / AC Asia Pac ex JP correlation exceeds `correlation_high_threshold` (0.8)98- `primary_code`: `"CHINA_ASIA_DEPENDENCE"` if flag is true and this is the primary concern; `"GLOBAL_DEVELOPED_OVERLAP"` if developed-market pairs dominate; `"NO_MATERIAL_CONCENTRATION"` otherwise99- `high_threshold_breached`: true if ANY pair exceeds 0.8100101**Diversification candidates**: Among the allowed candidate set, select indices whose pairwise correlations are ≤ `correlation_low_threshold` (0.2) with the concentration source. Sort alphabetically.102103**Index/pair ordering**: Always sort index IDs alphabetically within pairs and lists.104105---106107### Type 3: Allocation View Refresh (train_003 — CIO Q2 2026)108109**Goal**: Derive active allocation views from macro signal scores using policy thresholds.110111**Step-by-step**:1121. `GET /api/allocation/opportunity-sets` → asset_class for each opportunity set1132. `GET /api/macro-signals` → filter to `quarter: "Q2_2026"` entries; extract `score` and `rationale_code` for each requested opportunity set1143. `GET /api/allocation/prior-views` → filter to `quarter: <target_quarter>` entries (these are the views set in the prior quarter for the target quarter); extract `view` as the prior view1154. `GET /api/policies` → allocation_mapping thresholds116117**View derivation from signal score** (use `allocation_mapping.view_score_thresholds`):118| Condition | View |119|---|---|120| score ≥ OW_min (0.35) | `"OW"` |121| score ≤ UW_max (-0.35) | `"UW"` |122| -0.35 < score < 0.35 | `"N"` |123124**Change vs. prior quarter** (use `allocation_mapping.view_rank`: OW=1, N=0, UW=-1):125| Condition | Change |126|---|---|127| current_rank > prior_rank | `"UP"` |128| current_rank < prior_rank | `"DOWN"` |129| current_rank == prior_rank | `"UNCHANGED"` |130131**Conviction from abs(score)** (use `allocation_mapping.conviction_thresholds`):132| abs(score) | Conviction |133|---|---|134| ≥ HIGH_abs_min (0.7) | `"HIGH"` |135| ≥ MEDIUM_abs_min (0.35) | `"MEDIUM"` |136| < LOW_abs_below (0.35) | `"LOW"` |137138**Rationale code**: Copy the `rationale_code` from the macro-signal record for that opportunity set. This is a direct lookup, not derived.139140**Risk overlay**: Select `overlay_code` and `primary_action` based on the aggregate pattern of views. Common patterns:141- Many UW credit views + OW duration views → `"CREDIT_RISK_REDUCTION"` / `"trim_credit_beta"`142- OW on duration, mixed equities → `"DURATION_QUALITY_TILT"` / `"tilt_to_duration_quality"`143- No strong tilt → `"NO_OVERLAY"` / `"hold_policy_weights"`144145`rationale_codes` for the overlay: list the most relevant rationale codes from the macro signals that support the overlay choice, in business priority order (highest priority first).146147**Row ordering**: Follow the request payload's `focus_opportunity_sets` order exactly.148149**Policy ID**: Use the `policy_id` from the `allocation_mapping` block in `/api/policies`.150151---152153### Type 4: Fixed-Income Risk Rebalance (train_004 — PF-FI-LUMEN)154155**Goal**: Rotate out of HY/watchlist positions and into eligible IG candidates while meeting reduction targets and duration constraints.156157**Step-by-step**:1581. `GET /api/portfolios/<id>` → current holdings, MV, constraint_policy_id1592. `GET /api/policies` → read the matching constraint block for HY cap, duration band, target HY reduction1603. `GET /api/instruments/bonds` → current holdings' details + candidate pool1614. `GET /api/issuers` → watchlist cross-reference1625. Design SELL/BUY rotation, compute post-trade metrics, fill template163164**Rotation design rules**:165- SELL: target HY-rated holdings and/or watchlisted-issuer bonds. Use the portfolio's actual current quantities (from API), not stale payload quantities.166- BUY: select from `candidate: true` bonds that are IG-rated and NOT from watchlisted issuers. Match the portfolio's sector/style.167- SELL and BUY notional amounts should balance (or be sized to meet the HY reduction target).168- `hy_reduction_pct_points` = pre_trade_hy_pct − post_trade_hy_pct, rounded to 2 decimals.169170**Post-trade watchlist exposure**: Sum of all post-trade holdings from watchlisted issuers. If all watchlisted bonds are sold, this is 0.0.171172**Trade ordering**: SELL before BUY, then by `instrument_id` ascending within each action group.173174**Constraint flags**:175- `hy_cap_pass`: post_trade_hy_pct ≤ max_hy_allocation_pct176- `duration_band_pass`: post_trade_duration ∈ [band_low, band_high]177- `target_hy_reduction_met`: hy_reduction_pct_points ≥ target_hy_reduction_pct (4.0 for risk-reduction policy)178- `watchlist_exposure_cleared`: post_trade_watchlist_exposure_usd_m == 0.0 (or negligible)179180**Watchlist handling output**:181- `watchlist_sell_ids`: instrument_ids of sold watchlisted bonds, sorted ascending182- `buys_avoid_watchlist`: true if all BUY tickets are from non-watchlisted issuers183184**risk_note_code**: Pick the enum value that best describes the primary risk trade-off in the rotation.185186---187188### Type 5: Multi-Asset Committee JSON (train_005 — PF-MA-HELIO)189190**Goal**: Combine equity correlation findings with active allocation views into a single committee decision file.191192**Step-by-step**:1931. `GET /api/portfolios/<id>` → holdings, policy_id1942. `GET /api/index-levels` → for the 4-index subset (EM, China, India, LatAm), compute Pearson correlations (same Type 2 method)1953. `GET /api/macro-signals` → for the requested opportunity sets (Emerging Markets, India, Latin America, USD), get scores and rationale codes1964. `GET /api/allocation/prior-views` → prior views for the requested sets1975. `GET /api/policies` → thresholds198199**Correlation summary** (2 items, in order):2001. `highest_concentration`: the pair with the highest positive correlation among the 4-index subset → indicates concentration risk2012. `best_diversifier`: the pair with the lowest (most negative) correlation → indicates diversification benefit202203**Target sleeve actions** (ordered by opportunity_set as specified):204- Derive from both correlation signals AND allocation views205- `"trim"` for concentrated/overweight positions; `"add"` for diversifying/underweight positions; `"hold"` for neutral; `"hedge"` for high-concentration positions needing offset; `"monitor"` for borderline cases; `"rotate"` for replacing one exposure with another206207**Allocation views**: Same Type 3 derivation but includes `signal_score` (raw score, rounded to 3 decimals) and `prior_view` (from prior-views endpoint).208209**rebalance_trigger**: The primary reason for action — pick the enum that matches the dominant signal.210211**portfolio_risk_concentration_flag**: true if any correlation in the subset exceeds the high threshold (0.8).212213**next_step**: The committee's decision based on constraint checks and risk flags.214215---216217## Precision & Rounding Rules218219All numeric output fields must match the precision declared in the answer template:220221| Field type | Typical precision | Example |222|---|---|---|223| Market values, notionals | 1 decimal (USD M) | `4.0`, `68.0` |224| Portfolio MV | 2 decimals (USD M) | `68.00` |225| HY allocation % | 2 decimals | `13.24` |226| Duration (years) | 2 decimals | `3.28` |227| YTM % | 2 decimals | `5.80` |228| Correlation | 3 decimals | `0.974` |229| Signal score | 3 decimals | `0.732` |230| HY reduction (pp) | 2 decimals | `4.00` |231232Use standard rounding (round half to even, or `round(value, N)` in Python). Do not truncate.233234---235236## Sorting & Ordering Conventions2372381. **Index IDs in pairs**: Always alphabetical ascending (e.g., `["IDX_CHINA", "IDX_LATAM"]`)2392. **Index lists**: Alphabetical ascending by index_id2403. **Trade lists**: When template says "SELL before BUY, then instrument_id ascending within each action" — follow that exactly2414. **Allocation rows**: Follow the request payload's `focus_opportunity_sets` order — preserve the input sequence, do not re-sort2425. **Correlation summary items**: Follow the template's `item_order` array2436. **Watchlist sell IDs**: Ascending instrument_id order2447. **Overlay rationale codes**: Business priority order (highest priority first)245246---247248## Common Pitfalls2492501. **Using stale payload quantities as current holdings**. Always fetch current quantities from `/api/portfolios/<id>`. Local payloads often contain stale snapshots.2512. **Log returns instead of simple returns**. The spec says "monthly simple returns" — use `(P₁-P₀)/P₀`, never `ln(P₁/P₀)`.2523. **Confusing signal score sign with view direction**. A negative score below -0.35 maps to UW; a positive score above 0.35 maps to OW. The sign of the score IS the direction signal.2534. **Watchlist check via bond tags only**. Always cross-reference `/api/issuers` → `watchlist: true`. The `WATCHLIST_RISK` theme tag on a bond is a hint, but the issuer record is authoritative.2545. **Filtering out non-energy-linked bonds for non-energy portfolios**. Only apply `energy_linked: true` filter when the task explicitly requires energy-linked bonds (train_001). Other credit tasks use the full bond universe.2556. **Wrong policy block**. Match the portfolio's `constraint_policy_id` to the correct block in `/api/policies`. Different portfolios use different policies with different thresholds.2567. **Incorrect observation count**. With 12 monthly index levels, there are 11 simple returns. Report `return_observations: 11`.2578. **Missing the `candidate` filter**. Only bonds with `candidate: true` can be selected for BUY trades. Held bonds may have `candidate: false`.2589. **Overlooking issuer concentration from existing holdings**. When checking `selected_issuer_diversification_pass`, account for the TOTAL post-trade exposure per issuer (existing + new), not just the new tickets.25910. **Using the wrong quarter's prior views**. Filter prior-views by `quarter: <target_quarter>` to get the views that were set in the prior quarter for the current target quarter.26011. **Rounding before all calculations are done**. Keep full precision through intermediate steps; only round final output values.26112. **Not using the answer_template.json structure**. Every task provides an `answer_template.json` — match its exact key names, types, enums, and nesting.262263---264265## Output Conventions266267- Return **only** the JSON object (unless the task explicitly allows narrative). No markdown wrapping, no commentary outside the JSON.268- All `required` keys in the template must be present, even if their value is an empty list.269- Boolean fields use JSON `true`/`false` (not strings).270- String enum values must match the allowed values exactly, including case and underscores.271- The `portfolio_id` field is always a required string matching the portfolio being analyzed.