feat(kevin): alembic migration for v2 trading tables
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled
3 new tables + seeds the 'kevin' row in strategies with a pinned UUID constant so Trade.strategy_id can be joined back to the strategy across live + backtest paths.
This commit is contained in:
parent
4d40536da7
commit
c4e92b580e
1 changed files with 235 additions and 0 deletions
235
alembic/versions/d4e5f6a7b8c9_kevin_v2_trading_tables.py
Normal file
235
alembic/versions/d4e5f6a7b8c9_kevin_v2_trading_tables.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
"""kevin v2 trading tables + seed kevin strategy row.
|
||||
|
||||
Revision ID: d4e5f6a7b8c9
|
||||
Revises: c3d4e5f6a7b8
|
||||
Create Date: 2026-05-23
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects.postgresql import ENUM, JSONB, UUID
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "d4e5f6a7b8c9"
|
||||
down_revision: Union[str, Sequence[str], None] = "c3d4e5f6a7b8"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
KEVIN_STRATEGY_UUID = uuid.UUID("4b8d1c2a-5e7f-4d3b-9a1c-6f8b2e4d7a90")
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 1) Seed the strategies.kevin row that Trade.strategy_id will reference
|
||||
op.execute(
|
||||
f"""
|
||||
INSERT INTO strategies (id, name, description, current_weight, active, created_at, updated_at)
|
||||
VALUES (
|
||||
'{KEVIN_STRATEGY_UUID}',
|
||||
'kevin',
|
||||
'Meet Kevin signal-driven strategy (paper trading)',
|
||||
1.0,
|
||||
true,
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
"""
|
||||
)
|
||||
|
||||
# 2) Enums
|
||||
bridge_status_enum = sa.Enum(
|
||||
"emitted",
|
||||
"skipped_non_tradable",
|
||||
"skipped_blocklist",
|
||||
"skipped_caps",
|
||||
"deferred",
|
||||
"broker_rejected",
|
||||
"dry_run",
|
||||
name="kevin_bridge_status",
|
||||
)
|
||||
bridge_status_enum.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
run_status_enum = sa.Enum(
|
||||
"running",
|
||||
"completed",
|
||||
"failed",
|
||||
name="kevin_backtest_run_status",
|
||||
)
|
||||
run_status_enum.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
trigger_source_enum = sa.Enum(
|
||||
"manual",
|
||||
"scheduled",
|
||||
name="kevin_backtest_trigger_source",
|
||||
)
|
||||
trigger_source_enum.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
# 3) Bridge audit table
|
||||
op.create_table(
|
||||
"kevin_signal_bridge_state",
|
||||
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column(
|
||||
"mention_id",
|
||||
sa.BigInteger,
|
||||
sa.ForeignKey("kevin_stock_mentions.id"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
),
|
||||
sa.Column(
|
||||
"bridge_status",
|
||||
ENUM(name="kevin_bridge_status", create_type=False),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"signal_id",
|
||||
UUID(as_uuid=True),
|
||||
sa.ForeignKey("signals.id"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"trade_id",
|
||||
UUID(as_uuid=True),
|
||||
sa.ForeignKey("trades.id"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("effective_conviction", sa.Numeric(4, 3), nullable=True),
|
||||
sa.Column(
|
||||
"decided_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("notes", sa.Text, nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_bridge_state_status_decided",
|
||||
"kevin_signal_bridge_state",
|
||||
["bridge_status", "decided_at"],
|
||||
)
|
||||
|
||||
# 4) Backtest runs
|
||||
op.create_table(
|
||||
"kevin_backtest_runs",
|
||||
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("run_uuid", UUID(as_uuid=True), unique=True, nullable=False),
|
||||
sa.Column(
|
||||
"started_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column(
|
||||
"status",
|
||||
ENUM(name="kevin_backtest_run_status", create_type=False),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"trigger_source",
|
||||
ENUM(name="kevin_backtest_trigger_source", create_type=False),
|
||||
nullable=False,
|
||||
server_default="manual",
|
||||
),
|
||||
sa.Column("params_json", JSONB, nullable=False),
|
||||
sa.Column("metrics_json", JSONB, nullable=True),
|
||||
sa.Column("equity_curve_json", JSONB, nullable=True),
|
||||
sa.Column("benchmark_curve_json", JSONB, nullable=True),
|
||||
sa.Column("error_message", sa.Text, nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_backtest_runs_started",
|
||||
"kevin_backtest_runs",
|
||||
["started_at"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_backtest_runs_status_started",
|
||||
"kevin_backtest_runs",
|
||||
["status", "started_at"],
|
||||
)
|
||||
|
||||
# 5) Backtest trades
|
||||
op.create_table(
|
||||
"kevin_backtest_trades",
|
||||
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column(
|
||||
"run_id",
|
||||
UUID(as_uuid=True),
|
||||
sa.ForeignKey("kevin_backtest_runs.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("symbol", sa.String(16), nullable=False),
|
||||
sa.Column(
|
||||
"source_mention_id",
|
||||
sa.BigInteger,
|
||||
sa.ForeignKey("kevin_stock_mentions.id"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("entry_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("entry_price", sa.Numeric(12, 4), nullable=False),
|
||||
sa.Column("exit_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("exit_price", sa.Numeric(12, 4), nullable=True),
|
||||
sa.Column("qty", sa.Numeric(14, 4), nullable=False),
|
||||
sa.Column("pnl_usd", sa.Numeric(14, 4), nullable=True),
|
||||
sa.Column("pnl_pct", sa.Numeric(8, 4), nullable=True),
|
||||
sa.Column("holding_days_actual", sa.Integer, nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_backtest_trades_run_symbol",
|
||||
"kevin_backtest_trades",
|
||||
["run_id", "symbol"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_backtest_trades_run_entry",
|
||||
"kevin_backtest_trades",
|
||||
["run_id", "entry_at"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("kevin_backtest_trades")
|
||||
op.drop_table("kevin_backtest_runs")
|
||||
op.drop_table("kevin_signal_bridge_state")
|
||||
bind = op.get_bind()
|
||||
ENUM(name="kevin_backtest_trigger_source").drop(bind, checkfirst=True)
|
||||
ENUM(name="kevin_backtest_run_status").drop(bind, checkfirst=True)
|
||||
ENUM(name="kevin_bridge_status").drop(bind, checkfirst=True)
|
||||
op.execute(f"DELETE FROM strategies WHERE id = '{KEVIN_STRATEGY_UUID}'")
|
||||
Loading…
Add table
Add a link
Reference in a new issue