日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:
49
backend/alembic/versions/20260902_01_adj_factor_cover.py
Normal file
49
backend/alembic/versions/20260902_01_adj_factor_cover.py
Normal 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,
|
||||||
|
)
|
||||||
@@ -18,7 +18,7 @@ import json
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import pandas as pd
|
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 import delete, func, select, text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.sql.elements import TextClause
|
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.aggregation import bars_per_year, resample_bars
|
||||||
from .data.market_overview import MarketOverviewError, fetch_overview
|
from .data.market_overview import MarketOverviewError, fetch_overview
|
||||||
from .data.symbols import plain_code
|
from .data.symbols import plain_code
|
||||||
from .db import get_session
|
from .db import async_session, get_session
|
||||||
from .domain import Bar
|
from .domain import Bar
|
||||||
from . import indicators as ind
|
from . import indicators as ind
|
||||||
from .trades import parse_statement
|
from .trades import parse_statement
|
||||||
@@ -41,7 +41,6 @@ from .models import (
|
|||||||
AdjFactor,
|
AdjFactor,
|
||||||
BacktestRun,
|
BacktestRun,
|
||||||
Candle,
|
Candle,
|
||||||
DailySnapshot,
|
|
||||||
ScreenerQuery,
|
ScreenerQuery,
|
||||||
StockBasic,
|
StockBasic,
|
||||||
UserPreference,
|
UserPreference,
|
||||||
@@ -89,6 +88,25 @@ from .screener.llm import ScreenerError, parse_conditions, parse_event_spec
|
|||||||
router = APIRouter(prefix="/api", dependencies=[Depends(require_user)])
|
router = APIRouter(prefix="/api", dependencies=[Depends(require_user)])
|
||||||
|
|
||||||
|
|
||||||
|
def _raw_json(resp) -> str:
|
||||||
|
"""pydantic-core(Rust)序列化:与 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 后仍是 str),Redis 命中顺手晋级本地。"""
|
||||||
|
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]:
|
def _series_to_jsonable(s: pd.Series) -> list[float | None]:
|
||||||
"""NaN -> None(lightweight-charts 的 whitespace data,跳过指标预热期)。"""
|
"""NaN -> None(lightweight-charts 的 whitespace data,跳过指标预热期)。"""
|
||||||
out: list[float | None] = []
|
out: list[float | None] = []
|
||||||
@@ -114,6 +132,40 @@ _ADJUST_MODES = ("bfq", "qfq", "hfq")
|
|||||||
# MA 全量集合(前端已改为本地计算 MA,后端始终返回此集合以保证缓存一致)
|
# MA 全量集合(前端已改为本地计算 MA,后端始终返回此集合以保证缓存一致)
|
||||||
_FULL_MA_SET = (5, 10, 20, 30, 60, 120, 250)
|
_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]:
|
def _adjust_bars(bars: list[Bar], factors, from_mode: str, to_mode: str) -> list[Bar]:
|
||||||
"""按复权因子把 K 线从 from_mode 换算到 to_mode(bfq/qfq/hfq)。
|
"""按复权因子把 K 线从 from_mode 换算到 to_mode(bfq/qfq/hfq)。
|
||||||
@@ -121,7 +173,7 @@ def _adjust_bars(bars: list[Bar], factors, from_mode: str, to_mode: str) -> list
|
|||||||
相对不复权的乘数:bfq=1,qfq=f(t)/f(latest),hfq=f(t)。
|
相对不复权的乘数:bfq=1,qfq=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]
|
fdates = [d for d, _ in fd]
|
||||||
f_latest = fd[-1][1]
|
f_latest = fd[-1][1]
|
||||||
|
|
||||||
@@ -262,26 +314,27 @@ async def list_stocks(
|
|||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
user=Depends(require_user),
|
user=Depends(require_user),
|
||||||
) -> StockListResponse:
|
) -> Response:
|
||||||
"""全市场股票列表:stock_basic 基本信息 + candles 最新行情 + daily_snapshot 估值指标
|
"""全市场股票列表:stock_basic 基本信息 + candles 最新行情 + daily_snapshot 估值指标
|
||||||
(换手率/PE-TTM/PB/市值,无快照则这些列为空)。
|
(换手率/PE-TTM/PB/市值,无快照则这些列为空)。
|
||||||
watched_only=true 只看自选(自选有独立的「自选」分类入口,列表不再把自选排最前)。
|
watched_only=true 只看自选(自选有独立的「自选」分类入口,列表不再把自选排最前)。
|
||||||
sort ∈ {symbol,total_mv,circ_mv,pe_ttm,pb,turnover_rate}(白名单,其他值回落 symbol),
|
sort ∈ {symbol,total_mv,circ_mv,pe_ttm,pb,turnover_rate}(白名单,其他值回落 symbol),
|
||||||
order ∈ asc/desc;快照列排序时缺失值(无快照/亏损无 PE)恒排末尾。
|
order ∈ asc/desc;快照列排序时缺失值(无快照/亏损无 PE)恒排末尾。
|
||||||
Redis 缓存:按「用户自选版本 + 查询参数(含排序)」缓存整页(含 total);自选增删即时失效。"""
|
缓存:按「用户自选版本 + 查询参数(含排序)」缓存整页(含 total);自选增删即时失效;
|
||||||
|
与 preview 同款序列化 JSON 直返(j 前缀),命中跳过 pydantic 校验/序列化。"""
|
||||||
search = search.strip()
|
search = search.strip()
|
||||||
sort = sort if sort in _STOCKS_SORTS else "symbol"
|
sort = sort if sort in _STOCKS_SORTS else "symbol"
|
||||||
order = "desc" if order.lower() == "desc" else "asc"
|
order = "desc" if order.lower() == "desc" else "asc"
|
||||||
limit = max(1, min(limit, 500))
|
limit = max(1, min(limit, 500))
|
||||||
offset = max(0, offset)
|
offset = max(0, offset)
|
||||||
key = (
|
key = (
|
||||||
f"stocks:u{user.id}"
|
f"stocksj:u{user.id}"
|
||||||
f":v{await cache.get_version(f'watchlist:{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)}"
|
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:
|
if cached is not None:
|
||||||
return StockListResponse(**cached)
|
return cached
|
||||||
params = {
|
params = {
|
||||||
"search": search,
|
"search": search,
|
||||||
"psearch": f"%{search}%",
|
"psearch": f"%{search}%",
|
||||||
@@ -296,16 +349,18 @@ async def list_stocks(
|
|||||||
total = (await session.execute(_STOCKS_COUNT_SQL, params)).scalar_one()
|
total = (await session.execute(_STOCKS_COUNT_SQL, params)).scalar_one()
|
||||||
rows = (await session.execute(_stocks_sql(sort, order), params)).mappings().all()
|
rows = (await session.execute(_stocks_sql(sort, order), params)).mappings().all()
|
||||||
resp = StockListResponse(total=total, items=[StockListItemOut(**r) for r in rows])
|
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)
|
raw = _raw_json(resp)
|
||||||
return 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)
|
@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 很少变,长缓存。"""
|
"""看股页筛选项:行业 / 地域(含数量,按数量降序)。stock_basic 很少变,长缓存。"""
|
||||||
cached = await cache.cache_get("facets:stocks")
|
cached = await _cached_json_response("facetsj:stocks")
|
||||||
if cached is not None:
|
if cached is not None:
|
||||||
return StockFacetsResponse(**cached)
|
return cached
|
||||||
industries = (
|
industries = (
|
||||||
await session.execute(text("""
|
await session.execute(text("""
|
||||||
SELECT industry AS name, count(*) AS n FROM stock_basic
|
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],
|
industries=[FacetItemOut(name=r["name"], count=r["n"]) for r in industries],
|
||||||
areas=[FacetItemOut(name=r["name"], count=r["n"]) for r in areas],
|
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)
|
raw = _raw_json(resp)
|
||||||
return 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)
|
@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",
|
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,
|
zx: str = "10,20,30,60", end: str | None = None,
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
) -> PreviewResponse:
|
) -> Response:
|
||||||
"""个股详情预览:日线(candles 不复权底座 + adj_factor 本地换算 bfq/qfq/hfq,
|
"""个股详情预览:日线(candles 不复权底座 + adj_factor 本地换算 bfq/qfq/hfq,
|
||||||
未缓存自动拉取,落后全市场最新交易日则强制刷新)+ 全套指标 + 最新截面信息卡。
|
未缓存自动拉取,落后全市场最新交易日则强制刷新)+ 全套指标 + 最新截面信息卡。
|
||||||
timeframe 聚合到周/月/年(先复权再聚合);mas 指定主图 MA 周期(逗号分隔)。
|
timeframe 聚合到周/月/年(先复权再聚合);mas 指定主图 MA 周期(逗号分隔)。
|
||||||
@@ -819,36 +876,41 @@ async def screener_preview(
|
|||||||
raise HTTPException(status_code=400, detail="end 格式应为 YYYY-MM-DD")
|
raise HTTPException(status_code=400, detail="end 格式应为 YYYY-MM-DD")
|
||||||
symbol = plain_code(ts_code)
|
symbol = plain_code(ts_code)
|
||||||
|
|
||||||
# --- Redis 读缓存:历史窗口(end 翻页)只增不改,最新窗口每日由全市场同步推进;
|
# --- 两级读缓存:历史窗口(end 翻页)只增不改,最新窗口每日由全市场同步推进;
|
||||||
# 键含 ver:candles 版本号(同步完成后自增,旧缓存全部失效),TTL 兜底(cache.py)
|
# 键含 ver:candles 版本号(同步完成后自增,旧缓存全部失效),TTL 兜底(cache.py)。
|
||||||
|
# 存序列化好的 JSON 直返(j: 前缀),跳过 json.loads + pydantic 校验/序列化(热路径数百 ms → 个位数)。
|
||||||
# 注:ma_periods 不参与缓存键 —— 前端已改为本地计算 MA,后端始终返回全量 MA 集合
|
# 注:ma_periods 不参与缓存键 —— 前端已改为本地计算 MA,后端始终返回全量 MA 集合
|
||||||
cache_key = cache.digest(
|
cache_key = cache.digest(
|
||||||
"preview", ts_code, timeframe, limit, adjust,
|
"preview", ts_code, timeframe, limit, adjust,
|
||||||
end_dt.strftime("%Y-%m-%d") if end_dt else None,
|
end_dt.strftime("%Y-%m-%d") if end_dt else None,
|
||||||
await cache.get_version("candles"),
|
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:
|
if cached is not None:
|
||||||
return PreviewResponse.model_validate(cached)
|
return cached
|
||||||
|
|
||||||
# --- 日线:candles(全量不复权底座);未缓存拉取,落后于全市场最新交易日则强制刷新 ---
|
# --- 日线:candles(全量不复权底座);未缓存拉取,落后于全市场最新交易日则强制刷新 ---
|
||||||
# fetcher 只做「不复权」增量 upsert,底座口径恒为 bfq(TDX 全量 + Tushare 增量),
|
# fetcher 只做「不复权」增量 upsert,底座口径恒为 bfq(TDX 全量 + Tushare 增量),
|
||||||
# 复权(qfq/hfq)读取时按 adj_factor 表本地换算。
|
# 复权(qfq/hfq)读取时按 adj_factor 表本地换算。
|
||||||
# 每次只取「窗口 + 400 根预热」行(MA250/MACD EMA 在 400 根内充分收敛),不拉全量:
|
# 每次只取「窗口 + 400 根预热」行(MA250/MACD EMA 在 400 根内充分收敛),不拉全量:
|
||||||
# 首屏 ~500 根秒开,向左滚动时按 end 参数逐页向前翻。
|
# 首屏 ~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]
|
frame_mult = {"1d": 1, "1w": 6, "1M": 24, "1y": 280}[timeframe]
|
||||||
fetch_n = min(100000, limit * frame_mult + 400)
|
fetch_n = min(100000, limit * frame_mult + 400)
|
||||||
source = "bfq"
|
source = "bfq"
|
||||||
mode = "bfq"
|
mode = "bfq"
|
||||||
|
# 并发约定:注入 session 与 s2 各占一条连接,每次 gather 里每个 session 恰好跑一条查询
|
||||||
|
# (AsyncSession 单连接非并发安全),把 ~6 次串行 DB RTT 折叠成 2 个波次。
|
||||||
|
async with async_session() as s2:
|
||||||
if end_dt is not None:
|
if end_dt is not None:
|
||||||
# 向前翻页:取 end 之前的历史窗口,不触发同步(历史浏览)
|
# 向前翻页:取 end 之前的历史窗口,不触发同步(历史浏览);max(ts) 用不到
|
||||||
rows = await repository.get_candles_before(session, symbol, "1d", before=end_dt, limit=fetch_n)
|
rows = await repository.get_candles_before(session, symbol, "1d", before=end_dt, limit=fetch_n)
|
||||||
|
global_latest = None
|
||||||
else:
|
else:
|
||||||
# 注意取「最新 fetch_n 根」而非最旧:get_candles 是 asc+limit(取最旧),窗口化后首屏会停在过期日期
|
# Wave 1:candles 窗口(注入 session)+ 全市场最新交易日(s2)并行
|
||||||
rows = await repository.get_recent_candles(session, symbol, "1d", limit=fetch_n)
|
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:
|
try:
|
||||||
if not rows:
|
if not rows:
|
||||||
await fetcher.sync_symbol(session, symbol, source="auto")
|
await fetcher.sync_symbol(session, symbol, source="auto")
|
||||||
@@ -869,23 +931,37 @@ async def screener_preview(
|
|||||||
prev_daily = bars[-2] if len(bars) > 1 else None
|
prev_daily = bars[-2] if len(bars) > 1 else None
|
||||||
# 翻页到底(end 之前无数据):返回空页 + has_more=False,前端停止向前翻页
|
# 翻页到底(end 之前无数据):返回空页 + has_more=False,前端停止向前翻页
|
||||||
|
|
||||||
# --- 复权换算:请求模式与底座模式不同时按 adj_factor 本地换算(无因子则维持原样) ---
|
# --- Wave 2:复权因子(s2,覆盖索引 Index Only Scan)+ 信息卡(注入 session,LATERAL 一条)并行 ---
|
||||||
# 只取窗口内因子(qfq 归一还需全局最新因子,追加到最后一行即可,_adjust_bars 取 f_latest=末项)
|
async def _fetch_factors() -> list | None:
|
||||||
if adjust != mode and bars:
|
if adjust == mode or not bars:
|
||||||
fq = select(AdjFactor).where(AdjFactor.ts_code == ts_code)
|
return None
|
||||||
window_end = end_dt if end_dt is not None else bars[-1].ts
|
# 只取因子「变化点」行(覆盖索引 Index Only Scan,免堆访问——adj_factor 堆碎片化
|
||||||
if window_end is not None:
|
# 严重);bisect 在阶梯函数上取值与日级序列逐字节一致
|
||||||
fq = fq.where(AdjFactor.trade_date <= window_end)
|
if end_dt is not None:
|
||||||
factors = list((await session.execute(fq.order_by(AdjFactor.trade_date))).scalars().all())
|
# 分页:窗口 ≤ end 的变化点 + 全局最新因子(qfq 以最新因子归一)
|
||||||
if factors:
|
win = list((await s2.execute(_FACTOR_STEP_SQL, {"code": ts_code, "upto": end_dt})).all())
|
||||||
latest_f = (
|
if win:
|
||||||
await session.execute(
|
latest_f = (await s2.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)
|
.order_by(AdjFactor.trade_date.desc()).limit(1)
|
||||||
)
|
)).first()
|
||||||
).scalars().first()
|
|
||||||
if latest_f is not None:
|
if latest_f is not None:
|
||||||
factors.append(latest_f)
|
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:
|
||||||
bars = _adjust_bars(bars, factors, mode, adjust)
|
bars = _adjust_bars(bars, factors, mode, adjust)
|
||||||
mode = adjust
|
mode = adjust
|
||||||
source = adjust
|
source = adjust
|
||||||
@@ -927,24 +1003,19 @@ async def screener_preview(
|
|||||||
for key in group:
|
for key in group:
|
||||||
group[key] = group[key][-limit:]
|
group[key] = group[key][-limit:]
|
||||||
|
|
||||||
# --- 信息卡:stock_basic + candles 最新日线 bar + 与其对齐的快照(避免混用不同交易日) ---
|
# --- 信息卡:Wave 2 已并行取回(stock_basic + 与行情同日对齐的快照、缺则最新日,见 _INFO_SQL) ---
|
||||||
sb = (await session.execute(select(StockBasic).where(StockBasic.ts_code == ts_code))).scalars().first()
|
row = info_row.first()
|
||||||
ds = None
|
if row is not None:
|
||||||
if last_daily is not None:
|
m = row._mapping
|
||||||
# 优先取与行情同日的快照;缺当日快照时退最新(字段可能与行情差日期,罕见)
|
sb_name, sb_industry, sb_area, sb_market, sb_list_date = (
|
||||||
ds = (
|
m["name"], m["industry"], m["area"], m["market"], m["list_date"]
|
||||||
await session.execute(
|
|
||||||
select(DailySnapshot).where(
|
|
||||||
DailySnapshot.ts_code == ts_code, DailySnapshot.trade_date == last_daily.ts
|
|
||||||
)
|
)
|
||||||
|
ds_turnover, ds_pe, ds_pb, ds_tmv, ds_cmv = (
|
||||||
|
m["turnover_rate"], m["pe_ttm"], m["pb"], m["total_mv"], m["circ_mv"]
|
||||||
)
|
)
|
||||||
).scalars().first()
|
else:
|
||||||
if ds is None:
|
sb_name = sb_industry = sb_area = sb_market = sb_list_date = None
|
||||||
ds = (
|
ds_turnover = ds_pe = ds_pb = ds_tmv = ds_cmv = None
|
||||||
await session.execute(
|
|
||||||
select(DailySnapshot).where(DailySnapshot.ts_code == ts_code).order_by(DailySnapshot.trade_date.desc()).limit(1)
|
|
||||||
)
|
|
||||||
).scalars().first()
|
|
||||||
|
|
||||||
def _yi(v) -> float | None:
|
def _yi(v) -> float | None:
|
||||||
if v is None:
|
if v is None:
|
||||||
@@ -955,11 +1026,11 @@ async def screener_preview(
|
|||||||
info = PreviewInfoOut(
|
info = PreviewInfoOut(
|
||||||
ts_code=ts_code,
|
ts_code=ts_code,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
name=sb.name if sb else ts_code,
|
name=sb_name or ts_code,
|
||||||
industry=sb.industry if sb else None,
|
industry=sb_industry,
|
||||||
area=sb.area if sb else None,
|
area=sb_area,
|
||||||
market=sb.market if sb else None,
|
market=sb_market,
|
||||||
list_date=sb.list_date if sb else None,
|
list_date=sb_list_date,
|
||||||
trade_date=last_daily.ts if last_daily else None,
|
trade_date=last_daily.ts if last_daily else None,
|
||||||
open=last_daily.open if last_daily else None,
|
open=last_daily.open if last_daily else None,
|
||||||
high=last_daily.high 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,
|
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, # 股 -> 手
|
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, # 元 -> 亿元
|
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,
|
turnover_rate=ds_turnover,
|
||||||
pe_ttm=ds.pe_ttm if ds else None,
|
pe_ttm=ds_pe,
|
||||||
pb=ds.pb if ds else None,
|
pb=ds_pb,
|
||||||
total_mv=_yi(ds.total_mv) if ds else None,
|
total_mv=_yi(ds_tmv),
|
||||||
circ_mv=_yi(ds.circ_mv) if ds else None,
|
circ_mv=_yi(ds_cmv),
|
||||||
)
|
)
|
||||||
|
|
||||||
candles = [
|
candles = [
|
||||||
@@ -984,15 +1055,18 @@ async def screener_preview(
|
|||||||
]
|
]
|
||||||
resp = PreviewResponse(ts_code=ts_code, symbol=symbol, source=source, info=info,
|
resp = PreviewResponse(ts_code=ts_code, symbol=symbol, source=source, info=info,
|
||||||
candles=candles, indicators=indicators, has_more=has_more)
|
candles=candles, indicators=indicators, has_more=has_more)
|
||||||
await cache.cache_set(f"pv:{cache_key}", resp.model_dump(mode="json"), ttl=600)
|
# 只序列化一次:本地(同步,120s)+ Redis(后台写,600s TTL 兜底跨进程/重启)
|
||||||
return resp
|
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)
|
@router.get("/stock/{ts_code}/chips", response_model=ChipsResponse)
|
||||||
async def stock_chips(
|
async def stock_chips(
|
||||||
ts_code: str, date: str | None = None, adjust: str = "qfq",
|
ts_code: str, date: str | None = None, adjust: str = "qfq",
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
) -> ChipsResponse:
|
) -> Response:
|
||||||
"""个股筹码峰(Tushare cyq_chips + cyq_perf,数据自 2018 年起)。
|
"""个股筹码峰(Tushare cyq_chips + cyq_perf,数据自 2018 年起)。
|
||||||
|
|
||||||
date=YYYY-MM-DD 为参考日(日 K 传当日;周/月 K 由前端传周期末):返回
|
date=YYYY-MM-DD 为参考日(日 K 传当日;周/月 K 由前端传周期末):返回
|
||||||
@@ -1008,11 +1082,12 @@ async def stock_chips(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise HTTPException(status_code=400, detail="date 格式应为 YYYY-MM-DD")
|
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"))
|
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:
|
if cached is not None:
|
||||||
return ChipsResponse.model_validate(cached)
|
return cached
|
||||||
|
|
||||||
try:
|
try:
|
||||||
perf, rows = await asyncio.to_thread(tushare_provider.fetch_chips, ts_code, ref)
|
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,
|
ts_code=ts_code, trade_date=None, adjust=adjust,
|
||||||
error="无筹码数据(cyq 数据自 2018 年起,或参考日早于数据起点)",
|
error="无筹码数据(cyq 数据自 2018 年起,或参考日早于数据起点)",
|
||||||
)
|
)
|
||||||
await cache.cache_set(f"chips:{cache_key}", resp.model_dump(mode="json"), ttl=3600)
|
raw = _raw_json(resp)
|
||||||
return 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")
|
d = datetime.strptime(str(perf["trade_date"]), "%Y%m%d")
|
||||||
|
|
||||||
@@ -1032,16 +1109,17 @@ async def stock_chips(
|
|||||||
mult = 1.0
|
mult = 1.0
|
||||||
if adjust != "bfq":
|
if adjust != "bfq":
|
||||||
factors = (await session.execute(
|
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)
|
.order_by(AdjFactor.trade_date)
|
||||||
)).scalars().all()
|
)).all()
|
||||||
if factors:
|
if factors:
|
||||||
latest_f = (await session.execute(
|
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)
|
.order_by(AdjFactor.trade_date.desc()).limit(1)
|
||||||
)).scalars().first()
|
)).first()
|
||||||
f_at = float(factors[-1].adj_factor) # <=d 的最近因子(因子是阶梯函数)
|
f_at = float(factors[-1][1]) # <=d 的最近因子(因子是阶梯函数)
|
||||||
f_latest = float(latest_f.adj_factor) if latest_f else f_at
|
f_latest = float(latest_f[1]) if latest_f else f_at
|
||||||
mult = f_at / f_latest if adjust == "qfq" else f_at
|
mult = f_at / f_latest if adjust == "qfq" else f_at
|
||||||
|
|
||||||
def _px(v) -> float | None:
|
def _px(v) -> float | None:
|
||||||
@@ -1059,5 +1137,7 @@ async def stock_chips(
|
|||||||
weight_avg=_px(perf.get("weight_avg")),
|
weight_avg=_px(perf.get("weight_avg")),
|
||||||
winner_rate=_px(perf.get("winner_rate")),
|
winner_rate=_px(perf.get("winner_rate")),
|
||||||
)
|
)
|
||||||
await cache.cache_set(f"chips:{cache_key}", resp.model_dump(mode="json"), ttl=21600)
|
raw = _raw_json(resp)
|
||||||
return 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")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import secrets
|
import secrets
|
||||||
|
import time
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from argon2 import PasswordHasher
|
from argon2 import PasswordHasher
|
||||||
@@ -10,7 +11,7 @@ from argon2.exceptions import InvalidHashError, VerificationError
|
|||||||
from fastapi import Cookie, Depends, HTTPException, status
|
from fastapi import Cookie, Depends, HTTPException, status
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
from .db import get_session
|
from .db import get_session
|
||||||
@@ -75,7 +76,7 @@ async def get_auth_session(
|
|||||||
now = utcnow()
|
now = utcnow()
|
||||||
stmt = (
|
stmt = (
|
||||||
select(AuthSession)
|
select(AuthSession)
|
||||||
.options(selectinload(AuthSession.user))
|
.options(joinedload(AuthSession.user))
|
||||||
.where(
|
.where(
|
||||||
AuthSession.token_hash == token_digest(token),
|
AuthSession.token_hash == token_digest(token),
|
||||||
AuthSession.revoked_at.is_(None),
|
AuthSession.revoked_at.is_(None),
|
||||||
@@ -93,13 +94,44 @@ async def get_auth_session(
|
|||||||
return 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(
|
async def require_user(
|
||||||
stock_session: str | None = Cookie(default=None, alias=settings.auth_cookie_name),
|
stock_session: str | None = Cookie(default=None, alias=settings.auth_cookie_name),
|
||||||
db: AsyncSession = Depends(get_session),
|
db: AsyncSession = Depends(get_session),
|
||||||
) -> User:
|
) -> User:
|
||||||
if not stock_session:
|
if not stock_session:
|
||||||
raise unauthorized()
|
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)
|
auth_session = await get_auth_session(stock_session, db)
|
||||||
if auth_session is None:
|
if auth_session is None:
|
||||||
raise unauthorized()
|
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
|
return auth_session.user
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from sqlalchemy import delete, select, update
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from .auth import (
|
from .auth import (
|
||||||
|
drop_session_cache,
|
||||||
get_auth_session,
|
get_auth_session,
|
||||||
new_session_token,
|
new_session_token,
|
||||||
require_user,
|
require_user,
|
||||||
@@ -131,6 +132,7 @@ async def logout(
|
|||||||
.values(revoked_at=utcnow())
|
.values(revoked_at=utcnow())
|
||||||
)
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
drop_session_cache(digest=token_digest(stock_session)) # 同进程立即失效,登出即时生效
|
||||||
clear_session_cookie(response)
|
clear_session_cookie(response)
|
||||||
response.status_code = status.HTTP_204_NO_CONTENT
|
response.status_code = status.HTTP_204_NO_CONTENT
|
||||||
response.headers["Cache-Control"] = "no-store"
|
response.headers["Cache-Control"] = "no-store"
|
||||||
@@ -149,6 +151,7 @@ async def logout_all(
|
|||||||
.values(revoked_at=utcnow())
|
.values(revoked_at=utcnow())
|
||||||
)
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
drop_session_cache(user_id=user.id) # 该用户全部 token 的本地缓存立即失效
|
||||||
clear_session_cookie(response)
|
clear_session_cookie(response)
|
||||||
response.status_code = status.HTTP_204_NO_CONTENT
|
response.status_code = status.HTTP_204_NO_CONTENT
|
||||||
response.headers["Cache-Control"] = "no-store"
|
response.headers["Cache-Control"] = "no-store"
|
||||||
|
|||||||
@@ -6,11 +6,16 @@
|
|||||||
旧缓存 key 里带着旧版本号,无需 SCAN 批量删除。
|
旧缓存 key 里带着旧版本号,无需 SCAN 批量删除。
|
||||||
- 只缓存「读多写少、可容忍短暂陈旧」的聚合数据(股票列表、筛选项等);
|
- 只缓存「读多写少、可容忍短暂陈旧」的聚合数据(股票列表、筛选项等);
|
||||||
K线/回测等口径敏感数据不走这里。
|
K线/回测等口径敏感数据不走这里。
|
||||||
|
- Redis 之前还有一层进程内本地缓存(local_get/local_set,0 RTT):只存已序列化好的
|
||||||
|
JSON 字符串,命中后接口直接 Response(content=raw) 原样返回,跳过 json.loads +
|
||||||
|
pydantic 校验/序列化(大响应这两步合计可达数百 ms)。本地 TTL 恒 ≤ Redis TTL,
|
||||||
|
多进程部署时本地条目最多比 Redis 多陈旧 120s;版本号 bump 在同进程立即生效。
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import redis.asyncio as aioredis
|
import redis.asyncio as aioredis
|
||||||
@@ -48,6 +53,46 @@ def digest(*parts: Any) -> str:
|
|||||||
return hashlib.md5(raw.encode()).hexdigest() # noqa: S324
|
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:
|
async def cache_get(key: str) -> Any | None:
|
||||||
c = _client()
|
c = _client()
|
||||||
if c is None:
|
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:
|
async def get_version(name: str) -> int:
|
||||||
"""读版本号(缺省 0)。版本号参与缓存 key:INCR 后旧 key 全部失效。"""
|
"""读版本号(缺省 0)。版本号参与缓存 key:INCR 后旧 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()
|
c = _client()
|
||||||
if c is None:
|
if c is None:
|
||||||
return 0
|
return 0
|
||||||
try:
|
try:
|
||||||
v = await c.get(f"ver:{name}")
|
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
|
except Exception: # noqa: BLE001
|
||||||
_bail()
|
_bail()
|
||||||
return 0
|
return 0
|
||||||
@@ -89,7 +141,9 @@ async def bump_version(name: str) -> None:
|
|||||||
if c is None:
|
if c is None:
|
||||||
return
|
return
|
||||||
try:
|
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
|
except Exception: # noqa: BLE001
|
||||||
_bail()
|
_bail()
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ class Base(DeclarativeBase):
|
|||||||
|
|
||||||
|
|
||||||
# echo=False;远程 PG 的空闲连接可能被中间层断开,pre_ping + recycle 自动剔除死连接
|
# echo=False;远程 PG 的空闲连接可能被中间层断开,pre_ping + recycle 自动剔除死连接
|
||||||
|
# pool_size/max_overflow:preview 冷路径每请求开 2 个会话(2 波并发),Redis 故障时全请求变冷,
|
||||||
|
# 默认 5+10 不够并发冷请求用(连接池满了只会排队,不会崩)
|
||||||
engine = create_async_engine(
|
engine = create_async_engine(
|
||||||
settings.database_url, echo=False, future=True,
|
settings.database_url, echo=False, future=True,
|
||||||
pool_pre_ping=True, pool_recycle=1800,
|
pool_pre_ping=True, pool_recycle=1800,
|
||||||
|
pool_size=10, max_overflow=20,
|
||||||
)
|
)
|
||||||
async_session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
|
async_session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Candle 表设计与 TimescaleDB hypertable 完全兼容:将来在目标 PG 库
|
|||||||
"""
|
"""
|
||||||
from datetime import date, datetime
|
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 sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from .db import Base
|
from .db import Base
|
||||||
@@ -136,11 +136,14 @@ class AdjFactor(Base):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||||
trade_date: Mapped[datetime] = mapped_column(DateTime, index=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)
|
adj_factor: Mapped[float] = mapped_column(Float)
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("ts_code", "trade_date", name="uq_adj_code_date"),
|
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"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta, timezone
|
|||||||
import httpx
|
import httpx
|
||||||
from sqlalchemy import delete, select
|
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.db import async_session, engine
|
||||||
from app.main import app
|
from app.main import app
|
||||||
from app.models import AuthSession, Candle, User
|
from app.models import AuthSession, Candle, User
|
||||||
@@ -87,6 +87,9 @@ async def main() -> None:
|
|||||||
).scalar_one()
|
).scalar_one()
|
||||||
auth_session.expires_at = datetime.now(timezone.utc) - timedelta(seconds=1)
|
auth_session.expires_at = datetime.now(timezone.utc) - timedelta(seconds=1)
|
||||||
await db.commit()
|
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
|
assert (await client.get("/api/auth/me")).status_code == 401
|
||||||
print("expired session: 401")
|
print("expired session: 401")
|
||||||
|
|
||||||
|
|||||||
430
backend_run.log
430
backend_run.log
@@ -1,379 +1,61 @@
|
|||||||
INFO: Will watch for changes in these directories: ['D:\\Project\\stock\\backend']
|
INFO: Started server process [17808]
|
||||||
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: Waiting for application startup.
|
INFO: Waiting for application startup.
|
||||||
INFO: Application startup complete.
|
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: 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 [47304]
|
||||||
INFO: Started server process [46460]
|
|
||||||
INFO: Waiting for application startup.
|
INFO: Waiting for application startup.
|
||||||
INFO: Application startup complete.
|
INFO: Application startup complete.
|
||||||
INFO: 127.0.0.1:55384 - "GET /api/health 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:55388 - "GET /api/stock/000001.SZ/chips?adjust=qfq HTTP/1.1" 401 Unauthorized
|
INFO: 127.0.0.1:52358 - "GET /docs HTTP/1.1" 200 OK
|
||||||
INFO: 127.0.0.1:55749 - "GET /api/stock/000001.SZ/chips?adjust=qfq 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:55760 - "GET /api/stock/000001.SZ/chips?adjust=bfq&date=2024-07-24 HTTP/1.1" 200 OK
|
INFO: 127.0.0.1:52497 - "GET /api/preferences 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:52508 - "GET /api/stocks/facets HTTP/1.1" 200 OK
|
||||||
INFO: 127.0.0.1:56029 - "GET /api/preferences 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: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:52531 - "GET /api/trades?ts_code=002648.SZ 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:52534 - "GET /api/watchlist 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:52533 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK
|
||||||
INFO: 127.0.0.1:56044 - "GET /api/watchlist 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:56041 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d 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: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:52542 - "GET /api/screener/preview/000776.SZ?limit=500&adjust=qfq&timeframe=1d 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: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:56062 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq 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:56065 - "GET /api/stock/002648.SZ/chips?date=2026-04-29&adjust=qfq 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:56071 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq 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:56075 - "GET /api/stock/002648.SZ/chips?date=2026-03-11&adjust=qfq 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:56081 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq 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:56084 - "GET /api/stock/002648.SZ/chips?date=2026-05-08&adjust=qfq 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:56089 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq 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:56096 - "GET /api/stock/002648.SZ/chips?date=2026-03-10&adjust=qfq 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:56100 - "GET /api/stock/002648.SZ/chips?date=2025-12-25&adjust=qfq 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:56111 - "GET /api/stock/002648.SZ/chips?date=2025-10-24&adjust=qfq 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:56116 - "GET /api/stock/002648.SZ/chips?date=2025-09-05&adjust=qfq 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:56121 - "GET /api/stock/002648.SZ/chips?date=2025-06-26&adjust=qfq 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:56106 - "GET /api/stock/002648.SZ/chips?date=2025-12-24&adjust=qfq 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:56129 - "GET /api/stock/002648.SZ/chips?date=2025-09-16&adjust=qfq 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:56133 - "GET /api/stock/002648.SZ/chips?date=2025-06-12&adjust=qfq 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:56139 - "GET /api/stock/002648.SZ/chips?date=2025-03-28&adjust=qfq 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:56145 - "GET /api/stock/002648.SZ/chips?date=2024-12-30&adjust=qfq 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:56149 - "GET /api/stock/002648.SZ/chips?date=2024-11-13&adjust=qfq 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:56156 - "GET /api/stock/002648.SZ/chips?date=2024-09-04&adjust=qfq 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:56160 - "GET /api/stock/002648.SZ/chips?date=2024-10-22&adjust=qfq 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:56167 - "GET /api/stock/002648.SZ/chips?date=2025-04-29&adjust=qfq 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:56171 - "GET /api/stock/002648.SZ/chips?date=2025-07-09&adjust=qfq 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:56177 - "GET /api/stock/002648.SZ/chips?date=2025-09-24&adjust=qfq 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:56196 - "GET /api/stock/002648.SZ/chips?date=2025-12-23&adjust=qfq 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:56200 - "GET /api/stock/002648.SZ/chips?date=2026-04-01&adjust=qfq 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:56207 - "GET /api/stock/002648.SZ/chips?date=2026-06-22&adjust=qfq 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:56210 - "GET /api/stock/002648.SZ/chips?date=2026-08-12&adjust=qfq HTTP/1.1" 200 OK
|
INFO: 127.0.0.1:52712 - "GET /api/preferences 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:52713 - "GET /api/market/overview 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:52731 - "GET /api/auth/me 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:52734 - "GET /api/market/overview 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:52733 - "GET /api/preferences 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:52742 - "GET /api/market/overview 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:52757 - "GET /api/screener/queries?limit=20 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:52772 - "GET /api/screener/queries?limit=20 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:52752 - "GET /api/screener/sync/status 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:52771 - "POST /api/screener/run HTTP/1.1" 200 OK
|
||||||
INFO: 127.0.0.1:56571 - "GET /api/watchlist HTTP/1.1" 200 OK
|
INFO: Started server process [764]
|
||||||
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: Waiting for application startup.
|
||||||
INFO: 127.0.0.1:56581 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK
|
INFO: Application startup complete.
|
||||||
INFO: 127.0.0.1:56584 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d 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:56587 - "GET /api/watchlist HTTP/1.1" 200 OK
|
INFO: 127.0.0.1:60296 - "GET /docs 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
|
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
$ vite
|
$ vite
|
||||||
|
|
||||||
[32m[1mVITE[22m v6.4.3[39m [2mready in [0m[1m598[22m[2m[0m ms[22m
|
[32m[1mVITE[22m v6.4.3[39m [2mready in [0m[1m2144[22m[2m[0m ms[22m
|
||||||
|
|
||||||
[32m➜[39m [1mLocal[22m: [36mhttp://localhost:[1m5173[22m/[39m
|
[32m➜[39m [1mLocal[22m: [36mhttp://localhost:[1m5173[22m/[39m
|
||||||
[2m [32m➜[39m [1mNetwork[22m[2m: use [22m[1m--host[22m[2m to expose[22m
|
[2m [32m➜[39m [1mNetwork[22m[2m: use [22m[1m--host[22m[2m to expose[22m
|
||||||
[2m00:44:18[22m [36m[1m[vite][22m[39m [90m[2m(client)[22m[39m [32mhmr update [39m[2m/src/style.css, /@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue[22m
|
[2m15:58:04[22m [31m[1m[vite][22m[39m [31mhttp proxy error: /api/auth/me[39m
|
||||||
[2m00:44:23[22m [36m[1m[vite][22m[39m [90m[2m(client)[22m[39m [32mhmr update [39m[2m/@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue, /src/style.css[22m
|
AggregateError [ECONNREFUSED]:
|
||||||
[2m00:44:24[22m [36m[1m[vite][22m[39m [90m[2m(client)[22m[39m [32mhmr update [39m[2m/src/style.css, /@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue[22m
|
at internalConnectMultiple (node:net:1134:18)
|
||||||
|
at afterConnectMultiple (node:net:1715:7)
|
||||||
|
$ vite
|
||||||
|
Port 5173 is in use, trying another one...
|
||||||
|
|
||||||
|
[32m[1mVITE[22m v6.4.3[39m [2mready in [0m[1m579[22m[2m[0m ms[22m
|
||||||
|
|
||||||
|
[32m➜[39m [1mLocal[22m: [36mhttp://localhost:[1m5174[22m/[39m
|
||||||
|
[2m [32m➜[39m [1mNetwork[22m[2m: use [22m[1m--host[22m[2m to expose[22m
|
||||||
|
|||||||
Reference in New Issue
Block a user