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.

80 lines
2.5 KiB
Python

"""
Earnings Surprise schemas
"""
from datetime import date, datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field
class EarningsSurpriseEntry(BaseModel):
model_config = ConfigDict(from_attributes=True)
fiscal_date_ending: date
reported_date: Optional[date] = None
reported_eps: Optional[float] = None
estimated_eps: Optional[float] = None
surprise: Optional[float] = None
surprise_percentage: Optional[float] = None
beat: Optional[bool] = None
@classmethod
def from_orm_obj(cls, obj) -> "EarningsSurpriseEntry":
surprise = obj.surprise
return cls(
fiscal_date_ending=(
obj.fiscal_date_ending.date()
if hasattr(obj.fiscal_date_ending, "date")
else obj.fiscal_date_ending
),
reported_date=(
obj.reported_date.date()
if obj.reported_date and hasattr(obj.reported_date, "date")
else obj.reported_date
),
reported_eps=obj.reported_eps,
estimated_eps=obj.estimated_eps,
surprise=surprise,
surprise_percentage=obj.surprise_percentage,
beat=surprise > 0 if surprise is not None else None,
)
class EarningsSurpriseResponse(BaseModel):
symbol: str
quarters: List[EarningsSurpriseEntry]
streak: int = 0 # positive = consecutive beats, negative = consecutive misses
avg_surprise_pct: Optional[float] = None
metadata: Dict[str, Any] = Field(default_factory=dict)
class EarningsCalendarEntry(BaseModel):
symbol: str
earnings_date: datetime
earnings_time: str # "pre_market" | "post_market" | "during_market" | "unknown"
estimated_eps: Optional[float] = None
reported_eps: Optional[float] = None # non-null when as_of_date is historical
source: str = "yfinance"
fetched_at: datetime
class EarningsCalendarResponse(BaseModel):
symbol: str
upcoming_earnings: List[EarningsCalendarEntry]
metadata: Dict[str, Any] = Field(default_factory=dict)
class BulkEarningsCalendarRequest(BaseModel):
symbols: List[str] = Field(..., min_length=1, max_length=50)
days_ahead: int = Field(30, ge=1, le=365)
limit: int = Field(4, ge=1, le=20)
as_of_date: Optional[date] = Field(
None, description="PIT date for backtesting (YYYY-MM-DD). Defaults to today."
)
class BulkEarningsCalendarResponse(BaseModel):
entries: List[EarningsCalendarEntry]
metadata: Dict[str, Any] = Field(default_factory=dict)