Refactor: restructure market module with services, stores, and utils
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user