Asteria Investment Office — committee JSON solver
0. Golden rules (apply to every archetype)
- The HTTP environment is the book of record. Local payloads
(desk_request, review_request, memo, committee_request, "stale" snapshots,
exception boards, prior-week shortlists) are intake context only. Whenever a
local number disagrees with the environment (holding quantities, marks, HY %,
duration, currency views, index levels), USE THE ENVIRONMENT VALUE. A common
trap: a memo lists a stale holding quantity (e.g. 10.0) while the live
portfolio shows 12.0 — compute with 12.0. When the schema has a
data_precedence field, the value is current_environment_over_stale_payload
in essentially every refresh/rebalance case.
as_of_date = the environment's current as-of date (the as_of_date
returned by /api/policies, /api/portfolios/<id>, /api/market/energy,
etc.), NOT any date written in the local payload.
- Follow the answer_template exactly: required keys, enum value sets
(verbatim casing), rounding precision, list lengths, and ordering rules.
Echo
required_value fields literally (portfolio_id, task_id, quarters).
- Rounding: round only at the end, to the precision the template declares
per field. Trailing-zero forms are fine (5.8 == 5.80). Compute with full
precision; never round intermediate weighted sums.
- Ordering: respect each field's stated order — "ascending by instrument_id",
"alphabetical by index id", "SELL before BUY then id ascending", or "in the
request payload's focus order / item_order". Within a correlation pair, sort
the two ids alphabetically.
- Use
python (not python3) and curl. Save fetched JSON locally and
compute deterministically; do not eyeball.
1. Endpoints
Base: GET on the Asteria HTTP service.
/api/catalog — id inventory (portfolios, policies, indices, issuers, bonds, opportunity sets).
/api/policies — all thresholds (see §2). Also gives the current as_of_date.
/api/portfolios/<id> — objective, constraints (incl. policy_id), holdings (instrument_id, quantity_usd_m, sleeve), market_value_usd_m.
/api/instruments/bonds — bond master: rating_bucket (IG/HY), modified_duration_years, yield_to_maturity_pct, coupon_pct, spread_bps, sector, subsector, energy_linked, candidate (eligible for purchase), issuer_id, recommended_theme_tags.
/api/issuers — watchlist (bool), credit_outlook, rating_bucket, sector, subsector, research_tags.
/api/market/energy — commodity signals with score/direction (oil, gas, LNG, refining, renewables) + pitch_themes.
/api/indices and /api/index-levels[/<id>] — monthly index level series.
/api/allocation/opportunity-sets — opportunity_set → asset_class (Equities/Duration/Credit/Currency) + display_order.
/api/allocation/prior-views — prior active views per opportunity_set & quarter (view, conviction, previous_quarter).
/api/macro-signals — per opportunity_set & quarter: score (signed) and rationale_code and drivers.
Most list endpoints accept equality filters (?rating_bucket=HY, ?candidate=true, ?quarter=Q2_2026).
2. Policy thresholds (from /api/policies — read them, don't hardcode blindly)
- Allocation mapping (
POL_ALLOCATION_MAPPING):
- View from signal score:
OW if score ≥ OW_min (0.35); UW if score ≤ UW_max (-0.35); else N. Boundaries are inclusive of OW/UW.
- Conviction from |score|:
HIGH if |score| ≥ 0.70; LOW if |score| < 0.35; else MEDIUM.
- view_rank: UW=-1, N=0, OW=+1 (used to derive
change).
- Correlation (
POL_CORRELATION_DEFAULT): correlation_high_threshold = 0.8, correlation_low_threshold = 0.2. Review window dates are given by the policy/request (e.g. 2025-05-30 → 2026-04-30).
- Credit default (
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 band/cap but target_hy_reduction_pct = 4.0.
- Multi-asset sets compose the above;
POL_MULTI_ASSET_RISK escalates to committee on "two_or_more_material_exceptions".
Always take the policy_id from the portfolio's own constraints.policy_id and use that policy's numbers.
3. Core computation recipes
3a. Pearson correlation (archetypes b, e)
- For each index pull its monthly level series for the window; sort by date.
- Compute simple returns between consecutive levels: r_t = level_t/level_{t-1} − 1.
A 12-month window of levels yields 11 returns →
return_observations = 11.
- Pearson r over the paired return vectors. Round to 3 decimals.
highest_positive / highest_concentration = pair with the max correlation.
lowest / best_diversifier = pair with the minimum (most negative) correlation.
- Each
pair_id/pair is the two index ids sorted alphabetically.
3b. Notional-weighted portfolio metrics (archetypes a, d)
Build the post-trade position list (current holdings ± trades), then weight by
quantity_usd_m:
total_market_value_usd_m = Σ quantity (BUY-only packages add to MV; sell-and-fund rebalances keep MV constant when sells == buys).
weighted_modified_duration_years = Σ(dur·qty)/Σqty.
weighted_yield_to_maturity_pct = Σ(ytm·qty)/Σqty.
hy_allocation_pct = (Σ qty where rating_bucket==HY) / total · 100, on post-trade MV.
hy_reduction_pct_points = pre-trade HY% − post-trade HY%.
- watchlist exposure = Σ qty of held instruments whose issuer.watchlist is true.
Note HY% and HY-reduction depend only on which HY notional is added/removed —
they are invariant to which IG bond you buy; only duration/YTM depend on buy identity.
3c. Signal → view mapping (archetypes c, e)
For each requested opportunity_set, take the current macro score for the
target quarter, map to view/conviction via §2, set rationale_code to the macro
signal's own rationale_code, set asset_class from the opportunity-set
taxonomy, and set change = sign of (new view_rank − prior view_rank) →
UP/DOWN/UNCHANGED. The prior view comes from /api/allocation/prior-views
(the row whose quarter == target quarter; its recorded view is the standing
prior). Always recompute the view from the live score — do not copy the
recorded prior view as the new view.
4. Archetype playbooks
(a) Energy-credit trade package (e.g. PF-EN-ALTA)
Goal: N BUY tickets of equal notional that raise carry while staying inside
credit constraints and suitable for an income pitch.
Eligibility filter for a candidate bond (ALL must hold):
candidate == true AND energy_linked == true
- issuer
watchlist == false
- not already a current holding (new sleeve money)
- single-bond
modified_duration_years within the duration band [3.0, 5.0]
— this per-bond duration gate is decisive. A long-dated bond (e.g. dur 5.9)
is ineligible even though the portfolio average might still land in band.
Selection among eligible bonds:
- Anchor on the strongest energy signal (typically LNG/gas — check
/api/market/energy; LNG export pull is usually the top score). Pick the
IG LNG/gas name (theme tags like LNG_EXPORTS / GAS_DEMAND).
- Pair it with the highest-YTM diversifier that has a different issuer AND
different subsector, to maximize carry while passing diversification.
- Verify post-trade: HY% ≤ cap, weighted duration in band, both
selected_issuer_diversification and selected_subsector_diversification
true, watchlist_avoidance true. Set those constraint booleans honestly.
Schema conventions: trade_package length 2, sorted ascending by
instrument_id, each {action:"BUY", instrument_id, notional_usd_m (1 dp)}.
post_trade_metrics (MV 2dp, hy% 2dp, dur 2dp, ytm 2dp). sales_positioning:
target_segment from the client context (e.g. multi_asset_income), theme from
the dominant signal (e.g. lng_export_tailwind). data_precedence =
current_environment_over_stale_payload.
(b) International equity correlation review (e.g. PF-INT-NEXVEN)
- Compute the full correlation matrix over the requested index universe (§3a).
extreme_pairs.highest_positive and .lowest per §3a; return_observations
from the window (11 for a 12-level year).
index_set = the universe sorted alphabetically.
concentration:
high_threshold_breached = any pair correlation ≥ 0.8 (true when the cluster
is tightly correlated).
china_asia_dependence_flag = true when China/Asia/EM sleeves are mutually
≥ 0.8 (a dedicated-China + Asia-beta overlap exists).
primary_code = the dominant concentration label. Choose
CHINA_ASIA_DEPENDENCE when the China/Asia block drives it, else
GLOBAL_DEVELOPED_OVERLAP when the top pair is a broad developed/global
overlap (e.g. EM↔World), else NO_MATERIAL_CONCENTRATION. (Keep
china_asia_dependence_flag and primary_code logically consistent with which
pair is actually the maximum.)
diversification_candidates = the low-correlation diversifier sleeve(s) from
the allowed set (the index whose correlations are negative / well below the
0.2 low threshold is the clearest — a LatAm-type sleeve; include an India-type
only if it is a genuine relative diversifier, exclude broad EM-ex-China which
stays ≥0.8).
sleeve_actions (ordered ascending by sleeve): trim the concentrated
sleeve (the dedicated China/over-weighted one) and add the low-correlation
diversifier. Prefer decisive trim/add over monitor/hold for a flagged
concentration.
(c) Active allocation refresh (balanced multi-asset, e.g. Q2 focus list)
For each focus opportunity_set, emit a row {opportunity_set, asset_class, view,
change, conviction, rationale_code} using §3c. Order rows by the request payload's
focus order. Lineage: policy_id = POL_ALLOCATION_MAPPING; quarters echoed
from the request; as_of_date = env as-of.
risk_overlay:
- Pick
overlay_code/primary_action from the dominant cross-asset tilt implied
by the views: duration support + IG quality with HY underweight →
DURATION_QUALITY_TILT / tilt_to_duration_quality; a credit-led de-risk →
CREDIT_RISK_REDUCTION / trim_credit_beta; strong cyclical equity →
EQUITY_BETA_EXTENSION; defensive FX → CURRENCY_DEFENSIVE_HEDGE; else
NO_OVERLAY.
rationale_codes = the supporting macro rationale codes in business-priority
order (duration/quality support first, then the credit-risk code), highest
priority first.
(The allocation rows are the heavily weighted, deterministic part — get the
score-mapping, conviction boundaries, and change exactly right.)
(d) Fixed-income risk rebalance (sell-and-fund, e.g. PF-FI-LUMEN)
Use POL_CREDIT_RISK_REDUCTION (target HY reduction 4.0pp, cap 20%, band [3,5]).
Recipe:
- Identify held HY (rating_bucket==HY) and held watchlist names (issuer.watchlist).
- Sells: always sell the held watchlist name(s) to clear watchlist
exposure to 0. Add the minimum additional HY needed to bring post-trade
HY% under the 20% cap (and to meet the ≥4pp reduction). When choosing which
extra HY to sell, prefer the lowest-carry (lowest YTM) HY name so carry is
preserved — do not dump the highest-yielding HY.
- Buys: fund with non-watchlist IG
candidate bonds named in the desk
shortlist, keeping total buys == total sells so MV/duration are preserved
and no duration shortfall is created. Exclude any shortlist name whose
issuer is on the watchlist (a tempting high-carry watchlist candidate must be
dropped). Favor the cross-desk LNG IG name and a sector diversifier over a
pure long-duration ballast when both are offered.
risk_metrics per §3b. exception_flags: hy_cap_pass (≤20%),
duration_band_pass (in [3,5]), target_hy_reduction_met (≥4pp),
watchlist_exposure_cleared (==0) — all true for a clean rotation.
watchlist_handling.watchlist_sell_ids = held watchlist ids you sold
(ascending). buys_avoid_watchlist = true. risk_note_code = the dominant
pressure (hy_cap_pressure when starting HY% far exceeds the cap).
rotation.trades ordered SELL before BUY, then instrument_id ascending within
each action; quantities 1 dp.
(e) Multi-asset committee decision (e.g. PF-MA-HELIO)
Combines (b) + (c) on a small index/opportunity subset.
correlation_summary (length 2, item order [highest_concentration,
best_diversifier]): highest_concentration = max-correlation pair;
best_diversifier = min (most negative) pair; pairs alphabetical, corr 3dp.
allocation_views per §3c, including prior_view (from prior-views) and
signal_score (the macro score, 3dp). Refresh stale currency views: a stale
note keeping USD overweight is superseded — recompute from the live score
(often lands at N), with change DOWN from the prior OW.
target_sleeve_actions (item order matches the request): map view direction
to action — UW→trim, OW→add (high-conviction OW may hold), and for a
currency sleeve being de-risked use hedge rather than hold.
portfolio_risk_concentration_flag = true when a pair ≥ the 0.8 high
threshold (e.g. China↔EM concentration).
rebalance_trigger = committee_review for a committee decision file
(prefer this over the more specific correlation_cap_breach in committee
framing). next_step = approve_with_monitoring when a concentration flag is
true but the rotation is actionable (escalate/defer only on ≥2 material
exceptions).
5. Pre-submission checklist
1---2name: asteria-investment-office-json-43description: Produce committee-grade JSON answers for the Asteria Investment Office across five portfolio archetypes — (a) energy-credit trade packages, (b) international equity correlation reviews, (c) active allocation-view refreshes, (d) fixed-income risk rebalances, and (e) multi-asset committee decisions. Covers the exact endpoints to pull, the deterministic computation recipes (Pearson correlation on monthly simple returns, notional-weighted modified duration and YTM, HY allocation %, carry, concentration), the policy thresholds that drive views/conviction/constraint checks, and the output-schema conventions (field names, enum sets, rounding, id ordering) that the desk's answer templates require. Use whenever a task references an Asteria portfolio (PF-*), index (IDX_*), bond (BND_*), issuer (ISS_*), policy (POL_*), or an answer_template.json.4---56# Asteria Investment Office — committee JSON solver78## 0. Golden rules (apply to every archetype)9101. **The HTTP environment is the book of record.** Local payloads11 (desk_request, review_request, memo, committee_request, "stale" snapshots,12 exception boards, prior-week shortlists) are *intake context only*. Whenever a13 local number disagrees with the environment (holding quantities, marks, HY %,14 duration, currency views, index levels), USE THE ENVIRONMENT VALUE. A common15 trap: a memo lists a stale holding quantity (e.g. 10.0) while the live16 portfolio shows 12.0 — compute with 12.0. When the schema has a17 `data_precedence` field, the value is `current_environment_over_stale_payload`18 in essentially every refresh/rebalance case.192. **`as_of_date`** = the environment's current as-of date (the `as_of_date`20 returned by `/api/policies`, `/api/portfolios/<id>`, `/api/market/energy`,21 etc.), NOT any date written in the local payload.223. **Follow the answer_template exactly**: required keys, enum value sets23 (verbatim casing), rounding precision, list lengths, and ordering rules.24 Echo `required_value` fields literally (portfolio_id, task_id, quarters).254. **Rounding**: round only at the end, to the precision the template declares26 per field. Trailing-zero forms are fine (5.8 == 5.80). Compute with full27 precision; never round intermediate weighted sums.285. **Ordering**: respect each field's stated order — "ascending by instrument_id",29 "alphabetical by index id", "SELL before BUY then id ascending", or "in the30 request payload's focus order / item_order". Within a correlation pair, sort31 the two ids alphabetically.326. **Use `python` (not python3) and `curl`.** Save fetched JSON locally and33 compute deterministically; do not eyeball.3435## 1. Endpoints3637Base: `GET` on the Asteria HTTP service.3839- `/api/catalog` — id inventory (portfolios, policies, indices, issuers, bonds, opportunity sets).40- `/api/policies` — **all thresholds** (see §2). Also gives the current `as_of_date`.41- `/api/portfolios/<id>` — objective, constraints (incl. `policy_id`), holdings (instrument_id, quantity_usd_m, sleeve), `market_value_usd_m`.42- `/api/instruments/bonds` — bond master: `rating_bucket` (IG/HY), `modified_duration_years`, `yield_to_maturity_pct`, `coupon_pct`, `spread_bps`, `sector`, `subsector`, `energy_linked`, `candidate` (eligible for purchase), `issuer_id`, `recommended_theme_tags`.43- `/api/issuers` — `watchlist` (bool), `credit_outlook`, `rating_bucket`, `sector`, `subsector`, `research_tags`.44- `/api/market/energy` — commodity `signals` with `score`/`direction` (oil, gas, LNG, refining, renewables) + `pitch_themes`.45- `/api/indices` and `/api/index-levels[/<id>]` — monthly index level series.46- `/api/allocation/opportunity-sets` — opportunity_set → asset_class (Equities/Duration/Credit/Currency) + display_order.47- `/api/allocation/prior-views` — prior active views per opportunity_set & quarter (view, conviction, previous_quarter).48- `/api/macro-signals` — per opportunity_set & quarter: `score` (signed) and `rationale_code` and `drivers`.4950Most list endpoints accept equality filters (`?rating_bucket=HY`, `?candidate=true`, `?quarter=Q2_2026`).5152## 2. Policy thresholds (from `/api/policies` — read them, don't hardcode blindly)5354- **Allocation mapping** (`POL_ALLOCATION_MAPPING`):55 - View from signal score: `OW` if score ≥ `OW_min` (0.35); `UW` if score ≤ `UW_max` (-0.35); else `N`. Boundaries are inclusive of OW/UW.56 - Conviction from |score|: `HIGH` if |score| ≥ 0.70; `LOW` if |score| < 0.35; else `MEDIUM`.57 - view_rank: UW=-1, N=0, OW=+1 (used to derive `change`).58- **Correlation** (`POL_CORRELATION_DEFAULT`): `correlation_high_threshold` = 0.8, `correlation_low_threshold` = 0.2. Review window dates are given by the policy/request (e.g. 2025-05-30 → 2026-04-30).59- **Credit default** (`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.60- **Credit risk reduction** (`POL_CREDIT_RISK_REDUCTION`): same band/cap but `target_hy_reduction_pct` = 4.0.61- **Multi-asset** sets compose the above; `POL_MULTI_ASSET_RISK` escalates to committee on "two_or_more_material_exceptions".6263Always take the policy_id from the portfolio's own `constraints.policy_id` and use that policy's numbers.6465## 3. Core computation recipes6667### 3a. Pearson correlation (archetypes b, e)681. For each index pull its monthly level series for the window; sort by date.692. Compute **simple returns** between consecutive levels: r_t = level_t/level_{t-1} − 1.70 A 12-month window of levels yields **11 returns** → `return_observations` = 11.713. Pearson r over the paired return vectors. Round to **3 decimals**.724. `highest_positive` / `highest_concentration` = pair with the **max** correlation.73 `lowest` / `best_diversifier` = pair with the **minimum (most negative)** correlation.745. Each `pair_id`/`pair` is the two index ids sorted **alphabetically**.7576### 3b. Notional-weighted portfolio metrics (archetypes a, d)77Build the post-trade position list (current holdings ± trades), then weight by78`quantity_usd_m`:79- `total_market_value_usd_m` = Σ quantity (BUY-only packages add to MV; sell-and-fund rebalances keep MV constant when sells == buys).80- `weighted_modified_duration_years` = Σ(dur·qty)/Σqty.81- `weighted_yield_to_maturity_pct` = Σ(ytm·qty)/Σqty.82- `hy_allocation_pct` = (Σ qty where rating_bucket==HY) / total · 100, on **post-trade** MV.83- `hy_reduction_pct_points` = pre-trade HY% − post-trade HY%.84- watchlist exposure = Σ qty of held instruments whose issuer.watchlist is true.85Note HY% and HY-reduction depend only on which HY notional is added/removed —86they are invariant to *which* IG bond you buy; only duration/YTM depend on buy identity.8788### 3c. Signal → view mapping (archetypes c, e)89For each requested opportunity_set, take the **current** macro `score` for the90target quarter, map to view/conviction via §2, set `rationale_code` to the macro91signal's own `rationale_code`, set `asset_class` from the opportunity-set92taxonomy, and set `change` = sign of (new view_rank − prior view_rank) →93UP/DOWN/UNCHANGED. The prior view comes from `/api/allocation/prior-views`94(the row whose quarter == target quarter; its recorded `view` is the standing95prior). Always **recompute** the view from the live score — do not copy the96recorded prior view as the new view.9798## 4. Archetype playbooks99100### (a) Energy-credit trade package (e.g. PF-EN-ALTA)101Goal: N BUY tickets of equal notional that raise carry while staying inside102credit constraints and suitable for an income pitch.103104Eligibility filter for a candidate bond (ALL must hold):105- `candidate == true` AND `energy_linked == true`106- issuer `watchlist == false`107- not already a current holding (new sleeve money)108- **single-bond `modified_duration_years` within the duration band [3.0, 5.0]**109 — this per-bond duration gate is decisive. A long-dated bond (e.g. dur 5.9)110 is ineligible even though the *portfolio average* might still land in band.111112Selection among eligible bonds:113- Anchor on the **strongest energy signal** (typically LNG/gas — check114 `/api/market/energy`; LNG export pull is usually the top score). Pick the115 IG LNG/gas name (theme tags like LNG_EXPORTS / GAS_DEMAND).116- Pair it with the **highest-YTM** diversifier that has a **different issuer AND117 different subsector**, to maximize carry while passing diversification.118- Verify post-trade: HY% ≤ cap, weighted duration in band, both119 `selected_issuer_diversification` and `selected_subsector_diversification`120 true, `watchlist_avoidance` true. Set those constraint booleans honestly.121122Schema conventions: `trade_package` length 2, sorted ascending by123`instrument_id`, each {action:"BUY", instrument_id, notional_usd_m (1 dp)}.124`post_trade_metrics` (MV 2dp, hy% 2dp, dur 2dp, ytm 2dp). `sales_positioning`:125target_segment from the client context (e.g. `multi_asset_income`), theme from126the dominant signal (e.g. `lng_export_tailwind`). `data_precedence` =127`current_environment_over_stale_payload`.128129### (b) International equity correlation review (e.g. PF-INT-NEXVEN)1301. Compute the full correlation matrix over the requested index universe (§3a).1312. `extreme_pairs.highest_positive` and `.lowest` per §3a; `return_observations`132 from the window (11 for a 12-level year).1333. `index_set` = the universe sorted alphabetically.1344. `concentration`:135 - `high_threshold_breached` = any pair correlation ≥ 0.8 (true when the cluster136 is tightly correlated).137 - `china_asia_dependence_flag` = true when China/Asia/EM sleeves are mutually138 ≥ 0.8 (a dedicated-China + Asia-beta overlap exists).139 - `primary_code` = the dominant concentration label. Choose140 `CHINA_ASIA_DEPENDENCE` when the China/Asia block drives it, else141 `GLOBAL_DEVELOPED_OVERLAP` when the top pair is a broad developed/global142 overlap (e.g. EM↔World), else `NO_MATERIAL_CONCENTRATION`. (Keep143 china_asia_dependence_flag and primary_code logically consistent with which144 pair is actually the maximum.)1455. `diversification_candidates` = the low-correlation diversifier sleeve(s) from146 the allowed set (the index whose correlations are negative / well below the147 0.2 low threshold is the clearest — a LatAm-type sleeve; include an India-type148 only if it is a genuine relative diversifier, exclude broad EM-ex-China which149 stays ≥0.8).1506. `sleeve_actions` (ordered ascending by sleeve): **trim** the concentrated151 sleeve (the dedicated China/over-weighted one) and **add** the low-correlation152 diversifier. Prefer decisive `trim`/`add` over `monitor`/`hold` for a flagged153 concentration.154155### (c) Active allocation refresh (balanced multi-asset, e.g. Q2 focus list)156For each focus opportunity_set, emit a row {opportunity_set, asset_class, view,157change, conviction, rationale_code} using §3c. Order rows by the request payload's158focus order. Lineage: `policy_id` = `POL_ALLOCATION_MAPPING`; quarters echoed159from the request; `as_of_date` = env as-of.160161`risk_overlay`:162- Pick `overlay_code`/`primary_action` from the dominant cross-asset tilt implied163 by the views: duration support + IG quality with HY underweight →164 `DURATION_QUALITY_TILT` / `tilt_to_duration_quality`; a credit-led de-risk →165 `CREDIT_RISK_REDUCTION` / `trim_credit_beta`; strong cyclical equity →166 `EQUITY_BETA_EXTENSION`; defensive FX → `CURRENCY_DEFENSIVE_HEDGE`; else167 `NO_OVERLAY`.168- `rationale_codes` = the supporting macro rationale codes in business-priority169 order (duration/quality support first, then the credit-risk code), highest170 priority first.171(The allocation rows are the heavily weighted, deterministic part — get the172score-mapping, conviction boundaries, and `change` exactly right.)173174### (d) Fixed-income risk rebalance (sell-and-fund, e.g. PF-FI-LUMEN)175Use `POL_CREDIT_RISK_REDUCTION` (target HY reduction 4.0pp, cap 20%, band [3,5]).176Recipe:1771. Identify held HY (rating_bucket==HY) and held watchlist names (issuer.watchlist).1782. **Sells**: always sell the held **watchlist** name(s) to clear watchlist179 exposure to 0. Add the **minimum** additional HY needed to bring post-trade180 HY% under the 20% cap (and to meet the ≥4pp reduction). When choosing which181 extra HY to sell, prefer the **lowest-carry (lowest YTM)** HY name so carry is182 preserved — do not dump the highest-yielding HY.1833. **Buys**: fund with **non-watchlist IG `candidate` bonds named in the desk184 shortlist**, keeping total buys == total sells so MV/duration are preserved185 and no duration shortfall is created. **Exclude** any shortlist name whose186 issuer is on the watchlist (a tempting high-carry watchlist candidate must be187 dropped). Favor the cross-desk LNG IG name and a sector diversifier over a188 pure long-duration ballast when both are offered.1894. `risk_metrics` per §3b. `exception_flags`: hy_cap_pass (≤20%),190 duration_band_pass (in [3,5]), target_hy_reduction_met (≥4pp),191 watchlist_exposure_cleared (==0) — all true for a clean rotation.1925. `watchlist_handling.watchlist_sell_ids` = held watchlist ids you sold193 (ascending). `buys_avoid_watchlist` = true. `risk_note_code` = the dominant194 pressure (`hy_cap_pressure` when starting HY% far exceeds the cap).1956. `rotation.trades` ordered SELL before BUY, then instrument_id ascending within196 each action; quantities 1 dp.197198### (e) Multi-asset committee decision (e.g. PF-MA-HELIO)199Combines (b) + (c) on a small index/opportunity subset.2001. `correlation_summary` (length 2, item order [highest_concentration,201 best_diversifier]): highest_concentration = max-correlation pair;202 best_diversifier = min (most negative) pair; pairs alphabetical, corr 3dp.2032. `allocation_views` per §3c, including `prior_view` (from prior-views) and204 `signal_score` (the macro score, 3dp). Refresh stale currency views: a stale205 note keeping USD overweight is superseded — recompute from the live score206 (often lands at N), with `change` DOWN from the prior OW.2073. `target_sleeve_actions` (item order matches the request): map view direction208 to action — UW→`trim`, OW→`add` (high-conviction OW may `hold`), and for a209 currency sleeve being de-risked use `hedge` rather than `hold`.2104. `portfolio_risk_concentration_flag` = true when a pair ≥ the 0.8 high211 threshold (e.g. China↔EM concentration).2125. `rebalance_trigger` = `committee_review` for a committee decision file213 (prefer this over the more specific `correlation_cap_breach` in committee214 framing). `next_step` = `approve_with_monitoring` when a concentration flag is215 true but the rotation is actionable (escalate/defer only on ≥2 material216 exceptions).217218## 5. Pre-submission checklist219- [ ] Pulled live env data; reconciled every stale local number to it.220- [ ] `as_of_date` and `policy_id` taken from the environment, not the payload.221- [ ] All enum values match the template's allowed sets verbatim.222- [ ] Correlations: 3dp, alphabetical pairs, correct obs count, max/min picked correctly.223- [ ] Weighted metrics weighted by notional; HY%, duration, YTM rounded per template.224- [ ] Constraint/exception booleans computed from the actual post-trade book.225- [ ] Eligibility gates applied (per-bond duration band; candidate; non-watchlist; not already held).226- [ ] Lists in the exact required order and length.227- [ ] Required_value fields echoed literally; no extra keys, no narrative outside JSON.