Refactor: restructure market module with services, stores, and utils

This commit is contained in:
guaiwoluo2020
2026-03-19 09:47:41 +08:00
parent ef15b92342
commit b7ea37ad09
78 changed files with 10594 additions and 7645 deletions
+4 -8
View File
@@ -1,6 +1,6 @@
<template>
<v-app>
<v-app-bar app color="primary" dark>
<v-app-bar color="primary" dark>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>AITrader</v-toolbar-title>
<v-spacer></v-spacer>
@@ -9,20 +9,16 @@
</v-btn>
</v-app-bar>
<v-navigation-drawer v-model="drawer" app>
<v-navigation-drawer v-model="drawer">
<v-list>
<v-list-item
v-for="item in menuItems"
:key="item.title"
:to="item.path"
:prepend-icon="item.icon"
:title="item.title"
link
>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
+40
View File
@@ -229,6 +229,46 @@ export const marketAPI = {
async getTradeHistoryStatistics() {
const response = await api.get('/trade_history/statistics')
return response.data
},
// ==================== 策略配置 ====================
// 获取所有策略配置
async getStrategies() {
const response = await api.get('/strategy')
return response.data
},
// 获取品种策略配置
async getStrategy(symbol) {
const response = await api.get(`/strategy/${encodeURIComponent(symbol)}`)
return response.data
},
// 更新品种策略配置
async updateStrategy(symbol, data) {
const response = await api.post(`/strategy/${encodeURIComponent(symbol)}`, data)
return response.data
},
// 删除品种策略配置
async deleteStrategy(symbol) {
const response = await api.delete(`/strategy/${encodeURIComponent(symbol)}`)
return response.data
},
// 获取决策历史
async getDecisions(symbol = null, count = 20) {
const params = { count }
if (symbol) params.symbol = symbol
const response = await api.get('/strategy/decisions', { params })
return response.data
},
// 手动触发策略决策
async triggerStrategyDecision(symbol) {
const response = await api.post(`/strategy/trigger/${encodeURIComponent(symbol)}`)
return response.data
}
}
+202 -493
View File
@@ -18,7 +18,7 @@
>
<div class="d-flex align-center">
<v-icon small class="mr-2">mdi-lightning-bolt</v-icon>
<v-chip v-if="latestFlashNews.speaker" color="primary" x-small class="mr-2">
<v-chip v-if="latestFlashNews.speaker" color="primary" size="x-small" class="mr-2">
{{ latestFlashNews.speaker }}
</v-chip>
<span class="text-body-2">{{ latestFlashNews.content }}</span>
@@ -31,7 +31,7 @@
v-for="(impact, symbol) in latestFlashNews.impact"
:key="symbol"
:color="getImpactColor(impact.direction)"
x-small
size="x-small"
class="mr-1"
>
{{ symbol }}: {{ impact.direction }}
@@ -53,10 +53,10 @@
>
<div class="d-flex align-center">
<v-icon small class="mr-2">mdi-calendar-alert</v-icon>
<v-chip color="error" x-small class="mr-2">
<v-chip color="error" size="x-small" class="mr-2">
重要性 {{ topCalendarEvent.importance }}
</v-chip>
<v-chip :color="getCurrencyColor(topCalendarEvent.currency)" x-small class="mr-2">
<v-chip :color="getCurrencyColor(topCalendarEvent.currency)" size="x-small" class="mr-2">
{{ topCalendarEvent.currency }}
</v-chip>
<span class="text-body-2 font-weight-medium">{{ topCalendarEvent.name }}</span>
@@ -81,7 +81,7 @@
<div v-if="topCalendarEvent.result" class="mt-1">
<v-chip
:color="getEventResultColor(topCalendarEvent.result)"
x-small
size="x-small"
>
{{ getEventResultLabel(topCalendarEvent.result) }}
</v-chip>
@@ -90,206 +90,68 @@
</v-col>
</v-row>
<!-- 转折点提醒通知 -->
<v-row v-if="pivotAlerts.length > 0">
<!-- 交易决策提醒 -->
<v-row v-if="decisionAlerts.length > 0">
<v-col cols="12">
<v-alert
v-for="(alert, index) in pivotAlerts"
v-for="(alert, index) in decisionAlerts"
:key="index"
:type="alert.direction === 'high' ? 'warning' : 'info'"
:type="alert.action === 'buy' ? 'success' : 'warning'"
dismissible
class="mb-2"
@input="removeAlert(index)"
@input="removeDecisionAlert(index)"
>
<div class="d-flex flex-wrap align-center">
<v-icon small class="mr-1">mdi-chart-line</v-icon>
<strong>{{ alert.symbol }}</strong>
<v-chip small :color="alert.direction === 'high' ? 'error' : 'success'" class="ml-2">
接近{{ alert.direction === 'high' ? '高点' : '低点' }}
<v-chip small :color="alert.action === 'buy' ? 'success' : 'error'" class="ml-2">
{{ alert.action === 'buy' ? '买入' : '卖出' }}
</v-chip>
<v-chip small :color="getPivotPeriodColor(alert.period)" class="ml-2">
{{ alert.period }}
<v-chip v-if="alert.confidence" small color="primary" class="ml-2">
置信度: {{ alert.confidence }}%
</v-chip>
</div>
<div class="mt-1">
<span class="text-caption">转折点: <strong>{{ alert.pivot_price }}</strong></span>
<span class="text-caption ml-4">当前价格: <strong>{{ alert.current_price }}</strong></span>
<span class="text-caption ml-4">距离: <strong>{{ (alert.distance_pct * 100).toFixed(2) }}%</strong></span>
</div>
<!-- 显示各周期信息如果有合并数据 -->
<div v-if="alert.periods && alert.periods.length > 0" class="mt-2">
<div class="mt-2">
<span class="text-caption mr-4">入场价: <strong>{{ alert.price?.toFixed(2) }}</strong></span>
<span class="text-caption mr-4">止损: <strong>{{ alert.sl?.toFixed(2) }}</strong></span>
<span class="text-caption mr-4">止盈: <strong>{{ alert.tp?.toFixed(2) }}</strong></span>
<span v-if="alert.risk_reward_ratio" class="text-caption">
盈亏比: <strong>{{ alert.risk_reward_ratio }}</strong>
</span>
</div>
<!-- 信号来源 -->
<div v-if="alert.signals && alert.signals.length > 0" class="mt-2">
<span class="text-caption mr-2">信号来源:</span>
<v-chip
v-for="p in alert.periods"
:key="p.period"
x-small
class="mr-1 mb-1"
:color="getPivotPeriodColor(p.period)"
v-for="(signal, sIdx) in getVisibleSignals(alert)"
:key="sIdx"
size="x-small"
class="mr-1"
:color="getSignalSourceColor(signal.source)"
>
{{ p.period }}: {{ p.price }} ({{ p.distance_pct }}%)
{{ formatSignalLabel(signal) }}
</v-chip>
<v-btn
v-if="alert.signals.length > 3"
size="x-small"
variant="text"
class="ml-1"
@click="toggleSignalExpand(index)"
>
{{ isSignalExpanded(index) ? '收起' : `+${alert.signals.length - 3} 更多` }}
<v-icon end small>{{ isSignalExpanded(index) ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
</v-btn>
</div>
<!-- 如果有待确认订单显示订单信息和操作按钮 -->
<div v-if="alert.pending_order" class="mt-3 pa-2 grey lighten-4 rounded">
<!-- 待确认订单操作 -->
<div v-if="alert.pending_order && !alert.pending_order.confirmed" class="mt-3 pa-2 grey lighten-4 rounded">
<div class="text-subtitle-2 font-weight-bold mb-2">
<v-icon small color="primary" class="mr-1">mdi-file-document-edit</v-icon>
自动生成交易指令
待确认订单
</div>
<!-- AI与技术信号比较 -->
<div v-if="alert.pending_order.ai_driven" class="mb-2">
<div class="d-flex align-center mb-1">
<span class="text-caption mr-2">技术信号:</span>
<v-chip :color="alert.pending_order.tech_action === '买入' ? 'success' : 'error'" x-small>
{{ alert.pending_order.tech_action }}
</v-chip>
<span class="text-caption mx-2">AI建议:</span>
<v-chip :color="alert.pending_order.ai_direction === '买入' ? 'success' : 'error'" x-small>
{{ alert.pending_order.ai_direction }}
</v-chip>
</div>
</div>
<!-- AI冲突警告 -->
<v-alert v-if="alert.pending_order.ai_conflict" type="warning" dense class="mb-2">
<div class="d-flex align-center">
<v-icon small class="mr-1">mdi-alert-circle</v-icon>
<strong>AI与技术信号冲突以AI方向为准</strong>
</div>
<div v-if="alert.pending_order.ai_reason" class="text-caption mt-1">
AI理由: {{ alert.pending_order.ai_reason }}
</div>
</v-alert>
<!-- AI一致提示 -->
<v-alert v-if="alert.pending_order.ai_aligned" type="success" dense class="mb-2">
<div class="d-flex align-center">
<v-icon small class="mr-1">mdi-check-circle</v-icon>
<strong>AI与技术信号一致</strong>
</div>
<div v-if="alert.pending_order.ai_reason" class="text-caption mt-1">
AI理由: {{ alert.pending_order.ai_reason }}
</div>
</v-alert>
<div class="mb-2">
<v-chip :color="alert.pending_order.action === 'b' ? 'success' : 'error'" x-small class="mr-2">
{{ alert.pending_order.action === 'b' ? '买入' : '卖出' }}
</v-chip>
<span class="mr-3">价格: {{ alert.pending_order.price?.toFixed(2) }}</span>
</div>
<!-- 可编辑字段 -->
<v-row dense class="mb-2">
<v-col cols="3">
<v-text-field
v-model.number="alert.pending_order.mount"
label="手数"
type="number"
step="0.01"
min="0.01"
dense
hide-details
outlined
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="alert.pending_order.sl"
label="止损"
type="number"
step="0.01"
dense
hide-details
outlined
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="alert.pending_order.tp"
label="止盈"
type="number"
step="0.01"
dense
hide-details
outlined
:hint="alert.pending_order.ai_aligned ? 'AI建议' : ''"
persistent-hint
></v-text-field>
</v-col>
<v-col cols="3" class="d-flex align-center">
<v-btn
color="success"
small
class="mr-1"
:loading="confirmingOrderId === alert.pending_order.order_id"
@click="confirmAlertOrder(alert.pending_order, index)"
>
<v-icon left small>mdi-check</v-icon>
确认
</v-btn>
<v-btn
color="error"
small
outlined
:loading="rejectingOrderId === alert.pending_order.order_id"
@click="rejectAlertOrder(alert.pending_order.order_id, index)"
>
放弃
</v-btn>
</v-col>
</v-row>
<div class="text-caption grey--text mb-1">
{{ alert.pending_order.reason }}
</div>
<div class="text-caption grey--text">
<v-icon small>mdi-clock-outline</v-icon>
3分钟内未操作将自动移除
</div>
</div>
</v-alert>
</v-col>
</v-row>
<!-- AI入场价提醒通知 -->
<v-row v-if="aiEntryAlerts.length > 0">
<v-col cols="12">
<v-alert
v-for="(alert, index) in aiEntryAlerts"
:key="'ai-'+index"
type="info"
dismissible
class="mb-2"
@input="removeAiEntryAlert(index)"
>
<div class="d-flex flex-wrap align-center">
<v-icon small color="white" class="mr-1">mdi-robot</v-icon>
<strong>{{ alert.symbol }} {{ alert.period }}</strong>
<span class="ml-2">AI建议入场</span>
<v-chip small class="ml-2" :color="alert.direction === 'buy' ? 'success' : 'error'">
{{ alert.direction === 'buy' ? '买入' : '卖出' }}
</v-chip>
<span class="ml-2">入场价: {{ alert.entry_price }}</span>
<span class="ml-2">当前价: {{ alert.current_price }}</span>
<span class="ml-2">差距: {{ alert.price_diff_pct }}%</span>
</div>
<!-- 如果有待确认订单显示订单信息和操作按钮 -->
<div v-if="alert.pending_order" class="mt-3 pa-2 grey lighten-4 rounded">
<div class="text-subtitle-2 font-weight-bold mb-2">
<v-icon small color="primary" class="mr-1">mdi-file-document-edit</v-icon>
AI交易建议
</div>
<div class="mb-2">
<v-chip :color="alert.pending_order.action === 'b' ? 'success' : 'error'" x-small class="mr-2">
{{ alert.pending_order.action === 'b' ? '买入' : '卖出' }}
</v-chip>
<span class="mr-3">入场价: {{ alert.pending_order.price?.toFixed(2) }}</span>
<span class="mr-3">止损: {{ alert.pending_order.sl }}</span>
<span class="mr-3">止盈: {{ alert.pending_order.tp }}</span>
</div>
<!-- 可编辑字段 -->
<v-row dense class="mb-2">
<v-col cols="3">
<v-text-field
@@ -331,9 +193,9 @@
small
class="mr-1"
:loading="confirmingOrderId === alert.pending_order.order_id"
@click="confirmAiEntryOrder(alert.pending_order, index)"
@click="confirmDecisionOrder(alert.pending_order, index)"
>
<v-icon left small>mdi-check</v-icon>
<v-icon start small>mdi-check</v-icon>
确认
</v-btn>
<v-btn
@@ -341,167 +203,28 @@
small
outlined
:loading="rejectingOrderId === alert.pending_order.order_id"
@click="rejectAiEntryOrder(alert.pending_order.order_id, index)"
@click="rejectDecisionOrder(alert.pending_order.order_id, index)"
>
放弃
</v-btn>
</v-col>
</v-row>
<div class="text-caption grey--text mb-1">
<v-icon small class="mr-1">mdi-lightbulb</v-icon>
{{ alert.reason }}
</div>
<div class="text-caption grey--text">
<v-icon small>mdi-clock-outline</v-icon>
3分钟内未操作将自动移除
{{ formatTime(alert.timestamp) }}
</div>
</div>
</v-alert>
</v-col>
</v-row>
<!-- 关键点位订单提醒 -->
<v-row v-if="keyLevelAlerts.length > 0">
<v-col cols="12">
<v-alert
v-for="(alert, index) in keyLevelAlerts"
:key="'key-'+index"
type="success"
dismissible
class="mb-2"
@input="removeKeyLevelAlert(index)"
>
<div class="d-flex flex-wrap align-center">
<v-icon small color="white" class="mr-1">mdi-chart-line</v-icon>
<strong>{{ alert.symbol }}</strong>
<v-chip small :color="alert.action === 'b' ? 'success' : 'error'" class="ml-2">
{{ alert.action_text }}
<!-- 已确认状态 -->
<div v-if="alert.pending_order?.confirmed" class="mt-2">
<v-chip small color="success">
<v-icon start small>mdi-check-circle</v-icon>
已确认等待执行
</v-chip>
<span class="ml-2">关键点位策略</span>
</div>
<div class="mt-1">
<span class="text-caption">关键位: <strong>{{ alert.key_level }}</strong></span>
<span class="text-caption ml-4">入场价: <strong>{{ alert.price?.toFixed(2) }}</strong></span>
<span class="text-caption ml-4">距离: <strong>{{ alert.distance_pct }}%</strong></span>
</div>
<!-- 如果有待确认订单显示订单信息和操作按钮 -->
<div v-if="alert.pending_order" class="mt-3 pa-2 grey lighten-4 rounded">
<div class="text-subtitle-2 font-weight-bold mb-2">
<v-icon small color="primary" class="mr-1">mdi-file-document-edit</v-icon>
关键点位交易建议
</div>
<div class="mb-2">
<v-chip :color="alert.pending_order.action === 'b' ? 'success' : 'error'" x-small class="mr-2">
{{ alert.pending_order.action === 'b' ? '买入' : '卖出' }}
</v-chip>
<span class="mr-3">入场价: {{ alert.pending_order.price?.toFixed(2) }}</span>
<span class="mr-3">止损: {{ alert.pending_order.sl }}</span>
<span class="mr-3">止盈: {{ alert.pending_order.tp }}</span>
</div>
<!-- AI方向对比 -->
<div class="mb-2 pa-2 white rounded">
<div class="text-caption font-weight-bold mb-1">
<v-icon small class="mr-1">mdi-robot</v-icon>
AI方向对比
</div>
<div v-if="alert.pending_order.ai_directions && Object.keys(alert.pending_order.ai_directions).length > 0" class="d-flex flex-wrap align-center">
<span class="text-caption mr-2">关键点位: <strong :class="alert.pending_order.action === 'b' ? 'success--text' : 'error--text'">{{ alert.pending_order.key_level_direction_text }}</strong></span>
<template v-for="(dirInfo, period) in alert.pending_order.ai_directions" :key="period">
<v-chip
:color="dirInfo.direction === 'buy' ? 'success' : 'error'"
x-small
outlined
class="mr-1"
>
{{ period }}: {{ dirInfo.text }}
</v-chip>
</template>
</div>
<div v-else class="text-caption grey--text">
关键点位: <strong :class="alert.pending_order.action === 'b' ? 'success--text' : 'error--text'">{{ alert.pending_order.key_level_direction_text }}</strong>
<span class="ml-2">AI暂无分析数据</span>
</div>
<div v-if="alert.pending_order.recommendation" class="mt-1">
<v-chip
:color="alert.pending_order.recommendation_color || 'grey'"
x-small
dark
>
<v-icon x-small left>mdi-lightbulb</v-icon>
{{ alert.pending_order.recommendation }}
</v-chip>
</div>
</div>
<!-- 可编辑字段 -->
<v-row dense class="mb-2">
<v-col cols="3">
<v-text-field
v-model.number="alert.pending_order.mount"
label="手数"
type="number"
step="0.01"
min="0.01"
dense
hide-details
outlined
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="alert.pending_order.sl"
label="止损"
type="number"
step="0.01"
dense
hide-details
outlined
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="alert.pending_order.tp"
label="止盈"
type="number"
step="0.01"
dense
hide-details
outlined
></v-text-field>
</v-col>
<v-col cols="3" class="d-flex align-center">
<v-btn
color="success"
small
class="mr-1"
:loading="confirmingOrderId === alert.pending_order.order_id"
@click="confirmKeyLevelOrder(alert.pending_order, index)"
>
<v-icon left small>mdi-check</v-icon>
确认
</v-btn>
<v-btn
color="error"
small
outlined
:loading="rejectingOrderId === alert.pending_order.order_id"
@click="rejectKeyLevelOrder(alert.pending_order.order_id, index)"
>
放弃
</v-btn>
</v-col>
</v-row>
<div class="text-caption grey--text mb-1">
<v-icon small class="mr-1">mdi-lightbulb</v-icon>
{{ alert.reason }}
</div>
<div class="text-caption grey--text">
<v-icon small>mdi-clock-outline</v-icon>
3分钟内未操作将自动移除
</div>
</div>
</v-alert>
</v-col>
@@ -515,7 +238,7 @@
small
class="mr-2"
>
<v-icon left small>mdi-lan-connect</v-icon>
<v-icon start small>mdi-lan-connect</v-icon>
{{ wsConnected ? 'WebSocket 已连接' : 'WebSocket 断开' }}
</v-chip>
</v-col>
@@ -562,15 +285,15 @@
<div v-else>
<v-expansion-panels>
<v-expansion-panel v-for="(data, symbol) in llmAnalysis" :key="symbol">
<v-expansion-panel-header>
<v-expansion-panel-title>
<div class="d-flex align-center">
<strong class="mr-3">{{ symbol }}</strong>
<v-chip
v-if="data.analysis && data.analysis.overall_trend"
:color="getTrendChipColor(data.analysis.overall_trend.direction)"
v-if="data.overall_trend"
:color="getTrendChipColor(data.overall_trend.direction)"
small
>
{{ data.analysis.overall_trend.direction }}
{{ data.overall_trend.direction }}
</v-chip>
<!-- 休市状态 -->
<v-chip
@@ -579,7 +302,7 @@
small
class="ml-2"
>
<v-icon left x-small>mdi-pause-circle</v-icon>
<v-icon start size="x-small">mdi-pause-circle</v-icon>
休市中
</v-chip>
<!-- 数据未更新 -->
@@ -589,12 +312,12 @@
small
class="ml-2"
>
<v-icon left x-small>mdi-alert</v-icon>
<v-icon start size="x-small">mdi-alert</v-icon>
数据未更新
</v-chip>
</div>
</v-expansion-panel-header>
<v-expansion-panel-content>
</v-expansion-panel-title>
<v-expansion-panel-text>
<!-- 休市提示 -->
<v-alert
v-if="data.market_status === 'closed'"
@@ -626,9 +349,9 @@
</v-alert>
<!-- 各周期趋势休市时可能没有分析结果-->
<div v-if="data.analysis && data.analysis.trend_analysis" class="mb-4">
<div v-if="data.trend_analysis" class="mb-4">
<div class="text-subtitle-2 mb-2">各周期趋势</div>
<v-simple-table dense>
<v-table density="compact">
<template v-slot:default>
<thead>
<tr>
@@ -642,10 +365,10 @@
</tr>
</thead>
<tbody>
<tr v-for="(trend, period) in data.analysis.trend_analysis" :key="period">
<tr v-for="(trend, period) in data.trend_analysis" :key="period">
<td><strong>{{ period }}</strong></td>
<td>
<v-chip :color="getTrendChipColor(trend.trend)" x-small>
<v-chip :color="getTrendChipColor(trend.trend)" size="x-small">
{{ trend.trend }}
</v-chip>
</td>
@@ -659,7 +382,7 @@
<v-chip
v-if="getTechTrend(symbol, period)"
:color="getTrendColor(getTechTrend(symbol, period).trend)"
x-small
size="x-small"
>
{{ getTrendLabel(getTechTrend(symbol, period).trend) }}
</v-chip>
@@ -674,7 +397,7 @@
<td>
<v-chip
:color="getConclusionColor(symbol, period, trend)"
x-small
size="x-small"
>
{{ getConclusion(symbol, period, trend) }}
</v-chip>
@@ -682,32 +405,32 @@
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
</div>
<!-- 关键价位 -->
<div v-if="data.analysis && data.analysis.key_levels" class="mb-4">
<div v-if="data.key_levels" class="mb-4">
<div class="text-subtitle-2 mb-2">关键价位</div>
<v-row>
<v-col cols="6">
<div class="text-caption grey--text">压力位</div>
<div v-for="(level, i) in data.analysis.key_levels.resistance" :key="'r'+i">
<v-chip color="error" x-small class="mr-1">{{ level }}</v-chip>
<div v-for="(level, i) in data.key_levels.resistance" :key="'r'+i">
<v-chip color="error" size="x-small" class="mr-1">{{ level }}</v-chip>
</div>
</v-col>
<v-col cols="6">
<div class="text-caption grey--text">支撑位</div>
<div v-for="(level, i) in data.analysis.key_levels.support" :key="'s'+i">
<v-chip color="success" x-small class="mr-1">{{ level }}</v-chip>
<div v-for="(level, i) in data.key_levels.support" :key="'s'+i">
<v-chip color="success" size="x-small" class="mr-1">{{ level }}</v-chip>
</div>
</v-col>
</v-row>
</div>
<!-- 交易建议 -->
<div v-if="data.analysis && data.analysis.trade_suggestions && data.analysis.trade_suggestions.length > 0">
<div v-if="data.trade_suggestions && data.trade_suggestions.length > 0">
<div class="text-subtitle-2 mb-2">交易建议</div>
<v-simple-table dense>
<v-table density="compact">
<template v-slot:default>
<thead>
<tr>
@@ -720,10 +443,10 @@
</tr>
</thead>
<tbody>
<tr v-for="(suggestion, i) in data.analysis.trade_suggestions" :key="i">
<tr v-for="(suggestion, i) in data.trade_suggestions" :key="i">
<td>{{ suggestion.period }}</td>
<td>
<v-chip :color="suggestion.direction === 'buy' ? 'success' : 'error'" x-small>
<v-chip :color="suggestion.direction === 'buy' ? 'success' : 'error'" size="x-small">
{{ suggestion.direction === 'buy' ? '买入' : '卖出' }}
</v-chip>
</td>
@@ -734,13 +457,13 @@
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
</div>
<div class="text-caption grey--text mt-2">
分析时间: {{ data.analyzed_at }}
</div>
</v-expansion-panel-content>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</div>
@@ -767,9 +490,8 @@ export default {
const allPivots = ref([])
const marketStatus = ref({})
const thresholds = ref({})
const pivotAlerts = ref([])
const aiEntryAlerts = ref([])
const keyLevelAlerts = ref([]) // 关键点位订单提醒
const decisionAlerts = ref([]) // 统一的决策提醒列表
const expandedSignals = ref(new Set()) // 展开信号的状态
const loading = ref(false)
const showError = ref(false)
const errorMessage = ref('')
@@ -838,28 +560,54 @@ export default {
ws.value = marketAPI.createWebSocket(
// onMessage
(data) => {
if (data.type === 'pivot_alert') {
// 添加新的提醒到列表顶部
pivotAlerts.value.unshift(data)
// 只保留最近10条
if (pivotAlerts.value.length > 10) {
pivotAlerts.value.pop()
if (data.type === 'trading_decision') {
// 策略层产生的交易决策
console.log('收到交易决策:', data)
const decision = data.data
// 转换为统一的 alert 格式
const alert = {
type: 'trading_decision',
decision_id: decision.decision_id,
symbol: decision.symbol,
action: decision.action,
price: decision.entry_price,
sl: decision.sl,
tp: decision.tp,
volume: decision.volume,
reason: decision.decision_reason,
confidence: decision.confidence_score,
signals: decision.signals || [],
signal_summary: decision.signal_summary || {},
risk_reward_ratio: decision.risk_reward_ratio,
timestamp: decision.timestamp || new Date().toISOString(),
pending_order: {
order_id: decision.decision_id,
action: decision.action === 'buy' ? 'b' : 's',
price: decision.entry_price,
sl: decision.sl,
tp: decision.tp,
mount: decision.volume,
reason: decision.decision_reason
}
}
} else if (data.type === 'ai_entry_alert') {
// AI入场价提醒
console.log('收到AI入场价提醒:', data)
aiEntryAlerts.value.unshift(data)
// 只保留最近10条
if (aiEntryAlerts.value.length > 10) {
aiEntryAlerts.value.pop()
decisionAlerts.value.unshift(alert)
if (decisionAlerts.value.length > 10) {
decisionAlerts.value.pop()
}
} else if (data.type === 'key_level_alert') {
// 关键点位订单提醒
console.log('收到关键点位订单提醒:', data)
keyLevelAlerts.value.unshift(data)
// 只保留最近10条
if (keyLevelAlerts.value.length > 10) {
keyLevelAlerts.value.pop()
} else if (data.type === 'pending_order') {
// 订单状态更新(确认后)
console.log('收到订单更新:', data)
const order = data.data
// 更新对应的 alert
const alertIndex = decisionAlerts.value.findIndex(
a => a.pending_order?.order_id === order.order_id
)
if (alertIndex >= 0) {
decisionAlerts.value[alertIndex].pending_order = {
...decisionAlerts.value[alertIndex].pending_order,
...order,
confirmed: true
}
}
} else if (data.type === 'connected') {
wsConnected.value = true
@@ -1240,24 +988,21 @@ export default {
}
}
// 从提醒列表中移除
const removeAlert = (index) => {
pivotAlerts.value.splice(index, 1)
// 决策提醒操作
const removeDecisionAlert = (index) => {
decisionAlerts.value.splice(index, 1)
}
// 确认提醒中的订单(可修改参数)
const confirmAlertOrder = async (order, alertIndex) => {
const confirmDecisionOrder = async (order, alertIndex) => {
confirmingOrderId.value = order.order_id
try {
// 发送修改后的订单数据
const data = await marketAPI.confirmOrderWithUpdate(order.order_id, {
mount: order.mount,
sl: order.sl,
tp: order.tp
})
if (data.status === 'ok') {
// 移除该提醒
pivotAlerts.value.splice(alertIndex, 1)
decisionAlerts.value.splice(alertIndex, 1)
await loadPendingOrders()
} else {
errorMessage.value = data.message || '确认订单失败'
@@ -1271,14 +1016,12 @@ export default {
}
}
// 放弃提醒中的订单
const rejectAlertOrder = async (orderId, alertIndex) => {
const rejectDecisionOrder = async (orderId, alertIndex) => {
rejectingOrderId.value = orderId
try {
const data = await marketAPI.rejectOrder(orderId)
if (data.status === 'ok') {
// 移除该提醒
pivotAlerts.value.splice(alertIndex, 1)
decisionAlerts.value.splice(alertIndex, 1)
await loadPendingOrders()
} else {
errorMessage.value = data.message || '放弃订单失败'
@@ -1292,98 +1035,67 @@ export default {
}
}
// AI入场价提醒操作
const removeAiEntryAlert = (index) => {
aiEntryAlerts.value.splice(index, 1)
}
const confirmAiEntryOrder = async (order, alertIndex) => {
confirmingOrderId.value = order.order_id
try {
const data = await marketAPI.confirmOrderWithUpdate(order.order_id, {
mount: order.mount,
sl: order.sl,
tp: order.tp
})
if (data.status === 'ok') {
aiEntryAlerts.value.splice(alertIndex, 1)
await loadPendingOrders()
} else {
errorMessage.value = data.message || '确认订单失败'
showError.value = true
}
} catch (err) {
errorMessage.value = `确认订单失败: ${err.message}`
showError.value = true
} finally {
confirmingOrderId.value = null
// 辅助方法
const getSignalSourceColor = (source) => {
const colors = {
'pivot': 'primary',
'key_level': 'success',
'ai_entry': 'info'
}
return colors[source] || 'grey'
}
const rejectAiEntryOrder = async (orderId, alertIndex) => {
rejectingOrderId.value = orderId
try {
const data = await marketAPI.rejectOrder(orderId)
if (data.status === 'ok') {
aiEntryAlerts.value.splice(alertIndex, 1)
await loadPendingOrders()
} else {
errorMessage.value = data.message || '放弃订单失败'
showError.value = true
}
} catch (err) {
errorMessage.value = `放弃订单失败: ${err.message}`
showError.value = true
} finally {
rejectingOrderId.value = null
// 格式化信号标签(显示周期)
const formatSignalLabel = (signal) => {
const source = signal.source || ''
const period = signal.source_period || signal.ai_analysis_period || ''
const confidence = signal.confidence || 0
// 显示信号源名称
const sourceNames = {
'pivot': 'Pivot',
'key_level': 'KeyLevel',
'ai_entry': 'AI'
}
}
const sourceName = sourceNames[source] || source
// 关键点位订单提醒操作
const removeKeyLevelAlert = (index) => {
keyLevelAlerts.value.splice(index, 1)
}
const confirmKeyLevelOrder = async (order, alertIndex) => {
confirmingOrderId.value = order.order_id
try {
const data = await marketAPI.confirmOrderWithUpdate(order.order_id, {
mount: order.mount,
sl: order.sl,
tp: order.tp
})
if (data.status === 'ok') {
keyLevelAlerts.value.splice(alertIndex, 1)
await loadPendingOrders()
} else {
errorMessage.value = data.message || '确认订单失败'
showError.value = true
}
} catch (err) {
errorMessage.value = `确认订单失败: ${err.message}`
showError.value = true
} finally {
confirmingOrderId.value = null
// 如果有周期,显示周期
if (period) {
return `${sourceName}[${period}] (${confidence}%)`
}
return `${sourceName} (${confidence}%)`
}
const rejectKeyLevelOrder = async (orderId, alertIndex) => {
rejectingOrderId.value = orderId
try {
const data = await marketAPI.rejectOrder(orderId)
if (data.status === 'ok') {
keyLevelAlerts.value.splice(alertIndex, 1)
await loadPendingOrders()
} else {
errorMessage.value = data.message || '放弃订单失败'
showError.value = true
}
} catch (err) {
errorMessage.value = `放弃订单失败: ${err.message}`
showError.value = true
} finally {
rejectingOrderId.value = null
// 获取可见的信号列表
const getVisibleSignals = (alert) => {
if (!alert.signals) return []
const alertIndex = decisionAlerts.value.indexOf(alert)
if (expandedSignals.value.has(alertIndex)) {
return alert.signals
}
return alert.signals.slice(0, 3)
}
// 切换信号展开状态
const toggleSignalExpand = (index) => {
if (expandedSignals.value.has(index)) {
expandedSignals.value.delete(index)
} else {
expandedSignals.value.add(index)
}
// 触发响应式更新
expandedSignals.value = new Set(expandedSignals.value)
}
// 检查信号是否展开
const isSignalExpanded = (index) => {
return expandedSignals.value.has(index)
}
const formatTime = (timestamp) => {
if (!timestamp) return ''
const date = new Date(timestamp)
return date.toLocaleString('zh-CN')
}
const getTrendColor = (trend) => {
@@ -1439,7 +1151,7 @@ export default {
lowPivots,
marketStatus,
thresholds,
pivotAlerts,
decisionAlerts,
loading,
showError,
errorMessage,
@@ -1455,22 +1167,19 @@ export default {
rejectOrder,
getTrendColor,
getTrendLabel,
// 提醒订单操作
// 决策订单操作
confirmingOrderId,
rejectingOrderId,
removeAlert,
confirmAlertOrder,
rejectAlertOrder,
// AI入场价提醒
aiEntryAlerts,
removeAiEntryAlert,
confirmAiEntryOrder,
rejectAiEntryOrder,
// 关键点位订单提醒
keyLevelAlerts,
removeKeyLevelAlert,
confirmKeyLevelOrder,
rejectKeyLevelOrder,
removeDecisionAlert,
confirmDecisionOrder,
rejectDecisionOrder,
getSignalSourceColor,
formatTime,
// 信号显示
formatSignalLabel,
getVisibleSignals,
toggleSignalExpand,
isSignalExpanded,
// 大模型分析
llmStatus,
llmAnalysis,
+14 -14
View File
@@ -62,11 +62,11 @@
<!-- 持仓列表 -->
<v-card-text>
<v-btn color="primary" small class="mb-3" @click="loadPositions" :loading="loading">
<v-icon left small>mdi-refresh</v-icon>
<v-icon start small>mdi-refresh</v-icon>
刷新
</v-btn>
<v-simple-table v-if="positions.length > 0">
<v-table v-if="positions.length > 0">
<template v-slot:default>
<thead>
<tr>
@@ -87,7 +87,7 @@
<td>{{ pos.ticket }}</td>
<td><strong>{{ pos.symbol }}</strong></td>
<td>
<v-chip x-small :color="pos.type === 'BUY' ? 'success' : 'error'">
<v-chip size="x-small" :color="pos.type === 'BUY' ? 'success' : 'error'">
{{ pos.type === 'BUY' ? '买入' : '卖出' }}
</v-chip>
</td>
@@ -100,14 +100,14 @@
<td>{{ pos.distance_tp || '-' }}</td>
<td>{{ formatTime(pos.updated_at) }}</td>
<td>
<v-btn x-small color="error" outlined @click="closePosition(pos)">
<v-btn size="x-small" color="error" outlined @click="closePosition(pos)">
平仓
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
<div v-else class="text-center grey--text py-8">
<v-icon large>mdi-folder-open-outline</v-icon>
<div class="mt-2">暂无持仓</div>
@@ -119,7 +119,7 @@
<v-tab-item>
<v-card-text>
<v-btn color="primary" small class="mb-3" @click="loadTradeHistory" :loading="historyLoading">
<v-icon left small>mdi-refresh</v-icon>
<v-icon start small>mdi-refresh</v-icon>
刷新
</v-btn>
@@ -242,7 +242,7 @@
<!-- 成交列表 -->
<div class="text-subtitle-1 font-weight-bold mb-2">成交记录</div>
<v-simple-table v-if="tradeDeals.length > 0" fixed-header height="400">
<v-table v-if="tradeDeals.length > 0" fixed-header height="400">
<template v-slot:default>
<thead>
<tr>
@@ -263,12 +263,12 @@
<td>{{ deal.ticket }}</td>
<td><strong>{{ deal.symbol }}</strong></td>
<td>
<v-chip x-small :color="deal.type === 0 ? 'success' : 'error'">
<v-chip size="x-small" :color="deal.type === 0 ? 'success' : 'error'">
{{ deal.type_text }}
</v-chip>
</td>
<td>
<v-chip x-small outlined :color="deal.entry === 1 ? 'warning' : 'info'">
<v-chip size="x-small" outlined :color="deal.entry === 1 ? 'warning' : 'info'">
{{ deal.entry_text }}
</v-chip>
</td>
@@ -280,16 +280,16 @@
<td>{{ deal.commission.toFixed(2) }}</td>
<td>{{ deal.time }}</td>
<td>
<v-chip v-if="deal.order_source === '自动'" x-small color="primary">
<v-chip v-if="deal.order_source === '自动'" size="x-small" color="primary">
{{ deal.comment }}
</v-chip>
<v-chip v-else-if="deal.order_source === '止损触发'" x-small color="error" outlined>
<v-chip v-else-if="deal.order_source === '止损触发'" size="x-small" color="error" outlined>
{{ deal.comment }}
</v-chip>
<v-chip v-else-if="deal.order_source === '止盈触发'" x-small color="success" outlined>
<v-chip v-else-if="deal.order_source === '止盈触发'" size="x-small" color="success" outlined>
{{ deal.comment }}
</v-chip>
<v-chip v-else-if="deal.order_source === '强制平仓'" x-small color="error" dark>
<v-chip v-else-if="deal.order_source === '强制平仓'" size="x-small" color="error" dark>
{{ deal.comment }}
</v-chip>
<span v-else class="grey--text">{{ deal.order_source }}</span>
@@ -297,7 +297,7 @@
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
<div v-else class="text-center grey--text py-8">
<v-icon large>mdi-history</v-icon>
<div class="mt-2">暂无历史交易数据</div>
+596 -15
View File
@@ -27,7 +27,7 @@
<!-- 品种配置表格 -->
<div class="text-subtitle-2 mt-2 mb-2">品种配置</div>
<v-simple-table dense>
<v-table density="compact">
<template v-slot:default>
<thead>
<tr>
@@ -88,13 +88,13 @@
></v-text-field>
</td>
<td>
<v-btn x-small color="primary" @click="saveTradeConfig">保存</v-btn>
<v-btn x-small color="error" outlined class="ml-1" @click="removeSymbolConfig(symbol)">删除</v-btn>
<v-btn size="x-small" color="primary" @click="saveTradeConfig">保存</v-btn>
<v-btn size="x-small" color="error" outlined class="ml-1" @click="removeSymbolConfig(symbol)">删除</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
<!-- 添加新品种配置 -->
<v-row class="mt-3" align="center">
@@ -153,7 +153,7 @@
</v-col>
<v-col cols="2">
<v-btn color="primary" small @click="addSymbolConfig">
<v-icon left small>mdi-plus</v-icon>
<v-icon start small>mdi-plus</v-icon>
添加
</v-btn>
</v-col>
@@ -161,8 +161,369 @@
<div class="text-caption grey--text mt-3">
<v-icon small>mdi-information</v-icon>
支撑压力策略: M1周期接近转折点时自动生成交易指令止损偏移为固定点数<br/>
关键点位策略: 价格接近关键点位时生成反向订单例如下降趋势接近5000时生成买单阈值表示触发距离默认0.0008
品种基础配置手数止损偏移关键点位等
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- 策略配置 -->
<v-row class="mt-4">
<v-col cols="12">
<v-card>
<v-card-title>
<v-icon class="mr-2">mdi-strategy</v-icon>
策略配置
<v-btn icon small class="ml-2" @click="loadStrategies" :loading="strategiesLoading">
<v-icon small>mdi-refresh</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<!-- 策略列表 -->
<v-expansion-panels v-if="strategies.length > 0">
<v-expansion-panel v-for="strategy in strategies" :key="strategy.symbol">
<v-expansion-panel-title>
<div class="d-flex align-center">
<strong class="mr-3">{{ strategy.symbol }}</strong>
<v-chip :color="strategy.enabled ? 'success' : 'grey'" size="x-small">
{{ strategy.enabled ? '启用' : '禁用' }}
</v-chip>
<span class="text-caption grey--text ml-3">{{ strategy.strategy_name }}</span>
</div>
</v-expansion-panel-title>
<v-expansion-panel-text>
<!-- 基本信息 -->
<v-row class="mb-3">
<v-col cols="12" md="3">
<v-switch
v-model="strategy.enabled"
label="启用策略"
dense
hide-details
@change="updateStrategy(strategy)"
></v-switch>
</v-col>
<v-col cols="12" md="3">
<v-text-field
v-model="strategy.strategy_name"
label="策略名称"
dense
hide-details
@change="updateStrategy(strategy)"
></v-text-field>
</v-col>
<v-col cols="12" md="3">
<v-text-field
v-model.number="strategy.min_confidence"
label="最低置信度(%)"
type="number"
min="0"
max="100"
dense
hide-details
@change="updateStrategy(strategy)"
></v-text-field>
</v-col>
<v-col cols="12" md="3">
<v-select
v-model="strategy.consistency_requirement"
:items="consistencyOptions"
label="一致性要求"
dense
hide-details
@change="updateStrategy(strategy)"
></v-select>
</v-col>
</v-row>
<!-- 信号权重 -->
<div class="text-subtitle-2 mb-2">信号源配置</div>
<v-row class="mb-3">
<v-col cols="12">
<v-table density="compact">
<template v-slot:default>
<thead>
<tr>
<th>信号源</th>
<th>启用</th>
<th>M1</th>
<th>M5</th>
<th>M15</th>
<th>H1</th>
<th>H4</th>
</tr>
</thead>
<tbody>
<!-- Pivot 信号 -->
<tr>
<td>
<v-chip size="x-small" color="primary">Pivot</v-chip>
<span class="ml-2 text-caption">转折点信号</span>
</td>
<td>
<v-checkbox
v-model="getSignalConfig(strategy, 'pivot').enabled"
density="compact"
hide-details
@change="onSignalConfigChange(strategy, 'pivot')"
></v-checkbox>
</td>
<td v-for="period in ['M1', 'M5', 'M15', 'H1', 'H4']" :key="period">
<div class="d-flex align-center">
<v-checkbox
v-model="getPeriodConfig(strategy, 'pivot', period).enabled"
density="compact"
hide-details
:disabled="!getSignalConfig(strategy, 'pivot').enabled"
@change="onSignalConfigChange(strategy, 'pivot')"
></v-checkbox>
<v-text-field
v-model.number="getPeriodConfig(strategy, 'pivot', period).weight"
type="number"
min="0"
max="100"
density="compact"
hide-details
style="width: 50px"
:disabled="!getSignalConfig(strategy, 'pivot').enabled || !getPeriodConfig(strategy, 'pivot', period).enabled"
@change="onSignalConfigChange(strategy, 'pivot')"
></v-text-field>
</div>
</td>
</tr>
<!-- KeyLevel 信号 -->
<tr>
<td>
<v-chip size="x-small" color="success">KeyLevel</v-chip>
<span class="ml-2 text-caption">关键点位信号</span>
</td>
<td>
<v-checkbox
v-model="getSignalConfig(strategy, 'key_level').enabled"
density="compact"
hide-details
@change="onSignalConfigChange(strategy, 'key_level')"
></v-checkbox>
</td>
<td colspan="5">
<v-text-field
v-model.number="getSignalConfig(strategy, 'key_level').weight"
label="权重"
type="number"
min="0"
max="100"
density="compact"
hide-details
style="width: 80px"
:disabled="!getSignalConfig(strategy, 'key_level').enabled"
@change="onSignalConfigChange(strategy, 'key_level')"
></v-text-field>
<span class="text-caption grey--text ml-2">不区分周期</span>
</td>
</tr>
<!-- AI Entry 信号 -->
<tr>
<td>
<v-chip size="x-small" color="info">AI Entry</v-chip>
<span class="ml-2 text-caption">AI入场信号</span>
</td>
<td>
<v-checkbox
v-model="getSignalConfig(strategy, 'ai_entry').enabled"
density="compact"
hide-details
@change="onSignalConfigChange(strategy, 'ai_entry')"
></v-checkbox>
</td>
<td v-for="period in ['M1', 'M5', 'M15', 'H1', 'H4']" :key="period">
<div class="d-flex align-center">
<v-checkbox
v-model="getPeriodConfig(strategy, 'ai_entry', period).enabled"
density="compact"
hide-details
:disabled="!getSignalConfig(strategy, 'ai_entry').enabled"
@change="onSignalConfigChange(strategy, 'ai_entry')"
></v-checkbox>
<v-text-field
v-model.number="getPeriodConfig(strategy, 'ai_entry', period).weight"
type="number"
min="0"
max="100"
density="compact"
hide-details
style="width: 50px"
:disabled="!getSignalConfig(strategy, 'ai_entry').enabled || !getPeriodConfig(strategy, 'ai_entry', period).enabled"
@change="onSignalConfigChange(strategy, 'ai_entry')"
></v-text-field>
</div>
</td>
</tr>
</tbody>
</template>
</v-table>
</v-col>
</v-row>
<!-- 兼容旧版信号权重隐藏但保留数据 -->
<div class="text-caption grey--text mb-2">
<v-icon small>mdi-information</v-icon>
勾选启用周期设置权重值KeyLevel信号不区分周期只需设置权重
</div>
<!-- 仓位管理 -->
<div class="text-subtitle-2 mb-2">仓位管理</div>
<v-row class="mb-3">
<v-col cols="3">
<v-text-field
v-model.number="strategy.fixed_volume"
label="固定手数"
type="number"
step="0.01"
min="0.01"
dense
hide-details
@change="updateStrategy(strategy)"
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="strategy.max_positions"
label="最大持仓数"
type="number"
min="1"
dense
hide-details
@change="updateStrategy(strategy)"
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="strategy.max_same_direction"
label="同向最大持仓"
type="number"
min="1"
dense
hide-details
@change="updateStrategy(strategy)"
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="strategy.risk_percent"
label="风险百分比(%)"
type="number"
min="0.1"
step="0.1"
dense
hide-details
@change="updateStrategy(strategy)"
></v-text-field>
</v-col>
</v-row>
<!-- 止损止盈 -->
<div class="text-subtitle-2 mb-2">止损止盈规则</div>
<v-row class="mb-3">
<v-col cols="3">
<v-select
v-model="strategy.sl_mode"
:items="slModeOptions"
label="止损模式"
dense
hide-details
@change="updateStrategy(strategy)"
></v-select>
</v-col>
<v-col cols="3">
<v-select
v-model="strategy.tp_mode"
:items="tpModeOptions"
label="止盈模式"
dense
hide-details
@change="updateStrategy(strategy)"
></v-select>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="strategy.min_risk_reward"
label="最小盈亏比"
type="number"
min="0.5"
step="0.5"
dense
hide-details
@change="updateStrategy(strategy)"
></v-text-field>
</v-col>
<v-col cols="3">
<v-text-field
v-model.number="strategy.tp_risk_reward"
label="止盈盈亏比"
type="number"
min="1"
step="0.5"
dense
hide-details
@change="updateStrategy(strategy)"
></v-text-field>
</v-col>
</v-row>
<!-- 操作按钮 -->
<v-row>
<v-col cols="12">
<v-btn color="primary" small @click="updateStrategy(strategy)" :loading="strategySaving === strategy.symbol">
<v-icon start small>mdi-content-save</v-icon>
保存
</v-btn>
<v-btn color="error" small outlined class="ml-2" @click="deleteStrategy(strategy.symbol)">
<v-icon start small>mdi-delete</v-icon>
删除策略
</v-btn>
</v-col>
</v-row>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
<div v-else class="text-center grey--text py-4">
<v-icon large>mdi-strategy</v-icon>
<div class="mt-2">暂无策略配置添加品种后会自动创建默认策略</div>
</div>
<!-- 添加新策略 -->
<v-row class="mt-4" align="center">
<v-col cols="4">
<v-select
v-model="newStrategySymbol"
:items="symbolsWithoutStrategy"
label="选择品种添加策略"
dense
hide-details
></v-select>
</v-col>
<v-col cols="4">
<v-text-field
v-model="newStrategyName"
label="策略名称(可选)"
dense
hide-details
placeholder="默认:Strategy_{品种}"
></v-text-field>
</v-col>
<v-col cols="4">
<v-btn color="primary" small @click="addStrategy" :loading="strategySaving === 'new'">
<v-icon start small>mdi-plus</v-icon>
添加策略
</v-btn>
</v-col>
</v-row>
<div class="text-caption grey--text mt-3">
<v-icon small>mdi-information</v-icon>
策略配置信号权重过滤规则仓位管理止损止盈等每个品种绑定一个策略
</div>
</v-card-text>
</v-card>
@@ -214,7 +575,7 @@
<v-row class="mt-2">
<v-col cols="12">
<v-btn color="primary" @click="saveLLMConfig" :loading="llmSaving">
<v-icon left>mdi-content-save</v-icon>
<v-icon start>mdi-content-save</v-icon>
保存配置
</v-btn>
<v-chip
@@ -249,7 +610,7 @@
</v-btn>
</v-card-title>
<v-card-text>
<v-simple-table dense v-if="symbolStatus.length > 0">
<v-table dense v-if="symbolStatus.length > 0">
<template v-slot:default>
<thead>
<tr>
@@ -265,7 +626,7 @@
<tr v-for="item in symbolStatus" :key="item.symbol">
<td><strong>{{ item.symbol }}</strong></td>
<td>
<v-chip x-small :color="item.has_data ? 'success' : 'error'">
<v-chip size="x-small" :color="item.has_data ? 'success' : 'error'">
{{ item.has_data ? '有数据' : '无数据' }}
</v-chip>
</td>
@@ -276,14 +637,14 @@
<span v-else>-</span>
</td>
<td>
<v-chip x-small :color="getMarketStatusColor(item.market_status)">
<v-chip size="x-small" :color="getMarketStatusColor(item.market_status)">
{{ getMarketStatusText(item.market_status) }}
</v-chip>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
<div v-else class="text-center grey--text py-4">
<v-icon large>mdi-database-off</v-icon>
<div class="mt-2">暂无已配置的品种</div>
@@ -299,12 +660,12 @@
</v-row>
<!-- 错误提示 -->
<v-snackbar v-model="showError" color="error" timeout="5000">
<v-snackbar v-model="showError" color="error" timeout="5000" location="top">
{{ errorMessage }}
</v-snackbar>
<!-- 成功提示 -->
<v-snackbar v-model="showSuccess" color="success" timeout="3000">
<v-snackbar v-model="showSuccess" color="success" timeout="3000" location="top">
{{ successMessage }}
</v-snackbar>
</v-container>
@@ -534,11 +895,213 @@ export default {
}
}
// ==================== 策略配置 ====================
// 策略数据
const strategies = ref([])
const strategiesLoading = ref(false)
const strategySaving = ref(null)
const newStrategySymbol = ref('')
const newStrategyName = ref('')
// 策略选项
const consistencyOptions = [
{ title: '任一信号即可', value: 'any' },
{ title: '多数信号一致', value: 'majority' },
{ title: '所有信号一致', value: 'all' }
]
const slModeOptions = [
{ title: '使用信号建议', value: 'signal' },
{ title: '固定点数', value: 'fixed_points' }
]
const tpModeOptions = [
{ title: '使用信号建议', value: 'signal' },
{ title: '固定点数', value: 'fixed_points' },
{ title: '风险回报比', value: 'risk_reward' }
]
// 没有策略的品种
const symbolsWithoutStrategy = computed(() => {
const strategySymbols = strategies.value.map(s => s.symbol)
return symbols.value.filter(s => !strategySymbols.includes(s))
})
// 加载策略列表
const loadStrategies = async () => {
strategiesLoading.value = true
try {
const data = await marketAPI.getStrategies()
if (data.status === 'ok') {
strategies.value = data.strategies || []
}
} catch (err) {
console.error('加载策略配置失败:', err)
} finally {
strategiesLoading.value = false
}
}
// 更新策略
const updateStrategy = async (strategy) => {
strategySaving.value = strategy.symbol
try {
// 确保signal_config存在
if (!strategy.signal_config) {
strategy.signal_config = {
pivot: {
enabled: true,
periods: {
M1: { enabled: true, weight: 15 },
M5: { enabled: true, weight: 20 },
M15: { enabled: false, weight: 25 },
H1: { enabled: false, weight: 20 },
H4: { enabled: false, weight: 20 }
}
},
key_level: { enabled: true, weight: 40 },
ai_entry: {
enabled: true,
periods: {
M1: { enabled: false, weight: 15 },
M5: { enabled: true, weight: 20 },
M15: { enabled: true, weight: 30 },
H1: { enabled: true, weight: 25 },
H4: { enabled: false, weight: 20 }
}
}
}
}
const data = await marketAPI.updateStrategy(strategy.symbol, {
enabled: strategy.enabled,
strategy_name: strategy.strategy_name,
min_confidence: strategy.min_confidence,
consistency_requirement: strategy.consistency_requirement,
signal_config: strategy.signal_config,
signal_weights: strategy.signal_weights,
fixed_volume: strategy.fixed_volume,
max_positions: strategy.max_positions,
max_same_direction: strategy.max_same_direction,
risk_percent: strategy.risk_percent,
sl_mode: strategy.sl_mode,
tp_mode: strategy.tp_mode,
min_risk_reward: strategy.min_risk_reward,
tp_risk_reward: strategy.tp_risk_reward
})
if (data.status === 'ok') {
successMessage.value = `${strategy.symbol} 策略配置已保存`
showSuccess.value = true
// 更新本地策略数据
if (data.strategy) {
Object.assign(strategy, data.strategy)
}
} else {
errorMessage.value = data.message || '保存失败'
showError.value = true
}
} catch (err) {
console.error('保存策略失败:', err)
errorMessage.value = `保存策略失败: ${err.message}`
showError.value = true
} finally {
strategySaving.value = null
}
}
// 获取信号源配置
const getSignalConfig = (strategy, source) => {
if (!strategy.signal_config) {
strategy.signal_config = {
pivot: { enabled: true, periods: {} },
key_level: { enabled: true, weight: 40 },
ai_entry: { enabled: true, periods: {} }
}
}
if (!strategy.signal_config[source]) {
if (source === 'key_level') {
strategy.signal_config[source] = { enabled: true, weight: 40 }
} else {
strategy.signal_config[source] = { enabled: true, periods: {} }
}
}
return strategy.signal_config[source]
}
// 获取周期配置
const getPeriodConfig = (strategy, source, period) => {
const config = getSignalConfig(strategy, source)
if (source === 'key_level') {
return { enabled: true, weight: config.weight || 40 }
}
if (!config.periods) {
config.periods = {}
}
if (!config.periods[period]) {
config.periods[period] = { enabled: false, weight: 20 }
}
return config.periods[period]
}
// 信号配置变更处理
const onSignalConfigChange = (strategy, source) => {
// 触发保存
updateStrategy(strategy)
}
// 删除策略
const deleteStrategy = async (symbol) => {
if (!confirm(`确定要删除 ${symbol} 的策略配置吗?`)) return
try {
const data = await marketAPI.deleteStrategy(symbol)
if (data.status === 'ok') {
successMessage.value = '策略已删除'
showSuccess.value = true
await loadStrategies()
} else {
errorMessage.value = data.message || '删除失败'
showError.value = true
}
} catch (err) {
errorMessage.value = `删除策略失败: ${err.message}`
showError.value = true
}
}
// 添加策略
const addStrategy = async () => {
if (!newStrategySymbol.value) return
strategySaving.value = 'new'
try {
const data = await marketAPI.updateStrategy(newStrategySymbol.value, {
enabled: true,
strategy_name: newStrategyName.value || `Strategy_${newStrategySymbol.value}`
})
if (data.status === 'ok') {
successMessage.value = '策略已添加'
showSuccess.value = true
newStrategySymbol.value = ''
newStrategyName.value = ''
await loadStrategies()
} else {
errorMessage.value = data.message || '添加失败'
showError.value = true
}
} catch (err) {
errorMessage.value = `添加策略失败: ${err.message}`
showError.value = true
} finally {
strategySaving.value = null
}
}
onMounted(() => {
loadSymbols()
loadTradeConfig()
loadLLMConfig()
loadSymbolStatus()
loadStrategies()
})
return {
@@ -567,7 +1130,25 @@ export default {
symbolStatusLoading,
loadSymbolStatus,
getMarketStatusColor,
getMarketStatusText
getMarketStatusText,
// 策略配置
strategies,
strategiesLoading,
strategySaving,
newStrategySymbol,
newStrategyName,
consistencyOptions,
slModeOptions,
tpModeOptions,
symbolsWithoutStrategy,
loadStrategies,
updateStrategy,
deleteStrategy,
addStrategy,
// 信号配置
getSignalConfig,
getPeriodConfig,
onSignalConfigChange
}
}
}