Asteria Investment Office — JSON answer workflow
You produce a single JSON object that conforms to the task's
input/payloads/answer_template.json. The shared environment is a read-only HTTP
API and the current book of record. Local payloads are intake context only and
may be stale; when they disagree with the environment, the environment wins.
Base URL: <remote-env-url> (see environment_access.md). Use
curl for HTTP and python (NOT python3) for math. Save fetched JSON inside your
own working directory.
0. Universal SOP (do this for every task)
- Read the prompt + every payload + the answer_template first. The template is
the contract: it dictates required keys, enum value sets, rounding precision,
list lengths, and ordering. Mirror it exactly. Extra or missing top-level keys,
wrong enum spelling, or wrong ordering are failures.
- Pull the live data you need from the endpoints below — never trust a stale
mark, weight, watchlist flag, view, or duration from the local payload. Recompute
everything from the environment.
- Identify the governing policy. Each portfolio summary / detail carries a
constraint_policy_id. Resolve thresholds from /api/policies (see §1). Do not
hardcode numbers from memory; read them.
- Compute, then cross-check internal consistency (e.g. sum of holding
quantities equals stated market value; HY% is HY market value / total market
value; correlation observation count = number of monthly levels − 1).
- Emit JSON only (no prose) when the prompt says so. Round each numeric field
to the precision the template declares. Apply the exact ordering rules.
- Set any data-precedence / lineage field to reflect that the current
environment overrode the stale payload when (and only when) a real conflict
exists; otherwise use the "no conflict" value.
Key endpoints and what they authoritatively provide
GET /api/catalog — valid ids for portfolios, policies, indices, issuers, bonds,
opportunity sets. Use to sanity-check id spelling.
GET /api/policies — all thresholds (correlation, allocation mapping, credit
bands, HY caps, issuer concentration, target HY reduction) plus as_of_date.
GET /api/portfolios and /api/portfolios/<id> — as_of_date,
market_value_usd_m, constraint_policy_id, and holdings (instrument_id,
quantity_usd_m, sleeve, asset_class). Holding quantities are USD millions and (in
this dataset) sum to the portfolio market value, so quantity = market-value weight.
GET /api/instruments/bonds — per-bond security master: candidate (true =
buyable candidate, false = currently-held/not-offered), energy_linked,
rating_bucket (IG/HY), modified_duration_years, yield_to_maturity_pct,
coupon_pct, spread_bps, subsector, issuer_id, recommended_theme_tags.
GET /api/issuers — watchlist (bool), rating_bucket, sector, subsector,
credit_outlook, research_tags. The issuer watchlist flag is the source of
truth for watchlist avoidance (bond theme tags like WATCHLIST_RISK corroborate
but rely on the issuer flag).
GET /api/indices and /api/index-levels (or /api/index-levels/<id>) — index
metadata and monthly {date, level} arrays per index id.
GET /api/allocation/opportunity-sets — opportunity_set → asset_class
(Equities / Duration / Credit / Currency) and display_order.
GET /api/allocation/prior-views — rows keyed by quarter + previous_quarter,
each with view (UW/N/OW) and conviction. The prior view for a target quarter Q
is the row whose quarter == Q (its previous_quarter is the quarter before).
GET /api/macro-signals — per opportunity_set per quarter: numeric score,
rationale_code, drivers. This is the signal that maps to a view.
GET /api/market/energy — current commodity signals + pitch_themes for energy
trade positioning.
List endpoints accept simple equality filters, e.g. ?rating_bucket=HY,
?candidate=true, ?quarter=Q2_2026.
1. Policy thresholds (read live from /api/policies)
The current set (POLICY_SET_2026_05, as_of_date 2026-05-29) exposes:
- allocation_mapping (
POL_ALLOCATION_MAPPING):
view_score_thresholds: OW_min = +0.35, UW_max = −0.35, neutral band
(−0.35, +0.35). Score ≥ OW_min → OW; ≤ UW_max → UW; else N.
conviction_thresholds on absolute score: HIGH_abs_min = 0.70,
MEDIUM_abs_min = 0.35, LOW_abs_below = 0.35. |score| ≥ 0.70 → HIGH;
0.35 ≤ |score| < 0.70 → MEDIUM; |score| < 0.35 → LOW.
view_rank: UW = −1, N = 0, OW = +1. Use for change direction.
- correlation (
POL_CORRELATION_DEFAULT): correlation_high_threshold = 0.80,
correlation_low_threshold = 0.20, review window 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 bands/caps but
target_hy_reduction_pct = 4.0 (used by risk-reduction rebalances).
- multi_asset (
POL_MULTI_ASSET_DEFAULT): composes allocation_mapping +
correlation_default + credit_default.
- multi_asset_risk (
POL_MULTI_ASSET_RISK): composes correlation_default +
credit_risk_reduction; committee_escalation_threshold =
"two_or_more_material_exceptions".
Always re-read these; do not assume they are unchanged between runs.
2. Core computation recipes
Pearson correlation from monthly simple returns
- For each index, fetch
/api/index-levels/<id>; keep rows with
level_start_date <= date <= level_end_date (the window from the request /
policy). Sort ascending by date.
- The standard window has 12 monthly levels → 11 monthly returns
(
return_observations = levels − 1). Verify all indices share the same dates.
- Simple return r_t = level_t / level_{t-1} − 1.
- Pearson r over the aligned return vectors. Round to 3 decimals unless the
template says otherwise.
- Compare against
correlation_high_threshold (0.80) and
correlation_low_threshold (0.20). "Highest positive" = max r; "lowest" = min r
(can be strongly negative).
Weighted modified duration / weighted YTM
Quantity-weighted (= market-value-weighted, since qty sums to MV):
Σ(qty_i × metric_i) / Σ qty_i. Use post-trade quantities for post-trade metrics.
HY allocation %
100 × (HY market value) / (total post-trade market value). HY = bonds whose
rating_bucket == "HY". Denominator is the portfolio market value AFTER the trades
(for BUY-only sleeves, MV increases by the bought notional; for self-funded
rotations, MV is unchanged).
Issuer / subsector concentration & "selected" diversification
- Portfolio issuer concentration = max issuer MV / total MV vs
issuer_concentration_limit_pct (12%). NOTE: a held position can already breach
this; a pure-BUY task's selected_*_diversification_pass flags refer to the
selected trade package, i.e. the chosen tickets use ≥2 distinct issuers and
≥2 distinct subsectors (subsector_min_count_for_diversified = 2) — not a
portfolio-wide recomputation. Read the template field name to decide which.
Carry / YTM improvement
"Improve carry" = raise portfolio weighted yield_to_maturity_pct (and/or
coupon_pct) versus the current weighted YTM. Compute current weighted YTM first
as the baseline to beat.
3. View / conviction / change mapping (allocation archetypes)
For each opportunity set, for the target quarter:
signal_score = score from /api/macro-signals (that opportunity_set, that
quarter). Round to 3 decimals where the template exposes it.
view from view_score_thresholds (§1).
conviction from |score| (§1).
prior_view = /api/allocation/prior-views row where quarter == target.
change by comparing view_rank(new view) vs view_rank(prior view):
greater → UP, less → DOWN, equal → UNCHANGED.
rationale_code = the macro signal's rationale_code for that set (it already
uses the allowed enum). asset_class from /api/allocation/opportunity-sets.
Watch for stale-payload traps: a local note may assert an old view (e.g. "kept USD
overweight"); if the current score maps elsewhere (e.g. USD score in the neutral
band → N with change DOWN), the environment value wins.
4. Archetype playbooks
(a) Energy-credit recommendation / trade-package selection
Endpoints: portfolio detail, /api/instruments/bonds, /api/issuers,
/api/market/energy, /api/policies.
- Eligible BUY universe =
candidate == true AND energy_linked == true AND issuer
watchlist == false (committee is sensitive to watchlist yield traps; the energy
desk theme is AVOID_REFINING_WATCHLIST). Exclude already-held when the task asks
for new tickets.
- Honour the ticket spec literally (e.g. "exactly two BUY tickets totalling USD 8.0m
split evenly" → two BUYs of 4.0 each).
- Choose tickets that (i) raise weighted YTM above the current baseline, (ii) keep
post-trade weighted duration inside [3.0, 5.0], (iii) keep post-trade HY ≤ 20%,
(iv) use ≥2 issuers and ≥2 subsectors (selected diversification), and (v) fit the
income/theme story. For an LNG/gas income pitch, favour Natural Gas/LNG and
midstream/oil names (the top energy signal is LNG export pull); set
sales_positioning.target_segment = multi_asset_income and
sales_positioning.theme = lng_export_tailwind. Use avoid_watchlist_yield_trap
only if the story is explicitly about dodging watchlist carry.
- Output:
trade_package sorted ascending by instrument_id; numeric precision
per template (notional 1 dp; metrics 2 dp). post_trade_metrics recomputed on the
full post-trade book. constraint_checks booleans: hy_cap_pass,
duration_band_pass, selected_issuer_diversification_pass,
selected_subsector_diversification_pass, watchlist_avoidance_pass.
data_precedence = current_environment_over_stale_payload when the live
portfolio (MV, HY, duration) differs from the stale snapshot.
as_of_date = the portfolio/environment as_of_date.
(b) International equity correlation review
Endpoints: portfolio detail, /api/policies, /api/indices, /api/index-levels.
- Build the correlation matrix over the requested index universe and window (§2).
review_window.return_observations = levels − 1.
extreme_pairs.highest_positive = max-correlation pair; .lowest =
min-correlation pair. Each pair_id is the two index ids sorted ascending
alphabetically; correlation to 3 decimals.
index_set = the universe sorted ascending alphabetically.
concentration: set high_threshold_breached = (max pair r ≥ 0.80);
china_asia_dependence_flag = true when the dominant high-correlation cluster is
China/Asia regional overlap; primary_code ∈
{CHINA_ASIA_DEPENDENCE, GLOBAL_DEVELOPED_OVERLAP, NO_MATERIAL_CONCENTRATION} —
pick CHINA_ASIA_DEPENDENCE when China/Asia drive the concentration, else the
global-developed overlap or none.
diversification_candidates ⊆ {IDX_EM_EX_CHINA, IDX_INDIA, IDX_LATAM} — the
low/negative-correlation diversifiers (LatAm is the classic strong diversifier when
it shows negative correlations); sorted ascending alphabetically.
sleeve_actions: length 2, ordered ascending by sleeve; action ∈
{trim, add, hold, hedge, monitor, rotate}; target_index_id ∈
{IDX_CHINA, IDX_EM_EX_CHINA, IDX_LATAM}. Typical pattern: trim the
concentrated/over-overlapping sleeve, add the diversifier.
(c) Active allocation view refresh
Endpoints: /api/allocation/opportunity-sets, /api/allocation/prior-views,
/api/macro-signals, /api/policies.
- One row per requested opportunity set, rows ordered by the request payload's
focus_opportunity_sets order (NOT alphabetical) unless template says otherwise.
- Each row: opportunity_set, asset_class, view, change, conviction, rationale_code
per §3.
risk_overlay: pick overlay_code + primary_action from the dominant signal
cluster across the requested sets. When duration/quality signals dominate
(Treasuries/Bunds/IG positive) →
DURATION_QUALITY_TILT / tilt_to_duration_quality. When HY risk dominates
(Corporate High Yield strongly negative) →
CREDIT_RISK_REDUCTION / trim_credit_beta. When cyclical equity is broadly
positive → EQUITY_BETA_EXTENSION / add_cyclical_equity_beta. When a defensive
currency hedge is warranted → CURRENCY_DEFENSIVE_HEDGE / add_currency_hedge.
Otherwise NO_OVERLAY / hold_policy_weights. rationale_codes is a list ordered
by business priority (strongest driver first), drawn from the enum.
policy_id = the allocation/policy id (POL_ALLOCATION_MAPPING or the composing
policy set id, per template). as_of_date, target_quarter, prior_quarter, and
any fixed task_id/required_value come straight from the template/payload.
(d) Fixed-income risk rebalance
Endpoints: portfolio detail, /api/instruments/bonds, /api/issuers,
/api/policies (governing policy is usually POL_CREDIT_RISK_REDUCTION).
- Baseline: compute current HY%, weighted duration, watchlist exposure (Σ qty of
holdings whose issuer
watchlist == true).
- SELL pressure points: watchlist HY first (to clear watchlist exposure), then
enough additional HY so post-trade HY ≤ 20% (max_hy_allocation_pct) AND HY
reduction ≥
target_hy_reduction_pct (4.0 pp). Selling only the single watchlist
bond is usually NOT enough to satisfy the 20% cap — verify the arithmetic and sell
a second HY line if needed.
- BUY current eligible candidates (
candidate == true, issuer watchlist == false)
to fund the sells and preserve duration inside [3.0, 5.0] (IG ballast like data
centers / mining / utilities). NEVER buy a watchlist candidate even if it is on the
desk shortlist (e.g. a high-carry watchlist HY name is a deliberate distractor) →
buys_avoid_watchlist = true.
- Keep the rotation self-funded (Σ SELL notional = Σ BUY notional) unless the task
says otherwise.
- Output:
trades sorted by action with SELL before BUY, then ascending
instrument_id within each action; quantity 1 dp. risk_metrics (post_trade_hy %
2dp, post_trade_duration 2dp, hy_reduction_pct_points 2dp, watchlist_exposure 1dp).
exception_flags booleans (hy_cap_pass, duration_band_pass,
target_hy_reduction_met, watchlist_exposure_cleared). watchlist_sell_ids
ascending. risk_note_code = dominant driver (watchlist_concentration or
hy_cap_pressure; duration_preservation / carry_tradeoff / no_action
otherwise). as_of_date = portfolio as_of_date.
(e) Multi-asset committee decision
Endpoints: portfolio detail, index levels, /api/policies, prior-views,
macro-signals (it links a correlation finding to allocation views).
correlation_summary: length 2 in fixed order
[highest_concentration, best_diversifier] over the requested equity index subset
(window = current 12-month monthly window = policy window). Each item: pair_role
enum, pair (two ids sorted alphabetically), correlation 3dp. Highest
concentration = max-correlation pair; best diversifier = min-correlation pair.
target_sleeve_actions and allocation_views: one row per opportunity set in the
payload's item_order (NOT alphabetical). allocation_views expose prior_view,
signal_score (3dp), view, change, conviction, rationale_code per §3. sleeve actions
use {trim, add, hold, hedge, monitor, rotate} — typically trim the concentration
driver, add/hold the diversifier, hedge/monitor a defensive currency.
rebalance_trigger ∈ {correlation_cap_breach, hy_cap_pressure, duration_drift,
watchlist_concentration, committee_review}: use correlation_cap_breach when the
top equity pair ≥ 0.80, else committee_review.
portfolio_risk_concentration_flag = true when a material concentration exists
(e.g. China/Asia equity pair ≥ high threshold).
next_step ∈ {approve_rotation, defer_pending_risk_review,
approve_with_monitoring, reject_constraint_breach}: choose
approve_with_monitoring for a sound but concentration-flagged plan,
reject_constraint_breach only on a hard breach, defer_pending_risk_review when
data/escalation is incomplete. Honour the multi_asset_risk escalation rule (two or
more material exceptions → committee escalation).
- Refresh against the stale local note (e.g. don't carry a stale USD overweight if
the current USD score is neutral).
as_of_date = environment as_of_date.
5. Common misjudgments to avoid
- Trusting stale local marks/weights/views/watchlist flags instead of the live API.
- Forgetting the off-by-one: N monthly levels give N−1 returns; observation count and
correlation both use returns, not levels.
- Using log returns or pairwise-dropped data — use simple returns over the
common, fully-aligned window.
- Mis-ordering output: pair ids alphabetical within a pair; lists sometimes
alphabetical (correlation review), sometimes payload-defined order (allocation
rows), sometimes action-then-id (rotations). Read each ordering rule.
- Buying or holding a watchlist name because it has the highest carry — it is a
yield-trap distractor; watchlist avoidance overrides carry.
- Selling only the watchlist bond and assuming the HY cap is satisfied — verify HY%
≤ 20 explicitly and sell more HY if needed.
- Confusing candidate-vs-held:
candidate == false bonds are existing/un-offered;
only candidate == true are buyable.
- Confusing "selected diversification" (within the chosen trade package) with
portfolio-wide issuer concentration.
- Rounding at the wrong precision, or returning narrative text when JSON-only is
required.
- Never call any scoring/judge endpoint; only the read-only data endpoints above.
1---2name: asteria-investment-office-json-23description: Executable workflow for the Asteria Investment Office task family. Covers five archetypes (energy-credit trade package, international equity correlation review, active allocation view refresh, fixed-income risk rebalance, multi-asset committee decision). Documents which HTTP endpoints supply which fields, the exact computation recipes (Pearson correlation, weighted modified duration, HY %, carry/ YTM, issuer & subsector diversification), the policy thresholds in /api/policies, and the output-schema conventions (field names, enum value sets, rounding, id ordering). The remote environment is always the book of record.4---56# Asteria Investment Office — JSON answer workflow78You produce a single JSON object that conforms to the task's9`input/payloads/answer_template.json`. The shared environment is a read-only HTTP10API and the **current book of record**. Local payloads are intake context only and11may be stale; when they disagree with the environment, **the environment wins**.1213Base URL: `<remote-env-url>` (see `environment_access.md`). Use14`curl` for HTTP and `python` (NOT `python3`) for math. Save fetched JSON inside your15own working directory.1617## 0. Universal SOP (do this for every task)18191. **Read the prompt + every payload + the answer_template first.** The template is20 the contract: it dictates required keys, enum value sets, rounding precision,21 list lengths, and ordering. Mirror it exactly. Extra or missing top-level keys,22 wrong enum spelling, or wrong ordering are failures.232. **Pull the live data** you need from the endpoints below — never trust a stale24 mark, weight, watchlist flag, view, or duration from the local payload. Recompute25 everything from the environment.263. **Identify the governing policy.** Each portfolio summary / detail carries a27 `constraint_policy_id`. Resolve thresholds from `/api/policies` (see §1). Do not28 hardcode numbers from memory; read them.294. **Compute, then cross-check internal consistency** (e.g. sum of holding30 quantities equals stated market value; HY% is HY market value / total market31 value; correlation observation count = number of monthly levels − 1).325. **Emit JSON only** (no prose) when the prompt says so. Round each numeric field33 to the precision the template declares. Apply the exact ordering rules.346. **Set any data-precedence / lineage field** to reflect that the current35 environment overrode the stale payload when (and only when) a real conflict36 exists; otherwise use the "no conflict" value.3738### Key endpoints and what they authoritatively provide39- `GET /api/catalog` — valid ids for portfolios, policies, indices, issuers, bonds,40 opportunity sets. Use to sanity-check id spelling.41- `GET /api/policies` — all thresholds (correlation, allocation mapping, credit42 bands, HY caps, issuer concentration, target HY reduction) plus `as_of_date`.43- `GET /api/portfolios` and `/api/portfolios/<id>` — `as_of_date`,44 `market_value_usd_m`, `constraint_policy_id`, and `holdings` (instrument_id,45 quantity_usd_m, sleeve, asset_class). Holding quantities are USD millions and (in46 this dataset) sum to the portfolio market value, so quantity = market-value weight.47- `GET /api/instruments/bonds` — per-bond security master: `candidate` (true =48 buyable candidate, false = currently-held/not-offered), `energy_linked`,49 `rating_bucket` (IG/HY), `modified_duration_years`, `yield_to_maturity_pct`,50 `coupon_pct`, `spread_bps`, `subsector`, `issuer_id`, `recommended_theme_tags`.51- `GET /api/issuers` — `watchlist` (bool), `rating_bucket`, `sector`, `subsector`,52 `credit_outlook`, `research_tags`. The **issuer** `watchlist` flag is the source of53 truth for watchlist avoidance (bond theme tags like `WATCHLIST_RISK` corroborate54 but rely on the issuer flag).55- `GET /api/indices` and `/api/index-levels` (or `/api/index-levels/<id>`) — index56 metadata and monthly `{date, level}` arrays per index id.57- `GET /api/allocation/opportunity-sets` — `opportunity_set` → `asset_class`58 (Equities / Duration / Credit / Currency) and `display_order`.59- `GET /api/allocation/prior-views` — rows keyed by `quarter` + `previous_quarter`,60 each with `view` (UW/N/OW) and `conviction`. The prior view for a target quarter Q61 is the row whose `quarter == Q` (its `previous_quarter` is the quarter before).62- `GET /api/macro-signals` — per `opportunity_set` per `quarter`: numeric `score`,63 `rationale_code`, `drivers`. This is the signal that maps to a view.64- `GET /api/market/energy` — current commodity signals + `pitch_themes` for energy65 trade positioning.6667List endpoints accept simple equality filters, e.g. `?rating_bucket=HY`,68`?candidate=true`, `?quarter=Q2_2026`.6970## 1. Policy thresholds (read live from /api/policies)7172The current set (`POLICY_SET_2026_05`, `as_of_date` 2026-05-29) exposes:7374- **allocation_mapping** (`POL_ALLOCATION_MAPPING`):75 - `view_score_thresholds`: `OW_min` = +0.35, `UW_max` = −0.35, neutral band76 (−0.35, +0.35). Score ≥ OW_min → **OW**; ≤ UW_max → **UW**; else **N**.77 - `conviction_thresholds` on **absolute** score: `HIGH_abs_min` = 0.70,78 `MEDIUM_abs_min` = 0.35, `LOW_abs_below` = 0.35. |score| ≥ 0.70 → HIGH;79 0.35 ≤ |score| < 0.70 → MEDIUM; |score| < 0.35 → LOW.80 - `view_rank`: UW = −1, N = 0, OW = +1. Use for change direction.81- **correlation** (`POL_CORRELATION_DEFAULT`): `correlation_high_threshold` = 0.80,82 `correlation_low_threshold` = 0.20, review window `2025-05-30` … `2026-04-30`.83- **credit_default** (`POL_CREDIT_DEFAULT`): `duration_band_years` [3.0, 5.0],84 `max_hy_allocation_pct` 20.0, `issuer_concentration_limit_pct` 12.0,85 `subsector_min_count_for_diversified` 2, `target_hy_reduction_pct` 0.0.86- **credit_risk_reduction** (`POL_CREDIT_RISK_REDUCTION`): same bands/caps but87 `target_hy_reduction_pct` = 4.0 (used by risk-reduction rebalances).88- **multi_asset** (`POL_MULTI_ASSET_DEFAULT`): composes allocation_mapping +89 correlation_default + credit_default.90- **multi_asset_risk** (`POL_MULTI_ASSET_RISK`): composes correlation_default +91 credit_risk_reduction; `committee_escalation_threshold` =92 "two_or_more_material_exceptions".9394Always re-read these; do not assume they are unchanged between runs.9596## 2. Core computation recipes9798### Pearson correlation from monthly simple returns991. For each index, fetch `/api/index-levels/<id>`; keep rows with100 `level_start_date <= date <= level_end_date` (the window from the request /101 policy). Sort ascending by date.1022. The standard window has **12 monthly levels → 11 monthly returns**103 (`return_observations` = levels − 1). Verify all indices share the same dates.1043. Simple return r_t = level_t / level_{t-1} − 1.1054. Pearson r over the aligned return vectors. Round to **3 decimals** unless the106 template says otherwise.1075. Compare against `correlation_high_threshold` (0.80) and108 `correlation_low_threshold` (0.20). "Highest positive" = max r; "lowest" = min r109 (can be strongly negative).110111### Weighted modified duration / weighted YTM112Quantity-weighted (= market-value-weighted, since qty sums to MV):113`Σ(qty_i × metric_i) / Σ qty_i`. Use post-trade quantities for post-trade metrics.114115### HY allocation %116`100 × (HY market value) / (total post-trade market value)`. HY = bonds whose117`rating_bucket == "HY"`. Denominator is the portfolio market value AFTER the trades118(for BUY-only sleeves, MV increases by the bought notional; for self-funded119rotations, MV is unchanged).120121### Issuer / subsector concentration & "selected" diversification122- Portfolio issuer concentration = max issuer MV / total MV vs123 `issuer_concentration_limit_pct` (12%). NOTE: a held position can already breach124 this; a pure-BUY task's `selected_*_diversification_pass` flags refer to the125 **selected trade package**, i.e. the chosen tickets use ≥2 distinct issuers and126 ≥2 distinct subsectors (`subsector_min_count_for_diversified` = 2) — not a127 portfolio-wide recomputation. Read the template field name to decide which.128129### Carry / YTM improvement130"Improve carry" = raise portfolio weighted `yield_to_maturity_pct` (and/or131`coupon_pct`) versus the current weighted YTM. Compute current weighted YTM first132as the baseline to beat.133134## 3. View / conviction / change mapping (allocation archetypes)135136For each opportunity set, for the target quarter:1371. `signal_score` = `score` from `/api/macro-signals` (that opportunity_set, that138 quarter). Round to 3 decimals where the template exposes it.1392. `view` from `view_score_thresholds` (§1).1403. `conviction` from |score| (§1).1414. `prior_view` = `/api/allocation/prior-views` row where `quarter == target`.1425. `change` by comparing `view_rank(new view)` vs `view_rank(prior view)`:143 greater → `UP`, less → `DOWN`, equal → `UNCHANGED`.1446. `rationale_code` = the macro signal's `rationale_code` for that set (it already145 uses the allowed enum). `asset_class` from `/api/allocation/opportunity-sets`.146147Watch for stale-payload traps: a local note may assert an old view (e.g. "kept USD148overweight"); if the current score maps elsewhere (e.g. USD score in the neutral149band → N with change DOWN), the environment value wins.150151## 4. Archetype playbooks152153### (a) Energy-credit recommendation / trade-package selection154Endpoints: portfolio detail, `/api/instruments/bonds`, `/api/issuers`,155`/api/market/energy`, `/api/policies`.156- Eligible BUY universe = `candidate == true` AND `energy_linked == true` AND issuer157 `watchlist == false` (committee is sensitive to watchlist yield traps; the energy158 desk theme is `AVOID_REFINING_WATCHLIST`). Exclude already-held when the task asks159 for *new* tickets.160- Honour the ticket spec literally (e.g. "exactly two BUY tickets totalling USD 8.0m161 split evenly" → two BUYs of 4.0 each).162- Choose tickets that (i) raise weighted YTM above the current baseline, (ii) keep163 post-trade weighted duration inside [3.0, 5.0], (iii) keep post-trade HY ≤ 20%,164 (iv) use ≥2 issuers and ≥2 subsectors (selected diversification), and (v) fit the165 income/theme story. For an LNG/gas income pitch, favour Natural Gas/LNG and166 midstream/oil names (the top energy signal is LNG export pull); set167 `sales_positioning.target_segment` = `multi_asset_income` and168 `sales_positioning.theme` = `lng_export_tailwind`. Use `avoid_watchlist_yield_trap`169 only if the story is explicitly about dodging watchlist carry.170- Output: `trade_package` sorted **ascending by instrument_id**; numeric precision171 per template (notional 1 dp; metrics 2 dp). `post_trade_metrics` recomputed on the172 full post-trade book. `constraint_checks` booleans: hy_cap_pass,173 duration_band_pass, selected_issuer_diversification_pass,174 selected_subsector_diversification_pass, watchlist_avoidance_pass.175 `data_precedence` = `current_environment_over_stale_payload` when the live176 portfolio (MV, HY, duration) differs from the stale snapshot.177- `as_of_date` = the portfolio/environment `as_of_date`.178179### (b) International equity correlation review180Endpoints: portfolio detail, `/api/policies`, `/api/indices`, `/api/index-levels`.181- Build the correlation matrix over the requested index universe and window (§2).182 `review_window.return_observations` = levels − 1.183- `extreme_pairs.highest_positive` = max-correlation pair; `.lowest` =184 min-correlation pair. Each `pair_id` is the two index ids **sorted ascending185 alphabetically**; `correlation` to 3 decimals.186- `index_set` = the universe sorted ascending alphabetically.187- `concentration`: set `high_threshold_breached` = (max pair r ≥ 0.80);188 `china_asia_dependence_flag` = true when the dominant high-correlation cluster is189 China/Asia regional overlap; `primary_code` ∈190 {CHINA_ASIA_DEPENDENCE, GLOBAL_DEVELOPED_OVERLAP, NO_MATERIAL_CONCENTRATION} —191 pick CHINA_ASIA_DEPENDENCE when China/Asia drive the concentration, else the192 global-developed overlap or none.193- `diversification_candidates` ⊆ {IDX_EM_EX_CHINA, IDX_INDIA, IDX_LATAM} — the194 low/negative-correlation diversifiers (LatAm is the classic strong diversifier when195 it shows negative correlations); sorted ascending alphabetically.196- `sleeve_actions`: length 2, ordered ascending by sleeve; `action` ∈197 {trim, add, hold, hedge, monitor, rotate}; `target_index_id` ∈198 {IDX_CHINA, IDX_EM_EX_CHINA, IDX_LATAM}. Typical pattern: trim the199 concentrated/over-overlapping sleeve, add the diversifier.200201### (c) Active allocation view refresh202Endpoints: `/api/allocation/opportunity-sets`, `/api/allocation/prior-views`,203`/api/macro-signals`, `/api/policies`.204- One row per requested opportunity set, rows ordered by the **request payload's205 focus_opportunity_sets order** (NOT alphabetical) unless template says otherwise.206- Each row: opportunity_set, asset_class, view, change, conviction, rationale_code207 per §3.208- `risk_overlay`: pick `overlay_code` + `primary_action` from the dominant signal209 cluster across the requested sets. When duration/quality signals dominate210 (Treasuries/Bunds/IG positive) →211 `DURATION_QUALITY_TILT` / `tilt_to_duration_quality`. When HY risk dominates212 (Corporate High Yield strongly negative) →213 `CREDIT_RISK_REDUCTION` / `trim_credit_beta`. When cyclical equity is broadly214 positive → `EQUITY_BETA_EXTENSION` / `add_cyclical_equity_beta`. When a defensive215 currency hedge is warranted → `CURRENCY_DEFENSIVE_HEDGE` / `add_currency_hedge`.216 Otherwise `NO_OVERLAY` / `hold_policy_weights`. `rationale_codes` is a list ordered217 by business priority (strongest driver first), drawn from the enum.218- `policy_id` = the allocation/policy id (`POL_ALLOCATION_MAPPING` or the composing219 policy set id, per template). `as_of_date`, `target_quarter`, `prior_quarter`, and220 any fixed `task_id`/`required_value` come straight from the template/payload.221222### (d) Fixed-income risk rebalance223Endpoints: portfolio detail, `/api/instruments/bonds`, `/api/issuers`,224`/api/policies` (governing policy is usually `POL_CREDIT_RISK_REDUCTION`).225- Baseline: compute current HY%, weighted duration, watchlist exposure (Σ qty of226 holdings whose issuer `watchlist == true`).227- SELL pressure points: watchlist HY first (to clear watchlist exposure), then228 enough additional HY so post-trade HY ≤ 20% (max_hy_allocation_pct) AND HY229 reduction ≥ `target_hy_reduction_pct` (4.0 pp). Selling only the single watchlist230 bond is usually NOT enough to satisfy the 20% cap — verify the arithmetic and sell231 a second HY line if needed.232- BUY current eligible candidates (`candidate == true`, issuer `watchlist == false`)233 to fund the sells and preserve duration inside [3.0, 5.0] (IG ballast like data234 centers / mining / utilities). NEVER buy a watchlist candidate even if it is on the235 desk shortlist (e.g. a high-carry watchlist HY name is a deliberate distractor) →236 `buys_avoid_watchlist` = true.237- Keep the rotation self-funded (Σ SELL notional = Σ BUY notional) unless the task238 says otherwise.239- Output: `trades` sorted by action with **SELL before BUY**, then ascending240 instrument_id within each action; quantity 1 dp. `risk_metrics` (post_trade_hy %241 2dp, post_trade_duration 2dp, hy_reduction_pct_points 2dp, watchlist_exposure 1dp).242 `exception_flags` booleans (hy_cap_pass, duration_band_pass,243 target_hy_reduction_met, watchlist_exposure_cleared). `watchlist_sell_ids`244 ascending. `risk_note_code` = dominant driver (`watchlist_concentration` or245 `hy_cap_pressure`; `duration_preservation` / `carry_tradeoff` / `no_action`246 otherwise). `as_of_date` = portfolio as_of_date.247248### (e) Multi-asset committee decision249Endpoints: portfolio detail, index levels, `/api/policies`, prior-views,250macro-signals (it links a correlation finding to allocation views).251- `correlation_summary`: length 2 in fixed order252 [highest_concentration, best_diversifier] over the requested equity index subset253 (window = current 12-month monthly window = policy window). Each item: pair_role254 enum, `pair` (two ids sorted alphabetically), `correlation` 3dp. Highest255 concentration = max-correlation pair; best diversifier = min-correlation pair.256- `target_sleeve_actions` and `allocation_views`: one row per opportunity set in the257 payload's `item_order` (NOT alphabetical). allocation_views expose prior_view,258 signal_score (3dp), view, change, conviction, rationale_code per §3. sleeve actions259 use {trim, add, hold, hedge, monitor, rotate} — typically trim the concentration260 driver, add/hold the diversifier, hedge/monitor a defensive currency.261- `rebalance_trigger` ∈ {correlation_cap_breach, hy_cap_pressure, duration_drift,262 watchlist_concentration, committee_review}: use `correlation_cap_breach` when the263 top equity pair ≥ 0.80, else `committee_review`.264- `portfolio_risk_concentration_flag` = true when a material concentration exists265 (e.g. China/Asia equity pair ≥ high threshold).266- `next_step` ∈ {approve_rotation, defer_pending_risk_review,267 approve_with_monitoring, reject_constraint_breach}: choose268 `approve_with_monitoring` for a sound but concentration-flagged plan,269 `reject_constraint_breach` only on a hard breach, `defer_pending_risk_review` when270 data/escalation is incomplete. Honour the multi_asset_risk escalation rule (two or271 more material exceptions → committee escalation).272- Refresh against the stale local note (e.g. don't carry a stale USD overweight if273 the current USD score is neutral). `as_of_date` = environment as_of_date.274275## 5. Common misjudgments to avoid276277- Trusting stale local marks/weights/views/watchlist flags instead of the live API.278- Forgetting the off-by-one: N monthly levels give N−1 returns; observation count and279 correlation both use returns, not levels.280- Using log returns or pairwise-dropped data — use **simple** returns over the281 common, fully-aligned window.282- Mis-ordering output: pair ids alphabetical within a pair; lists sometimes283 alphabetical (correlation review), sometimes payload-defined order (allocation284 rows), sometimes action-then-id (rotations). Read each ordering rule.285- Buying or holding a watchlist name because it has the highest carry — it is a286 yield-trap distractor; watchlist avoidance overrides carry.287- Selling only the watchlist bond and assuming the HY cap is satisfied — verify HY%288 ≤ 20 explicitly and sell more HY if needed.289- Confusing candidate-vs-held: `candidate == false` bonds are existing/un-offered;290 only `candidate == true` are buyable.291- Confusing "selected diversification" (within the chosen trade package) with292 portfolio-wide issuer concentration.293- Rounding at the wrong precision, or returning narrative text when JSON-only is294 required.295- Never call any scoring/judge endpoint; only the read-only data endpoints above.