feat(s2): 投研核心端到端跑通(IC 表 + tears 报告)

- Task 加 raw_result 字段;orchestrator get_raw_result(内存存 FactorReport)
- 路由 /factor/list、/task/{id}/ic-summary、/task/{id}/report/{factor}(query token 给 iframe)
- analyzer cfg=None 时加载 data_platform.yaml(修 API 路径 read_db_daily 崩)
- get_status 返回 error_msg(调试+前端 failed 展示)
- 前端 投研-新建(多因子/多标的/日期)+ 结果页(IC 表 + tears iframe)
- factor 冒烟通过:ma5 → IC 1D/5D/10D 真实数据
This commit is contained in:
2026-07-07 06:28:01 +08:00
parent 28aea67232
commit 212ad6426d
11 changed files with 416 additions and 8 deletions
+80 -2
View File
@@ -1,4 +1,82 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getFactors, submitFactor, type FactorItem } from '@/api/factor'
const router = useRouter()
const factors = ref<FactorItem[]>([])
const loading = ref(false)
const submitting = ref(false)
const form = reactive({
factor_names: [] as string[],
symbolsText: '600000\n000001\n300750',
start: '2024-01-01',
end: '2024-06-30',
})
onMounted(async () => {
loading.value = true
try {
factors.value = await getFactors()
} catch {
ElMessage.error('因子列表加载失败')
} finally {
loading.value = false
}
})
async function onSubmit(): Promise<void> {
const symbols = form.symbolsText
.split(/[\s,]+/)
.map((s) => s.trim())
.filter(Boolean)
if (!form.factor_names.length || symbols.length < 2) {
ElMessage.warning('至少选 1 个因子 + 2 个标的(IC 横截面需多标的)')
return
}
submitting.value = true
try {
const tid = await submitFactor({
symbols,
factor_names: form.factor_names,
start: form.start,
end: form.end,
})
ElMessage.success('因子分析已提交')
router.push(`/factor/progress/${tid}`)
} catch {
ElMessage.error('提交失败')
} finally {
submitting.value = false
}
}
</script>
<template>
<el-empty description="投研 - 新建因子分析(S2 切片实现)" />
<el-card v-loading="loading">
<template #header>
<h3>新建因子分析</h3>
</template>
<el-form label-width="140px">
<el-form-item label="因子">
<el-select v-model="form.factor_names" multiple placeholder="选择因子" style="width: 380px">
<el-option v-for="f in factors" :key="f.name" :label="f.name" :value="f.name" />
</el-select>
</el-form-item>
<el-form-item label="标的(≥2,每行一个)">
<el-input v-model="form.symbolsText" type="textarea" :rows="3" placeholder="600000&#10;000001&#10;300750" />
</el-form-item>
<el-form-item label="开始日期">
<el-date-picker v-model="form.start" type="date" value-format="YYYY-MM-DD" style="width: 220px" />
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="form.end" type="date" value-format="YYYY-MM-DD" style="width: 220px" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="submitting" @click="onSubmit">提交分析</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
+87 -2
View File
@@ -1,4 +1,89 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getIcSummary, reportUrl } from '@/api/factor'
interface IcStats {
mean?: number
std?: number
icir?: number
t_stat?: number
count?: number
error?: string
}
interface FactorInfo {
status?: string
ic?: Record<string, IcStats>
error?: string
}
const route = useRoute()
const taskId = String(route.params.id)
const loading = ref(true)
const icSummary = ref<Record<string, FactorInfo>>({})
const rows = computed(() => {
const out: Array<Record<string, unknown>> = []
for (const [factor, info] of Object.entries(icSummary.value)) {
const ic = info?.ic || {}
for (const [period, s] of Object.entries(ic)) {
out.push({ factor, period, ...s })
}
}
return out
})
const factors = computed(() => Object.keys(icSummary.value))
function fmt(v: unknown): string {
if (typeof v === 'number') return (Math.round(v * 10000) / 10000).toString()
return v == null ? '' : String(v)
}
onMounted(async () => {
try {
icSummary.value = (await getIcSummary(taskId)) as Record<string, FactorInfo>
} catch {
ElMessage.error('IC 摘要加载失败')
} finally {
loading.value = false
}
})
</script>
<template>
<el-empty description="投研 - 结果(S2 切片实现)" />
<div v-loading="loading">
<el-card>
<template #header>
<h3>IC 统计</h3>
</template>
<el-table :data="rows" stripe size="small" empty-text=" IC 数据">
<el-table-column prop="factor" label="因子" width="120" />
<el-table-column prop="period" label="周期" width="80" />
<el-table-column label="IC 均值">
<template #default="{ row }">{{ fmt(row.mean) }}</template>
</el-table-column>
<el-table-column label="IC 标准差">
<template #default="{ row }">{{ fmt(row.std) }}</template>
</el-table-column>
<el-table-column label="ICIR">
<template #default="{ row }">{{ fmt(row.icir) }}</template>
</el-table-column>
<el-table-column label="t 统计">
<template #default="{ row }">{{ fmt(row.t_stat) }}</template>
</el-table-column>
<el-table-column label="样本数">
<template #default="{ row }">{{ row.count }}</template>
</el-table-column>
</el-table>
</el-card>
<el-card v-for="f in factors" :key="f" style="margin-top: 16px">
<template #header>
<h3>tears 报告 {{ f }}</h3>
</template>
<iframe :src="reportUrl(taskId, f)" style="width: 100%; height: 600px; border: 0" />
</el-card>
</div>
</template>