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
72 lines
3.0 KiB
Python
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)
|