Initial commit with 2026 World Cup Quant Platform core modules and CI/CD

This commit is contained in:
QuantBot
2026-06-13 23:18:18 +08:00
commit 073abf98c1
155 changed files with 19539 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
from __future__ import annotations
from datetime import datetime
from enum import Enum
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, func
from sqlalchemy import Enum as SAEnum
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base
class MatchStatus(str, Enum):
PRE_MATCH = 'pre-match'
IN_PLAY = 'in-play'
FINISHED = 'finished'
class Venue(Base):
"""球場主資料:海拔與時區是 2026 世界盃關鍵參數。"""
__tablename__ = 'venues'
id: Mapped[str] = mapped_column(String(36), primary_key=True)
name: Mapped[str] = mapped_column(String(200), nullable=False)
city: Mapped[str] = mapped_column(String(120), nullable=False)
country: Mapped[str] = mapped_column(String(120), nullable=False)
altitude_meters: Mapped[int | None] = mapped_column(Integer, nullable=True)
timezone: Mapped[str] = mapped_column(String(80), nullable=False)
matches: Mapped[list[Match]] = relationship('Match', back_populates='venue', lazy='raise')
class Team(Base):
"""球隊主表,保留排名與 Elo 給量化模型做能力修正。"""
__tablename__ = 'teams'
id: Mapped[str] = mapped_column(String(36), primary_key=True)
name: Mapped[str] = mapped_column(String(140), nullable=False, unique=True)
fifa_rank: Mapped[int | None] = mapped_column(Integer, nullable=True)
current_elo_rating: Mapped[float | None] = mapped_column(Float, nullable=True)
group_name: Mapped[str | None] = mapped_column(String(10), nullable=True)
home_matches: Mapped[list[Match]] = relationship(
'Match',
foreign_keys='Match.home_team_id',
back_populates='home_team',
)
away_matches: Mapped[list[Match]] = relationship(
'Match',
foreign_keys='Match.away_team_id',
back_populates='away_team',
)
class Bookmaker(Base):
"""莊家主檔。"""
__tablename__ = 'bookmakers'
id: Mapped[str] = mapped_column(String(36), primary_key=True)
name: Mapped[str] = mapped_column(String(120), nullable=False, unique=True)
odds_rows: Mapped[list[OddsHistory]] = relationship('OddsHistory', back_populates='bookmaker')
class Match(Base):
"""賽事基本結構,儲存 UTC 時間、場地與賽前 xG。"""
__tablename__ = 'matches'
id: Mapped[str] = mapped_column(String(64), primary_key=True)
home_team_id: Mapped[str] = mapped_column(ForeignKey('teams.id'), nullable=False)
away_team_id: Mapped[str] = mapped_column(ForeignKey('teams.id'), nullable=False)
venue_id: Mapped[str] = mapped_column(ForeignKey('venues.id'), nullable=False)
match_time_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
status: Mapped[MatchStatus] = mapped_column(
SAEnum(MatchStatus, name='match_status', native_enum=False),
default=MatchStatus.PRE_MATCH,
)
home_xg: Mapped[float | None] = mapped_column(Float, nullable=True)
away_xg: Mapped[float | None] = mapped_column(Float, nullable=True)
home_team: Mapped[Team] = relationship('Team', foreign_keys=[home_team_id], back_populates='home_matches')
away_team: Mapped[Team] = relationship('Team', foreign_keys=[away_team_id], back_populates='away_matches')
venue: Mapped[Venue] = relationship('Venue', back_populates='matches')
odds_history: Mapped[list[OddsHistory]] = relationship('OddsHistory', back_populates='match')
recommendations: Mapped[list['ValueBetRecommendation']] = relationship(
'ValueBetRecommendation',
back_populates='match',
cascade='all, delete-orphan',
)
class OddsHistory(Base):
"""時間序列賠率表(待轉為 TimescaleDB Hypertable"""
__tablename__ = 'odds_history'
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
match_id: Mapped[str] = mapped_column(ForeignKey('matches.id'), nullable=False, index=True)
bookmaker_id: Mapped[str] = mapped_column(ForeignKey('bookmakers.id'), nullable=False, index=True)
market_type: Mapped[str] = mapped_column(String(30), nullable=False)
selection: Mapped[str] = mapped_column(String(30), nullable=False)
decimal_odds: Mapped[float] = mapped_column(Float, nullable=False)
implied_probability: Mapped[float] = mapped_column(Float, nullable=False)
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
match: Mapped[Match] = relationship('Match', back_populates='odds_history')
bookmaker: Mapped[Bookmaker] = relationship('Bookmaker', back_populates='odds_rows')
class SmartMoneyFlow(Base):
"""聰明錢流向快照表。"""
__tablename__ = 'smart_money_flow'
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
match_id: Mapped[str] = mapped_column(ForeignKey('matches.id'), nullable=False, index=True)
market_type: Mapped[str] = mapped_column(String(30), nullable=False)
selection: Mapped[str] = mapped_column(String(30), nullable=False)
ticket_pct: Mapped[float] = mapped_column(Float, nullable=False)
handle_pct: Mapped[float] = mapped_column(Float, nullable=False)
sharp_indicator: Mapped[bool] = mapped_column(Boolean, nullable=False)
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
class ValueBetRecommendation(Base):
"""可驗證獲利帳本紀錄(公開透明)。"""
__tablename__ = 'value_bet_recommendations'
id: Mapped[str] = mapped_column(String(64), primary_key=True)
match_id: Mapped[str] = mapped_column(ForeignKey('matches.id'), nullable=False, index=True)
market_type: Mapped[str] = mapped_column(String(30), nullable=False)
selection: Mapped[str] = mapped_column(String(30), nullable=False)
stake: Mapped[float] = mapped_column(Float, nullable=False)
recommended_odds: Mapped[float] = mapped_column(Float, nullable=False)
closing_odds: Mapped[float] = mapped_column(Float, nullable=True)
is_win: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
settled_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
clv_ratio: Mapped[float] = mapped_column(Float, nullable=True)
pnl: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
note: Mapped[str | None] = mapped_column(String(240), nullable=True)
match: Mapped[Match] = relationship('Match', back_populates='recommendations')
class AffiliateBookmaker(Base):
"""聯盟行銷博彩公司追蹤碼設定。"""
__tablename__ = 'affiliate_bookmakers'
id: Mapped[str] = mapped_column(String(36), primary_key=True)
name: Mapped[str] = mapped_column(String(120), nullable=False, unique=True)
tracking_url: Mapped[str] = mapped_column(String(512), nullable=False)
commission_rate: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
class AffiliateClick(Base):
"""聯盟行銷跳轉點擊紀錄(防廣告攔截)。"""
__tablename__ = 'affiliate_clicks'
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
bookmaker_id: Mapped[str] = mapped_column(ForeignKey('affiliate_bookmakers.id'), nullable=False, index=True)
user_ip_hash: Mapped[str] = mapped_column(String(128), nullable=False)
user_agent: Mapped[str | None] = mapped_column(String(512), nullable=True)
referrer: Mapped[str | None] = mapped_column(String(512), nullable=True)
timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now(), index=True)
converted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
class UserProfile(Base):
"""量化大神排行榜與跟單系統的用戶資料。"""
__tablename__ = 'user_profiles'
id: Mapped[str] = mapped_column(String(36), primary_key=True)
username: Mapped[str] = mapped_column(String(120), nullable=False, unique=True)
clv_score: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
roi_30d: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
sharp_rating: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
class CopyBet(Base):
"""一鍵跟單交易紀錄。"""
__tablename__ = 'copy_bets'
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
follower_id: Mapped[str] = mapped_column(ForeignKey('user_profiles.id'), nullable=False, index=True)
leader_id: Mapped[str] = mapped_column(ForeignKey('user_profiles.id'), nullable=False, index=True)
recommendation_id: Mapped[str] = mapped_column(ForeignKey('value_bet_recommendations.id'), nullable=False)
follower_stake: Mapped[float] = mapped_column(Float, nullable=False)
copied_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now())