Files
trumpsignal-backend/app/models.py
T
k 4ffcb442fe feat: add daily budget, active window, trade snapshots, and price impact monitor
- New migrations for daily_budget, active_window, and bottrade snapshot
- Add trumpstruth scraper and price_impact_monitor service
- Expand bot_engine, hyperliquid, recovery, and tp_sl_monitor logic
- Update API/schemas/models for new features

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 16:04:49 +08:00

123 lines
6.3 KiB
Python

from datetime import datetime, timezone
from typing import List, Optional
from sqlalchemy import (
BigInteger,
Boolean,
DateTime,
Float,
ForeignKey,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
def utcnow() -> datetime:
"""Naive UTC datetime. All datetimes in this codebase are stored as naive-UTC."""
return datetime.now(timezone.utc).replace(tzinfo=None)
def iso_utc(dt: Optional[datetime]) -> Optional[str]:
"""Serialize a naive-UTC datetime to an explicit ISO-8601 UTC string (suffix 'Z').
If dt already carries tzinfo, convert to UTC first. Returns None if dt is None."""
if dt is None:
return None
if dt.tzinfo is not None:
dt = dt.astimezone(timezone.utc).replace(tzinfo=None)
# Drop microseconds past millisecond for compactness; always end in Z
return dt.isoformat(timespec="milliseconds") + "Z"
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
external_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
text: Mapped[str] = mapped_column(Text, nullable=False)
source: Mapped[str] = mapped_column(String(32), nullable=False, default="truth")
published_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
sentiment: Mapped[str] = mapped_column(String(16), nullable=False, default="neutral")
ai_confidence: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
relevant: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
price_impact_asset: Mapped[Optional[str]] = mapped_column(String(8), nullable=True)
price_impact_m5: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
price_impact_m15: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
price_impact_m1h: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
price_at_post: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
signal: Mapped[Optional[str]] = mapped_column(String(8), nullable=True)
ai_reasoning: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
prefilter_reason: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
analysis_version: Mapped[Optional[str]] = mapped_column(String(16), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=utcnow)
trades: Mapped[List["BotTrade"]] = relationship("BotTrade", back_populates="trigger_post")
class Candle(Base):
__tablename__ = "candles"
__table_args__ = (UniqueConstraint("asset", "timeframe", "time", name="uq_candle_asset_tf_time"),)
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
asset: Mapped[str] = mapped_column(String(8), nullable=False, index=True)
timeframe: Mapped[str] = mapped_column(String(4), nullable=False)
time: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
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[float] = mapped_column(Float, nullable=False)
class BotTrade(Base):
__tablename__ = "bot_trades"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
asset: Mapped[str] = mapped_column(String(8), nullable=False)
side: Mapped[str] = mapped_column(String(8), nullable=False)
entry_price: Mapped[float] = mapped_column(Float, nullable=False)
exit_price: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
pnl_usd: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
hold_seconds: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
trigger_post_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("posts.id"), nullable=True, index=True
)
wallet_address: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
opened_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=utcnow)
closed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
hl_order_id: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
# Snapshot of user settings AT TIME OF ENTRY. Required so changes to
# Subscription.position_size_usd / leverage after-the-fact don't corrupt
# historical PnL or daily-budget accounting.
size_usd: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
leverage: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
trigger_post: Mapped[Optional["Post"]] = relationship("Post", back_populates="trades")
class Subscription(Base):
__tablename__ = "subscriptions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
wallet_address: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
hl_api_key: Mapped[Optional[str]] = mapped_column(String(256), nullable=True)
active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
subscribed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=utcnow)
# Per-user trading config (null → use platform defaults)
leverage: Mapped[int] = mapped_column(Integer, nullable=False, default=3)
position_size_usd: Mapped[float] = mapped_column(Float, nullable=False, default=20.0)
take_profit_pct: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # e.g. 2.0 = close at +2%
stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # e.g. 1.5 = close at -1.5%
min_confidence: Mapped[int] = mapped_column(Integer, nullable=False, default=80)
daily_budget_usd: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # e.g. 15.0 = max $15 of new positions per UTC day
# Optional active window. Both None = always on (when Subscription.active=True).
# Both stored as naive-UTC datetimes.
active_from: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
active_until: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)