File size: 6,348 Bytes
590a501 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | <template>
<div class="factor-research-view">
<div class="page-header">
<h2>因子研究</h2>
<p>对接 ML-Alpha-Research-System:因子注册表、算子库、策略框架</p>
</div>
<el-alert
v-if="!researchAvailable"
title="Qlib 研究模块未启动"
type="warning"
show-icon
:closable="false"
class="mb-16"
>
请先在「服务控制台」启动 <strong>Qlib 因子研究</strong> 模块
</el-alert>
<el-tabs v-model="activeTab" @tab-change="onTabChange">
<el-tab-pane label="注册因子" name="factors">
<el-table :data="factors" v-loading="loadingFactors" stripe max-height="520">
<el-table-column prop="name" label="名称" width="160" />
<el-table-column prop="expression" label="表达式" min-width="280" show-overflow-tooltip />
<el-table-column prop="category" label="分类" width="100" />
<el-table-column prop="description" label="说明" min-width="180" show-overflow-tooltip />
</el-table>
</el-tab-pane>
<el-tab-pane label="算子库" name="operators">
<el-row :gutter="16">
<el-col :span="12">
<el-card shadow="never">
<template #header>内置算子</template>
<el-table :data="operators" size="small" max-height="400">
<el-table-column prop="name" label="算子" width="120" />
<el-table-column prop="category" label="类别" width="90" />
<el-table-column prop="description" label="说明" show-overflow-tooltip />
</el-table>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never">
<template #header>构建表达式</template>
<el-form label-width="80px" size="small">
<el-form-item label="因子名">
<el-input v-model="buildForm.name" placeholder="my_factor" />
</el-form-item>
<el-form-item label="算子树">
<el-input
v-model="buildForm.treeJson"
type="textarea"
:rows="6"
placeholder='{"op":"rank","args":[{"op":"$close"}]}'
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="building" @click="buildExpression">生成表达式</el-button>
</el-form-item>
</el-form>
<el-input v-if="builtExpression" v-model="builtExpression" readonly type="textarea" :rows="3" />
</el-card>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="策略" name="strategies">
<el-table :data="strategies" v-loading="loadingStrategies" stripe>
<el-table-column prop="name" label="策略 ID" width="180" />
<el-table-column prop="description" label="说明" min-width="260" />
<el-table-column prop="class" label="实现类" width="200" show-overflow-tooltip />
</el-table>
</el-tab-pane>
<el-tab-pane label="快速命令" name="commands">
<el-card shadow="never">
<pre class="command-block">{{ commandHints }}</pre>
</el-card>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { researchApi } from '../api'
const activeTab = ref('factors')
const researchAvailable = ref(true)
const factors = ref([])
const operators = ref([])
const strategies = ref([])
const loadingFactors = ref(false)
const loadingStrategies = ref(false)
const building = ref(false)
const builtExpression = ref('')
const buildForm = ref({
name: 'custom_factor',
treeJson: '{"op":"rank","args":[{"field":"$close"}]}',
})
const commandHints = `# 因子列表与计算
python scripts/run_factor.py list
python scripts/run_factor.py compute --name momentum_20
# 单因子回测
python scripts/run_factor.py backtest --name momentum_20
# 策略回测
python scripts/run_backtest.py --strategy topk_dropout --signal-path artifacts/factors/momentum_20.pkl
# Qlib 实验
python scripts/run_qrun.py --config config/workflows/workflow_alpha158_baseline.yaml
`
async function loadFactors() {
loadingFactors.value = true
try {
const res = await researchApi.listFactors()
factors.value = res.data.factors || []
researchAvailable.value = true
} catch (e) {
researchAvailable.value = false
factors.value = []
} finally {
loadingFactors.value = false
}
}
async function loadOperators() {
try {
const res = await researchApi.listOperators()
operators.value = res.data.operators || []
} catch (e) {
operators.value = []
}
}
async function loadStrategies() {
loadingStrategies.value = true
try {
const res = await researchApi.listStrategies()
strategies.value = res.data.strategies || []
} catch (e) {
strategies.value = []
} finally {
loadingStrategies.value = false
}
}
async function buildExpression() {
building.value = true
builtExpression.value = ''
try {
let tree
try {
tree = JSON.parse(buildForm.value.treeJson)
} catch {
ElMessage.error('算子树 JSON 格式错误')
return
}
const res = await researchApi.buildExpression({
name: buildForm.value.name,
tree,
})
builtExpression.value = res.data.expression || ''
ElMessage.success('表达式已生成')
} catch (e) {
ElMessage.error(e.response?.data?.detail || '构建失败')
} finally {
building.value = false
}
}
function onTabChange(name) {
if (name === 'operators' && !operators.value.length) loadOperators()
if (name === 'strategies' && !strategies.value.length) loadStrategies()
}
onMounted(() => {
loadFactors()
})
</script>
<style scoped>
.factor-research-view { padding: 0 4px; }
.page-header { margin-bottom: 20px; }
.page-header h2 { margin: 0 0 8px; font-size: 22px; }
.page-header p { margin: 0; color: #909399; font-size: 14px; }
.mb-16 { margin-bottom: 16px; }
.command-block {
margin: 0;
padding: 16px;
background: #1e1e1e;
color: #d4d4d4;
border-radius: 6px;
font-size: 13px;
line-height: 1.6;
overflow-x: auto;
}
</style>
|