You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
9.9 KiB
Python
386 lines
9.9 KiB
Python
"""Pydantic response models for Stock Oracle API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Company Info
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class CompanyInfo(BaseModel):
|
|
ticker: str
|
|
name: str | None = None
|
|
cik: str | None = None
|
|
exchange: str | None = None
|
|
sector: str | None = None
|
|
industry: str | None = None
|
|
country: str | None = None
|
|
market_cap: float | None = None
|
|
extra: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SEC Filings
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class FilingEntry(BaseModel):
|
|
accession_no: str
|
|
form_type: str
|
|
filing_date: str # ISO date string
|
|
accepted_at: str | None = None
|
|
primary_document: str | None = None
|
|
description: str | None = None
|
|
items: list[str] = Field(default_factory=list)
|
|
|
|
@field_validator("items", mode="before")
|
|
@classmethod
|
|
def _coerce_none_to_list(cls, v: object) -> object:
|
|
return v if v is not None else []
|
|
|
|
|
|
class FilingSearchResponse(BaseModel):
|
|
ticker: str
|
|
filings: list[FilingEntry] = Field(default_factory=list)
|
|
total: int = 0
|
|
|
|
|
|
class ExhibitDocument(BaseModel):
|
|
exhibit_type: str
|
|
filename: str | None = None
|
|
url: str | None = None
|
|
|
|
|
|
class FilingDocumentsResponse(BaseModel):
|
|
accession_no: str
|
|
exhibits: list[ExhibitDocument] = Field(default_factory=list)
|
|
|
|
|
|
class ExhibitResponse(BaseModel):
|
|
accession_no: str
|
|
exhibit_type: str
|
|
content: str
|
|
content_type: str = "text/plain"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Filing Events (pre-parsed by Oracle)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class FilingEventEntry(BaseModel):
|
|
id: str
|
|
ticker: str
|
|
accession_number: str
|
|
form_type: str
|
|
filing_date: str
|
|
item_number: str | None = None
|
|
event_type: str
|
|
title: str | None = None
|
|
summary: str | None = None
|
|
content_source: str | None = None
|
|
|
|
|
|
class FilingEventsResponse(BaseModel):
|
|
ticker: str
|
|
events: list[FilingEventEntry] = Field(default_factory=list)
|
|
total_count: int = 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Price / Market Data
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class PriceBar(BaseModel):
|
|
date: str # ISO date
|
|
open: float
|
|
high: float
|
|
low: float
|
|
close: float
|
|
volume: int
|
|
vwap: float | None = None
|
|
trade_count: int | None = None
|
|
|
|
|
|
class PriceDataResponse(BaseModel):
|
|
ticker: str
|
|
bars: list[PriceBar] = Field(default_factory=list)
|
|
source: str = "yfinance"
|
|
|
|
|
|
class PriceQuote(BaseModel):
|
|
ticker: str
|
|
price: float
|
|
bid: float | None = None
|
|
ask: float | None = None
|
|
volume: int | None = None
|
|
timestamp: str | None = None
|
|
|
|
|
|
class IntradayBar(BaseModel):
|
|
timestamp: str
|
|
open: float
|
|
high: float
|
|
low: float
|
|
close: float
|
|
volume: int
|
|
|
|
|
|
class IntradayResponse(BaseModel):
|
|
ticker: str
|
|
bars: list[IntradayBar] = Field(default_factory=list)
|
|
timeframe: str = "1m"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Financial / XBRL
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class FinancialPeriod(BaseModel):
|
|
period: str # e.g. "2025-Q4"
|
|
period_end: str # ISO date
|
|
revenue: float | None = None
|
|
net_income: float | None = None
|
|
eps: float | None = None
|
|
gross_margin: float | None = None
|
|
operating_margin: float | None = None
|
|
extra: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class FinancialDataResponse(BaseModel):
|
|
ticker: str
|
|
periods: list[FinancialPeriod] = Field(default_factory=list)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# FRED
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class FredObservation(BaseModel):
|
|
date: str # ISO date
|
|
value: float | None = None
|
|
|
|
|
|
class FredProxyResponse(BaseModel):
|
|
series_id: str
|
|
observations: list[FredObservation] = Field(default_factory=list)
|
|
realtime_start: str | None = None
|
|
realtime_end: str | None = None
|
|
|
|
|
|
class FredSeriesInfo(BaseModel):
|
|
id: str
|
|
title: str | None = None
|
|
frequency: str | None = None
|
|
units: str | None = None
|
|
notes: str | None = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# FINRA Short Volume
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class ShortVolumeEntry(BaseModel):
|
|
date: str # ISO date
|
|
short_volume: int
|
|
short_exempt_volume: int | None = None
|
|
total_volume: int | None = None
|
|
|
|
|
|
class ShortVolumeResponse(BaseModel):
|
|
symbol: str
|
|
data: list[ShortVolumeEntry] = Field(default_factory=list)
|
|
|
|
|
|
class ShortRatioPoint(BaseModel):
|
|
date: str
|
|
short_ratio: float | None = None
|
|
short_percent: float | None = None
|
|
|
|
|
|
class ShortRatioResponse(BaseModel):
|
|
symbol: str
|
|
data: list[ShortRatioPoint] = Field(default_factory=list)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dividends
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class DividendCalendarEntry(BaseModel):
|
|
ticker: str
|
|
ex_dividend_date: str
|
|
amount: float
|
|
declaration_date: str | None = None
|
|
record_date: str | None = None
|
|
payment_date: str | None = None
|
|
currency: str = "USD"
|
|
dividend_type: str = "regular"
|
|
frequency: str | None = None
|
|
as_of_date: str
|
|
source: str
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Form 4 Insider Transactions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class Form4Transaction(BaseModel):
|
|
symbol: str
|
|
filing_date: str
|
|
transaction_date: str | None = None
|
|
owner_cik: str | None = None
|
|
owner_name: str | None = None
|
|
owner_relationship: str | None = None
|
|
is_officer: bool = False
|
|
is_director: bool = False
|
|
is_ten_percent_owner: bool = False
|
|
is_ceo: bool = False
|
|
is_cfo: bool = False
|
|
is_c_suite: bool = False
|
|
transaction_code: str | None = None
|
|
transaction_type: str | None = None
|
|
shares: float = 0.0
|
|
price: float = 0.0
|
|
total_value: float = 0.0
|
|
shares_owned_following: float | None = None
|
|
purchase_pct_of_holding: float | None = None
|
|
accession_number: str | None = None
|
|
|
|
|
|
class Form4AggregateResult(BaseModel):
|
|
symbol: str
|
|
as_of: str
|
|
window_days: int
|
|
buy_count: int = 0
|
|
buy_dollar_total: float = 0.0
|
|
cluster_size: int = 0
|
|
csuite_count: int = 0
|
|
avg_pct_of_holding: float | None = None
|
|
recency_days: int = 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SC 13D/13G Activist Ownership
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class ActivistEvent(BaseModel):
|
|
symbol: str
|
|
filing_date: str
|
|
filer_name: str | None = None
|
|
filer_cik: str | None = None
|
|
form_type: str | None = None
|
|
ownership_pct: float | None = None
|
|
shares_owned: float | None = None
|
|
is_amendment: bool = False
|
|
change_pct: float | None = None
|
|
accession_number: str | None = None
|
|
parse_status: str | None = None
|
|
|
|
|
|
class DividendUpcomingResponse(BaseModel):
|
|
dividends: list[DividendCalendarEntry] = Field(default_factory=list)
|
|
total_count: int = 0
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class DividendHistoryResponse(BaseModel):
|
|
symbol: str
|
|
dividends: list[DividendCalendarEntry] = Field(default_factory=list)
|
|
total_count: int = 0
|
|
annual_yield_estimate: float | None = None
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class DividendIngestResponse(BaseModel):
|
|
symbols_processed: int
|
|
total_records_upserted: int
|
|
failed_symbols: list[str] = Field(default_factory=list)
|
|
status: str
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Screener
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class ScreenerStock(BaseModel):
|
|
symbol: str
|
|
name: str | None = None
|
|
exchange: str | None = None
|
|
market_cap: float | None = None
|
|
avg_volume: float | None = None
|
|
|
|
|
|
class ScreenerResponse(BaseModel):
|
|
stocks: list[ScreenerStock] = Field(default_factory=list)
|
|
total: int = 0
|
|
page: int = 1
|
|
page_size: int = 250
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Attention
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class EntityInfo(BaseModel):
|
|
ticker: str
|
|
canonical_name: str
|
|
wiki_title: str | None = None
|
|
gdelt_query: str | None = None
|
|
aliases: list[str] = Field(default_factory=list)
|
|
resolver_confidence: float = 0.0
|
|
is_manual_override: bool = False
|
|
|
|
|
|
class EntityResolveResponse(BaseModel):
|
|
ticker: str
|
|
entity: EntityInfo
|
|
status: str
|
|
message: str
|
|
|
|
|
|
class WikiFeatures(BaseModel):
|
|
views: int | None = None
|
|
baseline_10d: float | None = None
|
|
spike_10d: float | None = None
|
|
zscore_20d: float | None = None
|
|
|
|
|
|
class NewsFeatures(BaseModel):
|
|
article_count_1d: int = 0
|
|
article_count_3d: int = 0
|
|
unique_domains_3d: int = 0
|
|
us_article_count_3d: int = 0
|
|
gdelt_status: str = "not_collected"
|
|
|
|
|
|
class EventAttentionResponse(BaseModel):
|
|
ticker: str
|
|
event_date: str
|
|
entity: EntityInfo
|
|
wiki: WikiFeatures
|
|
news: NewsFeatures
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class CollectionStatusResponse(BaseModel):
|
|
ticker: str
|
|
source: str
|
|
records_collected: int
|
|
date_range: dict[str, Any] = Field(default_factory=dict)
|
|
status: str
|