Skip to content

Latest commit

 

History

History
1296 lines (1216 loc) · 131 KB

api_reference.md

File metadata and controls

1296 lines (1216 loc) · 131 KB

TradingAgents API Reference (fork)

Complete reference for the tradingagents package: configuration, graph flow, state, structured output, the vendor data contract, strategy overlays, persistence, reporting, CLI, and scripts. Generated values are checked against the code (config keys, tool/vendor tables). For environment and operational quirks see docs/AGENT_ONBOARDING.md; for the daily workflow see docs/howto_end_to_end.md.


1. Configuration

Two ways to configure: DEFAULT_CONFIG in tradingagents/default_config.py, overridden by any TRADINGAGENTS_* env var in the table below (value type is coerced from the existing default; a TRADINGAGENTS_MAX_WORKERS example lives in batch.py).

1.1 Env -> config overrides (complete)

The table is generated from the code — _ENV_OVERRIDES plus the literal os.getenv("TRADINGAGENTS_...", fallback) reads inside DEFAULT_CONFIG (the union is the surface). tests/test_api_reference_env_table.py re-derives that surface and asserts this table lists exactly it, keys each row correctly, uses one cell count, and lists no env var twice — so "complete" is checked, not claimed. Re-run py -3.12 scripts/gen_api_reference_table.py --write after changing a config key; --check exits non-zero when the table is stale.

Env var Config key Notes
TRADINGAGENTS_LLM_PROVIDER llm_provider default openai
TRADINGAGENTS_DEEP_THINK_LLM deep_think_llm default gpt-5.5
TRADINGAGENTS_QUICK_THINK_LLM quick_think_llm default gpt-5.4-mini
TRADINGAGENTS_LLM_BACKEND_URL backend_url default None
TRADINGAGENTS_OUTPUT_LANGUAGE output_language default English
TRADINGAGENTS_MAX_DEBATE_ROUNDS max_debate_rounds default 1
TRADINGAGENTS_MAX_RISK_ROUNDS max_risk_discuss_rounds per-round override; wins over RESEARCH_DEPTH
TRADINGAGENTS_RESEARCH_DEPTH research_depth ONE depth knob (1/3/5): drives BOTH research and risk debate rounds to the same level
TRADINGAGENTS_ENABLE_DEBATE enable_debate structured multi-agent debate on (default off — legacy one-shot chain unchanged)
TRADINGAGENTS_DEBATE_BULL_MODEL debate_bull_model per-role family:id models; empty = quick/deep fallback. BULL drives bull (research) + aggressive (risk); BEAR drives bear + conservative; JUDGE drives both blind judges; neutral = quick
TRADINGAGENTS_DEBATE_BEAR_MODEL debate_bear_model default empty
TRADINGAGENTS_DEBATE_JUDGE_MODEL debate_judge_model default empty
TRADINGAGENTS_DEBATE_NEUTRAL_MODEL debate_neutral_model family:id for the neutral RISK debater; empty = quick fallback
TRADINGAGENTS_DEBATE_JUDGE_ENSEMBLE debate_judge_ensemble default 1
TRADINGAGENTS_DEBATE_MAX_ROUNDS debate_max_rounds 5 (DebaterTurnPayload round_index 1..5)
TRADINGAGENTS_DEBATE_MAX_OUTPUT_TOKENS debate_max_output_tokens
TRADINGAGENTS_DEBATE_TEMPERATURE debate_temperature default 0.1
TRADINGAGENTS_DEBATE_JSON_MODE debate_json_mode default true
TRADINGAGENTS_DEBATE_MIN_GAIN debate_min_gain termination knobs
TRADINGAGENTS_DEBATE_STOP_CONSECUTIVE debate_stop_consecutive default 2
TRADINGAGENTS_DEBATE_CONSENSUS_THRESH debate_consensus_thresh default 0.85
TRADINGAGENTS_DEBATE_SCORING_WEIGHTS debate_scoring_weights default {'evidence': 0.6, 'novelty': 0.25, 'constraint': 0.15}
TRADINGAGENTS_DEBATE_ABSTAIN_ALLOWED debate_abstain_allowed default true
TRADINGAGENTS_DEBATE_FAST_ABORT debate_fast_abort default true
TRADINGAGENTS_DEBATE_REGEN_MAX debate_regen_max R1' bounded single-role regeneration budget (1)
TRADINGAGENTS_DEBATE_DIVERGENCE_CAP_ROUNDS debate_divergence_cap_rounds default 1
TRADINGAGENTS_DEBATE_REWEIGHT_TO_BASELINE debate_reweight_to_baseline R2' base α toward baseline (0.5)
TRADINGAGENTS_DEBATE_ENTRENCH_THRESH debate_entrench_thresh I_entrench above this -> penalty (0.8)
TRADINGAGENTS_DEBATE_DIVERGENCE_MIN debate_divergence_min default 0.15
TRADINGAGENTS_DEBATE_BASELINE_FALLBACK debate_baseline_fallback R1': an unverifiable debate degrades to the baseline stances (true, default); false stops the run with DebateBaselineFallbackError
TRADINGAGENTS_DEBATE_REQUIRE_CAPABILITY_MATRIX debate_require_capability_matrix R3 startup check: raises DebateCapabilityError for a debate role (assessed on its debate_*_model or the tier fallback) that cannot meet its floor (default false = warn only)
TRADINGAGENTS_CHECKPOINT_ENABLED checkpoint_enabled default false
TRADINGAGENTS_BENCHMARK_TICKER benchmark_ticker default None
TRADINGAGENTS_TEMPERATURE temperature default None
TRADINGAGENTS_TOP_P top_p default None
TRADINGAGENTS_FREQUENCY_PENALTY frequency_penalty default None
TRADINGAGENTS_PRESENCE_PENALTY presence_penalty default None
TRADINGAGENTS_LLM_MAX_RETRIES llm_max_retries default None
TRADINGAGENTS_MAX_TOKENS max_tokens default None; a hard cap on output tokens forwarded to every provider chat client (Gemini as max_output_tokens)
TRADINGAGENTS_FINNHUB_API_KEY finnhub_api_key
TRADINGAGENTS_FMP_API_KEY fmp_api_key
TRADINGAGENTS_EODHD_API_KEY eodhd_api_key EODHD daily OHLCV (free 20 calls/day; EOD plan $19.99/mo = 100k calls/day @ 1000/min, 30+ years) — a replacement for the moomoo K-line quota (100 calls/7 days)
TRADINGAGENTS_MASSIVE_API_KEY massive_api_key
TIINGO_API_KEY tiingo_api_key Tiingo market data (free Starter tier: EOD OHLCV + fundamental statements + IEX quote + crypto; ~1,000 calls/day)
TWELVEDATA_API_KEY twelve_data_api_key Twelve Data (free "Basic": 800 credits/day, 8/min; realtime US stocks/forex/crypto quotes + historical time-series OHLCV; tail of core_stock_apis)
STOCKDATA_API_KEY stockdata_api_key StockData.org (free "$0/mo": 100 requests/day; quote/EOD/intraday/news; tail of core_stock_apis + news_data)
NEWSAPI_API_KEY newsapi_api_key NewsAPI.org (free Developer: 100 req/day; global macro headlines; tail of news_data/get_global_news)
BENZINGA_API_KEY benzinga_api_key Benzinga (live key registered 2026-09-20; ticker news = headline+link, plus calendars/fundamentals/insider/congress on the same key). GDELT is keyless.
TRADINGAGENTS_PATENTSVIEW_API_KEY patentsview_api_key
TRADINGAGENTS_ENABLE_MASSIVE_FLAT enable_massive_flat default false
TRADINGAGENTS_MASSIVE_FLAT_DIR massive_flat_dir default data/massive_flat
TRADINGAGENTS_TOOL_CALL_LOG_DIR tool_call_log_dir default empty
TRADINGAGENTS_ALPACA_API_KEY_ID alpaca_api_key_id
TRADINGAGENTS_ALPACA_API_SECRET alpaca_api_secret
TRADINGAGENTS_ENABLE_ALPACA enable_alpaca default false
TRADINGAGENTS_MOOMOO_HOST moomoo_host default 127.0.0.1
TRADINGAGENTS_MOOMOO_PORT moomoo_port default 11111
TRADINGAGENTS_MOOMOO_ACCOUNT moomoo_account default None
TRADINGAGENTS_MOOMOO_AUTOSTART moomoo_autostart default false
TRADINGAGENTS_MOOMOO_OPEND_PATH moomoo_opend_path default None
TRADINGAGENTS_ENABLE_STRATEGY_OVERLAYS enable_strategy_overlays default true
TRADINGAGENTS_ENABLE_REFLECTION enable_reflection default true
TRADINGAGENTS_ENABLE_SENTIMENT enable_sentiment when on (default True), the sentiment report gets the computed StockTwits score + surprise velocity injected
TRADINGAGENTS_ENABLE_ORDERFLOW enable_orderflow default false
TRADINGAGENTS_ENABLE_POSITION_CONTRACT enable_position_contract default false
TRADINGAGENTS_ENABLE_CALIBRATION enable_calibration default false
TRADINGAGENTS_ENABLE_AGREEMENT enable_agreement consensus/agreement scaling of the size
TRADINGAGENTS_ENABLE_COMPOSITE_RANK enable_composite_rank default false
TRADINGAGENTS_ENABLE_FACTOR_PROFILE enable_factor_profile default false
TRADINGAGENTS_ENABLE_TOPK_DROP enable_topk_drop default false
TRADINGAGENTS_ENABLE_ENHANCED_INDEX enable_enhanced_index default false
TRADINGAGENTS_BACKTEST_LIMIT_THRESHOLD backtest_limit_threshold default 0.0
TRADINGAGENTS_BACKTEST_VOLUME_PARTICIPATION backtest_volume_participation default 0.0
TRADINGAGENTS_BACKTEST_DEAL_PRICE backtest_deal_price default close
TRADINGAGENTS_ENABLE_PIT_REGISTRY enable_pit_registry default false
TRADINGAGENTS_ENABLE_FACTOR_MODEL enable_factor_model default false
TRADINGAGENTS_ENABLE_DECISION_GUARDRAIL enable_decision_guardrail default false
TRADINGAGENTS_ENABLE_SKILL_OVERLAYS enable_skill_overlays inert (docstring-only mentions)
TRADINGAGENTS_SKILL_DIR skill_dir default empty
TRADINGAGENTS_ENABLE_NEWS_RELEVANCE enable_news_relevance default false
TRADINGAGENTS_ENABLE_REPORT_ATTRIBUTION enable_report_attribution default false
TRADINGAGENTS_ENABLE_REPORT_INFLUENCE enable_report_influence default false
TRADINGAGENTS_ENABLE_PREDICTION_LEDGER enable_prediction_ledger default false
TRADINGAGENTS_PREDICTION_HORIZON_DAYS prediction_horizon_days default 60
TRADINGAGENTS_LLM_TIER_MAP llm_tier_map default {}
TRADINGAGENTS_LLM_TIER_MODEL llm_tier_model default {}
TRADINGAGENTS_MONITOR_NOTIFY monitor_notify default false
TRADINGAGENTS_MONITOR_WEBHOOK monitor_webhook default empty
TRADINGAGENTS_ENABLE_FACTOR_PROPOSAL_LOOP enable_factor_proposal_loop default false
TRADINGAGENTS_ENABLE_TUNER enable_tuner default false
TRADINGAGENTS_ENABLE_INDEPENDENT_VOTE enable_independent_vote when on, the 3 risk + bull/bear stances are sampled INDEPENDENTLY before the debate and the agreement/consensus (G3 + the PM's dissent flag + the G1 contract multiply) comes from those uncontaminated pre-debate opinions — the debate stays the risk-surfacing layer
TRADINGAGENTS_ENABLE_SENTIMENT_FACTOR enable_sentiment_factor when on (default off), the position scale multiplies by 1 ± sentiment_factor_max_scale ONLY when the name's measured news-sentiment rank IC ≥ sentiment_factor_min_ic (else neutral 1.0, never blocks)
TRADINGAGENTS_SENTIMENT_FACTOR_MIN_IC sentiment_factor_min_ic measured rank-IC floor for the sentiment fold (default 0.02)
TRADINGAGENTS_SENTIMENT_FACTOR_MAX_SCALE sentiment_factor_max_scale max +/- position-scale move from the sentiment fold (default 0.2)
TRADINGAGENTS_SENTIMENT_FACTOR_MIN_SCALE sentiment_factor_min_scale floor for the sentiment fold scale (default 0.5)
TRADINGAGENTS_VOLATILITY_ESTIMATOR volatility_estimator overlay sizing estimator: close (default) \
TRADINGAGENTS_COVARIANCE_SHRINKAGE_ENABLE covariance_shrinkage_enable Ledoit-Wolf shrunk covariance in the covariance allocators (advisory, default off)
TRADINGAGENTS_COVARIANCE_SHRINKAGE_TARGET covariance_shrinkage_target shrinkage target: scaled_identity (default) \
TRADINGAGENTS_ENABLE_KELLY_ALLOC enable_kelly_alloc allocation block uses multi-asset fractional Kelly (advisory, default off)
TRADINGAGENTS_KELLY_FRACTION kelly_alloc_fraction fractional-Kelly scaling (default 0.25)
TRADINGAGENTS_ENABLE_EXITS enable_exits default false
TRADINGAGENTS_ENABLE_EXIT_ENGINE enable_exit_engine default false
TRADINGAGENTS_ENABLE_RISK_GOVERNOR enable_risk_governor default false
TRADINGAGENTS_ENABLE_DECISION_AUDIT enable_decision_audit on, the PM's report shows a claim-vs-computed audit note
TRADINGAGENTS_REGIME_STATE_ENABLE regime_state_enable regime-state size multiplier
TRADINGAGENTS_VOL_CAP_ENABLE vol_cap_enable volatility-cap size multiplier
TRADINGAGENTS_ENABLE_HARD_GUARDS enable_hard_guards hard-guard labels (max_portfolio_risk / data_quality_failure / insufficient_liquidity)
TRADINGAGENTS_ALPHA_LEDGER_ENABLE alpha_ledger_enable default false
TRADINGAGENTS_KNIFE_COMPOSITE_ENABLE knife_composite_enable default false
TRADINGAGENTS_KNIFE_SCORE_WEIGHTS knife_score_weights default [0.25, 0.2, 0.2, 0.2, 0.15]
TRADINGAGENTS_KNIFE_SCORE_BANDS knife_score_bands default [1.5, 2.5, 3.0]
TRADINGAGENTS_VALUE_DIP_KNIFE_ENABLE value_dip_knife_enable default false
TRADINGAGENTS_VALUE_DIP_VDU_ENABLE value_dip_vdu_enable strict: VDU ladder hard-gates entry (dry-up + trigger candle + RVOL>=1.3 + momentum confirmation); candidate=False -> no entry
TRADINGAGENTS_VALUE_DIP_REQUIRE_CATALYST value_dip_require_catalyst strict: value-dip needs re-rating evidence
TRADINGAGENTS_VALUE_DIP_REGIME_GATE value_dip_regime_gate strict: block dip entries in high-vol/fast-downtrend/catalyst windows
TRADINGAGENTS_VALUE_DIP_REGIME_VOL_CAP value_dip_regime_vol_cap vol_pct above this blocks MR entries (gate ON)
TRADINGAGENTS_VALUE_DIP_REGIME_DOWNTREND_BAND value_dip_regime_downtrend_band knife guard: price below 200-SMA by this
TRADINGAGENTS_VALUE_DIP_REGIME_HALVE value_dip_regime_halve instead of block, size x0.5
TRADINGAGENTS_RISK_DAILY_LOSS_BUDGET_PCT risk_daily_loss_budget_pct daily realized-loss cap -> de-risk
TRADINGAGENTS_RISK_HWM_SOFT_PCT risk_hwm_soft_pct drawdown-from-HWM tiers
TRADINGAGENTS_RISK_HWM_HARD_PCT risk_hwm_hard_pct default 0.2
TRADINGAGENTS_BREAKEVEN_TRIGGER breakeven_trigger atr \
TRADINGAGENTS_STOP_NEVER_WIDEN stop_never_widen unified stop never widened
TRADINGAGENTS_MIN_HOLDING_DAYS min_holding_days turnover guards
TRADINGAGENTS_MAX_TRADES_PER_PERIOD max_trades_per_period turnover guards
TRADINGAGENTS_SLEEVE_TAG_ENABLED sleeve_tag_enabled default true
TRADINGAGENTS_DRIFT_THRESHOLD drift_threshold alpha-decay win-rate drift trigger
TRADINGAGENTS_ENABLE_PREOPEN_RVOL enable_preopen_rvol P1: pre-market RVOL vs 30d pre-open avg (Alpaca free IEX)
TRADINGAGENTS_PREOPEN_RVOL_INSTITUTIONAL_X preopen_rvol_institutional_x P1: pre-market RVOL vs 30d pre-open avg (Alpaca free IEX)
TRADINGAGENTS_PSR_BENCHMARK_SHARPE psr_benchmark_sharpe PSR benchmark Sharpe (default 0.0)
TRADINGAGENTS_ROLLING_WINDOW rolling_window rolling evaluation window (default 132)
TRADINGAGENTS_DOWNSIDE_MAR downside_mar minimum acceptable return for downside measures (default 0.0)
TRADINGAGENTS_TRAILING_STOP_PCT trailing_stop_pct peak-to-exit trail % for trailing exits (default 0.05)
TRADINGAGENTS_ENABLE_TRAILING_EXIT enable_trailing_exit peak-trailing / give-back exits on (default OFF)
TRADINGAGENTS_RISK_PARITY_ENABLED risk_parity_enabled risk-parity / min-variance allocation on (default OFF)
TRADINGAGENTS_RISK_MANAGER_DRAWDOWN_PCT risk_manager_drawdown_pct risk-manager drawdown trigger (default 0.05)
TRADINGAGENTS_ENABLE_RISK_MANAGER enable_risk_manager two-pass risk manager on (default OFF; not wired to runtime)
TRADINGAGENTS_VOLUME_SHARE_VOL_LIMIT volume_share_vol_limit volume-share slippage volume limit (default 0.1)
TRADINGAGENTS_VOLUME_SHARE_PRICE_IMPACT volume_share_price_impact volume-share slippage price impact (default 0.025)
TRADINGAGENTS_ENABLE_EVENTS enable_events default true
TRADINGAGENTS_CATALYST_WINDOW_DAYS catalyst_window_days default 5
TRADINGAGENTS_CATALYST_BASELINE_MOVE catalyst_baseline_move default 0.02
TRADINGAGENTS_CATALYST_MACRO_WINDOW_DAYS catalyst_macro_window_days default 3
TRADINGAGENTS_CATALYST_MACRO_SCALE catalyst_macro_scale default 0.6
TRADINGAGENTS_CATALYST_FED_WINDOW_DAYS catalyst_fed_window_days default 10
TRADINGAGENTS_CATALYST_FED_SCALE catalyst_fed_scale default 0.6
TRADINGAGENTS_CATALYST_MISS_SCALE catalyst_miss_scale default 0.5
TRADINGAGENTS_CATALYST_SCALE_FLOOR catalyst_scale_floor default 0.25
TRADINGAGENTS_CATALYST_HARD_BLOCK_DAYS catalyst_hard_block_days # default 5 (forward earnings blackout)
TRADINGAGENTS_MARKET_STRESS_INDEX market_stress_index default empty
TRADINGAGENTS_MARKET_STRESS_VOL_CAP market_stress_vol_cap default 0.85
TRADINGAGENTS_MIN_DOLLAR_VOLUME min_dollar_volume default None
TRADINGAGENTS_MAX_SPREAD_BPS max_spread_bps default None
TRADINGAGENTS_RISK_MAX_DRAWDOWN_PCT risk_max_drawdown_pct default 0.1
TRADINGAGENTS_RISK_STANDALONE_CVAR_LIMIT_PCT risk_standalone_cvar_limit_pct the analyzed name's own daily-tail CVaR limit; default 0.03
TRADINGAGENTS_RISK_PORTFOLIO_CVAR_BUDGET_PCT risk_portfolio_cvar_budget_pct the portfolio (book) daily-tail CVaR budget; default 0.03
TRADINGAGENTS_RISK_CVAR_MIN_OBSERVATIONS risk_cvar_min_observations the observation floor a CVaR leg must clear before it publishes; default 250
TRADINGAGENTS_RISK_BASKET_TICKERS risk_basket_tickers default []
TRADINGAGENTS_RISK_BASKET_WEIGHTS risk_basket_weights default {}
TRADINGAGENTS_HOLDINGS_TICKERS holdings_tickers Option B: actual-book tickers for the PM holdings read; empty = the risk basket is used (Option A)
TRADINGAGENTS_HOLDINGS_WEIGHTS holdings_weights Option B: actual-book weights (fractions of the whole book incl. cash; <1.0 remainder = cash sleeve). See scripts/positions_to_basket.py to derive them from position CSVs
TRADINGAGENTS_MOOMOO_MAX_CONNECTIONS moomoo_max_connections default 25
TRADINGAGENTS_MOOMOO_CALL_TIMEOUT moomoo_call_timeout per-call wall-clock timeout (s) for moomoo SDK calls; the SDK's own ReqInfo.wait() allows 20s, this caps a degraded gateway at 5s (default) so a run can't stall on hundreds of slow calls
TRADINGAGENTS_MOOMOO_SCREEN_ENABLE moomoo_screen_enable default false
TRADINGAGENTS_MOOMOO_SCREEN_PE_MAX moomoo_screen_pe_max default 30.0
TRADINGAGENTS_MOOMOO_SCREEN_ROE_MIN moomoo_screen_roe_min default 0.12
TRADINGAGENTS_MOOMOO_SCREEN_MAX_CHG5D moomoo_screen_max_chg5d default -0.05
TRADINGAGENTS_MOOMOO_SCREEN_MAX_RSI moomoo_screen_max_rsi default 35.0
TRADINGAGENTS_MOOMOO_SCREEN_PB_MIN moomoo_screen_pb_min default None
TRADINGAGENTS_MOOMOO_SCREEN_PB_MAX moomoo_screen_pb_max default None
TRADINGAGENTS_MOOMOO_SCREEN_DIP_DAYS moomoo_screen_dip_days default 5
TRADINGAGENTS_MOOMOO_SCREEN_EXCHANGES moomoo_screen_exchanges default NYSE,NASDAQ
TRADINGAGENTS_RISK_COMPACT_REPORT risk_compact_report default false
TRADINGAGENTS_GOOGLE_THINKING_LEVEL google_thinking_level default None
TRADINGAGENTS_OPENAI_REASONING_EFFORT openai_reasoning_effort default None
TRADINGAGENTS_ANTHROPIC_EFFORT anthropic_effort default None
TRADINGAGENTS_ANALYST_CONCURRENCY analyst_concurrency default 1
TRADINGAGENTS_ANALYST_FORCED_TOOLS analyst_forced_tools default []
TRADINGAGENTS_ANALYST_FORCED_TOOLS_MAX_PARALLEL analyst_forced_tools_max_parallel default 1
TRADINGAGENTS_ANALYST_FORCED_TOOLS_TIMEOUT_S analyst_forced_tools_timeout_s default 30
TRADINGAGENTS_ANALYST_FORCED_TOOLS_SUMMARY_WINDOW analyst_forced_tools_summary_window default 12000
TRADINGAGENTS_ANALYST_TOOLS_MODEL_SUPPLIED analyst_tools_model_supplied default []
TRADINGAGENTS_OPENROUTER_REASONING_EFFORT openrouter_reasoning_effort default empty
TRADINGAGENTS_OPENROUTER_IGNORE_PROVIDERS openrouter_ignore_providers comma-separated provider slugs to always skip (sent as provider.ignore in the OpenRouter request body via extra_body) to block slow/unreliable endpoints; empty = no restriction
TRADINGAGENTS_OPENROUTER_STREAMING openrouter_streaming stream OpenRouter responses; default false. A large prompt spends a long time in prefill with nothing on the wire, and OpenRouter only commits HTTP 200 + headers once a provider accepts the request (later failures arrive in-stream, not as a bare 504). langchain aggregates the chunks, so invoke()/tool loops are unchanged
TRADINGAGENTS_OPENROUTER_SESSION_ID openrouter_session_id sticky-routing key sent as the request-body session_id: auto = one generated id per client (per symbol run), a literal string (≤256 chars) pins a named session, empty (default) = off. Keeps each turn on the provider holding the warmed prefix cache; OpenRouter's sticky routing is disabled by provider.order, so this repo never sends one
TRADINGAGENTS_OPENROUTER_PROVIDER_SORT openrouter_provider_sort provider.sort: price \
TRADINGAGENTS_OPENROUTER_PREFERRED_MAX_LATENCY openrouter_preferred_max_latency provider.preferred_max_latency in seconds; empty/0 = not sent
TRADINGAGENTS_OPENROUTER_PREFERRED_MIN_THROUGHPUT openrouter_preferred_min_throughput provider.preferred_min_throughput in tokens/sec; empty/0 = not sent
TRADINGAGENTS_OPENROUTER_REQUIRE_PARAMETERS openrouter_require_parameters true = only hosts that honor every parameter in the request (tools, max_tokens)
TRADINGAGENTS_OPENROUTER_ALLOW_FALLBACKS openrouter_allow_fallbacks default true (OpenRouter's own default, so nothing is sent); false = fail instead of falling back when the preferred host is unavailable
TRADINGAGENTS_MAX_OUTPUT_TOKENS max_output_tokens per-role max output tokens (hard ceiling via max_tokens); default 8000; the fallback for both tiers
TRADINGAGENTS_MAX_OUTPUT_TOKENS_QUICK max_output_tokens_quick quick-tier cap (analysts / researchers / debaters / trader); default 8000 (raised from 6000 after 2026-08-27 reports truncated mid-sentence at the 6000 cap)
TRADINGAGENTS_MAX_OUTPUT_TOKENS_DEEP max_output_tokens_deep deep-tier cap (Research Manager + Portfolio Manager); default 2500
TRADINGAGENTS_BACKUP_LLM backup_llm backup model for truncation-continuation retries: provider:model (or a bare model id using TRADINGAGENTS_LLM_PROVIDER). When ANY LLM response is cut at the output cap, the continuation retry runs on this model instead of re-paying the one that truncated. Empty = same-model continuations (legacy behavior)
TRADINGAGENTS_ENABLE_VALUE_DIP enable_value_dip default false
TRADINGAGENTS_ENABLE_TRANCHE_RISK enable_tranche_risk default false
TRADINGAGENTS_TRANCHE_WEIGHTS tranche_weights default [0.3, 0.3, 0.4]
TRADINGAGENTS_TRANCHE_STOP_MULT tranche_stop_mult default 1.5
TRADINGAGENTS_TRANCHE_RISK_PCT tranche_risk_pct default 0.015
TRADINGAGENTS_TRANCHE_ACCOUNT tranche_account default 100000.0
TRADINGAGENTS_MIN_RR min_rr default 2.0
TRADINGAGENTS_MAX_STOP_FRACTION max_stop_fraction default 0.10
TRADINGAGENTS_ENABLE_LIQUIDITY_GATE enable_liquidity_gate on, the risk governor sizes against the ILLIQ/float-turnover/IWF liquidity verdict (Strategies/risk2.md)
TRADINGAGENTS_ENABLE_COMPUTED_CONTEXT enable_computed_context default true
TRADINGAGENTS_ENABLE_PRE_MARKET_REVIEW enable_pre_market_review default false
TRADINGAGENTS_ENABLE_JV_VERDICT enable_jev_verdict default false
TRADINGAGENTS_ENABLE_MSDEC_DECIDER enable_msdec_decider default false (the Microsoft microsoft/microsoft-decision-1 judge; replaced enable_pplx_decider on 2026-10-11)
TRADINGAGENTS_ENABLE_NOUL_DECIDER enable_noul_decider default false
TRADINGAGENTS_ENABLE_LUNA_DECIDER enable_luna_decider default false
TRADINGAGENTS_FCF_YIELD_FLOOR fcf_yield_floor default 0.06
TRADINGAGENTS_ENABLE_ETF_ENGINE enable_etf_engine default false
TRADINGAGENTS_ENABLE_CORRELATION_PENALTY enable_correlation_penalty when on, the allocation plan (allocation_block / get_allocation) down-weights names whose average pairwise correlation with the rest of the book exceeds correlation_threshold (risk-parity concentration control)
TRADINGAGENTS_CORRELATION_THRESHOLD correlation_threshold avg pairwise correlation above this triggers the penalty (default 0.6)
TRADINGAGENTS_MAX_PAIRWISE_CORR max_pairwise_corr hard cluster ceiling on pairwise position correlation (default off)
TRADINGAGENTS_TRAILING_STOP_ATR_MULT trailing_stop_atr_mult ATR-multiplied trailing stop (atr*mult) instead of static %% (default off)
TRADINGAGENTS_CORRELATION_PENALTY_FRAC correlation_penalty_frac weight cut for a penalized name, renormalized across the book (default 0.3)
TRADINGAGENTS_ENABLE_OPTIONS_SURFACE enable_options_surface CBOE delayed options chain -> IV/greeks surface (default OFF)
TRADINGAGENTS_ENABLE_RISK_FREE_CURVE enable_risk_free_curve NY Fed SOFR + Treasury par-yield curve (default OFF)
TRADINGAGENTS_ENABLE_SCREENER enable_screener yfinance universe screener (symbol/PE/EPS/beta/mkt-cap) (default OFF)
TRADINGAGENTS_ENABLE_MARKET_MOVERS enable_market_movers yfinance gainers/losers/actives (default OFF)
TRADINGAGENTS_ENABLE_BENZINGA_SURFACE enable_benzinga_surface Benzinga event surface: guidance revisions / FDA milestones / secondary offerings / analyst actions / news retractions (default OFF)
TRADINGAGENTS_ENABLE_MARKET_ROUTING enable_market_routing DSA §3.4 market-classified routing on (default OFF; the chain is bit-identical when off)
TRADINGAGENTS_MARKET_SOURCE_PRIORITY market_source_priority JSON market -> comma-separated vendor list, e.g. {"US": "eodhd,tiingo,yfinance,moomoo"}; reorders that method's chain for the market (takes effect with enable_market_routing; unregistered vendors in the list are skipped)
TRADINGAGENTS_ENABLE_SPREAD_ESTIMATOR enable_spread_estimator quote-free Corwin-Schultz / Abdi-Ranaldo spread FLOOR (Q1)
TRADINGAGENTS_ENABLE_BOOK_RISK_SIZING enable_book_risk_sizing minimum-CVaR book sizing under the CVaR budget (Q2)
TRADINGAGENTS_ENABLE_CONFORMAL_BANDS enable_conformal_bands calibrated valuation bands with realized coverage (Q3)
TRADINGAGENTS_ENABLE_RETURN_DECOMPOSITION enable_return_decomposition overnight vs intraday return decomposition (Q5)
TRADINGAGENTS_ENABLE_TEXT_FACTORS enable_text_factors Loughran-McDonald tone / readability / divergence (Q6)
TRADINGAGENTS_ENABLE_BOCPD enable_bocpd Bayesian online changepoint read in get_shift_detection (Q8)
TRADINGAGENTS_BOCPD_HAZARD_MODE bocpd_hazard_mode default constant
TRADINGAGENTS_ENABLE_RN_SKEW_PROXY enable_rn_skew_proxy default false
TRADINGAGENTS_ENABLE_TREND_SPECTRAL enable_trend_spectral default false
TRADINGAGENTS_ENABLE_TRIAL_LEDGER enable_trial_ledger default false
TRADINGAGENTS_ENABLE_JUMP_ROBUST_PROXIES enable_jump_robust_proxies default false
TRADINGAGENTS_ENABLE_MP_LOWER_SPECTRUM enable_mp_lower_spectrum default false
TRADINGAGENTS_ENABLE_LONG_MEMORY enable_long_memory default false
TRADINGAGENTS_ENABLE_METRIC_AUTHORITY enable_metric_authority default false
TRADINGAGENTS_ENABLE_FACT_LAYER enable_fact_layer default false
TRADINGAGENTS_ENABLE_BOOTSTRAP_INTERVALS enable_bootstrap_intervals default false
TRADINGAGENTS_ENABLE_EVENT_IV_LIFT enable_event_iv_lift default false
TRADINGAGENTS_ENABLE_TRIADIC_STRESS enable_triadic_stress default false
TRADINGAGENTS_ENABLE_REFUSAL_LEDGER enable_refusal_ledger default false
TRADINGAGENTS_ENABLE_MATERIALITY_VERDICT enable_materiality_verdict default false
TRADINGAGENTS_ENABLE_ACCURACY_CEILING enable_accuracy_ceiling default false
TRADINGAGENTS_ENABLE_SPECTRAL_NULL_BAND enable_spectral_null_band default false
TRADINGAGENTS_ENABLE_RND_RECOVERY enable_rnd_recovery default false
TRADINGAGENTS_ENABLE_EIGEN_ROTATION enable_eigen_rotation default false
TRADINGAGENTS_ENABLE_DRAWDOWN_ENVELOPE enable_drawdown_envelope default false
TRADINGAGENTS_ENABLE_HMM_HEAVY_TAILS enable_hmm_heavy_tails default false
TRADINGAGENTS_ENABLE_TAIL_RISK_LAYER enable_tail_risk_layer default false
TRADINGAGENTS_ENABLE_TURBULENCE_READ enable_turbulence_read default false
TRADINGAGENTS_ENABLE_FACTOR_AVAILABILITY_GATE enable_factor_availability_gate default false
TRADINGAGENTS_ENABLE_FORWARD_STRESS_PROBABILITY enable_forward_stress_probability default false
TRADINGAGENTS_ENABLE_PROMPT_CONDITION_HARNESS enable_prompt_condition_harness default false
TRADINGAGENTS_ENABLE_COVERAGE_WINDOW enable_coverage_window default false
TRADINGAGENTS_ENABLE_ALTMAN_VARIANTS enable_altman_variants default false
TRADINGAGENTS_ENABLE_F_SCORE_DETAIL enable_f_score_detail default false
TRADINGAGENTS_ENABLE_GROWTH_SCORES enable_growth_scores default false
TRADINGAGENTS_ENABLE_WEIGHTED_SENTIMENT_AGG enable_weighted_sentiment_agg default false
TRADINGAGENTS_ENABLE_CROWD_RATIO_BANDS enable_crowd_ratio_bands default false
TRADINGAGENTS_ENABLE_ANALYST_REVISION_INDEX enable_analyst_revision_index default false
TRADINGAGENTS_ENABLE_QUALITY_COMPOSITE enable_quality_composite default false
TRADINGAGENTS_ENABLE_SCORE_EVAL_ROWS enable_score_eval_rows default false
TRADINGAGENTS_ENABLE_WEIGHTED_SENTIMENT_WINDOW enable_weighted_sentiment_window default false
TRADINGAGENTS_ENABLE_EVIDENCE_SYMMETRY enable_evidence_symmetry default false
TRADINGAGENTS_ENABLE_FUNDAMENTAL_SCORE enable_fundamental_score default false
TRADINGAGENTS_ENABLE_TECHNICAL_SCORE enable_technical_score default false
TRADINGAGENTS_ENABLE_MOMENTUM_SCORE enable_momentum_score default false
TRADINGAGENTS_ENABLE_SENTIMENT_SCORE enable_sentiment_score default false
TRADINGAGENTS_ENABLE_NEWS_SCORE enable_news_score default false
TRADINGAGENTS_ENABLE_TRADE_SCORE enable_trade_score default false
TRADINGAGENTS_ENABLE_EVENT_STATE enable_event_state default false
TRADINGAGENTS_ENABLE_EVENT_CALENDARS enable_event_calendars default false
TRADINGAGENTS_ENABLE_MOOMOO_SNAPSHOT enable_moomoo_snapshot default false
TRADINGAGENTS_ENABLE_ANALYST_ESTIMATES enable_analyst_estimates default false
TRADINGAGENTS_ENABLE_EODHD_RATES enable_eodhd_rates default false
TRADINGAGENTS_ENABLE_REGIME_SCORE enable_regime_score default false
TRADINGAGENTS_ENABLE_RISK_SCORE enable_risk_score default false
TRADINGAGENTS_ENABLE_QUANT_SCORECARD enable_quant_scorecard default false
TRADINGAGENTS_ENABLE_DECISION_PACKET enable_decision_packet default false
TRADINGAGENTS_ENABLE_CONTEXT_EXPANSION enable_context_expansion default false
TRADINGAGENTS_ENABLE_DECISION_CHALLENGE enable_decision_challenge default false
TRADINGAGENTS_EVIDENCE_SYMMETRY_PAIRS evidence_symmetry_pairs default []
TRADINGAGENTS_ENABLE_THRESHOLD_GATE enable_threshold_gate inert - declared, read by nothing (see docs/gate_registry.md §4)
TRADINGAGENTS_ENABLE_REGIME enable_regime inert
TRADINGAGENTS_ENABLE_FACTORS enable_factors inert
TRADINGAGENTS_ENABLE_PREOPEN_DEPTH enable_preopen_depth P2: live IEX quote-depth thin-book proxy
TRADINGAGENTS_ENABLE_SECTOR_MULTIFACTOR enable_sector_multifactor sector-ranking reads (multi-factor, industry group, breadth, vendor constituents)
TRADINGAGENTS_ENABLE_SECTOR_INDUSTRY enable_sector_industry default false
TRADINGAGENTS_ENABLE_SECTOR_BREADTH enable_sector_breadth default false
TRADINGAGENTS_ENABLE_SECTOR_EODHD_CONSTITUENTS enable_sector_eodhd_constituents default false
TRADINGAGENTS_ENABLE_MECHANICAL_VOLUME_DISCOUNT enable_mechanical_volume_discount default false
TRADINGAGENTS_ENABLE_SECURITY_CONTEXT enable_security_context default false
TRADINGAGENTS_RISK_AUDIT_ENABLED risk_audit_enabled hash-chained risk audit ledger
TRADINGAGENTS_MAX_POSITION_PCT max_position_pct single-name position cap (governor)
TRADINGAGENTS_RISK_MAX_POSITION_PCT risk_max_position_pct whole-book position cap (governor)
TRADINGAGENTS_SECTOR_CAP_LIMIT sector_cap_limit sector concentration cap (governor)
TRADINGAGENTS_RESULTS_DIR results_dir default computed: os.path.join(_TRADINGAGENTS_HOME, 'logs')
TRADINGAGENTS_CACHE_DIR data_cache_dir default computed: os.path.join(_TRADINGAGENTS_HOME, 'cache')
TRADINGAGENTS_LLM_FAILURE_JOURNAL_DIR llm_failure_journal_dir default empty
TRADINGAGENTS_VERIFY_MODEL report_verify_model advisory LLM report-verifier model (empty = quick tier); see scripts/report_verify.py
TRADINGAGENTS_VERIFY_MAX_CALLS report_verify_max_calls verifier LLM calls per report tree (default 12)
TRADINGAGENTS_MEMORY_LOG_PATH memory_log_path default computed: os.path.join(_TRADINGAGENTS_HOME, 'memory', 'trading_memory.md')

(Secrets are read from env inside the vendors; TRADINGAGENTS_DISABLE_REDDIT=1 in .env turns off Reddit fetches.)

DEFAULT_CONFIG is range-checked by default_config.validate_config() when it is built, so a well-typed but out-of-range override (e.g. TRADINGAGENTS_TOP_P=1.5, tranche weights that do not sum to ~1) raises a ValueError listing every violation at config load instead of silently skewing an unattended run.

Every gate is flippable from .env. docs/gate_registry.md is the canonical list: each gate with its switch, what it can block, where it is enforced, the test that proves it fires, and the flags that are declared but inert (read by no code). tests/test_gate_env_toggles.py keeps that document true: it asserts each key exists and has an override row, that flipping it through the real loader works in both directions, and that every wired/inert claim matches the source.

1.2 All DEFAULT_CONFIG keys

Run py -3.12 -c "from tradingagents.default_config import DEFAULT_CONFIG as D; print(sorted(D))" for the canonical list; the important groups:

LLM - llm_provider, deep_think_llm, quick_think_llm, backend_url, openai_reasoning_effort, google_thinking_level, anthropic_effort, temperature, llm_max_retries.

Graph/run - max_debate_rounds, max_risk_discuss_rounds, max_recur_limit, checkpoint_enabled, analyst_concurrency (1 = sequential; >1 = parallel analysts), output_language.

Persistence - data_cache_dir (/.tradingagents/cache), results_dir (/.tradingagents/logs), memory_log_path (~/.tradingagents/memory/trading_memory.md), memory_log_max_entries (None = unbounded rotation), benchmark_ticker + benchmark_map (per-exchange alpha benchmarks: .NS->^NSEI, .BO->^BSESN, .T->^N225, .HK->^HSI, .L->^FTSE, .TO->^GSPTSE, .AX->^AXJO, .SS->000001.SS, .SZ->399001.SZ, ''->SPY).

News/input limits - news_article_limit=20, global_news_article_limit=10, global_news_lookback_days=7, global_news_queries (macro headlines).

Data vendors - data_vendors: dict (per-category chains), tool_vendors: dict (per-tool overrides), enable_market_routing + market_source_priority (opt-in DSA §3.4 per-market priority chains; see §6), finnhub_api_key, fmp_api_key, alpaca_api_key_id, alpaca_api_secret, enable_alpaca, moomoo_host/port/account/autostart/ opend_path/max_connections/call_timeout (moomoo_call_timeout = per-call wall-clock timeout in seconds, default 5.0; the SDK's own ReqInfo.wait() allows 20s per call, so a degraded gateway can burn 20s per call across hundreds of calls — the value screener's gating pass makes ~7 calls/symbol — which is how a web job hits its subprocess budget).

Vendor cache - vendor_cache_enabled=True, vendor_cache_ttl_seconds=21600, vendor_cache_skip_categories={'news_data'}.

Strategy flags - see section 5; enable_regime, enable_factors, enable_threshold_gate default False; enable_sentiment default True (computed sentiment score + surprise velocity injected into the sentiment report). Only enable_events (B1 catalyst gate), enable_reflection, enable_sentiment, and enable_strategy_overlays default True; enable_orderflow, enable_position_contract, enable_calibration, enable_agreement, enable_independent_vote, enable_composite_rank, enable_exits and enable_risk_governor default False (opt-in; the dev machine enables most via the gitignored .env). Sizing: position_sizing='kelly', target_vol=0.15, kelly_fraction=0.25, position_odds=1.0, breakeven_atr=1.0, target_atr=4.0, sector_cap_limit=0.35, risk_max_drawdown_pct=0.10, risk_standalone_cvar_limit_pct=0.03, risk_portfolio_cvar_budget_pct=0.03, risk_max_position_pct=0.45, enable_tranche_risk (default False) + tranche_weights (0.3,0.3,0.4), tranche_stop_mult (1.5), tranche_risk_pct (0.015), tranche_account (100000) — the deterministic tranche-scaling risk fold (Value_Dip_swing_Continue.md): with enable_position_contract + enable_risk_governor on, the governor sizes and throttles against the worst-case 3-tranche scale-in with config-frozen parameters (never the LLM), enforcing BOTH the capital-at-risk budget (sum of per-tranche losses at the hard stop) and the peak-deployed-at-scale-in per-trade cap; risk_stress_shock_pct_1 / risk_stress_shock_pct_2 (R2 scenario shocks, def -10%/-30%) evaluate_cost_bps=10, calibration_min_n=5, consensus_seeds=1, max_book_names=10, max_name_weight=0.25, risk_audit_enabled default True. Correlation-aware allocation (industry-practice item 1): enable_correlation_penalty (default False) + correlation_threshold (0.6)

  • correlation_penalty_frac (0.3) — when on, allocation_block and the get_allocation tool down-weight names whose average pairwise correlation with the rest of the book exceeds the threshold before the per-name/per-sector caps (risk-parity style concentration control; names without a measurable return series are never penalized). Catalyst: catalyst_hard_block_days=0 - when > 0, an earnings print inside that many days makes the risk governor REJECT new risk (section 5).

Round-2 quant formula additions (docs/design_quant_formulas_research_round2.md, phases Q1-Q8) - every flag defaults to False and its tool returns a DISABLED sentinel when off, so a run's artefacts cannot change silently: enable_spread_estimator (quote-free spread FLOOR folded into get_liquidity_risk, plus the get_spread_estimate tool and the spread-implied Almgren-Chriss temporary impact), enable_book_risk_sizing (+ book_risk_sizing_min_scenarios=60, book_risk_sizing_max_delta=0.05) for get_book_risk_budget, enable_conformal_bands (+ conformal_alpha=0.1, conformal_min_pairs=40, conformal_window=250) for get_valuation_band, enable_return_decomposition for get_return_decomposition, enable_text_factors for get_disclosure_tone, and enable_bocpd for the Bayesian changepoint line inside get_shift_detection. The report verifier gains two text-only families with no flag (they only fire on the cited lines): tone_claim_conflict and valuation_band_conflict.

Round-3 scoring & sentiment additions (docs/design_quant_formulas_research_round3.md, docs/implementation_plan_quant_formula_additions_round3.md, phases S1-S11) - ten flags, every one default False, every added line additive, and every unavailable input rendered "unavailable" rather than scored 0: enable_altman_variants (S1: altman_variant / altman_zone - the Z'/Z''/Z''-EM family plus the distress zones, X4 basis printed, funds and financials unavailable), enable_f_score_detail (S2: piotroski_f_score_detailed - per-signal booleans, the paper's 0-1/8-9 bands, and deviations recording every substitution), enable_growth_scores (S10: growth_score G1-G8 + overpriced_score C1-C6, industry medians from the resolved peer universe), enable_weighted_sentiment_agg (S4: aggregate_weighted_sentiment beside the byte-identical aggregate_daily_sentiment), enable_crowd_ratio_bands (S5: bull/bear ratio + dispersion, display-only bands), enable_analyst_revision_index (S6: analyst_revisions.revision_ratio and the get_analyst_revision_index tool), enable_quality_composite (S3: factors.category_scores 0-100 with the quality bands - never decision_guardrail.SCORE_BANDS), enable_fundamental_score (WP-2: the four FundamentalScore category sub-scores over the peer panel + their RESEARCH_ONLY composite; with it off the tool is not in any toolset at all), enable_technical_score (WP-3: the nine TechnicalScore category sub-scores over the run's own bars + their weighted composite, with the non-monotonic inputs band-mapped over their producers' own edges), enable_regime_score (WP-4: the market-level RegimeScore + the two-path disagreement read; NOT enable_regime, which is inert), enable_risk_score (WP-5: the inverted 0-100 RiskScore, 100 = low risk, with the three shipped sign conventions aligned in the score and printed per row), enable_sentiment_score (WP-7: the ten SentimentScore category sub-scores + the confirmation quadrant; two tone sources are refused rather than averaged), enable_news_score (WP-6: NewsScore; the five unsupplied categories print NA with a reason, and materiality comes from EventScore), enable_event_state (WP-8: the per-family event imminence + the earnings hard block passed through verbatim), enable_trade_score (WP-11: the four-engine decision composite; it reaches no gate, no size and no opportunity_score), enable_score_eval_rows (S8: alpha_health.score_evaluation_rows), enable_weighted_sentiment_window (S7: weighted_rolling_sentiment, the 10-day exponential window plus the min_history warm-up guard), and enable_evidence_symmetry (S11: evidence_gather.symmetry_report, the mirrored discretionary budget - driven by the declared pair specs in evidence_symmetry_pairs / TRADINGAGENTS_EVIDENCE_SYMMETRY_PAIRS, inert while that list is empty - and the gated argument-plan call). The peer universe behind S3/S10 is the screener scan universe, resolved once by strategies/peer_universe.py::resolve_peer_universe (plan §1.1).

2. LLM providers

deep_think_llm models the Research Manager + Portfolio Manager; quick the analysts/researchers/debaters/reflection. Client code: llm_clients/factory.py -> llm_clients/{openai,anthropic,google,azure, bedrock}_client.py + model_catalog.py (CLI model lists).

Provider Config value Env key Notes
openai openai OPENAI_API_KEY Responses API; openai_reasoning_effort
google google GOOGLE_API_KEY google_thinking_level
anthropic anthropic ANTHROPIC_API_KEY anthropic_effort
xai xai XAI_API_KEY
deepseek deepseek DEEPSEEK_API_KEY reasoning_content round-trip
mistral mistral MISTRAL_API_KEY
kimi kimi MOONSHOT_API_KEY
groq groq GROQ_API_KEY
nvidia nvidia NVIDIA_API_KEY
openrouter openrouter OPENROUTER_API_KEY default provider in .env
qwen / qwen-cn qwen/qwen-cn DASHSCOPE_API_KEY / DASHSCOPE_CN_API_KEY intl / CN
glm / glm-cn glm/glm-cn ZHIPU_API_KEY / ZHIPU_CN_API_KEY
minimax / minimax-cn minimax/minimax-cn MINIMAX_API_KEY / MINIMAX_CN_API_KEY reasoning_split
ollama ollama OLLAMA_BASE_URL keyless local
openai_compatible openai_compatible TRADINGAGENTS_LLM_BACKEND_URL vLLM / LM Studio / llama.cpp
azure azure Azure OpenAI env see .env.enterprise.example
bedrock bedrock AWS creds / AWS_BEARER_TOKEN_BEDROCK pip install ".[bedrock]"

Request timeout. Every client request (incl. streaming) is bounded by a 300 s default: request_timeout for the OpenAI-compatible family, default_request_timeout for Anthropic (the SDKs' real fields — the legacy timeout passthrough key is mapped to them, and a config timeout / request_timeout / default_request_timeout passed to create_llm_client wins over the default). This prevents a stalled provider (e.g. DeepSeek at US-night peak) from hanging a chain.invoke / the truncation-retry loop indefinitely — a stuck job now raises and degrades instead of appearing to re-try forever.

3. Graph & subagents

AgentState (LangGraph MessagesState): messages, company_of_interest, asset_type, instrument_context, trade_date, sender, market_report, sentiment_report, news_report, fundamentals_report, investment_debate_state, investment_plan, trader_investment_plan, risk_debate_state, final_trade_decision, past_context.

  • InvestDebateState: bull_history, bear_history, history, current_response, judge_decision, count.
  • RiskDebateState: aggressive_history, conservative_history, neutral_history, history, latest_speaker, current_aggressive_response, current_conservative_response, current_neutral_response, judge_decision, count.

Pipeline (see graph/setup.py for wiring):

START -> analysts* (tool loop each) -> Bull/Bear debate (rounds) -> Research Manager
      -> Trader -> Aggressive/Conservative/Neutral debate -> Portfolio Manager -> END
  • analyst_concurrency > 1 wraps analysts in isolated per-analyst subgraphs running in threads.
  • graph/checkpointer.py: per-ticker SQLite checkpoints keyed on ticker+date+graph-shape (analyst set, debate/risk rounds, asset type, concurrency). --checkpoint enables; cleared on successful completion.
  • graph/conditional_logic.py: routers bound to the message tail; the path maps in setup.py are complete so a fall-through can never raise.

4. Structured output (pydantic in agents/schemas.py)

Agent Schema Fields
Research Manager ResearchPlan recommendation (5-tier), rationale, strategic_actions
Trader TraderProposal action (Buy/Hold/Sell), reasoning, entry_price?, stop_loss?, target_price? (the §101 exit level — the card's own exit target, quoted, never invented), position_sizing?
Portfolio Manager PortfolioDecision rating, executive_summary, investment_thesis, entry_price? (§100's final entry price from the card), price_target?, time_horizon?, confidence (0-1), position_size, stop_loss?, consensus (high/low)
Sentiment Analyst SentimentReport overall_band (6-tier), overall_score (0-10), confidence (low/med/high), narrative (+ computed fields)

Each agent falls back to free-text when the provider lacks structured output; rendered markdown preserves backward-compatible headers for the log/site.

5. Strategy overlays (compute, don't narrate)

Applied after the graph in graph/trading_graph.py::_apply_strategy_overlays:

Layer Flag (default) Module Effect
Regime / size enable_strategy_overlays (T) strategies/regime.py, size.py vol-percentile/trend label + vol-target scale
Order flow enable_orderflow (F) strategies/orderflow.py distribution/divergence fold -> scale
Catalyst (B1) enable_events (T) strategies/catalyst.py earnings/macro/Fed scale + verdict; catalyst_hard_block_days > 0 turns an imminent print into a risk-governor REJECT
Position contract enable_position_contract (F) strategies/contract.py min(Kelly, risk/stop)volflowagreecatalyst; the dollar stop/BE/target anchor on the entry the decision publishes (contract.published_entry → §103's final_entry_price, else a tranche plan's weighted avg_entry, else the last close), so the published stop never sits above position.entry_price
Risk governor enable_risk_governor (F) strategies/risk_governor.py PASS/WARN/REJECT, risk_halt; CVaR from the configured risk_basket_tickers weighted mix (book_risk.portfolio_cvar) when set, else the analyzed name's series. If the weights sum < 1.0 the remainder is treated as zero-return cash (dilutes the tail) - "include cash as overall portfolio". With enable_tranche_risk on it also sizes/throttles against the worst-case 3-tranche scale-in (strategies/value_dip.py::tranche_risk_read): the peak-deployed-at-scale-in fraction vs the per-trade cap and the capital-at-risk budget (sum of per-tranche losses at the hard stop vs tranche_risk_pct)
Calibration enable_calibration (F) strategies/calibration.py calibrated P from ledger
Agreement enable_agreement (F) strategies/consensus.py debate agreement -> size; with enable_independent_vote the agreement comes from the INDEPENDENT pre-debate stances, not the debate transcript (no conformity contamination)
Sentiment factor enable_sentiment_factor (F) strategies/sentiment_research.py + overlays.fold_sentiment_into_overlay position scale x 1 ± sentiment_factor_max_scale ONLY when the name's measured rank IC ≥ sentiment_factor_min_ic; else neutral 1.0 (never blocks)
Exits enable_exits (F) strategies/exits.py stops/BE/targets
Reflection enable_reflection (T) strategies/reflection.py ledger, analyst hit-rates
Composite rank enable_composite_rank (F) strategies/factors.py EY + momentum + 52w composite
Factor profile enable_factor_profile (F) strategies/factor_expressions.py get_factor_profile — Alpha158-style 16-factor subset (momentum/reversal/volatility/value) off the OHLCV cache with expression-string cache + learn/infer fit-apply split (moments fit on train only); advisory, gated
Topk-Drop alloc enable_topk_drop (F) strategies/portfolio_strategy.py screener alloc block + get_topk_drop_plan — hold top-k by score, sell worst-held, equal-weight (turnover = 2·drop/topk)
Enhanced-index alloc enable_enhanced_index (F) strategies/portfolio_strategy.py screener alloc block + get_enhanced_index_tilt — convex program (long-only, Σw=1, turnover cap, b_dev, force-hold/sell masks, two-stage fallback); scipy SLSQP + pure-python fallback, cvxpy optional
Signal analysis — (always-on pure calc) strategies/signal_analysis.py rank IC/ICIR, quantile long-short, IC-decay half-life, pred-autocorrelation; strategy_quality_report gains the with/without-cost excess-return table
Baseline surface — (always-on pure calc) strategies/benchmark_surface.py benchmark_rows / benchmark_surface (FinRL 8): every arm on ONE shared window and ONE stated cost schedule, the classic six columns beside mean / variance / loss-side CVaR / P(return>=threshold), plus each baseline's per-metric delta; a strategy-only call is a named refusal ("a raw single curve is not a result"). Cost is charged to the strategy arm and, by default, not to the baselines
Per-run economics — (always-on pure calc) strategies/run_economics.py run_economics(run_card, verify_flags=...) (Dream-RSI Phase B): the run card's cap-bound llm_cost_est joined to the verifier's own per-stem/prose blocks, with an unpriced leg named and never zeroed; depth is None because the run card does not record the research-depth knob
Cross-sectional null — (always-on pure calc) null_harness.cross_sectional_null H3 Stage 3 over REAL per-name PnL: a one-sample t across names (df = N-1) plus an exact two-sided sign test (ties dropped); distinguishable_from_zero / consistent_with_zero / unavailable, never a pass by absence
Selection turnover — (always-on pure calc) evaluate.selection_turnover E4: per-fold winner changes over a {fold: {candidate: score}} matrix, with the distinct-winner count and the modal share. Reports no informativeness verdict on purpose - a caller prints it beside cscv_pbo over the same matrix
Market stress (turbulence) enable_turbulence_read (F) strategies/market_stress.py + get_turbulence_read (market analyst) FinRL's turbulence index: the day's cross-section vs its own strictly-prior window (the scored row is excluded), Sigma from covariance_models.ledoit_wolf_shrink, and an own-history quantile gate. A market-state WARNING - it sizes nothing, enters no score, and refuses a one-name panel
Skill overlays enable_skill_overlays (F) + skill_dir strategies/skills.py + strategies/skills/*.yaml declarative YAML strategy skills (instructions/tools/regimes/priority + bounded score adjustments), regime-from-opinion thresholds (bullish>=70 / bearish<=30 / 35-65), router precedence user->regime->priority
Prediction ledger + cost enable_prediction_ledger (F) + prediction_horizon_days strategies/prediction_ledger.py + llm_cost.py log every decision as a scorable prediction (rating/direction/levels/confidence/horizon); score against realized closes at N days (return, hit, MAE/MFE, stop/target); provider cost estimate for quality-per-dollar
Report disclosure + invalidation enable_report_attribution (F) strategies/report_disclosure.py + reporting.write_report_tree computed driver attribution (sum-100), consensus support/oppose, watch_conditions/next_check_time, >=1 invalidation per decision (stop/tp/data-quality/manual fallback), sources-used-vs-empty + models footers
News relevance + coalescing enable_news_relevance (F) strategies/news_relevance.py + dataflows/news_cache.py deterministic relevance scoring (code/company/official +8, macro -12, clamp), spam admission, degrade triple (all_failed/empty/unavailable), owner-wait coalescing TTL cache
Market routing + health enable_market_routing + market_source_priority / vendor_breaker_* (F) dataflows/market_router.py + vendor_breaker.py + effective_date.py market-classified priority chains (opt-in TRADINGAGENTS_ENABLE_MARKET_ROUTING + TRADINGAGENTS_MARKET_SOURCE_PRIORITY), 3-fail/300s breaker + half-open probe + negative capability cache, VendorResult honesty fields (fallback_from/stale/data_quality/missing_fields), effective-trading-date + all-closed skip (fail-open). NOTE: the negative capability cache half is no longer opt-in — the vendor modules (fmp_common.fmp_get, massive._get, finnhub.get_analyst_ratings_finnhub) now consult it unconditionally, so a 401/403 verdict is not re-asked per call; the market-routing breaker itself stays default-off (see docs/developer/03-dataflow-vendors.md §3.1b)
Decision guardrail enable_decision_guardrail (F) strategies/decision_guardrail.py post-PM downgrade-only stabilizer (risk-cap at Hold, near-resistance/no-flow, near-support/no-outflow) with recorded guardrail_reason; versioned 0-100 <-> rating consistency check; PM data_quality/guardrail_reason/risk_cap fields + confidence cap on stale data; per-field integrity retry in structured.retry_structured_missing_fields
Backtest tradability backtest_limit_threshold / backtest_volume_participation / backtest_deal_price strategies/market_tradability.py limit-up/down gates, suspended days (NaN close), participation caps, deal-price selector; wired into scripts/backtest_strategy.py fills (flags --limit-threshold etc.) + fill_model honesty block

Off by default: enable_regime, enable_factors, enable_threshold_gate. enable_sentiment is now on (computed sentiment score + surprise velocity injected into the sentiment report). Pure strategies/risk_manager.py (manage_risk two-pass exit override + trailing_stop_targets) also ships advisory: enable_risk_manager is OFF by default and it is not wired into the runtime graph yet. Strategy-eval scripts: scripts/evaluate_config_gate.py (G5 walk-forward/PBO; --cost-grid sweeps round-trip costs, --benchmark adds the baseline-relative surface, --folds adds selection turnover beside the PBO over the same matrix), scripts/strategy_quality_report.py (ledger metrics; the loss-side benchmark_surface block is always on, --benchmark renders the benchmark's own arms, --trees joins each tree's cap-bound cost to its verifier quality), scripts/orderflow_evaluate.py (ledger evaluation), scripts/risk_report.py (risk audit).

6. Vendor data contract

Everything flows through route_to_vendor(method, *args, **kwargs) in dataflows/interface.py. The vendor chain per category is configured in data_vendors (default chains: eodhd,moomoo,yfinance, fred,moomoo, polymarket,moomoo, moomoo,finnhub,yfinance, sec_edgar). Errors propagate through dataflows/errors.py, the router converts to sentinel strings (NO_DATA_AVAILABLE, DATA_UNAVAILABLE optional, DATA_DISABLED). technical_indicators / fundamental_data / news_data end their chains with alpha_vantage (key-gated last fallback via ALPHA_VANTAGE_API_KEY in .env).

Typed absence reasons (yfinance P1). When a chain ends on a typed failure, route_to_vendor_typed attaches a machine-readable absence dict to the VendorResult envelope: {reason, source, retryable, detail} where reason ∈ no_data | rate_limited | not_configured | error | unknown. The reason follows the verdict: no_data when the returned sentinel is NO_DATA_AVAILABLE (from the no-data vendor), the first real error for optional DATA_UNAVAILABLE. route_to_vendor (string path) keeps its plain contract — the reason is carried via a per-call contextvar side channel read (and reset) by route_to_vendor_typed, never in the returned string. The OHLCV read envelope (analysis_tools._ohlcv) surfaces absence, the web /api/ohlcv returns it on the no-history path, and run_card.json carries a data_absence block (null on success paths). Direct string callers (scripts/*) are unchanged.

yfinance pin (yfinance P5). yfinance~=1.4 (verified 1.4+); the guarded quirks (#986 exclusive-end, #1021 stale frames, unnamed index, statement newest-first, rate-limit retries in yf_retry) are documented in dataflows/README.md with a version-bump checklist — a 1.x bump is a deliberate step with a vendor-suite re-run.

## 6.1 Tools by category (auto-generated)

  • core_stock_apis : get_stock_data
  • technical_indicators : get_indicators
  • fundamental_data : get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement, get_basic_financials, get_company_peers, get_insider_activity, get_form4_insider, get_filing_sections, get_risk_factors, get_8k_filings
  • news_data : get_news, get_global_news, get_insider_transactions, get_massive_news
  • news_sentiment (optional): get_news_sentiment — daily series via eodhd,alpha_vantage,gdelt (EODHD /sentiments primary)
  • macro_data : get_macro_indicators
  • prediction_markets : get_prediction_markets
  • analyst_ratings : get_analyst_ratings
  • earnings_calendar : get_earnings_calendar
  • options_data : get_options_chain
  • sec_filings : get_sec_filings
  • short_interest : get_short_interest, get_short_volume
  • computed-analysis tools (bound to the analyst tool loops, see 6.4): get_swing_set, get_relative_strength, get_earnings_event_read, get_catalyst_scale, get_position_sizing, get_risk_gate, get_regime_read, get_volatility_contraction, get_orderflow_read, get_analyst_verdict, get_earnings_surprise, get_portfolio_weights, get_sector_rank, get_strategy_quality, get_margin_of_safety, get_composite_rank, get_tail_risk, get_credit_spread_read, get_session_discipline, get_earnings_quality, get_bollinger_pct_b, get_tranche_plan, get_trade_expectancy, get_fcf_yield, get_valuation_z_score, get_value_dip_setup, get_balance_sheet_health, get_macd_divergence, get_vdu_entry_setup, get_support_structure, get_decline_driver_check, get_extended_indicators (Ichimoku/CCI/ROC/momentum/TRIX/Force/A-D/VPT/CMF/ anchored VWAP/golden-death — strategies.extended_indicators), get_candlestick_patterns (doji/hammer/shooting-star/engulfing/morning+ evening star — strategies.extended_indicators.scan_candlesticks), get_news_sentiment_series (daily news-sentiment -1..1 + 7d SMA + latest innovation, news_sentiment chain), get_sentiment_lead_lag (Pearson/Spearman cross-correlation vs forward returns — strategies.sentiment_research)
  • moomoo-only optional: capital_flow (get_capital_flow), smart_money (get_smart_money), economic_calendar (get_economic_calendar), fed_watch (get_fed_watch), market_breadth (get_market_breadth), revenue_breakdown (get_revenue_breakdown), corporate_actions (get_corporate_actions), earnings_catalyst (get_earnings_catalyst), institution_data (get_institution_holdings), earnings_surprise (get_earnings_surprise_history), expected_move (get_expected_move).

6.2 Vendor implementations per tool (exact)

  • stock/indicators/financials/insiders: alpha_vantage, yfinance, moomoo, eodhd (OHLCV only), tiingo, twelve_data (OHLCV only), stockdata (OHLCV only)
  • news/global-news: alpha_vantage, yfinance, finnhub, moomoo, massive, eodhd, stockdata, gdelt, benzinga, newsapi, seekingalpha (GDELT keyless article list; NewsAPI 100 req/day; Benzinga free tier; seekingalpha = keyless opinion-only tail served last — GDELT/Benzinga opt-in via news_data chain, not default)
  • news-sentiment: eodhd /sentiments (primary, EOD plan), alpha_vantage NEWS_SENTIMENT (25 req/day), gdelt (refuses: its article-list response carries no per-article tone)
  • quant calculators (tools, strategies/*): get_volatility_estimators (Parkinson/GK/YZ/EWMA/GARCH), get_garch_volatility, get_covariance_read (Ledoit-Wolf shrunk + EWMA covariance), get_concentration_read (active share / effective holdings / HHI / entropy), get_tail_decomposition (incremental/component VaR), get_tail_extreme_var (EVT/GPD extreme-quantile VaR/ES), get_mean_reversion_quality (AR(1)/OU half-life), Roll spread + Kyle lambda (get_liquidity_risk/get_kyle_lambda), get_kelly_alloc (multi-asset fractional Kelly), preferred YTM/duration (capital_income --fi), credit hazard/default-prob (get_credit_spread_read), variance-swap strike (get_variance_premium), implementation shortfall (strategy_quality avg_is_bp), get_turbulence_read (the cross-section's Mahalanobis distance from its strictly-prior history under a shrunk covariance + an own-history quantile gate — strategies.market_stress, enable_turbulence_read)
  • market snapshot fallbacks: Massive -> EODHD -> Tiingo -> Twelve Data
  • crypto prices fallbacks: Tiingo -> Twelve Data
  • macro: fred, massive, moomoo (optional)
  • prediction markets: polymarket, moomoo (optional, SG/MY-gated)
  • analyst ratings + earnings calendar: finnhub, moomoo, yfinance (keyless fallback: get_analyst_ratings_yfinance = recommendation summary + price-target consensus; get_earnings_calendar_yfinance = earnings dates + EPS surprise + the days-to-print countdown, as do the Finnhub/moomoo calendar paths - quote that countdown instead of computing it). Institutional holdings also add a keyless yfinance option (get_institution_holdings_yfinance).
  • finnhub free-tier extra (key-gated): get_basic_financials (metrics), get_company_peers, get_insider_activity (insider sentiment); get_fundamentals also accepts finnhub as a vendor
  • options chain: yfinance, moomoo
  • SEC filings: sec_edgar (when EDGAR fails for any reason — e.g. HTTP 403 from SEC fair-access throttling or a non-US ticker with no EDGAR record — the get_sec_filings tool falls back to Massive's get_form4_insider_massive Form-4 insider-activity data, returned under an explicit label so the agent does not mistake it for the full 8-K/10-K set)
  • insider transactions (Form 4): moomoo, yfinance, alpha_vantage, sec_edgar (the news_data category override), then benzinga — the firehose stays last, because its endpoint takes no ticker filter and pages the market-wide stream. sec_edgar is the keyless EDGAR-native open-market read (get_insider_transactions_sec_edgar): issuer-scoped and quota-free, so it outranks the untargeted firehose, though it is narrower than the top three (open-market only, no ownership rollup). It needs the optional extra (pip install ".[edgar]"); without it the vendor raises a typed no-data and the chain advances. Argued in docs/developer/12-data-providers.md
  • short interest: yfinance, moomoo, massive
  • short-sale volume (Reg SHO daily) + ATS dark-pool flow: finra (official public tier, keyless, as-of dates rendered + staleness-gated)
  • congressional trades: congress (keyless House/Senate Stock Watcher mirrors)
  • patent activity: patentsview (free key PATENTSVIEW_API_KEY)
  • FX snapshot: fx (keyless yfinance DXY/pairs)
  • TGA balance: treasury_fiscal (keyless Treasury Fiscal Data)
  • earnings transcripts: fmp (free tier)
  • all A-series/tier tools: moomoo only (optional)

VENDOR_LIST is derived from VENDOR_METHODS (single source of truth) and is therefore exactly the set of registered vendors, sorted: alpha_vantage, benzinga, cboe, congress, eodhd, federal_reserve, finnhub, finra, fmp, fred, fx, gdelt, massive, moomoo, newsapi, patentsview, polymarket, sec_edgar, seekingalpha, stockdata, tiingo, treasury_fiscal, twelve_data, yfinance.

EODHD (TRADINGAGENTS_EODHD_API_KEY, key-gated) — the primary OHLCV vendor (EOD plan $19.99/mo = 100k calls/day @ 1000/min, 30+ years): get_stock_data_eodhd serves daily bars as the same CSV shape yfinance/moomoo produce, registered first in the core_stock_apis chain (eodhd,moomoo,yfinance by default) so moomoo/yfinance stay as fallbacks. The EOD plan also unlocks get_news_eodhd (news), get_corporate_actions_eodhd (splits + dividends), get_exchange_symbols_eodhd (full US symbol list, ~18k common stocks) — the screener's default --universe eodhd-us source — replacement; get_top_movers_symbols_eodhd is the machine-readable symbol table behind it, consumed by the screener's --universe eodhd-losers (equity-filtered against the exchange-symbol common-stock list). --value-dip-loose relaxes the value-dip technical entry to RSI<=35 OR %b<=0.10 and appends a ranked near-miss table). These back the get_market_snapshot / get_top_movers tools when Massive 403s on the free plan. Fundamentals/technicals/intraday/options are not on the EOD plan (they need the $59.99 Fundamentals feed), so those chains keep moomoo/yfinance first. A --vendor eodhd preset (batch.py/pipeline.py) puts EODHD first in the OHLCV + news + corporate-actions chains and disables the moomoo-only enrichment.

Tiingo (TIINGO_API_KEY, free Starter tier, key-gated) — additive market data: deep EOD OHLCV (/tiingo/daily/{t}/prices, 7+ yrs, with resampleFreq daily/weekly/monthly/annually), fundamental statements in JSON (/tiingo/fundamentals/{t}/statements — income/balance/cashflow keyed by dataCode, rendered to canonical-friendly label : value blocks that statement_parsing maps via _ROW_ALIASES), a delayed IEX quote (/iex/{t}), and crypto OHLCV (/tiingo/crypto/prices). News (403) and intraday (404) are NOT on the free tier. Low caps (~1,000 calls/day, 50/hr, 500 symbols/mo) mean it sits last in the chains after eodhd/moomoo/massive, relying on the 6h disk TTL cache; a 429 degrades to the next vendor via VendorRateLimitError. The IEX snapshot backs get_market_snapshot as a third fallback (Massive -> EODHD -> Tiingo).

Massive.com (MASSIVE_API_KEY, key-gated) — US-centric additive vendor: get_massive_news (get_news chain + dedicated tool on the news/social nodes) returning per-article structured sentiment (positive/negative/neutral

  • reasoning); and get_macro_indicators_massive (macro_data chain) serving treasury-yields / inflation / inflation-expectations / labor-market (aliases match the FRED surface: 10y_treasury, yield_curve, cpi, ...). The FRED macro_data vendor also exposes ICE BofA credit-OAS aliases for the credit-stress read: hy_oas/hy_spread/high_yield_oas → BAMLH0A0HYM2, ccc_oas/ccc_and_lower_oas → BAMLH0A3HYC, bb_oas → BAMLH0A1HYBB. Massive also drives a deterministic macro_backdrop (yield-curve inversion / elevated breakevens) that keeps the B1 catalyst overlay de-risking even when the OpenD event calendar is unavailable (see strategies/catalyst.py + docs/massive_integration.md §3a). Short interest (get_short_interest chain, FINRA 2-week cadence) and a get_short_volume tool (daily short-sale ratio, market analyst) come from the /stocks/v1/* endpoints. Form-4 insider activity is a get_form4_insider tool bound to the fundamentals analyst. (13-F is not wired: Massive's /stocks/filings/vX/13-F has no security filter — only filer_cik/filing_date — so a per-ticker aggregate would be misleading; moomoo get_institution_holdings stays the source for that signal.) NOI (massive_noi.py, WebSocket monitor) and Flat Files (massive_flat.py, bulk OHLCV loader for the screener/backtests) are standalone monitored utilities, not graph @tools — see docs/massive_integration.md §3e. Row 5 (entitled on the current plan): get_company_peers gains a massive option (related-companies) to the fundamentals analyst; get_corporate_actions and a dedicated get_dividends bind Massive dividends/splits to the fundamentals analyst; get_ipos (IPO reference) binds to the news analyst. Plan-dependent recency/entitlements; FMV/Greeks are Business-only (unavailable, never invented). US-only — supplements, not replaces, moomoo/yfinance non-US coverage. See docs/massive_integration.md.

➠ get_market_snapshot / get_top_movers also bound to the market analyst (plan-gated, see §6.4), and get_ratios to the fundamentals analyst; a pipeline.py --universe top-movers-massive option supplies a Massive mover universe (plan-gated). These degrade to "upgrade at massive.com/pricing" on the free plan and activate when the account's plan includes them. EODHD fallback: when Massive 403s (free plan), get_market_snapshot falls back to eodhd.get_market_snapshot_eodhd (/api/real-time/{ticker}, live 15-20 min delayed OHLCV + change, works on the EOD plan) and get_top_movers falls back to eodhd.get_top_movers_eodhd (/api/real-time/{ticker}?ex=US, one call returns ~18k US stocks sorted by change_p — a movers + universe replacement).

6.3 Symbol mapping (Yahoo <-> moomoo / broker)

dataflows/symbol_utils.py::normalize_symbol() maps broker symbols to Yahoo gold XAUUSD -> GC=F, forex EURUSD -> EURUSD=X, crypto BTCUSD -> BTC-USD, indices SPX500 -> ^GSPC; moomoo code map (_moomoo_code(): US., HK. pad, JP., SH., SZ., AU., CA., SG., MY., CC.USD).

US share classes are quoted with a hyphen on Yahoo (BRK-B, BF-A), but moomoo emits dotted (BRK.B, MOG.A, MOG.B, PBR.A), which Yahoo cannot resolve. normalize_symbol converts a dotted single-letter share-class suffix (.A/.B/.C/.K...) to the hyphen form (BRK.B -> BRK-B), while leaving London's single-letter .L exchange and all multi-letter exchange suffixes (.SA Brazil, .TO, .AX, .HK, .NS, .BO, ...) untouched. Moomoo's _moomoo_code deliberately does NOT use this - it keeps the raw dotted form the movers rank returns - so both vendors resolve the same symbol.

6.4 Computed-analysis tools

Market-analyst house tools (bound only to the market node, not part of the vendor category system - see 6.1): get_verified_market_snapshot (deterministic OHLCV/indicator verification snapshot, the market analyst's source of truth), get_momentum_scan (5-pillar day-trade pre-filter + first-pullback + intraday confirmation), get_market_snapshot_alpaca (live 1-minute Alpaca L1 price/VWAP/volume when configured). News/earnings house tools bound to the news node: get_catalyst_scale, get_earnings_event_read.

The analyst tool loops bind deterministic calculators from tradingagents/strategies/* so the LLM reasons over computed numbers rather than re-deriving them:

Surface labels in the "Bound to" column are machine-checked (tests/test_doc_binding_claims.py): market / news / fundamentals / risk debators / trader name the toolset a tool is callable from, and declared means no agent surface can call it — a library/CLI read whose reason lives in tests/test_calc_agent_wiring.py::TOOL_LEGACY_BINDING.

Tool Wraps Bound to Returns
get_market_technicals(ticker) domain_bundles.get_market_technicals market ONE-pass composite (W4-1): regime + swing structure + momentum detail, with the bundle's data_quality and missing_fields
get_fundamental_profile(ticker) domain_bundles.get_fundamental_profile fundamentals ONE-pass composite (W4-1): value floors + valuation z + FCF yield + margin of safety (PIT-gated), with data_quality / missing_fields
get_sentiment_flow_feed(ticker) domain_bundles.get_sentiment_flow_feed news ONE-pass composite (W4-1): news sentiment series + GDELT tone + order imbalance, with data_quality / missing_fields
get_portfolio_risk_envelope(ticker, basket?) domain_bundles.get_portfolio_risk_envelope risk debators ONE-pass composite (W4-1): the name's tail risk + liquidity read + book tail risk, with data_quality / missing_fields
get_swing_set(ticker) swing.swing_report market trend stack, RSI band, 1-ATR stop, 2R/3R targets, VCP, trail
get_swing_exits(ticker) swing.chandelier_exit + trail_ema + targets_rr market chandelier trailing stop (3x ATR(14, simple mean of TR) below the 22-bar high) + 20-day EMA trail + 2R/3R targets measured to the chandelier stop (named in the line; get_swing_set's 2R/3R use its structure stop)
get_dip_technical(ticker) swing.rsi + technical_factors (KST/MFI/Stoch) + value_dip.bollinger_pct_b market RSI/%b + Stochastic + MFI + KST dip-timing read (OVERSOLD / not-oversold)
get_mean_reversion_tech(ticker) technical_factors (StochRSI/RSI2/W%R/Keltner/Donchian/OBV/PSAR/Elder) market mean-reversion dip-timing + exit technicals
get_opening_range(ticker) market_session.opening_range market ORB breakout + 2R stop/target
get_option_breakeven(long_strike, long_premium, short_strike?, spot?, short_ttm_days?, delta?, days_to_earnings?, days_to_ex_div?) strategies.options_breakeven.pmcc_read market option-position breakeven (strike + premium) + PMCC short-call floor, long-leg intrinsic/extrinsic split, delta band, 30-45d theta window, earnings/ex-div assignment risk — advisory, None-safe (n/a, never fabricated)
get_vol_surface_shape(ticker) strategies.options_surface.surface_shape + term_structure_slope market 25-delta risk reversal (skew sign), 25-delta butterfly (smile curvature), term-structure slope
get_parity_screen(ticker, cost_bps=5) strategies.options_surface.parity_violation market put-call parity / conversion-reversal screen (a screen, not a trade)
get_shift_detection(ticker, kind=mean) strategies.regime.cusum + ewma_control + strategies.complexity market CUSUM/EWMA online shift detection + LZ complexity beside its same-length i.i.d. null band (complements the batch HMM)
get_taylor_read(policy_rate, inflation, output_gap?) strategies.cycle_tilt.taylor_rule news Taylor-rule implied policy rate + actual-vs-rule deviation (tight/easy/neutral stance)
get_lottery_factors(ticker) strategies.lottery market MAX (largest single-day return in the month) + IVOL (idiosyncratic vol) lottery-tilt screen — high = expected underperformance (quality penalty)
get_execution_schedule(notional, intervals, volatility?, temp_impact?, risk_aversion?, method=...) strategies.execution_schedule market Almgren-Chriss optimal trajectory + TWAP/VWAP/POV benchmarks (E[IS]/var(IS), per-interval trades)
get_spread_estimate(ticker, current_date?) strategies.liquidity_risk.corwin_schultz + abdi_ranaldo + spread_estimate market quote-free high-low spread FLOOR (median of both estimators) for names with no quoted spread; None when the two-day correction is negative
get_return_decomposition(ticker, current_date?) strategies.market_session.decompose_returns market overnight vs intraday log-return legs + intraday share of variance (which leg carried the move, never why)
get_quality_factors(ticker, current_date?) dataflows.quantitative_scores.gross_profitability + net_operating_assets (+ round-3 S1/S2/S10) fundamentals GP/A (Novy-Marx) + NOA (Hirshleifer); unavailable when COGS or the prior-year balance sheet is missing. Gated additive rows: the Altman Z'/Z''/Z''-EM variant + distress zone with the X4 basis (enable_altman_variants), the F-Score's paper band + recorded deviations (enable_f_score_detail), the G/C scores with each median leg's exclusion printed when no peer panel is supplied (enable_growth_scores) — also renders the §55 SBC-adjusted free-cash-flow read (reported FCF beside economic FCF = FCF minus us-gaap:ShareBasedCompensation, plus SBC/revenue) from the SEC XBRL series it already fetches, and prints the producer's refusal reason when the filer files no such concept
get_valuation_band(ticker, point_value?, current_date?, alpha?) strategies.conformal.rolling_band fundamentals conformal band around a model value with the REALIZED coverage printed beside the nominal level; needs >= conformal_min_pairs model-vs-realized pairs
get_analyst_revision_index(ticker, current_date) strategies.analyst_revisions.revision_index fundamentals weighted, coverage-guarded analyst revision ratio (MSCI weights 3/2/1 over the periods supplied; denominator up+down printed as a deviation from analyst coverage; <2 actions -> unavailable; the estimate-change leg states why it is unavailable) - gated by enable_analyst_revision_index
get_disclosure_tone(ticker, current_date?) strategies.text_factors.lm_tone + readability + divergence news Loughran-McDonald tone counts + readability + filing-vs-news tone/complexity gaps; a zero-hit read is NO SIGNAL, not neutral
get_book_risk_budget(current_date?) strategies.book_risk.min_cvar_weights + copula_scenarios risk debators minimum-CVaR weights at the family's 95% tail (tail_probability=0.05), with the solved book CVaR and its tail_count printed beside the configured budget (a comparison, not a solver constraint — the LP binds on the cap / move limit); advisory sizing only, never an order
get_risk_overlay(portfolio_value, floor?, expected_vol?, target_vol?, multiplier?) strategies.portfolio.cppi_exposure + size.volatility_target_scale market CPPI floor-protected risky exposure + vol-targeting scale (advisory overlay)
get_earnings_transcript(ticker) FMP Earnings Transcript API (free tier) fundamentals Latest earnings-call transcript: date/quarter + (when served) excerpt — quote only from returned text
get_congress_trades(ticker) House/Senate Stock Watcher mirrors (keyless) fundamentals Congressional open-market stock trades: net buys/sells + samples per chamber
get_financial_history(ticker, years=?) SEC EDGAR XBRL companyconcept (keyless) fundamentals Annual 10-K financial history (~10-15y; pre-XBRL years n/a)
get_tga_balance(days=?) US Treasury Fiscal Data API (keyless) news Daily Treasury General Account operating-cash balance + net draw/build (reserve injection/drain) read
get_fx_snapshot() yfinance (delayed) + FRED DTWEXBGS news DXY + major FX pairs with 1d/5d changes, plus RISK-20's per-unit 1-day 95% FX move (daily sigma of the broad dollar index × z=1.645) — multiply by the notional you hold; advisory
get_macro_indicators aliases extended fred.MACRO_SERIES news liquidity (tga/reverse_repo/repo/fed_balance_sheet/effr/sofr), commodities (wti/gold/natgas/copper), global rates (ecb_rate/boj_rate)
get_dark_pool_flow(ticker, weeks=?) FINRA ATS weekly (OTC Transparency, keyless) market Weekly off-exchange share/trade/notional flow per issue (as-of + staleness-gated)
get_short_sale_volume(ticker, days=?) FINRA Reg SHO daily (keyless) market Daily short-sale volume (% short-sale of volume), keyless variant of get_short_volume
get_edgar_fulltext_search(query, forms?, date_range?) SEC EDGAR full-text search (efts.sec.gov, keyless) fundamentals Full-text filing search (10-K 'major customer' footnote, peer mentions, thematic scans)
get_patent_activity(ticker) USPTO PatentsView (free key) fundamentals Annual granted patents + recent titles (name-based assignee) — innovation/moat gauge
get_gap_type(ticker) market_session.gap_type market common/breakaway/runaway/exhaustion + fill stats
get_order_imbalance(ticker) market_session.order_imbalance market buy/sell-heavy from flow nets
get_premarket_liquidity(ticker) market_session.premarket_liquidity market thin-book warning
get_post_close_confirmation(ticker) market_session.post_close_confirmation market stopped-out / target-hit / holding
get_gdelt_sentiment(ticker, look_back_days?) gdelt.get_gdelt_tone_series news UNAVAILABLE, with the reason: GDELT's DOC 2.0 article-list response carries no per-article tone (a per-day series needs mode=timelineTone, unwired, verified against GDELT's API docs 2026-09-27)
get_technical_factors(ticker) technical_factors (ADX/pivots R1-R3/S1-S3 + ATR-normalised pivot distance/Aroon/Fisher/Chaikin/Elder-Ray/Supertrend/volume-profile) market extended technicals in one call (shares the run-level OHLCV cache)
get_extended_indicators(ticker) strategies.extended_indicators (Ichimoku/CCI/ROC/momentum/TRIX/Force/A-D/VPT/CMF/anchored VWAP/golden-death) market the standard trend/momentum/volume group plus cloud + VWAP cost basis, one call (shares the OHLCV cache)
get_candlestick_patterns(ticker) strategies.extended_indicators.scan_candlesticks market latest-bar doji/hammer/shooting-star/engulfing/morning+evening star scan
get_book_tail_risk(ticker, weights?) book_risk.portfolio_cvar + book_correlated_stress + drawdown_gate + book_context resolver market/trader book-level portfolio CVaR + correlated -10% stress + the measured book drawdown, each labelled with the mix it came from (configured basket vs the analyzed name alone - a name-level drawdown is never printed as "the book")
get_covariance_read(tickers) covariance_models.ledoit_wolf_shrink + ewma_covariance risk debators Ledoit-Wolf shrunk covariance (delta = (b2/t)/d2) + EWMA vol for a name list
get_concentration_read(weights, benchmark_weights?) portfolio.active_share + weight_hhi + effective_holdings + weight_entropy risk debators how concentrated a proposed book is (active share vs benchmark, HHI, entropy)
get_tail_extreme_var(ticker, alpha?) book_risk.extreme_quantile_var risk debators EVT/GPD extreme-quantile VaR/ES (extrapolates beyond the observed worst day)
get_kyle_lambda(ticker) liquidity_risk.kyle_lambda declared daily-bar price-impact slope (cross-sectional liquidity proxy)
get_kelly_alloc(expected_excess_returns) portfolio.kelly_weights declared multi-asset fractional Kelly alloc (w = f·Σ⁻¹μ, long-only)
get_trade_outcome_metrics(ticker, entry, stop?, target?, direction?) prediction_ledger.outcome_metrics risk debators MAE/MFE + stop/target hits over the trailing closes (W1-3)
get_prediction_ledger_score(results_dir?, auto_invalidate?) prediction_ledger.score_all declared calibration read: scores every logged prediction vs realized closes (W1-1)
get_stress_grid_read(base_value, revenue_shifts?, discount_shifts?) regime_performance.stress_grid declared DCF-style scenario grid (W2-11)
get_macro_regime_read(rate_change?, curve?, spread?, usd?, vol?) regime_performance.macro_regime news cross-asset Risk-On / Liquidity-Contraction / Stagflation (W4-6)
get_liquidation_days(ticker, shares_to_liquidate?) liquidity_risk.days_to_absorb market days for the market to absorb a block at a 15% participation cap
get_premarket_review(ticker, prior_close?, open_price?, prior_stop?, entry_price?) pre_market.review_decision market deterministic CONFIRM / REVISE / REJECT arbiter from measured deltas
get_relative_strength(ticker) relative_strength.relative_strength_report + ::relative_strength_vs_sector market leading/uptrend/lagging/diverging/unknown vs SPY, plus the sector-relative excess over the ticker's SPDR ETF (TECH-19; the benchmark is reported beside it, never blended)
get_earnings_event_read(ticker, date) events.post_earnings_play + catalyst.last_earnings_surprise news surprise %, drift side, print-day move, PEAD setup
get_catalyst_scale(ticker, date) catalyst.build_catalyst_snapshot news 0..1 scale + verdict + reasons
get_position_sizing(confidence, stop_dist_pct, ...) size.kelly + risk-budget market min(kelly_quarter, risk/stop, cap). Every argument is a FRACTION of capital (0.01 = 1%); a percent number (stop_dist_pct=8.371, risk_per_trade=1.5) is refused with the corrected form instead of being read as a size
get_risk_gate(size_pct, ...) risk_governor.govern market/trader PASS / WARN / REJECT for the trade-level limits. size_pct and every other *_pct argument is a FRACTION: a percent number is refused before the governor runs (size_pct=1.0 used to be read as 100% and REJECT a 1% proposal - NVDA 2026-09-15). The measured book drawdown is NOT a caller input: a drawdown_pct passed here is reported as a hypothetical and never moves the verdict - the portfolio drawdown gate is get_composed_risk_gate (one resolver: strategies/book_context.py)
get_regime_read(ticker) overlays.build_strategy_overlays market regime label + position scale + momentum/52w
get_volatility_contraction(ticker) swing.vcp_setup market VCP base depths + contraction + near-breakout
get_orderflow_read(ticker) orderflow.summarize (+ guarded fetch) market inst/retail net, distribution, divergence, alignment
get_financial_trends(ticker, curr_date, items?, periods?) agents/utils/financial_trends.py fundamentals labelled quarter-series table: | Item | <date (FYq)> | … | YoY | QoQ |, fiscal labels derived from the vendor's own annual period ends, every delta naming the two periods it compared (+105.9% (Q2 FY26 -> Q2 FY27)), n/a/unavailable with a reason rather than a guess - the producer the 2026-09-12 NVDA review found missing when a +47% two-quarter move was reported as YoY
get_analyst_verdict(ticker, date) screener screen_ticker + normalized.trap_verdict fundamentals EY, EV/EBIT, Tobin's Q, F/M/Z, Net-Net, trap risk, ROE, YoY
get_earnings_surprise(ticker, date) events.surprise_score + catalyst.last_earnings_surprise fundamentals surprise % + side
get_portfolio_weights(scores, sector_map, ...) portfolio.value_ratio_weights + adjust_for_caps fundamentals cap-respecting value weights
get_basic_financials(ticker) finnhub.get_basic_financials_finnhub fundamentals EPS/revenue YoY, ROE/ROA, margins, payout (free tier)
get_insider_activity(ticker) finnhub.get_insider_activity_finnhub fundamentals 12m net insider change + mspr + trend
get_company_peers(ticker) finnhub.get_company_peers_finnhub fundamentals comparable peer group
get_form4_insider(ticker, start, end) massive.get_form4_insider_massive fundamentals net open-market Form 4 buys - sells (excl. A/M)
get_filing_sections(ticker, section?) massive.get_filing_sections_massive fundamentals 10-K item text (risk_factors, business) as bounded excerpts
get_risk_factors(ticker, include_taxonomy?) massive.get_risk_factors_massive fundamentals disclosed risks grouped by vendor category + supporting text
get_8k_filings(ticker) massive.get_8k_filings_massive fundamentals recent 8-K current reports with their item text
get_ratios(ticker, date?) strategies.ratios.compute_ratios (local derivation; Massive plan-gated cross-check via get_fundamentals) fundamentals computed EV/EBITDA, P/E, P/B, P/S, P/CF, P/FCF, ROE, ROA, D/E, Current, Quick, cash ratio, dividend yield, FCF, market cap (free, no paid plan; missing inputs n/a). The returned dict also carries the §55 SBC keys (sbc, sbc_to_revenue, sbc_adjusted_fcf, sbc_to_fcf), which are engine-only and NOT rendered: on this vendor-only path the flat sbc key has no source — fetch_ticker runs here without with_sec_series (the SEC 10-K series is what fills the key) and the vendor cash-flow row is labelled Stock Based Compensation, which no canonical alias maps (live MSFT 2026-09-27; INCY 2026-09-29, whose EDGAR sbc series does resolve) — so the read renders through get_quality_factors instead, which fetches the series
get_exit_check(entry, close?, atr?, ..., ticker?) strategies.exits.exit_check market stop-to-breakeven, ATR target, holding action. Pass ticker and the close + 14d ATR are MEASURED from the run's series (a conflicting caller value is reported as ignored); without one the output labels its levels CALLER-SUPPLIED, so an invented ATR (NVDA 2026-09-15 passed atr=7.0) is never read as computed
get_allocation(scores, sector_map?, returns_by_name?) strategies.portfolio.adjust_for_caps (+ correlation_penalty when enable_correlation_penalty is on and return series are provided) fundamentals cap-respecting book allocation; optionally correlation-penalized (down-weights names whose avg pairwise correlation with the book exceeds the threshold)
get_regime_components(ticker) strategies.regime market vol_pct / trend / chop / regime label breakdown, plus the SPY/QQQ/IWM index reads (REG-4), the market-depth block and the regime state block: HMM transition probability + duration (REG-16) and the entropy/confidence/stability/surprise metadata (REG-17)
get_consensus(ratings) strategies.consensus declared numeric agreement -> high/low consensus
get_momentum_detail(ticker) strategies.momentum market pillars, rvol (session-adjusted on a forming bar), vwap, ema9, first-pullback
get_beat_miss_sizing(side, catalyst) strategies.events.position_mult_by_side news post-earnings key multiplier
get_dcf_valuation(ticker, date, growth?, erp?) strategies.dcf.compute_dcf fundamentals provider-sourced DCF fair value + WACC / EV breakdown; Gordon terminal value anchored to the last projected year's FCF (F_0·(1+g)^years, discounted back from year N) — audit-fixed 2026-09-06
get_sector_rotation_screen(enable_breadth?, top_n?, constituent_universe?, eodhd_cap?) strategies.sector_screener market Sector-first rotation screen: regime cap + multi-factor SPDR rank + RRG quadrant + pullback-divergence leaders + dispersion trend (+ breadth on the full S&P-500 universe n<20-gated, EW/CW vs the real RSP* index/own-50d-SMA, multi-timeframe 20/50/200 matrix + McClellan Osc/MSI + Setup A-B) — advisory
get_sector_rank(ticker) strategies.sector_rank.rank_sectors + sector_standing (+ gated rank_sectors_multifactor / rank_industry_group / constituent_breadth / leadership_ratio) market 11-SPDR 1m/3m momentum ranking + the ticker's sector standing; when enable_sector_multifactor (P1) /enable_sector_industry (P2) /enable_sector_breadth (P3) are on, advisory score / industry / breadth / leadership lines append (default off → legacy output unchanged). The P2 industry layer uses the sector_rank.INDUSTRY_ETFS universe — extended to the IT subsector set (SOXX/IGV/CIBR/SKYY/AIQ/BOTZ/DTCR/NXTG/IYW/FINX/XSD parented XLK; VGT = benchmark, excluded from the pool) with dual-benchmark RS: on the XLK pool each row also gets rs2 (percentile vs VGT), so "strongest IT subsector" (vs VGT) and "beats the market" (vs SPY RS) are answered from one pass (top-pick line appends rs2_vs_vgt=…).
get_dupont_read(net_margin, asset_turnover, equity_multiplier, tax_burden?, interest_burden?) strategies.dupont fundamentals DuPont ROE decomposition + driver verdict (margin/turnover/leverage/mixed-led via log attribution)
get_scenario_dcf(fcf, wacc, shares?, cash?, debt?, g_base?, g_bear?, g_bull?, margin_shock_bear?, margin_shock_bull?, market_price?) strategies.scenario_dcf fundamentals bear/base/bull DCF value range + market band (below bear/bear-base/base-bull/above bull) + margin of safety
get_earnings_quality_verdict(net_income, ocf, total_assets, fcf?, capex?, eps_growth?, fcf_growth?) strategies.earnings_quality fundamentals earnings-quality CONCERN verdict (LOW/MEDIUM/HIGH = concern; cash conversion, accruals, FCF-vs-EPS divergence)
get_strategy_quality(ticker, returns?) strategies.evaluate + statistical.omega market net CAGR / annualized vol / Sharpe / Sortino / PSR / max drawdown / Omega over a return series
get_downside_read(ticker, target?) strategies.rate_utils.downside_measures market semi-deviation / downside deviation / shortfall probability / average shortfall vs a target (MAR)
get_horizon_var(ticker, horizon_days?, confidence_level?) strategies.book_risk.var_cvar_horizon market empirical + parametric VaR/CVaR at a multi-day horizon, with the sqrt(T) i.i.d. scaling gate. The argument is a confidence LEVEL (0.95), the opposite convention from the daily-tail cvar reads' tail probability (0.05) — the parameter was alpha until 2026-10-12
get_trailing_exit(ticker, entry, peak, current, trail_pct?) strategies.exits.trailing_stop_exit market peak-trailing / give-back stop verdict + exit price
get_exit_engine(ticker, entry_bars?, entry_date?, entry_price?, risk_gate?) strategies.exit_engine.evaluate_long_exit market the five-mechanism exit decision for a position that is ALREADY OPEN — 2.5×ATR initial stop, 3×ATR chandelier ratcheted from the high-water mark since entry, a confirmed MA50 breakdown (two consecutive closes), momentum deterioration as persistence, and the opportunity-cost stall — published as HOLD / REDUCE / EXIT / RISK_EXIT / UNAVAILABLE with the rule that decided. Point-in-time: the stop tested on a bar never uses that bar's own high, a close-evaluated signal fills at the next tradable price and an intraday stop that gapped fills at the open. Pass entry_bars or entry_date; any rule it cannot measure is named rather than assumed safe. Gated by enable_exit_engine
get_exit_plan(entry, atr?, current?, peak?, stop?, giveback_pct?, ticker?) strategies.exits.breakeven_after_confirmation + max_giveback_exit market structure/R breakeven trigger + margin-giveback stop in one exit-management read. Same measured-level contract as get_exit_check: pass ticker for a MEASURED close + ATR, otherwise the output labels its levels caller-supplied
get_scaleout_plan(entry, stop, t1_fraction?) strategies.swing.scaleout_plan market tiered partial-profit plan (sell T1 fraction -> break-even -> trail)
get_payoff_asymmetry(ticker, returns?, threshold?) strategies.statistical.omega market Omega ratio (gains/losses payoff asymmetry) about a threshold
get_book_correlation(returns_by_name, method?) strategies.statistical.correlation_matrix market full pairwise correlation (avg + max pair) over a book
get_risk_parity_alloc(ticker, returns_by_name) strategies.portfolio_optimizer (risk_parity + min_variance + max_diversification + risk_contribution) market risk-parity weights (equal marginal risk contribution via the converging w ← b⊘Σw fixed point — audit-fixed 2026-09-06; degrades to equal-weight with a note on non-convergence), min-variance weights, max-diversification weights (Choueifaty Σ⁻¹σ) and per-name risk contributions from a real covariance matrix
get_hrp_alloc(ticker, returns_by_name) strategies.hierarchical_risk_parity market Hierarchical Risk Parity book weights (single-linkage HRP; robust under noisy covariance, no Σ inversion) + cluster order
get_momentum_12_1(ticker) strategies.momentum.momentum_12_1 market canonical 12-1 momentum (skips the last month's short-term reversal; needs ~274 bars)
get_margin_of_safety(ticker, intrinsic) strategies.normalized.margin_of_safety fundamentals (intrinsic - price)/intrinsic safety band (wide/modest/negative)
get_cross_section_momentum(ticker, names?) strategies.cross_section.momentum_book (+ centered_rank, quantile_split, neutralize_book, book_risk.net_beta) market / fundamentals advisory cross-sectional momentum book over a caller-supplied panel: ranks by risk-adjusted momentum (factors.vol_adjusted_momentum, lookback 126 / vol window 21), equal-weights the top and bottom quintiles, neutralizes dollar + beta (and sector when the caller supplies sector_map), and reports net beta before → after; None under 8 scored names — a named gap, never a fabricated long/short. The panel is the names you pass (or the ticker's vendor peers), so its size travels with the read and it is not market breadth
get_composite_rank(ticker, factors?, current_date?) strategies.factors.composite_score (+ round-3 S3 under enable_quality_composite) fundamentals cross-sectional value+momentum composite percentile vs industry peers (the peers come from finnhub.peer_symbols, which PARSES the vendor's rendered "Peers: A, B, ..." string - iterating it made the peer set the sentence's characters); gated additively the 0-100 composite quality score over the same peer set with its metric set, coverage, peer count and quality band
get_tail_risk(ticker, alpha?) strategies.book_risk.cvar / simple_var / stress_loss + cdar market historical VaR / CVaR tail budget + CDaR/DVaR drawdown-tail + -10% uniform stress loss. The appended one-line family reads the same return series — p_loss (RISK-16's sibling §34), sigma_ratio/sigma_expansion, sterling, reversal, the loss-frequency family, and RISK-16's stop_hit_1m + stop_dist (the share of trailing month-long paths that touched the repo's own 2xATR stop) — each printing n/a when unmeasurable
get_credit_spread_read(date) strategies.credit_spread.credit_stress_level market FRED ICE BofA HY/CCC/BB OAS + deterministic credit-cycle band (low/mod/high/severe) + de-risk scale
get_session_discipline(ticker, peak_pnl?, current_pnl?) strategies.momentum.session_flags + psych_level + past_optimal_window market intraday walk-away rules (giveback, max-daily-loss, past 10:00 ET optimal) + nearest psych levels
get_earnings_quality(ticker, date) strategies.earnings_quality + strategies.normalized.trap_verdict fundamentals provider-fed earnings-quality read: consensus CONCERN verdict (cash conversion, accrual ratio, FCF=OCF-
get_bollinger_pct_b(ticker) strategies.value_dip.bollinger_pct_b market Bollinger %b (price position inside the 20-day 2-sigma population-SD band; a vendor sample-SD band is 1.026x wider); %b <= 0 at/piercing the lower band, <= 0.10 the mean-reversion entry zone
get_tranche_plan(ticker, weights?, risk_pct?, account?) strategies.value_dip.tranche_plan market 3-tranche scale-in plan (P1/P2/P3 at 1.0/2.0 ATR, the EXECUTED share-weighted avg entry with the nominal sum(w_i*P_i) beside it, composite stop P3-1.5ATR, capital-at-risk check, 1.8R/3.0R targets off the executed R + blended R:R + breakeven win rate)
get_trade_expectancy(p_win, avg_win, avg_loss, rr?) strategies.value_dip.expectancy + breakeven_win_rate market per-trade expectancy E = p*W - (1-p)*L and breakeven win rate 1/(1+R:R)
get_fcf_yield(ticker, date) strategies.value_dip.fcf_yield fundamentals FCF / market cap (>= 6% is the value-dip value-floor row)
get_capex_quality(ticker, date) strategies.capex_quality.capex_quality_read fundamentals Capital-allocation read (advisory, deterministic): CapEx intensity z vs the name's own history, funding cover OCF/CapEx, CapEx-vs-revenue 5y CAGR elasticity, incremental ROIC (3y lag, dNOPAT/dInvestedCapital) + economic spread vs WACC, CapEx ROI 3y, payback, FCF-recovery gap to a 3% target yield, a 5-regime label (HARVEST / PRODUCTIVE / PRODUCTIVE_INVESTMENT / INVESTMENT_WATCH / OVERINVESTMENT / DISTRESS) and a 0-100 quality score + advisory valuation penalty. NEVER a hard gate - distinguishes negative-FCF reinvestment (PRODUCTIVE_INVESTMENT) from value destruction (OVERINVESTMENT / DISTRESS)
get_valuation_z_score(ticker, date, multiple?) strategies.value_dip.valuation_z_read fundamentals historical valuation Z (current vs own trailing P/E, EV/EBITDA or P/FCF; Z <= -1.5 = cheap vs history)
get_value_dip_setup(ticker, date) strategies.value_dip.value_dip_setup fundamentals the hybrid allocation matrix (value floor + technical entry + trade risk + exit target) as one computed candidate verdict
get_balance_sheet_health(ticker, date) strategies.value_dip.balance_sheet_health fundamentals D/E < 1.0 OR current ratio > 1.5 (Step-1 balance-sheet gate)
get_macd_divergence(ticker) strategies.value_dip.macd_divergence market Daily RSI(14) / MACD-histogram momentum divergence (bullish-divergence / higher-low / lower-low-confirmation)
get_vdu_entry_setup(ticker) strategies.value_dip.vdu_entry_setup market Step-2 entry ladder: volume dry-up near support -> divergence/higher-low -> trigger candle (close above prior high, session-adjusted RVOL >= 1.3x), plus the reported volatility-compression reads (compression / range_tight) and, when enable_mechanical_volume_discount is on, the OPEX-week-discounted rvol_gate
get_support_structure(ticker) strategies.value_dip.support_structure market major weekly / multi-month base support + 200-day SMA proximity
get_decline_driver_check(ticker, date) strategies.value_dip.decline_driver_check fundamentals negative-force screen (clean/caution/structural): trap-HIGH, accruals>6%, negative 12-1m momentum, non-positive FCF/ROE, severe EPS decline

| get_regime_gate_read(ticker, catalyst_window?) | strategies.regime.regime_gate_read | risk debators | mean-reversion knife guard: vol_pct + fast-downtrend + pass/block verdict | | get_fixed_risk_size(equity, risk_frac, entry, stop_loss, commission_rate?, units?) | strategies.risk_sizing | risk debators / trader | commission-aware, tranche-aware fixed-risk share count (the governor-budget sizer) | | get_exit_overrides(targets, state_by_name, max_drawdown_pct?, trail_pct?) | strategies.risk_manager.manage_risk + trailing_stop_targets | risk debators | two-pass liquidate/shrink overrides (Lean L1) from persisted entry/peak/current state | | get_pre_trade_read(symbol, notional, max_notional?, max_rate?, window_secs?) | strategies.risk_checks.pre_trade_check + RateLimiter + notional | risk debators | notional-cap + rolling-rate submission gate (advisory) | | get_ledger_risk_state(ticker) | memory log + strategies.pre_market.ledger_track_record | risk debators | realized win-rate drift + paper-reviewer track record (daily-loss/HWM inputs) | | get_trade_plan(ticker, price?) | strategies.trade_plan.build_trade_plan | risk debators / trader | the written plan card as a callable. Carries §103's ENTRY block (entry ceiling, §100 final entry price = the min over the terms present with each term's value, target entry, execution price = §103's liquidity_adjusted_entry_price, risk-adjusted max entry, tranche levels) and its EXIT block (the §101 predicate: every evaluated condition, coverage N/7, the level that fires and its price, and the exits NOT measurable at plan time named absent - never "no exit"). The card is also the string injected into computed_decision_context, so the Trader and the PM read the same rows. The card's own assembly additionally captures the structured §103 object (build_trade_plan(capture=...)): the graph stores it on the entry_exit_block state channel, and trade_plan.render_entry_exit_block renders it into the Trader's plan and the PM's decision — so the entry price and the exit price print in 3_trading/trader.md, 5_portfolio/decision.md and the consolidated report, and travel in research_decision.json (see §"the only input contract" below). One assembly per run: those surfaces read the captured dict, never a second computation and never a regex over this card's markdown. The captured object carries §103's complete member set — all 13 ENTRY and all 16 EXIT names the spec lists — assembled from this card's own primitives plus strategies/entry_exit_families.py, which computes the §8-§42 constructions (the volatility and support entries, the volatility / ATR / support / break-even stops, the volatility / risk-multiple / fair-value targets) by the formula each section states. Each member is the price the run measured or None with the reason it is absent; momentum_entry_price / momentum_target have no score→price map and fair_value_price / fair_value_target need statement data the pre-graph card never fetches, so both pairs are named absent rather than defaulted | | get_fixed_income_risk(ticker, years?) | strategies.fixed_income | fundamentals | preferred yield + duration/DV01/convexity risk rows (perpetual YTM n/a) | | get_pair_risk(ticker_x, ticker_y, window?, maxlag?) | strategies.statistical.cointegration_pair + granger_causality (fetches both close series) | market | Engle-Granger cointegration + lag-wise Granger causality | | get_pair_trade_signal(x, y, entry?, exit_thresh?, stop?) | strategies.statistical.pair_signal (spread_zscore + cointegration + half-life) | market | cookbook recipe-3 pairs signal: entry |z|≥2 / exit ≤0.5 / stop ≥3, dollar-neutral pair_quantities + VECM ecm_loading advisory | | get_signal_quality(signal, forward_returns, quantile?) | strategies.signal_analysis (rank_ic / icir / long_short_precision) + evaluate.purged_cpcv_splits | market | deterministic validation read for a forecast: Spearman rank IC, ICIR, Qlib long-short precision (top-quantile sign hit rate) + combinatorial-CPCV path count (audit-fixed 2026-09-06) | | get_bsm_option_quote(spot, strike, t_years, vol, option_type?, r?, q?) | strategies.options_math.bsm_equity_surface | market | direct Black-Scholes-Merton spot-space price + Greeks (incl. charm with dividend term) when the chain does not quote the asked strike/expiry — advisory model quote | | get_constituent_cap_weights(weights, cap?, ceiling?) | strategies.capital_income.cap_and_redistribute | fundamentals | exact-ceiling constituent capping (excess pro-rata to sub-soft-cap names; flags degenerate fallback when the ceiling is not enforceable) | | get_ts_momentum_weights(closes_by_name, horizon?, target_vol?, max_leverage?) | strategies.momentum.ts_momentum_weights | market | MOP-style time-series momentum weights: sign(12m log return) / EWMA vol, target-vol normalized, gross-leverage capped | | get_event_pnl_response(spot, delta, gamma, vega, theta, dS_pct, dSigma?) | strategies.options_math.greek_pnl_response | market | cookbook recipe-5 scenario P&L: Δ·dS + ½Γ·dS² + ν·dσ + Θ·dt per option unit | | get_merton_distance(equity, debt, equity_vol, r?, t?) | strategies.credit_spread.merton_distance_to_default | market / risk debators | structural distance-to-default (equity-as-a-call fixed-point; DtD = d2 + risk-neutral PD) | | get_book_depth_read(bid, ask, bid_size, ask_size) | strategies.market_session.book_depth_read | market | microprice (bid·ask_sz + ask·bid_sz)/(bid_sz+ask_sz) + order-book imbalance + thin-side verdict | | get_vif_read(ticker, factors, window?) | strategies.factor_expressions primitives (rsi/mom/bias/zscore/std/vol/range) + statistical.variance_inflation_factor | market | per-column VIF (collinearity check; > 5 = HIGH) | | get_vol_cones(ticker) | strategies.rotation.vol_cones | risk debators | multi-horizon realized-vol percentiles (5/10/21/63/126d) | | get_trade_excursions(trades) | strategies.journal.trade_excursions | declared | MAE / MFE / profit-factor / max intra-trade drawdown (exit quality) | | get_alpha_scoring(direction, predicted_magnitude?, period_days?, actual_return?, confidence?) | strategies.alpha_eval.alpha_score | fundamentals | direction + magnitude-scored alpha ("said +12%, realized +2%") |

6.5b Decision-agent wiring (risk debators / Trader / PM / researchers)

  • The 3 risk debators (aggressive/conservative/neutral) now run an in-node risk-tool loop (agents/utils/risk_tool_loop.py): the plain LLM is bound to a 23-tool risk set (gate/tail/liquidity/vol/tranche/sizing/exits/credit/ pre-trade/ledger), capped at MAX_TOOL_ROUNDS (8), and degrades to a plain invocation when the provider cannot bind tools.
  • The Trader runs a 12-tool verification pass after its structured proposal (sizing / exit / tranche / expectancy / plan-card tools).
  • Calls a provider writes as TEXT are executed, not echoed. A relay can answer a tool-bound turn with its own tool-call markup in content and an empty tool_calls list; run_tool_loop parses that markup (agents/utils/tool_call_markup.py::parse_text_tool_calls — both wrapper widths, and the spelling that lost its tool_ prefix) and dispatches the calls through the same ToolExecutor, then re-asks for prose without tools. Markup never reaches a caller as prose: the loop returns the residual prose or an explicit unavailable: notice, and trader._verification_is_stub refuses to append a markup reply under the "Computed verification" heading. report_verifier uses the same vocabulary to flag an already-leaked block in a finished report.
  • The Research Manager and Bull/Bear researchers receive the computed decision context (regime gate, plan card, risk snapshot, factsheet).
  • get_risk_gate exposes the trade-level governor surface: book_total_pct, daily_loss_pct (daily-loss budget), hwm_drawdown_pct (soft/hard high-water-mark tiers), sector_pct (sector cap), capital_at_risk_pct / risk_cap_pct (tranche capital-at-risk), liquidity_verdict, halted. Not the realized book drawdown: that is a measured state input owned by strategies/book_context.py::measured_book_drawdown (the same resolver the governor's gate and the report's Book drawdown line use), and get_composed_risk_gate is the tool that applies it. A drawdown_pct passed to get_risk_gate is echoed as a labelled hypothetical only, so a model can no longer produce a REJECT the composed gate never issued.

Every tool follows the no-fabrication contract: exact computed numbers or an explicit "unavailable" message (both recorded in the agent's tool history for auditability), never an invented value.

6.6 Canonical line items & prior periods

The value screens read a canonical line-item dict (the vendor-output -> canonical layer in tradingagents.dataflows.statement_parsing, re-exported by scripts/value_screener for the backend CLI). For moomoo markdown the parser emits {"current": .., "prior": ..} dicts for keys present in two consecutive periods (statements list newest-first; tables are sorted by period year). The Beneish M-Score and Piotroski time-components use current/prior via quantitative_scores' _num()/_prv(); every other read unwraps with statement_parsing._latest(). moomoo --prefixed sub-item / contra lines (-Accounts Receivable, -Accumulated Depreciation) are skipped so the aggregate value wins. Because the M-Score needs both periods, its M column now computes on moomoo data (previously always n/a), and the latest-value bug (which silently kept the OLDEST period) is fixed.

7. Persistence & recovery

  • Memory log (agents/utils/memory.py): append-only markdown; pending entries resolved at the next same-ticker run with realised return/alpha + reflection; track-record stats injected into the PM context; mtime-cached parse.
  • Checkpoints: per-ticker SQLite under ~/.tradingagents/cache/checkpoints/, --checkpoint opt-in, clear_checkpoint/clear_all_checkpoints.
  • Vendor cache: disk TTL (6h) under data_cache_dir/vendor_cache/.

8. Reporting

reporting.py::write_report_tree(state, ticker, path) writes the per-section tree plus complete_report.md with H1 report -> H2 team -> H3 role -> H4+ agent content (agent headings demoted 3 levels) and an auto Table of Contents. When the risk governor ran, the Risk Gate (computed) block also renders the Analyzed-name CVaR and (with a configured risk basket) the Portfolio (book) CVaR — this fed the gate lines from risk_context; with the tranche fold on, it additionally shows Tranche peak-deployed (cap-ok) and Tranche capital-at-risk from tranche_context. scripts/rebuild_complete_report.py re-renders folders without a re-run and preserves Risk Gate (computed) blocks.

reporting.py::write_research_decision(state, ticker, path) writes research_decision.json next to the run card — the only input contract of the execution layer, which reads it as a versioned envelope. It declares schema_version 1.1.0 with an offset-aware expires_at (the close of the session following effective_date, 20:00 ET), a producer block whose run_id is the report directory name (stable across a re-emit, so the executor's inbox key stays stable), a UUIDv4 idempotency_key, and two self-excluding hashes (artifact_sha256 over the body, decision_hash over the body minus itself). It sets no executor-owned field: the advisory label is binding_constraint, never binding_gate/trade_permission (the executor's gate owns those), and risk_context is an allow-listed advisory passthrough the executor records but never reads as data. opportunity_score stays null: this repo has no deterministic producer for it, and the schema prefers an absent score to a fabricated one. position.entry_price is §100's final entry price — the §103 block's final_entry_price, falling back to the G1 contract's stop anchor when no final was measurable, and null when neither was. position.take_profit is the contract's measured target (the contract's one-line render carries it precisely so this field is recoverable rather than always null). The whole §103 ENTRY/EXIT object travels verbatim under entry_exit; it is advisory, and the executor's parser reads the four position members it knows and ignores the rest. The rules live in one module, tradingagents/execution_contract.py, shared by the emitter and the report verifier (verify_flags.json gains an envelope block; legacy 1.0.0 trees are exempt). Contract note and acceptance tests: docs/execution_v1_emitter_plan.md. The verification payload is machine-readable as well as readable. Alongside claims and overall (now PASS | FLAG | NUMERIC_ONLY | UNKNOWN), each stem carries basis: the typed triples the deterministic layer extracted, as {metric, value, basis, source}. basis is the period token the report prints beside the value (ttm:ttm, date:2026-06-30, period:annualized) or, when the report states none, the metric's unit class (level, percent, multiple, ratio) — a bare ratio is deliberately unbounded, because a ratio above 1 is ordinary here (P/E 135.94). source is evidence when the value resolves to a tool leaf within the metric's tolerance, else report. NUMERIC_ONLY means the LLM half could not produce a verdict but the figures were extracted, checked and found consistent; UNKNOWN is reserved for "nothing ran". The registry is what makes two runs of one ticker comparable without diffing prose — the same (AMZN, 2026-09-14) calls resolved FY-annual flows at 22:5xZ and TTM quarters at 19:08Z, which no sentence records.

Every number in these blocks states the period it was built from. get_ratios leads with - basis: flows TTM (4 quarters ending …); balance sheet … (the flows come from statement_parsing.trailing_twelve_months(), which reports a window only with full quarter coverage; compute_ratios labels the fallback price / reported EPS when it has to use it), get_balance_sheet_health prints the four canonical rows it divided, get_dupont_read(ticker, curr_date) derives all three legs from ONE payload and states its basis (the raw-number form labels the legs caller-supplied unless period= is given), and each Finnhub metric is tagged with its basis (roaTTM (TTM, Finnhub)) with an advisory note when the payload's own margin x turnover contradicts its ROA. write_report_tree also runs the deterministic identity checks at write time and records them in run_card.json as analyst_consistency ({stem: [{status, claim}]}) - report_verifier._valuation_identity_checks, i.e. the valuation quartet (DuPont, P/E basis, EV/net-cash, R-multiple) plus the net-debt sign, current-ratio, ROA, fiscal-quarter-label and date-count identities (the last recomputes a stated N days out from its own ISO date and the run date read off the report directory, and stays silent without an anchor), so a tree carries its own consistency record instead of depending on the opt-in scripts/report_verify.py pass. Advisory: it never blocks or edits a report.

9. Entry points

Command Purpose
python *.py ETF engine (docs/design_etf_fundamental_valuation.md) get_etf_valuation() weighted constituent P/E/fwd-P/E/yields/percentile/vs-SPY-XLK; get_etf_decline_driver() MARKET/SECTOR/ETF_SPECIFIC/CONSTITUENT_DRIVEN/UNKNOWN; get_etf_relative_strength() both legs vs SPY/QQQ/XLK; get_etf_risk() beta/capture/vol%-percentile/ATR%/maxDD; get_etf_mechanics() NAV premium/discount + distributions
classify_security(ticker) security-type gate: ETF (quote_type / universe / fund-issuer name) vs operating_company vs UNKNOWN (no behavior change)
TRADINGAGENTS_ENABLE_ETF_ENGINE default-off gate: fundamentals analyst swaps company statement tools for the ETF toolset when the security classifies as a fund
tradingagents interactive CLI (typer/rich), full flow
python batch.py --symbols ... headless concurrent; --vendor, --workers, --analysts, --depth, --date
python pipeline.py --universe top-losers --top 5 screener + composite rank + batch (B2)
python scripts/value_screener.py ... value screens / scans / composite
python scripts/exit_engine.py --tickers … --entry-bars N [--variants] the five-mechanism exit decision per name, with the per-rule census and the four-variant A/B (report → screener/)
python scripts/action_report.py ... conditional action report (basket vs report verdicts vs live market)
python scripts/positions_to_basket.py ... combine broker position CSVs -> TRADINGAGENTS_RISK_BASKET_* weights (cash in the denominator; --apply rewrites .env)
python scripts/rebuild_complete_report.py reports/<dir> re-render TOC reports
python scripts/verify_sweep.py ... post-verifier adjudication workbench: condense a report tree's verify_flags.json into N confirmed (MISQUOTED/contradicted/internal-conflict) vs M suspect (unsupported) needing review; --json --confirm-only
python scripts/rule_eval.py --ticker SKHY per-rule forward-return table (fwd 1/5/10/20d, hit, MAE/MFE, Sharpe, PF; n>=30 INSUFFICIENT guard); advisory
python scripts/jev_decide.py [--tree <dir>] [--verdict] judge a document with the TypeSafe decisions model (typesafe/jev-1.13) via POST /api/alpha/decisions — not a chat model (its model / endpoint / per-call timeout default from TRADINGAGENTS_JEV_MODEL / TRADINGAGENTS_JEV_ENDPOINT / TRADINGAGENTS_JEV_TIMEOUT in .env, falling back to the literals); --verdict = the analyst reports only, position language neutralised, buy/hold/sell battery; also --state-file / --state / --stems / --all / --neutralize / --rating / --questions; exit 1 on any failed call
get_composite_sizing(confidence, stop_dist_pct, ...) composite recommended position: min(quarter-Kelly, risk/stop, cap) x vol x liquidity x uncertainty, clamped 0 by blocking portfolio action; strategies/size.composite_position_size
python scripts/smoke_structured_output.py smoke structured output
python main.py minimal Python API demo

Entry points: detailed flags

  • batch.py: --symbols (required) --date --workers (1-4 default=capped, via batch.effective_workers; TRADINGAGENTS_MAX_WORKERS raises the cap) --depth (shallow|medium|deep) --analysts (market|social|news|fundamentals) --vendor (default|moomoo|yfinance|eodhd|tiingo) --verify (advisory LLM report-verification pass post-run -> verify_flags.json in each report tree).
  • pipeline.py: --universe (tickers|top-losers|heat-proxy|top-movers-massive) --file --top --limit --market --movers-count --min-mcap --price-min --pe-max --workers (capped via batch.effective_workers) --analysts --depth --vendor. -u/--universe (eodhd-us default | tickers | top-losers | heat-proxy | eodhd-losers | moomoo-screen) --market -n/--movers-count --min-mcap --price-min --pe-max --min-avg-vol --min-atr-pct --max-mcap --min-eps-yoy --min-rev-yoy --min-roe --max-chg5d --max-rsi --max-debt-assets --dip-days --pb-min --pb-max --exchanges (NYSE,NASDAQ default, '' = off) --sector-rank --revision --inst-accum --intraday --enrich-sector --enrich-rev --enrich-inst --scan (value|trend-pullback|breakout|momentum|momentum-score|swing|vcp|value-dip|all) --value-dip-loose --knife-z --out-dir --rank --enable-float --journal --alloc.
  • action_report.py: --basket (SYM=W,SYM=W override; default config risk_basket_weights) --reports-dir (default reports/) --date --llm (judge UNKNOWN conditions) --json --dry-run --out-dir (default action_reports/, keep-only-newest).
  • positions_to_basket.py: --positions DIR (default repo positions/) + explicit CSV files; --apply (backup .env.bak, rewrite the two basket lines); --min-value / --exclude SYM (repeatable); --write-book-json (dollar book to positions/book_value.json, gitignored); --json.

10. Docs index

  • docs/AGENT_ONBOARDING.md - environment runbook (interpreter, quirks, layout)
  • docs/howto_end_to_end.md - screener -> pipeline -> reports walkthrough
  • docs/EasyManual.md - the same project explained for a teenager (no finance degree needed): web app first, then terminal, reading reports, glossary
  • docs/developer/ - full developer map (topology, graph/workflow, dataflow, strategies, agents/tools, entrypoints, persistence, dev guide, tests layout, Massive)
  • docs/massive_integration.md - the Massive.com add-on plan + entitlement map
  • docs/pre_market_review.md - design + implemented (choice (a)): a pre-market overnight reviewer that CONFIRMs / REVISEs / REJECTs a prior close-time decision against measured deltas (gap, catalyst window, re-anchored tranche/contract, governor) — strategies/pre_market.py (deterministic arbiter + paper-book ledger) + scripts/pre_market_review.py (standalone pre-open; real pre-market quote, planned-level re-anchor), scripts/nightly_review.py (batch-summary driver; --mode recent reviews each symbol's newest report folder instead, covering CLI/API runs that never write a batch summary), scripts/decision_history.py (decision series), and the opt-in batch.py same-night step (enable_pre_market_review).
  • docs/developer/11-agent-decision-tools.md - the six decision-grounding tools implemented for the analyst LLMs
  • docs/developer/12-data-providers.md - the 13 data providers/sources and per-category vendor chains
  • Strategies/* - strategy plans and specs (index: Strategies/index.md)
  • docs/design_multi_agent_debate.md - research + design (no code) for the bull/bear research debate: two-layer judiciary (deterministic L1 gates before a blind dimensioned L2 LLM judge), heterogeneous per-role models + capability matrix, FSM orchestrator + canonical wire schemas (DebaterTurnPayload / L1DeterministicResult / L2JudgeDimensionedRubric) from Strategies/Multi_Agents_Debate.md, phased P0-P6 all enable_*-flagged OFF.
  • docs/design_quantlib_lean_enhancements.md - research-to-design: deep study of QuantLib (pricing/measurement rigor) + Lean (framework/operational rigor) mapped to concrete deterministic strategies/* enhancements, phased (highest ROI first) with quick-wins verdict.
  • docs/design_nautilus_trader_enhancements.md - research-to-design: deep study of NautilusTrader (execution/evaluation rigor) mapped to a backtest harness, consistent risk sizing, statistics and config validation - implemented (see CHANGELOG).
  • docs/design_webull_data_provider.md - provider study (2026-09-16, design only): Webull OpenAPI mapped onto data_vendors - a US-only bars + fundamentals vendor whose statements are natively basis-tagged (fiscal_year/fiscal_period/end_date/ publish_date), the entitlement and 2FA-token constraints, and a phased plan.
  • docs/design_longbridge_data_provider.md - provider study (2026-10-08, design only): the Longbridge (Longport) OpenAPI read against the chains in force - US + HK + CN, a near-total US overlap, a unique HK/CN surface with no consumer, a Non-Commercial licence, and the probed finding that the supplied app key + secret cannot authenticate without LONGBRIDGE_ACCESS_TOKEN (401001 token empty).
  • docs/design_external_research_leads.md - lead-source register (2026-10-08): a public "awesome-quant"-style list checked against this repo - the two process patterns adopted (the eligibility gate and the anti-doc-sprawl rule, now docs/AGENT_ONBOARDING.md section 0 rules 11-12, plus scripts/validate_registers.py and the SSRF-safe scripts/url_probe.py behind a weekly workflow), and every lead's disposition: which are already implemented here, which produced a change (rule_eval's baseline lift), and which are blocked on an owner decision.
  • docs/design_moomoo_unused_api_surface.md - provider study (2026-09-18, design only): the unused moomoo OpenAPI surface enumerated - 103 unused data methods of OpenQuoteContext's 166 public callables, every one read and ~35 live-probed against OpenD. get_market_snapshot returns 142 columns for a whole batch in one call (valuation block, pre/after/overnight sessions, short interest, ETF NAV/premium); batch IV rank/percentile/HV plus a 251-row IV-HV series; a valuation percentile and peer distribution; an independent fair value. Records the all-or-nothing batch constraint, the rule-15 overlap to settle before adopting the valuation fields, and a P0-P5 plan behind the new enable_moomoo_snapshot.
  • docs/design_finnhub_yfinance_unused_surface.md - provider study (2026-09-18, design only): the unused finnhub and yfinance surfaces - 108 of finnhub.Client's 117 methods and ~31 yfinance.Ticker properties, both diffed against the tree and live-probed (61 + 32 calls). The headline: Ticker.eps_trend supplies the five-level estimate series that analyst_revisions.estimate_change_index documents itself as never having, and which no production caller ever supplies. Records the finnhub free-tier wall (12 of 61 work, 48 return 403, all listed), the two rule-15 overlaps that block the tempting adoptions, and a P0-P5 plan behind the new enable_analyst_estimates.
  • docs/design_eodhd_unused_surface.md - provider study (2026-09-19, design only): the unused EODHD surface, probed live against the repo's own key across five rounds (~100 URLs). The supplied 68-endpoint catalog is materially wrong about reachability: 8 paths are already called by the tree, 9 are reachable and unused, 20 are plan-gated (403) and ~9 are not found at any probed path. Records the /ust/* Treasury family found by guessing a path the catalog did not list, the 422-vs-403 probe that separated /calendar/dividends (authorized but plan-gated) from a wrong path, and the rule-15 analysis that rejects six of the nine reachable endpoints as second producers. A P0-P3 plan behind the new enable_eodhd_rates; P0 is /ust/real-yield-rates, the one endpoint that changes what an existing number means. P0 is BUILT (2026-09-19): eodhd.real_yield_points_eodhd / ::get_real_yield_rates_eodhd (TIPS real yields) and ::inflation_expectation_eodhd (the single producer of nominal − real, both legs' dates and the basis printed), with federal_reserve.treasury_curve_points refactored to a structured read the existing renderer now draws from. P1 and P2 are BUILT (2026-09-27): eodhd.get_bill_auction_rates_eodhd (Treasury bill auction detail — discount/coupon, maturity_date, cusip) and eodhd.map_identifiers_eodhd (FIGI / LEI / CUSIP; the vendor's CIK is returned only as a labelled cik_cross_check, since sec_edgar._cik_for owns that join). P3 (/eod-bulk-last-day) and P4 (/news-word-weights) are BUILT (2026-10-01) at the owner's instruction: the first as a batched transport wired as a session + coverage cross-check (bulk_last_day_index_eodhd), never a price contributor (rule 15); the second as a news-term reference read (news_word_weights_eodhd, ~40 s/call, feeds no score — /sentiments owns the news-derived number). The per-symbol price-chain swap is still not built: no measurement shows that path is the bottleneck. All behind the one enable_eodhd_rates gate, read by scripts/value_screener.py --rates. Gate off by default. P5 is BUILT (2026-10-06): the refusal typing is corrected (401 names the token, 403 the entitlement — a 403 is a PLAN gate on a valid key, established live on eight datasets), a 402 is raised on the first attempt instead of being retried twice as a rate limit, Retry-After is honoured on a 429 and the 5xx path backs off; eodhd.resolve_symbol_eodhd reads the one newly-reachable endpoint, /search, and selects a listing by rule, never row 0 (isPrimary alone selects nothing — every TSM row reports False — and the vendor's spelling is never re-edited: BRK.B → BRK-B.US); and the new dataflows/eodhd_quota.py reads GET /user — the one endpoint EODHD does not count against the daily limit — to report the plan's allowance and the run's own weighted spend (5×/10× for the CBOE, market-cap and insider feeds).
  • docs/design_openbb_enhancements.md - research-to-design: deep study of OpenBB (typed provider envelopes, self-describing REST/CLI/MCP surface, quantitative/econometrics/technical toolkit, Tauri desktop + SPA product surfaces) mapped to TradingAgents dataflows/ + strategies/ and trading_web (watchlist/grid/charts/credentials/presets/MCP), phased with a quick-wins verdict.