@@ -1281,6 +1281,12 @@ const locale = {
|
||||
'trading-assistant.stopAll': 'Stop All',
|
||||
'trading-assistant.deleteAll': 'Delete All',
|
||||
'trading-assistant.symbolCount': 'symbols',
|
||||
'trading-assistant.strategyCount': 'strategies',
|
||||
'trading-assistant.groupBy': 'Group By',
|
||||
'trading-assistant.groupByStrategy': 'Strategy',
|
||||
'trading-assistant.groupBySymbol': 'Symbol',
|
||||
'trading-assistant.timeframe': 'Timeframe',
|
||||
'trading-assistant.indicator': 'Indicator',
|
||||
'trading-assistant.status.running': 'Running',
|
||||
'trading-assistant.status.stopped': 'Stopped',
|
||||
'trading-assistant.status.error': 'Error',
|
||||
@@ -2216,6 +2222,9 @@ const locale = {
|
||||
// Settings fields - Search
|
||||
'settings.field.SEARCH_PROVIDER': 'Search Provider',
|
||||
'settings.field.SEARCH_MAX_RESULTS': 'Max Results',
|
||||
'settings.field.TAVILY_API_KEYS': 'Tavily API Keys',
|
||||
'settings.field.BOCHA_API_KEYS': 'Bocha API Keys',
|
||||
'settings.field.SERPAPI_KEYS': 'SerpAPI Keys',
|
||||
'settings.field.SEARCH_GOOGLE_API_KEY': 'Google API Key',
|
||||
'settings.field.SEARCH_GOOGLE_CX': 'Google CX',
|
||||
'settings.field.SEARCH_BING_API_KEY': 'Bing API Key',
|
||||
@@ -2223,6 +2232,10 @@ const locale = {
|
||||
|
||||
// Settings descriptions (config item descriptions)
|
||||
// Note: These are optional since backend already provides English descriptions
|
||||
'settings.desc.SEARCH_PROVIDER': 'Web search provider for AI research. Bocha recommended for A-share news',
|
||||
'settings.desc.TAVILY_API_KEYS': 'Tavily Search API keys, comma-separated for rotation. Free 1000 requests/month',
|
||||
'settings.desc.BOCHA_API_KEYS': 'Bocha Search API keys, comma-separated for rotation. Best for A-share news',
|
||||
'settings.desc.SERPAPI_KEYS': 'SerpAPI keys for Google/Bing search, comma-separated for rotation',
|
||||
'settings.desc.ORDER_MODE': 'maker: Limit order first (lower fees), market: Market order (instant fill)',
|
||||
'settings.desc.MAKER_WAIT_SEC': 'Wait time for limit order fill before switching to market order',
|
||||
'settings.desc.MAKER_OFFSET_BPS': 'Price offset in basis points. Buy: price*(1-offset), Sell: price*(1+offset)',
|
||||
|
||||
@@ -1190,6 +1190,12 @@ const locale = {
|
||||
'trading-assistant.stopAll': '全部停止',
|
||||
'trading-assistant.deleteAll': '全部删除',
|
||||
'trading-assistant.symbolCount': '个币种',
|
||||
'trading-assistant.strategyCount': '个策略',
|
||||
'trading-assistant.groupBy': '分组方式',
|
||||
'trading-assistant.groupByStrategy': '按策略',
|
||||
'trading-assistant.groupBySymbol': '按标的',
|
||||
'trading-assistant.timeframe': '周期',
|
||||
'trading-assistant.indicator': '指标',
|
||||
'trading-assistant.status.running': '运行中',
|
||||
'trading-assistant.status.stopped': '已停止',
|
||||
'trading-assistant.status.error': '错误',
|
||||
@@ -1955,6 +1961,9 @@ const locale = {
|
||||
// Settings fields - Search
|
||||
'settings.field.SEARCH_PROVIDER': '搜索提供商',
|
||||
'settings.field.SEARCH_MAX_RESULTS': '最大结果数',
|
||||
'settings.field.TAVILY_API_KEYS': 'Tavily API Keys',
|
||||
'settings.field.BOCHA_API_KEYS': '博查 API Keys',
|
||||
'settings.field.SERPAPI_KEYS': 'SerpAPI Keys',
|
||||
'settings.field.SEARCH_GOOGLE_API_KEY': 'Google API Key',
|
||||
'settings.field.SEARCH_GOOGLE_CX': 'Google CX',
|
||||
'settings.field.SEARCH_BING_API_KEY': 'Bing API Key',
|
||||
@@ -2027,8 +2036,11 @@ const locale = {
|
||||
'settings.desc.PROXY_PORT': '代理服务器端口(留空则禁用代理)',
|
||||
'settings.desc.PROXY_SCHEME': '代理协议类型。socks5h 表示DNS也走代理',
|
||||
'settings.desc.PROXY_URL': '完整代理URL(设置后覆盖上面的配置)',
|
||||
'settings.desc.SEARCH_PROVIDER': '网页搜索提供商,用于AI研究功能',
|
||||
'settings.desc.SEARCH_PROVIDER': '网页搜索提供商,用于AI研究功能。博查(Bocha)推荐用于A股新闻',
|
||||
'settings.desc.SEARCH_MAX_RESULTS': '搜索返回的最大结果数',
|
||||
'settings.desc.TAVILY_API_KEYS': 'Tavily搜索API密钥,多个用逗号分隔可轮换。免费1000次/月',
|
||||
'settings.desc.BOCHA_API_KEYS': '博查搜索API密钥,多个用逗号分隔可轮换。A股新闻搜索效果最佳',
|
||||
'settings.desc.SERPAPI_KEYS': 'SerpAPI密钥,用于Google/Bing搜索,多个用逗号分隔可轮换',
|
||||
'settings.desc.SEARCH_GOOGLE_API_KEY': 'Google自定义搜索API密钥',
|
||||
'settings.desc.SEARCH_GOOGLE_CX': 'Google可编程搜索引擎ID (CX)',
|
||||
'settings.desc.SEARCH_BING_API_KEY': 'Microsoft Bing网页搜索API密钥',
|
||||
|
||||
@@ -393,6 +393,8 @@
|
||||
@ok="confirmIndicatorParams"
|
||||
@cancel="cancelIndicatorParams"
|
||||
:width="500"
|
||||
:maskClosable="false"
|
||||
:keyboard="false"
|
||||
>
|
||||
<div v-if="pendingIndicator" class="params-config-modal">
|
||||
<div class="indicator-info">
|
||||
|
||||
@@ -18,6 +18,21 @@
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 分组方式切换 -->
|
||||
<div class="group-mode-switch">
|
||||
<span class="group-mode-label">{{ $t('trading-assistant.groupBy') }}:</span>
|
||||
<a-radio-group v-model="groupByMode" size="small" button-style="solid">
|
||||
<a-radio-button value="strategy">
|
||||
<a-icon type="folder" />
|
||||
{{ $t('trading-assistant.groupByStrategy') }}
|
||||
</a-radio-button>
|
||||
<a-radio-button value="symbol">
|
||||
<a-icon type="stock" />
|
||||
{{ $t('trading-assistant.groupBySymbol') }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-empty v-if="!loading && strategies.length === 0" :description="$t('trading-assistant.noStrategy')" />
|
||||
<div v-else class="strategy-grouped-list">
|
||||
@@ -27,10 +42,10 @@
|
||||
<div class="strategy-group-header" @click="toggleGroup(group.id)">
|
||||
<div class="group-header-left">
|
||||
<a-icon :type="collapsedGroups[group.id] ? 'right' : 'down'" class="collapse-icon" />
|
||||
<a-icon type="folder" class="group-icon" />
|
||||
<a-icon :type="groupByMode === 'symbol' ? 'stock' : 'folder'" class="group-icon" />
|
||||
<span class="group-name">{{ group.baseName }}</span>
|
||||
<a-tag size="small" color="blue">{{ group.strategies.length }} {{
|
||||
$t('trading-assistant.symbolCount') }}</a-tag>
|
||||
groupByMode === 'symbol' ? $t('trading-assistant.strategyCount') : $t('trading-assistant.symbolCount') }}</a-tag>
|
||||
</div>
|
||||
<div class="group-header-right" @click.stop>
|
||||
<span v-if="group.runningCount > 0" class="group-status running">
|
||||
@@ -69,10 +84,28 @@
|
||||
<div class="strategy-item-content">
|
||||
<div class="strategy-item-header">
|
||||
<div class="strategy-name-wrapper">
|
||||
<span class="info-item" v-if="item.trading_config && item.trading_config.symbol">
|
||||
<a-icon type="dollar" />
|
||||
{{ item.trading_config.symbol }}
|
||||
</span>
|
||||
<!-- 按策略分组:显示 Symbol -->
|
||||
<template v-if="groupByMode === 'strategy'">
|
||||
<span class="info-item" v-if="item.trading_config && item.trading_config.symbol">
|
||||
<a-icon type="dollar" />
|
||||
{{ item.trading_config.symbol }}
|
||||
</span>
|
||||
</template>
|
||||
<!-- 按 Symbol 分组:显示策略名称、周期、指标 -->
|
||||
<template v-else>
|
||||
<span class="info-item strategy-name-text">
|
||||
<a-icon type="thunderbolt" />
|
||||
{{ item.displayInfo ? item.displayInfo.strategyName : item.strategy_name }}
|
||||
</span>
|
||||
<a-tag size="small" color="cyan" v-if="item.displayInfo && item.displayInfo.timeframe">
|
||||
<a-icon type="clock-circle" style="margin-right: 2px;" />
|
||||
{{ item.displayInfo.timeframe }}
|
||||
</a-tag>
|
||||
<a-tag size="small" color="purple" v-if="item.displayInfo && item.displayInfo.indicatorName && item.displayInfo.indicatorName !== '-'">
|
||||
<a-icon type="line-chart" style="margin-right: 2px;" />
|
||||
{{ item.displayInfo.indicatorName }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<span
|
||||
class="status-label"
|
||||
:class="[
|
||||
@@ -920,7 +953,10 @@
|
||||
<a-checkbox value="telegram">{{ $t('trading-assistant.notify.telegram') }}</a-checkbox>
|
||||
<a-checkbox value="discord">{{ $t('trading-assistant.notify.discord') }}</a-checkbox>
|
||||
<a-checkbox value="webhook">{{ $t('trading-assistant.notify.webhook') }}</a-checkbox>
|
||||
<<<<<<< HEAD
|
||||
<a-checkbox value="phone">{{ $t('trading-assistant.notify.phone') }}</a-checkbox>
|
||||
=======
|
||||
>>>>>>> 6876016 (sss)
|
||||
</a-checkbox-group>
|
||||
<div class="form-item-hint">{{ $t('trading-assistant.form.notifyChannelsHint') }}</div>
|
||||
</a-form-item>
|
||||
@@ -1241,7 +1277,9 @@
|
||||
:confirmLoading="addingSymbol"
|
||||
width="600px"
|
||||
:okText="$t('trading-assistant.form.confirmAdd')"
|
||||
:cancelText="$t('trading-assistant.form.cancel')">
|
||||
:cancelText="$t('trading-assistant.form.cancel')"
|
||||
:maskClosable="false"
|
||||
:keyboard="false">
|
||||
<div class="add-symbol-modal-content">
|
||||
<!-- 市场类型Tab -->
|
||||
<a-tabs v-model="addSymbolMarket" @change="handleAddSymbolMarketChange" class="market-tabs">
|
||||
@@ -1574,6 +1612,13 @@ export default {
|
||||
},
|
||||
// 策略分组显示
|
||||
groupedStrategies () {
|
||||
if (this.groupByMode === 'symbol') {
|
||||
return this.groupedBySymbol
|
||||
}
|
||||
return this.groupedByStrategy
|
||||
},
|
||||
// 按策略分组(原有逻辑)
|
||||
groupedByStrategy () {
|
||||
const groups = {}
|
||||
const ungrouped = []
|
||||
|
||||
@@ -1610,6 +1655,51 @@ export default {
|
||||
|
||||
return { groups: groupList, ungrouped }
|
||||
},
|
||||
// 按 Symbol 分组
|
||||
groupedBySymbol () {
|
||||
const groups = {}
|
||||
const ungrouped = []
|
||||
|
||||
for (const s of this.strategies) {
|
||||
const tc = s.trading_config || {}
|
||||
const symbol = tc.symbol
|
||||
if (symbol && symbol.trim()) {
|
||||
if (!groups[symbol]) {
|
||||
groups[symbol] = {
|
||||
id: `symbol_${symbol}`,
|
||||
baseName: symbol,
|
||||
strategies: [],
|
||||
runningCount: 0,
|
||||
stoppedCount: 0
|
||||
}
|
||||
}
|
||||
// 添加策略详情信息
|
||||
const strategyInfo = {
|
||||
...s,
|
||||
displayInfo: {
|
||||
strategyName: s.strategy_name || s.group_base_name || 'Unnamed',
|
||||
timeframe: tc.timeframe || '-',
|
||||
indicatorName: s.indicator_name || (s.indicator_config && s.indicator_config.name) || '-'
|
||||
}
|
||||
}
|
||||
groups[symbol].strategies.push(strategyInfo)
|
||||
if (s.status === 'running') {
|
||||
groups[symbol].runningCount++
|
||||
} else {
|
||||
groups[symbol].stoppedCount++
|
||||
}
|
||||
} else {
|
||||
ungrouped.push(s)
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为数组,按 symbol 名称排序
|
||||
const groupList = Object.values(groups).sort((a, b) => {
|
||||
return a.baseName.localeCompare(b.baseName)
|
||||
})
|
||||
|
||||
return { groups: groupList, ungrouped }
|
||||
},
|
||||
// Check if selected channels are configured in user profile
|
||||
unconfiguredChannels () {
|
||||
const missing = []
|
||||
@@ -1629,6 +1719,11 @@ export default {
|
||||
missing.push('Discord')
|
||||
}
|
||||
}
|
||||
if (this.notifyChannelsUi.includes('webhook')) {
|
||||
if (!this.userNotificationSettings.webhook_url) {
|
||||
missing.push('Webhook')
|
||||
}
|
||||
}
|
||||
// Phone/SMS check if needed
|
||||
// if (this.notifyChannelsUi.includes('phone') && !this.userNotificationSettings.phone) { ... }
|
||||
|
||||
@@ -1695,6 +1790,8 @@ export default {
|
||||
selectedSymbols: [],
|
||||
// 策略组折叠状态
|
||||
collapsedGroups: {},
|
||||
// 分组模式: 'strategy' 或 'symbol'
|
||||
groupByMode: 'strategy',
|
||||
// 添加交易对弹窗相关
|
||||
showAddSymbolModal: false,
|
||||
addSymbolMarket: 'Crypto',
|
||||
@@ -2410,12 +2507,12 @@ export default {
|
||||
})
|
||||
await this.handleIndicatorChange(finalId)
|
||||
|
||||
// 恢复已保存的指标参数值
|
||||
// 恢复已保存的指标参数值 - 使用 $set 确保响应式
|
||||
const savedParams = strategy.trading_config?.indicator_params
|
||||
if (savedParams && typeof savedParams === 'object') {
|
||||
Object.keys(savedParams).forEach(key => {
|
||||
if (key in this.indicatorParamValues) {
|
||||
this.indicatorParamValues[key] = savedParams[key]
|
||||
this.$set(this.indicatorParamValues, key, savedParams[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2869,10 +2966,12 @@ export default {
|
||||
// 响应拦截器已返回 response.data,所以直接访问 res.code 和 res.data
|
||||
if (res && res.code === 1 && Array.isArray(res.data)) {
|
||||
this.indicatorParams = res.data
|
||||
// 初始化参数值为默认值
|
||||
// 初始化参数值为默认值 - 先构建完整对象再赋值,确保响应式
|
||||
const paramValues = {}
|
||||
res.data.forEach(p => {
|
||||
this.indicatorParamValues[p.name] = p.default
|
||||
paramValues[p.name] = p.default
|
||||
})
|
||||
this.indicatorParamValues = paramValues
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to load indicator params:', err)
|
||||
@@ -3993,6 +4092,44 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// 分组模式切换
|
||||
.group-mode-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 0 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.group-mode-label {
|
||||
font-size: 13px;
|
||||
color: #8c8c8c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/deep/ .ant-radio-group {
|
||||
.ant-radio-button-wrapper {
|
||||
font-size: 12px;
|
||||
padding: 0 10px;
|
||||
height: 26px;
|
||||
line-height: 24px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:first-child {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .ant-card-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -4301,6 +4438,19 @@ export default {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.strategy-name-text {
|
||||
font-weight: 500;
|
||||
color: #1e3a5f;
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .ant-tag {
|
||||
margin-right: 0;
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user