- charts.py: serve OHLCV bars & trades from Postgres (klines/trades tables written by indexer), no synthetic data - entities.py: add TradeModel/KlineModel matching indexer schema - main.py: idempotent create_all on startup - config.py: unify default DATABASE_URL with docker-compose/indexer
156 lines
7.0 KiB
Python
156 lines
7.0 KiB
Python
import enum
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from sqlalchemy import (
|
|
Column,
|
|
String,
|
|
BigInteger,
|
|
Integer,
|
|
Numeric,
|
|
DateTime,
|
|
Enum,
|
|
Index,
|
|
JSON,
|
|
ForeignKey,
|
|
Boolean,
|
|
Text,
|
|
)
|
|
from sqlalchemy.orm import declarative_base
|
|
|
|
Base = declarative_base()
|
|
|
|
class LedgerEntryType(str, enum.Enum):
|
|
"""复式记账操作类型"""
|
|
DEPOSIT = "DEPOSIT" # 链上充值
|
|
WITHDRAW = "WITHDRAW" # 提现
|
|
TRADE_BUY = "TRADE_BUY" # 现货/曲线买入扣保证金
|
|
TRADE_SELL = "TRADE_SELL" # 现货/曲线卖出增保证金
|
|
FEE_PROTOCOL = "FEE_PROTOCOL" # 协议手续费扣除
|
|
FEE_CREATOR_REBATE = "FEE_CREATOR_REBATE" # 建盘者返佣
|
|
ORDER_FREEZE = "ORDER_FREEZE" # 挂单冻结
|
|
ORDER_UNFREEZE = "ORDER_UNFREEZE" # 撤单解冻
|
|
SETTLEMENT_PAYOUT = "SETTLEMENT_PAYOUT" # 最终获胜兑付
|
|
|
|
class MarketStatus(str, enum.Enum):
|
|
ACTIVE = "ACTIVE"
|
|
RESOLVED = "RESOLVED"
|
|
FINALISED = "FINALISED"
|
|
PAUSED = "PAUSED"
|
|
|
|
class UserModel(Base):
|
|
"""用户基础表"""
|
|
__tablename__ = "users"
|
|
|
|
address = Column(String(42), primary_key=True, index=True) # Checksum EVM Address
|
|
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
last_login_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
is_active = Column(Boolean, default=True, nullable=False)
|
|
|
|
class AccountBalanceModel(Base):
|
|
"""账户资金总览表 (由 Ledger 聚合维护)"""
|
|
__tablename__ = "account_balances"
|
|
|
|
address = Column(String(42), primary_key=True, index=True)
|
|
collateral_symbol = Column(String(16), primary_key=True, default="WUSD")
|
|
available_balance = Column(Numeric(precision=36, scale=18), default=Decimal("0"), nullable=False)
|
|
frozen_balance = Column(Numeric(precision=36, scale=18), default=Decimal("0"), nullable=False)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
|
|
|
class LedgerEntryModel(Base):
|
|
"""
|
|
不可变复式记账明细表 (Immutable Ledger)
|
|
资金流转与状态变更的唯一法定事实源
|
|
"""
|
|
__tablename__ = "ledger_entries"
|
|
|
|
id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True)
|
|
tx_hash = Column(String(66), nullable=True, index=True)
|
|
chain_id = Column(BigInteger, default=46630, nullable=False)
|
|
user_address = Column(String(42), nullable=False, index=True)
|
|
entry_type = Column(Enum(LedgerEntryType), nullable=False, index=True)
|
|
amount = Column(Numeric(precision=36, scale=18), nullable=False) # 变动金额 (+ / -)
|
|
balance_after = Column(Numeric(precision=36, scale=18), nullable=False) # 变动后可用余额
|
|
frozen_after = Column(Numeric(precision=36, scale=18), nullable=False) # 变动后冻结金额
|
|
related_market = Column(String(42), nullable=True, index=True)
|
|
related_order_id = Column(String(64), nullable=True, index=True)
|
|
extra_metadata = Column(JSON, default=dict)
|
|
created_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
|
|
|
|
__table_args__ = (
|
|
Index("idx_ledger_user_created", "user_address", "created_at"),
|
|
)
|
|
|
|
class MarketModel(Base):
|
|
"""预测市场主表"""
|
|
__tablename__ = "markets"
|
|
|
|
market_address = Column(String(42), primary_key=True)
|
|
question_id = Column(String(66), unique=True, nullable=False, index=True)
|
|
title = Column(String(512), nullable=False)
|
|
description = Column(Text, nullable=True)
|
|
category = Column(String(64), default="Crypto", nullable=False, index=True)
|
|
tier = Column(BigInteger, default=1, nullable=False)
|
|
creator = Column(String(42), nullable=False, index=True)
|
|
curve_address = Column(String(42), nullable=False)
|
|
collateral_address = Column(String(42), nullable=False)
|
|
fee_rate = Column(Numeric(precision=36, scale=18), default=Decimal("0.006"), nullable=False)
|
|
resolution_time = Column(DateTime, nullable=False)
|
|
outcomes = Column(JSON, nullable=False) # e.g. ["YES", "NO"]
|
|
num_outcomes = Column(BigInteger, default=2, nullable=False)
|
|
status = Column(Enum(MarketStatus), default=MarketStatus.ACTIVE, nullable=False, index=True)
|
|
winning_outcome = Column(BigInteger, nullable=True)
|
|
created_at_block = Column(BigInteger, nullable=False)
|
|
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
class OrderModel(Base):
|
|
"""链下/链上委托订单明细"""
|
|
__tablename__ = "orders"
|
|
|
|
order_id = Column(String(64), primary_key=True)
|
|
market_address = Column(String(42), ForeignKey("markets.market_address"), nullable=False, index=True)
|
|
user_address = Column(String(42), nullable=False, index=True)
|
|
side = Column(String(8), nullable=False) # "BUY" | "SELL"
|
|
outcome_index = Column(BigInteger, nullable=False)
|
|
amount = Column(Numeric(precision=36, scale=18), nullable=False) # WUSD 或 Token 数量
|
|
price = Column(Numeric(precision=36, scale=18), nullable=True) # 限价 (若为市价则为空)
|
|
status = Column(String(16), default="FILLED", nullable=False) # PENDING, FILLED, CANCELLED
|
|
filled_amount = Column(Numeric(precision=36, scale=18), default=Decimal("0"), nullable=False)
|
|
fee = Column(Numeric(precision=36, scale=18), default=Decimal("0"), nullable=False)
|
|
tx_hash = Column(String(66), nullable=True)
|
|
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
class TradeModel(Base):
|
|
"""链上真实成交明细 (由 Indexer 确定性投影写入)"""
|
|
__tablename__ = "trades"
|
|
|
|
id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True)
|
|
tx_hash = Column(String(66), nullable=True, index=True)
|
|
market_address = Column(String(42), nullable=False, index=True)
|
|
outcome_index = Column(BigInteger, nullable=False)
|
|
trade_type = Column(String(8), nullable=False) # "MINT" | "REDEEM"
|
|
price = Column(Numeric(precision=36, scale=18), nullable=False)
|
|
volume = Column(Numeric(precision=36, scale=18), nullable=False)
|
|
block_number = Column(BigInteger, nullable=True)
|
|
ts = Column(BigInteger, nullable=True, index=True)
|
|
|
|
__table_args__ = (
|
|
Index("idx_trades_market_ts", "market_address", "outcome_index", "ts"),
|
|
)
|
|
|
|
class KlineModel(Base):
|
|
"""秒级 OHLCV K 线 (由 Indexer 按 5 秒窗口确定性聚合写入)"""
|
|
__tablename__ = "klines"
|
|
|
|
market_address = Column(String(42), primary_key=True)
|
|
outcome_index = Column(BigInteger, primary_key=True)
|
|
bar_time = Column(BigInteger, primary_key=True)
|
|
open = Column(Numeric(precision=36, scale=18), nullable=False)
|
|
high = Column(Numeric(precision=36, scale=18), nullable=False)
|
|
low = Column(Numeric(precision=36, scale=18), nullable=False)
|
|
close = Column(Numeric(precision=36, scale=18), nullable=False)
|
|
volume = Column(Numeric(precision=36, scale=18), nullable=False)
|
|
|
|
__table_args__ = (
|
|
Index("idx_klines_market_time", "market_address", "outcome_index", "bar_time"),
|
|
)
|