first commit
Some checks failed
Deploy / lint (push) Failing after 7s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped

This commit is contained in:
Dennis Thiessen
2026-02-20 17:31:01 +01:00
commit 61ab24490d
160 changed files with 17034 additions and 0 deletions

1
app/schemas/__init__.py Normal file
View File

@@ -0,0 +1 @@

41
app/schemas/admin.py Normal file
View File

@@ -0,0 +1,41 @@
"""Admin request/response schemas."""
from pydantic import BaseModel, Field
class UserManagement(BaseModel):
"""Schema for user access management."""
has_access: bool
class PasswordReset(BaseModel):
"""Schema for resetting a user's password."""
new_password: str = Field(..., min_length=6)
class CreateUserRequest(BaseModel):
"""Schema for admin-created user accounts."""
username: str = Field(..., min_length=1)
password: str = Field(..., min_length=6)
role: str = Field(default="user", pattern=r"^(user|admin)$")
has_access: bool = False
class RegistrationToggle(BaseModel):
"""Schema for toggling registration on/off."""
enabled: bool
class SystemSettingUpdate(BaseModel):
"""Schema for updating a system setting."""
value: str = Field(..., min_length=1)
class DataCleanupRequest(BaseModel):
"""Schema for data cleanup — delete records older than N days."""
older_than_days: int = Field(..., gt=0)
class JobToggle(BaseModel):
"""Schema for enabling/disabling a scheduled job."""
enabled: bool

18
app/schemas/auth.py Normal file
View File

@@ -0,0 +1,18 @@
"""Auth request/response schemas."""
from pydantic import BaseModel, Field
class RegisterRequest(BaseModel):
username: str = Field(..., min_length=1)
password: str = Field(..., min_length=6)
class LoginRequest(BaseModel):
username: str
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"

13
app/schemas/common.py Normal file
View File

@@ -0,0 +1,13 @@
"""Shared API schemas used across all endpoints."""
from typing import Any, Literal
from pydantic import BaseModel
class APIEnvelope(BaseModel):
"""Standard JSON envelope for all API responses."""
status: Literal["success", "error"]
data: Any | None = None
error: str | None = None

View File

@@ -0,0 +1,18 @@
"""Pydantic schemas for fundamental data endpoints."""
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel
class FundamentalResponse(BaseModel):
"""Envelope-ready fundamental data response."""
symbol: str
pe_ratio: float | None = None
revenue_growth: float | None = None
earnings_surprise: float | None = None
market_cap: float | None = None
fetched_at: datetime | None = None

49
app/schemas/indicator.py Normal file
View File

@@ -0,0 +1,49 @@
"""Pydantic schemas for technical indicator endpoints."""
from __future__ import annotations
from datetime import date
from typing import Any, Literal
from pydantic import BaseModel, Field
class IndicatorRequest(BaseModel):
"""Query parameters for indicator computation."""
start_date: date | None = None
end_date: date | None = None
period: int | None = None
class IndicatorResult(BaseModel):
"""Raw indicator values plus normalized score."""
indicator_type: str
values: dict[str, Any]
score: float = Field(ge=0, le=100)
bars_used: int
class IndicatorResponse(BaseModel):
"""Envelope-ready indicator response."""
symbol: str
indicator: IndicatorResult
class EMACrossResult(BaseModel):
"""EMA cross signal details."""
short_ema: float
long_ema: float
short_period: int
long_period: int
signal: Literal["bullish", "bearish", "neutral"]
class EMACrossResponse(BaseModel):
"""Envelope-ready EMA cross response."""
symbol: str
ema_cross: EMACrossResult

31
app/schemas/ohlcv.py Normal file
View File

@@ -0,0 +1,31 @@
"""OHLCV request/response schemas."""
from __future__ import annotations
import datetime as _dt
from pydantic import BaseModel, Field
class OHLCVCreate(BaseModel):
symbol: str = Field(..., description="Ticker symbol (e.g. AAPL)")
date: _dt.date = Field(..., description="Trading date (YYYY-MM-DD)")
open: float = Field(..., ge=0, description="Opening price")
high: float = Field(..., ge=0, description="High price")
low: float = Field(..., ge=0, description="Low price")
close: float = Field(..., ge=0, description="Closing price")
volume: int = Field(..., ge=0, description="Trading volume")
class OHLCVResponse(BaseModel):
id: int
ticker_id: int
date: _dt.date
open: float
high: float
low: float
close: float
volume: int
created_at: _dt.datetime
model_config = {"from_attributes": True}

52
app/schemas/score.py Normal file
View File

@@ -0,0 +1,52 @@
"""Pydantic schemas for scoring endpoints."""
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel, Field
class DimensionScoreResponse(BaseModel):
"""A single dimension score."""
dimension: str
score: float
is_stale: bool
computed_at: datetime | None = None
class ScoreResponse(BaseModel):
"""Full score response for a ticker: composite + all dimensions."""
symbol: str
composite_score: float | None = None
composite_stale: bool = False
weights: dict[str, float] = {}
dimensions: list[DimensionScoreResponse] = []
missing_dimensions: list[str] = []
computed_at: datetime | None = None
class WeightUpdateRequest(BaseModel):
"""Request to update dimension weights."""
weights: dict[str, float] = Field(
...,
description="Dimension name → weight mapping. All weights must be positive.",
)
class RankingEntry(BaseModel):
"""A single entry in the rankings list."""
symbol: str
composite_score: float
dimensions: list[DimensionScoreResponse] = []
class RankingResponse(BaseModel):
"""Rankings response: tickers sorted by composite score descending."""
rankings: list[RankingEntry] = []
weights: dict[str, float] = {}

30
app/schemas/sentiment.py Normal file
View File

@@ -0,0 +1,30 @@
"""Pydantic schemas for sentiment endpoints."""
from __future__ import annotations
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
class SentimentScoreResult(BaseModel):
"""A single sentiment score record."""
id: int
classification: Literal["bullish", "bearish", "neutral"]
confidence: int = Field(ge=0, le=100)
source: str
timestamp: datetime
class SentimentResponse(BaseModel):
"""Envelope-ready sentiment response."""
symbol: str
scores: list[SentimentScoreResult]
count: int
dimension_score: float | None = Field(
None, ge=0, le=100, description="Time-decay weighted sentiment dimension score"
)
lookback_hours: float

27
app/schemas/sr_level.py Normal file
View File

@@ -0,0 +1,27 @@
"""Pydantic schemas for S/R level endpoints."""
from __future__ import annotations
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
class SRLevelResult(BaseModel):
"""A single support/resistance level."""
id: int
price_level: float
type: Literal["support", "resistance"]
strength: int = Field(ge=0, le=100)
detection_method: Literal["volume_profile", "pivot_point", "merged"]
created_at: datetime
class SRLevelResponse(BaseModel):
"""Envelope-ready S/R levels response."""
symbol: str
levels: list[SRLevelResult]
count: int

17
app/schemas/ticker.py Normal file
View File

@@ -0,0 +1,17 @@
"""Ticker request/response schemas."""
from datetime import datetime
from pydantic import BaseModel, Field
class TickerCreate(BaseModel):
symbol: str = Field(..., description="NASDAQ ticker symbol (e.g. AAPL)")
class TickerResponse(BaseModel):
id: int
symbol: str
created_at: datetime
model_config = {"from_attributes": True}

View File

@@ -0,0 +1,21 @@
"""Pydantic schemas for trade setup endpoints."""
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel
class TradeSetupResponse(BaseModel):
"""A single trade setup detected by the R:R scanner."""
id: int
symbol: str
direction: str
entry_price: float
stop_loss: float
target: float
rr_ratio: float
composite_score: float
detected_at: datetime

36
app/schemas/watchlist.py Normal file
View File

@@ -0,0 +1,36 @@
"""Pydantic schemas for watchlist endpoints."""
from __future__ import annotations
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
class SRLevelSummary(BaseModel):
"""Compact SR level for watchlist entry."""
price_level: float
type: Literal["support", "resistance"]
strength: int = Field(ge=0, le=100)
class DimensionScoreSummary(BaseModel):
"""Compact dimension score for watchlist entry."""
dimension: str
score: float
class WatchlistEntryResponse(BaseModel):
"""A single watchlist entry with enriched data."""
symbol: str
entry_type: Literal["auto", "manual"]
composite_score: float | None = None
dimensions: list[DimensionScoreSummary] = []
rr_ratio: float | None = None
rr_direction: str | None = None
sr_levels: list[SRLevelSummary] = []
added_at: datetime