日K加载提速:adj_factor 覆盖索引 + 冷路径并发 + 热路径进程内缓存直返

- adj_factor 覆盖索引 (ts_code,trade_date) INCLUDE (adj_factor):根治堆碎片化
  (单股 6516 行散 6516 块,满载时位图堆扫 1.5s+),Index Only Scan ~2ms;
  因子查询全部改 2 列投影,lag() 窗口只取变点(6516→32 行)
- preview 冷路径 2 会话 2 波并发,信息卡合并为 LEFT JOIN LATERAL 一条
- 鉴权会话 60s 进程内缓存(登出/全端登出即时失效),全站请求省 ~80ms
- pvj/chipsj/stocksj/facetsj 存 model_dump_json 原串直返(与 response_model
  字节一致),热路径 230-2190ms → 1-2ms;get_version 本地缓存+bump 即时可见
- 连接池 10+20;smoke_test 适配鉴权缓存
This commit is contained in:
2026-09-02 16:52:28 +08:00
parent bec4e8149c
commit 2f9c8bee2b
10 changed files with 411 additions and 494 deletions

View File

@@ -0,0 +1,49 @@
"""adj_factor 覆盖索引:因子查询免堆访问(碎片免疫)
Revision ID: 20260902_01
Revises: 20260815_04
Create Date: 2026-09-02
"""
from typing import Sequence, Union
from alembic import op
revision: str = "20260902_01"
down_revision: Union[str, Sequence[str], None] = "20260815_04"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# CONCURRENTLY / VACUUM 不能在事务内执行autocommit_block 内每条语句独立提交
with op.get_context().autocommit_block():
op.create_index(
"ix_adj_code_date_cover",
"adj_factor",
["ts_code", "trade_date"],
postgresql_concurrently=True,
postgresql_include=["adj_factor"],
)
# ts_code 单列索引被覆盖索引取代(同前缀),删掉省写放大
op.drop_index(
"ix_adj_factor_ts_code",
table_name="adj_factor",
postgresql_concurrently=True,
)
# 刷可见性映射Index Only Scan 的前提)+ 更新统计信息低峰执行3.2GB 表数十秒)
op.execute("VACUUM (ANALYZE) adj_factor")
def downgrade() -> None:
with op.get_context().autocommit_block():
op.create_index(
"ix_adj_factor_ts_code",
"adj_factor",
["ts_code"],
postgresql_concurrently=True,
)
op.drop_index(
"ix_adj_code_date_cover",
table_name="adj_factor",
postgresql_concurrently=True,
)

View File

@@ -18,7 +18,7 @@ import json
from datetime import datetime
import pandas as pd
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile
from sqlalchemy import delete, func, select, text
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.sql.elements import TextClause
@@ -33,7 +33,7 @@ from .data import fetcher, repository, tushare_provider
from .data.aggregation import bars_per_year, resample_bars
from .data.market_overview import MarketOverviewError, fetch_overview
from .data.symbols import plain_code
from .db import get_session
from .db import async_session, get_session
from .domain import Bar
from . import indicators as ind
from .trades import parse_statement
@@ -41,7 +41,6 @@ from .models import (
AdjFactor,
BacktestRun,
Candle,
DailySnapshot,
ScreenerQuery,
StockBasic,
UserPreference,
@@ -89,6 +88,25 @@ from .screener.llm import ScreenerError, parse_conditions, parse_event_spec
router = APIRouter(prefix="/api", dependencies=[Depends(require_user)])
def _raw_json(resp) -> str:
"""pydantic-coreRust序列化与 response_model 直返时的字节完全一致(紧凑分隔符、
非 ASCII 直出、浮点小数形式),且比 stdlib json.dumps 快。大响应preview ~250KB
命中缓存时直接 Response 原样返回,跳过校验/再序列化。"""
return resp.model_dump_json()
async def _cached_json_response(key: str) -> Response | None:
"""两级缓存读(进程内 → Redis命中返回可直接吐给客户端的 Response。
存的均为序列化好的 JSON 字符串Redis 侧 json.loads 后仍是 strRedis 命中顺手晋级本地。"""
raw = cache.local_get(key)
if raw is None:
raw = await cache.cache_get(key)
if not isinstance(raw, str):
return None
cache.local_set(key, raw, ttl=120)
return Response(content=raw, media_type="application/json")
def _series_to_jsonable(s: pd.Series) -> list[float | None]:
"""NaN -> Nonelightweight-charts 的 whitespace data跳过指标预热期"""
out: list[float | None] = []
@@ -114,6 +132,40 @@ _ADJUST_MODES = ("bfq", "qfq", "hfq")
# MA 全量集合(前端已改为本地计算 MA后端始终返回此集合以保证缓存一致
_FULL_MA_SET = (5, 10, 20, 30, 60, 120, 250)
# 信息卡一条 SQL 拿全stock_basic 基本信息 + 「优先与行情同日、缺则最新日」的 daily_snapshot
# LATERAL 单条替换原两条查询语义不变target 为 NULL 时全按最新日兜底)
_INFO_SQL = text(
"""
SELECT sb.ts_code, sb.symbol, sb.name, sb.industry, sb.area, sb.market, sb.list_date,
ds.turnover_rate, ds.pe_ttm, ds.pb, ds.total_mv, ds.circ_mv
FROM stock_basic sb
LEFT JOIN LATERAL (
SELECT turnover_rate, pe_ttm, pb, total_mv, circ_mv
FROM daily_snapshot
WHERE ts_code = sb.ts_code
ORDER BY (trade_date = cast(:target AS timestamp)) DESC, trade_date DESC
LIMIT 1
) ds ON true
WHERE sb.ts_code = :code
"""
)
# 复权因子是阶梯函数(除权日之间不变):只取「变化点」行,把每符号 ~7000 行日级因子压到
# 几十行600118 仅 32 行传输量再降两个数量级lag 窗口在覆盖索引上走 Index Only Scan。
# upto 传全局最新交易日(非分页)或 end 日期(分页);窗口首行 prev 为 NULL 恒被保留(窗口基线因子)。
_FACTOR_STEP_SQL = text(
"""
SELECT trade_date, adj_factor FROM (
SELECT trade_date, adj_factor,
lag(adj_factor) OVER (ORDER BY trade_date) AS prev
FROM adj_factor
WHERE ts_code = :code AND trade_date <= :upto
) t
WHERE adj_factor IS DISTINCT FROM prev
ORDER BY trade_date
"""
)
def _adjust_bars(bars: list[Bar], factors, from_mode: str, to_mode: str) -> list[Bar]:
"""按复权因子把 K 线从 from_mode 换算到 to_modebfq/qfq/hfq
@@ -121,7 +173,7 @@ def _adjust_bars(bars: list[Bar], factors, from_mode: str, to_mode: str) -> list
相对不复权的乘数bfq=1qfq=f(t)/f(latest)hfq=f(t)。
因子缺失的日期向前沿用最近因子(因子是阶梯函数,除权日之间不变)。
"""
fd = sorted((f.trade_date.date(), float(f.adj_factor)) for f in factors)
fd = sorted((f[0].date(), float(f[1])) for f in factors)
fdates = [d for d, _ in fd]
f_latest = fd[-1][1]
@@ -262,26 +314,27 @@ async def list_stocks(
offset: int = 0,
session: AsyncSession = Depends(get_session),
user=Depends(require_user),
) -> StockListResponse:
) -> Response:
"""全市场股票列表stock_basic 基本信息 + candles 最新行情 + daily_snapshot 估值指标
(换手率/PE-TTM/PB/市值,无快照则这些列为空)。
watched_only=true 只看自选(自选有独立的「自选」分类入口,列表不再把自选排最前)。
sort ∈ {symbol,total_mv,circ_mv,pe_ttm,pb,turnover_rate}(白名单,其他值回落 symbol
order ∈ asc/desc快照列排序时缺失值无快照/亏损无 PE恒排末尾。
Redis 缓存:按「用户自选版本 + 查询参数(含排序)」缓存整页(含 total自选增删即时失效"""
缓存:按「用户自选版本 + 查询参数(含排序)」缓存整页(含 total自选增删即时失效
与 preview 同款序列化 JSON 直返j 前缀),命中跳过 pydantic 校验/序列化。"""
search = search.strip()
sort = sort if sort in _STOCKS_SORTS else "symbol"
order = "desc" if order.lower() == "desc" else "asc"
limit = max(1, min(limit, 500))
offset = max(0, offset)
key = (
f"stocks:u{user.id}"
f"stocksj:u{user.id}"
f":v{await cache.get_version(f'watchlist:{user.id}')}"
f":{cache.digest(search, market, industry, area, watched_only, sort, order, limit, offset)}"
)
cached = await cache.cache_get(key)
cached = await _cached_json_response(key)
if cached is not None:
return StockListResponse(**cached)
return cached
params = {
"search": search,
"psearch": f"%{search}%",
@@ -296,16 +349,18 @@ async def list_stocks(
total = (await session.execute(_STOCKS_COUNT_SQL, params)).scalar_one()
rows = (await session.execute(_stocks_sql(sort, order), params)).mappings().all()
resp = StockListResponse(total=total, items=[StockListItemOut(**r) for r in rows])
await cache.cache_set(key, resp.model_dump(mode="json"), settings.stocks_cache_ttl)
return resp
raw = _raw_json(resp)
cache.local_set(key, raw, ttl=min(120, settings.stocks_cache_ttl))
asyncio.create_task(cache.cache_set(key, raw, ttl=settings.stocks_cache_ttl))
return Response(content=raw, media_type="application/json")
@router.get("/stocks/facets", response_model=StockFacetsResponse)
async def stock_facets(session: AsyncSession = Depends(get_session)) -> StockFacetsResponse:
async def stock_facets(session: AsyncSession = Depends(get_session)) -> Response:
"""看股页筛选项:行业 / 地域含数量按数量降序。stock_basic 很少变,长缓存。"""
cached = await cache.cache_get("facets:stocks")
cached = await _cached_json_response("facetsj:stocks")
if cached is not None:
return StockFacetsResponse(**cached)
return cached
industries = (
await session.execute(text("""
SELECT industry AS name, count(*) AS n FROM stock_basic
@@ -324,8 +379,10 @@ async def stock_facets(session: AsyncSession = Depends(get_session)) -> StockFac
industries=[FacetItemOut(name=r["name"], count=r["n"]) for r in industries],
areas=[FacetItemOut(name=r["name"], count=r["n"]) for r in areas],
)
await cache.cache_set("facets:stocks", resp.model_dump(mode="json"), settings.facets_cache_ttl)
return resp
raw = _raw_json(resp)
cache.local_set("facetsj:stocks", raw, ttl=min(120, settings.facets_cache_ttl))
asyncio.create_task(cache.cache_set("facetsj:stocks", raw, ttl=settings.facets_cache_ttl))
return Response(content=raw, media_type="application/json")
@router.get("/market/overview", response_model=MarketOverviewResponse)
@@ -788,7 +845,7 @@ async def screener_preview(
ts_code: str, limit: int = 500, adjust: str = "qfq", timeframe: str = "1d", mas: str = "5,10,20,60",
zx: str = "10,20,30,60", end: str | None = None,
session: AsyncSession = Depends(get_session),
) -> PreviewResponse:
) -> Response:
"""个股详情预览日线candles 不复权底座 + adj_factor 本地换算 bfq/qfq/hfq
未缓存自动拉取,落后全市场最新交易日则强制刷新)+ 全套指标 + 最新截面信息卡。
timeframe 聚合到周/月/年先复权再聚合mas 指定主图 MA 周期(逗号分隔)。
@@ -819,73 +876,92 @@ async def screener_preview(
raise HTTPException(status_code=400, detail="end 格式应为 YYYY-MM-DD")
symbol = plain_code(ts_code)
# --- Redis 读缓存历史窗口end 翻页)只增不改,最新窗口每日由全市场同步推进;
# 键含 ver:candles 版本号同步完成后自增旧缓存全部失效TTL 兜底cache.py
# --- 两级读缓存历史窗口end 翻页)只增不改,最新窗口每日由全市场同步推进;
# 键含 ver:candles 版本号同步完成后自增旧缓存全部失效TTL 兜底cache.py
# 存序列化好的 JSON 直返j: 前缀),跳过 json.loads + pydantic 校验/序列化(热路径数百 ms → 个位数)。
# 注ma_periods 不参与缓存键 —— 前端已改为本地计算 MA后端始终返回全量 MA 集合
cache_key = cache.digest(
"preview", ts_code, timeframe, limit, adjust,
end_dt.strftime("%Y-%m-%d") if end_dt else None,
await cache.get_version("candles"),
)
cached = await cache.cache_get(f"pv:{cache_key}")
cached = await _cached_json_response(f"pvj:{cache_key}")
if cached is not None:
return PreviewResponse.model_validate(cached)
return cached
# --- 日线candles(全量不复权底座);未缓存拉取,落后于全市场最新交易日则强制刷新 ---
# fetcher 只做「不复权」增量 upsert底座口径恒为 bfqTDX 全量 + Tushare 增量),
# 复权qfq/hfq读取时按 adj_factor 表本地换算。
# 每次只取「窗口 + 400 根预热」行MA250/MACD EMA 在 400 根内充分收敛),不拉全量:
# 首屏 ~500 根秒开,向左滚动时按 end 参数逐页向前翻。
global_latest = await session.scalar(
select(func.max(Candle.ts)).where(Candle.timeframe == "1d")
)
frame_mult = {"1d": 1, "1w": 6, "1M": 24, "1y": 280}[timeframe]
fetch_n = min(100000, limit * frame_mult + 400)
source = "bfq"
mode = "bfq"
if end_dt is not None:
# 向前翻页:取 end 之前的历史窗口,不触发同步(历史浏览)
rows = await repository.get_candles_before(session, symbol, "1d", before=end_dt, limit=fetch_n)
else:
# 注意取「最新 fetch_n 根」而非最旧get_candles 是 asc+limit取最旧窗口化后首屏会停在过期日期
rows = await repository.get_recent_candles(session, symbol, "1d", limit=fetch_n)
try:
if not rows:
await fetcher.sync_symbol(session, symbol, source="auto")
rows = await repository.get_recent_candles(session, symbol, "1d", limit=fetch_n)
elif global_latest is not None and rows[-1].ts.date() < global_latest.date():
await fetcher.sync_symbol(session, symbol, source="auto", force=True)
rows = await repository.get_recent_candles(session, symbol, "1d", limit=fetch_n)
except Exception: # noqa: BLE001 —— tushare/写库失败时回滚会话(否则毒化后兜底查询 500
await session.rollback()
if not rows:
rows = []
bars = _rows_to_bars(rows)
# 并发约定:注入 session 与 s2 各占一条连接,每次 gather 里每个 session 恰好跑一条查询
# AsyncSession 单连接非并发安全),把 ~6 次串行 DB RTT 折叠成 2 个波次。
async with async_session() as s2:
if end_dt is not None:
# 向前翻页:取 end 之前的历史窗口不触发同步历史浏览max(ts) 用不到
rows = await repository.get_candles_before(session, symbol, "1d", before=end_dt, limit=fetch_n)
global_latest = None
else:
# Wave 1candles 窗口(注入 session+ 全市场最新交易日s2并行
rows, global_latest = await asyncio.gather(
repository.get_recent_candles(session, symbol, "1d", limit=fetch_n),
s2.scalar(select(func.max(Candle.ts)).where(Candle.timeframe == "1d")),
)
try:
if not rows:
await fetcher.sync_symbol(session, symbol, source="auto")
rows = await repository.get_recent_candles(session, symbol, "1d", limit=fetch_n)
elif global_latest is not None and rows[-1].ts.date() < global_latest.date():
await fetcher.sync_symbol(session, symbol, source="auto", force=True)
rows = await repository.get_recent_candles(session, symbol, "1d", limit=fetch_n)
except Exception: # noqa: BLE001 —— tushare/写库失败时回滚会话(否则毒化后兜底查询 500
await session.rollback()
if not rows:
rows = []
bars = _rows_to_bars(rows)
if not bars and end_dt is None:
raise HTTPException(status_code=404, detail=f"无数据: {ts_code}(可先点「同步市场数据」)")
# 信息卡取未聚合的日线最新 bar聚合后 ts 是周期起点,不适用于「最新交易日」)
last_daily = bars[-1] if bars else None
prev_daily = bars[-2] if len(bars) > 1 else None
# 翻页到底end 之前无数据):返回空页 + has_more=False前端停止向前翻页
if not bars and end_dt is None:
raise HTTPException(status_code=404, detail=f"无数据: {ts_code}(可先点「同步市场数据」)")
# 信息卡取未聚合的日线最新 bar聚合后 ts 是周期起点,不适用于「最新交易日」)
last_daily = bars[-1] if bars else None
prev_daily = bars[-2] if len(bars) > 1 else None
# 翻页到底end 之前无数据):返回空页 + has_more=False前端停止向前翻页
# --- 复权换算:请求模式与底座模式不同时按 adj_factor 本地换算(无因子则维持原样) ---
# 只取窗口内因子qfq 归一还需全局最新因子追加到最后一行即可_adjust_bars 取 f_latest=末项)
if adjust != mode and bars:
fq = select(AdjFactor).where(AdjFactor.ts_code == ts_code)
window_end = end_dt if end_dt is not None else bars[-1].ts
if window_end is not None:
fq = fq.where(AdjFactor.trade_date <= window_end)
factors = list((await session.execute(fq.order_by(AdjFactor.trade_date))).scalars().all())
# --- Wave 2复权因子s2覆盖索引 Index Only Scan+ 信息卡(注入 sessionLATERAL 一条)并行 ---
async def _fetch_factors() -> list | None:
if adjust == mode or not bars:
return None
# 只取因子「变化点」行(覆盖索引 Index Only Scan免堆访问——adj_factor 堆碎片化
# 严重bisect 在阶梯函数上取值与日级序列逐字节一致
if end_dt is not None:
# 分页:窗口 ≤ end 的变化点 + 全局最新因子qfq 以最新因子归一)
win = list((await s2.execute(_FACTOR_STEP_SQL, {"code": ts_code, "upto": end_dt})).all())
if win:
latest_f = (await s2.execute(
select(AdjFactor.trade_date, AdjFactor.adj_factor)
.where(AdjFactor.ts_code == ts_code)
.order_by(AdjFactor.trade_date.desc()).limit(1)
)).first()
if latest_f is not None:
win.append(latest_f)
return win or None
# 非分页:上界 global_latest≥ 最新 bar末项变化点即全局最新因子
# 「窗口 ≤ bars[-1].ts + 单独 latest」少一次查询
return list((await s2.execute(
_FACTOR_STEP_SQL, {"code": ts_code, "upto": global_latest}
)).all()) or None
factors, info_row = await asyncio.gather(
_fetch_factors(),
session.execute(_INFO_SQL, {"code": ts_code, "target": last_daily.ts if last_daily else None}),
)
# --- 复权换算:请求模式与底座模式不同时按 adj_factor 本地换算(无因子则维持原样) ---
if factors:
latest_f = (
await session.execute(
select(AdjFactor).where(AdjFactor.ts_code == ts_code)
.order_by(AdjFactor.trade_date.desc()).limit(1)
)
).scalars().first()
if latest_f is not None:
factors.append(latest_f)
bars = _adjust_bars(bars, factors, mode, adjust)
mode = adjust
source = adjust
@@ -927,24 +1003,19 @@ async def screener_preview(
for key in group:
group[key] = group[key][-limit:]
# --- 信息卡stock_basic + candles 最新日线 bar + 与其对齐的快照(避免混用不同交易日 ---
sb = (await session.execute(select(StockBasic).where(StockBasic.ts_code == ts_code))).scalars().first()
ds = None
if last_daily is not None:
# 优先取与行情同日的快照;缺当日快照时退最新(字段可能与行情差日期,罕见)
ds = (
await session.execute(
select(DailySnapshot).where(
DailySnapshot.ts_code == ts_code, DailySnapshot.trade_date == last_daily.ts
)
)
).scalars().first()
if ds is None:
ds = (
await session.execute(
select(DailySnapshot).where(DailySnapshot.ts_code == ts_code).order_by(DailySnapshot.trade_date.desc()).limit(1)
)
).scalars().first()
# --- 信息卡:Wave 2 已并行取回(stock_basic + 与行情同日对齐的快照、缺则最新日,见 _INFO_SQL ---
row = info_row.first()
if row is not None:
m = row._mapping
sb_name, sb_industry, sb_area, sb_market, sb_list_date = (
m["name"], m["industry"], m["area"], m["market"], m["list_date"]
)
ds_turnover, ds_pe, ds_pb, ds_tmv, ds_cmv = (
m["turnover_rate"], m["pe_ttm"], m["pb"], m["total_mv"], m["circ_mv"]
)
else:
sb_name = sb_industry = sb_area = sb_market = sb_list_date = None
ds_turnover = ds_pe = ds_pb = ds_tmv = ds_cmv = None
def _yi(v) -> float | None:
if v is None:
@@ -955,11 +1026,11 @@ async def screener_preview(
info = PreviewInfoOut(
ts_code=ts_code,
symbol=symbol,
name=sb.name if sb else ts_code,
industry=sb.industry if sb else None,
area=sb.area if sb else None,
market=sb.market if sb else None,
list_date=sb.list_date if sb else None,
name=sb_name or ts_code,
industry=sb_industry,
area=sb_area,
market=sb_market,
list_date=sb_list_date,
trade_date=last_daily.ts if last_daily else None,
open=last_daily.open if last_daily else None,
high=last_daily.high if last_daily else None,
@@ -970,11 +1041,11 @@ async def screener_preview(
if last_daily and prev_daily and prev_daily.close else None,
volume_hand=round(last_daily.volume / 100, 0) if last_daily else None, # 股 -> 手
amount_yi=round(last_daily.amount / 1e8, 2) if last_daily and last_daily.amount else None, # 元 -> 亿元
turnover_rate=ds.turnover_rate if ds else None,
pe_ttm=ds.pe_ttm if ds else None,
pb=ds.pb if ds else None,
total_mv=_yi(ds.total_mv) if ds else None,
circ_mv=_yi(ds.circ_mv) if ds else None,
turnover_rate=ds_turnover,
pe_ttm=ds_pe,
pb=ds_pb,
total_mv=_yi(ds_tmv),
circ_mv=_yi(ds_cmv),
)
candles = [
@@ -984,15 +1055,18 @@ async def screener_preview(
]
resp = PreviewResponse(ts_code=ts_code, symbol=symbol, source=source, info=info,
candles=candles, indicators=indicators, has_more=has_more)
await cache.cache_set(f"pv:{cache_key}", resp.model_dump(mode="json"), ttl=600)
return resp
# 只序列化一次本地同步120s+ Redis后台写600s TTL 兜底跨进程/重启)
raw = _raw_json(resp)
cache.local_set(f"pvj:{cache_key}", raw, ttl=120)
asyncio.create_task(cache.cache_set(f"pvj:{cache_key}", raw, ttl=600))
return Response(content=raw, media_type="application/json")
@router.get("/stock/{ts_code}/chips", response_model=ChipsResponse)
async def stock_chips(
ts_code: str, date: str | None = None, adjust: str = "qfq",
session: AsyncSession = Depends(get_session),
) -> ChipsResponse:
) -> Response:
"""个股筹码峰Tushare cyq_chips + cyq_perf数据自 2018 年起)。
date=YYYY-MM-DD 为参考日(日 K 传当日;周/月 K 由前端传周期末):返回
@@ -1008,11 +1082,12 @@ async def stock_chips(
except ValueError:
raise HTTPException(status_code=400, detail="date 格式应为 YYYY-MM-DD")
# 历史截面不可变;键带 candles 版本号adj_factor 随同步更新后旧缓存失效)
# 历史截面不可变;键带 candles 版本号adj_factor 随同步更新后旧缓存失效)
# 同 preview存序列化 JSON 直返j: 前缀),跳过 pydantic 校验/序列化。
cache_key = cache.digest("chips", ts_code, ref or "latest", adjust, await cache.get_version("candles"))
cached = await cache.cache_get(f"chips:{cache_key}")
cached = await _cached_json_response(f"chipsj:{cache_key}")
if cached is not None:
return ChipsResponse.model_validate(cached)
return cached
try:
perf, rows = await asyncio.to_thread(tushare_provider.fetch_chips, ts_code, ref)
@@ -1023,8 +1098,10 @@ async def stock_chips(
ts_code=ts_code, trade_date=None, adjust=adjust,
error="无筹码数据cyq 数据自 2018 年起,或参考日早于数据起点)",
)
await cache.cache_set(f"chips:{cache_key}", resp.model_dump(mode="json"), ttl=3600)
return resp
raw = _raw_json(resp)
cache.local_set(f"chipsj:{cache_key}", raw, ttl=120)
await cache.cache_set(f"chipsj:{cache_key}", raw, ttl=3600)
return Response(content=raw, media_type="application/json")
d = datetime.strptime(str(perf["trade_date"]), "%Y%m%d")
@@ -1032,16 +1109,17 @@ async def stock_chips(
mult = 1.0
if adjust != "bfq":
factors = (await session.execute(
select(AdjFactor).where(AdjFactor.ts_code == ts_code, AdjFactor.trade_date <= d)
select(AdjFactor.trade_date, AdjFactor.adj_factor)
.where(AdjFactor.ts_code == ts_code, AdjFactor.trade_date <= d)
.order_by(AdjFactor.trade_date)
)).scalars().all()
)).all()
if factors:
latest_f = (await session.execute(
select(AdjFactor).where(AdjFactor.ts_code == ts_code)
select(AdjFactor.trade_date, AdjFactor.adj_factor).where(AdjFactor.ts_code == ts_code)
.order_by(AdjFactor.trade_date.desc()).limit(1)
)).scalars().first()
f_at = float(factors[-1].adj_factor) # <=d 的最近因子(因子是阶梯函数)
f_latest = float(latest_f.adj_factor) if latest_f else f_at
)).first()
f_at = float(factors[-1][1]) # <=d 的最近因子(因子是阶梯函数)
f_latest = float(latest_f[1]) if latest_f else f_at
mult = f_at / f_latest if adjust == "qfq" else f_at
def _px(v) -> float | None:
@@ -1059,5 +1137,7 @@ async def stock_chips(
weight_avg=_px(perf.get("weight_avg")),
winner_rate=_px(perf.get("winner_rate")),
)
await cache.cache_set(f"chips:{cache_key}", resp.model_dump(mode="json"), ttl=21600)
return resp
raw = _raw_json(resp)
cache.local_set(f"chipsj:{cache_key}", raw, ttl=120)
await cache.cache_set(f"chipsj:{cache_key}", raw, ttl=21600)
return Response(content=raw, media_type="application/json")

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import hashlib
import secrets
import time
from datetime import datetime, timedelta, timezone
from argon2 import PasswordHasher
@@ -10,7 +11,7 @@ from argon2.exceptions import InvalidHashError, VerificationError
from fastapi import Cookie, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from sqlalchemy.orm import joinedload
from .config import settings
from .db import get_session
@@ -75,7 +76,7 @@ async def get_auth_session(
now = utcnow()
stmt = (
select(AuthSession)
.options(selectinload(AuthSession.user))
.options(joinedload(AuthSession.user))
.where(
AuthSession.token_hash == token_digest(token),
AuthSession.revoked_at.is_(None),
@@ -93,13 +94,44 @@ async def get_auth_session(
return auth_session
# --- 鉴权会话进程内缓存 --------------------------------------------------------
# 原本每个 API 请求都要为鉴权付 ~3 个远程 RTT连接池 pre_ping + 查 AuthSession +
# joinedload User命中后完全跳过 DB。登出/撤销在 auth_api 同进程立即清;
# 其他进程撤销(多 worker 部署)最长 _SESSION_CACHE_TTL 后自然过期。
# 缓存的是脱管 ORM User属性已加载、expire_on_commit=False脱管访问安全
_SESSION_CACHE_TTL = 60.0
_session_cache: dict[str, tuple[float, datetime, User]] = {} # digest -> (mono 到期, 会话到期, user)
_SESSION_CACHE_MAX = 256
def drop_session_cache(digest: str | None = None, user_id: int | None = None) -> None:
"""登出/撤销时清缓存:按 token 或按用户logout-all"""
if digest is not None:
_session_cache.pop(digest, None)
return
if user_id is not None:
for k in [k for k, (_, _, u) in _session_cache.items() if u.id == user_id]:
_session_cache.pop(k)
async def require_user(
stock_session: str | None = Cookie(default=None, alias=settings.auth_cookie_name),
db: AsyncSession = Depends(get_session),
) -> User:
if not stock_session:
raise unauthorized()
d = token_digest(stock_session)
hit = _session_cache.get(d)
if hit is not None:
expires_mono, sess_expires, user = hit
if expires_mono > time.monotonic() and sess_expires > utcnow() and user.is_active:
return user
_session_cache.pop(d, None) # 过期/失效条目顺手清掉
auth_session = await get_auth_session(stock_session, db)
if auth_session is None:
raise unauthorized()
ttl = min(_SESSION_CACHE_TTL, max(1.0, (auth_session.expires_at - utcnow()).total_seconds()))
_session_cache[d] = (time.monotonic() + ttl, auth_session.expires_at, auth_session.user)
while len(_session_cache) > _SESSION_CACHE_MAX:
_session_cache.pop(next(iter(_session_cache)))
return auth_session.user

View File

@@ -8,6 +8,7 @@ from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from .auth import (
drop_session_cache,
get_auth_session,
new_session_token,
require_user,
@@ -131,6 +132,7 @@ async def logout(
.values(revoked_at=utcnow())
)
await db.commit()
drop_session_cache(digest=token_digest(stock_session)) # 同进程立即失效,登出即时生效
clear_session_cookie(response)
response.status_code = status.HTTP_204_NO_CONTENT
response.headers["Cache-Control"] = "no-store"
@@ -149,6 +151,7 @@ async def logout_all(
.values(revoked_at=utcnow())
)
await db.commit()
drop_session_cache(user_id=user.id) # 该用户全部 token 的本地缓存立即失效
clear_session_cookie(response)
response.status_code = status.HTTP_204_NO_CONTENT
response.headers["Cache-Control"] = "no-store"

View File

@@ -6,11 +6,16 @@
旧缓存 key 里带着旧版本号,无需 SCAN 批量删除。
- 只缓存「读多写少、可容忍短暂陈旧」的聚合数据(股票列表、筛选项等);
K线/回测等口径敏感数据不走这里。
- Redis 之前还有一层进程内本地缓存local_get/local_set0 RTT只存已序列化好的
JSON 字符串,命中后接口直接 Response(content=raw) 原样返回,跳过 json.loads +
pydantic 校验/序列化(大响应这两步合计可达数百 ms。本地 TTL 恒 ≤ Redis TTL
多进程部署时本地条目最多比 Redis 多陈旧 120s版本号 bump 在同进程立即生效。
"""
from __future__ import annotations
import hashlib
import json
import time
from typing import Any
import redis.asyncio as aioredis
@@ -48,6 +53,46 @@ def digest(*parts: Any) -> str:
return hashlib.md5(raw.encode()).hexdigest() # noqa: S324
# --- 进程内本地缓存层Redis 之前0 RTT-----------------------------------
# asyncio 单线程dict 读写无锁安全;按插入序淘汰近似 LRU命中不续期足够用
_LOCAL_MAX_BYTES = 64 * 1024 * 1024 # 总字节预算preview 响应 ~250KB留足 1y 大窗口)
_LOCAL_MAX_ENTRIES = 128
_local_store: dict[str, tuple[float, str]] = {} # key -> (expires_monotonic, raw_json)
_local_bytes = 0
def local_get(key: str) -> str | None:
"""命中返回已序列化的 JSON 字符串(调用方直接 Response 原样返回)。"""
global _local_bytes
ent = _local_store.get(key)
if ent is None:
return None
expires, raw = ent
if expires < time.monotonic():
_local_store.pop(key, None)
_local_bytes -= len(raw) + 64
return None
return raw
def local_set(key: str, raw: str, ttl: int) -> None:
"""ttl 为秒;调用方应传 min(本地默认, Redis TTL),保证本地不比 Redis 活得久。"""
global _local_bytes
old = _local_store.pop(key, None)
if old is not None:
_local_bytes -= len(old[1])
_local_store[key] = (time.monotonic() + max(1, min(ttl, 120)), raw)
_local_bytes += len(raw) + 64 # 连同 dict/tuple 开销粗略计入
while _local_store and (len(_local_store) > _LOCAL_MAX_ENTRIES or _local_bytes > _LOCAL_MAX_BYTES):
_local_bytes -= len(_local_store.popitem(last=False)[1][1]) + 64
# --- 版本号本地缓存:热请求连 Redis GET ver:xx 都省掉 ------------------------
# 同进程 bump_version 立即刷新本地;其他进程 bump 后本地最多陈旧 _LOCAL_VERSION_TTL。
_LOCAL_VERSION_TTL = 60.0
_local_versions: dict[str, tuple[int, float]] = {} # name -> (version, fetched_monotonic)
async def cache_get(key: str) -> Any | None:
c = _client()
if c is None:
@@ -72,13 +117,20 @@ async def cache_set(key: str, value: Any, ttl: int) -> None:
async def get_version(name: str) -> int:
"""读版本号(缺省 0。版本号参与缓存 keyINCR 后旧 key 全部失效。"""
"""读版本号(缺省 0。版本号参与缓存 keyINCR 后旧 key 全部失效。
先查本地60s热请求 0 RTT。"""
ent = _local_versions.get(name)
now = time.monotonic()
if ent is not None and ent[1] > now:
return ent[0]
c = _client()
if c is None:
return 0
try:
v = await c.get(f"ver:{name}")
return int(v) if v is not None else 0
val = int(v) if v is not None else 0
_local_versions[name] = (val, now + _LOCAL_VERSION_TTL)
return val
except Exception: # noqa: BLE001
_bail()
return 0
@@ -89,7 +141,9 @@ async def bump_version(name: str) -> None:
if c is None:
return
try:
await c.incr(f"ver:{name}")
v = await c.incr(f"ver:{name}")
# 同进程 bump 立即可见(夜同步跑在本进程时响应缓存零陈旧窗口)
_local_versions[name] = (int(v), time.monotonic() + _LOCAL_VERSION_TTL)
except Exception: # noqa: BLE001
_bail()

View File

@@ -10,9 +10,12 @@ class Base(DeclarativeBase):
# echo=False远程 PG 的空闲连接可能被中间层断开pre_ping + recycle 自动剔除死连接
# pool_size/max_overflowpreview 冷路径每请求开 2 个会话2 波并发Redis 故障时全请求变冷,
# 默认 5+10 不够并发冷请求用(连接池满了只会排队,不会崩)
engine = create_async_engine(
settings.database_url, echo=False, future=True,
pool_pre_ping=True, pool_recycle=1800,
pool_size=10, max_overflow=20,
)
async_session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)

View File

@@ -9,7 +9,7 @@ Candle 表设计与 TimescaleDB hypertable 完全兼容:将来在目标 PG 库
"""
from datetime import date, datetime
from sqlalchemy import BigInteger, Boolean, Date, DateTime, Float, ForeignKey, Integer, String, Text, UniqueConstraint
from sqlalchemy import BigInteger, Boolean, Date, DateTime, Float, ForeignKey, Index, Integer, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .db import Base
@@ -136,11 +136,14 @@ class AdjFactor(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
trade_date: Mapped[datetime] = mapped_column(DateTime, index=True)
ts_code: Mapped[str] = mapped_column(String(12), index=True)
ts_code: Mapped[str] = mapped_column(String(12))
adj_factor: Mapped[float] = mapped_column(Float)
__table_args__ = (
UniqueConstraint("ts_code", "trade_date", name="uq_adj_code_date"),
# 覆盖索引:因子查询只取 (trade_date, adj_factor) 两列时走 Index Only Scan
# 免堆访问adj_factor 堆碎片化严重,见 alembic/versions/20260902_01
Index("ix_adj_code_date_cover", "ts_code", "trade_date", postgresql_include=["adj_factor"]),
)

View File

@@ -12,7 +12,7 @@ from datetime import datetime, timedelta, timezone
import httpx
from sqlalchemy import delete, select
from app.auth import hash_password, token_digest
from app.auth import drop_session_cache, hash_password, token_digest
from app.db import async_session, engine
from app.main import app
from app.models import AuthSession, Candle, User
@@ -87,6 +87,9 @@ async def main() -> None:
).scalar_one()
auth_session.expires_at = datetime.now(timezone.utc) - timedelta(seconds=1)
await db.commit()
# 鉴权会话有 60s 进程内缓存auth.py会掩盖库里的过期态
# 清缓存模拟 TTL 已过,验证 require_user 对过期会话本身的判定
drop_session_cache(digest=token_digest(token))
assert (await client.get("/api/auth/me")).status_code == 401
print("expired session: 401")

View File

@@ -1,379 +1,61 @@
INFO: Will watch for changes in these directories: ['D:\\Project\\stock\\backend']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [40700] using WatchFiles
INFO: Started server process [42044]
INFO: Started server process [17808]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 127.0.0.1:50860 - "GET /api/health HTTP/1.1" 200 OK
INFO: 127.0.0.1:50992 - "GET /api/market/overview HTTP/1.1" 401 Unauthorized
INFO: 127.0.0.1:50994 - "GET /api/market/overview HTTP/1.1" 401 Unauthorized
WARNING: WatchFiles detected changes in 'app\data\market_overview.py'. Reloading...
INFO: 127.0.0.1:51975 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:52011 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:52012 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:52240 - "GET /api/stocks?sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52241 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:52250 - "GET /api/trades?ts_code=000001.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:52256 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:52255 - "GET /api/screener/preview/000001.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52271 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52809 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:52880 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:52890 - "GET /api/screener/sync/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:52982 - "GET /api/screener/queries?limit=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52992 - "DELETE /api/screener/queries/2 HTTP/1.1" 204 No Content
INFO: 127.0.0.1:52998 - "GET /api/screener/queries?limit=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53002 - "DELETE /api/screener/queries/1 HTTP/1.1" 204 No Content
INFO: 127.0.0.1:53005 - "GET /api/screener/queries?limit=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53199 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:53217 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:53203 - "GET /api/screener/sync/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:55376 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:55374 - "GET /api/stocks?sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55381 - "GET /api/trades?ts_code=000001.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:55387 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:55386 - "GET /api/screener/preview/000001.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:55393 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55407 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55429 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55432 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55443 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:55441 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:55442 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:55456 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55462 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56504 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56519 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56542 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56581 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56599 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56609 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56625 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:60121 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:60143 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:60135 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:60142 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:60152 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:60154 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:60153 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:60175 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:60183 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:60412 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:60410 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:60411 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:60421 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:60418 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62422 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62418 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62469 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62430 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62463 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62489 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62486 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62503 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62519 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62527 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62493 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62545 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62552 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62562 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62572 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62589 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62581 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62604 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62619 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62633 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62642 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62513 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62595 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:62614 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53139 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53151 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:53157 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:53156 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53165 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:53163 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:53164 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:53171 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53806 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53819 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:53820 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53821 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:53823 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:53826 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:53824 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:53830 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53846 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53853 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:53860 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:53859 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53862 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:53863 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:53865 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:53869 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53890 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:53892 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:53901 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53903 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:53905 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:53908 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:53911 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:53912 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54001 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54083 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54087 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54090 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54096 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54104 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54099 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54109 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54123 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54130 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54141 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54151 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54163 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54219 - "PUT /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:54227 - "PUT /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:54228 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54209 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54252 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:54250 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:54248 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:54261 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54353 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:54370 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:54364 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:54384 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54682 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54747 - "GET /api/stock/000001.SZ/chips?adjust=qfq HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54990 - "GET /api/stock/000001.SZ/chips?adjust=qfq HTTP/1.1" 404 Not Found
INFO: Will watch for changes in these directories: ['D:\\Project\\stock\\backend']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [48632] using WatchFiles
INFO: Started server process [46460]
INFO: Started server process [47304]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 127.0.0.1:55384 - "GET /api/health HTTP/1.1" 200 OK
INFO: 127.0.0.1:55388 - "GET /api/stock/000001.SZ/chips?adjust=qfq HTTP/1.1" 401 Unauthorized
INFO: 127.0.0.1:55749 - "GET /api/stock/000001.SZ/chips?adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:55760 - "GET /api/stock/000001.SZ/chips?adjust=bfq&date=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56017 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:56029 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:56030 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56031 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:56040 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:56044 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:56041 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:56051 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56054 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56062 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56065 - "GET /api/stock/002648.SZ/chips?date=2026-04-29&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56071 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56075 - "GET /api/stock/002648.SZ/chips?date=2026-03-11&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56081 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56084 - "GET /api/stock/002648.SZ/chips?date=2026-05-08&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56089 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56096 - "GET /api/stock/002648.SZ/chips?date=2026-03-10&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56100 - "GET /api/stock/002648.SZ/chips?date=2025-12-25&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56111 - "GET /api/stock/002648.SZ/chips?date=2025-10-24&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56116 - "GET /api/stock/002648.SZ/chips?date=2025-09-05&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56121 - "GET /api/stock/002648.SZ/chips?date=2025-06-26&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56106 - "GET /api/stock/002648.SZ/chips?date=2025-12-24&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56129 - "GET /api/stock/002648.SZ/chips?date=2025-09-16&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56133 - "GET /api/stock/002648.SZ/chips?date=2025-06-12&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56139 - "GET /api/stock/002648.SZ/chips?date=2025-03-28&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56145 - "GET /api/stock/002648.SZ/chips?date=2024-12-30&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56149 - "GET /api/stock/002648.SZ/chips?date=2024-11-13&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56156 - "GET /api/stock/002648.SZ/chips?date=2024-09-04&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56160 - "GET /api/stock/002648.SZ/chips?date=2024-10-22&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56167 - "GET /api/stock/002648.SZ/chips?date=2025-04-29&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56171 - "GET /api/stock/002648.SZ/chips?date=2025-07-09&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56177 - "GET /api/stock/002648.SZ/chips?date=2025-09-24&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56196 - "GET /api/stock/002648.SZ/chips?date=2025-12-23&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56200 - "GET /api/stock/002648.SZ/chips?date=2026-04-01&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56207 - "GET /api/stock/002648.SZ/chips?date=2026-06-22&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56210 - "GET /api/stock/002648.SZ/chips?date=2026-08-12&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56218 - "GET /api/stock/002648.SZ/chips?date=2026-06-17&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56265 - "GET /api/stock/002648.SZ/chips?date=2026-03-17&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56384 - "GET /api/stock/002648.SZ/chips?date=2026-05-22&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56458 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56501 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56520 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56545 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56564 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:56568 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:56571 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:56577 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56581 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:56584 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:56587 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:56589 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56690 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56693 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56699 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56715 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:56727 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:56731 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:56730 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56733 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:56734 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:56736 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:56739 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56747 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56749 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56754 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56756 - "GET /api/stock/002648.SZ/chips?date=2026-06-11&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56772 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56781 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:56793 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:56795 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:56794 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56805 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:56806 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:56809 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:56811 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56818 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56828 - "GET /api/stock/002648.SZ/chips?date=2026-08-13&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56835 - "GET /api/stock/002648.SZ/chips?date=2026-03-30&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56863 - "GET /api/stock/002648.SZ/chips?date=2026-07-23&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56882 - "GET /api/stocks?sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56887 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:56874 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:56900 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:56898 - "GET /api/trades?ts_code=000006.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:56899 - "GET /api/screener/preview/000006.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:56904 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56907 - "GET /api/stock/000006.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:56916 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56923 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2021-04-07 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56929 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56931 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56940 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56943 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56947 - "GET /api/trades?ts_code=000001.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:56951 - "GET /api/screener/preview/000001.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:56958 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56970 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56979 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56975 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56985 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2021-04-07 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56990 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2017-12-19 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56998 - "GET /api/trades?ts_code=000006.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:57000 - "GET /api/screener/preview/000006.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:57004 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57012 - "GET /api/stocks?sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57017 - "GET /api/stocks?search=%E5%8C%96%E5%AD%A6%E7%BB%99&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57019 - "GET /api/stocks?search=%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57024 - "GET /api/stocks?sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57026 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57037 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:57034 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:57035 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:57039 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57048 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57061 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:57058 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK
INFO: 127.0.0.1:57059 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:57066 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57099 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57103 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2021-04-07 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57131 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57134 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57139 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57143 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57136 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57146 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57151 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57153 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57917 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:57927 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:57931 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:57932 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:57937 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK
INFO: 127.0.0.1:57939 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:57938 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:57944 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58070 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:58075 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:58086 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:58085 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58088 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK
INFO: 127.0.0.1:58091 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:58089 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:58096 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58098 - "GET /api/stock/600118.SH/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58110 - "GET /api/stock/600118.SH/chips?date=2026-03-25&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58114 - "GET /api/stock/600118.SH/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58122 - "GET /api/stock/600118.SH/chips?date=2026-04-28&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58134 - "GET /api/stock/600118.SH/chips?date=2026-06-17&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58127 - "GET /api/stock/600118.SH/chips?date=2026-05-07&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58139 - "GET /api/stock/600118.SH/chips?date=2026-06-30&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58145 - "GET /api/stock/600118.SH/chips?date=2026-08-03&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58150 - "GET /api/stock/600118.SH/chips?date=2026-07-16&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58155 - "GET /api/stock/600118.SH/chips?date=2026-08-13&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58166 - "GET /api/stock/600118.SH/chips?date=2026-07-28&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58161 - "GET /api/stock/600118.SH/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58173 - "GET /api/stock/600118.SH/chips?date=2026-06-12&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58178 - "GET /api/stock/600118.SH/chips?date=2026-05-29&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58186 - "GET /api/stock/600118.SH/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58195 - "GET /api/stock/600118.SH/chips?date=2026-04-28&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58202 - "GET /api/stock/600118.SH/chips?date=2026-06-02&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58207 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58210 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58213 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58226 - "GET /api/stock/600118.SH/chips?date=2026-06-17&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58241 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:58247 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58222 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58249 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58237 - "GET /api/stock/600118.SH/chips?date=2025-08-07&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58240 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:58258 - "GET /api/stock/002648.SZ/chips?date=2026-04-27&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58219 - "GET /api/stock/600118.SH/chips?date=2026-06-15&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58270 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58273 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58277 - "GET /api/stock/002648.SZ/chips?date=2026-06-16&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58282 - "GET /api/stock/002648.SZ/chips?date=2026-03-02&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58288 - "GET /api/stock/002648.SZ/chips?date=2026-04-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58293 - "GET /api/stock/002648.SZ/chips?date=2026-06-01&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58298 - "GET /api/stock/002648.SZ/chips?date=2026-06-16&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58305 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58309 - "GET /api/stock/002648.SZ/chips?date=2026-06-09&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58315 - "GET /api/stock/002648.SZ/chips?date=2026-06-05&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58323 - "GET /api/stock/002648.SZ/chips?date=2026-04-03&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58326 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58329 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58333 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58341 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58346 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58350 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58356 - "GET /api/stock/002648.SZ/chips?date=2026-06-24&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58364 - "GET /api/stock/002648.SZ/chips?date=2026-04-30&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58368 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58371 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58374 - "GET /api/stock/002648.SZ/chips?date=2026-03-06&adjust=qfq HTTP/1.1" 200 OK
INFO: 127.0.0.1:58392 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:58400 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:58410 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:58409 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58414 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:58417 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:58415 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:58422 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58424 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58431 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK
INFO: 127.0.0.1:58432 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:58435 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58441 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58444 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58447 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58450 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58698 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58707 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58709 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58766 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:58767 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:58777 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK
INFO: 127.0.0.1:58769 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: 127.0.0.1:58778 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:58782 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:52358 - "GET /docs HTTP/1.1" 200 OK
INFO: 127.0.0.1:52494 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:52497 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:52508 - "GET /api/stocks/facets HTTP/1.1" 200 OK
INFO: 127.0.0.1:52506 - "GET /api/stocks?watched_only=true&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52531 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:52534 - "GET /api/watchlist HTTP/1.1" 200 OK
INFO: 127.0.0.1:52533 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52537 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52541 - "GET /api/trades?ts_code=000776.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:52542 - "GET /api/screener/preview/000776.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52544 - "GET /api/screener/preview/000776.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52547 - "GET /api/trades?ts_code=000783.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:52548 - "GET /api/screener/preview/000783.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52551 - "GET /api/screener/preview/000783.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52553 - "GET /api/screener/preview/000783.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52555 - "GET /api/screener/preview/000783.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52558 - "GET /api/screener/preview/000783.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52560 - "GET /api/screener/preview/000783.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52565 - "GET /api/screener/preview/000783.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52570 - "GET /api/trades?ts_code=002074.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:52572 - "GET /api/screener/preview/002074.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52574 - "GET /api/screener/preview/002074.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52580 - "GET /api/trades?ts_code=002100.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:52581 - "GET /api/screener/preview/002100.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52583 - "GET /api/screener/preview/002100.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52594 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
INFO: 127.0.0.1:52595 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52597 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52623 - "GET /api/trades?ts_code=600030.SH HTTP/1.1" 200 OK
INFO: 127.0.0.1:52624 - "GET /api/screener/preview/600030.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52628 - "GET /api/screener/preview/600030.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52640 - "GET /api/trades?ts_code=600036.SH HTTP/1.1" 200 OK
INFO: 127.0.0.1:52642 - "GET /api/screener/preview/600036.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
INFO: 127.0.0.1:52644 - "GET /api/screener/preview/600036.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-08-09 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52671 - "GET /api/stocks?market=%E4%B8%BB%E6%9D%BF&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52674 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:52708 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:52712 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:52713 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:52731 - "GET /api/auth/me HTTP/1.1" 200 OK
INFO: 127.0.0.1:52734 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:52733 - "GET /api/preferences HTTP/1.1" 200 OK
INFO: 127.0.0.1:52742 - "GET /api/market/overview HTTP/1.1" 200 OK
INFO: 127.0.0.1:52757 - "GET /api/screener/queries?limit=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52772 - "GET /api/screener/queries?limit=20 HTTP/1.1" 200 OK
INFO: 127.0.0.1:52752 - "GET /api/screener/sync/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:52771 - "POST /api/screener/run HTTP/1.1" 200 OK
INFO: Started server process [764]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:60296 - "GET /docs HTTP/1.1" 200 OK

View File

@@ -1,9 +1,17 @@
$ vite
VITE v6.4.3 ready in 598 ms
VITE v6.4.3 ready in 2144 ms
➜ Local: http://localhost:5173/
 ➜ Network: use --host to expose
00:44:18 [vite] (client) hmr update /src/style.css, /@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue
00:44:23 [vite] (client) hmr update /@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue, /src/style.css
00:44:24 [vite] (client) hmr update /src/style.css, /@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue
15:58:04 [vite] http proxy error: /api/auth/me
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
$ vite
Port 5173 is in use, trying another one...
VITE v6.4.3 ready in 579 ms
➜ Local: http://localhost:5174/
 ➜ Network: use --host to expose