Files
stock/frontend/src/api/client.ts
cirry 528357c3f5 feat: AI 自然语言选股(GLM)+ 全市场数据管道 + 远程 PostgreSQL
- 首页双入口(智能选股/策略回测):引入 vue-router,顶部导航
- 智能选股:自然语言 -> LLM 解析结构化条件(智谱 GLM,OpenAI 兼容,/v4 兼容)-> SQL 快照预筛 + pandas 指标过滤(复用 indicators 单一事实源)
- 条件模型:指标 vs 常数/指标(value_indicator,如 DIF>DEA、close<布林下轨)、lookback+match 表达连续N天/近N天任一天、市值/PE/PB/换手率快照条件、默认排除 ST/退市/北交所
- 全市场数据同步:按 trade_date 批量拉取未复权日线(与回测 candles qfq 隔离),交易日历/股票列表本地缓存,daily_basic 仅最新截面,Tushare 限频兜底(分钟级重试/小时级降级)
- 存储:DATABASE_URL 切远程 PostgreSQL(cirry.cn/stock),本地 SQLite 已移除
- .env 入库(私有仓库);smoke_test 扩展选股链路

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 14:49:53 +08:00

83 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
BacktestRequest,
BacktestResponse,
ScreenerRunRequest,
ScreenerRunResponse,
ScreenerSyncRequest,
ScreenerSyncStatus,
SyncRequest,
SyncResponse,
} from './types';
// dev 用 Vite 代理(/api -> :8000生产构建设 VITE_API_BASE 指向后端地址。
const BASE = import.meta.env.VITE_API_BASE ?? '';
export async function postBacktest(req: BacktestRequest): Promise<BacktestResponse> {
const res = await fetch(`${BASE}/api/backtest`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req),
});
if (!res.ok) {
throw new Error(`回测请求失败 (HTTP ${res.status}): ${await res.text()}`);
}
return (await res.json()) as BacktestResponse;
}
export async function syncData(req: SyncRequest): Promise<SyncResponse> {
const res = await fetch(`${BASE}/api/data/sync`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req),
});
if (!res.ok) {
throw new Error(`数据拉取失败 (HTTP ${res.status}): ${await res.text()}`);
}
return (await res.json()) as SyncResponse;
}
// ---------- 智能选股 ----------
export async function runScreener(req: ScreenerRunRequest): Promise<ScreenerRunResponse> {
const res = await fetch(`${BASE}/api/screener/run`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req),
});
if (!res.ok) {
// 后端 detail 字段携带可读中文原因503 未配 key / 409 未同步 / 502 LLM 错)
let detail = '';
try {
detail = (await res.json())?.detail ?? '';
} catch {
detail = await res.text();
}
throw new Error(detail || `选股失败 (HTTP ${res.status})`);
}
return (await res.json()) as ScreenerRunResponse;
}
export async function startScreenerSync(req: ScreenerSyncRequest = {}): Promise<ScreenerSyncStatus> {
const res = await fetch(`${BASE}/api/screener/sync`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req),
});
if (!res.ok) {
let detail = '';
try {
detail = (await res.json())?.detail ?? '';
} catch {
detail = await res.text();
}
throw new Error(detail || `启动同步失败 (HTTP ${res.status})`);
}
return (await res.json()) as ScreenerSyncStatus;
}
export async function getScreenerSyncStatus(): Promise<ScreenerSyncStatus> {
const res = await fetch(`${BASE}/api/screener/sync/status`);
if (!res.ok) throw new Error(`获取同步状态失败 (HTTP ${res.status})`);
return (await res.json()) as ScreenerSyncStatus;
}