feat: implement Smart Volatility Scanner with RSI/volume filters (issue #76)
Some checks failed
CI / test (pull_request) Has been cancelled

Add Python-first scanning pipeline that reduces Gemini API calls by filtering
stocks before AI analysis: KIS rankings API -> RSI/volume filter -> AI judgment.

## Implementation
- Add RSI calculation (Wilder's smoothing method) to VolatilityAnalyzer
- Add KIS API methods: fetch_market_rankings() and get_daily_prices()
- Create SmartVolatilityScanner with configurable thresholds
- Integrate scanner into main.py realtime mode
- Add selection_context logging to trades table for Evolution system

## Configuration
- RSI_OVERSOLD_THRESHOLD: 30 (configurable 0-50)
- RSI_MOMENTUM_THRESHOLD: 70 (configurable 50-100)
- VOL_MULTIPLIER: 2.0 (minimum volume ratio, configurable 1-10)
- SCANNER_TOP_N: 3 (max candidates per scan, configurable 1-10)

## Benefits
- Reduces Gemini API calls (process 1-3 qualified stocks vs 20-30 ranked)
- Python-based technical filtering before expensive AI judgment
- Tracks selection criteria (RSI, volume_ratio, signal, score) for strategy optimization
- Graceful fallback to static watchlist if ranking API fails

## Tests
- 13 new tests for SmartVolatilityScanner and RSI calculation
- All existing tests updated and passing
- Coverage maintained at 73%

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
agentson
2026-02-06 00:48:23 +09:00
parent 27f581f17d
commit f0ae25c533
9 changed files with 861 additions and 24 deletions

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import json
import sqlite3
from datetime import UTC, datetime
from pathlib import Path
@@ -38,6 +39,8 @@ def init_db(db_path: str) -> sqlite3.Connection:
conn.execute("ALTER TABLE trades ADD COLUMN market TEXT DEFAULT 'KR'")
if "exchange_code" not in columns:
conn.execute("ALTER TABLE trades ADD COLUMN exchange_code TEXT DEFAULT 'KRX'")
if "selection_context" not in columns:
conn.execute("ALTER TABLE trades ADD COLUMN selection_context TEXT")
# Context tree tables for multi-layered memory management
conn.execute(
@@ -118,15 +121,33 @@ def log_trade(
pnl: float = 0.0,
market: str = "KR",
exchange_code: str = "KRX",
selection_context: dict[str, any] | None = None,
) -> None:
"""Insert a trade record into the database."""
"""Insert a trade record into the database.
Args:
conn: Database connection
stock_code: Stock code
action: Trade action (BUY/SELL/HOLD)
confidence: Confidence level (0-100)
rationale: AI decision rationale
quantity: Number of shares
price: Trade price
pnl: Profit/loss
market: Market code
exchange_code: Exchange code
selection_context: Scanner selection data (RSI, volume_ratio, signal, score)
"""
# Serialize selection context to JSON
context_json = json.dumps(selection_context) if selection_context else None
conn.execute(
"""
INSERT INTO trades (
timestamp, stock_code, action, confidence, rationale,
quantity, price, pnl, market, exchange_code
quantity, price, pnl, market, exchange_code, selection_context
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
datetime.now(UTC).isoformat(),
@@ -139,6 +160,7 @@ def log_trade(
pnl,
market,
exchange_code,
context_json,
),
)
conn.commit()