"""add US stock trading support 미국 주식 매매 지원을 위한 스키마 확장. - portfolio, trade_history, active_trades에 currency_code, exchange_code 컬럼 추가 - trade_history에 fx_rate(체결 시점 환율) 컬럼 추가 - exchange_rates 환율 이력 테이블 생성 - us_stock_universe 미국 종목 유니버스 테이블 생성 Revision ID: 0004 Revises: 0003 Create Date: 2026-03-10 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy import inspect as sa_inspect revision: str = "0004" down_revision: Union[str, None] = "0003" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ── portfolio: currency_code, exchange_code 추가 ────────────────────── with op.batch_alter_table("portfolio", schema=None) as batch_op: batch_op.add_column( sa.Column( "currency_code", sa.String(3), nullable=False, server_default="KRW", ) ) batch_op.add_column( sa.Column( "exchange_code", sa.String(10), nullable=True, ) ) batch_op.create_index( "ix_portfolio_currency_code", ["currency_code"], ) # ── trade_history: currency_code, exchange_code, fx_rate 추가 ──────── with op.batch_alter_table("trade_history", schema=None) as batch_op: batch_op.add_column( sa.Column( "currency_code", sa.String(3), nullable=False, server_default="KRW", ) ) batch_op.add_column( sa.Column( "exchange_code", sa.String(10), nullable=True, ) ) batch_op.add_column( sa.Column( "fx_rate", sa.Numeric(10, 4), nullable=True, ) ) batch_op.create_index( "ix_trade_history_currency_code", ["currency_code"], ) # ── active_trades: currency_code, exchange_code 추가 ───────────────── with op.batch_alter_table("active_trades", schema=None) as batch_op: batch_op.add_column( sa.Column( "currency_code", sa.String(3), nullable=False, server_default="KRW", ) ) batch_op.add_column( sa.Column( "exchange_code", sa.String(10), nullable=True, ) ) batch_op.create_index( "ix_active_trades_currency_code", ["currency_code"], ) # ── exchange_rates 테이블 생성 (이미 존재하면 스킵) ──────────────────── bind = op.get_bind() inspector = sa_inspect(bind) existing_tables = inspector.get_table_names() if "exchange_rates" not in existing_tables: op.create_table( "exchange_rates", sa.Column("id", sa.Integer, primary_key=True, index=True), sa.Column("currency_pair", sa.String(7), nullable=False, index=True), sa.Column("rate", sa.Numeric(12, 4), nullable=False), sa.Column("source", sa.String(50), server_default="KIS"), sa.Column("recorded_at", sa.DateTime, server_default=sa.func.now(), index=True), ) # ── us_stock_universe 테이블 생성 (이미 존재하면 스킵) ───────────────── if "us_stock_universe" not in existing_tables: op.create_table( "us_stock_universe", sa.Column("id", sa.Integer, primary_key=True, index=True), sa.Column("ticker", sa.String(10), nullable=False, index=True), sa.Column("company_name", sa.String(200), nullable=False), sa.Column("exchange", sa.String(10), nullable=False), sa.Column("sector", sa.String(100), nullable=True), sa.Column("market_cap", sa.BigInteger, nullable=True), sa.Column("is_active", sa.Boolean, server_default=sa.text("true"), index=True), sa.Column("updated_at", sa.DateTime, server_default=sa.func.now()), sa.UniqueConstraint("ticker", name="uq_us_ticker"), ) def downgrade() -> None: # ── 테이블 삭제 ────────────────────────────────────────────────────── op.drop_table("us_stock_universe") op.drop_table("exchange_rates") # ── active_trades 롤백 ─────────────────────────────────────────────── with op.batch_alter_table("active_trades", schema=None) as batch_op: batch_op.drop_index("ix_active_trades_currency_code") batch_op.drop_column("exchange_code") batch_op.drop_column("currency_code") # ── trade_history 롤백 ─────────────────────────────────────────────── with op.batch_alter_table("trade_history", schema=None) as batch_op: batch_op.drop_index("ix_trade_history_currency_code") batch_op.drop_column("fx_rate") batch_op.drop_column("exchange_code") batch_op.drop_column("currency_code") # ── portfolio 롤백 ─────────────────────────────────────────────────── with op.batch_alter_table("portfolio", schema=None) as batch_op: batch_op.drop_index("ix_portfolio_currency_code") batch_op.drop_column("exchange_code") batch_op.drop_column("currency_code")