Asteria Investment Office — Operational Skill
Core principle
Always use the current shared Asteria environment (remote HTTP API) as the authoritative book of record. Local payloads may carry stale marks, worksheet snapshots, or pre-reconciliation estimates. When a local payload disagrees with the API, the API wins. Express this precedence explicitly in any output field that asks for it (use "current_environment_over_stale_payload").
API data sources and endpoints
| Resource |
Endpoint |
Key fields returned |
| Catalog |
GET /api/catalog |
All ids: portfolios, bonds, indices, issuers, policies, opportunity-sets |
| Policies |
GET /api/policies |
Top-level policy_id (e.g., POLICY_SET_2026_05), allocation-mapping thresholds, credit constraints, correlation thresholds |
| Portfolios |
GET /api/portfolios/<id> |
Holdings (instrument_id, quantity_usd_m, sleeve), as_of_date, constraint_policy_id, market_value_usd_m |
| Bonds |
GET /api/instruments/bonds |
instrument_id, issuer_id, rating_bucket (IG/HY), modified_duration_years, yield_to_maturity_pct, energy_linked, candidate, subsector, recommended_theme_tags |
| Issuers |
GET /api/issuers |
issuer_id, watchlist (bool), subsector, rating_bucket, research_tags |
| Energy signals |
GET /api/market/energy |
Commodity scores/direction, pitch themes |
| Indices |
GET /api/indices |
index_id, level_start_date, level_end_date, frequency |
| Index levels |
GET /api/index-levels |
Monthly level per index_id (dates YYYY-MM-DD, float levels) |
| Opportunity sets |
GET /api/allocation/opportunity-sets |
opportunity_set, asset_class, display_order |
| Prior views |
GET /api/allocation/prior-views |
opportunity_set, quarter, previous_quarter, view (UW/N/OW), conviction |
| Macro signals |
GET /api/macro-signals |
opportunity_set, quarter, score, rationale_code, drivers |
Always use the as_of_date from the portfolio or policies endpoint (typically "2026-05-29") as the answer's as_of_date.
Portfolio-metric calculations
Weighted modified duration
For each holding: weight × modified_duration_years. Sum products, divide by total market value. Round to 2 decimal places.
Weighted yield to maturity (YTM)
For each holding: weight × yield_to_maturity_pct. Sum products, divide by total market value. Round to 2 decimal places.
HY allocation percentage
Sum of all HY-rated holdings (rating_bucket == "HY") divided by total market value × 100. Round to 2 decimal places.
HY reduction (percentage points)
Pre-trade HY% minus post-trade HY%. Round to 2 decimal places.
Post-trade metrics
Compute over the full resulting portfolio (existing holdings minus sells plus buys). Each metric uses its own weighted-average formula. Re-verify that post-trade values sit inside the constraint band declared by the policy.
Policy thresholds
Allocation view mapping (POL_ALLOCATION_MAPPING)
From signal score:
- score > +0.35 → OW
- score < −0.35 → UW
- −0.35 ≤ score ≤ +0.35 → N
Conviction
From absolute signal score:
- |score| ≥ 0.70 → HIGH
- 0.35 ≤ |score| < 0.70 → MEDIUM
- |score| < 0.35 → LOW
View change
Compare the new view (from current signal score) to the prior quarter's view from /api/allocation/prior-views:
- Prior OW → new UW →
DOWN
- Prior OW → new OW →
UNCHANGED
- Prior N → new OW →
UP
- Prior N → new UW →
DOWN
- Prior UW → new N →
UP
- etc.
Always use the Q2_2026 signal scores with the Q1_2026→Q2_2026 prior views (the row where previous_quarter matches the prior quarter and quarter matches the target quarter).
Rationale code
Use the exact rationale_code from the macro-signal for that opportunity_set/quarter. Do not substitute even if another allowed code seems plausible.
Credit constraints (POL_CREDIT_DEFAULT / POL_CREDIT_RISK_REDUCTION)
max_hy_allocation_pct: 20.0 (HY cap)
duration_band_years: [3.0, 5.0]
issuer_concentration_limit_pct: 12.0
target_hy_reduction_pct: 4.0 (only when policy is POL_CREDIT_RISK_REDUCTION)
Correlation thresholds (POL_CORRELATION_DEFAULT)
correlation_high_threshold: 0.80 (breach = concentration risk)
correlation_low_threshold: 0.20 (below = diversifier)
Pearson correlation of monthly simple returns
- Extract levels for each index_id from
/api/index-levels over the requested date window.
- Number of levels = N, number of return observations = N − 1.
- Monthly simple return:
(level_t − level_{t−1}) / level_{t−1}.
- Compute standard Pearson correlation on the paired return vectors.
- Round to 3 decimal places.
- Within each pair, list the two index ids in ascending alphabetical order.
- For the
index_set output field, list all universe indices in ascending alphabetical order.
Bond selection rules
Energy-credit (e.g., PF-EN-ALTA)
- Filter to
energy_linked: true bonds.
- Exclude watchlisted issuers (cross-reference
/api/issuers — watchlist: true).
- Prefer bonds that improve the portfolio's weighted YTM over the current level.
- Both selected bonds must have different issuers and different subsectors.
- Post-trade HY must stay ≤
max_hy_allocation_pct (20%).
- Post-trade weighted duration must stay inside
duration_band_years [3.0, 5.0].
- Sort trade_package entries ascending by instrument_id.
- Each notional is in USD millions, precision 1 decimal.
Fixed-income risk rebalance (e.g., PF-FI-LUMEN)
- Identify all watchlisted holdings → must SELL fully.
- Sell HY pressure points until post-trade HY% ≤ 20% and HY reduction ≥ 4.0 ppt.
- Buy only IG, non-watchlist candidates from the shortlist.
- Post-trade weighted duration must stay in [3.0, 5.0].
- Trades list: SELL entries before BUY entries; within each action, ascending by instrument_id.
watchlist_sell_ids: ascending instrument_id order, listing every sold watchlisted bond.
buys_avoid_watchlist: must be true (hard constraint from meeting preferences).
Output conventions
Ordering rules
- Index ids in lists: ascending alphabetical (e.g.,
["IDX_CHINA", "IDX_EM"]).
- Trade entries: SELL before BUY; within each action, ascending by instrument_id.
- Allocation view rows: follow the request payload's
focus_opportunity_sets order exactly.
- Sleeve actions: follow the template's declared item order.
- Rationale codes in risk overlay: business priority order (highest priority first).
Numeric precision
- Portfolio market value, HY%, duration, YTM: 2 decimal places.
- Trade notionals, watchlist exposure: 1 decimal place.
- Correlations: 3 decimal places.
- Signal scores: 3 decimal places.
Boolean flags
- All constraint-check booleans must reflect the post-trade state.
hy_cap_pass: post-trade HY% ≤ max cap.
duration_band_pass: post-trade duration inside [min, max].
selected_issuer_diversification_pass: the chosen bonds have different issuer_ids.
selected_subsector_diversification_pass: the chosen bonds have different subsectors.
watchlist_avoidance_pass: no selected bond's issuer is on the watchlist.
target_hy_reduction_met: HY reduction ppt ≥ target_hy_reduction_pct.
watchlist_exposure_cleared: post-trade watchlist exposure = 0.
Policy ID in lineage
Use the top-level policy set id (e.g., "POLICY_SET_2026_05") from the policies endpoint, not a sub-policy id like "POL_ALLOCATION_MAPPING".
Data precedence
When the answer template includes a data_precedence field: choose "current_environment_over_stale_payload" whenever the local payload contains a snapshot date or worksheet that predates the API's as_of_date.
Common pitfalls
Using stale payload quantities instead of current portfolio holdings. The API portfolio endpoint is the book of record. The local payload may have different quantities due to unreconciled worksheets.
Wrong policy_id in lineage. Use the top-level policy_id from GET /api/policies (e.g., "POLICY_SET_2026_05"), not a child policy's policy_id.
Mixing up prior-view quarter semantics. The prior-views API returns rows where previous_quarter names the quarter in which the view was set and quarter names the target quarter. For a Q2_2026 target, look up the row with previous_quarter: "Q1_2026" and quarter: "Q2_2026".
Substituting rationale codes. Always take the rationale_code verbatim from the macro-signal record, even if another allowed enum value seems to fit better.
Incorrect return-observation count. With N monthly index levels, there are N−1 monthly return observations, not N.
Rounding before final calculation. Carry full precision through intermediate steps; round only the final answer fields to the declared precision.
Ignoring issuer concentration on new buys. Even when the selected bonds have different issuers and different subsectors, check that no single issuer's post-trade weight exceeds the concentration limit if the constraint applies.
Using the wrong correlation pair for "highest". The "highest_positive" pair is the one with the numerically largest Pearson correlation within the index universe — not the pair most relevant to a stated concern.
Mismatched sleeve names. In sleeve_actions and similar fields, use the exact sleeve name as it appears in the portfolio holdings.
Forgetting to sort pairs. Every pair_id output must list its two index ids in ascending alphabetical order.
1---2name: reflect-3-attempt-03-503description: Asteria Investment Office — Operational Skill4---5# Asteria Investment Office — Operational Skill67## Core principle8Always use the **current shared Asteria environment** (remote HTTP API) as the authoritative book of record. Local payloads may carry stale marks, worksheet snapshots, or pre-reconciliation estimates. When a local payload disagrees with the API, the API wins. Express this precedence explicitly in any output field that asks for it (use `"current_environment_over_stale_payload"`).910---1112## API data sources and endpoints1314| Resource | Endpoint | Key fields returned |15|---|---|---|16| Catalog | `GET /api/catalog` | All ids: portfolios, bonds, indices, issuers, policies, opportunity-sets |17| Policies | `GET /api/policies` | Top-level `policy_id` (e.g., `POLICY_SET_2026_05`), allocation-mapping thresholds, credit constraints, correlation thresholds |18| Portfolios | `GET /api/portfolios/<id>` | Holdings (instrument_id, quantity_usd_m, sleeve), as_of_date, constraint_policy_id, market_value_usd_m |19| Bonds | `GET /api/instruments/bonds` | instrument_id, issuer_id, rating_bucket (IG/HY), modified_duration_years, yield_to_maturity_pct, energy_linked, candidate, subsector, recommended_theme_tags |20| Issuers | `GET /api/issuers` | issuer_id, watchlist (bool), subsector, rating_bucket, research_tags |21| Energy signals | `GET /api/market/energy` | Commodity scores/direction, pitch themes |22| Indices | `GET /api/indices` | index_id, level_start_date, level_end_date, frequency |23| Index levels | `GET /api/index-levels` | Monthly level per index_id (dates YYYY-MM-DD, float levels) |24| Opportunity sets | `GET /api/allocation/opportunity-sets` | opportunity_set, asset_class, display_order |25| Prior views | `GET /api/allocation/prior-views` | opportunity_set, quarter, previous_quarter, view (UW/N/OW), conviction |26| Macro signals | `GET /api/macro-signals` | opportunity_set, quarter, score, rationale_code, drivers |2728**Always use the `as_of_date` from the portfolio or policies endpoint** (typically `"2026-05-29"`) as the answer's `as_of_date`.2930---3132## Portfolio-metric calculations3334### Weighted modified duration35For each holding: weight × modified_duration_years. Sum products, divide by total market value. Round to **2 decimal places**.3637### Weighted yield to maturity (YTM)38For each holding: weight × yield_to_maturity_pct. Sum products, divide by total market value. Round to **2 decimal places**.3940### HY allocation percentage41Sum of all HY-rated holdings (rating_bucket == "HY") divided by total market value × 100. Round to **2 decimal places**.4243### HY reduction (percentage points)44Pre-trade HY% minus post-trade HY%. Round to **2 decimal places**.4546### Post-trade metrics47Compute over the **full resulting portfolio** (existing holdings minus sells plus buys). Each metric uses its own weighted-average formula. Re-verify that post-trade values sit inside the constraint band declared by the policy.4849---5051## Policy thresholds5253### Allocation view mapping (`POL_ALLOCATION_MAPPING`)54From signal `score`:55- **score > +0.35 → OW**56- **score < −0.35 → UW**57- **−0.35 ≤ score ≤ +0.35 → N**5859### Conviction60From absolute signal score:61- **\|score\| ≥ 0.70 → HIGH**62- **0.35 ≤ \|score\| < 0.70 → MEDIUM**63- **\|score\| < 0.35 → LOW**6465### View change66Compare the new view (from current signal score) to the prior quarter's view from `/api/allocation/prior-views`:67- Prior OW → new UW → `DOWN`68- Prior OW → new OW → `UNCHANGED`69- Prior N → new OW → `UP`70- Prior N → new UW → `DOWN`71- Prior UW → new N → `UP`72- etc.7374Always use the **Q2_2026 signal scores** with the **Q1_2026→Q2_2026 prior views** (the row where `previous_quarter` matches the prior quarter and `quarter` matches the target quarter).7576### Rationale code77Use the **exact rationale_code from the macro-signal** for that opportunity_set/quarter. Do not substitute even if another allowed code seems plausible.7879### Credit constraints (`POL_CREDIT_DEFAULT` / `POL_CREDIT_RISK_REDUCTION`)80- `max_hy_allocation_pct`: 20.0 (HY cap)81- `duration_band_years`: [3.0, 5.0]82- `issuer_concentration_limit_pct`: 12.083- `target_hy_reduction_pct`: 4.0 (only when policy is `POL_CREDIT_RISK_REDUCTION`)8485### Correlation thresholds (`POL_CORRELATION_DEFAULT`)86- `correlation_high_threshold`: 0.80 (breach = concentration risk)87- `correlation_low_threshold`: 0.20 (below = diversifier)8889---9091## Pearson correlation of monthly simple returns92931. Extract levels for each index_id from `/api/index-levels` over the requested date window.942. Number of levels = N, number of return observations = **N − 1**.953. Monthly simple return: `(level_t − level_{t−1}) / level_{t−1}`.964. Compute standard Pearson correlation on the paired return vectors.975. Round to **3 decimal places**.986. Within each pair, list the two index ids in **ascending alphabetical order**.997. For the `index_set` output field, list all universe indices in ascending alphabetical order.100101---102103## Bond selection rules104105### Energy-credit (e.g., PF-EN-ALTA)1061. Filter to `energy_linked: true` bonds.1072. Exclude **watchlisted** issuers (cross-reference `/api/issuers` — `watchlist: true`).1083. Prefer bonds that improve the portfolio's weighted YTM over the current level.1094. Both selected bonds must have **different issuers** and **different subsectors**.1105. Post-trade HY must stay ≤ `max_hy_allocation_pct` (20%).1116. Post-trade weighted duration must stay inside `duration_band_years` [3.0, 5.0].1127. Sort trade_package entries **ascending by instrument_id**.1138. Each notional is in USD millions, precision 1 decimal.114115### Fixed-income risk rebalance (e.g., PF-FI-LUMEN)1161. Identify all watchlisted holdings → must SELL fully.1172. Sell HY pressure points until post-trade HY% ≤ 20% and HY reduction ≥ 4.0 ppt.1183. Buy only IG, non-watchlist candidates from the shortlist.1194. Post-trade weighted duration must stay in [3.0, 5.0].1205. Trades list: SELL entries before BUY entries; within each action, ascending by instrument_id.1216. `watchlist_sell_ids`: ascending instrument_id order, listing every sold watchlisted bond.1227. `buys_avoid_watchlist`: must be `true` (hard constraint from meeting preferences).123124---125126## Output conventions127128### Ordering rules129- **Index ids in lists**: ascending alphabetical (e.g., `["IDX_CHINA", "IDX_EM"]`).130- **Trade entries**: SELL before BUY; within each action, ascending by instrument_id.131- **Allocation view rows**: follow the request payload's `focus_opportunity_sets` order exactly.132- **Sleeve actions**: follow the template's declared item order.133- **Rationale codes in risk overlay**: business priority order (highest priority first).134135### Numeric precision136- Portfolio market value, HY%, duration, YTM: **2 decimal places**.137- Trade notionals, watchlist exposure: **1 decimal place**.138- Correlations: **3 decimal places**.139- Signal scores: **3 decimal places**.140141### Boolean flags142- All constraint-check booleans must reflect the **post-trade state**.143- `hy_cap_pass`: post-trade HY% ≤ max cap.144- `duration_band_pass`: post-trade duration inside [min, max].145- `selected_issuer_diversification_pass`: the chosen bonds have different issuer_ids.146- `selected_subsector_diversification_pass`: the chosen bonds have different subsectors.147- `watchlist_avoidance_pass`: no selected bond's issuer is on the watchlist.148- `target_hy_reduction_met`: HY reduction ppt ≥ target_hy_reduction_pct.149- `watchlist_exposure_cleared`: post-trade watchlist exposure = 0.150151### Policy ID in lineage152Use the **top-level policy set id** (e.g., `"POLICY_SET_2026_05"`) from the policies endpoint, not a sub-policy id like `"POL_ALLOCATION_MAPPING"`.153154### Data precedence155When the answer template includes a `data_precedence` field: choose `"current_environment_over_stale_payload"` whenever the local payload contains a snapshot date or worksheet that predates the API's `as_of_date`.156157---158159## Common pitfalls1601611. **Using stale payload quantities instead of current portfolio holdings.** The API portfolio endpoint is the book of record. The local payload may have different quantities due to unreconciled worksheets.1621632. **Wrong policy_id in lineage.** Use the top-level `policy_id` from `GET /api/policies` (e.g., `"POLICY_SET_2026_05"`), not a child policy's `policy_id`.1641653. **Mixing up prior-view quarter semantics.** The prior-views API returns rows where `previous_quarter` names the quarter in which the view was set and `quarter` names the target quarter. For a Q2_2026 target, look up the row with `previous_quarter: "Q1_2026"` and `quarter: "Q2_2026"`.1661674. **Substituting rationale codes.** Always take the rationale_code verbatim from the macro-signal record, even if another allowed enum value seems to fit better.1681695. **Incorrect return-observation count.** With N monthly index levels, there are N−1 monthly return observations, not N.1701716. **Rounding before final calculation.** Carry full precision through intermediate steps; round only the final answer fields to the declared precision.1721737. **Ignoring issuer concentration on new buys.** Even when the selected bonds have different issuers and different subsectors, check that no single issuer's post-trade weight exceeds the concentration limit if the constraint applies.1741758. **Using the wrong correlation pair for "highest".** The "highest_positive" pair is the one with the numerically largest Pearson correlation within the index universe — not the pair most relevant to a stated concern.1761779. **Mismatched sleeve names.** In sleeve_actions and similar fields, use the exact sleeve name as it appears in the portfolio holdings.17817910. **Forgetting to sort pairs.** Every pair_id output must list its two index ids in ascending alphabetical order.