From 61e95e56d57ce560328c6fe5217c929da6529150 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 22 Dec 2025 18:19:26 +0100 Subject: [PATCH] Phase 1.2: Create Exchange model Add Exchange model with: - user_id / user relationship - slot_start / slot_end (datetime) - direction (TradeDirection enum: buy/sell) - eur_amount (int, cents), sats_amount (int) - market_price_eur, agreed_price_eur (float) - premium_percentage (int, snapshot) - status (ExchangeStatus enum) - created_at, cancelled_at, completed_at timestamps - unique constraint on slot_start --- backend/models.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/backend/models.py b/backend/models.py index bf593b9..261d303 100644 --- a/backend/models.py +++ b/backend/models.py @@ -348,3 +348,56 @@ class PriceHistory(Base): created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(UTC) ) + + +class Exchange(Base): + """Bitcoin exchange trades booked by users.""" + + __tablename__ = "exchanges" + __table_args__ = (UniqueConstraint("slot_start", name="uq_exchange_slot_start"),) + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + user_id: Mapped[int] = mapped_column( + Integer, ForeignKey("users.id"), nullable=False, index=True + ) + user: Mapped[User] = relationship("User", foreign_keys=[user_id], lazy="joined") + + # Slot timing + slot_start: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, index=True + ) + slot_end: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) + + # Trade details + direction: Mapped[TradeDirection] = mapped_column( + Enum(TradeDirection), nullable=False + ) + eur_amount: Mapped[int] = mapped_column(Integer, nullable=False) # EUR cents + sats_amount: Mapped[int] = mapped_column(Integer, nullable=False) # Satoshis + + # Price information (snapshot at booking time) + market_price_eur: Mapped[float] = mapped_column( + Float, nullable=False + ) # EUR per BTC + agreed_price_eur: Mapped[float] = mapped_column( + Float, nullable=False + ) # EUR per BTC with premium + premium_percentage: Mapped[int] = mapped_column( + Integer, nullable=False + ) # e.g. 5 for 5% + + # Status + status: Mapped[ExchangeStatus] = mapped_column( + Enum(ExchangeStatus), nullable=False, default=ExchangeStatus.BOOKED + ) + + # Timestamps + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=lambda: datetime.now(UTC) + ) + cancelled_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) + completed_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + )