diff --git a/backend/app/api/__pycache__/market.cpython-313.pyc b/backend/app/api/__pycache__/market.cpython-313.pyc
index 0480197e..e810f9d1 100644
Binary files a/backend/app/api/__pycache__/market.cpython-313.pyc and b/backend/app/api/__pycache__/market.cpython-313.pyc differ
diff --git a/backend/app/api/market.py b/backend/app/api/market.py
index c7bea56c..ed80a130 100644
--- a/backend/app/api/market.py
+++ b/backend/app/api/market.py
@@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends
from app.data.tushare_client import tushare_client
from app.data import tencent_client
+from app.data.cache import cache
from app.data.market_breadth_client import get_market_breadth
from app.analysis.market_temp import build_realtime_market_temperature, calculate_market_temperature
from app.engine.recommender import get_latest_recommendations
@@ -72,15 +73,27 @@ async def get_overview():
@router.get("/strategy-board")
async def get_strategy_board():
"""获取今日市场作战面板(只读,不触发 LLM)"""
+ cache_key = "market:strategy_board:rules"
+ cached = cache.get(cache_key)
+ if cached is not None:
+ return cached
from app.llm.strategy_board import build_strategy_board
- return await build_strategy_board(include_llm=False)
+ result = await build_strategy_board(include_llm=False)
+ cache.set(cache_key, result, settings.cache_ttl_realtime)
+ return result
@router.get("/strategy-iteration")
async def get_strategy_iteration(limit: int = 50):
"""获取策略复盘迭代建议(只读,不触发 LLM)"""
+ cache_key = f"market:strategy_iteration:{limit}:rules"
+ cached = cache.get(cache_key)
+ if cached is not None:
+ return cached
from app.llm.strategy_iteration import build_strategy_iteration_report
- return await build_strategy_iteration_report(limit=limit, include_llm=False)
+ result = await build_strategy_iteration_report(limit=limit, include_llm=False)
+ cache.set(cache_key, result, settings.cache_ttl_realtime)
+ return result
@router.get("/ops-status")
@@ -160,14 +173,18 @@ async def get_ops_status():
async def generate_strategy_board(_admin: dict = Depends(get_current_admin)):
"""管理员手动生成带 LLM 说明的策略看板"""
from app.llm.strategy_board import build_strategy_board
- return await build_strategy_board(include_llm=True)
+ result = await build_strategy_board(include_llm=True)
+ cache.delete("market:strategy_board:rules")
+ return result
@router.post("/generate-strategy-iteration")
async def generate_strategy_iteration(limit: int = 50, _admin: dict = Depends(get_current_admin)):
"""管理员手动生成带 LLM 分析的策略复盘"""
from app.llm.strategy_iteration import build_strategy_iteration_report
- return await build_strategy_iteration_report(limit=limit, include_llm=True)
+ result = await build_strategy_iteration_report(limit=limit, include_llm=True)
+ cache.delete(f"market:strategy_iteration:{limit}:rules")
+ return result
async def _overview_realtime():
"""盘中:腾讯实时指数行情"""
diff --git a/backend/app/llm/strategy_iteration.py b/backend/app/llm/strategy_iteration.py
index 49264eb5..8b4770b1 100644
--- a/backend/app/llm/strategy_iteration.py
+++ b/backend/app/llm/strategy_iteration.py
@@ -27,6 +27,12 @@ async def build_strategy_iteration_report(limit: int = 50, include_llm: bool = F
return rule_report
+async def build_strategy_feedback_controls(limit: int = 50) -> dict:
+ rows = await _load_recent_tracking(limit)
+ report = _build_rule_report(rows)
+ return _derive_feedback_controls(report)
+
+
async def _load_recent_tracking(limit: int) -> list[dict]:
from sqlalchemy import text
from app.db.database import get_db
@@ -240,6 +246,67 @@ def _build_adjustment_suggestions(
return suggestions[:6]
+def _derive_feedback_controls(report: dict) -> dict:
+ suggestions = report.get("adjustment_suggestions", []) or []
+ sample_size = int(report.get("sample_size") or 0)
+
+ controls = {
+ "sample_size": sample_size,
+ "enabled": sample_size >= 10,
+ "buy_threshold_delta": 0,
+ "max_position_pct_delta": 0,
+ "actionable_limit_delta": 0,
+ "watch_limit_delta": 0,
+ "force_defensive": False,
+ "notes": [],
+ }
+
+ if sample_size < 10:
+ controls["notes"].append("样本不足,暂不启用自动回写。")
+ return controls
+
+ promote_count = 0
+ tighten_count = 0
+ reduce_count = 0
+
+ for item in suggestions[:6]:
+ action = item.get("action")
+ reason = item.get("reason", "")
+
+ if action == "promote":
+ promote_count += 1
+ controls["buy_threshold_delta"] -= 1
+ controls["watch_limit_delta"] += 1
+ elif action == "tighten":
+ tighten_count += 1
+ controls["buy_threshold_delta"] += 1
+ controls["actionable_limit_delta"] -= 1
+ controls["max_position_pct_delta"] -= 5
+ elif action == "reduce":
+ reduce_count += 1
+ controls["buy_threshold_delta"] += 1
+ controls["watch_limit_delta"] -= 1
+
+ if "弱势市场" in reason or item.get("target") == "defensive_watch":
+ controls["force_defensive"] = True
+
+ controls["buy_threshold_delta"] = max(-2, min(3, controls["buy_threshold_delta"]))
+ controls["max_position_pct_delta"] = max(-10, min(5, controls["max_position_pct_delta"]))
+ controls["actionable_limit_delta"] = max(-2, min(1, controls["actionable_limit_delta"]))
+ controls["watch_limit_delta"] = max(-2, min(2, controls["watch_limit_delta"]))
+
+ if controls["force_defensive"]:
+ controls["notes"].append("最近弱市亏损样本偏多,优先启用防守约束。")
+ elif tighten_count > promote_count:
+ controls["notes"].append("最近失效样本偏多,整体建议略收紧。")
+ elif promote_count > 0 and reduce_count == 0:
+ controls["notes"].append("最近有效样本改善,可适度放宽观察与出手空间。")
+ else:
+ controls["notes"].append("最近样本无明显单边倾向,仅做轻微校正。")
+
+ return controls
+
+
async def _generate_ai_iteration(rule_report: dict, rows: list[dict]) -> str:
from app.llm.client import chat_completion
diff --git a/backend/app/llm/strategy_selector.py b/backend/app/llm/strategy_selector.py
index 721fd01c..c91ff944 100644
--- a/backend/app/llm/strategy_selector.py
+++ b/backend/app/llm/strategy_selector.py
@@ -121,6 +121,7 @@ async def select_strategy_profile(
intraday: bool,
) -> StrategyProfile:
profile = _select_rule_profile(market_temp, hot_sectors, intraday)
+ profile = await _apply_strategy_feedback(profile)
if settings.deepseek_api_key:
llm_profile = await _select_llm_profile(market_temp, hot_sectors, intraday, profile)
@@ -151,6 +152,41 @@ def _select_rule_profile(
return get_strategy_profile_by_id("defensive_watch")
+async def _apply_strategy_feedback(profile: StrategyProfile) -> StrategyProfile:
+ from app.llm.strategy_iteration import build_strategy_feedback_controls
+
+ try:
+ controls = await build_strategy_feedback_controls(limit=50)
+ except Exception as e:
+ logger.debug(f"策略反馈控制生成失败: {e}")
+ return profile
+
+ if not controls.get("enabled"):
+ return profile
+
+ updated = profile.model_copy(deep=True)
+
+ if controls.get("force_defensive"):
+ updated.allow_trading = False
+ updated.actionable_limit = 0
+ updated.watch_limit = min(updated.watch_limit, 3)
+ updated.max_position_pct = min(updated.max_position_pct, 10)
+ updated.market_stance = "防守观察"
+
+ updated.buy_threshold = max(updated.min_score, min(updated.buy_threshold + int(controls.get("buy_threshold_delta") or 0), 80))
+ updated.max_position_pct = max(0, min(updated.max_position_pct + int(controls.get("max_position_pct_delta") or 0), 40))
+ updated.actionable_limit = max(0, min(updated.actionable_limit + int(controls.get("actionable_limit_delta") or 0), settings.actionable_limit))
+ updated.watch_limit = max(1, min(updated.watch_limit + int(controls.get("watch_limit_delta") or 0), settings.watch_limit))
+
+ notes = controls.get("notes") or []
+ if notes:
+ updated.notes.extend(notes[:2])
+ updated.decision_note = notes[0]
+
+ updated.generated_by = f"{updated.generated_by}+feedback"
+ return updated
+
+
async def _select_llm_profile(
market_temp: MarketTemperature | None,
hot_sectors: list[SectorInfo],
diff --git a/frontend/.next/server/app-paths-manifest.json b/frontend/.next/server/app-paths-manifest.json
index 55fc5abc..1f6f5ce8 100644
--- a/frontend/.next/server/app-paths-manifest.json
+++ b/frontend/.next/server/app-paths-manifest.json
@@ -1,5 +1,5 @@
{
+ "/(auth)/chat/page": "app/(auth)/chat/page.js",
"/(auth)/stock/[code]/page": "app/(auth)/stock/[code]/page.js",
- "/(public)/page": "app/(public)/page.js",
- "/(auth)/chat/page": "app/(auth)/chat/page.js"
+ "/(public)/page": "app/(public)/page.js"
}
\ No newline at end of file
diff --git a/frontend/src/app/(auth)/dashboard/page.tsx b/frontend/src/app/(auth)/dashboard/page.tsx
index 4b39885b..e70ce218 100644
--- a/frontend/src/app/(auth)/dashboard/page.tsx
+++ b/frontend/src/app/(auth)/dashboard/page.tsx
@@ -40,21 +40,17 @@ export default function DashboardPage() {
const loadData = useCallback(async () => {
try {
- const [latestResult, sectorResult, statusResult, overviewResult, boardResult, opsResult] = await Promise.allSettled([
+ const [latestResult, sectorResult, statusResult, overviewResult] = await Promise.allSettled([
fetchAPI
只保留少量名字方便回看,不参与今天的主决策。
-只保留少量名字方便回看,不参与今天的主决策。