| name | asteria-investment-office-json |
| description | Produce strict-JSON deliverables for the Asteria Investment Office (CIO / credit desk) task family: energy/fixed-income credit trade packages, international equity correlation reviews, cross-asset active-allocation view refreshes, fixed-income risk-rebalance rotations, and combined committee decision files. Use this whenever a prompt references an Asteria portfolio (PF-*), the shared Asteria HTTP/JSON environment, an answer_template.json output contract, and asks for trade packages, correlation/concentration findings, allocation views (UW/N/OW), or rotation/rebalance JSON. Covers which API endpoints to call, exact formulas/rounding, controlled-enum mapping, and current-environment-over-stale-payload precedence. |
Asteria Investment Office — Strict-JSON Task Solver
You answer institutional investment-office tasks that each return ONE strict JSON
object matching a provided answer_template.json. The shared Asteria environment
is a read-only HTTP/JSON API and is the single source of truth (book of record).
A local payload (desk_request / review_request / allocation_request / risk memo /
committee packet) provides intake context that is often stale; whenever it
disagrees with the environment on marks, ratings, holdings, quantities, watchlist
status, dates, prior views, or policy values, use the environment.
0. Golden rules (apply to every task)
- Read the
answer_template.json first. It defines the exact required keys,
list lengths, item ordering, enum allowed-values, and per-field numeric precision.
Emit exactly those keys with values in the declared enums. Return only the JSON
object — no prose, no markdown fences, no extra keys.
- Environment overrides stale payload. Pull current holdings, quantities,
ratings, watchlist flags, prior views, signal scores, policy thresholds, and the
as_of_date from the API — never from the local payload.
as_of_date (and policy_id, target/prior quarter) come from the environment.
The current env as_of_date is the value returned by /api/policies,
/api/portfolios/<id>, and most endpoints (they agree). Use that date, not the
payload's request_date / memo_as_of_date / committee_date.
- Round per field. Apply the precision the template states for each field
(
precision: N = N decimals). Round at the end, after computing in full precision.
Notionals are usually 1 decimal; percentages and durations 2 decimals; correlations
and signal scores 3 decimals. A whole number stays valid at its precision (e.g. 4.0,
68.0, 5.8).
- Honor ordering rules. "Sort ascending by instrument_id", "SELL before BUY then
instrument_id ascending", "item_order: [...]" (fixed business order), "ascending
alphabetical by index id", "rows in payload focus order". Follow the template's
exact instruction; do not re-sort against it.
- Pairs/lists of index ids are sorted alphabetically inside each pair.
1. Environment API quick reference (base http://127.0.0.1:8036, all GET/JSON)
| Endpoint | Use it for |
|---|
/api/catalog | All valid ids (portfolio, policy, index, issuer, bond, opportunity_set). |
/api/policies | Constraint thresholds, correlation thresholds, allocation-mapping thresholds, current as_of_date, and the global policy_id (e.g. POLICY_SET_2026_05). |
/api/portfolios | All portfolio summaries (name, market_value, holding_count, constraint_policy_id). |
/api/portfolios/<id> | One portfolio: objective, constraints (with its policy_id), and current holdings (instrument_id, quantity_usd_m, sleeve). |
/api/instruments/bonds | Bond master: rating_bucket (IG/HY), modified_duration_years, yield_to_maturity_pct, coupon_pct, spread_bps, subsector, sector, issuer_id, energy_linked, candidate, recommended_theme_tags. Filters: ?candidate=true, ?rating_bucket=HY, ?issuer_id=.... |
/api/issuers | Issuer watchlist (bool), credit_outlook, rating_bucket, sector/subsector, research_tags. Watchlist lives on the ISSUER, not the bond. |
/api/market/energy | Current energy signals (each with score and direction), pitch_themes. Drives credit theme selection. |
/api/indices | Index metadata, level_start_date/level_end_date, region. |
/api/index-levels / /api/index-levels/<id> | Monthly index levels (date, level) for correlations. |
Always resolve the bond -> issuer chain: bond.issuer_id -> issuers entry ->
watchlist. A bond is "watchlist" iff its issuer's watchlist is true.
2. Core formulas (verified against the data model)
Notional weighting. All portfolio metrics weight by quantity_usd_m (notional /
market value in USD millions). Treat quantity_usd_m as the market value.
total_market_value_usd_m = Σ quantity_usd_m over the post-trade book.
hy_allocation_pct = 100 * (Σ quantity of bonds with rating_bucket=="HY") / total.
weighted_modified_duration_years = Σ(modified_duration_years_i * q_i) / Σ q_i.
weighted_yield_to_maturity_pct = Σ(yield_to_maturity_pct_i * q_i) / Σ q_i.
watchlist_exposure_usd_m = Σ quantity of bonds whose issuer.watchlist is true.
hy_reduction_pct_points = pre_trade_hy_pct − post_trade_hy_pct (positive = reduced).
issuer_concentration_pct (one issuer) = 100 * Σ quantity for that issuer / total.
Post-trade book construction. Start from the current env holdings.
SELL subtracts quantity_usd_m; BUY adds it. Drop positions that reach ~0. Pre-trade
metrics use env holdings as-is.
Correlations (Pearson on monthly simple returns).
- Take each index's monthly levels within
[level_start_date, level_end_date]
inclusive (sorted by date). With 12 monthly levels you get 11 returns;
return_observations = (#levels in window − 1).
- Simple return
r_t = level_t / level_{t-1} − 1.
- Pearson
corr(X,Y) = cov(X,Y) / (std(X)*std(Y)) (population or sample — they cancel).
- Round correlation to 3 decimals.
- Compute correlations only across the requested index universe/subset.
Allocation view mapping (thresholds from /api/policies.allocation_mapping):
- Inputs per opportunity_set: macro
score (for target quarter) and prior view.
view: score >= OW_min(0.35) -> OW; score <= UW_max(-0.35) -> UW; else N.
conviction (by abs(score)): >= HIGH_abs_min(0.7) -> HIGH;
>= MEDIUM_abs_min(0.35) -> MEDIUM; < LOW_abs_below(0.35) -> LOW.
change vs prior using view_rank {UW:-1, N:0, OW:1}: new rank > prior -> UP;
new < prior -> DOWN; equal -> UNCHANGED.
rationale_code = the macro signal's rationale_code for that opportunity_set/quarter
(use it verbatim from the API; do not invent).
signal_score field (when required) = the raw macro score (3 decimals; trailing
zeros may drop, e.g. 0.48).
Read these thresholds from /api/policies each run rather than hardcoding; the
values above are the current defaults but the policy object is authoritative.
3. Quarter / prior-view lookup (critical, easy to get wrong)
For a target quarter (e.g. Q2_2026):
- macro signal: row where
opportunity_set matches AND quarter == target_quarter.
- prior view: row in
/api/allocation/prior-views where opportunity_set matches
AND quarter == target_quarter (that row's view/conviction is the standing
prior view; its previous_quarter equals the prior quarter). Do NOT filter prior-views
by quarter == prior_quarter.
4. SOPs by task type
Identify the task from the prompt + which payload + which template keys are present.
4A. Credit trade package — BUY tickets ("trade strategy", trade_package)
Template signals: trade_package, post_trade_metrics, constraint_checks,
sales_positioning, data_precedence.
GET /api/portfolios/<id> (current holdings + constraints), /api/instruments/bonds,
/api/issuers, /api/market/energy, /api/policies.
- Read the ticket constraints from the prompt/payload: ticket_count, total notional,
even split per ticket (e.g. 2 tickets / USD 8.0m -> 4.0 each), allowed actions (BUY).
- Eligible universe:
candidate == true, matching the requested sleeve/theme
(e.g. energy_linked == true for an energy sleeve), not already held.
- Hard filters (must all hold for selected buys):
- Watchlist avoidance: exclude any bond whose issuer
watchlist == true.
- Post-trade
hy_allocation_pct <= max_hy_allocation_pct (cap, default 20).
- Post-trade
weighted_modified_duration_years within duration_band_years [3.0,5.0].
- Selected-ticket diversification: the chosen buys span >=2 distinct issuers and
>= subsector_min_count_for_diversified (2) distinct subsectors.
- Selection priority among constraint-passing pairs (this is NOT pure carry-max):
(a) thematic fit to the dominant current energy signal (the highest-
score
signal — currently LNG/LNG_EXPORT_PULL ~0.72) and the desk's stated
preferred_exposures; (b) quality for a client income pitch — prefer at least one
IG anchor and keep HY comfortably under the cap; (c) then maximize carry (YTM).
A higher-carry pair that is lower quality / off-theme loses to an on-theme IG-anchored
pair that still adds carry.
trade_package: list of the selected BUYs, notional_usd_m per ticket, sorted
ascending by instrument_id.
post_trade_metrics: compute on env holdings + buys (Section 2 formulas; precision
per template — typically total/HY/duration/YTM at 2 decimals, but follow the field).
constraint_checks (booleans): hy_cap_pass, duration_band_pass,
selected_issuer_diversification_pass (buys from distinct issuers),
selected_subsector_diversification_pass (buys span >=2 subsectors),
watchlist_avoidance_pass (no selected buy issuer is watchlisted). Note the
"selected_*" checks judge the , not the whole legacy book (the
book may already exceed issuer concentration and that does not fail these flags).
4B. Fixed-income risk rebalance — rotation ("reduce HY / watchlist", rotation)
Template signals: rotation.trades, risk_metrics, exception_flags,
watchlist_handling, risk_note_code.
GET /api/portfolios/<id>, /api/instruments/bonds, /api/issuers, /api/policies.
Use the portfolio's constraint_policy_id (e.g. POL_CREDIT_RISK_REDUCTION, which
carries target_hy_reduction_pct, default 4.0).
- SELL side: target the HY and watchlist pressure points among current holdings.
- Sell ALL of every watchlisted holding (clears watchlist exposure to 0).
- Sell additional non-watchlist HY holdings as needed to meet the target HY
reduction (>=
target_hy_reduction_pct and any payload minimum_preferred_hy_reduction)
while keeping duration in band — prefer selling shorter-duration HY so post-trade
duration stays inside [3.0,5.0] and does not undershoot.
- BUY side: only from eligible candidates (
candidate == true) that are
NOT watchlisted (reject any watchlist candidate, e.g. a high-carry but watchlisted
name — record it as avoided). Prefer IG names that preserve/lift duration and carry.
- Cash-neutral: total BUY notional == total SELL notional (keeps total MV constant)
unless the prompt says otherwise.
rotation.trades: SELL rows before BUY rows; within each action sort instrument_id
ascending; quantity_usd_m at 1 decimal.
risk_metrics: post_trade_hy_allocation_pct (2dp), post_trade_duration_years
(2dp), hy_reduction_pct_points = pre − post (2dp),
post_trade_watchlist_exposure_usd_m (1dp, normally 0.0).
exception_flags (bool): hy_cap_pass (post HY <= cap), duration_band_pass
(post duration in band), target_hy_reduction_met (reduction >= target),
watchlist_exposure_cleared (post watchlist exposure == 0).
watchlist_handling: watchlist_sell_ids = sold instruments whose issuer is
watchlisted (ascending); buys_avoid_watchlist = true if no bought issuer is watchlisted.
risk_note_code: choose the dominant resolved risk — a watchlisted-issuer
concentration that the rotation clears -> watchlist_concentration; HY cap being the
binding pressure -> ; duration kept/preserved as the headline ->
; a carry-vs-risk tradeoff -> ; no trade ->
.
4C. International equity correlation review (extreme_pairs / concentration)
Template signals: review_window, index_set, extreme_pairs, concentration,
diversification_candidates, sleeve_actions.
GET /api/portfolios/<id> (held sleeves), /api/policies (correlation thresholds:
high 0.8, low 0.2), /api/indices, /api/index-levels.
review_window: use the payload's level_start_date/level_end_date;
return_observations = (#monthly levels in window − 1).
index_set: the requested universe, ascending alphabetical.
- Compute the pairwise Pearson correlation matrix over the universe (Section 2).
extreme_pairs.highest_positive = max-correlation pair; extreme_pairs.lowest =
minimum (most negative) pair. Each pair_id is the two ids alphabetically; correlation
to 3 decimals.
concentration:
high_threshold_breached = any pair correlation > correlation_high_threshold (0.8).
china_asia_dependence_flag = true when the China/Asia cluster is highly
intercorrelated and the portfolio leans on it (China + EM/Asia sleeves with high
mutual correlation, reinforced by memo concern codes like CHINA_DEDICATED_SLEEVE /
ASIA_BETA_OVERLAP).
primary_code: CHINA_ASIA_DEPENDENCE when the China/Asia overlap is the binding
concentration; GLOBAL_DEVELOPED_OVERLAP when developed-world (World/EAFE/ACWI)
overlap dominates; NO_MATERIAL_CONCENTRATION when nothing breaches the high
threshold.
diversification_candidates (from the allowed set, ascending): pick the indices that
genuinely reduce the concentration anchor — i.e. NOT highly correlated to the
anchor (China). A candidate whose correlation to the anchor exceeds the high threshold
(0.8) does NOT diversify and is excluded (e.g. India ~0.85 vs China is excluded), while
a strongly negative one (LatAm) and a structural de-China index (EM-ex-China) are
included.
sleeve_actions (ascending by sleeve, length per template): trim the
concentration-anchor sleeve toward its index, and add the best diversifier sleeve
toward its index. Use the portfolio's sleeve names and the target_index_allowed_values.
4D. Active allocation view refresh (allocation_views + risk_overlay)
Template signals: allocation_views (8 rows), risk_overlay, lineage keys.
GET /api/allocation/opportunity-sets, /api/allocation/prior-views,
/api/macro-signals, /api/policies.
- Lineage:
as_of_date (env), target_quarter/prior_quarter (payload), policy_id
(env global, e.g. POLICY_SET_2026_05), task_id (template required_value).
- For each opportunity_set in the payload's focus order (preserve order):
asset_class from opportunity-sets taxonomy; view/conviction/change/
rationale_code via Section 2 mapping using the target-quarter macro score and the
target-quarter prior view (Section 3).
risk_overlay:
overlay_code + primary_action from the view pattern:
duration OW & HY/credit UW -> DURATION_QUALITY_TILT / tilt_to_duration_quality;
credit/HY risk the dominant cut -> CREDIT_RISK_REDUCTION / trim_credit_beta;
broad cyclical-equity OW -> EQUITY_BETA_EXTENSION / add_cyclical_equity_beta;
currency-defensive dominant -> CURRENCY_DEFENSIVE_HEDGE / add_currency_hedge;
nothing material -> NO_OVERLAY / hold_policy_weights.
rationale_codes: the top supporting drivers, highest business priority first
(the tilt's primary driver first, then the risk being reduced) — typically ordered
by the strength (|score|) / centrality of the views that justify the overlay.
4E. Combined committee decision file (correlation_summary + allocation_views + decision enums)
Template signals: correlation_summary, target_sleeve_actions, allocation_views
(with prior_view/signal_score), rebalance_trigger,
portfolio_risk_concentration_flag, next_step.
- Do the correlation review (4C) on the requested subset and the allocation views
(4D) on the requested opportunity sets, then combine.
correlation_summary (item_order [highest_concentration, best_diversifier]):
highest_concentration = max-correlation pair in the subset; best_diversifier =
minimum (most negative) pair. Pairs alphabetical, correlation 3dp.
allocation_views: include prior_view (target-quarter prior view), signal_score
(raw macro score, 3dp), plus view/change/conviction/rationale_code. Keep the
requested item_order.
target_sleeve_actions (same item_order): map each set's resolved view to an action —
OW -> add; UW -> trim; N/hold -> hold; a currency set used as a defensive
offset -> hedge; rebalance/rotate context -> rotate/monitor as the template's
allowed values fit.
rebalance_trigger: if any subset pair correlation exceeds the high threshold ->
correlation_cap_breach; otherwise pick the binding pressure
(hy_cap_pressure / duration_drift / watchlist_concentration) or committee_review.
portfolio_risk_concentration_flag: true when a concentration/correlation breach is
present.
next_step: a breach with a viable rotation that still needs oversight ->
approve_with_monitoring; clean rotation -> approve_rotation; unresolved/ambiguous
risk -> defer_pending_risk_review; a hard constraint still violated post-trade ->
reject_constraint_breach.
5. Common pitfalls & exclusions
- Watchlist is on the issuer. Always join bond -> issuer; never read a bond field
for watchlist. Watchlisted names are excluded from BUYs and are first to be SOLD.
- Stale payload values are traps. Stale snapshots/exception boards may show wrong
quantities, HY%, marks, "kept USD overweight", or omit the latest index levels — always
recompute from the env. Set
data_precedence to current_environment_over_stale_payload
when they conflict.
return_observations = levels − 1, not the number of levels.
- Prior-view row keying: filter prior-views by
quarter == target_quarter
(Section 3). Using the prior_quarter row is wrong.
rationale_code and signal_score are taken from the macro-signals API verbatim
(rationale by code, score by raw value); do not derive your own.
- Selection is not naive carry-max (4A) and not naive lowest-correlation (4C):
apply theme/quality (credit) and anchor-reduction (correlation) logic above.
- "selected_*" diversification flags judge only the chosen tickets, not the
pre-existing book; the legacy book may already breach issuer concentration without
failing these flags.
- Cash-neutral rotations: BUY notional == SELL notional unless told otherwise; total
MV should be unchanged.
- Enum discipline: every enum field must be one of the template's allowed_values
exactly (case/spelling). Opportunity-set strings (e.g. "U.S. Treasuries", "Corporate
High Yield", "Latin America", "USD"/"EUR") must match the taxonomy exactly.
- Ordering & lengths: respect
required_length, length, and item_order/ordering
precisely; emit lists in the stated order.
- Numbers: compute in full precision, round once at the end to the field's declared
precision; do not pad or truncate beyond the stated decimals.
6. Workflow checklist
- Read the prompt + payload; open
answer_template.json; note keys, enums, lengths,
ordering, precisions, and any required_value fields.
- Pull the env:
/api/policies (thresholds + as_of_date + policy_id),
/api/portfolios/<id>, and the task-specific endpoints (bonds/issuers/energy, or
indices/index-levels, or opportunity-sets/prior-views/macro-signals).
- Reconcile payload vs env; decide precedence.
- Compute (Section 2 formulas) and select per the relevant SOP (Section 4).
- Map decisions to controlled enums (Section 4).
- Assemble JSON in exact template shape, apply ordering, round per field.
- Validate: all required keys present, enums valid, list lengths/orders correct, numbers
at declared precision, and the output is the JSON object only.