Asteria Investment Office — Portfolio JSON Deliverables
You build compact JSON decision files for the Asteria Investment Office. Five archetypes
share one environment and one policy set. This skill gives the exact endpoints,
computation recipes, schema conventions, and policy rules that produce correct output.
0. Universal SOPs (apply to every task)
- Environment is the book of record. Pull every current fact (holdings, market
values, ratings, watchlist flags, index levels, prior views, macro scores, policy
thresholds) from the HTTP API. Local payloads (
desk_request, review_request,
risk_meeting_memo, committee_request, stale snapshots, "prior-week shortlists",
"exception boards") are intake context only and frequently stale. When they
disagree with the environment, the environment wins. If the schema has a
data_precedence field, set it to current_environment_over_stale_payload whenever
the local payload's marks/preferences differ from current records (they usually do).
- as_of_date = the environment's current
as_of_date (read it from the policies or
portfolio response; do not copy the local memo date). All four portfolios and the
policy set share one as-of date.
- Read the answer_template literally. Emit exactly the required keys, in the required
order where ordering is specified, using only enum values from
allowed_values, and
round every numeric to its declared precision. Do not add commentary keys.
- Booleans are computed, not assumed. Every
*_pass / *_flag boolean must reflect
the real post-trade/post-review computation, even if you also report the metric.
- Rounding: round only at the end, to the declared decimals. Percentages are in
percent units (e.g. 14.10, not 0.141). Notionals/quantities in USD millions.
- id ordering: wherever ids appear in a list or a pair, sort ascending alphabetically
unless the template names a different order (e.g. "follow request payload order",
"SELL before BUY", or a fixed item_order list).
- Use
python (Anaconda) for math; curl for HTTP. Save fetched JSON only inside your
own working directory.
Endpoints
GET /api/catalog ids available
GET /api/policies all thresholds (see §6)
GET /api/portfolios/<id> objective, constraints, holdings, market_value, as_of_date
GET /api/instruments/bonds bond master: rating_bucket, modified_duration_years,
yield_to_maturity_pct, spread_bps, energy_linked,
candidate, subsector, issuer_id
GET /api/issuers sector/subsector/rating_bucket/watchlist/outlook
GET /api/market/energy commodity signal scores + pitch themes
GET /api/indices index metadata
GET /api/index-levels (or /<id>) monthly index levels
GET /api/allocation/opportunity-sets asset_class taxonomy per opportunity_set
GET /api/allocation/prior-views standing views by (opportunity_set, quarter)
GET /api/macro-signals per (opportunity_set, quarter): score, rationale_code, drivers
1. Pearson correlation recipe (used by archetypes b and e)
The single most reused, fully deterministic computation. Get it byte-exact:
- Window = the policy / request
review_window (e.g. level_start_date ..
level_end_date). The default correlation window is on /api/policies under
correlation (review_window_start, review_window_end). A "12-month monthly-level
window" = 12 monthly levels.
- For each index, take its monthly levels inside the window inclusive, sorted by
date. N levels → N-1 returns. Report
return_observations = N-1 (12 levels → 11).
- Monthly simple returns:
r[i] = level[i]/level[i-1] - 1. (Not log returns.)
- Pearson correlation of the two return series; round to 3 decimals.
- Pair id = the two index ids sorted alphabetically.
- Highest-correlation pair = max over all unordered pairs; lowest = min (can be negative).
- Concentration / "highest_concentration" pair = the max-positive pair; "best diversifier"
/ "lowest" pair = the min (most negative) pair.
def returns(levels, start, end):
rows = sorted((d for d in levels if start <= d["date"] <= end), key=lambda x: x["date"])
lv = [d["level"] for d in rows]
return [lv[i]/lv[i-1]-1 for i in range(1, len(lv))]
def pearson(x, y):
n=len(x); mx=sum(x)/n; my=sum(y)/n
cov=sum((a-mx)*(b-my) for a,b in zip(x,y))
sx=sum((a-mx)**2 for a in x)**0.5; sy=sum((b-my)**2 for b in y)**0.5
return cov/(sx*sy)
2. Allocation-view mapping recipe (archetypes c and e)
Fully deterministic from POL_ALLOCATION_MAPPING (under /api/policies →
allocation_mapping). For each opportunity_set, for the target quarter:
signal_score = the macro-signals score for that (opportunity_set, target_quarter).
Round to 3 decimals where the schema asks for signal_score.
view: OW if score ≥ OW_min (0.35); UW if score ≤ UW_max (-0.35); else N.
(neutral band is the open-ish interval (-0.35, 0.35).)
conviction: HIGH if |score| ≥ 0.7; MEDIUM if |score| ≥ 0.35; else LOW.
change: compare the new view rank (UW=-1, N=0, OW=1) to the prior view. The prior
view is the /api/allocation/prior-views row whose quarter == target_quarter and
previous_quarter == prior_quarter (i.e. the standing view going into this refresh).
rank diff > 0 → UP; < 0 → DOWN; 0 → UNCHANGED.
rationale_code: copy directly from the macro-signal row's rationale_code for that
(opportunity_set, target_quarter). Do not re-derive it.
asset_class: from /api/allocation/opportunity-sets
(Equities / Duration / Credit / Currency).
- Row order: follow the request payload's
focus_opportunity_sets /
opportunity_sets order, NOT alphabetical.
Worked enum example (target Q2_2026, do recompute live): a strongly positive domestic-growth
score → OW / HIGH / INDIA_OFFSET; a mildly negative dollar score (~-0.22) → N (it is inside
the neutral band) / LOW / NEUTRAL_BALANCE with change=DOWN if the prior view was OW. Always
honor the neutral band: a score between -0.35 and 0.35 is N even if it is clearly signed.
Portfolio risk overlay (archetype c)
For a balanced multi-asset model, read the duration and credit signals: a positive
duration/Treasury signal plus a negative high-yield signal → overlay_code
DURATION_QUALITY_TILT with primary_action tilt_to_duration_quality. If credit
de-risking dominates instead, use CREDIT_RISK_REDUCTION / trim_credit_beta. Populate
rationale_codes from the most material drivers ordered by |score| descending (highest
priority first), e.g. duration support before HY valuation risk before regional themes.
3. Archetype (a) — Energy-credit trade package
Goal: a small BUY package (e.g. 2 tickets, even split of a fixed total notional) that
improves carry and stays inside credit constraints, suitable for an income pitch.
- Eligible universe: bond is
candidate == true AND energy_linked == true
AND its issuer's watchlist == false. (Watchlist/refining names are the "yield
trap" to avoid; never buy them even though they have the fattest coupons.)
- Each ticket notional = total / ticket_count (even split). Sort
trade_package ascending
by instrument_id.
- post_trade_metrics on the post-trade book (current holdings + new buys; new MV =
current MV + total bought):
total_market_value_usd_m (2dp).
hy_allocation_pct = HY market value / total MV × 100 (2dp). HY = rating_bucket=="HY".
weighted_modified_duration_years = Σ(qty × modified_duration)/MV (2dp).
weighted_yield_to_maturity_pct = Σ(qty × ytm)/MV (2dp).
- constraint_checks (booleans):
hy_cap_pass: post-trade hy_allocation_pct ≤ max_hy_allocation_pct (20.0).
duration_band_pass: post-trade WMD within duration_band_years [3.0, 5.0] inclusive.
selected_issuer_diversification_pass: the selected buys are different issuers.
selected_subsector_diversification_pass: the selected buys span ≥ 2 distinct
subsectors (so two same-subsector LNG names fail this — pair one LNG name with a
different subsector).
watchlist_avoidance_pass: no selected bond's issuer is on the watchlist (true if you
obeyed the eligibility filter).
- sales_positioning: pick
target_segment and theme from the enum to match the
dominant energy signal and the client context. A multi-asset income client + a dominant
positive LNG export signal → target_segment multi_asset_income, theme
lng_export_tailwind. If refining/oversupply dominates, use the matching caution theme;
if the package's whole point is dodging watchlist names, avoid_watchlist_yield_trap.
- data_precedence:
current_environment_over_stale_payload (the desk worksheet marks
are stale vs the portfolio service).
- Selection principle: maximize carry subject to all constraints, but keep the package
defensible for an income pitch — favor the desk's stated preferred exposures (e.g. LNG /
gas demand) and do not stack the absolute-highest-yield long-dated HY names if a cleaner,
thematically-aligned package also clears the constraints.
4. Archetype (b) — International equity correlation review
index_set: the request's index universe, sorted ascending alphabetically.
review_window: echo level_start_date / level_end_date; return_observations =
(levels in window) - 1.
extreme_pairs.highest_positive = max-correlation pair; extreme_pairs.lowest =
min-correlation pair (correlation 3dp, pair_id alphabetical).
concentration:
china_asia_dependence_flag: true if the China / Asia-Pacific complex is highly
correlated with the broad sleeve (multiple China/Asia pairs ≥ correlation_high_threshold).
primary_code: CHINA_ASIA_DEPENDENCE when China/Asia drives the concentration;
GLOBAL_DEVELOPED_OVERLAP if developed-market overlap dominates; else
NO_MATERIAL_CONCENTRATION.
high_threshold_breached: true if any pair correlation ≥ correlation_high_threshold
(0.8). The top global/EM pair (~0.97) breaches it.
diversification_candidates (from the allowed set, alphabetical): the indices that are
genuinely low-correlated to the concentrated bloc, i.e. correlation to China/Asia/EM
below correlation_low_threshold (0.2). A negatively-correlated regional sleeve
(e.g. Latin America) qualifies decisively; sleeves still ≥ 0.8 correlated to the bloc do
not. Apply the threshold rule rather than guessing the whole allowed set.
sleeve_actions (length 2, ascending by sleeve; sleeve = the portfolio holding's
sleeve name string): trim the concentrated sleeve (action trim, target_index_id
= that bloc's index, e.g. IDX_CHINA) and add the low-correlation diversifier (action
add, target_index_id = the diversifier index, e.g. IDX_LATAM). Do NOT "rotate" the
concentrated sleeve into another still-correlated bloc — a plain trim + add scores higher
than a rotate.
5. Archetype (d) — Fixed-income risk rebalance
Goal: reduce HY and remove avoidable watchlist risk while keeping duration in the CIO band
and preserving carry. Governing policy is POL_CREDIT_RISK_REDUCTION
(target_hy_reduction_pct 4.0, max_hy_allocation_pct 20.0, duration_band_years
[3.0, 5.0]).
- The rebalance is market-value-neutral: SELL proceeds fund the BUYs, total MV
unchanged.
- Mandatory sell: the held HY bond whose issuer is on the watchlist. Put it in
watchlist_handling.watchlist_sell_ids (ascending) and set buys_avoid_watchlist=true.
- Be targeted, not maximal. Do NOT dump every HY position to 0% — over-reducing HY is
penalized because it destroys carry. Sell the watchlist name plus only the additional HY
needed to bring
hy_allocation_pct under the cap (20%) while meeting the ≥4.0pp reduction
target; preserve the highest-carry HY names where possible.
- Buys: investment-grade, non-watchlist
candidate==true bonds (e.g. data-center,
materials, LNG IG names). Size them MV-neutral so post-trade duration stays inside
[3.0, 5.0] and near the prior duration. Never buy a watchlist candidate even if it has
the highest carry.
rotation.trades ordering: SELL before BUY, then instrument_id ascending within each
action. quantity_usd_m to 1dp.
risk_metrics: post_trade_hy_allocation_pct (2dp), post_trade_duration_years (2dp),
hy_reduction_pct_points = current HY% − post HY% (2dp),
post_trade_watchlist_exposure_usd_m (1dp, → 0 once the watchlist name is sold).
exception_flags: hy_cap_pass (post HY% ≤ 20), duration_band_pass (in band),
target_hy_reduction_met (reduction ≥ 4.0pp), watchlist_exposure_cleared
(post watchlist exposure == 0).
risk_note_code: when the headline action is removing watchlist names, use
watchlist_concentration (this is the right note even though the HY cap was also
breached — the watchlist clean-up is the story). Use hy_cap_pressure only when the cap
is the sole driver and no watchlist name is involved.
6. Archetype (e) — Multi-asset committee decision
Combines §1 (correlation) and §2 (allocation views) into one committee file.
correlation_summary (length 2, fixed item_order [highest_concentration, best_diversifier]): over the committee's small index set, on the current 12-month
monthly-level window. highest_concentration = the max-correlation pair;
best_diversifier = the min (most negative) pair. Each item: pair_role, pair
(alphabetical), correlation (3dp).
allocation_views (item_order from the request, e.g. EM, India, LATAM, USD): full §2
recipe — prior_view, signal_score (3dp), view, change, conviction,
rationale_code. USD/currency sets follow the same thresholds (a mildly negative USD
score lands in the neutral band → N, typically DOWN from a prior OW, LOW).
target_sleeve_actions (same item_order): map view → action — UW→trim, OW→add
(or hold if the OW is unchanged), N→hold/monitor; a currency that has lost its
defensive overweight → hedge/monitor. (Action wording is the most lenient field;
keep it consistent with the view direction.)
portfolio_risk_concentration_flag: true if any committee-set pair correlation breaches
correlation_high_threshold (0.8).
rebalance_trigger: when a correlation pair breaches the high threshold, the natural
trigger is correlation_cap_breach; otherwise committee_review.
next_step: gate on the multi-asset-risk escalation rule
(committee_escalation_threshold = "two_or_more_material_exceptions"). With a single
material exception (e.g. one correlation breach), approve_with_monitoring; with two or
more material exceptions, escalate (defer_pending_risk_review); use reject_constraint_breach
only for an outright hard-constraint violation and approve_rotation when nothing is flagged.
7. Policy constants (verify live each run — values seen in the policy set)
- Credit (
credit_default / credit_risk_reduction): max_hy_allocation_pct 20.0;
duration_band_years [3.0, 5.0]; issuer_concentration_limit_pct 12.0;
subsector_min_count_for_diversified 2; target_hy_reduction_pct 0.0 (default) / 4.0
(risk-reduction).
- Correlation (
correlation): correlation_high_threshold 0.8; correlation_low_threshold
0.2; default window 2025-05-30 .. 2026-04-30.
- Allocation mapping (
allocation_mapping): view thresholds OW_min 0.35 / UW_max -0.35;
conviction HIGH ≥ 0.7, MEDIUM ≥ 0.35, else LOW; view_rank UW -1 / N 0 / OW 1.
- Multi-asset risk: escalate at two_or_more_material_exceptions.
Always re-read /api/policies rather than hard-coding; thresholds are authoritative there.
8. Pre-submit checklist
1---2name: asteria-investment-office-json-33description: Produce the Asteria Investment Office portfolio JSON deliverables (energy-credit trade packages, international equity correlation reviews, active allocation refreshes, fixed-income risk rebalances, and multi-asset committee decisions). Treat the shared Asteria HTTP environment as the authoritative book of record over any local payload, apply the documented policy thresholds, and emit answers that exactly match each task's answer_template (field names, enum value sets, rounding, id ordering). Use this whenever a prompt references Asteria portfolios (PF-*), bonds (BND_*), issuers (ISS_*), indices (IDX_*), opportunity sets, macro signals, or allocation policy.4---56# Asteria Investment Office — Portfolio JSON Deliverables78You build compact JSON decision files for the Asteria Investment Office. Five archetypes9share one environment and one policy set. This skill gives the exact endpoints,10computation recipes, schema conventions, and policy rules that produce correct output.1112## 0. Universal SOPs (apply to every task)13141. **Environment is the book of record.** Pull every current fact (holdings, market15 values, ratings, watchlist flags, index levels, prior views, macro scores, policy16 thresholds) from the HTTP API. Local payloads (`desk_request`, `review_request`,17 `risk_meeting_memo`, `committee_request`, stale snapshots, "prior-week shortlists",18 "exception boards") are **intake context only and frequently stale**. When they19 disagree with the environment, the environment wins. If the schema has a20 `data_precedence` field, set it to `current_environment_over_stale_payload` whenever21 the local payload's marks/preferences differ from current records (they usually do).222. **as_of_date** = the environment's current `as_of_date` (read it from the policies or23 portfolio response; do not copy the local memo date). All four portfolios and the24 policy set share one as-of date.253. **Read the answer_template literally.** Emit exactly the required keys, in the required26 order where ordering is specified, using only enum values from `allowed_values`, and27 round every numeric to its declared precision. Do not add commentary keys.284. **Booleans are computed, not assumed.** Every `*_pass` / `*_flag` boolean must reflect29 the real post-trade/post-review computation, even if you also report the metric.305. **Rounding:** round only at the end, to the declared decimals. Percentages are in31 percent units (e.g. 14.10, not 0.141). Notionals/quantities in USD millions.326. **id ordering:** wherever ids appear in a list or a pair, sort ascending alphabetically33 unless the template names a different order (e.g. "follow request payload order",34 "SELL before BUY", or a fixed item_order list).357. Use `python` (Anaconda) for math; `curl` for HTTP. Save fetched JSON only inside your36 own working directory.3738### Endpoints39```40GET /api/catalog ids available41GET /api/policies all thresholds (see §6)42GET /api/portfolios/<id> objective, constraints, holdings, market_value, as_of_date43GET /api/instruments/bonds bond master: rating_bucket, modified_duration_years,44 yield_to_maturity_pct, spread_bps, energy_linked,45 candidate, subsector, issuer_id46GET /api/issuers sector/subsector/rating_bucket/watchlist/outlook47GET /api/market/energy commodity signal scores + pitch themes48GET /api/indices index metadata49GET /api/index-levels (or /<id>) monthly index levels50GET /api/allocation/opportunity-sets asset_class taxonomy per opportunity_set51GET /api/allocation/prior-views standing views by (opportunity_set, quarter)52GET /api/macro-signals per (opportunity_set, quarter): score, rationale_code, drivers53```5455## 1. Pearson correlation recipe (used by archetypes b and e)5657The single most reused, fully deterministic computation. Get it byte-exact:58591. Window = the policy / request `review_window` (e.g. `level_start_date` ..60 `level_end_date`). The default correlation window is on `/api/policies` under61 `correlation` (`review_window_start`, `review_window_end`). A "12-month monthly-level62 window" = 12 monthly levels.632. For each index, take its monthly levels **inside the window inclusive**, sorted by64 date. **N levels → N-1 returns.** Report `return_observations` = N-1 (12 levels → 11).653. **Monthly simple returns:** `r[i] = level[i]/level[i-1] - 1`. (Not log returns.)664. **Pearson** correlation of the two return series; **round to 3 decimals**.675. Pair id = the two index ids sorted **alphabetically**.686. Highest-correlation pair = max over all unordered pairs; lowest = min (can be negative).697. Concentration / "highest_concentration" pair = the max-positive pair; "best diversifier"70 / "lowest" pair = the min (most negative) pair.7172```python73def returns(levels, start, end):74 rows = sorted((d for d in levels if start <= d["date"] <= end), key=lambda x: x["date"])75 lv = [d["level"] for d in rows]76 return [lv[i]/lv[i-1]-1 for i in range(1, len(lv))]7778def pearson(x, y):79 n=len(x); mx=sum(x)/n; my=sum(y)/n80 cov=sum((a-mx)*(b-my) for a,b in zip(x,y))81 sx=sum((a-mx)**2 for a in x)**0.5; sy=sum((b-my)**2 for b in y)**0.582 return cov/(sx*sy)83```8485## 2. Allocation-view mapping recipe (archetypes c and e)8687Fully deterministic from `POL_ALLOCATION_MAPPING` (under `/api/policies` →88`allocation_mapping`). For each opportunity_set, for the **target quarter**:8990- `signal_score` = the macro-signals `score` for that (opportunity_set, target_quarter).91 Round to 3 decimals where the schema asks for `signal_score`.92- `view`: `OW` if score ≥ `OW_min` (0.35); `UW` if score ≤ `UW_max` (-0.35); else `N`.93 (neutral band is the open-ish interval (-0.35, 0.35).)94- `conviction`: `HIGH` if |score| ≥ 0.7; `MEDIUM` if |score| ≥ 0.35; else `LOW`.95- `change`: compare the new `view` rank (UW=-1, N=0, OW=1) to the **prior view**. The prior96 view is the `/api/allocation/prior-views` row whose `quarter` == target_quarter and97 `previous_quarter` == prior_quarter (i.e. the standing view going into this refresh).98 rank diff > 0 → `UP`; < 0 → `DOWN`; 0 → `UNCHANGED`.99- `rationale_code`: copy directly from the macro-signal row's `rationale_code` for that100 (opportunity_set, target_quarter). Do not re-derive it.101- `asset_class`: from `/api/allocation/opportunity-sets`102 (Equities / Duration / Credit / Currency).103- **Row order:** follow the request payload's `focus_opportunity_sets` /104 `opportunity_sets` order, NOT alphabetical.105106Worked enum example (target Q2_2026, do recompute live): a strongly positive domestic-growth107score → OW / HIGH / INDIA_OFFSET; a mildly negative dollar score (~-0.22) → N (it is inside108the neutral band) / LOW / NEUTRAL_BALANCE with `change=DOWN` if the prior view was OW. Always109honor the neutral band: a score between -0.35 and 0.35 is `N` even if it is clearly signed.110111### Portfolio risk overlay (archetype c)112For a balanced multi-asset model, read the duration and credit signals: a positive113duration/Treasury signal plus a negative high-yield signal → `overlay_code`114`DURATION_QUALITY_TILT` with `primary_action` `tilt_to_duration_quality`. If credit115de-risking dominates instead, use `CREDIT_RISK_REDUCTION` / `trim_credit_beta`. Populate116`rationale_codes` from the most material drivers ordered by |score| descending (highest117priority first), e.g. duration support before HY valuation risk before regional themes.118119## 3. Archetype (a) — Energy-credit trade package120121Goal: a small BUY package (e.g. 2 tickets, even split of a fixed total notional) that122improves carry and stays inside credit constraints, suitable for an income pitch.123124- **Eligible universe:** bond is `candidate == true` **AND** `energy_linked == true`125 **AND** its issuer's `watchlist == false`. (Watchlist/refining names are the "yield126 trap" to avoid; never buy them even though they have the fattest coupons.)127- Each ticket notional = total / ticket_count (even split). Sort `trade_package` ascending128 by `instrument_id`.129- **post_trade_metrics** on the post-trade book (current holdings + new buys; new MV =130 current MV + total bought):131 - `total_market_value_usd_m` (2dp).132 - `hy_allocation_pct` = HY market value / total MV × 100 (2dp). HY = `rating_bucket=="HY"`.133 - `weighted_modified_duration_years` = Σ(qty × modified_duration)/MV (2dp).134 - `weighted_yield_to_maturity_pct` = Σ(qty × ytm)/MV (2dp).135- **constraint_checks** (booleans):136 - `hy_cap_pass`: post-trade hy_allocation_pct ≤ `max_hy_allocation_pct` (20.0).137 - `duration_band_pass`: post-trade WMD within `duration_band_years` [3.0, 5.0] inclusive.138 - `selected_issuer_diversification_pass`: the selected buys are different issuers.139 - `selected_subsector_diversification_pass`: the selected buys span ≥ 2 distinct140 subsectors (so two same-subsector LNG names fail this — pair one LNG name with a141 different subsector).142 - `watchlist_avoidance_pass`: no selected bond's issuer is on the watchlist (true if you143 obeyed the eligibility filter).144- **sales_positioning:** pick `target_segment` and `theme` from the enum to match the145 dominant energy signal and the client context. A multi-asset income client + a dominant146 positive LNG export signal → `target_segment` `multi_asset_income`, `theme`147 `lng_export_tailwind`. If refining/oversupply dominates, use the matching caution theme;148 if the package's whole point is dodging watchlist names, `avoid_watchlist_yield_trap`.149- **data_precedence:** `current_environment_over_stale_payload` (the desk worksheet marks150 are stale vs the portfolio service).151- Selection principle: maximize carry **subject to** all constraints, but keep the package152 defensible for an income pitch — favor the desk's stated preferred exposures (e.g. LNG /153 gas demand) and do not stack the absolute-highest-yield long-dated HY names if a cleaner,154 thematically-aligned package also clears the constraints.155156## 4. Archetype (b) — International equity correlation review157158- `index_set`: the request's index universe, sorted ascending alphabetically.159- `review_window`: echo `level_start_date` / `level_end_date`; `return_observations` =160 (levels in window) - 1.161- `extreme_pairs.highest_positive` = max-correlation pair; `extreme_pairs.lowest` =162 min-correlation pair (correlation 3dp, pair_id alphabetical).163- `concentration`:164 - `china_asia_dependence_flag`: true if the China / Asia-Pacific complex is highly165 correlated with the broad sleeve (multiple China/Asia pairs ≥ `correlation_high_threshold`).166 - `primary_code`: `CHINA_ASIA_DEPENDENCE` when China/Asia drives the concentration;167 `GLOBAL_DEVELOPED_OVERLAP` if developed-market overlap dominates; else168 `NO_MATERIAL_CONCENTRATION`.169 - `high_threshold_breached`: true if any pair correlation ≥ `correlation_high_threshold`170 (0.8). The top global/EM pair (~0.97) breaches it.171- `diversification_candidates` (from the allowed set, alphabetical): the indices that are172 **genuinely low-correlated to the concentrated bloc**, i.e. correlation to China/Asia/EM173 **below `correlation_low_threshold` (0.2)**. A negatively-correlated regional sleeve174 (e.g. Latin America) qualifies decisively; sleeves still ≥ 0.8 correlated to the bloc do175 not. Apply the threshold rule rather than guessing the whole allowed set.176- `sleeve_actions` (length 2, ascending by sleeve; `sleeve` = the portfolio holding's177 sleeve name string): **trim the concentrated sleeve** (action `trim`, `target_index_id`178 = that bloc's index, e.g. IDX_CHINA) and **add the low-correlation diversifier** (action179 `add`, `target_index_id` = the diversifier index, e.g. IDX_LATAM). Do NOT "rotate" the180 concentrated sleeve into another still-correlated bloc — a plain trim + add scores higher181 than a rotate.182183## 5. Archetype (d) — Fixed-income risk rebalance184185Goal: reduce HY and remove avoidable watchlist risk while keeping duration in the CIO band186and preserving carry. Governing policy is `POL_CREDIT_RISK_REDUCTION`187(`target_hy_reduction_pct` 4.0, `max_hy_allocation_pct` 20.0, `duration_band_years`188[3.0, 5.0]).189190- The rebalance is **market-value-neutral**: SELL proceeds fund the BUYs, total MV191 unchanged.192- **Mandatory sell:** the held HY bond whose issuer is on the watchlist. Put it in193 `watchlist_handling.watchlist_sell_ids` (ascending) and set `buys_avoid_watchlist=true`.194- **Be targeted, not maximal.** Do NOT dump every HY position to 0% — over-reducing HY is195 penalized because it destroys carry. Sell the watchlist name plus only the additional HY196 needed to bring `hy_allocation_pct` under the cap (20%) while meeting the ≥4.0pp reduction197 target; preserve the highest-carry HY names where possible.198- **Buys:** investment-grade, non-watchlist `candidate==true` bonds (e.g. data-center,199 materials, LNG IG names). Size them MV-neutral so post-trade duration stays inside200 [3.0, 5.0] and near the prior duration. Never buy a watchlist candidate even if it has201 the highest carry.202- `rotation.trades` ordering: **SELL before BUY, then instrument_id ascending within each203 action.** `quantity_usd_m` to 1dp.204- `risk_metrics`: `post_trade_hy_allocation_pct` (2dp), `post_trade_duration_years` (2dp),205 `hy_reduction_pct_points` = current HY% − post HY% (2dp),206 `post_trade_watchlist_exposure_usd_m` (1dp, → 0 once the watchlist name is sold).207- `exception_flags`: `hy_cap_pass` (post HY% ≤ 20), `duration_band_pass` (in band),208 `target_hy_reduction_met` (reduction ≥ 4.0pp), `watchlist_exposure_cleared`209 (post watchlist exposure == 0).210- `risk_note_code`: when the headline action is removing watchlist names, use211 `watchlist_concentration` (this is the right note even though the HY cap was also212 breached — the watchlist clean-up is the story). Use `hy_cap_pressure` only when the cap213 is the sole driver and no watchlist name is involved.214215## 6. Archetype (e) — Multi-asset committee decision216217Combines §1 (correlation) and §2 (allocation views) into one committee file.218219- `correlation_summary` (length 2, fixed item_order `[highest_concentration,220 best_diversifier]`): over the committee's small index set, on the current 12-month221 monthly-level window. `highest_concentration` = the max-correlation pair;222 `best_diversifier` = the min (most negative) pair. Each item: `pair_role`, `pair`223 (alphabetical), `correlation` (3dp).224- `allocation_views` (item_order from the request, e.g. EM, India, LATAM, USD): full §2225 recipe — `prior_view`, `signal_score` (3dp), `view`, `change`, `conviction`,226 `rationale_code`. USD/currency sets follow the same thresholds (a mildly negative USD227 score lands in the neutral band → `N`, typically `DOWN` from a prior OW, `LOW`).228- `target_sleeve_actions` (same item_order): map view → action — `UW`→`trim`, `OW`→`add`229 (or `hold` if the OW is unchanged), `N`→`hold`/`monitor`; a currency that has lost its230 defensive overweight → `hedge`/`monitor`. (Action wording is the most lenient field;231 keep it consistent with the view direction.)232- `portfolio_risk_concentration_flag`: true if any committee-set pair correlation breaches233 `correlation_high_threshold` (0.8).234- `rebalance_trigger`: when a correlation pair breaches the high threshold, the natural235 trigger is `correlation_cap_breach`; otherwise `committee_review`.236- `next_step`: gate on the multi-asset-risk escalation rule237 (`committee_escalation_threshold` = "two_or_more_material_exceptions"). With a single238 material exception (e.g. one correlation breach), `approve_with_monitoring`; with two or239 more material exceptions, escalate (`defer_pending_risk_review`); use `reject_constraint_breach`240 only for an outright hard-constraint violation and `approve_rotation` when nothing is flagged.241242## 7. Policy constants (verify live each run — values seen in the policy set)243244- Credit (`credit_default` / `credit_risk_reduction`): `max_hy_allocation_pct` 20.0;245 `duration_band_years` [3.0, 5.0]; `issuer_concentration_limit_pct` 12.0;246 `subsector_min_count_for_diversified` 2; `target_hy_reduction_pct` 0.0 (default) / 4.0247 (risk-reduction).248- Correlation (`correlation`): `correlation_high_threshold` 0.8; `correlation_low_threshold`249 0.2; default window 2025-05-30 .. 2026-04-30.250- Allocation mapping (`allocation_mapping`): view thresholds OW_min 0.35 / UW_max -0.35;251 conviction HIGH ≥ 0.7, MEDIUM ≥ 0.35, else LOW; view_rank UW -1 / N 0 / OW 1.252- Multi-asset risk: escalate at two_or_more_material_exceptions.253254Always re-read `/api/policies` rather than hard-coding; thresholds are authoritative there.255256## 8. Pre-submit checklist257258- [ ] Every value pulled from the environment, not the stale local payload.259- [ ] as_of_date = environment current date.260- [ ] All enums are from the template's `allowed_values`; no invented strings.261- [ ] Numerics rounded to declared precision; percents in percent units.262- [ ] Lists/pairs ordered exactly as specified (alphabetical id, request order, or263 SELL-before-BUY).264- [ ] Correlations: monthly **simple** returns, N-1 observations, 3dp, alphabetical pairs.265- [ ] Views: neutral band respected; change measured vs the standing prior view;266 rationale_code copied from the macro row.267- [ ] Constraint/exception booleans recomputed from the post-trade book.268- [ ] Watchlist names excluded from buys; required watchlist names sold; rebalance MV-neutral269 and not over-reduced.270- [ ] data_precedence set to current_environment_over_stale_payload when payload conflicts.271- [ ] Output is a single JSON object with exactly the required keys, no extra commentary.