2026-02-01 13:57:30 +00:00

492 lines
17 KiB
Python

from sqlalchemy import (
BigInteger,
Boolean,
CheckConstraint,
Column,
Date,
DateTime,
ForeignKey,
ForeignKeyConstraint,
Index,
Integer,
Numeric,
String,
Text,
UniqueConstraint,
func,
text,
)
from sqlalchemy.dialects.postgresql import JSONB
from app.services.db import Base
class AppUser(Base):
__tablename__ = "app_user"
id = Column(String, primary_key=True)
username = Column(String, nullable=False, unique=True)
password_hash = Column(String, nullable=False)
is_admin = Column(Boolean, nullable=False, server_default=text("false"))
is_super_admin = Column(Boolean, nullable=False, server_default=text("false"))
role = Column(String, nullable=False, server_default=text("'USER'"))
__table_args__ = (
CheckConstraint("role IN ('USER','ADMIN','SUPER_ADMIN')", name="chk_app_user_role"),
Index("idx_app_user_role", "role"),
Index("idx_app_user_is_admin", "is_admin"),
Index("idx_app_user_is_super_admin", "is_super_admin"),
)
class AppSession(Base):
__tablename__ = "app_session"
id = Column(String, primary_key=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
created_at = Column(DateTime(timezone=True), nullable=False)
last_seen_at = Column(DateTime(timezone=True))
expires_at = Column(DateTime(timezone=True), nullable=False)
__table_args__ = (
Index("idx_app_session_user_id", "user_id"),
Index("idx_app_session_expires_at", "expires_at"),
)
class UserBroker(Base):
__tablename__ = "user_broker"
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), primary_key=True)
broker = Column(String)
connected = Column(Boolean, nullable=False, server_default=text("false"))
access_token = Column(Text)
connected_at = Column(DateTime(timezone=True))
api_key = Column(Text)
user_name = Column(Text)
broker_user_id = Column(Text)
pending_broker = Column(Text)
pending_api_key = Column(Text)
pending_api_secret = Column(Text)
pending_started_at = Column(DateTime(timezone=True))
__table_args__ = (
Index("idx_user_broker_broker", "broker"),
Index("idx_user_broker_connected", "connected"),
)
class ZerodhaSession(Base):
__tablename__ = "zerodha_session"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
linked_at = Column(DateTime(timezone=True), nullable=False)
api_key = Column(Text)
access_token = Column(Text)
request_token = Column(Text)
user_name = Column(Text)
broker_user_id = Column(Text)
__table_args__ = (
Index("idx_zerodha_session_user_id", "user_id"),
Index("idx_zerodha_session_linked_at", "linked_at"),
)
class ZerodhaRequestToken(Base):
__tablename__ = "zerodha_request_token"
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), primary_key=True)
request_token = Column(Text, nullable=False)
class StrategyRun(Base):
__tablename__ = "strategy_run"
run_id = Column(String, primary_key=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
started_at = Column(DateTime(timezone=True))
stopped_at = Column(DateTime(timezone=True))
status = Column(String, nullable=False)
strategy = Column(String)
mode = Column(String)
broker = Column(String)
meta = Column(JSONB)
__table_args__ = (
UniqueConstraint("user_id", "run_id", name="uq_strategy_run_user_run"),
CheckConstraint("status IN ('RUNNING','STOPPED','ERROR')", name="chk_strategy_run_status"),
Index("idx_strategy_run_user_status", "user_id", "status"),
Index("idx_strategy_run_user_created", "user_id", "created_at"),
Index(
"uq_one_running_run_per_user",
"user_id",
unique=True,
postgresql_where=text("status = 'RUNNING'"),
),
)
class StrategyConfig(Base):
__tablename__ = "strategy_config"
id = Column(BigInteger, primary_key=True, autoincrement=True)
strategy = Column(String)
sip_amount = Column(Numeric)
sip_frequency_value = Column(Integer)
sip_frequency_unit = Column(String)
mode = Column(String)
broker = Column(String)
active = Column(Boolean)
frequency = Column(Text)
frequency_days = Column(Integer)
unit = Column(String)
next_run = Column(DateTime(timezone=True))
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, ForeignKey("strategy_run.run_id", ondelete="CASCADE"), nullable=False)
__table_args__ = (
UniqueConstraint("user_id", "run_id", name="uq_strategy_config_user_run"),
)
class StrategyLog(Base):
__tablename__ = "strategy_log"
seq = Column(BigInteger, primary_key=True)
ts = Column(DateTime(timezone=True), nullable=False)
level = Column(String)
category = Column(String)
event = Column(String)
message = Column(Text)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, ForeignKey("strategy_run.run_id", ondelete="CASCADE"), nullable=False)
meta = Column(JSONB)
__table_args__ = (
Index("idx_strategy_log_ts", "ts"),
Index("idx_strategy_log_event", "event"),
Index("idx_strategy_log_user_run_ts", "user_id", "run_id", "ts"),
)
class EngineStatus(Base):
__tablename__ = "engine_status"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, nullable=False)
status = Column(String, nullable=False)
last_updated = Column(DateTime(timezone=True), nullable=False)
__table_args__ = (
UniqueConstraint("user_id", "run_id", name="uq_engine_status_user_run"),
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
Index("idx_engine_status_user_run", "user_id", "run_id"),
)
class EngineState(Base):
__tablename__ = "engine_state"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, nullable=False)
total_invested = Column(Numeric)
nifty_units = Column(Numeric)
gold_units = Column(Numeric)
last_sip_ts = Column(DateTime(timezone=True))
last_run = Column(DateTime(timezone=True))
__table_args__ = (
UniqueConstraint("user_id", "run_id", name="uq_engine_state_user_run"),
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
)
class EngineStatePaper(Base):
__tablename__ = "engine_state_paper"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, nullable=False)
initial_cash = Column(Numeric)
cash = Column(Numeric)
total_invested = Column(Numeric)
nifty_units = Column(Numeric)
gold_units = Column(Numeric)
last_sip_ts = Column(DateTime(timezone=True))
last_run = Column(DateTime(timezone=True))
sip_frequency_value = Column(Integer)
sip_frequency_unit = Column(String)
__table_args__ = (
UniqueConstraint("user_id", "run_id", name="uq_engine_state_paper_user_run"),
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
CheckConstraint("cash >= 0", name="chk_engine_state_paper_cash_non_negative"),
)
class EngineEvent(Base):
__tablename__ = "engine_event"
id = Column(BigInteger, primary_key=True, autoincrement=True)
ts = Column(DateTime(timezone=True), nullable=False)
event = Column(String)
data = Column(JSONB)
message = Column(Text)
meta = Column(JSONB)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, ForeignKey("strategy_run.run_id", ondelete="CASCADE"), nullable=False)
__table_args__ = (
Index("idx_engine_event_ts", "ts"),
Index("idx_engine_event_user_run_ts", "user_id", "run_id", "ts"),
)
class PaperBrokerAccount(Base):
__tablename__ = "paper_broker_account"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, nullable=False)
cash = Column(Numeric, nullable=False)
__table_args__ = (
UniqueConstraint("user_id", "run_id", name="uq_paper_broker_account_user_run"),
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
CheckConstraint("cash >= 0", name="chk_paper_broker_cash_non_negative"),
)
class PaperPosition(Base):
__tablename__ = "paper_position"
user_id = Column(String, primary_key=True)
run_id = Column(String, primary_key=True)
symbol = Column(String, primary_key=True)
qty = Column(Numeric, nullable=False)
avg_price = Column(Numeric)
last_price = Column(Numeric)
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
__table_args__ = (
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
CheckConstraint("qty > 0", name="chk_paper_position_qty_positive"),
UniqueConstraint("user_id", "run_id", "symbol", name="uq_paper_position_scope"),
Index("idx_paper_position_user_run", "user_id", "run_id"),
)
class PaperOrder(Base):
__tablename__ = "paper_order"
id = Column(String, primary_key=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, nullable=False)
symbol = Column(String, nullable=False)
side = Column(String, nullable=False)
qty = Column(Numeric, nullable=False)
price = Column(Numeric)
status = Column(String, nullable=False)
timestamp = Column("timestamp", DateTime(timezone=True), nullable=False)
logical_time = Column(DateTime(timezone=True), nullable=False)
__table_args__ = (
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
UniqueConstraint("user_id", "run_id", "id", name="uq_paper_order_scope_id"),
UniqueConstraint(
"user_id",
"run_id",
"logical_time",
"symbol",
"side",
name="uq_paper_order_logical_key",
),
CheckConstraint("qty > 0", name="chk_paper_order_qty_positive"),
CheckConstraint("price >= 0", name="chk_paper_order_price_non_negative"),
Index("idx_paper_order_ts", "timestamp"),
Index("idx_paper_order_user_run_ts", "user_id", "run_id", "timestamp"),
)
class PaperTrade(Base):
__tablename__ = "paper_trade"
id = Column(String, primary_key=True)
order_id = Column(String)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, nullable=False)
symbol = Column(String, nullable=False)
side = Column(String, nullable=False)
qty = Column(Numeric, nullable=False)
price = Column(Numeric, nullable=False)
timestamp = Column("timestamp", DateTime(timezone=True), nullable=False)
logical_time = Column(DateTime(timezone=True), nullable=False)
__table_args__ = (
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
ForeignKeyConstraint(
["user_id", "run_id", "order_id"],
["paper_order.user_id", "paper_order.run_id", "paper_order.id"],
ondelete="CASCADE",
),
UniqueConstraint("user_id", "run_id", "id", name="uq_paper_trade_scope_id"),
UniqueConstraint(
"user_id",
"run_id",
"logical_time",
"symbol",
"side",
name="uq_paper_trade_logical_key",
),
CheckConstraint("qty > 0", name="chk_paper_trade_qty_positive"),
CheckConstraint("price >= 0", name="chk_paper_trade_price_non_negative"),
Index("idx_paper_trade_ts", "timestamp"),
Index("idx_paper_trade_user_run_ts", "user_id", "run_id", "timestamp"),
)
class PaperEquityCurve(Base):
__tablename__ = "paper_equity_curve"
user_id = Column(String, primary_key=True)
run_id = Column(String, primary_key=True)
timestamp = Column("timestamp", DateTime(timezone=True), nullable=False)
logical_time = Column(DateTime(timezone=True), primary_key=True)
equity = Column(Numeric, nullable=False)
pnl = Column(Numeric)
__table_args__ = (
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
Index("idx_paper_equity_curve_ts", "timestamp"),
Index("idx_paper_equity_curve_user_run_ts", "user_id", "run_id", "timestamp"),
)
class MTMLedger(Base):
__tablename__ = "mtm_ledger"
user_id = Column(String, primary_key=True)
run_id = Column(String, primary_key=True)
timestamp = Column("timestamp", DateTime(timezone=True), nullable=False)
logical_time = Column(DateTime(timezone=True), primary_key=True)
nifty_units = Column(Numeric)
gold_units = Column(Numeric)
nifty_price = Column(Numeric)
gold_price = Column(Numeric)
nifty_value = Column(Numeric)
gold_value = Column(Numeric)
portfolio_value = Column(Numeric)
total_invested = Column(Numeric)
pnl = Column(Numeric)
__table_args__ = (
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
Index("idx_mtm_ledger_ts", "timestamp"),
Index("idx_mtm_ledger_user_run_ts", "user_id", "run_id", "timestamp"),
)
class EventLedger(Base):
__tablename__ = "event_ledger"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(String, ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False)
run_id = Column(String, nullable=False)
timestamp = Column("timestamp", DateTime(timezone=True), nullable=False)
logical_time = Column(DateTime(timezone=True), nullable=False)
event = Column(String, nullable=False)
nifty_units = Column(Numeric)
gold_units = Column(Numeric)
nifty_price = Column(Numeric)
gold_price = Column(Numeric)
amount = Column(Numeric)
__table_args__ = (
ForeignKeyConstraint(
["user_id", "run_id"],
["strategy_run.user_id", "strategy_run.run_id"],
ondelete="CASCADE",
),
UniqueConstraint("user_id", "run_id", "event", "logical_time", name="uq_event_ledger_event_time"),
Index("idx_event_ledger_user_run_logical", "user_id", "run_id", "logical_time"),
Index("idx_event_ledger_ts", "timestamp"),
Index("idx_event_ledger_user_run_ts", "user_id", "run_id", "timestamp"),
)
class MarketClose(Base):
__tablename__ = "market_close"
symbol = Column(String, primary_key=True)
date = Column(Date, primary_key=True)
close = Column(Numeric, nullable=False)
__table_args__ = (
Index("idx_market_close_symbol", "symbol"),
Index("idx_market_close_date", "date"),
)
class AdminAuditLog(Base):
__tablename__ = "admin_audit_log"
id = Column(BigInteger, primary_key=True, autoincrement=True)
ts = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
actor_user_hash = Column(Text, nullable=False)
target_user_hash = Column(Text, nullable=False)
target_username_hash = Column(Text)
action = Column(Text, nullable=False)
meta = Column(JSONB)
class AdminRoleAudit(Base):
__tablename__ = "admin_role_audit"
id = Column(BigInteger, primary_key=True, autoincrement=True)
actor_user_id = Column(String, nullable=False)
target_user_id = Column(String, nullable=False)
old_role = Column(String, nullable=False)
new_role = Column(String, nullable=False)
changed_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())