galaxis-po/backend/alembic/versions/e5f6a7b8c9d0_add_trade_journal_table.py
머니페니 12d235a1f1 feat: add 9 new modules - notification alerts, trading journal, position sizing, pension allocation, drawdown monitoring, benchmark dashboard, tax simulation, correlation analysis, parameter optimizer
Phase 1:
- Real-time signal alerts (Discord/Telegram webhook)
- Trading journal with entry/exit tracking
- Position sizing calculator (Fixed/Kelly/ATR)

Phase 2:
- Pension asset allocation (DC/IRP 70% risk limit)
- Drawdown monitoring with SVG gauge
- Benchmark dashboard (portfolio vs KOSPI vs deposit)

Phase 3:
- Tax benefit simulation (Korean pension tax rules)
- Correlation matrix heatmap
- Parameter optimizer with grid search + overfit detection
2026-03-29 10:03:08 +09:00

72 lines
3.0 KiB
Python

"""add trade journal table
Revision ID: e5f6a7b8c9d0
Revises: d4e5f6a7b8c9
Create Date: 2026-03-29 12:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "e5f6a7b8c9d0"
down_revision: Union[str, None] = "d4e5f6a7b8c9"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"trade_journals",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False),
sa.Column("stock_code", sa.String(20), nullable=False),
sa.Column("stock_name", sa.String(100), nullable=True),
sa.Column(
"trade_type",
sa.Enum("buy", "sell", name="tradetype"),
nullable=False,
),
sa.Column("entry_price", sa.Numeric(12, 2), nullable=True),
sa.Column("target_price", sa.Numeric(12, 2), nullable=True),
sa.Column("stop_loss_price", sa.Numeric(12, 2), nullable=True),
sa.Column("exit_price", sa.Numeric(12, 2), nullable=True),
sa.Column("entry_date", sa.Date(), nullable=False),
sa.Column("exit_date", sa.Date(), nullable=True),
sa.Column("quantity", sa.Integer(), nullable=True),
sa.Column("profit_loss", sa.Numeric(14, 2), nullable=True),
sa.Column("profit_loss_pct", sa.Numeric(8, 4), nullable=True),
sa.Column("entry_reason", sa.Text(), nullable=True),
sa.Column("exit_reason", sa.Text(), nullable=True),
sa.Column("scenario", sa.Text(), nullable=True),
sa.Column("lessons_learned", sa.Text(), nullable=True),
sa.Column("emotional_state", sa.Text(), nullable=True),
sa.Column("strategy_id", sa.Integer(), nullable=True),
sa.Column(
"status",
sa.Enum("open", "closed", name="journalstatus"),
server_default="open",
),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now()),
)
op.create_index("ix_trade_journals_id", "trade_journals", ["id"])
op.create_index("ix_trade_journals_user_id", "trade_journals", ["user_id"])
op.create_index("ix_trade_journals_stock_code", "trade_journals", ["stock_code"])
op.create_index("ix_trade_journals_entry_date", "trade_journals", ["entry_date"])
op.create_index("ix_trade_journals_status", "trade_journals", ["status"])
def downgrade() -> None:
op.drop_index("ix_trade_journals_status")
op.drop_index("ix_trade_journals_entry_date")
op.drop_index("ix_trade_journals_stock_code")
op.drop_index("ix_trade_journals_user_id")
op.drop_index("ix_trade_journals_id")
op.drop_table("trade_journals")
sa.Enum(name="tradetype").drop(op.get_bind(), checkfirst=True)
sa.Enum(name="journalstatus").drop(op.get_bind(), checkfirst=True)