95 lines
3.9 KiB
Python
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")
|