"""paper trade book tag (manual vs shadow) + weekday cron repair Revision ID: 024 Revises: 023 Create Date: 2026-07-20 00:00:00.000000 Two things ship together because both are corrections to 023's stored state. 1. ``paper_trades.book`` separates the discretionary book from the automatic shadow book. Everything that exists today was opened by hand, so the backfill value is "manual". 2. 023 wrote weekday crons with a numeric day-of-week. APScheduler's from_crontab() feeds field 5 to its own day_of_week where 0=Monday, so "1-5" resolved to Tue-Sat: every Monday was skipped and the scanner ran on Saturdays against stale data. Rewrite only the rows that still hold the broken numeric form, so a hand-corrected setting is never clobbered. """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa revision: str = "024" down_revision: Union[str, None] = "023" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None # key -> (broken numeric form written by 023, corrected named form) _CRON_REPAIR: dict[str, tuple[str, str]] = { "schedule_near_close_pipeline_cron": ("30 15 * * 1-5", "30 15 * * mon-fri"), "schedule_after_close_pipeline_cron": ("45 16 * * 1-5", "45 16 * * mon-fri"), "schedule_intraday_pipeline_cron": ("0 10-15 * * 1-5", "0 10-15 * * mon-fri"), "schedule_fundamentals_cron": ("0 1 * * 1", "0 1 * * mon"), } def upgrade() -> None: # server_default backfills existing rows, so no separate UPDATE is needed. op.add_column( "paper_trades", sa.Column("book", sa.String(length=10), nullable=False, server_default="manual"), ) # Literals are inlined rather than bound because bound parameters render as # NULL under `alembic upgrade --sql`, which would silently produce a script # that matches nothing. Every value here is a constant defined above. for key, (broken, fixed) in _CRON_REPAIR.items(): op.execute( f"UPDATE system_settings SET value = '{fixed}' " # noqa: S608 f"WHERE key = '{key}' AND value = '{broken}'" ) def downgrade() -> None: op.drop_column("paper_trades", "book") # Crons are deliberately left corrected — restoring the numeric form would # reintroduce the skipped-Monday bug.