rebuild(stage-9): deliver deterministic intelligent screening
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
|
||||
from backend.database.migrations.runner import Migration
|
||||
|
||||
|
||||
def upgrade(connection: sqlite3.Connection) -> None:
|
||||
statements = (
|
||||
"""
|
||||
CREATE TABLE screener_factor_snapshots (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
trade_date TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
observed_at TEXT NOT NULL,
|
||||
state TEXT NOT NULL CHECK (state IN ('final', 'archive')),
|
||||
source_set_json TEXT NOT NULL,
|
||||
coverage_json TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
UNIQUE (trade_date, version)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE screener_factor_values (
|
||||
snapshot_id INTEGER NOT NULL
|
||||
REFERENCES screener_factor_snapshots(id) ON DELETE CASCADE,
|
||||
identifier TEXT NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
sector TEXT,
|
||||
listed_days INTEGER NOT NULL,
|
||||
is_st INTEGER NOT NULL CHECK (is_st IN (0, 1)),
|
||||
payload_json TEXT NOT NULL,
|
||||
PRIMARY KEY (snapshot_id, identifier)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE screener_runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
owner_user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
mode TEXT NOT NULL CHECK (mode IN ('stage', 'curated', 'custom')),
|
||||
strategy_id TEXT NOT NULL,
|
||||
strategy_name TEXT NOT NULL,
|
||||
strategy_version INTEGER NOT NULL,
|
||||
selection_date TEXT NOT NULL,
|
||||
factor_snapshot_id INTEGER NOT NULL
|
||||
REFERENCES screener_factor_snapshots(id) ON DELETE RESTRICT,
|
||||
status TEXT NOT NULL CHECK (
|
||||
status IN (
|
||||
'pending', 'running', 'completed', 'no_signal',
|
||||
'data_incomplete', 'failed'
|
||||
)
|
||||
),
|
||||
started_at TEXT,
|
||||
completed_at TEXT,
|
||||
coverage REAL NOT NULL DEFAULT 0 CHECK (coverage >= 0 AND coverage <= 1),
|
||||
missing_fields_json TEXT NOT NULL DEFAULT '[]',
|
||||
result_json TEXT NOT NULL DEFAULT '[]',
|
||||
error_message TEXT NOT NULL DEFAULT ''
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE UNIQUE INDEX screener_runs_idempotency_idx ON screener_runs (
|
||||
mode, strategy_id, selection_date, strategy_version,
|
||||
factor_snapshot_id, COALESCE(owner_user_id, 0)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE custom_screener_strategies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
formula_json TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
UNIQUE (user_id, name)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE strategy_tracks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
run_id INTEGER NOT NULL REFERENCES screener_runs(id) ON DELETE CASCADE,
|
||||
identifier TEXT NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
sector TEXT,
|
||||
selection_date TEXT NOT NULL,
|
||||
strategy_name TEXT NOT NULL,
|
||||
entry_price REAL NOT NULL CHECK (entry_price > 0),
|
||||
added_at TEXT NOT NULL,
|
||||
UNIQUE (user_id, run_id, identifier)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE strategy_track_bars (
|
||||
track_id INTEGER NOT NULL REFERENCES strategy_tracks(id) ON DELETE CASCADE,
|
||||
trade_date TEXT NOT NULL,
|
||||
open REAL NOT NULL,
|
||||
high REAL NOT NULL,
|
||||
low REAL NOT NULL,
|
||||
close REAL NOT NULL,
|
||||
PRIMARY KEY (track_id, trade_date)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE strategy_track_events (
|
||||
track_id INTEGER NOT NULL REFERENCES strategy_tracks(id) ON DELETE CASCADE,
|
||||
milestone TEXT NOT NULL CHECK (milestone IN ('t1', 't5')),
|
||||
created_at TEXT NOT NULL,
|
||||
PRIMARY KEY (track_id, milestone)
|
||||
)
|
||||
""",
|
||||
)
|
||||
for statement in statements:
|
||||
connection.execute(statement)
|
||||
|
||||
|
||||
def downgrade(connection: sqlite3.Connection) -> None:
|
||||
for table in (
|
||||
"strategy_track_events",
|
||||
"strategy_track_bars",
|
||||
"strategy_tracks",
|
||||
"custom_screener_strategies",
|
||||
"screener_runs",
|
||||
"screener_factor_values",
|
||||
"screener_factor_snapshots",
|
||||
):
|
||||
connection.execute(f"DROP TABLE {table}")
|
||||
|
||||
|
||||
MIGRATION = Migration(
|
||||
version=7,
|
||||
name="create_deterministic_screener",
|
||||
signature="screener:v1:factors-runs-custom-tracking",
|
||||
upgrade=upgrade,
|
||||
downgrade=downgrade,
|
||||
)
|
||||
@@ -4,6 +4,7 @@ from backend.database.migrations.m0003_market_foundation import MIGRATION as MAR
|
||||
from backend.database.migrations.m0004_sector_members import MIGRATION as SECTOR_MEMBERS
|
||||
from backend.database.migrations.m0005_market_insights import MIGRATION as MARKET_INSIGHTS
|
||||
from backend.database.migrations.m0006_watchlists import MIGRATION as WATCHLISTS
|
||||
from backend.database.migrations.m0007_screener import MIGRATION as SCREENER
|
||||
from backend.database.migrations.runner import Migration
|
||||
|
||||
MIGRATIONS: tuple[Migration, ...] = (
|
||||
@@ -13,4 +14,5 @@ MIGRATIONS: tuple[Migration, ...] = (
|
||||
SECTOR_MEMBERS,
|
||||
MARKET_INSIGHTS,
|
||||
WATCHLISTS,
|
||||
SCREENER,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user