"""initial portfolio schema Revision ID: 20260403_0001 Revises: Create Date: 2026-04-03 00:00:00 """ from __future__ import annotations from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql revision = "20260403_0001" down_revision = None branch_labels = None depends_on = None def upgrade() -> None: op.create_table( "strategy_sets", sa.Column("id", sa.Text(), primary_key=True), sa.Column("strategy_name", sa.Text(), nullable=False), sa.Column("strategy_config", sa.Text(), nullable=False, server_default=""), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("NOW()")), sa.UniqueConstraint("strategy_name", "strategy_config", name="uq_strategy_sets_name_config"), ) op.create_table( "portfolios", sa.Column("portfolio_id", sa.Text(), primary_key=True), sa.Column("broker", sa.Text(), nullable=False, server_default="kis"), sa.Column("status", sa.Text(), nullable=False, server_default="enabled"), sa.Column("symbol", sa.Text(), nullable=False), sa.Column("strategy_name", sa.Text(), nullable=False, server_default=""), sa.Column("strategy_params", postgresql.JSONB(astext_type=sa.Text()), nullable=False, server_default=sa.text("'{}'::jsonb")), sa.Column("cash_balance", sa.Float(), nullable=False), sa.Column("asset_quantity", sa.Float(), nullable=False), sa.Column("asset_price", sa.Float(), nullable=False, server_default="0"), sa.Column("profit_buffer", sa.Float(), nullable=False, server_default="0"), sa.Column("current_strategy_set_id", sa.Text(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("NOW()")), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("NOW()")), sa.Column("last_trade_at", sa.DateTime(timezone=True), nullable=True), ) op.create_table( "portfolio_ledger", sa.Column("portfolio_id", sa.Text(), nullable=False), sa.Column("recorded_at", sa.DateTime(timezone=True), nullable=False), sa.Column("action", sa.Text(), nullable=False), sa.Column("price", sa.Float(), nullable=False), sa.Column("quantity", sa.Float(), nullable=False), sa.Column("fee_amount", sa.Float(), nullable=False, server_default="0"), sa.Column("tax_amount", sa.Float(), nullable=False, server_default="0"), sa.Column("cash_balance_after", sa.Float(), nullable=False), sa.Column("asset_quantity_after", sa.Float(), nullable=False), sa.Column("total_value_after", sa.Float(), nullable=False), sa.Column("profit_buffer_after", sa.Float(), nullable=False, server_default="0"), sa.Column("strategy_set_id", sa.Text(), nullable=False, server_default="legacy"), sa.Column("note", sa.Text(), nullable=False, server_default=""), ) op.create_index( "idx_portfolio_ledger_portfolio_time", "portfolio_ledger", ["portfolio_id", sa.text("recorded_at DESC")], unique=False, ) op.create_index( "idx_portfolios_broker_status_updated", "portfolios", ["broker", "status", sa.text("updated_at DESC")], unique=False, ) bind = op.get_bind() has_timescaledb = bind.execute(sa.text("SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'timescaledb')")).scalar() if has_timescaledb: bind.execute(sa.text("SELECT create_hypertable('portfolio_ledger', 'recorded_at', if_not_exists => TRUE)")) def downgrade() -> None: op.drop_index("idx_portfolios_broker_status_updated", table_name="portfolios") op.drop_index("idx_portfolio_ledger_portfolio_time", table_name="portfolio_ledger") op.drop_table("portfolio_ledger") op.drop_table("portfolios") op.drop_table("strategy_sets")