feat: Add indicator code verification API and UI

- Add POST /api/indicator/verifyCode endpoint in Python backend.
- Update IndicatorEditor.vue with Verify Code button and error modal.
- Add i18n support for verification UI.
- Update README files.
This commit is contained in:
TIANHE
2026-01-06 19:53:23 +08:00
parent eea7d75615
commit c1dc3f3a71
7 changed files with 227 additions and 2 deletions
@@ -621,6 +621,10 @@ const locale = {
'dashboard.indicator.editor.aiPromptRequired': 'Please enter your idea',
'dashboard.indicator.editor.aiGenerateSuccess': 'Code generated successfully',
'dashboard.indicator.editor.aiGenerateError': 'Code generation failed, please try again later',
'dashboard.indicator.editor.verifyCode': 'Verify Code',
'dashboard.indicator.editor.verifyCodeSuccess': 'Verification Passed',
'dashboard.indicator.editor.verifyCodeFailed': 'Verification Failed',
'dashboard.indicator.editor.verifyCodeEmpty': 'Code cannot be empty',
'dashboard.indicator.guide.title': 'Python Indicator & Strategy Development Guide',
'dashboard.indicator.guide.intro': 'This platform supports writing custom technical indicators and trading signals using Python. The system includes built-in pandas and numpy data analysis libraries. You can use standard DataFrame operations to process K-line data and draw charts or mark buy/sell signals.',
'dashboard.indicator.guide.section1.title': '1. Runtime Environment & Predefined Variables',
@@ -617,6 +617,10 @@ const locale = {
'dashboard.indicator.editor.aiPromptRequired': 'あなたの考えを入力してください',
'dashboard.indicator.editor.aiGenerateSuccess': 'コード生成が成功しました',
'dashboard.indicator.editor.aiGenerateError': 'コード生成に失敗しました。後でもう一度お試しください。',
'dashboard.indicator.editor.verifyCode': 'コード検証',
'dashboard.indicator.editor.verifyCodeSuccess': '検証合格',
'dashboard.indicator.editor.verifyCodeFailed': '検証失敗',
'dashboard.indicator.editor.verifyCodeEmpty': 'コードは空にできません',
'dashboard.indicator.backtest.title': 'インジケーターのバックテスト',
'dashboard.indicator.backtest.config': 'バックテストパラメータ',
'dashboard.indicator.backtest.startDate': '開始日',
@@ -616,6 +616,10 @@ const locale = {
'dashboard.indicator.editor.aiPromptRequired': '당신의 생각을 입력해주세요',
'dashboard.indicator.editor.aiGenerateSuccess': '코드 생성 성공',
'dashboard.indicator.editor.aiGenerateError': '코드 생성에 실패했습니다. 나중에 다시 시도해 주세요.',
'dashboard.indicator.editor.verifyCode': '코드 검증',
'dashboard.indicator.editor.verifyCodeSuccess': '검증 통과',
'dashboard.indicator.editor.verifyCodeFailed': '검증 실패',
'dashboard.indicator.editor.verifyCodeEmpty': '코드는 비워둘 수 없습니다',
'dashboard.indicator.backtest.title': '지표 백테스트',
'dashboard.indicator.backtest.config': '백테스트 매개변수',
'dashboard.indicator.backtest.startDate': '시작일',
@@ -618,6 +618,10 @@ const locale = {
'dashboard.indicator.editor.aiPromptRequired': '请输入您的想法',
'dashboard.indicator.editor.aiGenerateSuccess': '代码生成成功',
'dashboard.indicator.editor.aiGenerateError': '代码生成失败,请稍后重试',
'dashboard.indicator.editor.verifyCode': '代码检查',
'dashboard.indicator.editor.verifyCodeSuccess': '代码检查通过',
'dashboard.indicator.editor.verifyCodeFailed': '代码检查未通过',
'dashboard.indicator.editor.verifyCodeEmpty': '代码不能为空',
'dashboard.indicator.boundary.message': '提示:指标脚本只负责“计算 + 绘图 + buy/sell 信号”;仓位、风控、加减仓、手续费/滑点属于策略执行配置。',
'dashboard.indicator.boundary.indicatorRule': "指标脚本请只输出 buy/sell(并设定 df['buy']/df['sell'])。不要在脚本内撰写仓位管理、止盈止损、加减仓。",
'dashboard.indicator.boundary.backtestRule': '规则:同一根K线若出现主信号(buy/sell→开/平仓/反手),本K线将跳过所有加仓与减仓。',
@@ -618,6 +618,10 @@ const locale = {
'dashboard.indicator.editor.aiPromptRequired': '請輸入您的想法',
'dashboard.indicator.editor.aiGenerateSuccess': '代碼生成成功',
'dashboard.indicator.editor.aiGenerateError': '代碼生成失敗,請稍後重試',
'dashboard.indicator.editor.verifyCode': '代碼檢查',
'dashboard.indicator.editor.verifyCodeSuccess': '代碼檢查通過',
'dashboard.indicator.editor.verifyCodeFailed': '代碼檢查未通過',
'dashboard.indicator.editor.verifyCodeEmpty': '代碼不能為空',
'dashboard.indicator.boundary.message': '提示:指標腳本只負責「計算 + 繪圖 + buy/sell 信號」;倉位、風控、加減倉、手續費/滑點屬於策略執行配置。',
'dashboard.indicator.boundary.indicatorRule': "指標腳本請只輸出 buy/sell(並設定 df['buy']/df['sell'])。不要在腳本內撰寫倉位管理、止盈止損、加減倉。",
'dashboard.indicator.boundary.backtestRule': '規則:同一根K線若出現主信號(buy/sell→開/平倉/反手),本K線將跳過所有加倉與減倉。',
@@ -25,6 +25,16 @@
<span class="section-title">{{ $t('dashboard.indicator.editor.code') }}</span>
</div>
<div class="section-actions">
<a-button
type="link"
size="small"
@click="handleVerifyCode"
:loading="verifying"
style="padding: 0 8px; color: #52c41a; font-weight: bold;"
>
<a-icon type="check-circle" />
{{ $t('dashboard.indicator.editor.verifyCode') }}
</a-button>
<a-button type="link" size="small" @click="goToDocs" style="padding: 0;">
<a-icon type="book" />
{{ $t('dashboard.indicator.editor.guide') }}
@@ -105,6 +115,7 @@ import 'codemirror/addon/edit/matchbrackets'
import 'codemirror/addon/selection/active-line'
import storage from 'store'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import request from '@/utils/request'
export default {
name: 'IndicatorEditor',
@@ -128,6 +139,7 @@ export default {
codeEditor: null,
aiPrompt: '',
aiGenerating: false,
verifying: false,
isMobile: false
}
},
@@ -393,6 +405,55 @@ export default {
window.open('https://github.com/brokermr810/QuantDinger/blob/main/docs/STRATEGY_DEV_GUIDE.md', '_blank')
},
// 验证代码
handleVerifyCode () {
const code = this.codeEditor ? this.codeEditor.getValue() : ''
if (!code || !code.trim()) {
this.$message.warning(this.$t('dashboard.indicator.editor.verifyCodeEmpty'))
return
}
this.verifying = true
// 使用 request 工具(axios)发送请求,它会自动处理 baseURL 和 token
request({
url: '/api/indicator/verifyCode',
method: 'post',
data: { code: code }
}).then(res => {
if (res.code === 1) {
const data = res.data || {}
this.$message.success(`${this.$t('dashboard.indicator.editor.verifyCodeSuccess')} (${data.plots_count || 0} plots, ${data.signals_count || 0} signals)`)
} else {
// 显示详细错误
const errorData = res.data || {}
this.$error({
title: this.$t('dashboard.indicator.editor.verifyCodeFailed'),
width: 600,
content: (h) => {
return h('div', [
h('p', { style: { fontWeight: 'bold', color: '#ff4d4f' } }, res.msg),
errorData.details ? h('pre', {
style: {
background: '#f5f5f5',
padding: '8px',
overflow: 'auto',
maxHeight: '300px',
marginTop: '8px',
fontSize: '12px',
fontFamily: 'monospace'
}
}, errorData.details) : null
])
}
})
}
}).catch(err => {
this.$message.error('Request Failed: ' + (err.message || 'Unknown Error'))
}).finally(() => {
this.verifying = false
})
},
// 清理代码中的 markdown 代码块标记
cleanMarkdownCodeBlocks (code) {
if (!code || typeof code !== 'string') {