492 lines
17 KiB
Python
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())
|