Asteria Investment Office — Risk Evaluation Skill
Transferable execution knowledge for the Asteria institutional portfolio-risk
tasks. The test task will give you one portfolio, a local request payload
(input/payloads/), an answer_template.json output contract, and access to
the same remote Asteria environment described below. You have NOT seen the
train tasks; this file is your playbook.
The tasks draw from three workflows:
- A. Energy / fixed-income trade strategy (bond ticket selection + post-trade metrics).
- B. International equity correlation review (compute correlations from index levels).
- C. Cross-asset active allocation view updates (map macro signals to views).
- A fourth shape D blends B + C into one committee JSON.
1. Environment access (the book of record)
Base URL: <remote-env-url>
The remote environment is the current book of record. Local payloads in
input/payloads/ are intake context and are frequently stale (old worksheet
dates, stale marks, stale quantities). When a local payload conflicts with the
environment, prefer the environment unless the task prompt explicitly says
otherwise.
Call with curl (Bash) or Python urllib/requests. Always re-fetch live data
per task — do not assume cached shapes.
Endpoints that matter
| Endpoint |
Use |
GET /api/catalog |
All portfolio ids, policy ids, index ids, issuer ids, bond ids, opportunity sets. |
GET /api/policies |
Constraints: HY cap, duration band, diversification rules, correlation thresholds, allocation-mapping thresholds. |
GET /api/portfolios |
Portfolio summaries. |
GET /api/portfolios/<id> |
Objective, constraints (incl. policy_id, max_hy_allocation_pct, duration_band_years, optional target_hy_reduction_pct), current holdings, market_value_usd_m, as_of_date. |
GET /api/instruments/bonds |
Held + candidate bond universe. Filters: ?candidate=true, ?rating_bucket=HY. |
GET /api/issuers |
Issuer sector, subsector, rating_bucket, watchlist (bool), credit_outlook, research_tags. Authoritative watchlist source. |
GET /api/market/energy |
Energy commodity signals (scores, directions) and pitch_themes for sale positioning. |
GET /api/indices |
Index metadata (region, currency, level_start_date, level_end_date, frequency). |
GET /api/index-levels / GET /api/index-levels/<id> |
Monthly index levels {date, level}. No correlations are precomputed — compute them yourself. |
GET /api/allocation/opportunity-sets |
Cross-asset taxonomy: opportunity_set, asset_class, display_order. |
GET /api/allocation/prior-views |
Prior-quarter active views (view, conviction, quarter, previous_quarter). |
GET /api/macro-signals |
Per opportunity-set score, rationale_code, drivers, quarter. Drives view/conviction/rationale. |
Most list endpoints accept simple equality filters matching field names
(?rating_bucket=HY, ?candidate=true, ?quarter=Q3_2026).
Environment rules
- Use only the listed endpoints. Do not read source/data files or any local
env/ directory.
- Numeric precision in the final answer must follow
input/payloads/answer_template.json per field.
- The environment
as_of_date (e.g. 2026-05-29) is the as-of date for answers unless the task gives another.
2. Data-precedence rule (applies to every workflow)
- Read the local request payload to learn the ask (which portfolio, which
window, which opportunity sets, ticket count, totals, preferences).
- Read the environment for the actual current values used in computation.
- If a stale local snapshot (worksheet date, stale marks, stale quantities,
stale_exception_board, stale_local_note) conflicts with the environment,
compute from the environment and emit data_precedence = current_environment_over_stale_payload.
- If no stale-conflict exists, emit
no_conflict_found.
local_payload_over_current_environment is essentially never correct unless
the task prompt explicitly overrides.
Where the answer schema has a data_precedence field, set it accordingly.
Where it does not (e.g. workflow B/C/D), still apply the rule silently in your
computations.
3. Workflow A — Energy / fixed-income trade strategy
Tasks ask for a proposed set of BUY (and in rotation variants SELL+BUY) bond
tickets that improve carry while keeping the portfolio inside credit-risk
constraints, plus post-trade metrics, constraint booleans, sales positioning,
and a data-precedence declaration. Output shape follows the task's own
answer_template.json; two variants were seen:
- BUY-only variant:
trade_package (list of {action,instrument_id,notional_usd_m}),
post_trade_metrics, constraint_checks, sales_positioning, data_precedence.
- Rotation variant:
rotation.trades (list of {action,instrument_id,quantity_usd_m}),
risk_metrics, exception_flags, watchlist_handling, risk_note_code.
SOP
GET /api/portfolios/<id> — read constraints, holdings, market_value_usd_m.
GET /api/instruments/bonds?candidate=true — candidate universe.
GET /api/issuers — join each candidate's issuer_id to get watchlist,
sector, subsector, rating_bucket. (The bonds endpoint watchlist field
is unreliable/None — use issuers.)
GET /api/market/energy — for energy tasks, read signals + pitch_themes.
GET /api/policies — confirm HY cap, duration band, diversification, and
any target_hy_reduction_pct.
Candidate selection rules
- Only buy bonds with
candidate=true.
- For an energy desk task, require
energy_linked=true and match desk
preferences (e.g. LNG exporters / gas demand). For a general credit rotation,
non-energy IG candidates (data centers, mining, REIT, banking, utility) are
eligible.
- Exclude watchlist issuers (
watchlist=true) from BUYs. Identify them via
/api/issuers, not the bond record.
- Exclude duration-ineligible distractors: a single bond whose
modified_duration_years is far outside the band is a distractor even if
attractive on yield; the portfolio-weighted duration must stay in band, but
avoid buying bonds that would push it out.
- Honor the ticket count, total notional, and split rules from the request
payload exactly (e.g. "exactly two BUY tickets totaling USD 8.0M split evenly"
→ two tickets of 4.0M each).
- Prefer candidates that improve carry (higher
yield_to_maturity_pct)
while keeping HY% under the cap and duration in band.
- BUYs must be diversified: distinct issuers and distinct subsectors among
the selected tickets (this drives
selected_issuer_diversification_pass and
selected_subsector_diversification_pass; policy subsector_min_count_for_diversified
is typically 2 distinct subsectors among what you add).
Rotation (SELL+BUY) rules
- Identify current holdings on watchlist issuers and SELL them first
(
watchlist_sell_ids).
- SELL enough HY to meet the portfolio's
target_hy_reduction_pct (percentage
points) — you may overshoot the target when also clearing watchlist risk.
- BUY IG (or otherwise eligible) candidates that fund the SELL notional, keep
weighted duration inside the band, and avoid watchlist issuers.
- Use environment holding quantities for SELL sizes, not stale local
stale_exception_board quantities.
Post-trade metric computation (market-value-weighted)
Treat each holding's quantity_usd_m as its market value (par approximation;
no separate clean-price field is provided). After applying trades:
total_market_value_usd_m = sum of all post-trade holding quantities (for
BUY-only funded by new allocation, add the bought notionals to the starting
market_value_usd_m; for a balanced rotation, total MV is unchanged).
hy_allocation_pct = (sum of post-trade HY holding market values) / total MV × 100.
weighted_modified_duration_years = Σ(qty_i × modified_duration_years_i) / total MV.
weighted_yield_to_maturity_pct = Σ(qty_i × yield_to_maturity_pct_i) / total MV.
- A holding is HY iff its issuer/bond
rating_bucket == "HY" (or rating startswith HY).
Verify each bond's modified_duration_years and yield_to_maturity_pct from
/api/instruments/bonds (match by instrument_id).
Constraint checks (booleans)
Read thresholds from /api/policies (policy_id from portfolio constraints):
hy_cap_pass: post-trade hy_allocation_pct <= max_hy_allocation_pct (e.g. 20.0).
duration_band_pass: post-trade weighted duration within duration_band_years (e.g. [3.0, 5.0]) inclusive.
selected_issuer_diversification_pass: selected BUY tickets have >=2 distinct issuers.
selected_subsector_diversification_pass: selected BUY tickets have >=2 distinct subsectors.
watchlist_avoidance_pass: none of the BUYs is a watchlist issuer (watchlist_avoidance_pass); for rotation also watchlist_exposure_cleared (post-trade watchlist MV == 0) and buys_avoid_watchlist.
- Rotation adds
target_hy_reduction_met: actual HY reduction (ppt) >= target_hy_reduction_pct.
Sales positioning (energy BUY-only variant)
target_segment: derive from the request's client context (e.g.
"multi-asset income update" → multi_asset_income). Allowed values are in
the template.
theme: pick the pitch_theme from /api/market/energy that matches the
dominant positive energy signal backing the trade (e.g. LNG export growth →
lng_export_tailwind). Map energy pitch_themes to the template's theme
enum (LNG_EXPORT_GROWTH→lng_export_tailwind, MIDSTREAM_DEFENSIVE_CARRY→midstream_stability,
OIL_DISCIPLINE→oil_oversupply_caution, RENEWABLES_RATE_RELIEF→transition_bond_selectivity,
AVOID_REFINING_WATCHLIST→avoid_watchlist_yield_trap).
Output precision & ordering
notional_usd_m / quantity_usd_m: 1 decimal.
- All
post_trade_metrics and risk_metrics percentages/durations: 2 decimals
(except post_trade_watchlist_exposure_usd_m: 1 decimal).
- BUY-only
trade_package: sort ascending by instrument_id.
- Rotation
rotation.trades: sort SELL before BUY, then instrument_id
ascending within each action.
watchlist_sell_ids: ascending instrument_id order.
- Use only enum values from the template. Always emit the
data_precedence
value when the schema requires it.
4. Workflow B — International equity correlation review
Compute pair correlations across an index universe from monthly index levels,
find extreme pairs, flag concentration, and propose sleeve actions.
SOP
- Read the request payload for
review_window (level_start_date,
level_end_date) and index_universe (list of index ids).
- For each index id,
GET /api/index-levels/<id> → levels list of
{date, level} (monthly).
- Build monthly simple returns: r_t = (level_t − level_{t−1}) / level_{t−1}
for consecutive levels. Number of return observations = (number of levels) − 1.
- Compute Pearson correlation over the common return series for every pair.
pearson(a,b) = Σ((a_k−mean_a)(b_k−mean_b)) / sqrt(Σ(a_k−mean_a)² × Σ(b_k−mean_b)²)
extreme_pairs:
highest_positive: the pair with the maximum correlation.
lowest: the pair with the minimum correlation (most negative).
- Each
pair_id is a 2-element list sorted alphabetically by index id.
correlation rounded to 3 decimals.
review_window: echo level_start_date, level_end_date, and
return_observations (integer = levels − 1).
index_set: the universe ids, ascending alphabetical.
Concentration flags (thresholds from /api/policies → correlation)
correlation_high_threshold (e.g. 0.8), correlation_low_threshold (e.g. 0.2).
high_threshold_breached: true if any pair correlation >= high threshold.
china_asia_dependence_flag: true when China/Asia-Pacific pairs breach the
high threshold (CHINA vs EM, CHINA vs AC_ASIA_PAC_EX_JP, etc.).
primary_code: CHINA_ASIA_DEPENDENCE when China/Asia concentration fires;
GLOBAL_DEVELOPED_OVERLAP when the breach is among developed/global indices
(WORLD/EAFE/etc.); NO_MATERIAL_CONCENTRATION when nothing breaches.
diversification_candidates: indices with the lowest (ideally negative)
correlation to the concentration source, restricted to the template's
allowed set (typically IDX_EM_EX_CHINA, IDX_INDIA, IDX_LATAM),
ascending alphabetical.
Sleeve actions
- Exactly the number required by the template (often 2), ordered ascending by
sleeve.
- Each item:
{sleeve, action, target_index_id}.
action from {trim, add, hold, hedge, monitor, rotate}: trim the
concentrated sleeve (e.g. China), add the best diversifier sleeve.
target_index_id from the template's allowed set.
Precision
- All correlations: 3 decimals. No rounding of level inputs.
5. Workflow C — Cross-asset active allocation view updates
Map macro signals + prior views into a refreshed set of active allocation views
plus a portfolio-level risk overlay.
SOP
- Read the request payload:
target_quarter, prior_quarter, and the
focus opportunity_sets list (output row order = this list's order).
GET /api/allocation/opportunity-sets — map each opportunity set to its
asset_class (Equities / Duration / Credit / Currency).
GET /api/allocation/prior-views — find the prior-quarter view and
conviction for each focus opportunity set (match quarter ==
prior_quarter, or use the row whose previous_quarter precedes the target).
GET /api/macro-signals — for the target quarter, get each focus set's
score, rationale_code, drivers.
GET /api/policies → allocation_mapping for thresholds (and policy_id
= top-level policy_id e.g. POLICY_SET_2026_05).
Deriving each view row (deterministic from the policy)
Using allocation_mapping:
view_score_thresholds: OW_min (e.g. 0.35), UW_max (e.g. −0.35),
neutral in between.
- score >= OW_min →
OW
- score <= UW_max →
UW
- else →
N
conviction from |score|:
- |score| >=
HIGH_abs_min (0.7) → HIGH
- |score| >=
MEDIUM_abs_min (0.35) → MEDIUM
- |score| <
LOW_abs_below (0.35) → LOW
change = compare new view vs prior view using view_rank
(OW=+1, N=0, UW=−1):
- new rank > prior rank →
UP
- new rank < prior rank →
DOWN
- equal →
UNCHANGED
rationale_code: copy directly from the macro-signal's rationale_code.
asset_class: from the opportunity-sets taxonomy.
Row order = the request payload's focus_opportunity_sets order (NOT alphabetised).
Risk overlay
overlay_code / primary_action: choose the pairing that matches the
dominant directional tilt of the views. Pairings:
DURATION_QUALITY_TILT ↔ tilt_to_duration_quality (duration OW + HY/credit risk).
CREDIT_RISK_REDUCTION ↔ trim_credit_beta (credit-spread / HY stress).
EQUITY_BETA_EXTENSION ↔ add_cyclical_equity_beta (broad equity OW).
CURRENCY_DEFENSIVE_HEDGE ↔ add_currency_hedge (defensive currency tilt).
NO_OVERLAY ↔ hold_policy_weights (no material tilt).
rationale_codes: the 2–4 most material risk rationale codes from the view
rows, in business-priority order (highest priority first) — typically
duration/credit/China-dependence drivers ahead of growth/balance ones.
Precision
- No numeric precision needed for views (all enums/strings).
policy_id,
as_of_date, target_quarter, prior_quarter echo the environment/payload.
6. Workflow D — Combined correlation + allocation committee JSON
Blends B and C: link non-US equity correlation findings to active allocation
views for a focused set of opportunity sets (e.g. Emerging Markets, India,
Latin America, USD).
SOP
- Compute correlations exactly as in Workflow B over the requested index set
(often a 4-index subset, e.g. EM/China/India/LatAm).
correlation_summary: exactly 2 items in this order:
pair_role: highest_concentration = the highest-positive-correlation pair.
pair_role: best_diversifier = the lowest (most negative) correlation pair.
- Each
pair is 2 ids sorted alphabetically; correlation to 3 decimals.
target_sleeve_actions: one per requested opportunity set, in the
request's order. Derive action from the resulting view:
UW→trim, OW→add, neutral currency (e.g. USD→N)→hedge when the
sleeve is a defensive currency. Actions from {trim, add, hold, hedge, monitor, rotate}.
allocation_views: one per requested opportunity set in request order.
Each row carries both lineage and derived fields:
prior_view (from prior-views), signal_score (macro score, 3 decimals),
view, change, conviction, rationale_code (derived per Workflow C).
rebalance_trigger: choose the trigger that matches the dominant breach —
correlation_cap_breach when a pair exceeds the correlation high threshold
(0.8); hy_cap_pressure, duration_drift, watchlist_concentration, or
committee_review otherwise.
portfolio_risk_concentration_flag: true when a material concentration/
exception exists (e.g. correlation cap breach or two-or-more material
exceptions).
next_step: choose by severity per the multi-asset risk policy
(committee_escalation_threshold = "two_or_more_material_exceptions"):
- single material exception, otherwise clean →
approve_with_monitoring.
- clean rotation ready →
approve_rotation.
- two+ material exceptions or unresolved breach →
defer_pending_risk_review
(or reject_constraint_breach for a hard constraint breach).
7. Common precision & ordering conventions (quick reference)
| Field |
Precision |
Ordering |
trade_package notional_usd_m |
1 dec |
asc by instrument_id |
rotation.trades quantity_usd_m |
1 dec |
SELL before BUY, then instrument_id asc |
post_trade_metrics (MV, HY%, dur, YTM) |
2 dec |
— |
risk_metrics pct/duration |
2 dec |
— |
post_trade_watchlist_exposure_usd_m |
1 dec |
— |
| correlations (all workflows) |
3 dec |
pair ids alphabetical |
signal_score |
3 dec |
— |
index_set, diversification_candidates |
— |
asc alphabetical |
sleeve_actions |
— |
asc by sleeve |
allocation_views (workflow C) |
— |
request payload focus_opportunity_sets order |
allocation_views (workflow D) |
— |
request payload opportunity-set order |
watchlist_sell_ids |
— |
asc instrument_id |
return_observations |
integer |
— |
Round only at output. Carry full precision through intermediate steps.
8. Common misjudgments to avoid
- Trusting the bond record's
watchlist field. It is often None. JOIN to
/api/issuers and use the issuer watchlist boolean. Watchlist issuers seen
include E&P, telecom, and refining names.
- Buying a watchlist issuer for carry. High-YTM candidates tagged
WATCHLIST_RISK / HIGH_CARRY are traps — exclude them.
- Using stale local quantity/marks.
stale_holding_snapshot,
stale_exception_board, stale_local_note are deliberately stale; use
environment holdings and mark data_precedence accordingly.
- Duration-ineligible distractors. A long-dated IG bond with duration > band
upper bound (e.g. 6.7y) looks safe (IG, good ytm) but breaks the band; the
portfolio weighted duration must stay in band, so avoid tickets that push it out.
- Ignoring the HY cap. Adding HY carry can breach
max_hy_allocation_pct.
Recompute HY% post-trade (sum HY MV / total MV).
- Alphabetising allocation rows. Workflow C/D rows keep the request payload's
opportunity-set order, not alphabetical.
- Pair id order. Always sort the two index ids alphabetically inside a pair.
- Correlation source. Use monthly simple returns of consecutive levels,
not levels themselves, not log returns unless told. return_observations =
levels − 1.
- Conviction vs view thresholds differ. View uses signed thresholds
(±0.35); conviction uses absolute thresholds (0.35 / 0.7). A score of 0.34 is
N view but LOW conviction; 0.36 is OW view, MEDIUM conviction.
change is view-vs-prior, not score-vs-prior. Compare the new view rank
to the prior view rank (OW/N/UW), not the numeric score.
- Forgetting
target_hy_reduction_pct. Risk-reduction portfolios carry a
policy target_hy_reduction_pct (e.g. 4.0); meet or exceed it (in pct points)
and set target_hy_reduction_met.
- Output contract drift. Always conform to the specific task's
answer_template.json — schema, required keys, enum values, and ordering vary
between BUY-only and rotation variants and between correlation variants.
9. Policy conventions (from /api/policies)
POL_CREDIT_DEFAULT: HY cap (e.g. 20%), duration band [3.0, 5.0], issuer
concentration limit (e.g. 12%), subsector_min_count_for_diversified 2,
target_hy_reduction_pct 0.
POL_CREDIT_RISK_REDUCTION: same but target_hy_reduction_pct 4.0 — used by
risk-reduction rotation portfolios.
POL_CORRELATION_DEFAULT: correlation_high_threshold 0.8,
correlation_low_threshold 0.2, review window start/end dates.
POL_ALLOCATION_MAPPING: view-score thresholds (OW>=0.35, UW<=−0.35, neutral
between), conviction thresholds (HIGH>=0.7, MEDIUM>=0.35, LOW<0.35),
view_rank (OW=1, N=0, UW=−1).
POL_MULTI_ASSET_DEFAULT / POL_MULTI_ASSET_RISK: composition policies; the
risk variant uses committee_escalation_threshold = "two_or_more_material_exceptions".
- Top-level
policy_id (e.g. POLICY_SET_2026_05) is the value to echo in
allocation answers' policy_id field.
A pass/fail boolean is true iff the post-trade (or post-review) state satisfies
the corresponding policy threshold with the environment's authoritative data.
10. Final checks before submitting
- Did I fetch every needed endpoint live (portfolio, bonds, issuers, market/energy
or index-levels, macro-signals, prior-views, policies, opportunity-sets)?
- Did I use environment values over stale local payloads, and set
data_precedence when the schema asks?
- Are all numeric fields rounded to the template-declared precision?
- Are all lists ordered per the template (instrument_id asc; SELL-before-BUY;
alphabetical index ids/pairs; request-payload order for allocation rows)?
- Are all enum values from the template's allowed sets?
- Does the JSON conform exactly to
answer_template.json required keys and
top-level shape? Return only the JSON object unless told otherwise.
1---2name: fewshot-attempt-03-73description: Asteria Investment Office — Risk Evaluation Skill4---5# Asteria Investment Office — Risk Evaluation Skill67Transferable execution knowledge for the Asteria institutional portfolio-risk8tasks. The test task will give you one portfolio, a local request payload9(`input/payloads/`), an `answer_template.json` output contract, and access to10the same remote Asteria environment described below. You have NOT seen the11train tasks; this file is your playbook.1213The tasks draw from three workflows:14- **A. Energy / fixed-income trade strategy** (bond ticket selection + post-trade metrics).15- **B. International equity correlation review** (compute correlations from index levels).16- **C. Cross-asset active allocation view updates** (map macro signals to views).17- A fourth shape **D** blends B + C into one committee JSON.1819---2021## 1. Environment access (the book of record)2223Base URL: `<remote-env-url>`2425The remote environment is the **current book of record**. Local payloads in26`input/payloads/` are intake context and are frequently **stale** (old worksheet27dates, stale marks, stale quantities). When a local payload conflicts with the28environment, prefer the environment unless the task prompt explicitly says29otherwise.3031Call with `curl` (Bash) or Python `urllib`/`requests`. Always re-fetch live data32per task — do not assume cached shapes.3334### Endpoints that matter3536| Endpoint | Use |37|---|---|38| `GET /api/catalog` | All portfolio ids, policy ids, index ids, issuer ids, bond ids, opportunity sets. |39| `GET /api/policies` | Constraints: HY cap, duration band, diversification rules, correlation thresholds, allocation-mapping thresholds. |40| `GET /api/portfolios` | Portfolio summaries. |41| `GET /api/portfolios/<id>` | Objective, constraints (incl. `policy_id`, `max_hy_allocation_pct`, `duration_band_years`, optional `target_hy_reduction_pct`), current holdings, `market_value_usd_m`, `as_of_date`. |42| `GET /api/instruments/bonds` | Held + candidate bond universe. Filters: `?candidate=true`, `?rating_bucket=HY`. |43| `GET /api/issuers` | Issuer `sector`, `subsector`, `rating_bucket`, `watchlist` (bool), `credit_outlook`, `research_tags`. **Authoritative watchlist source.** |44| `GET /api/market/energy` | Energy commodity signals (scores, directions) and `pitch_themes` for sale positioning. |45| `GET /api/indices` | Index metadata (region, currency, `level_start_date`, `level_end_date`, frequency). |46| `GET /api/index-levels` / `GET /api/index-levels/<id>` | Monthly index levels `{date, level}`. **No correlations are precomputed — compute them yourself.** |47| `GET /api/allocation/opportunity-sets` | Cross-asset taxonomy: `opportunity_set`, `asset_class`, `display_order`. |48| `GET /api/allocation/prior-views` | Prior-quarter active views (`view`, `conviction`, `quarter`, `previous_quarter`). |49| `GET /api/macro-signals` | Per opportunity-set `score`, `rationale_code`, `drivers`, `quarter`. Drives view/conviction/rationale. |5051Most list endpoints accept simple equality filters matching field names52(`?rating_bucket=HY`, `?candidate=true`, `?quarter=Q3_2026`).5354### Environment rules55- Use only the listed endpoints. Do not read source/data files or any local `env/` directory.56- Numeric precision in the final answer must follow `input/payloads/answer_template.json` per field.57- The environment `as_of_date` (e.g. `2026-05-29`) is the as-of date for answers unless the task gives another.5859---6061## 2. Data-precedence rule (applies to every workflow)62631. Read the local request payload to learn the *ask* (which portfolio, which64 window, which opportunity sets, ticket count, totals, preferences).652. Read the **environment** for the actual current values used in computation.663. If a stale local snapshot (worksheet date, stale marks, stale quantities,67 `stale_exception_board`, `stale_local_note`) conflicts with the environment,68 compute from the environment and emit `data_precedence =69 current_environment_over_stale_payload`.704. If no stale-conflict exists, emit `no_conflict_found`.715. `local_payload_over_current_environment` is essentially never correct unless72 the task prompt explicitly overrides.7374Where the answer schema has a `data_precedence` field, set it accordingly.75Where it does not (e.g. workflow B/C/D), still apply the rule silently in your76computations.7778---7980## 3. Workflow A — Energy / fixed-income trade strategy8182Tasks ask for a proposed set of BUY (and in rotation variants SELL+BUY) bond83tickets that improve carry while keeping the portfolio inside credit-risk84constraints, plus post-trade metrics, constraint booleans, sales positioning,85and a data-precedence declaration. Output shape follows the task's own86`answer_template.json`; two variants were seen:8788- **BUY-only variant:** `trade_package` (list of `{action,instrument_id,notional_usd_m}`),89 `post_trade_metrics`, `constraint_checks`, `sales_positioning`, `data_precedence`.90- **Rotation variant:** `rotation.trades` (list of `{action,instrument_id,quantity_usd_m}`),91 `risk_metrics`, `exception_flags`, `watchlist_handling`, `risk_note_code`.9293### SOP941. `GET /api/portfolios/<id>` — read `constraints`, `holdings`, `market_value_usd_m`.952. `GET /api/instruments/bonds?candidate=true` — candidate universe.963. `GET /api/issuers` — join each candidate's `issuer_id` to get `watchlist`,97 `sector`, `subsector`, `rating_bucket`. (The bonds endpoint `watchlist` field98 is unreliable/None — use issuers.)994. `GET /api/market/energy` — for energy tasks, read signals + `pitch_themes`.1005. `GET /api/policies` — confirm HY cap, duration band, diversification, and101 any `target_hy_reduction_pct`.102103### Candidate selection rules104- Only buy bonds with `candidate=true`.105- For an **energy** desk task, require `energy_linked=true` and match desk106 preferences (e.g. LNG exporters / gas demand). For a general credit rotation,107 non-energy IG candidates (data centers, mining, REIT, banking, utility) are108 eligible.109- **Exclude watchlist issuers** (`watchlist=true`) from BUYs. Identify them via110 `/api/issuers`, not the bond record.111- Exclude duration-ineligible distractors: a single bond whose112 `modified_duration_years` is far outside the band is a distractor even if113 attractive on yield; the *portfolio-weighted* duration must stay in band, but114 avoid buying bonds that would push it out.115- Honor the ticket count, total notional, and split rules from the request116 payload exactly (e.g. "exactly two BUY tickets totaling USD 8.0M split evenly"117 → two tickets of 4.0M each).118- Prefer candidates that **improve carry** (higher `yield_to_maturity_pct`)119 while keeping HY% under the cap and duration in band.120- BUYs must be **diversified**: distinct issuers and distinct subsectors among121 the selected tickets (this drives `selected_issuer_diversification_pass` and122 `selected_subsector_diversification_pass`; policy `subsector_min_count_for_diversified`123 is typically 2 distinct subsectors among what you add).124125### Rotation (SELL+BUY) rules126- Identify current holdings on watchlist issuers and SELL them first127 (`watchlist_sell_ids`).128- SELL enough HY to meet the portfolio's `target_hy_reduction_pct` (percentage129 points) — you may overshoot the target when also clearing watchlist risk.130- BUY IG (or otherwise eligible) candidates that fund the SELL notional, keep131 weighted duration inside the band, and avoid watchlist issuers.132- Use **environment** holding quantities for SELL sizes, not stale local133 `stale_exception_board` quantities.134135### Post-trade metric computation (market-value-weighted)136Treat each holding's `quantity_usd_m` as its market value (par approximation;137no separate clean-price field is provided). After applying trades:138139- `total_market_value_usd_m` = sum of all post-trade holding quantities (for140 BUY-only funded by new allocation, add the bought notionals to the starting141 `market_value_usd_m`; for a balanced rotation, total MV is unchanged).142- `hy_allocation_pct` = (sum of post-trade HY holding market values) / total MV × 100.143- `weighted_modified_duration_years` = Σ(qty_i × `modified_duration_years_i`) / total MV.144- `weighted_yield_to_maturity_pct` = Σ(qty_i × `yield_to_maturity_pct_i`) / total MV.145- A holding is HY iff its issuer/bond `rating_bucket == "HY"` (or `rating` startswith HY).146147Verify each bond's `modified_duration_years` and `yield_to_maturity_pct` from148`/api/instruments/bonds` (match by `instrument_id`).149150### Constraint checks (booleans)151Read thresholds from `/api/policies` (policy_id from portfolio constraints):152- `hy_cap_pass`: post-trade `hy_allocation_pct` <= `max_hy_allocation_pct` (e.g. 20.0).153- `duration_band_pass`: post-trade weighted duration within `duration_band_years` (e.g. [3.0, 5.0]) inclusive.154- `selected_issuer_diversification_pass`: selected BUY tickets have >=2 distinct issuers.155- `selected_subsector_diversification_pass`: selected BUY tickets have >=2 distinct subsectors.156- `watchlist_avoidance_pass`: none of the BUYs is a watchlist issuer (`watchlist_avoidance_pass`); for rotation also `watchlist_exposure_cleared` (post-trade watchlist MV == 0) and `buys_avoid_watchlist`.157- Rotation adds `target_hy_reduction_met`: actual HY reduction (ppt) >= `target_hy_reduction_pct`.158159### Sales positioning (energy BUY-only variant)160- `target_segment`: derive from the request's client context (e.g.161 "multi-asset income update" → `multi_asset_income`). Allowed values are in162 the template.163- `theme`: pick the `pitch_theme` from `/api/market/energy` that matches the164 dominant positive energy signal backing the trade (e.g. LNG export growth →165 `lng_export_tailwind`). Map energy `pitch_themes` to the template's theme166 enum (LNG_EXPORT_GROWTH→`lng_export_tailwind`, MIDSTREAM_DEFENSIVE_CARRY→`midstream_stability`,167 OIL_DISCIPLINE→`oil_oversupply_caution`, RENEWABLES_RATE_RELIEF→`transition_bond_selectivity`,168 AVOID_REFINING_WATCHLIST→`avoid_watchlist_yield_trap`).169170### Output precision & ordering171- `notional_usd_m` / `quantity_usd_m`: 1 decimal.172- All `post_trade_metrics` and `risk_metrics` percentages/durations: 2 decimals173 (except `post_trade_watchlist_exposure_usd_m`: 1 decimal).174- BUY-only `trade_package`: sort **ascending by `instrument_id`**.175- Rotation `rotation.trades`: sort **SELL before BUY, then `instrument_id`176 ascending within each action**.177- `watchlist_sell_ids`: ascending `instrument_id` order.178- Use only enum values from the template. Always emit the `data_precedence`179 value when the schema requires it.180181---182183## 4. Workflow B — International equity correlation review184185Compute pair correlations across an index universe from monthly index levels,186find extreme pairs, flag concentration, and propose sleeve actions.187188### SOP1891. Read the request payload for `review_window` (`level_start_date`,190 `level_end_date`) and `index_universe` (list of index ids).1912. For each index id, `GET /api/index-levels/<id>` → `levels` list of192 `{date, level}` (monthly).1933. Build **monthly simple returns**: r_t = (level_t − level_{t−1}) / level_{t−1}194 for consecutive levels. Number of return observations = (number of levels) − 1.1954. Compute **Pearson correlation** over the common return series for every pair.196197```198pearson(a,b) = Σ((a_k−mean_a)(b_k−mean_b)) / sqrt(Σ(a_k−mean_a)² × Σ(b_k−mean_b)²)199```2002015. `extreme_pairs`:202 - `highest_positive`: the pair with the maximum correlation.203 - `lowest`: the pair with the minimum correlation (most negative).204 - Each `pair_id` is a 2-element list **sorted alphabetically** by index id.205 - `correlation` rounded to **3 decimals**.2066. `review_window`: echo `level_start_date`, `level_end_date`, and207 `return_observations` (integer = levels − 1).2087. `index_set`: the universe ids, **ascending alphabetical**.209210### Concentration flags (thresholds from `/api/policies` → `correlation`)211- `correlation_high_threshold` (e.g. 0.8), `correlation_low_threshold` (e.g. 0.2).212- `high_threshold_breached`: true if any pair correlation >= high threshold.213- `china_asia_dependence_flag`: true when China/Asia-Pacific pairs breach the214 high threshold (CHINA vs EM, CHINA vs AC_ASIA_PAC_EX_JP, etc.). 215- `primary_code`: `CHINA_ASIA_DEPENDENCE` when China/Asia concentration fires;216 `GLOBAL_DEVELOPED_OVERLAP` when the breach is among developed/global indices217 (WORLD/EAFE/etc.); `NO_MATERIAL_CONCENTRATION` when nothing breaches.218- `diversification_candidates`: indices with the lowest (ideally negative)219 correlation to the concentration source, restricted to the template's220 allowed set (typically `IDX_EM_EX_CHINA`, `IDX_INDIA`, `IDX_LATAM`),221 **ascending alphabetical**.222223### Sleeve actions224- Exactly the number required by the template (often 2), **ordered ascending by225 `sleeve`**.226- Each item: `{sleeve, action, target_index_id}`.227- `action` from `{trim, add, hold, hedge, monitor, rotate}`: `trim` the228 concentrated sleeve (e.g. China), `add` the best diversifier sleeve.229- `target_index_id` from the template's allowed set.230231### Precision232- All correlations: **3 decimals**. No rounding of level inputs.233234---235236## 5. Workflow C — Cross-asset active allocation view updates237238Map macro signals + prior views into a refreshed set of active allocation views239plus a portfolio-level risk overlay.240241### SOP2421. Read the request payload: `target_quarter`, `prior_quarter`, and the243 focus `opportunity_sets` list (output row order = this list's order).2442. `GET /api/allocation/opportunity-sets` — map each opportunity set to its245 `asset_class` (Equities / Duration / Credit / Currency).2463. `GET /api/allocation/prior-views` — find the prior-quarter view and247 conviction for each focus opportunity set (match `quarter` ==248 `prior_quarter`, or use the row whose `previous_quarter` precedes the target).2494. `GET /api/macro-signals` — for the target quarter, get each focus set's250 `score`, `rationale_code`, `drivers`.2515. `GET /api/policies` → `allocation_mapping` for thresholds (and `policy_id`252 = top-level `policy_id` e.g. `POLICY_SET_2026_05`).253254### Deriving each view row (deterministic from the policy)255Using `allocation_mapping`:256- `view_score_thresholds`: `OW_min` (e.g. 0.35), `UW_max` (e.g. −0.35),257 neutral in between.258 - score >= OW_min → `OW`259 - score <= UW_max → `UW`260 - else → `N`261- `conviction` from `|score|`:262 - |score| >= `HIGH_abs_min` (0.7) → `HIGH`263 - |score| >= `MEDIUM_abs_min` (0.35) → `MEDIUM`264 - |score| < `LOW_abs_below` (0.35) → `LOW`265- `change` = compare new view vs prior view using `view_rank`266 (OW=+1, N=0, UW=−1):267 - new rank > prior rank → `UP`268 - new rank < prior rank → `DOWN`269 - equal → `UNCHANGED`270- `rationale_code`: copy directly from the macro-signal's `rationale_code`.271- `asset_class`: from the opportunity-sets taxonomy.272273Row order = the request payload's `focus_opportunity_sets` order (NOT alphabetised).274275### Risk overlay276- `overlay_code` / `primary_action`: choose the pairing that matches the277 dominant directional tilt of the views. Pairings:278 - `DURATION_QUALITY_TILT` ↔ `tilt_to_duration_quality` (duration OW + HY/credit risk).279 - `CREDIT_RISK_REDUCTION` ↔ `trim_credit_beta` (credit-spread / HY stress).280 - `EQUITY_BETA_EXTENSION` ↔ `add_cyclical_equity_beta` (broad equity OW).281 - `CURRENCY_DEFENSIVE_HEDGE` ↔ `add_currency_hedge` (defensive currency tilt).282 - `NO_OVERLAY` ↔ `hold_policy_weights` (no material tilt).283- `rationale_codes`: the 2–4 most material risk rationale codes from the view284 rows, in **business-priority order (highest priority first)** — typically285 duration/credit/China-dependence drivers ahead of growth/balance ones.286287### Precision288- No numeric precision needed for views (all enums/strings). `policy_id`,289 `as_of_date`, `target_quarter`, `prior_quarter` echo the environment/payload.290291---292293## 6. Workflow D — Combined correlation + allocation committee JSON294295Blends B and C: link non-US equity correlation findings to active allocation296views for a focused set of opportunity sets (e.g. Emerging Markets, India,297Latin America, USD).298299### SOP3001. Compute correlations exactly as in Workflow B over the requested index set301 (often a 4-index subset, e.g. EM/China/India/LatAm).3022. `correlation_summary`: exactly 2 items in this order:303 - `pair_role: highest_concentration` = the highest-positive-correlation pair.304 - `pair_role: best_diversifier` = the lowest (most negative) correlation pair.305 - Each `pair` is 2 ids **sorted alphabetically**; `correlation` to 3 decimals.3063. `target_sleeve_actions`: one per requested opportunity set, in the307 request's order. Derive `action` from the resulting view:308 `UW`→`trim`, `OW`→`add`, neutral currency (e.g. USD→`N`)→`hedge` when the309 sleeve is a defensive currency. Actions from `{trim, add, hold, hedge, monitor, rotate}`.3104. `allocation_views`: one per requested opportunity set in request order.311 Each row carries **both** lineage and derived fields:312 - `prior_view` (from prior-views), `signal_score` (macro score, **3 decimals**),313 `view`, `change`, `conviction`, `rationale_code` (derived per Workflow C).3145. `rebalance_trigger`: choose the trigger that matches the dominant breach —315 `correlation_cap_breach` when a pair exceeds the correlation high threshold316 (0.8); `hy_cap_pressure`, `duration_drift`, `watchlist_concentration`, or317 `committee_review` otherwise.3186. `portfolio_risk_concentration_flag`: true when a material concentration/319 exception exists (e.g. correlation cap breach or two-or-more material320 exceptions).3217. `next_step`: choose by severity per the multi-asset risk policy322 (`committee_escalation_threshold` = "two_or_more_material_exceptions"):323 - single material exception, otherwise clean → `approve_with_monitoring`.324 - clean rotation ready → `approve_rotation`.325 - two+ material exceptions or unresolved breach → `defer_pending_risk_review`326 (or `reject_constraint_breach` for a hard constraint breach).327328---329330## 7. Common precision & ordering conventions (quick reference)331332| Field | Precision | Ordering |333|---|---|---|334| `trade_package` `notional_usd_m` | 1 dec | asc by `instrument_id` |335| `rotation.trades` `quantity_usd_m` | 1 dec | SELL before BUY, then `instrument_id` asc |336| `post_trade_metrics` (MV, HY%, dur, YTM) | 2 dec | — |337| `risk_metrics` pct/duration | 2 dec | — |338| `post_trade_watchlist_exposure_usd_m` | 1 dec | — |339| correlations (all workflows) | 3 dec | pair ids alphabetical |340| `signal_score` | 3 dec | — |341| `index_set`, `diversification_candidates` | — | asc alphabetical |342| `sleeve_actions` | — | asc by `sleeve` |343| `allocation_views` (workflow C) | — | request payload `focus_opportunity_sets` order |344| `allocation_views` (workflow D) | — | request payload opportunity-set order |345| `watchlist_sell_ids` | — | asc `instrument_id` |346| `return_observations` | integer | — |347348Round only at output. Carry full precision through intermediate steps.349350---351352## 8. Common misjudgments to avoid353354- **Trusting the bond record's `watchlist` field.** It is often `None`. JOIN to355 `/api/issuers` and use the issuer `watchlist` boolean. Watchlist issuers seen356 include E&P, telecom, and refining names.357- **Buying a watchlist issuer for carry.** High-YTM candidates tagged358 `WATCHLIST_RISK` / `HIGH_CARRY` are traps — exclude them.359- **Using stale local quantity/marks.** `stale_holding_snapshot`,360 `stale_exception_board`, `stale_local_note` are deliberately stale; use361 environment holdings and mark `data_precedence` accordingly.362- **Duration-ineligible distractors.** A long-dated IG bond with duration > band363 upper bound (e.g. 6.7y) looks safe (IG, good ytm) but breaks the band; the364 portfolio *weighted* duration must stay in band, so avoid tickets that push it out.365- **Ignoring the HY cap.** Adding HY carry can breach `max_hy_allocation_pct`.366 Recompute HY% post-trade (sum HY MV / total MV).367- **Alphabetising allocation rows.** Workflow C/D rows keep the request payload's368 opportunity-set order, not alphabetical.369- **Pair id order.** Always sort the two index ids alphabetically inside a pair.370- **Correlation source.** Use monthly **simple returns** of consecutive levels,371 not levels themselves, not log returns unless told. return_observations =372 levels − 1.373- **Conviction vs view thresholds differ.** View uses signed thresholds374 (±0.35); conviction uses absolute thresholds (0.35 / 0.7). A score of 0.34 is375 `N` view but `LOW` conviction; 0.36 is `OW` view, `MEDIUM` conviction.376- **`change` is view-vs-prior, not score-vs-prior.** Compare the new view rank377 to the prior view rank (OW/N/UW), not the numeric score.378- **Forgetting `target_hy_reduction_pct`.** Risk-reduction portfolios carry a379 policy `target_hy_reduction_pct` (e.g. 4.0); meet or exceed it (in pct points)380 and set `target_hy_reduction_met`.381- **Output contract drift.** Always conform to the specific task's382 `answer_template.json` — schema, required keys, enum values, and ordering vary383 between BUY-only and rotation variants and between correlation variants.384385---386387## 9. Policy conventions (from `/api/policies`)388389- `POL_CREDIT_DEFAULT`: HY cap (e.g. 20%), duration band [3.0, 5.0], issuer390 concentration limit (e.g. 12%), `subsector_min_count_for_diversified` 2,391 `target_hy_reduction_pct` 0.392- `POL_CREDIT_RISK_REDUCTION`: same but `target_hy_reduction_pct` 4.0 — used by393 risk-reduction rotation portfolios.394- `POL_CORRELATION_DEFAULT`: `correlation_high_threshold` 0.8,395 `correlation_low_threshold` 0.2, review window start/end dates.396- `POL_ALLOCATION_MAPPING`: view-score thresholds (OW>=0.35, UW<=−0.35, neutral397 between), conviction thresholds (HIGH>=0.7, MEDIUM>=0.35, LOW<0.35),398 `view_rank` (OW=1, N=0, UW=−1).399- `POL_MULTI_ASSET_DEFAULT` / `POL_MULTI_ASSET_RISK`: composition policies; the400 risk variant uses `committee_escalation_threshold = "two_or_more_material_exceptions"`.401- Top-level `policy_id` (e.g. `POLICY_SET_2026_05`) is the value to echo in402 allocation answers' `policy_id` field.403404A pass/fail boolean is `true` iff the post-trade (or post-review) state satisfies405the corresponding policy threshold with the environment's authoritative data.406407---408409## 10. Final checks before submitting4104111. Did I fetch every needed endpoint live (portfolio, bonds, issuers, market/energy412 or index-levels, macro-signals, prior-views, policies, opportunity-sets)?4132. Did I use environment values over stale local payloads, and set414 `data_precedence` when the schema asks?4153. Are all numeric fields rounded to the template-declared precision?4164. Are all lists ordered per the template (instrument_id asc; SELL-before-BUY;417 alphabetical index ids/pairs; request-payload order for allocation rows)?4185. Are all enum values from the template's allowed sets?4196. Does the JSON conform exactly to `answer_template.json` required keys and420 top-level shape? Return only the JSON object unless told otherwise.