File size: 4,751 Bytes
9d29748
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""
Quantitative Models: Features, Factors, and Signals.

Stores computed quantitative data — technical features,
factor exposures, and generated trading signals.
"""

from __future__ import annotations

from datetime import date, datetime

from sqlalchemy import Date, DateTime, Float, ForeignKey, Integer, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.database import Base


class FeatureData(Base):
    """Computed technical features for an asset (SMA, RSI, MACD, etc.)."""

    __tablename__ = "feature_data"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    asset_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("assets.id"), nullable=False, index=True
    )
    date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
    feature_name: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
    feature_value: Mapped[float] = mapped_column(Float, nullable=True)
    parameters_json: Mapped[str] = mapped_column(Text, nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now()
    )

    asset = relationship("Asset", back_populates="features")

    def __repr__(self) -> str:
        return f"<FeatureData({self.feature_name}={self.feature_value}, date={self.date})>"


class Factor(Base):
    """Quantitative factor definition (momentum, value, size, etc.)."""

    __tablename__ = "factors"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)
    display_name: Mapped[str] = mapped_column(String(200), nullable=True)
    description: Mapped[str] = mapped_column(Text, nullable=True)
    category: Mapped[str] = mapped_column(
        String(50), nullable=False
    )  # style, macro, sector, custom
    calculation_method: Mapped[str] = mapped_column(Text, nullable=True)
    is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now()
    )

    exposures = relationship("FactorExposure", back_populates="factor", lazy="noload")

    def __repr__(self) -> str:
        return f"<Factor(name='{self.name}', category='{self.category}')>"


class FactorExposure(Base):
    """Factor exposure (loading) for an asset on a given date."""

    __tablename__ = "factor_exposures"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    asset_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("assets.id"), nullable=False, index=True
    )
    factor_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("factors.id"), nullable=False, index=True
    )
    date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
    exposure_value: Mapped[float] = mapped_column(Float, nullable=False)
    z_score: Mapped[float] = mapped_column(Float, nullable=True)
    percentile_rank: Mapped[float] = mapped_column(Float, nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now()
    )

    asset = relationship("Asset", back_populates="factor_exposures")
    factor = relationship("Factor", back_populates="exposures")

    def __repr__(self) -> str:
        return f"<FactorExposure(asset={self.asset_id}, factor={self.factor_id}, value={self.exposure_value})>"


class Signal(Base):
    """Generated quantitative trading signal."""

    __tablename__ = "signals"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(200), nullable=False, index=True)
    signal_type: Mapped[str] = mapped_column(
        String(50), nullable=False
    )  # momentum, mean_reversion, sentiment, volatility, factor
    asset_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("assets.id"), nullable=True, index=True
    )
    date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
    value: Mapped[float] = mapped_column(Float, nullable=False)
    strength: Mapped[float] = mapped_column(Float, nullable=True)  # 0-1 confidence
    direction: Mapped[str] = mapped_column(
        String(10), nullable=True
    )  # long, short, neutral
    metadata_json: Mapped[str] = mapped_column(Text, nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now()
    )

    asset = relationship("Asset", back_populates="signals")

    def __repr__(self) -> str:
        return f"<Signal(name='{self.name}', type='{self.signal_type}', direction='{self.direction}')>"