Signed-off-by: TsssIANHE <TIANHE@GMAIL.COM>
This commit is contained in:
TIANHE
2026-02-05 00:28:08 +08:00
parent c0738c09fe
commit af8e46011e
17 changed files with 2868 additions and 288 deletions
+13
View File
@@ -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)',
+13 -1
View File
@@ -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;
}
}
}