Skip to content

Commit 1438dbd

Browse files
committed
fix: 淇鎸囨爣鍙傛暟閰嶇疆闂鍜孌ocker鏋勫缓闂
1 parent e5bb37b commit 1438dbd

File tree

6 files changed

+221
-5
lines changed

6 files changed

+221
-5
lines changed

backend_api_python/app/routes/indicator.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from app.utils.db import get_db_connection
2525
from app.utils.logger import get_logger
2626
from app.utils.auth import login_required
27+
from app.services.indicator_params import IndicatorCaller
2728
import requests
2829

2930
logger = get_logger(__name__)
@@ -659,3 +660,93 @@ def stream():
659660
"X-Accel-Buffering": "no",
660661
},
661662
)
663+
664+
665+
@indicator_bp.route("/callIndicator", methods=["POST"])
666+
@login_required
667+
def call_indicator():
668+
"""
669+
调用另一个指标(供前端 Pyodide 环境使用)
670+
671+
POST /api/indicator/callIndicator
672+
Body: {
673+
"indicatorRef": int | str, # 指标ID或名称
674+
"klineData": List[Dict], # K线数据
675+
"params": Dict, # 传递给被调用指标的参数(可选)
676+
"currentIndicatorId": int # 当前指标ID(用于循环依赖检测,可选)
677+
}
678+
679+
Returns:
680+
{
681+
"code": 1,
682+
"data": {
683+
"df": List[Dict], # 执行后的DataFrame(转换为JSON)
684+
"columns": List[str] # DataFrame的列名
685+
}
686+
}
687+
"""
688+
try:
689+
data = request.get_json() or {}
690+
indicator_ref = data.get("indicatorRef")
691+
kline_data = data.get("klineData", [])
692+
params = data.get("params") or {}
693+
current_indicator_id = data.get("currentIndicatorId")
694+
695+
if not indicator_ref:
696+
return jsonify({
697+
"code": 0,
698+
"msg": "indicatorRef is required",
699+
"data": None
700+
}), 400
701+
702+
if not kline_data or not isinstance(kline_data, list):
703+
return jsonify({
704+
"code": 0,
705+
"msg": "klineData must be a non-empty list",
706+
"data": None
707+
}), 400
708+
709+
# 获取用户ID
710+
user_id = g.user_id
711+
712+
# 创建 IndicatorCaller
713+
indicator_caller = IndicatorCaller(user_id, current_indicator_id)
714+
715+
# 将前端传入的K线数据转换为DataFrame
716+
df = pd.DataFrame(kline_data)
717+
718+
# 确保必要的列存在
719+
required_columns = ['open', 'high', 'low', 'close', 'volume']
720+
for col in required_columns:
721+
if col not in df.columns:
722+
df[col] = 0.0
723+
724+
# 转换数据类型
725+
df['open'] = df['open'].astype('float64')
726+
df['high'] = df['high'].astype('float64')
727+
df['low'] = df['low'].astype('float64')
728+
df['close'] = df['close'].astype('float64')
729+
df['volume'] = df['volume'].astype('float64')
730+
731+
# 调用指标
732+
result_df = indicator_caller.call_indicator(indicator_ref, df, params)
733+
734+
# 将DataFrame转换为JSON格式(前端可以使用的格式)
735+
result_dict = result_df.to_dict(orient='records')
736+
737+
return jsonify({
738+
"code": 1,
739+
"msg": "success",
740+
"data": {
741+
"df": result_dict,
742+
"columns": list(result_df.columns)
743+
}
744+
})
745+
746+
except Exception as e:
747+
logger.error(f"Error calling indicator: {e}", exc_info=True)
748+
return jsonify({
749+
"code": 0,
750+
"msg": str(e),
751+
"data": None
752+
}), 500

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ services:
6666
build:
6767
context: ./quantdinger_vue
6868
dockerfile: Dockerfile
69+
# Force pull from official registry to avoid mirror issues
70+
pull: true
6971
container_name: quantdinger-frontend
7072
restart: unless-stopped
7173
ports:

quantdinger_vue/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ COPY . .
1717
RUN npm run build
1818

1919
# Stage 2: Production image (using nginx)
20-
FROM nginx:alpine
20+
# Use specific version to avoid mirror registry issues
21+
FROM nginx:1.25-alpine
2122

2223
# Copy build artifacts
2324
COPY --from=builder /app/dist /usr/share/nginx/html

quantdinger_vue/build-docker.ps1

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# PowerShell script for building Docker image with registry fallback
2+
3+
Write-Host "Building frontend Docker image..." -ForegroundColor Green
4+
5+
# Try to build with --pull flag to force pull from official registry
6+
$buildResult = docker build `
7+
--pull `
8+
--platform linux/amd64 `
9+
-t quantdinger-frontend:latest `
10+
-f Dockerfile `
11+
.
12+
13+
if ($LASTEXITCODE -ne 0) {
14+
Write-Host "Build failed, trying with no-cache..." -ForegroundColor Yellow
15+
docker build `
16+
--no-cache `
17+
--platform linux/amd64 `
18+
-t quantdinger-frontend:latest `
19+
-f Dockerfile `
20+
.
21+
}
22+
23+
if ($LASTEXITCODE -eq 0) {
24+
Write-Host "Build successful!" -ForegroundColor Green
25+
} else {
26+
Write-Host "Build failed. Please check Docker registry configuration." -ForegroundColor Red
27+
}

quantdinger_vue/build-docker.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
# Docker build script with registry fallback
3+
4+
# Try to build with --pull flag to force pull from official registry
5+
echo "Building frontend Docker image..."
6+
docker build \
7+
--pull \
8+
--platform linux/amd64 \
9+
-t quantdinger-frontend:latest \
10+
-f Dockerfile \
11+
.
12+
13+
if [ $? -ne 0 ]; then
14+
echo "Build failed, trying with no-cache..."
15+
docker build \
16+
--no-cache \
17+
--platform linux/amd64 \
18+
-t quantdinger-frontend:latest \
19+
-f Dockerfile \
20+
.
21+
fi

quantdinger_vue/src/views/indicator-analysis/index.vue

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@
392392
:confirmLoading="loadingParams"
393393
@ok="confirmIndicatorParams"
394394
@cancel="cancelIndicatorParams"
395+
@afterClose="handleParamsModalAfterClose"
395396
:width="500"
396397
:maskClosable="false"
397398
:keyboard="false"
@@ -746,6 +747,8 @@ export default {
746747
const indicatorParams = ref([]) // 指标参数声明
747748
const indicatorParamValues = ref({}) // 用户设置的参数值
748749
const loadingParams = ref(false)
750+
// 保存每个指标的参数值(key: indicatorId, value: { paramName: paramValue })
751+
const savedIndicatorParams = ref({})
749752
750753
// 折叠状态
751754
const customSectionCollapsed = ref(false) // 我创建的指标区域是否折叠
@@ -1416,10 +1419,34 @@ export default {
14161419
if (res && res.code === 1 && Array.isArray(res.data) && res.data.length > 0) {
14171420
// 有参数,显示配置弹窗
14181421
indicatorParams.value = res.data
1419-
indicatorParamValues.value = {}
1422+
// 获取指标的唯一标识(用于保存参数值)
1423+
const indicatorKey = `${source}-${indicator.id}`
1424+
// 先检查是否有保存的参数值
1425+
const savedParams = savedIndicatorParams.value[indicatorKey]
1426+
// 先清空,然后逐个设置,确保响应式正常工作
1427+
const newParamValues = {}
14201428
res.data.forEach(p => {
1421-
indicatorParamValues.value[p.name] = p.default
1429+
// 如果有保存的值,使用保存的值;否则使用默认值
1430+
// 需要处理类型转换
1431+
let value = savedParams && savedParams[p.name] !== undefined
1432+
? savedParams[p.name]
1433+
: p.default
1434+
1435+
// 根据参数类型进行类型转换
1436+
if (p.type === 'int') {
1437+
value = parseInt(value) || 0
1438+
} else if (p.type === 'float') {
1439+
value = parseFloat(value) || 0.0
1440+
} else if (p.type === 'bool') {
1441+
value = value === true || value === 'true' || value === 1 || value === '1'
1442+
} else {
1443+
value = value || ''
1444+
}
1445+
1446+
newParamValues[p.name] = value
14221447
})
1448+
// 一次性设置所有值,确保响应式更新
1449+
indicatorParamValues.value = newParamValues
14231450
pendingIndicator.value = indicator
14241451
pendingSource.value = source
14251452
showParamsModal.value = true
@@ -1440,6 +1467,10 @@ export default {
14401467
// 确认参数配置并运行指标
14411468
const confirmIndicatorParams = () => {
14421469
if (pendingIndicator.value) {
1470+
// 保存参数值(用于下次打开时使用)
1471+
const indicatorKey = `${pendingSource.value}-${pendingIndicator.value.id}`
1472+
savedIndicatorParams.value[indicatorKey] = { ...indicatorParamValues.value }
1473+
14431474
// 将参数传递给指标
14441475
const indicatorWithParams = {
14451476
...pendingIndicator.value,
@@ -1454,9 +1485,29 @@ export default {
14541485
14551486
// 取消参数配置
14561487
const cancelIndicatorParams = () => {
1488+
// 保存参数值(在关闭前保存)
1489+
saveCurrentParams()
14571490
showParamsModal.value = false
1458-
pendingIndicator.value = null
1459-
pendingSource.value = ''
1491+
// 延迟清空,确保 afterClose 能访问到数据
1492+
setTimeout(() => {
1493+
pendingIndicator.value = null
1494+
pendingSource.value = ''
1495+
}, 100)
1496+
}
1497+
1498+
// 保存当前参数值
1499+
const saveCurrentParams = () => {
1500+
if (pendingIndicator.value && pendingSource.value) {
1501+
const indicatorKey = `${pendingSource.value}-${pendingIndicator.value.id}`
1502+
// 深拷贝参数值,确保保存的是当前值
1503+
savedIndicatorParams.value[indicatorKey] = JSON.parse(JSON.stringify(indicatorParamValues.value))
1504+
}
1505+
}
1506+
1507+
// 弹窗关闭后的处理
1508+
const handleParamsModalAfterClose = () => {
1509+
// 确保参数值已保存
1510+
saveCurrentParams()
14601511
}
14611512
14621513
// 运行指标代码(从编辑器)
@@ -1891,6 +1942,28 @@ export default {
18911942
}
18921943
})
18931944
1945+
// 监听参数值变化,实时保存
1946+
watch(
1947+
() => indicatorParamValues.value,
1948+
(newVal) => {
1949+
// 只有在弹窗打开且有 pendingIndicator 时才保存
1950+
if (showParamsModal.value && pendingIndicator.value && pendingSource.value) {
1951+
const indicatorKey = `${pendingSource.value}-${pendingIndicator.value.id}`
1952+
// 深拷贝保存,避免引用问题
1953+
savedIndicatorParams.value[indicatorKey] = JSON.parse(JSON.stringify(newVal))
1954+
}
1955+
},
1956+
{ deep: true, immediate: false }
1957+
)
1958+
1959+
// 监听参数配置弹窗关闭
1960+
watch(showParamsModal, (newVal) => {
1961+
if (!newVal) {
1962+
// 弹窗关闭时,确保参数值已保存
1963+
saveCurrentParams()
1964+
}
1965+
})
1966+
18941967
return {
18951968
userId,
18961969
klineChart,
@@ -1955,6 +2028,7 @@ getMarketColor,
19552028
loadingParams,
19562029
confirmIndicatorParams,
19572030
cancelIndicatorParams,
2031+
handleParamsModalAfterClose,
19582032
// 回测相关
19592033
showBacktestModal,
19602034
backtestIndicator,

0 commit comments

Comments
 (0)