Files
portfolio-hub/migrations/versions/20260605_0025_ledger_consolidation.py
T

95 lines
3.9 KiB
Python

"""consolidate ledger trades into portfolio ledger
Revision ID: 20260605_0025
Revises: 20260601_0024
Create Date: 2026-06-05 00:25:00.000000
"""
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
revision: str = "20260605_0025"
down_revision: str | None = "20260601_0024"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
# PostgreSQL identity columns are implicitly NOT NULL. ``nullable=True``
# renders a conflicting trailing ``NULL`` clause on a real PostgreSQL DB.
op.add_column("portfolio_ledger", sa.Column("ledger_entry_id", sa.BigInteger(), sa.Identity(always=False), nullable=False))
op.add_column("portfolio_ledger", sa.Column("order_id", sa.Text(), nullable=True))
op.add_column("portfolio_ledger", sa.Column("execution_id", sa.Text(), nullable=True))
op.add_column("portfolio_ledger", sa.Column("side", sa.Text(), nullable=True))
op.add_column("portfolio_ledger", sa.Column("executed_at", sa.DateTime(timezone=True), nullable=True))
op.execute(
"""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'portfolio_ledger' AND column_name = 'action'
) THEN
UPDATE portfolio_ledger SET side = lower(action) WHERE side IS NULL;
END IF;
END $$;
"""
)
op.execute("UPDATE portfolio_ledger SET side = 'manual' WHERE side IS NULL")
op.execute("UPDATE portfolio_ledger SET executed_at = recorded_at WHERE executed_at IS NULL")
op.alter_column("portfolio_ledger", "side", nullable=False)
op.alter_column("portfolio_ledger", "executed_at", nullable=False)
for column in (
"price",
"quantity",
"fee_amount",
"tax_amount",
"cash_balance_after",
"asset_quantity_after",
"total_value_after",
"profit_buffer_after",
):
op.alter_column(
"portfolio_ledger",
column,
existing_type=sa.Float(),
type_=sa.Numeric(20, 8),
postgresql_using=f"{column}::numeric(20,8)",
)
op.drop_column("portfolio_ledger", "action")
op.create_index("idx_portfolio_ledger_portfolio_executed", "portfolio_ledger", ["portfolio_id", sa.text("executed_at DESC")])
op.drop_table("ledger_trades")
def downgrade() -> None:
op.create_table(
"ledger_trades",
sa.Column("ledger_trade_id", sa.BigInteger(), primary_key=True),
sa.Column("portfolio_id", sa.Text(), nullable=False),
sa.Column("order_id", sa.Text(), nullable=False),
sa.Column("execution_id", sa.Text(), nullable=False),
sa.Column("side", sa.Text(), nullable=False),
sa.Column("quantity", sa.Float(), nullable=False),
sa.Column("price", 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("executed_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("recorded_at", sa.DateTime(timezone=True), nullable=False),
sa.UniqueConstraint("portfolio_id", "order_id", "execution_id", name="uq_ledger_trades_idempotency"),
)
op.add_column("portfolio_ledger", sa.Column("action", sa.Text(), nullable=True))
op.execute("UPDATE portfolio_ledger SET action = upper(side) WHERE action IS NULL")
op.alter_column("portfolio_ledger", "action", nullable=False)
op.drop_index("idx_portfolio_ledger_portfolio_executed", table_name="portfolio_ledger")
op.drop_column("portfolio_ledger", "executed_at")
op.drop_column("portfolio_ledger", "side")
op.drop_column("portfolio_ledger", "execution_id")
op.drop_column("portfolio_ledger", "order_id")
op.drop_column("portfolio_ledger", "ledger_entry_id")