fix(swarm): make preset deliverables match what can actually be computed

Swarm workers get a strict tool whitelist, and several shipped presets ordered
deliverables their whitelisted tools could not produce -- so the agent filled
the gap from its own head and returned an institution-shaped report with
invented numbers in it. All thirty presets were re-read against what the tree
can now actually compute, and each claim resolved one of three ways.

Twenty-three claims were backed by a capability the node was simply never
granted; those got the tool. financial_rigor already implements a three-scenario
valuation and was not in fundamental_research_team's list. portfolio_review_board
ordered VaR, CVaR, concentration and days-to-liquidate from a node holding no
data tool at all, and its execution analyst was asked for implementation
shortfall with nothing to compute it from.

Eight are backed by the new quantlib modules and now name them.

Seven have no backing and say so, rather than being deleted. There is no DCF or
WACC engine, so fundamental_research_team now orders an assumption set with
every input tagged sourced or assumed and forbids quoting a valuation; the same
applies to event_driven_task_force and investment_committee. There is no Barra
model, so fund_selection_panel must label its style profile a proxy and define
the factors. There is no crypto options source of any kind -- the only chain
tool is Yahoo-backed US-listed equity -- so crypto_research_lab no longer asks
for a 25-delta risk reversal.

test_preset_claim_backing.py holds the mechanical half: every snake_case tool
identifier named in a prompt must be in that node's tools list, and every skills
entry must have a SKILL.md on disk. Matching bare English words instead of
snake_case identifiers is what made an earlier attempt at this gate almost
entirely false positives -- sentiment, pattern, backtest and remember are all
tool names and ordinary words.

Note for whoever reads the audit that prompted this: its headline example is
false. It reported that derivatives_strategy_desk grants get_options_chain to no
agent; the pre-edit file grants it to all three, at lines 55, 120 and 181.
This commit is contained in:
Haozhe Wu committed 2026-08-06 01:57:23 +08:00
1 parent b8276f2abf
commit c103708b15
20 files changed
+734 -84

No files matched your search

@@ -26,7 +26,25 @@ agents:
5. **Bond-Floor Value Ranking** — Composite bond-floor score based on YTM, premium over floor, and credit quality; output Top 30 ranked list with scoring rationale
Use load_skill("convertible-bond") for convertible bond analytical methodology; load_skill("fundamental-filter") to support credit assessment.
tools: [bash, read_file, write_file, load_skill]
## The bond math is implemented — run it, do not approximate it
`from src.quantlib import fixedincome` in `bash`:
- `bond_price(face, coupon_rate, ytm, n_periods, freq)` → the straight-bond floor
- `ytm_solve(price, face, coupon_rate, n_periods, freq)` → YTM from market price
- `modified_duration(...)`, `convexity(...)`, `dv01(...)` → the ±50 bp
sensitivity in output 4. Use duration AND convexity for a 50 bp move;
duration alone understates it
- `accrued_interest(...)` / `year_fraction(..., day_count=...)` → settlement math;
state the day-count convention you used, it changes the answer
- `fit_yield_curve(maturities, yields, model="svensson")` → the discount curve
when you are spreading against a term structure; report the fit's `.rmse`
Prices and terms come from `get_market_data` and `read_url` (issuer prospectus
/ exchange disclosure) — a coupon, maturity, put schedule or rating you did
not retrieve must not be entered into the model. **Credit RATINGS are not
available from any tool here**: quote a rating only with the source you read
it from, and if you have none, group by a stated proxy (issuer size, leverage
from `get_financial_statements`) and label the grouping a proxy, not a rating.
tools: [bash, read_file, write_file, load_skill, get_market_data, get_financial_statements, read_url]
skills: [convertible-bond, fundamental-filter, credit-analysis]
max_iterations: 50
timeout_seconds: 1800
@@ -44,8 +44,39 @@ agents:
5. **Credit Risk Red-Line Checklist** — List 5–8 key risk events that, if triggered, require immediate re-assessment (e.g., EBITDA/interest coverage below 2x / short-term debt > 50% of total / net financing turns negative)
Use load_skill("credit-analysis") for credit analysis methodology; load_skill("financial-statement") for advanced financial statement interpretation.
Use the factor_analysis tool to support cross-sectional multi-dimensional financial metric comparisons.
tools: [bash, read_file, write_file, load_skill, factor_analysis]
## The default models are implemented — run them, do not eyeball them
`from src.quantlib import credit` in `bash`:
- `altman_z_score(working_capital, retained_earnings, ebit, equity_value,
total_liabilities, total_assets, revenue=None, model=...)` returns an
`AltmanZScore`. Pick the `model` deliberately — `ALTMAN_MODELS` carries
variants with different coefficients and different zone cut-offs, and the
Safe/Grey/Distress boundaries quoted above belong to the ORIGINAL model.
If you use another variant, quote that variant's boundaries.
- `merton_model(equity_value, equity_vol, debt_face, risk_free, horizon)`
solves for asset value and vol and returns the `MertonResult`;
`kmv_default_point(short_term_debt, long_term_debt)` then
`kmv_distance_to_default(...)` for the KMV form.
- `edf_reference_band(dd)` maps distance-to-default to an EDF BAND. It is a
reference band, not a calibrated PD — report the band and say so. Do not
convert it into a single point default probability.
- `credit_spread_analysis(bond_yields, risk_free_yields, ...)` and
`spread_term_structure(...)` for the spread work; mind `input_unit`
(percent vs decimal) — passing the wrong one silently scales everything
by 100.
Statement inputs come from `get_financial_statements`, market cap / equity
vol / share count from `get_stock_profile` and `get_market_data`. Every model
input must trace to one of those calls; if a line item is missing, report the
model as not runnable rather than substituting an assumed value.
**There is no rating feed and no market-implied PD service here.** Agency
ratings and rating histories are only usable if you retrieved them via
`read_url` and cite the source. Your "proprietary rating" is your own
judgement mapped from the computed metrics — present it as that, never as
an agency-comparable rating.
`factor_analysis` computes IC/IR and layered NAV from CSVs you write; it does
not do cross-sectional peer comparison for you — build the peer table from
per-issuer lookups.
tools: [bash, read_file, write_file, load_skill, factor_analysis, get_financial_statements, get_stock_profile, get_market_data, read_url]
skills: [credit-analysis, financial-statement]
max_iterations: 50
timeout_seconds: 1800
@@ -139,14 +139,23 @@ agents:
- **Long/Short Ratio**:
- Retail long/short ratio: contrarian indicator — extreme retail bullishness often marks a top
### II. Options Market Sentiment
- **Put/Call Ratio (PCR)**:
- PCR > 0.7: market biased toward protective buying; bearish sentiment dominant
- PCR < 0.4: calls in favor; excessive optimism; watch for top
- **25-Delta Risk Reversal**: Implied vol difference between calls and puts
- Positive: market willing to pay more for upside insurance, bullish sentiment
- Negative (steep put skew): greater fear of downside
- **IV Percentile**: Current implied volatility in historical percentile; extremely low IV often precedes a large move
### II. Options Market Sentiment — RETRIEVED ONLY, NEVER ESTIMATED
This desk has no crypto options data source. The only options-chain tool in
the system is Yahoo-backed, covers US-listed equity options only, and is not
granted here; there is no Deribit or crypto-options connector anywhere in
this codebase. Nothing available to you can compute a PCR, a risk reversal,
or an IV percentile for {target}.
Your only honest route is to RETRIEVE a published figure with `read_url`
(e.g. a CoinGlass or Deribit metrics page) and quote it with its source URL
and retrieval timestamp. Interpretation thresholds, for a figure you
actually retrieved:
- **Put/Call Ratio (PCR)**: >0.7 protective buying dominant; <0.4 excessive optimism
- **25-Delta Risk Reversal**: positive = paying up for upside; negative = steep put skew, downside fear
- **IV Percentile**: extremely low IV often precedes a large move
If the retrieval fails or you did not run it, write "options sentiment
unavailable — no crypto options source" and score the composite sentiment
index without this component, saying which component is missing. Do not
infer a PCR or a risk reversal from funding rates, price action, or memory.
### III. Social Media and News Sentiment
- **Twitter/X Sentiment Index**: Sentiment direction of crypto-related tweets (positive / negative / neutral share)
@@ -168,7 +177,7 @@ agents:
## Output Requirements
1. **Composite Sentiment Index** — Custom composite sentiment score (0 = extreme fear, 100 = extreme greed), with sub-component contributions
2. **Funding Rate and OI Analysis** — Current funding rate level and trend, OI directional change, bull/bear force balance assessment
3. **Options Market Sentiment Signal** — PCR value, risk reversal status, options market directional implied view
3. **Options Market Sentiment Signal** — PCR value, risk reversal status, options market directional implied view, each with the source URL and retrieval time it came from; state "unavailable — no crypto options source" if nothing was retrieved
4. **Fear & Greed Index vs. Historical Comparison** — Current index value, historical zone positioning, price performance statistics following similar past sentiment readings
5. **Extreme Sentiment Warning** — Whether extreme greed or extreme fear signals are present; historical average returns following similar signals
6. **Sentiment Directional Signal** — Provide {timeframe} timing guidance from a sentiment perspective; explicitly state whether the view is "momentum-following" or "contrarian"
@@ -164,7 +164,21 @@ agents:
- Next review time / trigger for position reassessment
Use load_skill for risk analysis and asset allocation frameworks.
tools: [bash, read_file, write_file, load_skill]
The gate thresholds above are written as "X%" because YOU must set them from
measurement, not pick round numbers. Pull the histories with
`get_market_data` and, in `bash`:
- realised vol and the BTC-vs-index correlation for the correlation gate
(state the window — crypto correlation is unstable across regimes)
- `src.quantlib.risk.historical_var` / `historical_cvar` for the loss gates
and `max_drawdown_analysis` for the drawdown gate, so each threshold is
anchored to an observed distribution rather than a habit
- `fit_gpd_tail` for the tail beyond the sample
Funding, liquidation-cluster and stablecoin-flow readings come from upstream
analysts in `{upstream_context}` — cite the upstream figure. If an upstream
section reported its data as unavailable, the gate that depends on it is
also unavailable: say so instead of inventing a threshold for it.
tools: [bash, read_file, write_file, load_skill, get_market_data]
skills: [risk-analysis, asset-allocation, volatility, hedging-strategy]
max_iterations: 50
timeout_seconds: 1800
@@ -43,7 +43,31 @@ agents:
- Steep skew + low IV Premium → directionally biased strategy
Use load_skill("volatility") for volatility analysis standards; load_skill("options-advanced") for advanced options analysis methods.
Use the options_pricing tool for volatility calculations and surface modeling.
## Where each number actually comes from
`options_pricing` takes a volatility you already know and returns a
Black-Scholes price plus delta/gamma/theta/vega. It does NOT invert IV and
it does NOT build a surface — never cite it as the source of an IV.
- **Market IV, skew, term structure**: `get_options_chain` returns the
per-contract ladder (strike, bid/ask, last, volume, open interest,
`implied_volatility`) for one expiration, plus the expiration list. Call
it once per tenor to build a term structure, and read the skew off the
strike ladder. **Yahoo-backed and US-listed only** — if {target} is not
a US-listed underlying, say so and report the IV sections as unavailable
rather than estimating them.
- **25-delta strikes**: the chain gives no delta column. Recover delta by
running each contract's own `implied_volatility` back through
`src.quantlib.options.bs_greeks(S, K, T, r, sigma, option_type)` in `bash`,
then interpolate to the 25-delta point. State the interpolation you used.
- **IV from a price** (e.g. a contract with a stale or missing IV field):
`src.quantlib.options.implied_volatility(market_price, S, K, T, r, option_type)`
inverts it directly.
- **Historical volatility and GARCH persistence**: compute HV from bars in
`bash`; `src.quantlib.timeseries.fit_garch(returns, horizon)` gives the
fitted GARCH result for the persistence claim.
Any percentile you quote ("current HV sits at the 80th percentile") must be
computed from a history you actually pulled. If you did not pull the
history, do not quote a percentile.
## Output Requirements
1. **Volatility Environment Summary** — One-sentence qualitative characterization: current volatility is "low / medium / high", specific percentile, implied future volatility direction
@@ -52,7 +76,7 @@ agents:
4. **Term Structure Shape** — Front-month / second-month / quarterly IV comparison; term structure slope and trading implications
5. **Skew Characteristics Analysis** — Put skew and risk reversal current levels; compare vs. historical median; assess market sentiment bias
6. **Volatility Strategy Direction Recommendation** — Based on the above, explicitly recommend "long volatility / short volatility / directional bias / calendar spread"; include confidence level
tools: [bash, read_file, write_file, load_skill, options_pricing, get_options_chain]
tools: [bash, read_file, write_file, load_skill, options_pricing, get_options_chain, get_market_data]
skills: [volatility, options-advanced]
max_iterations: 50
timeout_seconds: 1800
@@ -117,7 +141,7 @@ agents:
4. **Initial Greeks Overview** — Current values of Delta / Gamma / Theta / Vega and their interpretation (risk exposure direction at initiation)
5. **Entry and Exit Rules** — Entry trigger conditions, profit-taking rules, stop-loss rules, time stop rules; specific and actionable
6. **Strategy Applicability and Failure Scenarios** — Under what conditions the strategy is effective; under what conditions it fails (requiring prompt adjustment)
tools: [bash, read_file, write_file, load_skill, options_pricing, get_options_chain]
tools: [bash, read_file, write_file, load_skill, options_pricing, get_options_chain, get_market_data]
skills: [options-strategy, options-advanced, hedging-strategy, options-payoff]
max_iterations: 50
timeout_seconds: 1800
@@ -155,6 +179,14 @@ agents:
- **Vanna**: Delta sensitivity to IV (cross-effect of direction and volatility)
- **Volga / Vomma**: Second derivative of Vega with respect to IV (convexity when vol moves to extremes)
`src.quantlib.options.bs_greeks` returns delta, gamma, theta, vega and rho —
there is no closed-form vanna or volga in this system. Compute them by
bumping sigma in `bash` and differencing, and say that is what you did:
`vanna ≈ (bs_greeks(..., sigma+h)["delta"] - bs_greeks(..., sigma-h)["delta"]) / (2h)`
`volga ≈ (bs_greeks(..., sigma+h)["vega"] - bs_greeks(..., sigma-h)["vega"]) / (2h)`
State the bump size `h` you used. A vanna or volga figure that did not come
out of this calculation must not appear in the report.
### III. Scenario Analysis
Build a 2D scenario matrix (underlying price × implied volatility):
- Price dimension: current price ±5% / ±10% / ±15% / ±20% (6 nodes)
@@ -169,16 +201,23 @@ agents:
- **Liquidity Risk**: Exit cost when bid-ask spreads widen to 3× normal levels
Use load_skill("options-advanced") for advanced Greeks calculation standards; load_skill("risk-analysis") for risk management standards; load_skill("volatility") for Vega and volatility risk understanding.
Use the options_pricing tool to compute portfolio Greeks and the scenario analysis matrix.
Use `options_pricing` (or `src.quantlib.options.bs_greeks` in `bash` when you need
rho or a bumped input) per leg, then aggregate across legs yourself — nothing
computes a portfolio-level Greek or a scenario matrix for you. Build the
price × IV matrix by re-pricing every leg at each grid node in `bash`.
For the CVaR / expected-shortfall figure, use
`src.quantlib.risk.historical_cvar` or `src.quantlib.risk.monte_carlo_gbm` +
`analyze_mc_results` on a return series you actually pulled — do not assert a
shortfall number without one.
## Output Requirements
1. **Portfolio Greeks Summary Table** — Current values of Delta / Gamma / Theta / Vega / Vanna, with intuitive interpretation ("earn/lose $X per day" format)
1. **Portfolio Greeks Summary Table** — Current values of Delta / Gamma / Theta / Vega / Vanna, aggregated from per-leg `bs_greeks` output (vanna by the sigma-bump above), with intuitive interpretation ("earn/lose $X per day" format)
2. **Scenario Analysis Matrix** — Price × volatility 2D matrix; clearly display P&L distribution; annotate breakeven boundaries
3. **Key Risk Point Identification** — Under which scenarios does the portfolio suffer the largest losses? Maximum loss amount and probability estimate for the worst case
4. **Gamma / Theta Trade-off Analysis** — Current intraday battle between Gamma and Theta; analyze whether it is "trading time for space" or "trading space for time"
5. **Stress Test Results** — Historical extreme scenario and single-day shock test results; provide maximum expected shortfall (ES / CVaR)
6. **Dynamic Adjustment Recommendations** — At what price / time / IV level should the strategy be adjusted (Delta rebalancing trigger, roll timing, stop-loss exit conditions)
tools: [bash, read_file, write_file, load_skill, options_pricing, get_options_chain]
tools: [bash, read_file, write_file, load_skill, options_pricing, get_options_chain, get_market_data]
skills: [options-advanced, risk-analysis, volatility, options-payoff]
max_iterations: 50
timeout_seconds: 1800
@@ -124,7 +124,27 @@ agents:
- Gap risk: stock can gap beyond stop-loss → position sizing must account for this
Use load_skill for options analysis and event-driven strategy frameworks.
tools: [bash, read_file, write_file, load_skill]
## Every number here needs the chain — retrieve it or drop the section
`load_skill` loads methodology; it does not fetch data.
- `get_options_chain(ticker, expiration)` gives the strike ladder with
bid/ask, last, volume, open interest and `implied_volatility` — that is
the source for the ATM straddle price, the put/call ratio, the skew and
the open-interest-by-strike map. **Yahoo-backed, US-listed only**: if the
target is not US-listed, say the options sections are unavailable.
- Implied move = ATM straddle price ÷ spot, from the retrieved quotes.
Realised earnings moves come from `get_market_data` bars around the past
8 reporting dates — retrieve those dates, don't recall them.
- `options_pricing` / `src.quantlib.options.bs_greeks` for per-leg theta and
the decay estimate; `src.quantlib.options.implied_volatility` to invert an
IV the chain did not carry.
- **Unusual activity — sweeps and block trades — is NOT available.** There is
no options tape or flow feed in this system; volume and open interest from
the chain are the most you have. Say that plainly instead of describing
flow you cannot see.
Any implied move, PCR, skew or OI figure that did not come from a chain call
you actually ran must not appear.
tools: [bash, read_file, write_file, load_skill, get_options_chain, get_market_data, options_pricing]
skills: [options-advanced, options-strategy, event-driven, volatility]
max_iterations: 50
timeout_seconds: 1800
@@ -99,7 +99,12 @@ agents:
6. **Action Recommendations** — Provide clear position and timing guidance
Ensure the report is internally consistent, data is traceable, and conclusions are logically supported.
tools: [bash, read_file, write_file]
Use load_skill("report-generate") for the research report format and writing standards.
You have no data tool: every figure in this report must already appear in
{upstream_context}. If two upstream analysts disagree on a number, surface
the disagreement rather than picking one; if a section reported its data as
unavailable, carry that through instead of filling the gap.
tools: [bash, read_file, write_file, load_skill]
skills: [report-generate]
max_iterations: 50
timeout_seconds: 1800
@@ -188,7 +188,22 @@ agents:
5. **Rebalancing Strategy Recommendation** — Recommend rebalancing trigger conditions (time + threshold dual trigger); estimate annual average rebalancing frequency and transaction costs (%); compare with no-rebalancing scenario risk
Use load_skill("risk-analysis") for risk budgeting and risk decomposition methods; load_skill("volatility") for volatility modeling and correlation estimation; load_skill("etf-analysis") for ETF product risk characteristic analysis.
tools: [bash, read_file, write_file, load_skill]
The whole risk table in output 1 is measured, not remembered: pull each
asset-class proxy ETF's history with `get_market_data` and compute
volatility, correlation and drawdown in `bash`
(`src.quantlib.risk.max_drawdown_analysis` for the drawdown column,
`historical_var` / `historical_cvar` for tail figures). Name the sample
window — asset-class correlations are regime-dependent and a number without
a window is meaningless.
For output 2, the optimizers exist — do not hand-assign the weights.
`risk_parity`, `equal_volatility` and `max_diversification` each live in
`backtest/optimizers/` and are selected through the `optimizer` field of the
backtest config, so run `backtest` once per scheme and compare the weights
it produced. The same runs give you the output-5 turnover and transaction
cost figures with rebalancing applied.
If a proxy's history cannot be retrieved, exclude that asset class and say so.
tools: [bash, read_file, write_file, load_skill, get_market_data, backtest]
skills: [risk-analysis, volatility, etf-analysis]
max_iterations: 50
timeout_seconds: 1800
@@ -57,31 +57,53 @@ agents:
- Impact on core competitiveness / moat (positive reinforcement vs. negative erosion)
2. **Valuation Impact Pathway**
- Expected direction and magnitude of change in P/E, P/B, EV/EBITDA, and other key multiples
- Impact on DCF key assumptions (changes to growth rate / discount rate / terminal growth rate)
- Impact on DCF key assumptions — direction and rough magnitude of the change to growth rate / discount rate / terminal growth rate. There is no DCF engine in this system, so describe how the assumptions move; do NOT quote a DCF value or a revised intrinsic value
- Statistical analysis of historical valuation re-rating magnitude under similar events
3. **Market Pricing Analysis**
- Whether post-event stock price reaction aligns with fundamental impact expectations
- Options implied volatility change (where options data available) reflecting market expectations
- Options implied volatility change reflecting market expectations — you have no options-chain tool, so this line is only usable if an IV reading appears in a source you retrieved via `read_url` / `get_stock_news`; cite it, or omit the line entirely
- Analyst consensus revision direction (upgrade / downgrade / maintain) and research rating change trends
- Conclusion: Is the market fully priced (overreaction / fully priced / underpriced)?
4. **Historical Event Benchmarking** — Find historical cases most similar to the current event (at least 2); analyze:
- Average cumulative abnormal return (CAR / CAAR) at T+1, T+5, T+20, T+60
- Hit rate of direction-consistent abnormal returns post-event
- Key factors driving persistence or dissipation of abnormal returns
**How to actually produce CAR/CAAR.** No tool computes an event study.
`factor_analysis` computes IC/IR and layered NAV from a factor CSV and a
return CSV you write — it cannot do this. Build it yourself in `bash`:
1. Establish each comparable event's DATE from a retrieved source —
`get_stock_news` or `read_url`. An event date you cannot cite is not
usable; drop that comparable rather than guessing when it happened.
2. Pull the security's and the benchmark's daily bars around each event
date with `get_market_data`.
3. Abnormal return = security return − benchmark return (market-adjusted
model). State that this is the market-adjusted model, not a
risk-adjusted one — there is no estimation-window factor model here.
4. Cumulate over each window for CAR; average across events for CAAR.
5. With fewer than ~5 comparable events, report the individual CARs and
say the sample is too small for a meaningful CAAR. Do not present a
mean of two observations as an "abnormal return distribution".
If you cannot complete steps 1–2, write "historical benchmarking not
available for this event" and move on. Never state a CAR figure you did
not compute from retrieved bars.
5. **Related Target Transmission** — For core targets, identify: directly impacted targets, supply chain upstream / downstream beneficiaries / casualties, competitive position changes for industry peers
## Output Requirements
1. **Event Impact Matrix** — For each high / medium-rated event: impact direction (+/-/?), impact magnitude (small <3% / medium 3–10% / large >10%), impact duration (very short / short / medium term), confidence (high / medium / low)
2. **Market Pricing Status** — Explicitly assess each event for pricing bias: overreaction (mean-reversion opportunity), fully priced (no opportunity), underpriced (follow-through opportunity)
3. **Historical CAR Statistics** — For the 3–5 most tradeable events, provide abnormal return distributions from historically similar events
3. **Historical CAR Statistics** — For the 3–5 most tradeable events, provide the abnormal return distribution computed from retrieved bars, naming each comparable event, its dated source, and the sample size; mark any event where the study could not be run as unavailable
4. **Related Target Map** — Impact transmission chain for each core event (list of beneficiary / loser targets)
5. **Top 5 Tradeable Events** — Ranked by composite score ("impact direction clarity × impact magnitude × market pricing bias × historical hit rate"); recommend the 5 most valuable events with rationale
6. **Devil's Advocate** — For each recommended event, list the counter-logic that could invalidate the thesis
Use load_skill("sentiment-analysis") for market sentiment and expectation quantification tools.
Use load_skill("valuation-model") for valuation analysis frameworks and tools.
Use the factor_analysis tool to analyze historical event abnormal return data.
tools: [bash, read_file, write_file, load_skill, factor_analysis]
`factor_analysis` computes IC/IR and layered NAV from a factor CSV and a return CSV
you write yourself — it does not fetch data and cannot run an event study.
Event dates come from `get_stock_news` / `read_url`, bars from `get_market_data`,
and the CAR arithmetic from your own `bash` code.
tools: [bash, read_file, write_file, load_skill, factor_analysis, get_market_data, get_stock_news, read_url]
skills: [corporate-events, sentiment-analysis, valuation-model]
max_iterations: 50
timeout_seconds: 1800
@@ -48,10 +48,18 @@ agents:
Attribution Framework:
1. **Brinson Attribution Decomposition (for actively managed funds)**
Implemented — run it, do not estimate it:
`from src.quantlib.attribution import brinson_fachler, carino_link`
- **Allocation Effect**: Excess return from overweighting/underweighting sectors vs benchmark; measures the manager's sector timing ability
- **Selection Effect**: Excess return from superior stock selection within sectors vs benchmark; measures the manager's stock-picking ability
- **Interaction Effect**: Residual effect of combined allocation and selection decisions
- Sum of three effects = total excess return; identifies the manager's primary alpha source
- `BrinsonResult.total_effect` is the total excess return the three effects reconcile to; use `carino_link` to compound multi-period effects rather than adding them
- Inputs come from data, not from memory: fund sector weights via
`etf_holdings` look-through (note the disclosed report period — holdings
are lagged, and `coverage` tells you whether you got the full book or
only a top-N), sector and benchmark return series via `get_market_data`.
A fund whose holdings you cannot retrieve gets NO Brinson table; say
the look-through was unavailable and fall back to return-based analysis.
2. **Barra Style Factor Analysis**
- Key style factor exposures: value (low P/B, P/E) / growth (high ROE growth) / size (large/mid/small cap) / momentum (recent strength) / volatility (low-vol premium) / quality (ROE / financial stability)
- Style factor return decomposition: what proportion of performance comes from style exposure (beta return) vs pure alpha
@@ -69,8 +77,8 @@ agents:
- Tracking error trend over time
## Output Requirements
1. **Brinson Attribution Report** — Three-effect decomposition for each candidate fund, clearly identifying primary alpha source (allocation ability / stock selection ability)
2. **Barra Style Profile** — Style factor exposure radar (text format) for each fund, noting deviation from benchmark
1. **Brinson Attribution Report** — Three-effect decomposition for each candidate fund from `brinson_fachler`, clearly identifying primary alpha source (allocation ability / stock selection ability); state the holdings report period and return series behind each table
2. **Barra Style Profile** — There is no Barra factor model in this system and no licensed Barra exposure feed. Build a proxy style profile from the look-through holdings and label it exactly that: a proxy, with the factor definitions you used. Never present a proxy exposure as a Barra number
3. **Excess Return Quality Rating** — Composite rating (A/B/C/D) based on IR/hit rate/consistency, distinguishing genuine investment skill from style exposure
4. **Up/Down Capture Matrix** — Offensive/defensive profile comparison across candidate funds, identifying funds with balanced attack and defense
5. **Style Drift Warnings** — Flag style-inconsistent funds with drift severity, recommend style weight adjustments in FOF construction
@@ -79,8 +87,11 @@ agents:
Use load_skill("performance-attribution") for the Brinson model and Barra factor analysis framework.
Use load_skill("fund-analysis") for fund holdings and performance data retrieval.
Use load_skill("multi-factor") for multi-factor style analysis tools.
Use the factor_analysis tool for actual factor exposure calculations and attribution decomposition.
tools: [bash, read_file, write_file, load_skill, factor_analysis]
`factor_analysis` computes IC/IR and layered NAV from a factor CSV and a return CSV
you write yourself; it does not fetch data and does not do Brinson attribution.
The attribution decomposition comes from `src.quantlib.attribution`, the holdings
from `etf_holdings`, and the return series from `get_market_data`.
tools: [bash, read_file, write_file, load_skill, factor_analysis, etf_holdings, get_market_data]
skills: [performance-attribution, fund-analysis, multi-factor]
max_iterations: 50
timeout_seconds: 1800
@@ -146,7 +157,7 @@ tasks:
- id: task-performance-attribution
agent_id: attribution_analyst
prompt_template: "Conduct deep performance attribution analysis on candidate funds: Brinson three-effect decomposition, Barra style factor exposure, excess return quality rating, up/down capture rate comparison. Output attribution report and shortlist for FOF construction. Objective: {goal}."
prompt_template: "Conduct deep performance attribution analysis on candidate funds: Brinson three-effect decomposition via src.quantlib.attribution on retrieved holdings and return series, a clearly-labelled proxy style profile (no Barra model exists here), excess return quality rating, up/down capture rate comparison. Output attribution report and shortlist for FOF construction. Objective: {goal}."
depends_on: [task-fund-screen]
input_from:
candidate_funds: task-fund-screen
@@ -33,7 +33,12 @@ agents:
- Financing activities: excessive reliance on external funding
Use load_skill("financial-statement") for financial analysis standards; load_skill("fundamental-filter") for screening criteria.
Use the factor_analysis tool to extract key financial factor data.
Pull the three statements with `get_financial_statements` and the summary
metrics with `get_stock_profile`; check every ratio you derive with
`financial_rigor` (`command="calc"` for exact arithmetic, `verify_valuation`
for PE/PB/ROE/FCF-yield consistency). Every number in your tables must come
from one of those calls — if a line item is missing from the filing, report
it as unavailable rather than estimating it.
## Output Requirements
1. **Financial Health Score** — Composite score 1–10, with rationale (equally weighted across earnings / assets / cash flow)
@@ -42,7 +47,7 @@ agents:
4. **Key Financial Metrics Table** — ROE / ROIC / gross margin / net margin / FCF margin / debt ratio and other core metrics, 3-year trend
5. **Improvement / Deterioration Signals** — Significant changes in the past 1–2 years, trend direction assessment
6. **Peer Comparison** — Key financial metrics vs industry average / sector leaders
tools: [bash, read_file, write_file, load_skill, factor_analysis]
tools: [bash, read_file, write_file, load_skill, get_financial_statements, get_stock_profile, financial_rigor]
skills: [financial-statement, fundamental-filter]
max_iterations: 50
timeout_seconds: 1800
@@ -60,15 +65,40 @@ agents:
## Valuation Method Matrix
### I. Absolute Valuation
- **DCF Model**: Build a 3-stage discounted free cash flow model
- Forecast period (5 years): based on historical growth, industry cycle, management guidance
- Transition period (years 6–10): convergence toward industry average
- Terminal value: Gordon Growth Model, perpetuity growth rate 1–3%
- WACC: computed from capital structure (target company beta, risk-free rate, equity risk premium)
- **DDM Model** (for high-dividend securities): dividend discount, implied return vs current price
**There is no DCF or WACC engine in this system.** Nothing computes a
discounted cash flow model for you, so you must either build the
arithmetic yourself in `bash` or say the number does not exist. Never
report a DCF value you did not actually compute from sourced inputs.
- **DCF assumption set** — lay out the 3-stage structure and state the
inputs a valuation would require, each tagged `sourced` or `assumed`:
- Historical FCF spine (`sourced`): pull the cash flow statement with
`get_financial_statements` and derive FCF ≈ operating cash flow − capex.
- Capital structure and beta (`sourced`): `get_stock_profile` returns
`beta`, `enterpriseValue`, `totalDebt`, `totalCash`, `sharesOutstanding`.
- Forecast-period growth, transition-period convergence, perpetuity
growth (1–3%), equity risk premium and cost of debt (`assumed`): no
tool supplies these. Declare each as an explicit assumption with your
reasoning. Do NOT present an assumed input as a retrieved one.
- If you do compute the model, show the WACC and per-year discounting
arithmetic in `bash` using the formulas in load_skill("valuation-model")
so every figure is reproducible. If you do not, say so and skip to
relative valuation rather than inventing a range.
- **Scenario target price** (the backed alternative): `financial_rigor`
with `command="three_scenario"` computes exact bull/base/bear target
prices from EPS-growth and target-PE assumptions. This is a real
calculator — prefer it over a hand-waved DCF range, and state that it
is a PE-based scenario model, not a discounted cash flow.
- **DDM Model** (for high-dividend securities): same rule — dividend
history is `sourced` via `get_financial_statements`; the payout growth
and discount rate are `assumed` and must be labelled as such.
### II. Relative Valuation
- **Comparable Company Method**: select 3–5 industry peers, compare P/E / P/B / P/S / EV/EBITDA / EV/Sales
- **Comparable Company Method**: select 3–5 industry peers and pull each
peer's multiples with `get_stock_profile` (`forwardPE`, `priceToBook`,
`enterpriseToEbitda`, `enterpriseToRevenue`, `pegRatio`). There is no
comps engine — you are assembling the table yourself from per-peer
lookups, so every cell must trace to a peer lookup you actually ran.
A peer whose lookup failed is reported as unavailable, not estimated.
- **Historical Valuation Method**: compare current P/E and P/B to 5-year historical percentile, assess relative richness/cheapness
- **PEG Analysis**: P/E divided by earnings growth rate, assess whether growth premium is justified
@@ -83,16 +113,20 @@ agents:
- Consumer: EV/EBITDA, brand premium estimation
Use load_skill("valuation-model") for valuation modeling standards; load_skill("earnings-forecast") for earnings forecasting methods.
Use the factor_analysis tool to extract valuation factor data.
Use `get_financial_statements` for the historical statement spine, `get_stock_profile`
for multiples / beta / share count, and `financial_rigor` for exact arithmetic
(`verify_valuation`, `three_scenario`, `calc`). Those three are your only data
sources — every valuation figure must trace to one of them, and no other tool
in this system values a company.
## Output Requirements
1. **Valuation Summary Conclusion** — Explicit "overvalued / fair / undervalued" judgment with margin of safety calculation (% premium/discount of current price vs intrinsic value)
2. **DCF Key Assumptions and Calculation** — WACC, terminal growth rate, forecast period revenue growth rate and other key assumptions; DCF valuation range (bear / base / bull)
3. **Comparable Company Valuation Matrix** — Key valuation multiples for peer companies, explaining relative premium/discount and rationale
2. **Valuation Assumption Ledger and Scenario Range** — Table every input as `sourced` (name the tool call) or `assumed` (name your reasoning): WACC components, terminal growth rate, forecast growth. Then give the bull/base/bear target-price range from `financial_rigor` `three_scenario`, labelled as a PE-scenario model. If you also built a DCF by hand in `bash`, show the arithmetic; if you did not, state plainly that no DCF value is being reported because no DCF engine exists
3. **Comparable Company Valuation Matrix** — Peer multiples, one row per peer, each traced to the `get_stock_profile` call that produced it; explain relative premium/discount and rationale. Mark any peer you could not retrieve as unavailable
4. **Historical Valuation Percentile** — Current P/E and P/B vs historical percentile, interpreted alongside fundamental changes
5. **Target Price Calculation** — Weighted multi-method target price with 12-month upside/downside range
6. **Valuation Catalysts** — 3–5 positive and negative catalysts that could drive re-rating
tools: [bash, read_file, write_file, load_skill, factor_analysis]
tools: [bash, read_file, write_file, load_skill, get_financial_statements, get_stock_profile, financial_rigor]
skills: [valuation-model, earnings-forecast]
max_iterations: 50
timeout_seconds: 1800
@@ -190,7 +224,7 @@ tasks:
- id: task-valuation
agent_id: valuation_analyst
prompt_template: "Conduct multi-method cross-validated valuation of {target}, including DCF, comparable company method, and historical valuation method. Provide a target price range."
prompt_template: "Value {target} by cross-validating the methods you can actually source: peer multiples from get_stock_profile, historical valuation percentiles, and a three_scenario target-price range from financial_rigor. If you build a DCF, build it explicitly in bash and label every assumed input; if you do not, say so. Provide a target price range with the method behind it named."
depends_on: []
- id: task-quality
@@ -70,7 +70,10 @@ agents:
### Valuation bubble
- Use load_skill("fundamental-filter") for valuation framework
- PE/PB/PS vs historical percentile and industry premium
- Gap between DCF intrinsic value and market price
- Gap between price and fair value — there is no DCF engine here, so do NOT
report a DCF intrinsic value. Anchor fair value on something you can
source: historical multiple percentiles, or a mean-reversion target from
a stated multiple and earnings assumption, with the assumption labelled
- Promised vs delivered earnings track record
- Red flags in goodwill, deferred revenue, receivables, etc.
@@ -84,15 +87,21 @@ agents:
- Use load_skill("risk-analysis") and load_skill("volatility") for quant risk
- Historical max drawdown; tail metrics (VaR/CVaR)
- Correlation and beta vs market/industry
- Short cost and unusual signals in option-implied vol
- Macro headwinds: rates, FX, regulation
Compute the risk numbers, do not assert them. From bars pulled with
`get_market_data`, in `bash`:
`src.quantlib.risk.historical_var` / `parametric_var` / `historical_cvar`
(confidence and horizon are arguments — state which you used),
`max_drawdown_analysis(equity)` for the drawdown profile, and
`fit_gpd_tail(returns)` for the tail beyond the empirical sample.
You have no options tool, so do not report option-implied vol or short cost.
## Required outputs
1. **Bear risk bullets** — 3–5 one-line top bear points, each with severity (high/medium)
2. **Technical breakdown risk** — All topping patterns and divergences, with resistance and stop levels
3. **Valuation bubble assessment** — Quantify premium vs fair value; fair-value / mean-reversion downside target
3. **Valuation bubble assessment** — Quantify premium vs fair value; state the fair-value method (historical percentile or stated-multiple mean reversion) and its assumptions, not a DCF
4. **Fundamental deterioration evidence** — Concrete data for weakening quality
5. **Risk metrics** — VaR, expected max drawdown, downside under stress scenarios
5. **Risk metrics** — VaR and CVaR from `src.quantlib.risk` (name the confidence level and horizon), max drawdown from `max_drawdown_analysis`, tail downside from `fit_gpd_tail`; state the sample window every figure came from
6. **Bear target prices** — Downside range from multiple mean reversion and earnings cuts
7. **What would disprove the bear case** — Signals that would invalidate the bear thesis (i.e., bull-side rebuttal points)
@@ -134,6 +143,18 @@ agents:
- Stop discipline: recommended stop levels and triggers
- Hedges: options or related shorts to trim tail risk
Every number above is computed, not judged. Pull bars with `get_market_data`
and run, in `bash`:
- realised vol, ADV and correlation from the bars themselves
- `src.quantlib.risk.historical_var` / `historical_cvar` for the loss levels,
`fit_gpd_tail` for the black-swan tail, `max_drawdown_analysis` for drawdown
- `monte_carlo_gbm(...)` + `analyze_mc_results(...)` for the three stress
scenarios; report the seed and path count so the run is reproducible
Name the sample window for each figure. If you could not retrieve bars, say
the risk metrics are unavailable and give a qualitative review — an
unsourced VaR or stop price is worse than none. You have no options tool:
recommend a hedge structure if you wish, but do not price it.
### Compliance & concentration
- Single-name cap (often 5–10% of NAV)
- Industry / market concentration
@@ -149,7 +170,7 @@ agents:
7. **Risk conditions** — If approval is conditional, state prerequisites (e.g., wait for earnings)
Stay independent; do not cheerlead either side; optimize portfolio-level risk.
tools: [bash, read_file, write_file, load_skill]
tools: [bash, read_file, write_file, load_skill, get_market_data]
skills: [risk-analysis, volatility, correlation-analysis]
max_iterations: 50
timeout_seconds: 1800
@@ -53,8 +53,15 @@ agents:
5. **Correlation stability report** — Top 20: did correlation collapse materially in past 2y? Fragile pairs and warnings
Use load_skill("correlation-analysis"), load_skill("pair-trading"), load_skill("multi-factor").
Use factor_analysis for correlation and clustering.
tools: [bash, read_file, write_file, load_skill, factor_analysis]
Pull the price panel with `get_market_data` and compute the correlation matrix,
the clustering and the ratio statistics yourself in `bash` (pandas / scipy).
`factor_analysis` computes IC/IR and layered NAV from CSVs you write — it does
not compute correlations or clusters, so do not cite it for them. Use
`screen_market` to enumerate the {market} / {sector} universe before scanning.
Every correlation, cluster and score must come from the panel you pulled;
name the date range. A pair whose history you could not retrieve is excluded
from the pool, not scored from expectation.
tools: [bash, read_file, write_file, load_skill, factor_analysis, get_market_data, screen_market]
skills: [correlation-analysis, pair-trading, multi-factor]
max_iterations: 50
timeout_seconds: 1800
@@ -116,15 +123,33 @@ agents:
- Historical stability of cointegration × 15
## Required outputs
1. **Cointegration summary** — EG p / Johansen / verdict—strong / weak / none—table
1. **Cointegration summary** — Engle–Granger p from `cointegration_test` and the verdict (strong / weak / none) per pair. Include a Johansen column only if you implemented and ran the test yourself; otherwise mark it "not computed" and base the verdict on EG alone
2. **OU parameter table** — θ, μ, σ, half-life for passing pairs—sort by half-life
3. **Hedge-ratio stability** — Top 10: OLS vs Kalman history—stable / medium / highly time-varying
4. **Hurst & MR quality rank** — Hurst + half-life joint “tradable mean-reversion” rank
5. **Rolling cointegration** — Top 10: 12m rolling window p-value time series; periods of failure; fragility today
Use load_skill("correlation-analysis"), load_skill("quant-statistics").
Use factor_analysis for ADF, Johansen, Kalman where applicable.
tools: [bash, read_file, write_file, load_skill, factor_analysis]
## What is implemented vs what is not
`factor_analysis` computes IC/IR and layered NAV from a factor CSV and a
return CSV you write yourself. It does NOT run ADF, Johansen or Kalman —
never cite it as the source of a test statistic.
Pull the price series with `get_market_data`, then in `bash`:
- `src.quantlib.timeseries.adf_test(series, significance)` → Step 1 unit-root test
- `src.quantlib.timeseries.cointegration_test(y, x, significance)` → Engle–Granger
- `src.quantlib.timeseries.find_hedge_ratio(y, x)` → the static OLS β
- `src.quantlib.timeseries.compute_half_life(spread)` → t½ directly
These lazily import an optional statistics backend and raise an actionable
`ImportError` if it is absent. If that happens, report the test as
unavailable — do not substitute a judgement call for a p-value.
**KPSS, Johansen (trace / max-eigenvalue), Kalman time-varying β, VECM and
the Hurst exponent are NOT implemented anywhere in this system.** Either
write and show the estimator yourself in `bash`, or drop that row and say
it was not computed. A Johansen trace statistic or a Hurst value that did
not come out of code you ran is a fabricated number — the classification
thresholds above then apply only to the tests you actually completed.
tools: [bash, read_file, write_file, load_skill, factor_analysis, get_market_data]
skills: [correlation-analysis, quant-statistics]
max_iterations: 50
timeout_seconds: 1800
@@ -14,9 +14,23 @@ agents:
## Attribution framework
### Brinson attribution
Do NOT hand-estimate these three effects — they are implemented. Run them
in `bash`:
`from src.quantlib.attribution import brinson_fachler, carino_link`
- `brinson_fachler(portfolio_weights, benchmark_weights, portfolio_returns,
benchmark_returns)` returns a `BrinsonResult` carrying a `SectorEffect`
per sector (allocation / selection / interaction, each with `.total`)
plus `.total_effect`.
- For a multi-period review, compute one `BrinsonResult` per period and
pass the sequence to `carino_link` so the sub-period effects compound to
the true cumulative active return instead of being naively summed.
- **Allocation effect**: P&L from sector/asset-class weights vs benchmark
- **Selection effect**: P&L from stock selection at given weights vs benchmark
- **Interaction effect**: cross term of allocation and selection
Weights and returns are inputs, not outputs: take the portfolio's weights
from {portfolio} and pull the sector/benchmark return series with
`get_market_data`. If a required series cannot be retrieved, report the
attribution as not computable for that sector — never fill the gap.
### Factor attribution
- Decompose excess return into: market, size, value, momentum, quality, industry exposures
@@ -27,14 +41,18 @@ agents:
- Identify top 5 contributors and bottom 5 detractors
## Required outputs
1. **Brinson three-way split** — Quantified % contribution from allocation / selection / interaction, broken out by industry
1. **Brinson three-way split** — Quantified % contribution from allocation / selection / interaction, broken out by industry, produced by `brinson_fachler` (Cariño-linked across sub-periods); state the weight and return series you fed it
2. **Factor exposure table** — Average portfolio factor exposures (β-like) and return attribution; identify dominant return drivers
3. **Stock contribution ranking** — Top 5 contributors and bottom 5 detractors with return source analysis
4. **Alpha quality** — Whether excess return is systematic alpha (persistent) vs luck; give a confidence score
5. **Attribution diagnosis** — Whether the book’s core thesis worked this period; what was right/wrong; improvements for next cycle
Use load_skill("performance-attribution") for methodology, load_skill("multi-factor") for factor exposure; factor_analysis for quant factor attribution.
tools: [bash, read_file, write_file, load_skill, factor_analysis]
Use load_skill("performance-attribution") for methodology, load_skill("multi-factor") for factor exposure.
`factor_analysis` computes IC/IR and layered NAV from a factor CSV and a return CSV
that you write yourself — it does not fetch data and does not perform Brinson
attribution. Use `get_market_data` for the return series, `src.quantlib.attribution`
for the Brinson decomposition, and `factor_analysis` only for the factor IC work.
tools: [bash, read_file, write_file, load_skill, factor_analysis, get_market_data]
skills: [performance-attribution, multi-factor]
max_iterations: 50
timeout_seconds: 1800
@@ -78,7 +96,18 @@ agents:
5. **Overall risk rating** — Portfolio risk score 1–5 stars (5 = highest risk); top 3 issues and suggested actions
Use load_skill("risk-analysis") for methodology, load_skill("volatility") for vol and VaR.
tools: [bash, read_file, write_file, load_skill]
VaR and CVaR are implemented — run them rather than estimating. Pull the
constituent bars with `get_market_data`, build the portfolio return series
at the current weights in `bash`, then:
`src.quantlib.risk.historical_var(returns, confidence, horizon)`,
`parametric_var(...)`, `historical_cvar(...)` at both 95% and 99%,
`max_drawdown_analysis(equity)`, and `fit_gpd_tail(returns)` for the tail
beyond the sample. For the −20% market shock, apply the portfolio's
measured beta to the shock rather than asserting a loss figure.
ADV and pairwise correlations come from the same bars. If a position's bars
cannot be retrieved, list it as uncovered and state what share of NAV the
risk report therefore excludes — never extrapolate over the gap.
tools: [bash, read_file, write_file, load_skill, get_market_data]
skills: [risk-analysis, volatility]
max_iterations: 50
timeout_seconds: 1800
@@ -121,7 +150,18 @@ agents:
5. **Execution improvements** — Concrete fixes (order types, slicing, optimal time windows)
Use load_skill("execution-model") for execution analytics, load_skill("market-microstructure") for microstructure-driven costs.
tools: [bash, read_file, write_file, load_skill]
Execution analysis needs FILLS, and fills only come from the user. Parse a
broker blotter (同花顺 / 东财 / 富途 / generic CSV) with
`analyze_trade_journal`, and pull the matching bars with `get_market_data`
to build VWAP/TWAP benchmarks in `bash`. Market impact is modelled, not
observed — `src.quantlib.impact.linear_impact` and `sqrt_impact` give the
estimate, `fixed_slippage` the flat-bps baseline; name which model and which
coefficient you used, and label the output an estimate.
If no blotter was supplied, say so and report execution quality as not
assessable. Do not synthesise fill prices, VWAP deviations, or an
implementation shortfall from position weights alone.
tools: [bash, read_file, write_file, load_skill, analyze_trade_journal, get_market_data]
skills: [execution-model, market-microstructure]
max_iterations: 50
timeout_seconds: 1800
@@ -93,6 +93,24 @@ agents:
5. **Risk recommendations** — Position sizing, stops, risk-control improvements
Use load_skill for volatility methods.
You are auditing a backtest, so the return series already exists: read the
backtester's output files with `read_file` (they are the audit's evidence
base — do not re-derive equity from memory), then in `bash`:
- `src.quantlib.risk.max_drawdown_analysis(equity)` → output 1
- annualised and downside vol from the returns;
`src.quantlib.timeseries.fit_garch(returns)` for the clustering claim in
output 2, and `autocorrelation_test` / `heteroscedasticity_test` to back it
- `src.quantlib.risk.historical_var` / `parametric_var` / `historical_cvar`
and `fit_gpd_tail` → output 3 (name confidence, horizon, sample)
- `src.quantlib.timeseries.bootstrap_sharpe(returns)` → a confidence interval
on the strategy's Sharpe. Report the interval: a single Sharpe from one
backtest is the number most likely to be overfit, and the interval is the
honest form of output 4
For output 4, an in-sample/out-of-sample gap requires an actual out-of-sample
run. If the backtester produced only one period, say the split was not run
and that overfitting is therefore unmeasured — do not assert robustness that
was never tested.
tools: [bash, read_file, write_file, load_skill]
skills: [volatility]
max_iterations: 50
+31 -11
View File
@@ -20,8 +20,16 @@ agents:
4. **Current drawdown state** — Whether in a drawdown now; distance from last high
5. **Drawdown alert** — Estimated drawdown probability based on volatility and trend
Use load_skill for data access and volatility methods.
tools: [bash, read_file, write_file, load_skill]
`load_skill` loads methodology documents; it does NOT fetch data. Pull the
price history with `get_market_data`, build the equity curve in `bash`, then
call `src.quantlib.risk.max_drawdown_analysis(equity)` for the drawdown
profile. Rank the top-5 events and the recovery times from that output.
For output 5, run `src.quantlib.risk.monte_carlo_gbm(...)` +
`analyze_mc_results(...)` and report the seed, path count and drift/vol
inputs so the probability is reproducible — a drawdown probability stated
without a simulation behind it is a guess and must not be given.
If the history cannot be retrieved, say so and produce nothing numeric.
tools: [bash, read_file, write_file, load_skill, get_market_data]
skills: [volatility]
max_iterations: 50
timeout_seconds: 1800
@@ -38,14 +46,17 @@ agents:
{upstream_context}
## Required outputs
1. **VaR estimates** — 95%/99%/99.9% VaR via parametric + historical simulation
2. **CVaR (ES)** — Conditional tail expectation / expected shortfall
3. **Stress tests** — At least three historical crisis scenarios with simulated loss
4. **Tail event probability** — Extreme-value (GEV/GPD style) probability framing where appropriate
5. **Protection ideas** — Methods to hedge tail risk
1. **VaR estimates** — 95%/99% VaR from `src.quantlib.risk.parametric_var` AND `historical_var` on the same return series, reported side by side. 99.9% historical VaR needs roughly 1000+ observations to mean anything: if your sample is shorter, say so and give the parametric figure only
2. **CVaR (ES)** — `src.quantlib.risk.historical_cvar` at each confidence level
3. **Stress tests** — At least three historical crisis scenarios. Define each by its DATE RANGE, pull that window with `get_market_data`, and report the realised loss over it. Do not quote a crisis loss from memory
4. **Tail event probability** — `src.quantlib.risk.fit_gpd_tail(returns, threshold_pct)`; report the threshold you used, since the fit depends on it
5. **Protection ideas** — Qualitative hedge structures. You have no options tool, so describe the structure; do not price it
Use load_skill for vol analytics and statistical methods.
tools: [bash, read_file, write_file, load_skill]
`load_skill` loads methodology documents; it does NOT fetch data — pull returns
with `get_market_data` first. Every figure above must name the sample window
it came from. If the series cannot be retrieved, report the section as
unavailable rather than producing an illustrative number.
tools: [bash, read_file, write_file, load_skill, get_market_data]
skills: [volatility]
max_iterations: 50
timeout_seconds: 1800
@@ -68,8 +79,17 @@ agents:
4. **Historical analogs** — 2–3 past periods most similar to today
5. **Forward look** — 1–3 month path probabilities under a regime framework
Use load_skill for technical and volatility methods.
tools: [bash, read_file, write_file, load_skill]
`load_skill` loads methodology documents; it does NOT fetch data. Pull bars
with `get_market_data` and derive the regime evidence in `bash`:
realised vol and trend from the bars, `technical_indicators` for the
momentum readings, `src.quantlib.timeseries.fit_garch(returns, horizon)` for
volatility persistence, and `src.quantlib.risk.monte_carlo_gbm` +
`analyze_mc_results` for the forward path probabilities (state seed and
path count). There is no regime-switching model (HMM or otherwise) in this
system — your regime label is a rule you define on measured inputs, so state
the rule. For historical analogs, name the date range of each and show the
measured similarity; an analog you did not measure is an anecdote, label it one.
tools: [bash, read_file, write_file, load_skill, get_market_data, technical_indicators]
skills: [volatility, technical-basic]
max_iterations: 50
timeout_seconds: 1800
@@ -41,8 +41,27 @@ agents:
5. **Cointegration stability report** — Rolling tests on Top 10; flag structural breaks
Use load_skill("pair-trading"), load_skill("quant-statistics").
Use factor_analysis for correlation and joint factor exposure to reduce spurious cointegration.
tools: [bash, read_file, write_file, load_skill, factor_analysis]
## What is implemented vs what is not
`factor_analysis` computes IC/IR and layered NAV from a factor CSV and a
return CSV you write — it does not compute correlations or cointegration.
Pull the price panel with `get_market_data` (`screen_market` to enumerate the
universe first), then in `bash`:
- `src.quantlib.timeseries.adf_test(series)` — unit-root precondition
- `src.quantlib.timeseries.cointegration_test(y, x)` — the p-value in output 1
- `src.quantlib.timeseries.find_hedge_ratio(y, x)` — the OLS hedge ratio
- `src.quantlib.timeseries.compute_half_life(spread)` — half-life for output 4
- `src.quantlib.timeseries.granger_test(data, x_col, y_col, max_lag)` — the
price-discovery direction
- `src.quantlib.timeseries.bootstrap_statistic(...)` — confidence intervals
on any spread statistic you report
These lazily import an optional statistics backend and raise an actionable
`ImportError` if it is absent; if that happens, report the test as
unavailable rather than guessing a p-value.
**The Hurst exponent and a Kalman time-varying hedge ratio are NOT
implemented here.** Write and show the estimator yourself in `bash`, or drop
those columns and say they were not computed — do not populate them by feel.
tools: [bash, read_file, write_file, load_skill, factor_analysis, get_market_data, screen_market]
skills: [pair-trading, quant-statistics, correlation-analysis]
max_iterations: 50
timeout_seconds: 1800
@@ -83,7 +102,19 @@ agents:
5. **Liquidity risk alerts** — Pairs likely to dry up in stress (e.g. one-sided drawdown days); contingency exit plan
Use load_skill("market-microstructure"), load_skill("execution-model").
tools: [bash, read_file, write_file, load_skill]
ADV, turnover and intraday liquidity are measured from bars — pull them with
`get_market_data` (intraday interval for the time-of-day question). Market
impact is MODELLED, not observed: `src.quantlib.impact.linear_impact` and
`sqrt_impact` give the estimate (the sqrt form needs ADV and volatility),
`fixed_slippage` the flat-bps baseline, and `delayed_execution` shows the
cost of acting a bar late. Name the model and the coefficient you used and
label the output an estimate.
**There is no order-book depth or quote feed here** — depth, bid-ask spread
and queue position cannot be measured. Score depth from a stated proxy
(turnover, ADV) and say it is a proxy, or leave it out. Granger causality
between legs comes from `src.quantlib.timeseries.granger_test`.
tools: [bash, read_file, write_file, load_skill, get_market_data]
skills: [market-microstructure, execution-model]
max_iterations: 50
timeout_seconds: 1800
@@ -181,7 +212,20 @@ agents:
5. **Verdict** — Pass / conditional pass / fail; pre-launch conditions and ongoing monitors
Use load_skill("risk-analysis"), load_skill("volatility").
tools: [bash, read_file, write_file, load_skill]
Compute the risk numbers from data. Pull the leg histories with
`get_market_data`, build the book's return series at the proposed weights in
`bash`, then use `src.quantlib.risk.historical_var` / `parametric_var` /
`historical_cvar` for output 2 (state confidence and horizon),
`max_drawdown_analysis` for the drawdown view, and `fit_gpd_tail` for the
joint-failure tail. For the "correlation zeros" stress, rebuild the return
series under that assumption and re-run VaR — do not scale the base number by
a guessed multiplier. `src.quantlib.timeseries.bootstrap_sharpe` gives a
confidence interval on the book's Sharpe; report the interval, not a point
estimate, when the sample is short.
Net beta and factor tilts must be regressed against a benchmark series you
pulled. Any figure without a named sample window does not go in the report.
tools: [bash, read_file, write_file, load_skill, get_market_data]
skills: [risk-analysis, volatility]
max_iterations: 50
timeout_seconds: 1800
@@ -65,8 +65,13 @@ agents:
6. **Key levels** — strong/weak support and resistance with prices
7. **Composite TA score** — −5 bearish to +5 bullish aggregate
Every claim needs explicit price levels—no vague prose.
tools: [bash, read_file, write_file, load_skill]
Every claim needs explicit price levels—no vague prose. Those levels must be
READ OFF DATA: pull {timeframe} bars with `get_market_data` and compute the
moving averages, MACD/RSI/KDJ and volume statistics with
`technical_indicators` (or in `bash` from the bars). A support, resistance,
MA value or measured target that you did not derive from retrieved bars is
fabricated — if the bars cannot be retrieved, say so and give no levels.
tools: [bash, read_file, write_file, load_skill, get_market_data, technical_indicators]
skills: [technical-basic, candlestick]
max_iterations: 50
timeout_seconds: 1800
@@ -117,8 +122,13 @@ agents:
6. **Five-element resonance** — price/Tenkan/Kijun/Chikou/cloud alignment
7. **Ichimoku score** — −5..+5 with element weights
Cover all five building blocks—no partial analysis.
tools: [bash, read_file, write_file, load_skill]
Cover all five building blocks—no partial analysis. Ichimoku is fully
determined by the bars: pull them with `get_market_data` and compute
Tenkan / Kijun / Senkou A / Senkou B / Chikou in `bash` from the standard
9 / 26 / 52 windows (state the windows if you deviate). Cloud bounds and
the quantised S/R prices are outputs of that calculation, never estimates.
If the bars cannot be retrieved, report the analysis as unavailable.
tools: [bash, read_file, write_file, load_skill, get_market_data, technical_indicators]
skills: [ichimoku]
max_iterations: 50
timeout_seconds: 1800
@@ -261,8 +271,14 @@ agents:
6. **Optimal entry zone** — combined OB/FVG/liquidity for long/short
7. **SMC score** — −5..+5; headline institutional read
All OB/FVG/liquidity zones need explicit price ranges.
tools: [bash, read_file, write_file, load_skill]
All OB/FVG/liquidity zones need explicit price ranges, and every range must
be located in retrieved bars: pull them with `get_market_data` (minute or
intraday interval as {timeframe} requires) and identify swings, order blocks
and fair-value gaps from the actual OHLC sequence in `bash`. A fair-value gap
is a specific three-bar condition — point to the bars that form it. Zones you
cannot locate in the data do not get listed, and a "fill probability" must be
a measured historical fill rate or be omitted.
tools: [bash, read_file, write_file, load_skill, get_market_data]
skills: [smc, minute-analysis]
max_iterations: 50
timeout_seconds: 1800
@@ -148,8 +148,13 @@ agents:
- Honestly report information gaps: if the four masters lack information on some dimension, state "under missing X, conclusion confidence is Y".
- Use report_audit (extract → verdict) as a quality gate on key numbers in the final report.
Use load_skill("report-generate") for the report format and writing standards.
You have no data tool: every figure must already appear in {upstream_context}.
Carry forward any dimension an upstream lens reported as missing rather than
resolving it yourself.
Output the full Markdown report and save it with write_file.
tools: [bash, read_file, write_file, report_audit]
tools: [bash, read_file, write_file, load_skill, report_audit]
skills: [report-generate]
max_iterations: 50
timeout_seconds: 1800
+243
View File
@@ -0,0 +1,243 @@
"""CI gate: a swarm preset may not order work its own grants cannot reach.
Swarm workers get a strict per-node tool whitelist. ``build_swarm_registry``
projects the full registry down to ``agent_spec.tools`` and merely *logs* a
warning for anything it had to drop (``src/tools/__init__.py::_filter_registry``),
so a preset whose prompt instructs the worker to "use the ``foo`` tool" while
``foo`` is absent from that node's ``tools:`` list fails silently at runtime —
the model is told to call something it was never handed, and the usual failure
mode is that it invents the output instead.
Two structural claims are checkable without judging prose, and both are checked
here:
1. **Every real tool named in a node's prompt is granted to that node.** The
match set is deliberately narrowed twice. Only ``snake_case`` identifiers are
considered, and of those only the ones that are *actually* registered tool
names. Bare English words are what makes this scan otherwise worthless:
``sentiment``, ``pattern``, ``backtest`` and ``remember`` are all genuine tool
names AND ordinary vocabulary, so a word-boundary search on the plain names
produces almost nothing but false positives. Requiring an underscore removes
that entire class, and intersecting with the live registry removes the rest.
2. **Every ``skills:`` entry resolves to a ``SKILL.md`` on disk.** ``load_skill``
resolves a skill by directory name; a preset naming a skill that was renamed
or removed hands the worker a dead reference.
The gate is a regression fence, not a discovery tool: it is expected to pass.
When it fails, the fix is to grant the tool, correct the skill name, or stop
naming the tool in the prompt — never to relax the assertion.
"""
from __future__ import annotations
import re
from pathlib import Path
import pytest
import yaml
from src.swarm.presets import PRESETS_DIR
from src.tools import build_registry
#: Skill bodies live one directory per skill, each holding a ``SKILL.md``.
SKILLS_DIR = Path(__file__).resolve().parents[1] / "src" / "skills"
#: ``snake_case`` only — at least one underscore between lowercase/digit runs.
#: This is the single most important constraint in the file; see the module
#: docstring for why a looser pattern makes the scan meaningless.
_SNAKE_CASE = re.compile(r"\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b")
#: Prompt contexts that unambiguously present a name as a callable tool.
#: Kept narrow on purpose: a bare backtick is NOT a tool-reference context,
#: because presets legitimately backtick Python helpers such as
#: ``src.quantlib.risk.historical_var`` that are imported under ``bash`` rather
#: than dispatched through the tool registry.
_TOOL_REFERENCE_CONTEXTS = (
re.compile(r"\b([a-z][a-z0-9]*(?:_[a-z0-9]+)+)\s+tool\b"),
re.compile(r"\bthe\s+`?([a-z][a-z0-9]*(?:_[a-z0-9]+)+)`?\s+tool\b"),
)
def _preset_paths() -> list[Path]:
"""Every bundled preset YAML, sorted for stable test ids.
Returns:
Sorted list of paths to the bundled ``*.yaml`` presets.
"""
return sorted(PRESETS_DIR.glob("*.yaml"))
def _registered_tool_names() -> frozenset[str]:
"""Names of every tool a swarm worker could possibly be granted.
Shell tools are included because a preset may legitimately whitelist
``bash``; whether the operator enabled them at runtime is a separate
concern from whether the preset's prompt is self-consistent.
Returns:
Frozen set of registered tool names.
"""
return frozenset(build_registry(include_shell_tools=True).tool_names)
def _agent_prompt_text(preset: dict, agent_id: str, agent: dict) -> str:
"""All prompt text a given agent will actually see.
An agent's instructions arrive from two places: its own ``system_prompt``
and the ``prompt_template`` of every task routed to it. A tool named in
either one is a tool the worker is being told to call.
Args:
preset: The parsed preset mapping.
agent_id: The agent's ``id``.
agent: The agent's own spec mapping.
Returns:
The agent's system prompt concatenated with its task templates.
"""
parts = [agent.get("system_prompt") or ""]
parts.extend(
task.get("prompt_template") or ""
for task in preset.get("tasks") or []
if task.get("agent_id") == agent_id
)
return "\n".join(parts)
def _load(path: Path) -> dict:
"""Parse one preset YAML.
Args:
path: Path to the preset file.
Returns:
The parsed mapping.
"""
return yaml.safe_load(path.read_text(encoding="utf-8"))
PRESET_PATHS = _preset_paths()
def test_presets_are_discovered() -> None:
"""Guard the glob itself, so a path regression cannot vacuously pass.
Every other test in this module is parametrised over ``PRESET_PATHS``; if
that list were ever empty the whole gate would report success while
checking nothing.
"""
assert PRESET_PATHS, f"no presets found under {PRESETS_DIR}"
@pytest.mark.parametrize("path", PRESET_PATHS, ids=lambda p: p.stem)
def test_named_tools_are_granted_to_the_node_that_names_them(path: Path) -> None:
"""A prompt may not instruct a worker to use a tool the node lacks.
Args:
path: The preset under test.
"""
registered = _registered_tool_names()
preset = _load(path)
violations: list[str] = []
for agent in preset.get("agents") or []:
agent_id = agent.get("id", "<unnamed>")
granted = set(agent.get("tools") or [])
text = _agent_prompt_text(preset, agent_id, agent)
named_tools = {
token for token in _SNAKE_CASE.findall(text) if token in registered
}
for missing in sorted(named_tools - granted):
violations.append(
f"{path.name}::{agent_id} prompt names the registered tool "
f"{missing!r} but its tools list is {sorted(granted)}"
)
assert not violations, (
"Preset prompts name tools the node was never granted; the worker "
"cannot call them and will fabricate the output instead. Grant the "
"tool or stop naming it:\n " + "\n ".join(violations)
)
@pytest.mark.parametrize("path", PRESET_PATHS, ids=lambda p: p.stem)
def test_prompts_do_not_reference_nonexistent_tools(path: Path) -> None:
"""A prompt may not call something a "tool" when no such tool exists.
Catches the inverse of the previous check: a name that reads as a tool
reference but matches nothing in the registry, which is a demand the worker
can only satisfy by inventing a result.
Args:
path: The preset under test.
"""
registered = _registered_tool_names()
preset = _load(path)
violations: list[str] = []
for agent in preset.get("agents") or []:
agent_id = agent.get("id", "<unnamed>")
text = _agent_prompt_text(preset, agent_id, agent)
referenced: set[str] = set()
for pattern in _TOOL_REFERENCE_CONTEXTS:
referenced.update(pattern.findall(text))
for phantom in sorted(referenced - registered):
violations.append(
f"{path.name}::{agent_id} refers to {phantom!r} as a tool, "
"but no such tool is registered"
)
assert not violations, (
"Preset prompts reference tools that do not exist:\n "
+ "\n ".join(violations)
)
@pytest.mark.parametrize("path", PRESET_PATHS, ids=lambda p: p.stem)
def test_declared_skills_exist_on_disk(path: Path) -> None:
"""Every ``skills:`` entry must resolve to a real ``SKILL.md``.
Args:
path: The preset under test.
"""
preset = _load(path)
violations: list[str] = []
for agent in preset.get("agents") or []:
agent_id = agent.get("id", "<unnamed>")
for skill in agent.get("skills") or []:
if not (SKILLS_DIR / skill / "SKILL.md").is_file():
violations.append(
f"{path.name}::{agent_id} declares skill {skill!r}, but "
f"{SKILLS_DIR / skill / 'SKILL.md'} does not exist"
)
assert not violations, (
"Presets declare skills with no SKILL.md on disk:\n "
+ "\n ".join(violations)
)
@pytest.mark.parametrize("path", PRESET_PATHS, ids=lambda p: p.stem)
def test_every_node_naming_a_skill_can_load_it(path: Path) -> None:
"""A node with ``skills:`` must also be granted ``load_skill``.
Declaring a skill is inert on its own — the worker still has to call
``load_skill`` to pull the body into context. A node carrying skills it
cannot load is being pointed at methodology it will never actually read.
Args:
path: The preset under test.
"""
preset = _load(path)
violations = [
f"{path.name}::{agent.get('id', '<unnamed>')} declares skills "
f"{agent.get('skills')} but was not granted 'load_skill'"
for agent in preset.get("agents") or []
if (agent.get("skills") or []) and "load_skill" not in (agent.get("tools") or [])
]
assert not violations, (
"Presets declare skills on nodes that cannot load them:\n "
+ "\n ".join(violations)
)