完善知行短线

This commit is contained in:
2026-08-17 00:47:02 +08:00
parent 436a126e3b
commit 5029a22de1
12 changed files with 517 additions and 15 deletions

View File

@@ -3,6 +3,7 @@
GET /api/health 健康检查
GET /api/candles/{sym} 取 K 线(支持 1d/1w/1M/1y 周期,日线为基底聚合)
GET /api/stocks 全市场股票列表(基本信息 + 最新行情 + 缓存条数)
GET /api/stock/{code}/chips 个股筹码峰cyq_chips/cyq_perf按复权口径换算
GET /api/market/overview 主页大盘总览A 股/港美指数 + 两市市值成交统计)
POST /api/backtest 跑回测,返回 K线+指标+买卖点+净值+绩效
POST /api/screener/run 智能选股:自然语言 -> 条件 -> 全市场筛选
@@ -11,6 +12,7 @@
"""
from __future__ import annotations
import asyncio
import bisect
import json
from datetime import datetime
@@ -27,7 +29,7 @@ from .auth import require_user
from .backtest.events import EventEngineError, run_event_backtest
from .backtest.strategies import build_strategy
from .config import settings
from .data import fetcher, repository
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
@@ -50,6 +52,8 @@ from .schemas import (
BacktestRequest,
BacktestResponse,
CandleOut,
ChipRowOut,
ChipsResponse,
EquityPoint,
EventBacktestRequest,
EventBacktestResponse,
@@ -982,3 +986,78 @@ async def screener_preview(
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
@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:
"""个股筹码峰Tushare cyq_chips + cyq_perf数据自 2018 年起)。
date=YYYY-MM-DD 为参考日(日 K 传当日;周/月 K 由前端传周期末):返回
<=date 的最近有筹码数据的交易日截面;缺省取最新。
价格/成本均按 adjustbfq/qfq/hfq用 adj_factor 本地换算,与 K 线同口径。
"""
if adjust not in _ADJUST_MODES:
raise HTTPException(status_code=400, detail=f"adjust 仅支持 {'/'.join(_ADJUST_MODES)}")
ref: str | None = None
if date:
try:
ref = datetime.strptime(date.strip()[:10], "%Y-%m-%d").strftime("%Y%m%d")
except ValueError:
raise HTTPException(status_code=400, detail="date 格式应为 YYYY-MM-DD")
# 历史截面不可变;键带 candles 版本号adj_factor 随同步更新后旧缓存失效)
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}")
if cached is not None:
return ChipsResponse.model_validate(cached)
try:
perf, rows = await asyncio.to_thread(tushare_provider.fetch_chips, ts_code, ref)
except Exception as e: # noqa: BLE001
raise HTTPException(status_code=502, detail=f"筹码数据获取失败: {e}")
if not perf:
resp = ChipsResponse(
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
d = datetime.strptime(str(perf["trade_date"]), "%Y%m%d")
# 复权换算(与 _adjust_bars 同口径qfq=f(d)/f_latesthfq=f(d)bfq=1
mult = 1.0
if adjust != "bfq":
factors = (await session.execute(
select(AdjFactor).where(AdjFactor.ts_code == ts_code, AdjFactor.trade_date <= d)
.order_by(AdjFactor.trade_date)
)).scalars().all()
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()
f_at = float(factors[-1].adj_factor) # <=d 的最近因子(因子是阶梯函数)
f_latest = float(latest_f.adj_factor) if latest_f else f_at
mult = f_at / f_latest if adjust == "qfq" else f_at
def _px(v) -> float | None:
return None if v is None or v != v else round(float(v) * mult, 3)
resp = ChipsResponse(
ts_code=ts_code,
trade_date=str(perf["trade_date"]),
adjust=adjust,
rows=[ChipRowOut(price=round(p * mult, 3), percent=v) for p, v in rows],
his_low=_px(perf.get("his_low")), his_high=_px(perf.get("his_high")),
cost_5pct=_px(perf.get("cost_5pct")), cost_15pct=_px(perf.get("cost_15pct")),
cost_50pct=_px(perf.get("cost_50pct")), cost_85pct=_px(perf.get("cost_85pct")),
cost_95pct=_px(perf.get("cost_95pct")),
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