Files
DinQuant/backend_api_python/app/services/reflection.py
T
Dinger 2e9c7cd69e feat: AI analysis engine refactor, dark theme polish & virtual position management
Core changes:
- Refactor FastAnalysisService: single LLM multi-factor analysis replaces
  7-agent pipeline; add multi-timeframe consensus, threshold calibration,
  confidence calibration, multi-model ensemble voting
- Add RAG memory injection and reflection validation (analysis_memory +
  reflection worker)
- Simplify billing config: remove unused strategy_run/backtest/portfolio_monitor,
  add ai_code_gen separate billing (different token consumption scale)
- Settings hot-reload after save, no backend restart needed

Frontend:
- Global dark theme overhaul: pure black palette replacing blue-tinted colors
  across sidebar/header/dashboard/analysis/K-line/user-manage/profile/settings/billing
- Fix USDT payment modal dark theme (portal rendering broke CSS selectors)
- Refactor position modal: direction + quantity + entry price, remove add/reduce
  logic, show raw DB values on re-open, save exactly what user inputs
- Fix Polymarket prediction market dark text
- i18n for position modal title

Backend:
- Position management: one record per symbol (DELETE+INSERT replacing
  ON CONFLICT with side), fixes PnL showing 0 when switching long/short
- MarketDataCollector data fetching optimization
- portfolio_monitor scheduled monitoring improvements
- env.example reorganized: common config first, advanced config last

Documentation:
- README architecture diagram updated to FastAnalysisService flow
- Add virtual position, AI tuning config, billing items documentation
- Add INDICATOR_DEFINITIONS_CN.md, FRONTEND_FAST_ANALYSIS.md

Made-with: Cursor
2026-03-23 23:01:04 +08:00

101 lines
3.7 KiB
Python

"""
Reflection Service - Post-trade validation and learning.
Validates historical AI decisions against actual price outcomes,
updates qd_analysis_memory with was_correct/actual_return_pct,
and optionally triggers AI calibration.
"""
import os
import threading
import time
from typing import Dict, Any, Optional
from app.utils.logger import get_logger
from app.services.analysis_memory import get_analysis_memory
logger = get_logger(__name__)
_reflection_thread: Optional[threading.Thread] = None
_reflection_stop = threading.Event()
class ReflectionService:
"""
Runs verification cycle: validate unvalidated decisions, optionally run calibration.
"""
def run_verification_cycle(self) -> Dict[str, Any]:
"""
Run one verification cycle:
1. Validate unvalidated analysis records (older than min_age_days)
2. Optionally run AI calibration for configured markets
"""
memory = get_analysis_memory()
min_age_days = int(os.getenv("REFLECTION_MIN_AGE_DAYS", "7"))
limit = int(os.getenv("REFLECTION_VALIDATE_LIMIT", "200"))
stats = memory.validate_unvalidated_older_than(
min_age_days=min_age_days,
limit=limit,
)
logger.info(f"Reflection validation: {stats}")
if stats.get("validated", 0) > 0:
self._maybe_run_calibration()
else:
logger.debug("No new validations, skipping calibration")
return stats
def _maybe_run_calibration(self) -> None:
"""Run AI calibration if enabled."""
if os.getenv("ENABLE_OFFLINE_AI_CALIBRATION", "true").lower() != "true":
return
try:
from app.services.ai_calibration import AICalibrationService
svc = AICalibrationService()
markets = (os.getenv("AI_CALIBRATION_MARKETS", "Crypto") or "Crypto").strip().split(",")
for market in markets:
market = market.strip()
if not market:
continue
result = svc.calibrate_market(
market=market,
lookback_days=int(os.getenv("AI_CALIBRATION_LOOKBACK_DAYS", "30")),
min_samples=int(os.getenv("AI_CALIBRATION_MIN_SAMPLES", "80")),
validate_before=False,
)
if result:
logger.info(
f"[Reflection] Calibration updated for {market}: "
f"accuracy={result.best_accuracy:.1f}% thr=±{result.buy_threshold:.1f}"
)
except Exception as e:
logger.warning(f"Reflection calibration failed: {e}", exc_info=True)
def start_reflection_worker() -> None:
"""Start background reflection worker (validates + calibrates periodically)."""
global _reflection_thread
# Default to ON to reduce environment-specific configuration needs.
if os.getenv("ENABLE_REFLECTION_WORKER", "true").lower() != "true":
logger.info("Reflection worker disabled (ENABLE_REFLECTION_WORKER != true).")
return
interval_sec = int(os.getenv("REFLECTION_WORKER_INTERVAL_SEC", "86400"))
if _reflection_thread and _reflection_thread.is_alive():
return
def _run():
_reflection_stop.clear()
logger.info(f"Reflection worker started, interval={interval_sec}s")
while not _reflection_stop.is_set():
try:
ReflectionService().run_verification_cycle()
except Exception as e:
logger.error(f"Reflection cycle failed: {e}", exc_info=True)
_reflection_stop.wait(timeout=interval_sec)
logger.info("Reflection worker stopped.")
_reflection_thread = threading.Thread(target=_run, daemon=True)
_reflection_thread.start()