This commit is contained in:
2026-09-09 11:35:02 +08:00
parent bc1c72d558
commit d656c05b3d
35 changed files with 711 additions and 3194 deletions

View File

@@ -1,7 +1,5 @@
import type {
AdjustMode,
BacktestRequest,
BacktestResponse,
Candle,
CurrentUser,
EventBacktestRequest,
@@ -11,6 +9,9 @@ import type {
GlobalIndexList,
IndexDetail,
IndexWeights,
LimitBoard,
ThsBoardList,
ThsBoardMembers,
LoginRequest,
LoginResponse,
MarketOverview,
@@ -27,8 +28,6 @@ import type {
StockFinanceOut,
StockReferenceOut,
StockListResponse,
SyncRequest,
SyncResponse,
Timeframe,
TradesClearResponse,
TradesImportResponse,
@@ -96,18 +95,6 @@ export async function logout(): Promise<void> {
if (!res.ok && res.status !== 401) throw new ApiError(await readError(res, '退出登录失败'), res.status);
}
export async function postBacktest(req: BacktestRequest): Promise<BacktestResponse> {
const res = await apiFetch('/api/backtest', { method: 'POST', body: JSON.stringify(req) });
if (!res.ok) throw new ApiError(`回测请求失败 (HTTP ${res.status}): ${await res.text()}`, res.status);
return (await res.json()) as BacktestResponse;
}
export async function syncData(req: SyncRequest): Promise<SyncResponse> {
const res = await apiFetch('/api/data/sync', { method: 'POST', body: JSON.stringify(req) });
if (!res.ok) throw new ApiError(`数据拉取失败 (HTTP ${res.status}): ${await res.text()}`, res.status);
return (await res.json()) as SyncResponse;
}
/** 自然语言事件回测。全市场扫描较慢timeout 放宽到 15 分钟。 */
export async function postEventBacktest(req: EventBacktestRequest): Promise<EventBacktestResponse> {
const ctrl = new AbortController();
@@ -172,7 +159,7 @@ export async function startScreenerSync(req: ScreenerSyncRequest = {}): Promise<
export async function getScreenerSyncStatus(): Promise<ScreenerSyncStatus> {
const res = await apiFetch('/api/screener/sync/status');
if (!res.ok) throw new ApiError(`获取同步状态失败 (HTTP ${res.status})`, res.status);
if (!res.ok) throw new ApiError(await readError(res, `获取同步状态失败 (HTTP ${res.status})`), res.status);
return (await res.json()) as ScreenerSyncStatus;
}
@@ -185,9 +172,10 @@ export async function getStockPreview(
mas?: number[];
/** 向前翻页:返回该日期(不含)之前的 limit 根 K 线 + 预热好的指标 */
end?: string;
signal?: AbortSignal;
} = {},
): Promise<PreviewResponse> {
const { limit = 500, adjust = 'qfq', timeframe = '1d', mas, end } = opts;
const { limit = 500, adjust = 'qfq', timeframe = '1d', mas, end, signal } = opts;
const q = new URLSearchParams({
limit: String(limit),
adjust,
@@ -195,7 +183,7 @@ export async function getStockPreview(
...(mas?.length ? { mas: mas.join(',') } : {}),
...(end ? { end } : {}),
});
const res = await apiFetch(`/api/screener/preview/${encodeURIComponent(tsCode)}?${q.toString()}`);
const res = await apiFetch(`/api/screener/preview/${encodeURIComponent(tsCode)}?${q.toString()}`, { signal });
if (!res.ok) throw new ApiError(await readError(res, `获取个股详情失败 (HTTP ${res.status})`), res.status);
return (await res.json()) as PreviewResponse;
}
@@ -235,6 +223,26 @@ export async function getMarketOverview(): Promise<MarketOverview> {
return (await res.json()) as MarketOverview;
}
// ---------- 打板专题(主页) ----------
export async function getLimitBoard(): Promise<LimitBoard> {
const res = await apiFetch('/api/market/limit-board');
if (!res.ok) throw new ApiError(await readError(res, '获取打板数据失败'), res.status);
return (await res.json()) as LimitBoard;
}
// ---------- 概念板块THS ----------
export async function getThsBoards(opts: { signal?: AbortSignal } = {}): Promise<ThsBoardList> {
const res = await apiFetch('/api/market/boards', { signal: opts.signal });
if (!res.ok) throw new ApiError(await readError(res, '获取板块列表失败'), res.status);
return (await res.json()) as ThsBoardList;
}
export async function getThsBoardMembers(code: string, opts: { signal?: AbortSignal } = {}): Promise<ThsBoardMembers> {
const res = await apiFetch(`/api/market/boards/${encodeURIComponent(code)}/members`, { signal: opts.signal });
if (!res.ok) throw new ApiError(await readError(res, '获取板块成分失败'), res.status);
return (await res.json()) as ThsBoardMembers;
}
/** 上证指数全量 K 线(日线基底聚合到目标周期;收盘口径) */
export async function getIndexCandles(timeframe: Timeframe): Promise<Candle[]> {
const res = await apiFetch(`/api/market/index-candles?timeframe=${encodeURIComponent(timeframe)}`);
@@ -256,9 +264,10 @@ export async function getIndexDetail(code: string): Promise<IndexDetail> {
}
/** 白名单指数全量 K 线(国内 index_daily / 国际 index_global日线基底聚合 */
export async function getAnyIndexCandles(code: string, timeframe: Timeframe): Promise<Candle[]> {
export async function getAnyIndexCandles(code: string, timeframe: Timeframe, opts: { signal?: AbortSignal } = {}): Promise<Candle[]> {
const res = await apiFetch(
`/api/market/indexes/${encodeURIComponent(code)}/candles?timeframe=${encodeURIComponent(timeframe)}`,
{ signal: opts.signal },
);
if (!res.ok) throw new ApiError(await readError(res, `获取指数K线失败 (HTTP ${res.status})`), res.status);
return (await res.json()) as Candle[];
@@ -283,6 +292,7 @@ export async function getStocks(params: {
order?: 'asc' | 'desc';
limit?: number;
offset?: number;
signal?: AbortSignal;
}): Promise<StockListResponse> {
const q = new URLSearchParams();
if (params.search) q.set('search', params.search);
@@ -294,7 +304,7 @@ export async function getStocks(params: {
if (params.order) q.set('order', params.order);
q.set('limit', String(params.limit ?? 100));
q.set('offset', String(params.offset ?? 0));
const res = await apiFetch(`/api/stocks?${q.toString()}`);
const res = await apiFetch(`/api/stocks?${q.toString()}`, { signal: params.signal });
if (!res.ok) throw new ApiError(await readError(res, `获取股票列表失败 (HTTP ${res.status})`), res.status);
return (await res.json()) as StockListResponse;
}
@@ -314,6 +324,7 @@ export async function getEtfs(params: {
order?: 'asc' | 'desc';
limit?: number;
offset?: number;
signal?: AbortSignal;
}): Promise<EtfListResponse> {
const q = new URLSearchParams();
if (params.search) q.set('search', params.search);
@@ -323,7 +334,7 @@ export async function getEtfs(params: {
if (params.order) q.set('order', params.order);
q.set('limit', String(params.limit ?? 100));
q.set('offset', String(params.offset ?? 0));
const res = await apiFetch(`/api/etfs?${q.toString()}`);
const res = await apiFetch(`/api/etfs?${q.toString()}`, { signal: params.signal });
if (!res.ok) throw new ApiError(await readError(res, `获取 ETF 列表失败 (HTTP ${res.status})`), res.status);
return (await res.json()) as EtfListResponse;
}