Asteria Investment Office — Institutional Portfolio-Risk Solver Skill
Reusable execution knowledge for the Asteria Investment Office remote
environment. Test tasks draw from three workflows: (A) energy / fixed-income
trade strategy, (B) international correlation review, (C) cross-asset active
allocation view updates (sometimes combined in one committee JSON). Re-derive
every value from the live environment per task — do NOT memorize specific
answers.
0. Data-precedence rule (applies to every task)
The remote environment at <remote-env-url> is the current book of
record. Files in input/payloads/ are intake context and MAY BE STALE:
they often carry an earlier snapshot date, stale holding quantities/exception
boards, or stale prior preferences. When a local payload conflicts with the
environment, prefer the environment unless the task prompt explicitly
instructs otherwise.
Staleness signals to ignore in favor of the environment:
stale_holding_snapshot, stale_exception_board, stale_local_note,
candidate_shortlist_from_prior_week, snapshot_date keys.
- Any payload date earlier than the environment
as_of_date (currently
2026-05-29).
- A stale note saying "USD overweight as a defensive offset" or "old worksheet
highlighted…" — these are prior preferences, not current views; refresh from
macro signals / index levels.
The as_of_date you put in every answer = the environment's as_of_date
(2026-05-29), NOT the payload's request/memo date.
1. Environment endpoints & field shapes
Base URL: <remote-env-url>. Call with curl or python urllib.
GET /api/catalog — available portfolio ids, policy ids, index ids, issuer
ids, bond ids, opportunity sets. Good first call to confirm IDs.
GET /api/policies — all policy thresholds (see §2).
GET /api/portfolios — portfolio summaries.
GET /api/portfolios/<id> — {portfolio_id, name, objective, strategy, base_currency, as_of_date, market_value_usd_m, constraints{policy_id, duration_band_years, max_hy_allocation_pct, ...}, holdings[{instrument_id, quantity_usd_m, asset_class, sleeve, notes}]}. The constraints.policy_id
tells you which credit policy applies (and thus the target HY reduction).
GET /api/instruments/bonds — supports filters ?candidate=true,
?rating_bucket=HY. Each bond: {instrument_id, issuer_id, issuer_name, candidate, energy_linked, coupon_pct, maturity, modified_duration_years, rating, rating_bucket (IG|HY), sector, subsector, spread_bps, yield_to_maturity_pct, recommended_theme_tags}.
GET /api/issuers — {issuer_id, issuer_name, sector, subsector, rating_bucket, watchlist (bool), credit_outlook, research_tags}. Key the
watchlist check by issuer_id.
GET /api/market/energy — oil/gas/LNG/refining/renewables signals (theme
context for §A; not directly required for the arithmetic).
GET /api/indices — index metadata.
GET /api/index-levels — a dict mapping each index_id to a list of
{date, level} monthly observations for ALL indices.
GET /api/index-levels/<id> returns one index. No correlations are
precomputed — compute them yourself (§5).
GET /api/allocation/opportunity-sets — 25 sets, each
{opportunity_set, asset_class (Equities|Duration|Credit|Currency), sub_asset_class, display_order}. Use this to map an opportunity-set name to
its asset_class.
GET /api/allocation/prior-views — list of records for multiple quarters;
each {opportunity_set, quarter, previous_quarter, view (UW|N|OW), conviction}. You must filter quarter == target_quarter; the view on
that record is the prior view held entering the target quarter.
GET /api/macro-signals — list of {opportunity_set, quarter, score, rationale_code, drivers}. Filter quarter == target_quarter.
Roster of list endpoints also accept simple equality filters matching field
names, e.g. ?rating_bucket=HY, ?candidate=true, ?quarter=Q2_2026.
2. Policy conventions (GET /api/policies)
Top-level: policy_id: POLICY_SET_2026_05, as_of_date: 2026-05-29.
credit_default (policy_id POL_CREDIT_DEFAULT): duration_band_years
[3.0, 5.0], max_hy_allocation_pct 20.0, issuer_concentration_limit_pct
12.0, subsector_min_count_for_diversified 2, target_hy_reduction_pct 0.0.
credit_risk_reduction (POL_CREDIT_RISK_REDUCTION): same as above but
target_hy_reduction_pct 4.0. Used by risk-rebalance portfolios
(rotation tasks). The per-portfolio constraints names which one applies.
correlation (POL_CORRELATION_DEFAULT): correlation_high_threshold 0.8,
correlation_low_threshold 0.2, review_window_start 2025-05-30,
review_window_end 2026-04-30.
allocation_mapping (POL_ALLOCATION_MAPPING):
view_score_thresholds: OW_min 0.35, UW_max −0.35, neutral
[−0.35, 0.35].
conviction_thresholds: HIGH_abs_min 0.7, MEDIUM_abs_min 0.35,
LOW_abs_below 0.35.
view_rank: {UW: −1, N: 0, OW: 1}.
multi_asset uses allocation_mapping + correlation_default + credit_default.
multi_asset_risk uses credit_risk_reduction + correlation_default;
committee_escalation_threshold: "two_or_more_material_exceptions".
For credit tasks, read the portfolio's constraints to pick
target_hy_reduction_pct (0 vs 4) and the duration band.
3. Precision & ordering conventions (always honor the template)
Always follow the per-field precision declared in
input/payloads/answer_template.json. Verified defaults:
trade_package/rotation notional_usd_m and quantity_usd_m: 1 decimal.
post_trade_metrics: total_market_value_usd_m 2 dp;
hy_allocation_pct 2 dp; weighted_modified_duration_years 2 dp;
weighted_yield_to_maturity_pct 2 dp.
correlation: 3 decimals.
signal_score: 3 decimals.
hy_reduction_pct_points: 2 dp; post_trade_watchlist_exposure_usd_m: 1 dp.
Ordering rules (verified):
- Energy BUY-only
trade_package: sort ascending by instrument_id.
- Rotation
.trades: SELL before BUY, then instrument_id ascending within
each action.
index_set, diversification_candidates, pair ids within a pair:
ascending alphabetical.
allocation_views / committee lists: follow the request payload's
focus/opportunity_sets order (NOT alphabetical) unless the template says
otherwise — read the template's ordering field.
rationale_codes (overlay): business priority order, highest first.
watchlist_sell_ids: ascending instrument_id.
4. Post-trade metric recipe (all credit workflows)
Given current holdings (env quantity_usd_m) + trades (BUY adds, SELL
subtracts), build the post-trade holdings map, then:
total_market_value_usd_m = Σ post-trade quantities (round 2 dp). For a
funded rotation (Σ sells = Σ buys) this equals the current portfolio MV.
hy_allocation_pct = (Σ q where bond rating_bucket=='HY') / total × 100.
weighted_modified_duration_years = Σ(q × modified_duration_years) / total.
weighted_yield_to_maturity_pct = Σ(q × yield_to_maturity_pct) / total.
hy_reduction_pct_points = pre_trade_hy_pct − post_trade_hy_pct, where
pre_trade_hy_pct = (current HY MV) / (current total MV) × 100.
post_trade_watchlist_exposure_usd_m = Σ post-trade q where the bond's
issuer (via issuer_id → /api/issuers) has watchlist == true.
Get rating_bucket, modified_duration_years, yield_to_maturity_pct from
/api/instruments/bonds; get the watchlist flag from /api/issuers keyed by
issuer_id. Round each metric per §3. This recipe reproduces the env-verified
metric set exactly (market-value-weighted).
Constraint booleans
hy_cap_pass: post_trade_hy_allocation_pct <= max_hy_allocation_pct (20.0).
duration_band_pass: post_trade weighted_modified_duration_years within
[band[0], band[1]] inclusive (default [3.0, 5.0]).
target_hy_reduction_met: hy_reduction_pct_points >= target_hy_reduction_pct
(0 for credit_default, 4.0 for credit_risk_reduction).
watchlist_exposure_cleared: post_trade_watchlist_exposure_usd_m == 0
(and buys_avoid_watchlist == true).
selected_issuer_diversification_pass: selected buys come from distinct
issuers (and no issuer exceeds issuer_concentration_limit_pct).
selected_subsector_diversification_pass: selected buys span ≥
subsector_min_count_for_diversified (2) distinct subsectors.
watchlist_avoidance_pass: no bought bond's issuer is on the watchlist.
5. Workflow A — Energy / fixed-income trade strategy (BUY tickets)
Inputs: portfolio_id, ticket count, total notional (+ split rule, e.g.
evenly), allowed actions (usually BUY), income period, preferred exposures
(e.g. "LNG exporters", "gas demand", "non-watchlist carry"), client segment,
watchlist-yield sensitivity.
SOP:
GET /api/portfolios/<id> → as_of_date, market_value_usd_m,
holdings, constraints (policy_id, duration band, HY cap).
GET /api/instruments/bonds?candidate=true → candidate universe. Filter to
energy_linked==true (or per preferred exposures). Build a lookup of bond
→ {rating_bucket, modified_duration_years, yield_to_maturity_pct, issuer_id, subsector}.
GET /api/issuers → mark watchlist issuers. Exclude every candidate whose
issuer watchlist==true (watchlist_avoidance).
- Select exactly the requested ticket count, summing to the total notional
with the required split. Prefer bonds that:
(a) improve carry (higher YTM) while keeping post-trade HY % ≤ cap and
post-trade duration within the band;
(b) diversify — ≥ 2 distinct subsectors among selected buys and distinct
issuers;
(c) match the desk's preferred exposures/themes /
recommended_theme_tags.
- Compute post-trade metrics (§4) on env holdings + selected buys.
- Derive constraint booleans (§4).
sales_positioning.target_segment + theme: pick from the template's
enums driven by desk context (e.g. multi_asset_income +
lng_export_tailwind when LNG exporters are selected; use
avoid_watchlist_yield_trap when a watchlisted high-carry bond was the
near-miss that you correctly excluded).
data_precedence = current_environment_over_stale_payload whenever the
payload snapshot differs from the env (the usual case). Only use
no_conflict_found if values truly match.
- Sort
trade_package ascending by instrument_id; round notional_usd_m
to 1 dp.
Rotation variant (sell existing pressure, fund IG buys):
- SELL in full every watchlisted holding (clear avoidable watchlist risk).
- SELL additional HY holdings as needed to (i) fund the IG buys and (ii)
meet/exceed
target_hy_reduction_pct. Size so Σ sells ≈ Σ buys (funded
rotation → total MV preserved). Use env quantities, NOT the
stale_exception_board quantities (they differ).
- BUY only IG, non-watchlist candidates whose durations, blended in, keep
portfolio duration inside the band. Avoid any desk-shortlisted candidate
flagged for issuer-status concern.
watchlist_sell_ids = watchlisted holdings sold (ascending instrument_id);
buys_avoid_watchlist = true.
risk_note_code enum: watchlist_concentration when watchlist clearing
dominated; hy_cap_pressure when capping HY was the lever;
duration_preservation when duration ballast drove buys; carry_tradeoff;
no_action.
6. Workflow B — International correlation review
Inputs: portfolio_id, as_of, review_window{level_start_date, level_end_date}, index_universe[], CIO concern codes.
SOP:
GET /api/index-levels. For each index in index_universe, filter
observations to [level_start_date, level_end_date] inclusive and sort
ascending by date.
- Compute monthly simple returns per index:
r[i] = level[i+1] / level[i] − 1. return_observations = #levels − 1
(e.g. 12 monthly levels → 11 returns).
- For every unordered pair, compute Pearson correlation of the two
return series (equal length, aligned by date). Round to 3 decimals.
extreme_pairs.highest_positive = max-corr pair; lowest = min-corr pair.
Each pair_id sorted alphabetical; correlation to 3 dp.
index_set = index_universe sorted ascending alphabetical.
concentration:
high_threshold_breached = any pair corr ≥ correlation_high_threshold
(0.8).
china_asia_dependence_flag = true if any pair ≥ 0.8 involves a
China/Asia-related index (IDX_CHINA, IDX_AC_ASIA_PAC_EX_JP, IDX_EM,
IDX_EM_EX_CHINA, IDX_INDIA).
primary_code = CHINA_ASIA_DEPENDENCE if that flag is true; else
GLOBAL_DEVELOPED_OVERLAP if the high cluster is only among
developed/global indices (IDX_WORLD, IDX_EAFE, IDX_ACWI_IMI); else
NO_MATERIAL_CONCENTRATION. The memo's concern codes
(ASIA_BETA_OVERLAP, CHINA_DEDICATED_SLEEVE) hint toward
CHINA_ASIA_DEPENDENCE.
diversification_candidates: indices with the lowest (most negative /
lowest-abs) correlation to the concentration cluster — typically
IDX_EM_EX_CHINA and IDX_LATAM. Sort ascending alphabetical; restrict to
the template's allowed_values.
sleeve_actions: trim the concentration-driving sleeve (e.g. China →
trim, IDX_CHINA) and add a diversifier (e.g. Latin America → add,
IDX_LATAM). Respect the template's length/ordering and
target_index_id allowed values; action ∈ {trim, add, hold, hedge,
monitor, rotate}.
7. Workflow C — Cross-asset active allocation view updates
For each requested opportunity_set (in the request's focus order):
asset_class = opportunity-sets[os]['asset_class']
(Equities / Duration / Credit / Currency). Note mappings: U.S. Treasuries
→ Duration; Corporate High Yield → Credit; EUR/USD/currency names →
Currency.
prior_view = prior-views filtered to quarter == target_quarter AND
opportunity_set == os → view. (Filter by quarter — the endpoint holds
multiple quarters; the wrong record gives the next quarter's view.)
macro = macro-signals filtered to quarter == target_quarter AND
opportunity_set == os → score, rationale_code.
signal_score = round(score, 3).
view: OW if score ≥ 0.35; UW if score ≤ −0.35; else N.
change: compare view_rank[new] vs view_rank[prior_view]:
UP if new>prior, DOWN if <, UNCHANGED if ==. Rank {UW:−1, N:0, OW:1}. (A score moving 0.6→0.4 is still OW→OW = UNCHANGED.)
conviction: HIGH if |score| ≥ 0.7; MEDIUM if 0.35 ≤ |score| < 0.7;
LOW if |score| < 0.35.
rationale_code = macro['rationale_code'] (the enum from the signal
record — GROWTH_IMPROVES, RATE_CUT_SUPPORT, CREDIT_SPREAD_RISK,
DOLLAR_DEFENSIVE, CHINA_DEPENDENCE, LATAM_DIVERSIFIER, INDIA_OFFSET,
DURATION_SUPPORT, HY_VALUATION_RISK, EUROPE_RECOVERY, JAPAN_POLICY_RISK,
NEUTRAL_BALANCE).
- Order rows per the template (usually the request payload's focus order).
policy_id (top-level, when required) = POLICY_SET_2026_05.
as_of_date = env as_of (2026-05-29); target_quarter/prior_quarter
from the request (e.g. Q2_2026 / Q1_2026).
Risk overlay (standalone allocation memo)
overlay_code + primary_action: pick the dominant theme from the derived
views:
- Duration OW + HY UW →
DURATION_QUALITY_TILT / tilt_to_duration_quality.
- Credit/HY stress dominant →
CREDIT_RISK_REDUCTION / trim_credit_beta.
- Equity growth tilt →
EQUITY_BETA_EXTENSION / add_cyclical_equity_beta.
- Currency defensive →
CURRENCY_DEFENSIVE_HEDGE / add_currency_hedge.
- No strong tilt →
NO_OVERLAY / hold_policy_weights.
rationale_codes: the rationale codes of the most consequential views, in
business priority order (highest priority first).
Combined committee JSON (correlation + allocation, e.g. multi-asset sleeve)
correlation_summary: 2 items — highest_concentration (max-corr pair) and
best_diversifier (min-corr / most-negative pair). Each pair sorted
alphabetical; correlation to 3 dp (Pearson of monthly simple returns from
/api/index-levels over the window).
target_sleeve_actions: per sleeve, action ∈ {trim, add, hold, hedge,
monitor, rotate} (e.g. trim the concentration sleeve, add the diversifier,
hedge a defensive currency).
allocation_views: per opportunity_set include prior_view,
signal_score, view, change, conviction, rationale_code (all
derived per §7 steps 2–8). Use the prior from /api/allocation/prior-views
(filtered to target quarter), not the stale_local_note.
rebalance_trigger enum: correlation_cap_breach if any relevant pair ≥
0.8; else hy_cap_pressure / duration_drift / watchlist_concentration /
committee_review per the dominant risk.
portfolio_risk_concentration_flag (bool): true when the correlation
high-threshold (0.8) is breached.
next_step enum: approve_rotation / defer_pending_risk_review /
approve_with_monitoring (typical when concentration flagged but overlay
addresses it) / reject_constraint_breach.
8. Common misjudgments & exclusion rules
- Stale-payload values: don't use
stale_holding_snapshot MV/HY%/
quantity or stale_exception_board quantities — they differ from the env
(e.g. payload 58.5 MV vs env 60.0; payload 10.0 held vs env 12.0). Always
recompute from /api/portfolios/<id>.
- Watchlist yield trap: before selecting any candidate, check its
issuer_id in /api/issuers and exclude if watchlist==true. A high-carry
watchlisted bond is the classic distractor.
- Duration-ineligible distractor: a long-dated or HY bond whose inclusion
would push post-trade duration outside [3.0, 5.0] or breach the HY cap.
Always compute post-trade metrics before finalizing.
- Wrong prior view:
/api/allocation/prior-views holds multiple quarters.
Filter quarter == target_quarter. Taking the first/last record without
filtering yields the next quarter's view and corrupts change.
- Mis-deriving
change: it is new view vs prior view by view_rank,
NOT score direction or conviction change. Equal views ⇒ UNCHANGED.
- Correlation on levels: must be Pearson of monthly simple returns
(
level[i+1]/level[i] − 1), not of price levels.
- Window/observation count: filter levels to
[start, end] inclusive;
return_observations = #levels − 1 (12 levels → 11 returns).
- Trade ordering: energy BUY-only ⇒ ascending
instrument_id; rotation ⇒
SELL-before-BUY then instrument_id ascending. Swapping these fails the
sort check.
- Funded-rotation base: Σ sells = Σ buys so total MV is preserved; compute
HY reduction against the env pre-trade HY MV / env total MV, not the stale
board.
- Stale desk preference override: ignore old "USD overweight as defensive
offset" notes when refreshing — re-derive
view/change from current
macro-signals + April index levels.
- Missing
data_precedence: when a payload snapshot conflicts with env,
set current_environment_over_stale_payload. It is almost always set (the
payloads are deliberately stale).
- Ignoring the answer template's exact field set / enums: re-read
answer_template.json each task — required keys, allowed enum values,
ordering instructions, and precision per field vary slightly across tasks.
Return only the JSON object, no narrative.
9. End-to-end checklist (every task)
- Read
prompt.txt + every input/payloads/*.json (request + answer
template). Identify the workflow(s) and the required top-level keys.
- Confirm IDs via
/api/catalog if unsure; then pull the per-task endpoints.
- Use env
as_of_date as the answer's as_of_date.
- Compute from the environment (holdings/bonds/issuers/index-levels/prior-
views/macro-signals/policies) — never trust stale payload snapshots.
- Round per the template; sort per the template; use only allowed enum values.
- Verify pass/fail booleans against §4 thresholds.
- Return only the JSON object.
1---2name: fewshot-attempt-02-103description: Asteria Investment Office — Institutional Portfolio-Risk Solver Skill4---5# Asteria Investment Office — Institutional Portfolio-Risk Solver Skill67Reusable execution knowledge for the Asteria Investment Office remote8environment. Test tasks draw from three workflows: (A) energy / fixed-income9trade strategy, (B) international correlation review, (C) cross-asset active10allocation view updates (sometimes combined in one committee JSON). Re-derive11every value from the live environment per task — do NOT memorize specific12answers.1314---1516## 0. Data-precedence rule (applies to every task)1718The remote environment at `<remote-env-url>` is the **current book of19record**. Files in `input/payloads/` are intake context and MAY BE STALE:20they often carry an earlier snapshot date, stale holding quantities/exception21boards, or stale prior preferences. When a local payload conflicts with the22environment, **prefer the environment** unless the task prompt explicitly23instructs otherwise.2425Staleness signals to ignore in favor of the environment:26- `stale_holding_snapshot`, `stale_exception_board`, `stale_local_note`,27 `candidate_shortlist_from_prior_week`, `snapshot_date` keys.28- Any payload date earlier than the environment `as_of_date` (currently29 `2026-05-29`).30- A stale note saying "USD overweight as a defensive offset" or "old worksheet31 highlighted…" — these are prior preferences, not current views; refresh from32 macro signals / index levels.3334The `as_of_date` you put in every answer = the environment's `as_of_date`35(`2026-05-29`), NOT the payload's request/memo date.3637---3839## 1. Environment endpoints & field shapes4041Base URL: `<remote-env-url>`. Call with `curl` or `python urllib`.4243- `GET /api/catalog` — available portfolio ids, policy ids, index ids, issuer44 ids, bond ids, opportunity sets. Good first call to confirm IDs.45- `GET /api/policies` — all policy thresholds (see §2).46- `GET /api/portfolios` — portfolio summaries.47- `GET /api/portfolios/<id>` — `{portfolio_id, name, objective, strategy,48 base_currency, as_of_date, market_value_usd_m, constraints{policy_id,49 duration_band_years, max_hy_allocation_pct, ...}, holdings[{instrument_id,50 quantity_usd_m, asset_class, sleeve, notes}]}`. The `constraints.policy_id`51 tells you which credit policy applies (and thus the target HY reduction).52- `GET /api/instruments/bonds` — supports filters `?candidate=true`,53 `?rating_bucket=HY`. Each bond: `{instrument_id, issuer_id, issuer_name,54 candidate, energy_linked, coupon_pct, maturity, modified_duration_years,55 rating, rating_bucket (IG|HY), sector, subsector, spread_bps,56 yield_to_maturity_pct, recommended_theme_tags}`.57- `GET /api/issuers` — `{issuer_id, issuer_name, sector, subsector,58 rating_bucket, watchlist (bool), credit_outlook, research_tags}`. Key the59 watchlist check by `issuer_id`.60- `GET /api/market/energy` — oil/gas/LNG/refining/renewables signals (theme61 context for §A; not directly required for the arithmetic).62- `GET /api/indices` — index metadata.63- `GET /api/index-levels` — a **dict** mapping each `index_id` to a list of64 `{date, level}` monthly observations for ALL indices.65 `GET /api/index-levels/<id>` returns one index. No correlations are66 precomputed — compute them yourself (§5).67- `GET /api/allocation/opportunity-sets` — 25 sets, each68 `{opportunity_set, asset_class (Equities|Duration|Credit|Currency),69 sub_asset_class, display_order}`. Use this to map an opportunity-set name to70 its `asset_class`.71- `GET /api/allocation/prior-views` — list of records for **multiple quarters**;72 each `{opportunity_set, quarter, previous_quarter, view (UW|N|OW),73 conviction}`. **You must filter `quarter == target_quarter`**; the `view` on74 that record is the prior view held entering the target quarter.75- `GET /api/macro-signals` — list of `{opportunity_set, quarter, score,76 rationale_code, drivers}`. Filter `quarter == target_quarter`.7778Roster of list endpoints also accept simple equality filters matching field79names, e.g. `?rating_bucket=HY`, `?candidate=true`, `?quarter=Q2_2026`.8081---8283## 2. Policy conventions (`GET /api/policies`)8485Top-level: `policy_id: POLICY_SET_2026_05`, `as_of_date: 2026-05-29`.8687- `credit_default` (policy_id `POL_CREDIT_DEFAULT`): `duration_band_years`88 `[3.0, 5.0]`, `max_hy_allocation_pct` 20.0, `issuer_concentration_limit_pct`89 12.0, `subsector_min_count_for_diversified` 2, `target_hy_reduction_pct` 0.0.90- `credit_risk_reduction` (`POL_CREDIT_RISK_REDUCTION`): same as above but91 `target_hy_reduction_pct` **4.0**. Used by risk-rebalance portfolios92 (rotation tasks). The per-portfolio `constraints` names which one applies.93- `correlation` (`POL_CORRELATION_DEFAULT`): `correlation_high_threshold` 0.8,94 `correlation_low_threshold` 0.2, `review_window_start` 2025-05-30,95 `review_window_end` 2026-04-30.96- `allocation_mapping` (`POL_ALLOCATION_MAPPING`):97 - `view_score_thresholds`: `OW_min` 0.35, `UW_max` −0.35, `neutral`98 `[−0.35, 0.35]`.99 - `conviction_thresholds`: `HIGH_abs_min` 0.7, `MEDIUM_abs_min` 0.35,100 `LOW_abs_below` 0.35.101 - `view_rank`: `{UW: −1, N: 0, OW: 1}`.102- `multi_asset` uses allocation_mapping + correlation_default + credit_default.103- `multi_asset_risk` uses credit_risk_reduction + correlation_default;104 `committee_escalation_threshold`: "two_or_more_material_exceptions".105106For credit tasks, read the portfolio's `constraints` to pick107`target_hy_reduction_pct` (0 vs 4) and the duration band.108109---110111## 3. Precision & ordering conventions (always honor the template)112113Always follow the per-field precision declared in114`input/payloads/answer_template.json`. Verified defaults:115116- `trade_package`/rotation `notional_usd_m` and `quantity_usd_m`: **1 decimal**.117- `post_trade_metrics`: `total_market_value_usd_m` 2 dp;118 `hy_allocation_pct` 2 dp; `weighted_modified_duration_years` 2 dp;119 `weighted_yield_to_maturity_pct` 2 dp.120- `correlation`: **3 decimals**.121- `signal_score`: **3 decimals**.122- `hy_reduction_pct_points`: 2 dp; `post_trade_watchlist_exposure_usd_m`: 1 dp.123124Ordering rules (verified):125- Energy BUY-only `trade_package`: **sort ascending by `instrument_id`**.126- Rotation `.trades`: **SELL before BUY, then `instrument_id` ascending within127 each action**.128- `index_set`, `diversification_candidates`, `pair` ids within a pair:129 **ascending alphabetical**.130- `allocation_views` / committee lists: follow the **request payload's131 focus/opportunity_sets order** (NOT alphabetical) unless the template says132 otherwise — read the template's `ordering` field.133- `rationale_codes` (overlay): **business priority order, highest first**.134- `watchlist_sell_ids`: ascending `instrument_id`.135136---137138## 4. Post-trade metric recipe (all credit workflows)139140Given current holdings (env `quantity_usd_m`) + trades (BUY adds, SELL141subtracts), build the post-trade holdings map, then:1421431. `total_market_value_usd_m` = Σ post-trade quantities (round 2 dp). For a144 funded rotation (Σ sells = Σ buys) this equals the current portfolio MV.1452. `hy_allocation_pct` = (Σ q where bond `rating_bucket`=='HY') / total × 100.1463. `weighted_modified_duration_years` = Σ(q × `modified_duration_years`) / total.1474. `weighted_yield_to_maturity_pct` = Σ(q × `yield_to_maturity_pct`) / total.1485. `hy_reduction_pct_points` = `pre_trade_hy_pct` − `post_trade_hy_pct`, where149 `pre_trade_hy_pct` = (current HY MV) / (current total MV) × 100.1506. `post_trade_watchlist_exposure_usd_m` = Σ post-trade q where the bond's151 issuer (via `issuer_id` → `/api/issuers`) has `watchlist == true`.152153Get `rating_bucket`, `modified_duration_years`, `yield_to_maturity_pct` from154`/api/instruments/bonds`; get the watchlist flag from `/api/issuers` keyed by155`issuer_id`. Round each metric per §3. This recipe reproduces the env-verified156metric set exactly (market-value-weighted).157158### Constraint booleans159160- `hy_cap_pass`: `post_trade_hy_allocation_pct <= max_hy_allocation_pct` (20.0).161- `duration_band_pass`: `post_trade weighted_modified_duration_years` within162 `[band[0], band[1]]` inclusive (default [3.0, 5.0]).163- `target_hy_reduction_met`: `hy_reduction_pct_points >= target_hy_reduction_pct`164 (0 for `credit_default`, 4.0 for `credit_risk_reduction`).165- `watchlist_exposure_cleared`: `post_trade_watchlist_exposure_usd_m == 0`166 (and `buys_avoid_watchlist == true`).167- `selected_issuer_diversification_pass`: selected buys come from distinct168 issuers (and no issuer exceeds `issuer_concentration_limit_pct`).169- `selected_subsector_diversification_pass`: selected buys span ≥170 `subsector_min_count_for_diversified` (2) distinct subsectors.171- `watchlist_avoidance_pass`: no bought bond's issuer is on the watchlist.172173---174175## 5. Workflow A — Energy / fixed-income trade strategy (BUY tickets)176177**Inputs**: portfolio_id, ticket count, total notional (+ split rule, e.g.178evenly), allowed actions (usually BUY), income period, preferred exposures179(e.g. "LNG exporters", "gas demand", "non-watchlist carry"), client segment,180watchlist-yield sensitivity.181182**SOP**:1831. `GET /api/portfolios/<id>` → `as_of_date`, `market_value_usd_m`,184 `holdings`, `constraints` (policy_id, duration band, HY cap).1852. `GET /api/instruments/bonds?candidate=true` → candidate universe. Filter to186 `energy_linked==true` (or per preferred exposures). Build a lookup of bond187 → `{rating_bucket, modified_duration_years, yield_to_maturity_pct,188 issuer_id, subsector}`.1893. `GET /api/issuers` → mark watchlist issuers. **Exclude every candidate whose190 issuer `watchlist==true`** (watchlist_avoidance).1914. Select exactly the requested ticket count, summing to the total notional192 with the required split. Prefer bonds that:193 (a) improve carry (higher YTM) while keeping post-trade HY % ≤ cap and194 post-trade duration within the band;195 (b) diversify — ≥ 2 distinct subsectors among selected buys and distinct196 issuers;197 (c) match the desk's preferred exposures/themes / `recommended_theme_tags`.1985. Compute post-trade metrics (§4) on env holdings + selected buys.1996. Derive constraint booleans (§4).2007. `sales_positioning.target_segment` + `theme`: pick from the template's201 enums driven by desk context (e.g. `multi_asset_income` +202 `lng_export_tailwind` when LNG exporters are selected; use203 `avoid_watchlist_yield_trap` when a watchlisted high-carry bond was the204 near-miss that you correctly excluded).2058. `data_precedence = current_environment_over_stale_payload` whenever the206 payload snapshot differs from the env (the usual case). Only use207 `no_conflict_found` if values truly match.2089. Sort `trade_package` ascending by `instrument_id`; round `notional_usd_m`209 to 1 dp.210211**Rotation variant (sell existing pressure, fund IG buys)**:212- SELL in full every **watchlisted** holding (clear avoidable watchlist risk).213- SELL additional **HY** holdings as needed to (i) fund the IG buys and (ii)214 meet/exceed `target_hy_reduction_pct`. Size so Σ sells ≈ Σ buys (funded215 rotation → total MV preserved). Use env quantities, NOT the216 `stale_exception_board` quantities (they differ).217- BUY only **IG, non-watchlist** candidates whose durations, blended in, keep218 portfolio duration inside the band. Avoid any desk-shortlisted candidate219 flagged for issuer-status concern.220- `watchlist_sell_ids` = watchlisted holdings sold (ascending instrument_id);221 `buys_avoid_watchlist = true`.222- `risk_note_code` enum: `watchlist_concentration` when watchlist clearing223 dominated; `hy_cap_pressure` when capping HY was the lever;224 `duration_preservation` when duration ballast drove buys; `carry_tradeoff`;225 `no_action`.226227---228229## 6. Workflow B — International correlation review230231**Inputs**: portfolio_id, as_of, `review_window{level_start_date,232level_end_date}`, `index_universe[]`, CIO concern codes.233234**SOP**:2351. `GET /api/index-levels`. For each index in `index_universe`, filter236 observations to `[level_start_date, level_end_date]` **inclusive** and sort237 ascending by date.2382. Compute monthly simple returns per index:239 `r[i] = level[i+1] / level[i] − 1`. `return_observations = #levels − 1`240 (e.g. 12 monthly levels → 11 returns).2413. For every unordered pair, compute **Pearson correlation** of the two242 return series (equal length, aligned by date). Round to **3 decimals**.2434. `extreme_pairs.highest_positive` = max-corr pair; `lowest` = min-corr pair.244 Each `pair_id` sorted **alphabetical**; `correlation` to 3 dp.2455. `index_set` = `index_universe` sorted **ascending alphabetical**.2466. `concentration`:247 - `high_threshold_breached` = any pair corr ≥ `correlation_high_threshold`248 (0.8).249 - `china_asia_dependence_flag` = true if any pair ≥ 0.8 involves a250 China/Asia-related index (`IDX_CHINA`, `IDX_AC_ASIA_PAC_EX_JP`, `IDX_EM`,251 `IDX_EM_EX_CHINA`, `IDX_INDIA`).252 - `primary_code` = `CHINA_ASIA_DEPENDENCE` if that flag is true; else253 `GLOBAL_DEVELOPED_OVERLAP` if the high cluster is only among254 developed/global indices (`IDX_WORLD`, `IDX_EAFE`, `IDX_ACWI_IMI`); else255 `NO_MATERIAL_CONCENTRATION`. The memo's concern codes256 (ASIA_BETA_OVERLAP, CHINA_DEDICATED_SLEEVE) hint toward257 `CHINA_ASIA_DEPENDENCE`.2587. `diversification_candidates`: indices with the lowest (most negative /259 lowest-abs) correlation to the concentration cluster — typically260 `IDX_EM_EX_CHINA` and `IDX_LATAM`. Sort ascending alphabetical; restrict to261 the template's `allowed_values`.2628. `sleeve_actions`: trim the concentration-driving sleeve (e.g. China →263 `trim`, `IDX_CHINA`) and add a diversifier (e.g. Latin America → `add`,264 `IDX_LATAM`). Respect the template's length/ordering and265 `target_index_id` allowed values; `action` ∈ {trim, add, hold, hedge,266 monitor, rotate}.267268---269270## 7. Workflow C — Cross-asset active allocation view updates271272For **each requested opportunity_set** (in the request's focus order):2732741. `asset_class` = `opportunity-sets[os]['asset_class']`275 (Equities / Duration / Credit / Currency). Note mappings: `U.S. Treasuries`276 → Duration; `Corporate High Yield` → Credit; `EUR`/`USD`/currency names →277 Currency.2782. `prior_view` = `prior-views` filtered to `quarter == target_quarter` AND279 `opportunity_set == os` → `view`. (**Filter by quarter — the endpoint holds280 multiple quarters; the wrong record gives the next quarter's view.**)2813. `macro` = `macro-signals` filtered to `quarter == target_quarter` AND282 `opportunity_set == os` → `score`, `rationale_code`.2834. `signal_score` = round(`score`, 3).2845. `view`: `OW` if score ≥ 0.35; `UW` if score ≤ −0.35; else `N`.2856. `change`: compare `view_rank[new]` vs `view_rank[prior_view]`:286 `UP` if new>prior, `DOWN` if <, `UNCHANGED` if ==. Rank `{UW:−1, N:0,287 OW:1}`. (A score moving 0.6→0.4 is still OW→OW = UNCHANGED.)2887. `conviction`: `HIGH` if |score| ≥ 0.7; `MEDIUM` if 0.35 ≤ |score| < 0.7;289 `LOW` if |score| < 0.35.2908. `rationale_code` = `macro['rationale_code']` (the enum from the signal291 record — GROWTH_IMPROVES, RATE_CUT_SUPPORT, CREDIT_SPREAD_RISK,292 DOLLAR_DEFENSIVE, CHINA_DEPENDENCE, LATAM_DIVERSIFIER, INDIA_OFFSET,293 DURATION_SUPPORT, HY_VALUATION_RISK, EUROPE_RECOVERY, JAPAN_POLICY_RISK,294 NEUTRAL_BALANCE).2959. Order rows per the template (usually the request payload's focus order).29610. `policy_id` (top-level, when required) = `POLICY_SET_2026_05`.297 `as_of_date` = env as_of (`2026-05-29`); `target_quarter`/`prior_quarter`298 from the request (e.g. `Q2_2026` / `Q1_2026`).299300### Risk overlay (standalone allocation memo)301- `overlay_code` + `primary_action`: pick the dominant theme from the derived302 views:303 - Duration OW + HY UW → `DURATION_QUALITY_TILT` / `tilt_to_duration_quality`.304 - Credit/HY stress dominant → `CREDIT_RISK_REDUCTION` / `trim_credit_beta`.305 - Equity growth tilt → `EQUITY_BETA_EXTENSION` / `add_cyclical_equity_beta`.306 - Currency defensive → `CURRENCY_DEFENSIVE_HEDGE` / `add_currency_hedge`.307 - No strong tilt → `NO_OVERLAY` / `hold_policy_weights`.308- `rationale_codes`: the rationale codes of the most consequential views, in309 business priority order (highest priority first).310311### Combined committee JSON (correlation + allocation, e.g. multi-asset sleeve)312- `correlation_summary`: 2 items — `highest_concentration` (max-corr pair) and313 `best_diversifier` (min-corr / most-negative pair). Each `pair` sorted314 alphabetical; `correlation` to 3 dp (Pearson of monthly simple returns from315 `/api/index-levels` over the window).316- `target_sleeve_actions`: per sleeve, action ∈ {trim, add, hold, hedge,317 monitor, rotate} (e.g. trim the concentration sleeve, add the diversifier,318 hedge a defensive currency).319- `allocation_views`: per opportunity_set include `prior_view`,320 `signal_score`, `view`, `change`, `conviction`, `rationale_code` (all321 derived per §7 steps 2–8). Use the prior from `/api/allocation/prior-views`322 (filtered to target quarter), **not** the `stale_local_note`.323- `rebalance_trigger` enum: `correlation_cap_breach` if any relevant pair ≥324 0.8; else `hy_cap_pressure` / `duration_drift` / `watchlist_concentration` /325 `committee_review` per the dominant risk.326- `portfolio_risk_concentration_flag` (bool): true when the correlation327 high-threshold (0.8) is breached.328- `next_step` enum: `approve_rotation` / `defer_pending_risk_review` /329 `approve_with_monitoring` (typical when concentration flagged but overlay330 addresses it) / `reject_constraint_breach`.331332---333334## 8. Common misjudgments & exclusion rules3353361. **Stale-payload values**: don't use `stale_holding_snapshot` MV/HY%/337 quantity or `stale_exception_board` quantities — they differ from the env338 (e.g. payload 58.5 MV vs env 60.0; payload 10.0 held vs env 12.0). Always339 recompute from `/api/portfolios/<id>`.3402. **Watchlist yield trap**: before selecting any candidate, check its341 `issuer_id` in `/api/issuers` and exclude if `watchlist==true`. A high-carry342 watchlisted bond is the classic distractor.3433. **Duration-ineligible distractor**: a long-dated or HY bond whose inclusion344 would push post-trade duration outside [3.0, 5.0] or breach the HY cap.345 Always compute post-trade metrics before finalizing.3464. **Wrong prior view**: `/api/allocation/prior-views` holds multiple quarters.347 Filter `quarter == target_quarter`. Taking the first/last record without348 filtering yields the next quarter's view and corrupts `change`.3495. **Mis-deriving `change`**: it is `new view` vs `prior view` by `view_rank`,350 NOT score direction or conviction change. Equal views ⇒ UNCHANGED.3516. **Correlation on levels**: must be Pearson of monthly **simple returns**352 (`level[i+1]/level[i] − 1`), not of price levels.3537. **Window/observation count**: filter levels to `[start, end]` inclusive;354 `return_observations = #levels − 1` (12 levels → 11 returns).3558. **Trade ordering**: energy BUY-only ⇒ ascending `instrument_id`; rotation ⇒356 SELL-before-BUY then `instrument_id` ascending. Swapping these fails the357 sort check.3589. **Funded-rotation base**: Σ sells = Σ buys so total MV is preserved; compute359 HY reduction against the env pre-trade HY MV / env total MV, not the stale360 board.36110. **Stale desk preference override**: ignore old "USD overweight as defensive362 offset" notes when refreshing — re-derive `view`/`change` from current363 `macro-signals` + April index levels.36411. **Missing `data_precedence`**: when a payload snapshot conflicts with env,365 set `current_environment_over_stale_payload`. It is almost always set (the366 payloads are deliberately stale).36712. **Ignoring the answer template's exact field set / enums**: re-read368 `answer_template.json` each task — required keys, allowed enum values,369 ordering instructions, and precision per field vary slightly across tasks.370 Return only the JSON object, no narrative.371372---373374## 9. End-to-end checklist (every task)3753761. Read `prompt.txt` + every `input/payloads/*.json` (request + answer377 template). Identify the workflow(s) and the required top-level keys.3782. Confirm IDs via `/api/catalog` if unsure; then pull the per-task endpoints.3793. Use env `as_of_date` as the answer's `as_of_date`.3804. Compute from the environment (holdings/bonds/issuers/index-levels/prior-381 views/macro-signals/policies) — never trust stale payload snapshots.3825. Round per the template; sort per the template; use only allowed enum values.3836. Verify pass/fail booleans against §4 thresholds.3847. Return **only** the JSON object.