Files
signal-platform/alembic/versions/025_trade_setup_scan_run_id.py
T
dennisthiessenandClaude Fable 5 565484de87
Deploy / lint (push) Successful in 9s
Deploy / test (push) Successful in 1m21s
Deploy / deploy (push) Successful in 39s
fix: select shadow book setups by scan run id, not a time window
The run-id marker proved which scan wrote last, but the shadow book still
selected setups by detected_at >= scan_start. An overlapping manual scan
could insert rows in that same window; if the pipeline's scan wrote the
marker last its id matched and the shadow book proceeded, then swept in --
or ranked highest -- a manual-scan row. The identity check gated entry but
selection did not.

Carry the run id onto the rows. Migration 025 adds an indexed
trade_setups.scan_run_id. scan_all_tickers computes one id per run
(pipeline's when a step, else fresh), passes it to scan_ticker which stamps
every row after enhancement, and writes the same id to the completion
marker. The shadow book selects WHERE scan_run_id == the matched id, so a
concurrent scan's rows are excluded by identity regardless of their
detected_at. The now-unused STARTED marker is dropped; COMPLETED
(freshness) and RUN_ID (identity) remain.

Decisive test: the pipeline's id matches, but a same-window manual row with
a higher rank is present and is excluded -- only the pipeline's own row is
traded. A time-window select would have swept it in and ranked it first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 11:25:03 +02:00

39 lines
1.2 KiB
Python

"""trade_setup scan_run_id — identity of the producing scan run
Revision ID: 025
Revises: 024
Create Date: 2026-07-21 00:00:00.000000
The shadow book must select the exact batch produced by its pipeline's scan.
Matching the scan-completion marker's run id proves which scan wrote last, but
setup selection was still a detected_at window that a concurrent manual scan
could write rows into. Stamping each row with its scan's run id lets the shadow
book select by identity instead. Existing rows are null (they predate the
column and are never traded by the shadow book).
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "025"
down_revision: Union[str, None] = "024"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column(
"trade_setups",
sa.Column("scan_run_id", sa.String(length=32), nullable=True),
)
op.create_index(
"ix_trade_setups_scan_run_id", "trade_setups", ["scan_run_id"]
)
def downgrade() -> None:
op.drop_index("ix_trade_setups_scan_run_id", table_name="trade_setups")
op.drop_column("trade_setups", "scan_run_id")