| <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> |
|
|