{"openapi":"3.1.0","info":{"title":"Spark","description":"Agentic Trade Intelligence — AI-powered volatility analysis and options strategy generation","version":"1.0.0"},"paths":{"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/ready":{"get":{"summary":"Ready","description":"Readiness from real Schwab signals (S-12, 2026-09-24).\n\nChecks the token file the pipeline reads (present, parseable, not past its\nown expiry) and the outcome of the last Schwab call this process made. It\nused to report ``HAS_SCHWAB`` (Schwab env vars set + optional client built),\nwhich stayed \"connected\" through dead tokens. 200 only when ``status`` is\n``ok``; 503 with the reasons when ``degraded`` or ``unavailable``.","operationId":"ready_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/health/deep":{"get":{"summary":"Health Deep","description":"Deep stage health: the input of the IgniteEdge bot's stage-health agent (S-E5).\n\nToken file expiry + age, last Schwab OK / 401 / failure, per-model reachability\n(served vs configured model id; probed at most once per 60 s), build sha,\nconfig, recent phase p50s. Read-only, JSON, always 200: read ``status``\n(``ok`` / ``degraded`` / ``unavailable``) and ``reasons``. Sync on purpose:\nFastAPI runs it in its threadpool, so a model probe never blocks the loop.","operationId":"health_deep_v1_health_deep_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/info":{"get":{"summary":"Info","operationId":"info_v1_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/handshake":{"get":{"summary":"Handshake","operationId":"handshake_v1_handshake_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandshakeResponse"}}}}}}},"/v1/market-insights":{"post":{"summary":"Market Insights","operationId":"market_insights_v1_market_insights_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketInsightRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradeIdeasResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portfolio-recommendations":{"post":{"summary":"Portfolio Recommendations","operationId":"portfolio_recommendations_v1_portfolio_recommendations_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortfolioRecommendationRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradeIdeasResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/schwab/status":{"get":{"summary":"Schwab Status","description":"Read-only token health (S-12, 2026-09-24): file mtime, own expiry, last call.","operationId":"schwab_status_v1_schwab_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}}}}},"/v1/schwab/refresh":{"post":{"summary":"Schwab Refresh","description":"Returns 410 Gone — refresh is no longer owned by spark-api.\n\nThe single-writer Schwab token chain on this deployment is owned by\nbot-claude-bridge on bot-ctrl. spark-api is a read-only consumer and\nmust never call ``grant_type=refresh_token`` (would invalidate the\nbridge's refresh_token on its next call and break the chain).","operationId":"schwab_refresh_v1_schwab_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/universes":{"get":{"summary":"Universes","description":"Return available symbol macros and the tickers each resolves to.\n\nMacros may be passed in the ``symbols`` field of a market-insights or\nportfolio-recommendations request and will be expanded server-side.","operationId":"universes_v1_universes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Universes V1 Universes Get"}}}}}}},"/v1/binary/pricing":{"get":{"summary":"Binary Pricing","description":"Analytical pricing ladders for XSPBW binaries and QSB $1-wide verticals.\n\nBinary prices are $0-$100 cash-or-nothing values on the XSPBX\nsettlement index (SPX close / 10). Greeks are closed-form:\ndelta/gamma per $1 XSPBX, theta per trading day, vega per vol point,\nrho per 1% rate.","operationId":"binary_pricing_v1_binary_pricing_get","parameters":[{"name":"expiry","in":"query","required":false,"schema":{"type":"string","pattern":"^(0dte|1dte)$","description":"Contract expiry: 0dte or 1dte","default":"0dte","title":"Expiry"},"description":"Contract expiry: 0dte or 1dte"},{"name":"iv","in":"query","required":false,"schema":{"anyOf":[{"type":"number","exclusiveMaximum":10,"exclusiveMinimum":0},{"type":"null"}],"description":"Override IV (annualized, e.g. 0.18); default VIX/100","title":"Iv"},"description":"Override IV (annualized, e.g. 0.18); default VIX/100"},{"name":"r","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":0.25,"minimum":-0.05},{"type":"null"}],"description":"Override risk-free rate","title":"R"},"description":"Override risk-free rate"},{"name":"q","in":"query","required":false,"schema":{"type":"number","maximum":0.1,"minimum":0,"description":"Dividend yield","default":0.012,"title":"Q"},"description":"Dividend yield"},{"name":"ladder_span","in":"query","required":false,"schema":{"type":"integer","maximum":15,"minimum":1,"description":"Strikes each side of ATM","default":5,"title":"Ladder Span"},"description":"Strikes each side of ATM"},{"name":"skew_slope","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":0.1,"minimum":-0.1},{"type":"null"}],"description":"d(sigma)/dK per $1 XSPBX strike for skew-adjusted call prices (SPX skew is negative)","title":"Skew Slope"},"description":"d(sigma)/dK per $1 XSPBX strike for skew-adjusted call prices (SPX skew is negative)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Binary Pricing V1 Binary Pricing Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/binary/implied-vol":{"get":{"summary":"Binary Implied Vol","description":"Branch-aware implied volatility for an XSPBW binary price.\n\nBinary prices are not globally monotonic in vol: when the forward is\nbelow the strike there is a critical vol where the call price peaks\n(put troughs), so a single price can map to TWO implied vols. The\nresponse returns every root plus regime metadata.","operationId":"binary_implied_vol_v1_binary_implied_vol_get","parameters":[{"name":"price","in":"query","required":true,"schema":{"type":"number","exclusiveMaximum":100,"exclusiveMinimum":0,"description":"Observed binary price in dollars (0-100)","title":"Price"},"description":"Observed binary price in dollars (0-100)"},{"name":"strike","in":"query","required":true,"schema":{"type":"number","exclusiveMinimum":0,"description":"XSPBW strike","title":"Strike"},"description":"XSPBW strike"},{"name":"option_type","in":"query","required":false,"schema":{"type":"string","pattern":"^(call|put)$","default":"call","title":"Option Type"}},{"name":"expiry","in":"query","required":false,"schema":{"type":"string","pattern":"^(0dte|1dte)$","default":"0dte","title":"Expiry"}},{"name":"spot","in":"query","required":false,"schema":{"anyOf":[{"type":"number","exclusiveMinimum":0},{"type":"null"}],"description":"Override XSPBX spot (default: live quote)","title":"Spot"},"description":"Override XSPBX spot (default: live quote)"},{"name":"r","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":0.25,"minimum":-0.05},{"type":"null"}],"title":"R"}},{"name":"q","in":"query","required":false,"schema":{"type":"number","maximum":0.1,"minimum":0,"default":0.012,"title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Binary Implied Vol V1 Binary Implied Vol Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/robots.txt":{"get":{"summary":"Robots Txt","operationId":"robots_txt_robots_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/llms.txt":{"get":{"summary":"Llms Txt","operationId":"llms_txt_llms_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}}},"components":{"schemas":{"EconomicsInputsOut":{"properties":{"spot":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Spot"},"t_years":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"T Years","description":"Years to the 16:00 ET expiry at resolution time"},"risk_free_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Risk Free Rate"},"iv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Iv","description":"Mean of the legs' IVs (the IV behind expected_vrp)"},"rv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rv","description":"20-day realized vol (decimal)"},"multiplier":{"type":"integer","title":"Multiplier","default":100},"iv_basis":{"type":"string","title":"Iv Basis","default":""},"rv_basis":{"type":"string","title":"Rv Basis","default":""}},"type":"object","title":"EconomicsInputsOut"},"ExpectedGreeks":{"properties":{"delta":{"type":"string","title":"Delta","default":""},"gamma":{"type":"string","title":"Gamma","default":""},"vega":{"type":"string","title":"Vega","default":""},"theta":{"type":"string","title":"Theta","default":""}},"type":"object","title":"ExpectedGreeks","description":"Per-idea aggregated Greeks for the entire multi-leg structure.\n\nEach leg's per-share greek is multiplied by ``side_sign * quantity *\n100`` (option contract multiplier) and summed across legs. Decimal\nstrings preserve precision. The bot adds these into a portfolio-wide\nGlobal Risk total without recomputing.\n\nReturned as ``None`` on the response if no leg had usable greek data\nin the chain — consumers should treat that as \"compute your own\"."},"GreekIntent":{"properties":{"delta_target":{"type":"string","title":"Delta Target","default":""},"gamma_exposure":{"type":"string","title":"Gamma Exposure","default":""},"vega_exposure":{"type":"string","title":"Vega Exposure","default":""},"theta_target":{"type":"string","title":"Theta Target","default":""}},"type":"object","title":"GreekIntent","description":"Categorical Greek exposure intent from the LLM (e.g. 'bullish', 'short').\n\nDistinct from ``ExpectedGreeks`` (numeric per-trade aggregate computed\nfrom the chain). ``GreekIntent`` is the LLM's stated thesis;\n``ExpectedGreeks`` is what the chain actually says the trade exposes."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HandshakeResponse":{"properties":{"service":{"type":"string","title":"Service","default":"spark"},"version":{"type":"string","title":"Version","default":"1.0.0"},"capabilities":{"items":{"type":"string"},"type":"array","title":"Capabilities"},"pricing":{"$ref":"#/components/schemas/PricingInfo"},"demo_trades":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Demo Trades"},"sample_context":{"additionalProperties":true,"type":"object","title":"Sample Context"}},"type":"object","title":"HandshakeResponse","description":"Response for the free handshake endpoint."},"IdeaEconomicsOut":{"properties":{"idea_index":{"type":"integer","title":"Idea Index"},"symbol":{"type":"string","title":"Symbol","default":""},"title":{"type":"string","title":"Title","default":""},"inputs":{"$ref":"#/components/schemas/EconomicsInputsOut"},"legs":{"items":{"$ref":"#/components/schemas/LegEconomicsOut"},"type":"array","title":"Legs"},"net_premium":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Net Premium","description":"Dollars for the structure; > 0 debit, < 0 credit"},"premium_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Premium Type","description":"'debit' | 'credit' | 'even'"},"max_profit":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Profit"},"max_loss":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Loss","description":"Positive dollars; null unless defined risk"},"defined_risk":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Defined Risk"},"breakevens":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Breakevens"},"pop_iv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pop Iv","description":"P(P&L at expiry > 0), lognormal at inputs.iv"},"pop_rv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pop Rv","description":"Same at inputs.rv"},"ev_iv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Iv","description":"BS value at inputs.iv minus net_premium, dollars"},"ev_rv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Rv","description":"BS value at inputs.rv minus net_premium, dollars"},"reward_risk":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Reward Risk"},"structure_spread_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Structure Spread Pct"},"min_open_interest":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Open Interest"},"null_reasons":{"additionalProperties":{"type":"string"},"type":"object","title":"Null Reasons","description":"Why each null figure is null"}},"type":"object","required":["idea_index","inputs"],"title":"IdeaEconomicsOut","description":"Deterministic economics of ``trade_ideas[idea_index]`` from its resolved legs (S-E1)."},"IdeaScoreOut":{"properties":{"idea_index":{"type":"integer","title":"Idea Index"},"symbol":{"type":"string","title":"Symbol","default":""},"title":{"type":"string","title":"Title","default":""},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score","description":"In [0, 1]; null only if scoring failed (see errors[])"},"rank":{"type":"integer","title":"Rank","description":"1 = best, in the final order; equal to idea_index + 1"},"pre_rank_index":{"type":"integer","title":"Pre Rank Index","description":"Position in the pre-score order (diversity + DIVE-04 gate), the tie-break"},"degrades_held_book":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Degrades Held Book","description":"TRAD-04: adding this idea's underlying lowers the held book's diversification_ratio. Primary sort key (non-degrading ideas rank first, then score), so a lower score can rank above a higher one. null when TRAD-04 did not run (no held book or gate NO-OP)"},"components":{"items":{"$ref":"#/components/schemas/ScoreComponentOut"},"type":"array","title":"Components"}},"type":"object","required":["idea_index","rank","pre_rank_index"],"title":"IdeaScoreOut","description":"Deterministic score of ``trade_ideas[idea_index]`` (S-E2); the list is in rank order."},"LegEconomicsOut":{"properties":{"occ_symbol":{"type":"string","title":"Occ Symbol","default":""},"side":{"type":"string","title":"Side","default":""},"right":{"type":"string","title":"Right","default":""},"strike":{"type":"number","title":"Strike"},"quantity":{"type":"integer","title":"Quantity"},"bid":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bid"},"ask":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ask"},"mid":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mid","description":"(bid+ask)/2 when two-sided, else the leg's limit_price"},"mid_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mid Source","description":"'bid_ask' | 'limit_price' | null"},"spread_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Spread Pct","description":"(ask-bid)/mid x 100, two-sided quotes only"},"open_interest":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Open Interest","description":"The chain's openInterest for the contract"},"iv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Iv","description":"Decimal IV used for the leg's greeks (its iv_source rung)"}},"type":"object","required":["strike","quantity"],"title":"LegEconomicsOut","description":"One leg's quote as the economics used it (S-E1)."},"LegGreeks":{"properties":{"delta":{"type":"string","title":"Delta","default":""},"gamma":{"type":"string","title":"Gamma","default":""},"vega":{"type":"string","title":"Vega","default":""},"theta":{"type":"string","title":"Theta","default":""}},"type":"object","title":"LegGreeks","description":"Per-leg per-share Greeks pulled from the chain entry.\n\nValues are unsigned — they reflect the raw market quote, not the\nposition direction. The bot multiplies by side (BUY=+1 / SELL=-1)\nand ``quantity * 100`` to roll up portfolio exposure.\n\nAll fields are decimal strings to preserve precision. Empty string\nmeans the chain did not surface that greek (Schwab returns ``-999``\nsentinels for unavailable greeks; we drop them)."},"MarketContext":{"properties":{"sabr_params":{"additionalProperties":true,"type":"object","title":"Sabr Params"},"risk_reversal":{"additionalProperties":true,"type":"object","title":"Risk Reversal"},"term_structure":{"type":"string","title":"Term Structure","default":""},"model_used":{"type":"string","title":"Model Used","default":""},"spot_prices":{"additionalProperties":{"type":"string"},"type":"object","title":"Spot Prices","description":"Per-ticker spot price snapshot Spark used when proposing the ideas. Keys are uppercase tickers, values are decimal strings (e.g. {'AAPD': '48.32'}). Lets consumers ground ATM strikes without a second market-data round-trip."},"spot_prices_at":{"type":"string","title":"Spot Prices At","description":"ISO-8601 UTC timestamp ('Z' suffix) of the spot_prices snapshot, within a few minutes of processing_time_ms when spot_prices is non-empty; the EMPTY STRING when no spot was captured (ERROR / NO_IDEAS). Never null: the bot's contract (tradingBots contracts/insights.py, coupling B1) declares it a plain string.","default":""},"ml_regime_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ml Regime Summary","description":"One-line summary of the regime block in the packed ml_regime payload. e.g. 'composite=Bull + Low Vol, GARCH persistence=0.97, vol_regime=Low'. Format: 'composite={regime_name}, GARCH persistence={alpha+beta:.2f}, vol_regime={regime_name}'. None when regime detection skipped or failed."},"macro_stress_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Macro Stress Level","description":"FRED-aggregated macro stress label. One of 'low_stress' / 'normal' / 'elevated' / 'crisis' (verbatim from library/risk/extreme_crash._compute_macro_regime). None when FRED unavailable or all indicators non-finite."},"diversification_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Diversification Ratio","description":"Portfolio diversification ratio (standalone VaR sum / correlation-adjusted VaR). >1 = diversified. Populated only on /v1/portfolio-recommendations when at least 2 held symbols have >=60 close candles."}},"type":"object","title":"MarketContext","description":"Market context returned alongside trade ideas."},"MarketInsightRequest":{"properties":{"agent_id":{"type":"string","title":"Agent Id","description":"Unique identifier for the calling agent"},"symbols":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"Symbols","description":"Ticker symbols, or macro sentinels prefixed with '@' (e.g. '@ssetf_underlying', '@index_etfs', '@ssetf_all'). Macros expand server-side; see GET /v1/universes for available macros."},"num_ideas":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Num Ideas","description":"Number of trade ideas to generate","default":3},"preferred_strategies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Preferred Strategies","description":"Preferred strategy types (e.g. credit_spread, calendar_spread)"},"market_context":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Market Context","description":"Agent's market context or thesis"},"market_reference_symbol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Reference Symbol","description":"Optional benchmark ticker for risk-side computations: tail-beta vs. this benchmark in extreme-crash analysis, and a zero-weight reference column in the correlation panel. When None (default), no benchmark is fetched and those risk blocks run benchmark-free. Per round-X 'no-implicit-benchmark' rule, no symbol is silently added on the agent's behalf. Whitespace is stripped; empty/whitespace-only values normalize to None."}},"type":"object","required":["agent_id","symbols"],"title":"MarketInsightRequest","description":"Request for market insights — trade ideas for specified symbols."},"OptionLegStructured":{"properties":{"underlying":{"type":"string","title":"Underlying","description":"Traded ticker the OCC chain belongs to"},"right":{"type":"string","title":"Right","description":"'CALL' or 'PUT'"},"side":{"type":"string","title":"Side","description":"'BUY' or 'SELL' (derived from instruction)"},"instruction":{"type":"string","title":"Instruction","description":"Original Schwab-style instruction (SELL_TO_OPEN, BUY_TO_OPEN, SELL_TO_CLOSE, BUY_TO_CLOSE).","default":""},"expiry":{"type":"string","title":"Expiry","description":"Expiration in YYYY-MM-DD format"},"strike":{"type":"string","title":"Strike","description":"Strike price as decimal string, e.g. '48.50'"},"quantity":{"type":"integer","minimum":1.0,"title":"Quantity","description":"Number of contracts"},"occ_symbol":{"type":"string","title":"Occ Symbol","description":"OCC option symbol (e.g. 'AAPD  260501P00048500'). Round-trips cleanly to the structured fields."},"limit_price":{"type":"string","title":"Limit Price","description":"Recommended LIMIT order price for this leg as a 2-decimal string (e.g. '1.85'). Computed server-side as the mid of the bid-ask spread observed in the chain Spark used for evaluation. Falls back to ``mark`` then ``last`` when bid/ask are unavailable. Empty string when no usable price could be computed — consumers should fall back to their own quote source rather than trade blind.","default":""},"bid":{"type":"string","title":"Bid","description":"Bid quote observed by Spark, decimal string. Informational.","default":""},"ask":{"type":"string","title":"Ask","description":"Ask quote observed by Spark, decimal string. Informational.","default":""},"greeks":{"anyOf":[{"$ref":"#/components/schemas/LegGreeks"},{"type":"null"}],"description":"Per-share unsigned Greeks from the chain entry. ``None`` if the chain did not surface usable greeks for this contract."},"iv_source":{"type":"string","title":"Iv Source","description":"Provenance of the IV used to compute this leg's Greeks. One of: ``'chain'`` (Schwab's per-strike IV), ``'bs_solved_from_mid'`` (Newton-Raphson IV solved from the bid/ask mid), ``'atm_chain'`` (closest-to-spot strike's IV on the same expiry), ``'sabr'`` (Hagan smile evaluator on calibrated SABR params), ``'vix_default'`` (last-resort VIX-derived). Empty string when no IV could be recovered. Lets consumers weight risk by data quality.","default":""}},"type":"object","required":["underlying","right","side","expiry","strike","quantity","occ_symbol"],"title":"OptionLegStructured","description":"Structured option leg with resolved strike, expiry, and OCC symbol.\n\nSurfaces the same data as the OCC string in ``legs[]`` but split into\ntyped fields so consumers do not have to parse OCC formatting (which\nhas provider-specific padding rules). Strike is sent as a string to\npreserve trailing zeros / fractional pennies without floating-point drift."},"PortfolioData":{"properties":{"cash_balance":{"type":"number","minimum":0.0,"title":"Cash Balance"},"positions":{"items":{"$ref":"#/components/schemas/PortfolioPosition"},"type":"array","title":"Positions"}},"type":"object","required":["cash_balance"],"title":"PortfolioData","description":"Agent-provided portfolio data."},"PortfolioPosition":{"properties":{"symbol":{"type":"string","title":"Symbol"},"quantity":{"type":"integer","title":"Quantity"},"average_price":{"type":"number","title":"Average Price"},"asset_type":{"type":"string","title":"Asset Type","description":"EQUITY or OPTION"},"underlying_symbol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Underlying Symbol"},"put_call":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Put Call"},"strike_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Strike Price"},"expiration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration"}},"type":"object","required":["symbol","quantity","average_price","asset_type"],"title":"PortfolioPosition","description":"A single position in the agent's portfolio."},"PortfolioRecommendationRequest":{"properties":{"agent_id":{"type":"string","title":"Agent Id","description":"Unique identifier for the calling agent"},"symbols":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"Symbols","description":"Ticker symbols, or macro sentinels prefixed with '@' (e.g. '@ssetf_underlying', '@index_etfs', '@ssetf_all'). Macros expand server-side."},"portfolio":{"$ref":"#/components/schemas/PortfolioData"},"num_ideas":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Num Ideas","default":3},"preferred_strategies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Preferred Strategies"},"market_context":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Market Context"},"market_reference_symbol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Reference Symbol","description":"Optional benchmark ticker for risk-side computations. See MarketInsightRequest.market_reference_symbol — same semantics + same validation. None (default) skips benchmark fetches."}},"type":"object","required":["agent_id","symbols","portfolio"],"title":"PortfolioRecommendationRequest","description":"Request for portfolio-aware trade recommendations."},"PricingInfo":{"properties":{"market_insights":{"type":"string","title":"Market Insights","default":"$0.05"},"portfolio_recommendations":{"type":"string","title":"Portfolio Recommendations","default":"$0.10"},"network":{"type":"string","title":"Network","default":"eip155:8453"},"currency":{"type":"string","title":"Currency","default":"USDC"}},"type":"object","title":"PricingInfo","description":"Pricing for paid endpoints."},"ScoreComponentOut":{"properties":{"tag":{"type":"string","title":"Tag"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value"},"weight":{"type":"number","title":"Weight"},"contribution":{"type":"number","title":"Contribution"},"note":{"type":"string","title":"Note","default":""}},"type":"object","required":["tag","weight","contribution"],"title":"ScoreComponentOut"},"SpotQuote":{"properties":{"price":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price","description":"Decimal string (same form as context.spot_prices, e.g. '48.32'); null when Schwab returned no usable quote for the symbol (see reason). Never invented."},"quote_ts":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote Ts","description":"ISO-8601 UTC ('Z') time of the quote, taken from the Schwab payload (tradeTime for source last/close, quoteTime for mark/mid), never the server clock; null when the price is null or the payload had no time."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Which quote field priced it: 'last' | 'mark' | 'mid' | 'close'; null without a price."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Null when price and quote_ts are both present; otherwise why not."}},"type":"object","title":"SpotQuote","description":"One symbol's spot price and quote time (``TradeIdeasResponse.spots``, S-E4)."},"StatusResponse":{"properties":{"connected":{"type":"boolean","title":"Connected"},"expired":{"type":"boolean","title":"Expired","default":false},"saved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Saved At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"status":{"type":"string","title":"Status","default":"unavailable"},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons","default":[]},"last_call":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Last Call"}},"type":"object","required":["connected"],"title":"StatusResponse"},"TradeIdea":{"properties":{"title":{"type":"string","title":"Title"},"sentiment":{"type":"string","title":"Sentiment","default":""},"rationale":{"type":"string","title":"Rationale","default":""},"regime_alignment":{"type":"string","title":"Regime Alignment","default":""},"greek_intent":{"anyOf":[{"$ref":"#/components/schemas/GreekIntent"},{"type":"null"}]},"expected_greeks":{"anyOf":[{"$ref":"#/components/schemas/ExpectedGreeks"},{"type":"null"}],"description":"Per-idea numeric Greek exposure aggregated from the chain. Each leg's per-share greek is signed by side and scaled by ``quantity * 100`` (option contract multiplier), then summed. ``None`` when no leg had usable chain Greeks. Distinct from ``greek_intent`` which is the LLM's categorical thesis."},"expected_vrp":{"type":"string","title":"Expected Vrp","description":"Variance Risk Premium: representative implied vol minus realized vol, expressed in vol **points** as a decimal string (e.g. ``'1.20'`` = 1.20 percentage points). Positive ⇒ options are rich vs realized (vol-selling favorable); negative ⇒ options are cheap (vol-buying). IV is the mean of the legs' BS IVs (whatever rung of the IV-recovery chain supplied them); RV is 20-day annualized (``√252``) std-dev of log returns from Schwab daily bars. Empty string when RV could not be computed (no daily-bar history available) — bot should fall back to its own estimate rather than treating zero as a real signal.","default":""},"target_dte":{"type":"string","title":"Target Dte","description":"Calendar days to the expiry every leg was resolved on (Schwab's own DTE for that expiry), e.g. '7'; '0' is a same-day expiry (2026-09-24, S-6: it was '' on every live idea). The LLM's requested band (target_dte_min/max) chooses the expiry; an expiry outside the band is flagged in errors[] ('expiry out of band: ...').","default":""},"legs":{"items":{"type":"string"},"type":"array","title":"Legs"},"legs_structured":{"items":{"$ref":"#/components/schemas/OptionLegStructured"},"type":"array","title":"Legs Structured","description":"Structured echo of legs[] with resolved strike/expiry/right/occ_symbol/limit_price/greeks, always the same length as legs[]: an idea whose legs could not all be resolved against the traded ticker's chain is dropped and counted in errors[] (2026-09-24), never returned with an empty or partial legs_structured."},"symbol":{"type":"string","title":"Symbol","default":""}},"type":"object","required":["title"],"title":"TradeIdea","description":"A single trade idea."},"TradeIdeasResponse":{"properties":{"decision":{"type":"string","title":"Decision","default":"GENERATED"},"trade_ideas":{"items":{"$ref":"#/components/schemas/TradeIdea"},"type":"array","title":"Trade Ideas"},"context":{"$ref":"#/components/schemas/MarketContext"},"processing_time_ms":{"type":"integer","title":"Processing Time Ms","default":0},"errors":{"items":{"type":"string"},"type":"array","title":"Errors","description":"What went wrong, never log-only. On decision=ERROR the first entry is the reason (e.g. a Schwab 401); on GENERATED / NO_IDEAS it lists degraded pipeline phases, dropped ideas and failed Schwab calls."},"spots":{"additionalProperties":{"$ref":"#/components/schemas/SpotQuote"},"type":"object","title":"Spots","description":"S-E4 (2026-09-24): spot price + quote time for EVERY requested symbol (after macro expansion), the market_reference_symbol when given, and SPY and QQQ, whether or not the symbol produced an idea. Keys are upper-case tickers in that order. One batched Schwab quote call per request (quotes the request already fetched are reused). A symbol without a usable quote has price=null and a reason, and errors[] gets a 'spots: ...' line. Empty only when the request failed before market data was available (auth failure, empty symbols). Additive: every other field is unchanged; context.spot_prices keeps its old meaning."},"economics":{"items":{"$ref":"#/components/schemas/IdeaEconomicsOut"},"type":"array","title":"Economics","description":"S-E1 (2026-09-24): one entry per trade_ideas[] item, same order (economics[i].idea_index == i), computed from the resolved legs only: net mid premium, max profit / max loss (defined risk only), breakevens, PoP and EV under the legs' IV and under 20-day realized vol, reward:risk, spread % and open interest per leg and for the structure. Every input is echoed (inputs, legs) so each figure is reproducible; a figure that cannot be computed is null with null_reasons[<field>]. Top-level because the bot validates trade_ideas[] with extra='forbid'. [] when there are no ideas."},"scores":{"items":{"$ref":"#/components/schemas/IdeaScoreOut"},"type":"array","title":"Scores","description":"S-E2 (2026-09-24): one entry per trade_ideas[] item, same order; the ideas were ranked by this score before the num_ideas trim (pre-score order breaks ties). components[] = {tag, value, weight, contribution, note}; weights in engine/score.py WEIGHTS. [] when there are no ideas."}},"type":"object","title":"TradeIdeasResponse","description":"Response for both market-insights and portfolio-recommendations."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}