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
This commit is contained in:
counterweight 2025-12-22 18:19:26 +01:00
parent 30e5d0828e
commit 61e95e56d5
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C

View file

@ -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
)