Files
stock/frontend/src/components/ScreenerTable.vue
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

76 lines
3.2 KiB
Vue
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.
<script setup lang="ts">
import { computed } from 'vue';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
import type { ScreenerItemOut, ScreenerRunResponse } from '@/api/types';
const props = defineProps<{ result: ScreenerRunResponse }>();
const items = computed(() => props.result.items);
// 动态指标列:[{ key: 'kdj_j', label: 'KDJ J(9,3,3)' }]
const indCols = computed(() =>
Object.entries(props.result.indicator_labels).map(([key, label]) => ({ key, label })),
);
function pctClass(v: number | null) {
if (v == null) return '';
return v > 0 ? 'pos' : v < 0 ? 'neg' : '';
}
function fmt(v: number | null, digits = 2) {
return v == null ? '—' : v.toFixed(digits);
}
</script>
<template>
<div class="panel">
<div class="panel-title">
命中 {{ result.total }} {{ result.total > items.length ? `(仅显示前 ${items.length}` : '' }}
<span v-if="result.trade_date" style="color: var(--ink-3); margin-left: 8px;">· 数据基准 {{ result.trade_date.slice(0, 10) }}</span>
</div>
<DataTable :value="items" size="small" striped-rows :scrollable="true" scroll-height="560px" sort-mode="single">
<Column field="ts_code" header="代码" sortable style="min-width: 92px;">
<template #body="{ data }">
<span style="font-variant-numeric: tabular-nums; color: var(--ink);">{{ data.ts_code }}</span>
</template>
</Column>
<Column field="name" header="名称" sortable style="min-width: 96px;" />
<Column field="close" header="最新价" sortable style="min-width: 84px;">
<template #body="{ data }">{{ fmt(data.close) }}</template>
</Column>
<Column field="pct_chg" header="涨跌幅%" sortable style="min-width: 88px;">
<template #body="{ data }">
<span :class="pctClass(data.pct_chg)">{{ data.pct_chg == null ? '—' : (data.pct_chg > 0 ? '+' : '') + data.pct_chg.toFixed(2) }}</span>
</template>
</Column>
<Column field="total_mv" header="总市值(亿)" sortable style="min-width: 100px;">
<template #body="{ data }">{{ fmt(data.total_mv) }}</template>
</Column>
<Column field="circ_mv" header="流通市值(亿)" sortable style="min-width: 106px;">
<template #body="{ data }">{{ fmt(data.circ_mv) }}</template>
</Column>
<Column field="pe_ttm" header="PE-TTM" sortable style="min-width: 84px;">
<template #body="{ data }">{{ fmt(data.pe_ttm) }}</template>
</Column>
<Column field="pb" header="PB" sortable style="min-width: 70px;">
<template #body="{ data }">{{ fmt(data.pb) }}</template>
</Column>
<Column field="turnover_rate" header="换手率%" sortable style="min-width: 88px;">
<template #body="{ data }">{{ fmt(data.turnover_rate) }}</template>
</Column>
<Column
v-for="col in indCols"
:key="col.key"
:field="`indicators.${col.key}`"
:header="col.label"
sortable
style="min-width: 110px;"
>
<template #body="{ data }">
<span style="font-variant-numeric: tabular-nums;">{{ data.indicators?.[col.key] == null ? '—' : data.indicators[col.key].toFixed(2) }}</span>
</template>
</Column>
</DataTable>
</div>
</template>