1
This commit is contained in:
parent
a05ccfd1b4
commit
4dcacc5650
Binary file not shown.
@ -8,6 +8,7 @@ import logging
|
||||
import json
|
||||
import asyncio
|
||||
import traceback
|
||||
from functools import partial
|
||||
from datetime import datetime, timedelta
|
||||
from app.engine.screener import run_screening
|
||||
from app.data.models import Recommendation, MarketTemperature, SectorInfo
|
||||
@ -21,6 +22,13 @@ _scan_lock = asyncio.Lock()
|
||||
_scan_running = False
|
||||
|
||||
|
||||
async def _run_async_in_worker(async_fn, *args, **kwargs):
|
||||
"""在独立工作线程中运行重负载异步任务,避免阻塞主事件循环。"""
|
||||
|
||||
runner = partial(asyncio.run, async_fn(*args, **kwargs))
|
||||
return await asyncio.to_thread(runner)
|
||||
|
||||
|
||||
def _has_valid_market_breadth(market_temp: MarketTemperature | None) -> bool:
|
||||
if not market_temp:
|
||||
return False
|
||||
@ -38,7 +46,9 @@ async def refresh_recommendations(trade_date: str = None, scan_session: str = "m
|
||||
async with _scan_lock:
|
||||
_scan_running = True
|
||||
try:
|
||||
result = await run_screening(trade_date)
|
||||
# run_screening 内部混合了大量同步行情请求和 pandas 计算,
|
||||
# 若直接在主事件循环执行,会导致页面读接口和 WebSocket 被拖住。
|
||||
result = await _run_async_in_worker(run_screening, trade_date)
|
||||
|
||||
# 给每条推荐添加 scan_session
|
||||
for rec in result.get("recommendations", []):
|
||||
@ -62,7 +72,7 @@ async def _update_tracking():
|
||||
from sqlalchemy import text
|
||||
from app.data.tushare_client import tushare_client
|
||||
|
||||
trade_date = tushare_client.get_latest_trade_date()
|
||||
trade_date = await asyncio.to_thread(tushare_client.get_latest_trade_date)
|
||||
|
||||
async with get_db() as db:
|
||||
# 查找所有活跃的推荐(有 entry_price 且未被标记为 closed)
|
||||
@ -86,7 +96,7 @@ async def _update_tracking():
|
||||
|
||||
# 获取这些股票的今日收盘价
|
||||
codes = [r[1] for r in rows]
|
||||
daily_all = tushare_client.get_daily_all(trade_date)
|
||||
daily_all = await asyncio.to_thread(tushare_client.get_daily_all, trade_date)
|
||||
price_map = {}
|
||||
if not daily_all.empty:
|
||||
for _, row in daily_all.iterrows():
|
||||
@ -100,7 +110,8 @@ async def _update_tracking():
|
||||
if current_price is None or entry_price is None or entry_price <= 0:
|
||||
continue
|
||||
|
||||
track_metrics = _calculate_tracking_metrics(
|
||||
track_metrics = await asyncio.to_thread(
|
||||
_calculate_tracking_metrics,
|
||||
ts_code=ts_code,
|
||||
entry_price=float(entry_price),
|
||||
current_price=float(current_price),
|
||||
|
||||
Binary file not shown.
@ -35,6 +35,31 @@
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/(auth)/settings/page.js"
|
||||
],
|
||||
"/(auth)/recommendations/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/(auth)/recommendations/page.js"
|
||||
],
|
||||
"/(auth)/strategy/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/(auth)/strategy/page.js"
|
||||
],
|
||||
"/(auth)/sectors/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/(auth)/sectors/page.js"
|
||||
],
|
||||
"/(auth)/stock/[code]/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/(auth)/stock/[code]/page.js"
|
||||
],
|
||||
"/_not-found/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/_not-found/page.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,9 @@
|
||||
"polyfillFiles": [
|
||||
"static/chunks/polyfills.js"
|
||||
],
|
||||
"devFiles": [],
|
||||
"devFiles": [
|
||||
"static/chunks/react-refresh.js"
|
||||
],
|
||||
"ampDevFiles": [],
|
||||
"lowPriorityFiles": [
|
||||
"static/development/_buildManifest.js",
|
||||
@ -13,7 +15,16 @@
|
||||
"static/chunks/main-app.js"
|
||||
],
|
||||
"pages": {
|
||||
"/_app": []
|
||||
"/_app": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main.js",
|
||||
"static/chunks/pages/_app.js"
|
||||
],
|
||||
"/_error": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main.js",
|
||||
"static/chunks/pages/_error.js"
|
||||
]
|
||||
},
|
||||
"ampFirstPages": []
|
||||
}
|
||||
@ -1 +1,20 @@
|
||||
{}
|
||||
{
|
||||
"app/(auth)/sectors/page.tsx -> echarts": {
|
||||
"id": "app/(auth)/sectors/page.tsx -> echarts",
|
||||
"files": [
|
||||
"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js"
|
||||
]
|
||||
},
|
||||
"components/capital-flow.tsx -> echarts": {
|
||||
"id": "components/capital-flow.tsx -> echarts",
|
||||
"files": [
|
||||
"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js"
|
||||
]
|
||||
},
|
||||
"components/kline-chart.tsx -> echarts": {
|
||||
"id": "components/kline-chart.tsx -> echarts",
|
||||
"files": [
|
||||
"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
{
|
||||
"/(auth)/settings/page": "app/(auth)/settings/page.js",
|
||||
"/_not-found/page": "app/_not-found/page.js",
|
||||
"/(auth)/dashboard/page": "app/(auth)/dashboard/page.js",
|
||||
"/(public)/page": "app/(public)/page.js",
|
||||
"/(public)/login/page": "app/(public)/login/page.js"
|
||||
"/(auth)/recommendations/page": "app/(auth)/recommendations/page.js",
|
||||
"/(auth)/stock/[code]/page": "app/(auth)/stock/[code]/page.js",
|
||||
"/(auth)/strategy/page": "app/(auth)/strategy/page.js",
|
||||
"/(auth)/sectors/page": "app/(auth)/sectors/page.js",
|
||||
"/(public)/page": "app/(public)/page.js"
|
||||
}
|
||||
@ -2,7 +2,9 @@ self.__BUILD_MANIFEST = {
|
||||
"polyfillFiles": [
|
||||
"static/chunks/polyfills.js"
|
||||
],
|
||||
"devFiles": [],
|
||||
"devFiles": [
|
||||
"static/chunks/react-refresh.js"
|
||||
],
|
||||
"ampDevFiles": [],
|
||||
"lowPriorityFiles": [],
|
||||
"rootMainFiles": [
|
||||
@ -10,7 +12,16 @@ self.__BUILD_MANIFEST = {
|
||||
"static/chunks/main-app.js"
|
||||
],
|
||||
"pages": {
|
||||
"/_app": []
|
||||
"/_app": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main.js",
|
||||
"static/chunks/pages/_app.js"
|
||||
],
|
||||
"/_error": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main.js",
|
||||
"static/chunks/pages/_error.js"
|
||||
]
|
||||
},
|
||||
"ampFirstPages": []
|
||||
};
|
||||
|
||||
@ -1 +1 @@
|
||||
self.__REACT_LOADABLE_MANIFEST="{}"
|
||||
self.__REACT_LOADABLE_MANIFEST="{\"app/(auth)/sectors/page.tsx -> echarts\":{\"id\":\"app/(auth)/sectors/page.tsx -> echarts\",\"files\":[\"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js\"]},\"components/capital-flow.tsx -> echarts\":{\"id\":\"components/capital-flow.tsx -> echarts\",\"files\":[\"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js\"]},\"components/kline-chart.tsx -> echarts\":{\"id\":\"components/kline-chart.tsx -> echarts\",\"files\":[\"static/chunks/_app-pages-browser_node_modules_echarts_index_js.js\"]}}"
|
||||
@ -1 +1,5 @@
|
||||
{}
|
||||
{
|
||||
"/_error": "pages/_error.js",
|
||||
"/_app": "pages/_app.js",
|
||||
"/_document": "pages/_document.js"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -42,20 +42,29 @@ export default function DashboardPage() {
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
const [latest, sectorData, status, overview, board, ops] = await Promise.all([
|
||||
const [latestResult, sectorResult, statusResult, overviewResult, boardResult, opsResult] = await Promise.allSettled([
|
||||
fetchAPI<LatestResult>("/api/recommendations/latest"),
|
||||
fetchAPI<SectorData[]>("/api/sectors/hot?limit=8"),
|
||||
fetchAPI<ScanStatus>("/api/recommendations/status"),
|
||||
fetchAPI<IndexOverview[]>("/api/market/overview").catch(() => []),
|
||||
fetchAPI<StrategyBoard>("/api/market/strategy-board").catch(() => null),
|
||||
fetchAPI<OpsStatusResponse>("/api/market/ops-status").catch(() => null),
|
||||
fetchAPI<IndexOverview[]>("/api/market/overview"),
|
||||
fetchAPI<StrategyBoard>("/api/market/strategy-board"),
|
||||
fetchAPI<OpsStatusResponse>("/api/market/ops-status"),
|
||||
]);
|
||||
const realtimeTemp = await fetchAPI<MarketTemperatureData>("/api/market/temperature").catch(() => latest.market_temperature);
|
||||
|
||||
const latest = latestResult.status === "fulfilled" ? latestResult.value : null;
|
||||
const sectorData = sectorResult.status === "fulfilled" ? sectorResult.value : [];
|
||||
const status = statusResult.status === "fulfilled" ? statusResult.value : null;
|
||||
const overview = overviewResult.status === "fulfilled" ? overviewResult.value : [];
|
||||
const board = boardResult.status === "fulfilled" ? boardResult.value : null;
|
||||
const ops = opsResult.status === "fulfilled" ? opsResult.value : null;
|
||||
|
||||
if (latest) {
|
||||
setData(latest);
|
||||
const realtimeTemp = await fetchAPI<MarketTemperatureData>("/api/market/temperature").catch(() => latest.market_temperature);
|
||||
setMarketTemperature(realtimeTemp ?? latest.market_temperature ?? null);
|
||||
}
|
||||
setSectors(sectorData);
|
||||
setScanStatus(status);
|
||||
if (status) setScanStatus(status);
|
||||
setIndices(overview);
|
||||
setStrategyBoard(board);
|
||||
setOpsStatus(ops);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user