feat(screener): add news_sentiment table + ai_news defaults + migration
This commit is contained in:
@@ -12,6 +12,7 @@ DEFAULT_WEIGHTS = {
|
|||||||
"rs_rating": 1.2,
|
"rs_rating": 1.2,
|
||||||
"ma_alignment": 1.0,
|
"ma_alignment": 1.0,
|
||||||
"vcp_lite": 0.8,
|
"vcp_lite": 0.8,
|
||||||
|
"ai_news": 0.8,
|
||||||
}
|
}
|
||||||
DEFAULT_NODE_PARAMS = {
|
DEFAULT_NODE_PARAMS = {
|
||||||
"foreign_buy": {"window_days": 5},
|
"foreign_buy": {"window_days": 5},
|
||||||
@@ -21,6 +22,7 @@ DEFAULT_NODE_PARAMS = {
|
|||||||
"rs_rating": {"weights": {"3m": 2, "6m": 1, "9m": 1, "12m": 1}},
|
"rs_rating": {"weights": {"3m": 2, "6m": 1, "9m": 1, "12m": 1}},
|
||||||
"ma_alignment": {"ma_periods": [50, 150, 200]},
|
"ma_alignment": {"ma_periods": [50, 150, 200]},
|
||||||
"vcp_lite": {"short_window": 40, "long_window": 252},
|
"vcp_lite": {"short_window": 40, "long_window": 252},
|
||||||
|
"ai_news": {"min_news_count": 1},
|
||||||
}
|
}
|
||||||
DEFAULT_GATE_PARAMS = {
|
DEFAULT_GATE_PARAMS = {
|
||||||
"min_market_cap_won": 50_000_000_000,
|
"min_market_cap_won": 50_000_000_000,
|
||||||
@@ -110,12 +112,45 @@ CREATE TABLE IF NOT EXISTS screener_results (
|
|||||||
FOREIGN KEY (run_id) REFERENCES screener_runs(id) ON DELETE CASCADE
|
FOREIGN KEY (run_id) REFERENCES screener_runs(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS idx_results_run_rank ON screener_results(run_id, rank);
|
CREATE INDEX IF NOT EXISTS idx_results_run_rank ON screener_results(run_id, rank);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS news_sentiment (
|
||||||
|
ticker TEXT NOT NULL,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
score_raw REAL NOT NULL,
|
||||||
|
reason TEXT NOT NULL DEFAULT '',
|
||||||
|
news_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
tokens_input INTEGER NOT NULL DEFAULT 0,
|
||||||
|
tokens_output INTEGER NOT NULL DEFAULT 0,
|
||||||
|
model TEXT NOT NULL DEFAULT 'claude-haiku-4-5-20251001',
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
||||||
|
PRIMARY KEY (ticker, date)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_news_sentiment_date ON news_sentiment(date DESC);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def ensure_screener_schema(conn: sqlite3.Connection) -> None:
|
def ensure_screener_schema(conn: sqlite3.Connection) -> None:
|
||||||
"""Create tables and seed default settings (idempotent)."""
|
"""Create tables and seed default settings (idempotent)."""
|
||||||
conn.executescript(DDL)
|
conn.executescript(DDL)
|
||||||
|
# ai_news 키 누락 시 1회 보충 (이미 운영 중인 환경에 대해)
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT weights_json, node_params_json FROM screener_settings WHERE id=1"
|
||||||
|
).fetchone()
|
||||||
|
if row is not None:
|
||||||
|
w = json.loads(row[0])
|
||||||
|
p = json.loads(row[1])
|
||||||
|
changed = False
|
||||||
|
if "ai_news" not in w:
|
||||||
|
w["ai_news"] = DEFAULT_WEIGHTS["ai_news"]
|
||||||
|
changed = True
|
||||||
|
if "ai_news" not in p:
|
||||||
|
p["ai_news"] = DEFAULT_NODE_PARAMS["ai_news"]
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE screener_settings SET weights_json=?, node_params_json=? WHERE id=1",
|
||||||
|
(json.dumps(w), json.dumps(p)),
|
||||||
|
)
|
||||||
existing = conn.execute("SELECT id FROM screener_settings WHERE id=1").fetchone()
|
existing = conn.execute("SELECT id FROM screener_settings WHERE id=1").fetchone()
|
||||||
if existing is None:
|
if existing is None:
|
||||||
now = datetime.now(timezone.utc).isoformat()
|
now = datetime.now(timezone.utc).isoformat()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# 주식 서비스용 라이브러리
|
# 주식 서비스용 라이브러리
|
||||||
|
anthropic==0.39.0
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
beautifulsoup4==4.12.3
|
beautifulsoup4==4.12.3
|
||||||
|
|||||||
Reference in New Issue
Block a user