fix: Fix indicator parameter configuration and Docker build issues
- Fix indicator parameter values being reset after modification in indicator-analysis page - Fix Docker build nginx image pull failure (use specific version tag) - Add Docker build scripts (PowerShell and Bash versions) - Revert call_indicator feature (temporarily removed to avoid affecting existing indicator execution)
This commit is contained in:
@@ -24,6 +24,7 @@ import numpy as np
|
|||||||
from app.utils.db import get_db_connection
|
from app.utils.db import get_db_connection
|
||||||
from app.utils.logger import get_logger
|
from app.utils.logger import get_logger
|
||||||
from app.utils.auth import login_required
|
from app.utils.auth import login_required
|
||||||
|
from app.services.indicator_params import IndicatorCaller
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -659,3 +660,93 @@ IMPORTANT: Output Python code directly, without explanations, without descriptio
|
|||||||
"X-Accel-Buffering": "no",
|
"X-Accel-Buffering": "no",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@indicator_bp.route("/callIndicator", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def call_indicator():
|
||||||
|
"""
|
||||||
|
调用另一个指标(供前端 Pyodide 环境使用)
|
||||||
|
|
||||||
|
POST /api/indicator/callIndicator
|
||||||
|
Body: {
|
||||||
|
"indicatorRef": int | str, # 指标ID或名称
|
||||||
|
"klineData": List[Dict], # K线数据
|
||||||
|
"params": Dict, # 传递给被调用指标的参数(可选)
|
||||||
|
"currentIndicatorId": int # 当前指标ID(用于循环依赖检测,可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"code": 1,
|
||||||
|
"data": {
|
||||||
|
"df": List[Dict], # 执行后的DataFrame(转换为JSON)
|
||||||
|
"columns": List[str] # DataFrame的列名
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = request.get_json() or {}
|
||||||
|
indicator_ref = data.get("indicatorRef")
|
||||||
|
kline_data = data.get("klineData", [])
|
||||||
|
params = data.get("params") or {}
|
||||||
|
current_indicator_id = data.get("currentIndicatorId")
|
||||||
|
|
||||||
|
if not indicator_ref:
|
||||||
|
return jsonify({
|
||||||
|
"code": 0,
|
||||||
|
"msg": "indicatorRef is required",
|
||||||
|
"data": None
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
if not kline_data or not isinstance(kline_data, list):
|
||||||
|
return jsonify({
|
||||||
|
"code": 0,
|
||||||
|
"msg": "klineData must be a non-empty list",
|
||||||
|
"data": None
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# 获取用户ID
|
||||||
|
user_id = g.user_id
|
||||||
|
|
||||||
|
# 创建 IndicatorCaller
|
||||||
|
indicator_caller = IndicatorCaller(user_id, current_indicator_id)
|
||||||
|
|
||||||
|
# 将前端传入的K线数据转换为DataFrame
|
||||||
|
df = pd.DataFrame(kline_data)
|
||||||
|
|
||||||
|
# 确保必要的列存在
|
||||||
|
required_columns = ['open', 'high', 'low', 'close', 'volume']
|
||||||
|
for col in required_columns:
|
||||||
|
if col not in df.columns:
|
||||||
|
df[col] = 0.0
|
||||||
|
|
||||||
|
# 转换数据类型
|
||||||
|
df['open'] = df['open'].astype('float64')
|
||||||
|
df['high'] = df['high'].astype('float64')
|
||||||
|
df['low'] = df['low'].astype('float64')
|
||||||
|
df['close'] = df['close'].astype('float64')
|
||||||
|
df['volume'] = df['volume'].astype('float64')
|
||||||
|
|
||||||
|
# 调用指标
|
||||||
|
result_df = indicator_caller.call_indicator(indicator_ref, df, params)
|
||||||
|
|
||||||
|
# 将DataFrame转换为JSON格式(前端可以使用的格式)
|
||||||
|
result_dict = result_df.to_dict(orient='records')
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"code": 1,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"df": result_dict,
|
||||||
|
"columns": list(result_df.columns)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calling indicator: {e}", exc_info=True)
|
||||||
|
return jsonify({
|
||||||
|
"code": 0,
|
||||||
|
"msg": str(e),
|
||||||
|
"data": None
|
||||||
|
}), 500
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./quantdinger_vue
|
context: ./quantdinger_vue
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
# Force pull from official registry to avoid mirror issues
|
||||||
|
pull: true
|
||||||
container_name: quantdinger-frontend
|
container_name: quantdinger-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ COPY . .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Stage 2: Production image (using nginx)
|
# Stage 2: Production image (using nginx)
|
||||||
FROM nginx:alpine
|
# Use specific version to avoid mirror registry issues
|
||||||
|
FROM nginx:1.25-alpine
|
||||||
|
|
||||||
# Copy build artifacts
|
# Copy build artifacts
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# PowerShell script for building Docker image with registry fallback
|
||||||
|
|
||||||
|
Write-Host "Building frontend Docker image..." -ForegroundColor Green
|
||||||
|
|
||||||
|
# Try to build with --pull flag to force pull from official registry
|
||||||
|
$buildResult = docker build `
|
||||||
|
--pull `
|
||||||
|
--platform linux/amd64 `
|
||||||
|
-t quantdinger-frontend:latest `
|
||||||
|
-f Dockerfile `
|
||||||
|
.
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "Build failed, trying with no-cache..." -ForegroundColor Yellow
|
||||||
|
docker build `
|
||||||
|
--no-cache `
|
||||||
|
--platform linux/amd64 `
|
||||||
|
-t quantdinger-frontend:latest `
|
||||||
|
-f Dockerfile `
|
||||||
|
.
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Host "Build successful!" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Build failed. Please check Docker registry configuration." -ForegroundColor Red
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Docker build script with registry fallback
|
||||||
|
|
||||||
|
# Try to build with --pull flag to force pull from official registry
|
||||||
|
echo "Building frontend Docker image..."
|
||||||
|
docker build \
|
||||||
|
--pull \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
-t quantdinger-frontend:latest \
|
||||||
|
-f Dockerfile \
|
||||||
|
.
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Build failed, trying with no-cache..."
|
||||||
|
docker build \
|
||||||
|
--no-cache \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
-t quantdinger-frontend:latest \
|
||||||
|
-f Dockerfile \
|
||||||
|
.
|
||||||
|
fi
|
||||||
@@ -392,6 +392,7 @@
|
|||||||
:confirmLoading="loadingParams"
|
:confirmLoading="loadingParams"
|
||||||
@ok="confirmIndicatorParams"
|
@ok="confirmIndicatorParams"
|
||||||
@cancel="cancelIndicatorParams"
|
@cancel="cancelIndicatorParams"
|
||||||
|
@afterClose="handleParamsModalAfterClose"
|
||||||
:width="500"
|
:width="500"
|
||||||
:maskClosable="false"
|
:maskClosable="false"
|
||||||
:keyboard="false"
|
:keyboard="false"
|
||||||
@@ -746,6 +747,8 @@ export default {
|
|||||||
const indicatorParams = ref([]) // 指标参数声明
|
const indicatorParams = ref([]) // 指标参数声明
|
||||||
const indicatorParamValues = ref({}) // 用户设置的参数值
|
const indicatorParamValues = ref({}) // 用户设置的参数值
|
||||||
const loadingParams = ref(false)
|
const loadingParams = ref(false)
|
||||||
|
// 保存每个指标的参数值(key: indicatorId, value: { paramName: paramValue })
|
||||||
|
const savedIndicatorParams = ref({})
|
||||||
|
|
||||||
// 折叠状态
|
// 折叠状态
|
||||||
const customSectionCollapsed = ref(false) // 我创建的指标区域是否折叠
|
const customSectionCollapsed = ref(false) // 我创建的指标区域是否折叠
|
||||||
@@ -1416,10 +1419,34 @@ export default {
|
|||||||
if (res && res.code === 1 && Array.isArray(res.data) && res.data.length > 0) {
|
if (res && res.code === 1 && Array.isArray(res.data) && res.data.length > 0) {
|
||||||
// 有参数,显示配置弹窗
|
// 有参数,显示配置弹窗
|
||||||
indicatorParams.value = res.data
|
indicatorParams.value = res.data
|
||||||
indicatorParamValues.value = {}
|
// 获取指标的唯一标识(用于保存参数值)
|
||||||
|
const indicatorKey = `${source}-${indicator.id}`
|
||||||
|
// 先检查是否有保存的参数值
|
||||||
|
const savedParams = savedIndicatorParams.value[indicatorKey]
|
||||||
|
// 先清空,然后逐个设置,确保响应式正常工作
|
||||||
|
const newParamValues = {}
|
||||||
res.data.forEach(p => {
|
res.data.forEach(p => {
|
||||||
indicatorParamValues.value[p.name] = p.default
|
// 如果有保存的值,使用保存的值;否则使用默认值
|
||||||
|
// 需要处理类型转换
|
||||||
|
let value = savedParams && savedParams[p.name] !== undefined
|
||||||
|
? savedParams[p.name]
|
||||||
|
: p.default
|
||||||
|
|
||||||
|
// 根据参数类型进行类型转换
|
||||||
|
if (p.type === 'int') {
|
||||||
|
value = parseInt(value) || 0
|
||||||
|
} else if (p.type === 'float') {
|
||||||
|
value = parseFloat(value) || 0.0
|
||||||
|
} else if (p.type === 'bool') {
|
||||||
|
value = value === true || value === 'true' || value === 1 || value === '1'
|
||||||
|
} else {
|
||||||
|
value = value || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
newParamValues[p.name] = value
|
||||||
})
|
})
|
||||||
|
// 一次性设置所有值,确保响应式更新
|
||||||
|
indicatorParamValues.value = newParamValues
|
||||||
pendingIndicator.value = indicator
|
pendingIndicator.value = indicator
|
||||||
pendingSource.value = source
|
pendingSource.value = source
|
||||||
showParamsModal.value = true
|
showParamsModal.value = true
|
||||||
@@ -1440,6 +1467,10 @@ export default {
|
|||||||
// 确认参数配置并运行指标
|
// 确认参数配置并运行指标
|
||||||
const confirmIndicatorParams = () => {
|
const confirmIndicatorParams = () => {
|
||||||
if (pendingIndicator.value) {
|
if (pendingIndicator.value) {
|
||||||
|
// 保存参数值(用于下次打开时使用)
|
||||||
|
const indicatorKey = `${pendingSource.value}-${pendingIndicator.value.id}`
|
||||||
|
savedIndicatorParams.value[indicatorKey] = { ...indicatorParamValues.value }
|
||||||
|
|
||||||
// 将参数传递给指标
|
// 将参数传递给指标
|
||||||
const indicatorWithParams = {
|
const indicatorWithParams = {
|
||||||
...pendingIndicator.value,
|
...pendingIndicator.value,
|
||||||
@@ -1454,9 +1485,29 @@ export default {
|
|||||||
|
|
||||||
// 取消参数配置
|
// 取消参数配置
|
||||||
const cancelIndicatorParams = () => {
|
const cancelIndicatorParams = () => {
|
||||||
|
// 保存参数值(在关闭前保存)
|
||||||
|
saveCurrentParams()
|
||||||
showParamsModal.value = false
|
showParamsModal.value = false
|
||||||
pendingIndicator.value = null
|
// 延迟清空,确保 afterClose 能访问到数据
|
||||||
pendingSource.value = ''
|
setTimeout(() => {
|
||||||
|
pendingIndicator.value = null
|
||||||
|
pendingSource.value = ''
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存当前参数值
|
||||||
|
const saveCurrentParams = () => {
|
||||||
|
if (pendingIndicator.value && pendingSource.value) {
|
||||||
|
const indicatorKey = `${pendingSource.value}-${pendingIndicator.value.id}`
|
||||||
|
// 深拷贝参数值,确保保存的是当前值
|
||||||
|
savedIndicatorParams.value[indicatorKey] = JSON.parse(JSON.stringify(indicatorParamValues.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗关闭后的处理
|
||||||
|
const handleParamsModalAfterClose = () => {
|
||||||
|
// 确保参数值已保存
|
||||||
|
saveCurrentParams()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 运行指标代码(从编辑器)
|
// 运行指标代码(从编辑器)
|
||||||
@@ -1891,6 +1942,28 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听参数值变化,实时保存
|
||||||
|
watch(
|
||||||
|
() => indicatorParamValues.value,
|
||||||
|
(newVal) => {
|
||||||
|
// 只有在弹窗打开且有 pendingIndicator 时才保存
|
||||||
|
if (showParamsModal.value && pendingIndicator.value && pendingSource.value) {
|
||||||
|
const indicatorKey = `${pendingSource.value}-${pendingIndicator.value.id}`
|
||||||
|
// 深拷贝保存,避免引用问题
|
||||||
|
savedIndicatorParams.value[indicatorKey] = JSON.parse(JSON.stringify(newVal))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: false }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听参数配置弹窗关闭
|
||||||
|
watch(showParamsModal, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
// 弹窗关闭时,确保参数值已保存
|
||||||
|
saveCurrentParams()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId,
|
userId,
|
||||||
klineChart,
|
klineChart,
|
||||||
@@ -1955,6 +2028,7 @@ getMarketColor,
|
|||||||
loadingParams,
|
loadingParams,
|
||||||
confirmIndicatorParams,
|
confirmIndicatorParams,
|
||||||
cancelIndicatorParams,
|
cancelIndicatorParams,
|
||||||
|
handleParamsModalAfterClose,
|
||||||
// 回测相关
|
// 回测相关
|
||||||
showBacktestModal,
|
showBacktestModal,
|
||||||
backtestIndicator,
|
backtestIndicator,
|
||||||
|
|||||||
Reference in New Issue
Block a user