功能更新
This commit is contained in:
@@ -1,16 +1,23 @@
|
||||
import type {
|
||||
AdjustMode,
|
||||
BacktestRequest,
|
||||
BacktestResponse,
|
||||
CurrentUser,
|
||||
EventBacktestRequest,
|
||||
EventBacktestResponse,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
PreviewResponse,
|
||||
ScreenerQueryItem,
|
||||
ScreenerRunRequest,
|
||||
ScreenerRunResponse,
|
||||
ScreenerSyncRequest,
|
||||
ScreenerSyncStatus,
|
||||
StockFacets,
|
||||
StockListResponse,
|
||||
SyncRequest,
|
||||
SyncResponse,
|
||||
Timeframe,
|
||||
} from './types';
|
||||
|
||||
// dev 用 Vite 代理(/api -> :8000);生产构建设 VITE_API_BASE 指向后端地址。
|
||||
@@ -81,6 +88,28 @@ export async function syncData(req: SyncRequest): Promise<SyncResponse> {
|
||||
return (await res.json()) as SyncResponse;
|
||||
}
|
||||
|
||||
/** 自然语言事件回测。全市场扫描较慢,timeout 放宽到 15 分钟。 */
|
||||
export async function postEventBacktest(req: EventBacktestRequest): Promise<EventBacktestResponse> {
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), 15 * 60 * 1000);
|
||||
try {
|
||||
const res = await apiFetch('/api/backtest/event', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(req),
|
||||
signal: ctrl.signal,
|
||||
});
|
||||
if (!res.ok) throw new ApiError(await readError(res, `事件回测失败 (HTTP ${res.status})`), res.status);
|
||||
return (await res.json()) as EventBacktestResponse;
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === 'AbortError') {
|
||||
throw new ApiError('事件回测超时(全市场扫描较慢,可先缩短日期范围或指定单只股票)', 0);
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runScreener(req: ScreenerRunRequest): Promise<ScreenerRunResponse> {
|
||||
const res = await apiFetch('/api/screener/run', { method: 'POST', body: JSON.stringify(req) });
|
||||
if (!res.ok) throw new ApiError(await readError(res, `选股失败 (HTTP ${res.status})`), res.status);
|
||||
@@ -99,8 +128,96 @@ export async function getScreenerSyncStatus(): Promise<ScreenerSyncStatus> {
|
||||
return (await res.json()) as ScreenerSyncStatus;
|
||||
}
|
||||
|
||||
export async function getStockPreview(tsCode: string, limit = 260): Promise<PreviewResponse> {
|
||||
const res = await apiFetch(`/api/screener/preview/${encodeURIComponent(tsCode)}?limit=${limit}`);
|
||||
export async function getStockPreview(
|
||||
tsCode: string,
|
||||
opts: {
|
||||
limit?: number;
|
||||
adjust?: AdjustMode;
|
||||
timeframe?: Timeframe;
|
||||
mas?: number[];
|
||||
} = {},
|
||||
): Promise<PreviewResponse> {
|
||||
const { limit = 10000, adjust = 'qfq', timeframe = '1d', mas } = opts;
|
||||
const q = new URLSearchParams({
|
||||
limit: String(limit),
|
||||
adjust,
|
||||
timeframe,
|
||||
...(mas?.length ? { mas: mas.join(',') } : {}),
|
||||
});
|
||||
const res = await apiFetch(`/api/screener/preview/${encodeURIComponent(tsCode)}?${q.toString()}`);
|
||||
if (!res.ok) throw new ApiError(await readError(res, `获取个股详情失败 (HTTP ${res.status})`), res.status);
|
||||
return (await res.json()) as PreviewResponse;
|
||||
}
|
||||
|
||||
export async function getStocks(params: {
|
||||
search?: string;
|
||||
market?: string;
|
||||
industry?: string;
|
||||
area?: string;
|
||||
watched_only?: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<StockListResponse> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.search) q.set('search', params.search);
|
||||
if (params.market) q.set('market', params.market);
|
||||
if (params.industry) q.set('industry', params.industry);
|
||||
if (params.area) q.set('area', params.area);
|
||||
if (params.watched_only) q.set('watched_only', 'true');
|
||||
q.set('limit', String(params.limit ?? 100));
|
||||
q.set('offset', String(params.offset ?? 0));
|
||||
const res = await apiFetch(`/api/stocks?${q.toString()}`);
|
||||
if (!res.ok) throw new ApiError(await readError(res, `获取股票列表失败 (HTTP ${res.status})`), res.status);
|
||||
return (await res.json()) as StockListResponse;
|
||||
}
|
||||
|
||||
export async function getStockFacets(): Promise<StockFacets> {
|
||||
const res = await apiFetch('/api/stocks/facets');
|
||||
if (!res.ok) throw new ApiError(await readError(res, `获取筛选项失败 (HTTP ${res.status})`), res.status);
|
||||
return (await res.json()) as StockFacets;
|
||||
}
|
||||
|
||||
// ---------- 用户偏好 / 自选股 / 提问历史 ----------
|
||||
export async function getPreferences(): Promise<Record<string, unknown>> {
|
||||
const res = await apiFetch('/api/preferences');
|
||||
if (!res.ok) throw new ApiError(await readError(res, `获取偏好失败 (HTTP ${res.status})`), res.status);
|
||||
const data = (await res.json()) as { prefs: Record<string, unknown> };
|
||||
return data.prefs ?? {};
|
||||
}
|
||||
|
||||
export async function putPreferences(prefs: Record<string, unknown>): Promise<Record<string, unknown>> {
|
||||
const res = await apiFetch('/api/preferences', { method: 'PUT', body: JSON.stringify({ prefs }) });
|
||||
if (!res.ok) throw new ApiError(await readError(res, `保存偏好失败 (HTTP ${res.status})`), res.status);
|
||||
const data = (await res.json()) as { prefs: Record<string, unknown> };
|
||||
return data.prefs ?? {};
|
||||
}
|
||||
|
||||
export async function getWatchlist(): Promise<string[]> {
|
||||
const res = await apiFetch('/api/watchlist');
|
||||
if (!res.ok) throw new ApiError(await readError(res, `获取自选股失败 (HTTP ${res.status})`), res.status);
|
||||
return (await res.json()) as string[];
|
||||
}
|
||||
|
||||
export async function addWatchlist(tsCode: string): Promise<string[]> {
|
||||
const res = await apiFetch('/api/watchlist', { method: 'POST', body: JSON.stringify({ ts_code: tsCode }) });
|
||||
if (!res.ok) throw new ApiError(await readError(res, `加自选失败 (HTTP ${res.status})`), res.status);
|
||||
return (await res.json()) as string[];
|
||||
}
|
||||
|
||||
export async function removeWatchlist(tsCode: string): Promise<string[]> {
|
||||
const res = await apiFetch(`/api/watchlist/${encodeURIComponent(tsCode)}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw new ApiError(await readError(res, `移除自选失败 (HTTP ${res.status})`), res.status);
|
||||
return (await res.json()) as string[];
|
||||
}
|
||||
|
||||
export async function getScreenerQueries(limit = 20): Promise<ScreenerQueryItem[]> {
|
||||
const res = await apiFetch(`/api/screener/queries?limit=${limit}`);
|
||||
if (!res.ok) throw new ApiError(await readError(res, `获取提问历史失败 (HTTP ${res.status})`), res.status);
|
||||
const data = (await res.json()) as { items: ScreenerQueryItem[] };
|
||||
return data.items ?? [];
|
||||
}
|
||||
|
||||
export async function deleteScreenerQuery(id: number): Promise<void> {
|
||||
const res = await apiFetch(`/api/screener/queries/${id}`, { method: 'DELETE' });
|
||||
if (!res.ok && res.status !== 401) throw new ApiError(`删除失败 (HTTP ${res.status})`, res.status);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user