@ -4,7 +4,7 @@ Main FastAPI application
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI , HTTPException
from fastapi import FastAPI , HTTPException , Request
from fastapi . middleware . cors import CORSMiddleware
from fastapi . responses import RedirectResponse , HTMLResponse
@ -62,350 +62,119 @@ app.add_middleware(
# Include API router
app . include_router ( api_router , prefix = settings . API_PREFIX )
# Root documentation endpoint
# Root documentation endpoint — auto-generated from OpenAPI schema
@app.get ( " / " , response_class = HTMLResponse , include_in_schema = False )
async def root_documentation ( ) :
"""
Display comprehensive API documentation at root path
"""
async def root_documentation ( request : Request ) :
try :
# Simple working version
simple_html = f """
< ! DOCTYPE html >
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1.0 " >
< title > Stock Oracle API Documentation < / title >
< style >
body { {
font - family : - apple - system , BlinkMacSystemFont , ' Segoe UI ' , Roboto , sans - serif ;
line - height : 1.6 ;
max - width : 1000 px ;
margin : 0 auto ;
padding : 20 px ;
color : #333;
} }
h1 { { color : #007acc; border-bottom: 2px solid #007acc; padding-bottom: 10px; }}
h2 { { color : #2c3e50; margin-top: 2em; }}
. nav - links { { margin : 20 px 0 ; } }
. nav - links a { {
display : inline - block ;
margin - right : 15 px ;
padding : 10 px 20 px ;
background : #007acc;
color : white ;
text - decoration : none ;
border - radius : 5 px ;
} }
. nav - links a : hover { { background : #0066aa; }}
code { { background : #f4f4f4; padding: 2px 4px; border-radius: 3px; }}
pre { { background : #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }}
. new - badge { { background : #e74c3c; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.8em; }}
< / style >
< / head >
< body >
< h1 > 🔮 Stock Oracle API Documentation < / h1 >
< p > < strong > Comprehensive Investment Data Analysis API < / strong > < / p >
< div class = " nav-links " >
< a href = " {settings.API_PREFIX} /docs " > 📋 Interactive API Docs < / a >
< a href = " {settings.API_PREFIX} /redoc " > 📖 ReDoc < / a >
< a href = " {settings.API_PREFIX} /health " > 💚 Health Check < / a >
< a href = " {settings.API_PREFIX} /openapi.json " > ⚙ ️ OpenAPI Schema < / a >
< / div >
schema = request . app . openapi ( )
paths = schema . get ( " paths " , { } )
api_prefix = settings . API_PREFIX
# Collect all tags defined in schema (preserves order)
tag_order = [ t [ " name " ] for t in schema . get ( " tags " , [ ] ) ]
# Group routes by first tag
from collections import defaultdict
tag_routes : dict = defaultdict ( list )
untagged : list = [ ]
for path , methods in paths . items ( ) :
for method , op in methods . items ( ) :
if method . upper ( ) not in ( " GET " , " POST " , " PUT " , " DELETE " , " PATCH " ) :
continue
tags = op . get ( " tags " , [ ] )
entry = {
" method " : method . upper ( ) ,
" path " : path ,
" summary " : op . get ( " summary " , " " ) ,
" deprecated " : op . get ( " deprecated " , False ) ,
}
if tags :
tag_routes [ tags [ 0 ] ] . append ( entry )
else :
untagged . append ( entry )
# Build tag sections — respect schema tag order, then alphabetical remainder
all_tags = tag_order + sorted ( t for t in tag_routes if t not in tag_order )
if untagged :
all_tags . append ( " other " )
tag_routes [ " other " ] = untagged
method_colors = {
" GET " : " #61affe " ,
" POST " : " #49cc90 " ,
" PUT " : " #fca130 " ,
" DELETE " : " #f93e3e " ,
" PATCH " : " #50e3c2 " ,
}
sections_html = " "
for tag in all_tags :
routes = tag_routes . get ( tag , [ ] )
if not routes :
continue
rows = " "
for r in routes :
color = method_colors . get ( r [ " method " ] , " #999 " )
deprecated = " style= ' opacity:0.5;text-decoration:line-through ' " if r [ " deprecated " ] else " "
display_path = r [ " path " ] . removeprefix ( api_prefix )
rows + = (
f " <tr { deprecated } > "
f " <td><span class= ' method ' style= ' background: { color } ' > { r [ ' method ' ] } </span></td> "
f " <td><code> { display_path } </code></td> "
f " <td> { r [ ' summary ' ] } </td> "
f " </tr> "
)
sections_html + = f " <h3> { tag } </h3><table> { rows } </table> "
title = schema . get ( " info " , { } ) . get ( " title " , " API " )
version = schema . get ( " info " , { } ) . get ( " version " , " " )
total = sum ( len ( v ) for v in tag_routes . values ( ) )
html = f """ <!DOCTYPE html>
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1.0 " >
< title > { title } < / title >
< style >
body { { font - family : - apple - system , BlinkMacSystemFont , ' Segoe UI ' , Roboto , sans - serif ;
line - height : 1.6 ; max - width : 1100 px ; margin : 0 auto ; padding : 24 px ; color : #333; }}
h1 { { color : #1a1a2e; border-bottom: 3px solid #007acc; padding-bottom: 10px; }}
h3 { { color : #2c3e50; margin-top: 2em; text-transform: capitalize; border-left: 4px solid #007acc; padding-left: 10px; }}
. nav { { margin : 20 px 0 ; display : flex ; gap : 10 px ; flex - wrap : wrap ; } }
. nav a { { padding : 8 px 18 px ; background : #007acc; color: white; text-decoration: none;
border - radius : 6 px ; font - size : 0.9 em ; } }
. nav a : hover { { background : #005fa3; }}
. meta { { color : #666; font-size: 0.9em; margin-bottom: 1.5em; }}
table { { width : 100 % ; border - collapse : collapse ; margin - bottom : 1 em ; font - size : 0.9 em ; } }
tr : hover { { background : #f8f9fa; }}
td { { padding : 7 px 10 px ; border - bottom : 1 px solid #eee; vertical-align: top; }}
td : first - child { { width : 72 px ; } }
td : nth - child ( 2 ) { { width : 38 % ; } }
code { { background : #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 0.85em; word-break: break-all; }}
. method { { display : inline - block ; color : white ; font - weight : bold ; font - size : 0.75 em ;
padding : 3 px 8 px ; border - radius : 4 px ; letter - spacing : 0.5 px ; } }
footer { { margin - top : 3 em ; padding - top : 1.5 em ; border - top : 1 px solid #eee;
text - align : center ; color : #888; font-size: 0.85em; }}
< / style >
< / head >
< body >
< h1 > 🔮 { title } < / h1 >
< div class = " nav " >
< a href = " {api_prefix} /docs " > 📋 Swagger UI < / a >
< a href = " {api_prefix} /redoc " > 📖 ReDoc < / a >
< a href = " {api_prefix} /health " > 💚 Health < / a >
< a href = " {api_prefix} /openapi.json " > ⚙ ️ OpenAPI JSON < / a >
< / div >
< p class = " meta " > v { version } & nbsp ; · & nbsp ; Base URL : < code > { api_prefix } < / code > & nbsp ; · & nbsp ; { total } endpoints < / p >
{ sections_html }
< footer > Auto - generated from OpenAPI schema · < a href = " {api_prefix} /docs " > Full interactive docs < / a > < / footer >
< / body >
< / html > """
return HTMLResponse ( content = html )
< h2 > 🚀 Quick Start < / h2 >
< p > < strong > Base URL : < / strong > < code > http : / / localhost : 18001 / api / v1 < / code > < / p >
< h2 > 🎯 Main Endpoints < / h2 >
< h3 > Health & System < / h3 >
< ul >
< li > < code > GET / health < / code > - API health status < / li >
< li > < code > GET / health / detailed < / code > - Detailed system health < / li >
< / ul >
< h3 > Financial Data < / h3 >
< ul >
< li > < code > POST / financial / data < / code > - Get comprehensive financial data < / li >
< li > < code > GET / financial / data / { { ticker } } < / code > - Simple financial data < / li >
< li > < code > POST / financial / data / bulk < / code > - Bulk financial data < / li >
< / ul >
< h3 > Price Data < / h3 >
< ul >
< li > < code > POST / price / data < / code > - Get historical price data ( OHLCV ) < / li >
< li > < code > GET / price / data / { { ticker } } < / code > - Simple price data < / li >
< li > < code > POST / price / data / bulk < / code > - Bulk price data < / li >
< li > < code > GET / price / quote / { { ticker } } < / code > - Latest quote ( regular / pre / post market ) < / li >
< li > < code > GET / price / intraday / { { ticker } } < / code > - Intraday candles ( interval , period ) < / li >
< li > < code > GET / price / today / { { ticker } } < / code > - Today ' s OHLC (daily or 1m aggregate)</li>
< / ul >
< h3 > Stock Market Data < span class = " new-badge " > NEW < / span > < / h3 >
< ul >
< li > < code > GET / stocks / trending < / code > - Trending stocks with intelligent parameter coordination ( n = 500 default ) < / li >
< li > < code > GET / stocks / most - active < / code > - Most actively traded stocks < / li >
< li > < code > GET / stocks / 52 - week - gainers < / code > - Top 52 - week gaining stocks < / li >
< li > < code > GET / stocks / index / { { index_name } } < / code > - S & P 500 / Nasdaq 100 constituents from Wikipedia ( 24 h cache ) — < code > sp500 < / code > | < code > nasdaq100 < / code > < / li >
< / ul >
< h3 > FRED Economic Data < span class = " new-badge " > NEW < / span > < / h3 >
< ul >
< li > < code > GET / fred / proxy / { { endpoint } } < / code > - Universal FRED API proxy with caching < / li >
< li > < code > GET / fred / endpoints < / code > - List all supported FRED API endpoints < / li >
< li > < code > GET / fred / stats / usage < / code > - API usage statistics and monitoring < / li >
< / ul >
< h3 > News & Social Media < span class = " new-badge " > NEW < / span > < / h3 >
< ul >
< li > < code > GET / news / { { ticker } } < / code > - Complete news and social media data < / li >
< li > < code > GET / news / { { ticker } } / news - only < / code > - News articles only ( faster ) < / li >
< li > < code > GET / news / { { ticker } } / social - only < / code > - Social media posts only < / li >
< / ul >
< h3 > SEC Filings < span class = " new-badge " > NEW < / span > < / h3 >
< ul >
< li > < code > GET / filings / search / { { ticker } } < / code > - Search SEC filings ( 8 - K , 6 - K , 20 - F , 40 - F , 10 - K , 10 - Q ) < / li >
< li > < code > GET / filings / documents / { { accession_number } } < / code > - List all documents in a filing < / li >
< li > < code > GET / filings / exhibit / { { accession_number } } < / code > - Extract exhibit content ( e . g . , EX - 99.1 press releases ) < / li >
< / ul >
< h3 > Alpaca Market Data < span class = " new-badge " > NEW < / span > < / h3 >
< ul >
< li > < code > GET / alpaca / status < / code > - Alpaca connection status and key validity < / li >
< li > < code > GET / alpaca / bars / { { ticker } } < / code > - Fetch bars directly from Alpaca ( raw , no DB ) < / li >
< / ul >
< h3 > FINRA Short Volume < span class = " new-badge " > NEW < / span > < / h3 >
< ul >
< li > < code > GET / finra / short - volume / { { symbol } } < / code > - Short sale volume data for a symbol < / li >
< li > < code > GET / finra / short - ratio / { { symbol } } < / code > - Short ratio history ( aggregated ) < / li >
< li > < code > POST / finra / admin / ingest < / code > - Manually ingest FINRA data for a date / range < / li >
< / ul >
< h3 > ETF Holdings < / h3 >
< ul >
< li > < code > GET / etf / holdings / { { ticker } } < / code > - ETF holdings at date or most recent < / li >
< li > < code > POST / etf / admin / refresh - maps < / code > - Refresh CUSIP / CIK maps < / li >
< / ul >
< h3 > Stock Screener < span class = " new-badge " > NEW < / span > < / h3 >
< ul >
< li > < code > GET / screener / stocks < / code > - Filter stocks by market cap , volume , price , P / E , sector , exchange < / li >
< li > < code > GET / screener / fields < / code > - Available filter options , sectors , sort fields ( metadata ) < / li >
< / ul >
< h3 > Attention Overlay < span class = " new-badge " > NEW < / span > < / h3 >
< ul >
< li > < code > GET / overlay / { { symbol } } < / code > - Overlay score + features + source details ( 0 ~ 1 score , band , hints ) < / li >
< li > < code > GET / overlay / bulk ? symbols = AAPL , TSLA , NVDA < / code > - Bulk overlay scores ( max 50 symbols ) < / li >
< li > < code > GET / overlay / top - movers < / code > - Symbols with highest overlay scores in last 24 h < / li >
< li > < code > GET / overlay / { { symbol } } / headlines < / code > - Recent news headlines matched to symbol < / li >
< li > < code > GET / overlay / { { symbol } } / youtube < / code > - YouTube video mentions from investing channels < / li >
< li > < code > GET / overlay / { { symbol } } / wiki < / code > - Wikipedia pageview time series < / li >
< li > < code > GET / overlay / { { symbol } } / crowding < / code > - FINRA short - sale crowding metrics < / li >
< li > < code > GET / overlay / { { symbol } } / trends < / code > - Google Trends interest data ( if enabled ) < / li >
< li > < code > GET / overlay / { { symbol } } / history < / code > - Historical overlay scores ( backtesting ) < / li >
< li > < code > GET / overlay / admin / health < / code > - Data source health and last collection status < / li >
< li > < code > POST / overlay / admin / trigger - pipeline < / code > - Manually trigger full data collection + scoring < / li >
< li > < code > GET / overlay / admin / job - log < / code > - Pipeline job execution log < / li >
< / ul >
< h2 > 📊 Example Requests < / h2 >
< h3 > Financial Data < / h3 >
< pre > < code > curl - X POST " http://localhost:18001/api/v1/financial/data " \\
- H " Content-Type: application/json " \\
- d ' {{ " ticker " : " AAPL " , " period " : " 1y " , " include_metrics " : true}} ' < / code > < / pre >
< h3 > Trending Stocks < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # Get trending stocks (default: 500 total stocks with intelligent coordination)
curl " http://localhost:18001/api/v1/stocks/trending "
# Custom total count
curl " http://localhost:18001/api/v1/stocks/trending?n=200 " < / code > < / pre >
< h3 > Index Constituents < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # S&P 500 constituents (~503 stocks, cached 24h)
curl " http://localhost:18001/api/v1/stocks/index/sp500 "
# Nasdaq 100 constituents (~101 stocks, cached 24h)
curl " http://localhost:18001/api/v1/stocks/index/nasdaq100 "
# Force refresh (bypass cache)
curl " http://localhost:18001/api/v1/stocks/index/sp500?force_refresh=true " < / code > < / pre >
< h3 > FRED Economic Data < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # Get GDP series information
curl " http://localhost:18001/api/v1/fred/proxy/series?series_id=GDP "
# Get unemployment rate observations
curl " http://localhost:18001/api/v1/fred/proxy/series/observations?series_id=UNRATE&limit=12 " < / code > < / pre >
< h3 > News & Social Data < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > curl " http://localhost:18001/api/v1/news/AAPL?days_back=7&max_articles=20 " < / code > < / pre >
< h3 > Price - Quote / Intraday / Today < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # Quote (latest regular/pre/post)
curl " http://localhost:18001/api/v1/price/quote/AAPL?use_prepost=true "
# Intraday 1m candles for 1 day
curl " http://localhost:18001/api/v1/price/intraday/AAPL?interval=1m&period=1d "
# Today's OHLC (daily if available; otherwise 1m aggregate)
curl " http://localhost:18001/api/v1/price/today/AAPL " < / code > < / pre >
< h3 > SEC Filings < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # Search AAPL 8-K filings (earnings announcements, material events)
curl " http://localhost:18001/api/v1/filings/search/AAPL?form_type=8-K&limit=5 "
# Search foreign issuer filings
curl " http://localhost:18001/api/v1/filings/search/TSM?form_type=20-F "
curl " http://localhost:18001/api/v1/filings/search/SAP?form_type=6-K "
# List all documents in a filing
curl " http://localhost:18001/api/v1/filings/documents/0000320193-26-000005 "
# Extract press release (Exhibit 99.1) from an 8-K
curl " http://localhost:18001/api/v1/filings/exhibit/0000320193-26-000005?exhibit_type=EX-99.1 " < / code > < / pre >
< h3 > ETF Holdings < / h3 >
< pre > < code > curl " http://localhost:18001/api/v1/etf/holdings/QQQ " < / code > < / pre >
< h2 > 🐍 Python Client < / h2 >
< pre > < code > from stock_oracle_client import StockOracleClient
client = StockOracleClient ( " http://localhost:18001 " )
# Check health
health = client . get_health ( )
print ( " API Status: " , health [ " status " ] )
# Get financial data
data = client . get_financial_data ( " AAPL " , period = " 1y " )
# Get news data (NEW!)
news = client . get_news_social_data ( " AAPL " , days_back = 7 ) < / code > < / pre >
< h2 > 📈 Key Features < / h2 >
< ul >
< li > < strong > SEC EDGAR Data < / strong > - Official company filings ( 10 - K , 10 - Q ) with XBRL financial data < / li >
< li > < strong > SEC Filings Search < / strong > - Index and search 8 - K , 6 - K , 20 - F , 40 - F filings with exhibit extraction < / li >
< li > < strong > Real - time News < / strong > - Yahoo Finance + NewsAPI integration < / li >
< li > < strong > Social Sentiment < / strong > - Reddit discussions and sentiment analysis < / li >
< li > < strong > ETF Holdings < / strong > - Complete ETF portfolio analysis via N - PORT < / li >
< li > < strong > Price Data < / strong > - Historical OHLCV data from Yahoo Finance < / li >
< li > < strong > Investment Metrics < / strong > - P / E , ROE , debt ratios , growth metrics < / li >
< li > < strong > Attention Overlay < / strong > - Multi - source retail interest signal ( news burst , Wikipedia views , YouTube mentions , FINRA crowding ) < / li >
< / ul >
< h3 > Attention Overlay Response Example < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > GET / api / v1 / overlay / AAPL
{ {
" symbol " : " AAPL " ,
" as_of_ts " : " 2026-03-13T20:30:00Z " ,
" overlay_score " : 0.72 , / / 0 ~ 1 ( higher = more retail attention )
" overlay_confidence " : 0.80 , / / fraction of sources with data
" overlay_band " : " supportive " , / / silent / tepid / supportive / loud / frenzied
" hold_extension_hint " : " neutral " , / / extend / neutral / trim
" add_on_eligibility " : false ,
" features " : { {
" headline_burst_z " : 1.2 , / / z - score vs 30 - day window
" youtube_influence_z " : 0.8 ,
" wiki_attention_z " : null ,
" theme_heat_z " : null ,
" crowding_stress_z " : - 0.3
} } ,
" source_presence " : { {
" yahoo " : true , " youtube " : true ,
" wikimedia " : false , " google_trends " : false , " finra " : true
} } ,
" source_details " : { {
" yahoo " : { { " headline_count_6h " : 5 , " headline_count_24h " : 12 , " publisher_breadth_24h " : 4 } } ,
" finra " : { { " short_volume_ratio " : 0.42 , " short_volume_spike_zscore " : - 0.3 } }
} }
} } < / code > < / pre >
< h3 > Alpaca Market Data < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # Check Alpaca connection status
curl " http://localhost:18001/api/v1/alpaca/status "
# Get daily bars from Alpaca
curl " http://localhost:18001/api/v1/alpaca/bars/AAPL?interval=1d&start_date=2025-01-01&end_date=2025-01-31 " < / code > < / pre >
< h3 > FINRA Short Volume < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # Ingest FINRA data for a specific date
curl - X POST " http://localhost:18001/api/v1/finra/admin/ingest?date=2025-03-10 "
# Get short volume for AAPL (last 30 days)
curl " http://localhost:18001/api/v1/finra/short-volume/AAPL?days=30 "
# Get short ratio history
curl " http://localhost:18001/api/v1/finra/short-ratio/AAPL?days=60 " < / code > < / pre >
< h3 > Stock Screener < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # Small/mid-cap stocks on NYSE+NASDAQ with avg volume > 500K, sorted by market cap
curl " http://localhost:18001/api/v1/screener/stocks?market_cap_min=500000000&market_cap_max=10000000000&exchange=NYSE,NASDAQ&min_avg_volume=500000&exclude_types=ETF,FUND "
# Technology sector only
curl " http://localhost:18001/api/v1/screener/stocks?market_cap_min=500000000&exchange=NASDAQ§or=Technology&page=1&page_size=50 "
# Available filter metadata
curl " http://localhost:18001/api/v1/screener/fields " < / code > < / pre >
< h3 > Attention Overlay < span class = " new-badge " > NEW < / span > < / h3 >
< pre > < code > # Overlay score for a single symbol
curl " http://localhost:18001/api/v1/overlay/AAPL "
# Bulk scores (comma-separated, max 50)
curl " http://localhost:18001/api/v1/overlay/bulk?symbols=AAPL,TSLA,NVDA,AMD,META "
# Top movers by overlay score (last 24h)
curl " http://localhost:18001/api/v1/overlay/top-movers?limit=10 "
# Recent headlines matched to AAPL
curl " http://localhost:18001/api/v1/overlay/AAPL/headlines?hours=24 "
# Wikipedia pageview trend (last 30 days)
curl " http://localhost:18001/api/v1/overlay/AAPL/wiki?days=30 "
# FINRA crowding metrics
curl " http://localhost:18001/api/v1/overlay/AAPL/crowding "
# Historical overlay scores (backtesting)
curl " http://localhost:18001/api/v1/overlay/AAPL/history?days=90 "
# Manually trigger full pipeline
curl - X POST " http://localhost:18001/api/v1/overlay/admin/trigger-pipeline " < / code > < / pre >
< h2 > 🔧 Data Sources < / h2 >
< ul >
< li > < strong > SEC EDGAR < / strong > - Official company filings and ETF holdings < / li >
< li > < strong > Yahoo Finance < / strong > - Price data , financial news , and RSS headlines < / li >
< li > < strong > Alpaca < / strong > - Market price data ( OHLCV ) with optional API key < / li >
< li > < strong > FINRA < / strong > - RegSHO short sale volume data ( public CDN , no API key ) < / li >
< li > < strong > Wikimedia < / strong > - Wikipedia daily page view counts per company < / li >
< li > < strong > YouTube Data API < / strong > - Video mentions from curated investing channels < / li >
< li > < strong > Google Trends < / strong > - Theme interest data ( experimental , opt - in ) < / li >
< li > < strong > NewsAPI < / strong > - Professional news aggregation < / li >
< li > < strong > Reddit API < / strong > - Social media sentiment from investing subreddits < / li >
< / ul >
< footer style = " margin-top: 3em; padding-top: 2em; border-top: 1px solid #eee; text-align: center; color: #666; " >
< p > < strong > Stock Oracle API < / strong > - Built with FastAPI , powered by SEC EDGAR data < / p >
< p > For complete interactive documentation , visit < a href = " {settings.API_PREFIX} /docs " > Swagger UI < / a > < / p >
< p > < small > Version { settings . APP_VERSION } < / small > < / p >
< / footer >
< / body >
< / html >
"""
return HTMLResponse ( content = simple_html )
except Exception as e :
# Fallback to Swagger UI if anything goes wrong
except Exception :
return RedirectResponse ( url = f " { settings . API_PREFIX } /docs " )
# Additional metadata for OpenAPI