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

25
app/models/__init__.py Normal file
View File

@@ -0,0 +1,25 @@
from app.models.ticker import Ticker
from app.models.ohlcv import OHLCVRecord
from app.models.user import User
from app.models.sentiment import SentimentScore
from app.models.fundamental import FundamentalData
from app.models.score import DimensionScore, CompositeScore
from app.models.sr_level import SRLevel
from app.models.trade_setup import TradeSetup
from app.models.watchlist import WatchlistEntry
from app.models.settings import SystemSetting, IngestionProgress
__all__ = [
"Ticker",
"OHLCVRecord",
"User",
"SentimentScore",
"FundamentalData",
"DimensionScore",
"CompositeScore",
"SRLevel",
"TradeSetup",
"WatchlistEntry",
"SystemSetting",
"IngestionProgress",
]

24
app/models/fundamental.py Normal file
View File

@@ -0,0 +1,24 @@
from datetime import datetime
from sqlalchemy import DateTime, Float, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class FundamentalData(Base):
__tablename__ = "fundamental_data"
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
pe_ratio: Mapped[float | None] = mapped_column(Float, nullable=True)
revenue_growth: Mapped[float | None] = mapped_column(Float, nullable=True)
earnings_surprise: Mapped[float | None] = mapped_column(Float, nullable=True)
market_cap: Mapped[float | None] = mapped_column(Float, nullable=True)
fetched_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
ticker = relationship("Ticker", back_populates="fundamental_data")

30
app/models/ohlcv.py Normal file
View File

@@ -0,0 +1,30 @@
from datetime import date, datetime
from sqlalchemy import BigInteger, Date, DateTime, Float, ForeignKey, Index, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class OHLCVRecord(Base):
__tablename__ = "ohlcv_records"
__table_args__ = (
UniqueConstraint("ticker_id", "date", name="uq_ohlcv_ticker_date"),
Index("ix_ohlcv_ticker_date", "ticker_id", "date"),
)
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
date: Mapped[date] = mapped_column(Date, nullable=False)
open: Mapped[float] = mapped_column(Float, nullable=False)
high: Mapped[float] = mapped_column(Float, nullable=False)
low: Mapped[float] = mapped_column(Float, nullable=False)
close: Mapped[float] = mapped_column(Float, nullable=False)
volume: Mapped[int] = mapped_column(BigInteger, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow, nullable=False
)
ticker = relationship("Ticker", back_populates="ohlcv_records")

40
app/models/score.py Normal file
View File

@@ -0,0 +1,40 @@
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class DimensionScore(Base):
__tablename__ = "dimension_scores"
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
dimension: Mapped[str] = mapped_column(String(50), nullable=False)
score: Mapped[float] = mapped_column(Float, nullable=False)
is_stale: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
computed_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
ticker = relationship("Ticker", back_populates="dimension_scores")
class CompositeScore(Base):
__tablename__ = "composite_scores"
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
score: Mapped[float] = mapped_column(Float, nullable=False)
is_stale: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
weights_json: Mapped[str] = mapped_column(Text, nullable=False)
computed_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
ticker = relationship("Ticker", back_populates="composite_scores")

23
app/models/sentiment.py Normal file
View File

@@ -0,0 +1,23 @@
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class SentimentScore(Base):
__tablename__ = "sentiment_scores"
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
classification: Mapped[str] = mapped_column(String(20), nullable=False)
confidence: Mapped[int] = mapped_column(Integer, nullable=False)
source: Mapped[str] = mapped_column(String(100), nullable=False)
timestamp: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
ticker = relationship("Ticker", back_populates="sentiment_scores")

35
app/models/settings.py Normal file
View File

@@ -0,0 +1,35 @@
from datetime import date, datetime
from sqlalchemy import Date, DateTime, ForeignKey, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class SystemSetting(Base):
__tablename__ = "system_settings"
id: Mapped[int] = mapped_column(primary_key=True)
key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
value: Mapped[str] = mapped_column(Text, nullable=False)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
class IngestionProgress(Base):
__tablename__ = "ingestion_progress"
__table_args__ = (
UniqueConstraint("ticker_id", name="uq_ingestion_progress_ticker"),
)
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
last_ingested_date: Mapped[date] = mapped_column(Date, nullable=False)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
ticker = relationship("Ticker", back_populates="ingestion_progress")

24
app/models/sr_level.py Normal file
View File

@@ -0,0 +1,24 @@
from datetime import datetime
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class SRLevel(Base):
__tablename__ = "sr_levels"
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
price_level: Mapped[float] = mapped_column(Float, nullable=False)
type: Mapped[str] = mapped_column(String(20), nullable=False)
strength: Mapped[int] = mapped_column(Integer, nullable=False)
detection_method: Mapped[str] = mapped_column(String(50), nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow, nullable=False
)
ticker = relationship("Ticker", back_populates="sr_levels")

27
app/models/ticker.py Normal file
View File

@@ -0,0 +1,27 @@
from datetime import datetime
from sqlalchemy import String, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class Ticker(Base):
__tablename__ = "tickers"
id: Mapped[int] = mapped_column(primary_key=True)
symbol: Mapped[str] = mapped_column(String(10), unique=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow, nullable=False
)
# Relationships (cascade deletes)
ohlcv_records = relationship("OHLCVRecord", back_populates="ticker", cascade="all, delete-orphan")
sentiment_scores = relationship("SentimentScore", back_populates="ticker", cascade="all, delete-orphan")
fundamental_data = relationship("FundamentalData", back_populates="ticker", cascade="all, delete-orphan")
sr_levels = relationship("SRLevel", back_populates="ticker", cascade="all, delete-orphan")
dimension_scores = relationship("DimensionScore", back_populates="ticker", cascade="all, delete-orphan")
composite_scores = relationship("CompositeScore", back_populates="ticker", cascade="all, delete-orphan")
trade_setups = relationship("TradeSetup", back_populates="ticker", cascade="all, delete-orphan")
watchlist_entries = relationship("WatchlistEntry", back_populates="ticker", cascade="all, delete-orphan")
ingestion_progress = relationship("IngestionProgress", back_populates="ticker", cascade="all, delete-orphan", uselist=False)

26
app/models/trade_setup.py Normal file
View File

@@ -0,0 +1,26 @@
from datetime import datetime
from sqlalchemy import DateTime, Float, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class TradeSetup(Base):
__tablename__ = "trade_setups"
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
direction: Mapped[str] = mapped_column(String(10), nullable=False)
entry_price: Mapped[float] = mapped_column(Float, nullable=False)
stop_loss: Mapped[float] = mapped_column(Float, nullable=False)
target: Mapped[float] = mapped_column(Float, nullable=False)
rr_ratio: Mapped[float] = mapped_column(Float, nullable=False)
composite_score: Mapped[float] = mapped_column(Float, nullable=False)
detected_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
ticker = relationship("Ticker", back_populates="trade_setups")

24
app/models/user.py Normal file
View File

@@ -0,0 +1,24 @@
from datetime import datetime
from sqlalchemy import Boolean, DateTime, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
role: Mapped[str] = mapped_column(String(20), nullable=False, default="user")
has_access: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
watchlist_entries = relationship("WatchlistEntry", back_populates="user", cascade="all, delete-orphan")

28
app/models/watchlist.py Normal file
View File

@@ -0,0 +1,28 @@
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class WatchlistEntry(Base):
__tablename__ = "watchlist_entries"
__table_args__ = (
UniqueConstraint("user_id", "ticker_id", name="uq_watchlist_user_ticker"),
)
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
entry_type: Mapped[str] = mapped_column(String(10), nullable=False)
added_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow, nullable=False
)
user = relationship("User", back_populates="watchlist_entries")
ticker = relationship("Ticker", back_populates="watchlist_entries")