feat: near-close scan schedule and distinct-day gate reset
Move the only qualifying R:R scan to 15:30 ET with chained Telegram alerts, put outcome eval after a final-bar OHLCV fetch, enforce NY trading-day requalify semantics, stamp paper trades fill_mode=near_close, and migrate stored schedule_* keys to America/New_York.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""near-close schedule cutover + paper trade fill_mode era tag
|
||||
|
||||
Revision ID: 023
|
||||
Revises: 022
|
||||
Create Date: 2026-07-18 00:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision: str = "023"
|
||||
down_revision: Union[str, None] = "022"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
# Deliberate schedule rewrite (not a soft defaults refresh). Old stored values
|
||||
# are logged then replaced so prod does not keep scanning at 07:00 Berlin.
|
||||
_SCHEDULE_REWRITE: dict[str, str] = {
|
||||
"schedule_timezone": "America/New_York",
|
||||
"schedule_daily_pipeline_cron": "0 2 * * *",
|
||||
"schedule_near_close_pipeline_cron": "30 15 * * 1-5",
|
||||
"schedule_after_close_pipeline_cron": "45 16 * * 1-5",
|
||||
"schedule_intraday_pipeline_cron": "0 10-15 * * 1-5",
|
||||
"schedule_fundamentals_cron": "0 1 * * 1",
|
||||
}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"paper_trades",
|
||||
sa.Column("fill_mode", sa.String(length=20), nullable=True),
|
||||
)
|
||||
|
||||
conn = op.get_bind()
|
||||
settings = sa.table(
|
||||
"system_settings",
|
||||
sa.column("id", sa.Integer),
|
||||
sa.column("key", sa.String),
|
||||
sa.column("value", sa.Text),
|
||||
sa.column("updated_at", sa.DateTime(timezone=True)),
|
||||
)
|
||||
now = sa.func.now()
|
||||
|
||||
for key, new_value in _SCHEDULE_REWRITE.items():
|
||||
row = conn.execute(
|
||||
sa.select(settings.c.value).where(settings.c.key == key)
|
||||
).fetchone()
|
||||
old_value = row[0] if row is not None else None
|
||||
# Always log so ops can recover the pre-cutover schedule from migration output.
|
||||
print(
|
||||
f"schedule_cutover {key}: {old_value!r} -> {new_value!r}",
|
||||
flush=True,
|
||||
)
|
||||
if row is None:
|
||||
conn.execute(
|
||||
sa.insert(settings).values(
|
||||
key=key, value=new_value, updated_at=now
|
||||
)
|
||||
)
|
||||
else:
|
||||
conn.execute(
|
||||
sa.update(settings)
|
||||
.where(settings.c.key == key)
|
||||
.values(value=new_value, updated_at=now)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("paper_trades", "fill_mode")
|
||||
# Do not restore old crons — unknown prior values; leave stored schedule as-is.
|
||||
Reference in New Issue
Block a user