feat: add quick trade panel and strategy creation enhancements in indicator analysis
This commit is contained in:
@@ -68,6 +68,15 @@
|
|||||||
{{ priceChange > 0 ? '+' : '' }}{{ priceChange.toFixed(2) }}%
|
{{ priceChange > 0 ? '+' : '' }}{{ priceChange.toFixed(2) }}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<a-button
|
||||||
|
v-if="isCryptoMarket"
|
||||||
|
class="qt-header-btn"
|
||||||
|
size="small"
|
||||||
|
@click="openQuickTrade"
|
||||||
|
>
|
||||||
|
<a-icon type="thunderbolt" theme="filled" />
|
||||||
|
{{ tt('quickTrade.openPanel', 'Quick Trade') }}
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -210,6 +219,13 @@
|
|||||||
@click.stop="handlePublishIndicator(indicator)"
|
@click.stop="handlePublishIndicator(indicator)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
<a-tooltip :title="tt('trading-assistant.createStrategy', 'Create Strategy')">
|
||||||
|
<a-icon
|
||||||
|
type="rocket"
|
||||||
|
class="action-icon create-strategy-icon"
|
||||||
|
@click.stop="handleCreateStrategyFromIndicator(indicator)"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="card-desc">{{ indicator.description || '' }}</span>
|
<span class="card-desc">{{ indicator.description || '' }}</span>
|
||||||
@@ -230,6 +246,15 @@
|
|||||||
<a-icon :type="purchasedSectionCollapsed ? 'right' : 'down'" class="collapse-icon" />
|
<a-icon :type="purchasedSectionCollapsed ? 'right' : 'down'" class="collapse-icon" />
|
||||||
<span>{{ $t('dashboard.indicator.section.purchased') }} ({{ purchasedIndicators.length }})</span>
|
<span>{{ $t('dashboard.indicator.section.purchased') }} ({{ purchasedIndicators.length }})</span>
|
||||||
</div>
|
</div>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon="shop"
|
||||||
|
class="buy-indicator-btn"
|
||||||
|
@click.stop="goToIndicatorMarket"
|
||||||
|
>
|
||||||
|
{{ $t('menu.dashboard.community') }}
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="!purchasedSectionCollapsed" class="section-content custom-scrollbar">
|
<div v-show="!purchasedSectionCollapsed" class="section-content custom-scrollbar">
|
||||||
<div
|
<div
|
||||||
@@ -460,6 +485,17 @@
|
|||||||
@cancel="showBacktestRunViewer = false; selectedBacktestRun = null"
|
@cancel="showBacktestRunViewer = false; selectedBacktestRun = null"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<quick-trade-panel
|
||||||
|
:visible="showQuickTrade"
|
||||||
|
:symbol="qtSymbol"
|
||||||
|
:preset-side="qtSide"
|
||||||
|
:preset-price="qtPrice"
|
||||||
|
source="indicator"
|
||||||
|
market-type="swap"
|
||||||
|
@close="showQuickTrade = false"
|
||||||
|
@update:symbol="qtSymbol = $event"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 发布到社区弹窗 -->
|
<!-- 发布到社区弹窗 -->
|
||||||
<a-modal
|
<a-modal
|
||||||
:title="publishIndicator && publishIndicator.publish_to_community ? $t('dashboard.indicator.publish.editTitle') : $t('dashboard.indicator.publish.title')"
|
:title="publishIndicator && publishIndicator.publish_to_community ? $t('dashboard.indicator.publish.editTitle') : $t('dashboard.indicator.publish.title')"
|
||||||
@@ -658,6 +694,7 @@ import KlineChart from '@/views/indicator-analysis/components/KlineChart.vue'
|
|||||||
import BacktestModal from '@/views/indicator-analysis/components/BacktestModal.vue'
|
import BacktestModal from '@/views/indicator-analysis/components/BacktestModal.vue'
|
||||||
import BacktestHistoryDrawer from '@/views/indicator-analysis/components/BacktestHistoryDrawer.vue'
|
import BacktestHistoryDrawer from '@/views/indicator-analysis/components/BacktestHistoryDrawer.vue'
|
||||||
import BacktestRunViewer from '@/views/indicator-analysis/components/BacktestRunViewer.vue'
|
import BacktestRunViewer from '@/views/indicator-analysis/components/BacktestRunViewer.vue'
|
||||||
|
import QuickTradePanel from '@/components/QuickTradePanel.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DashboardIndicator',
|
name: 'DashboardIndicator',
|
||||||
@@ -666,7 +703,8 @@ export default {
|
|||||||
KlineChart,
|
KlineChart,
|
||||||
BacktestModal,
|
BacktestModal,
|
||||||
BacktestHistoryDrawer,
|
BacktestHistoryDrawer,
|
||||||
BacktestRunViewer
|
BacktestRunViewer,
|
||||||
|
QuickTradePanel
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
@@ -717,10 +755,20 @@ export default {
|
|||||||
const currentMarket = ref('')
|
const currentMarket = ref('')
|
||||||
const currentPrice = ref('--')
|
const currentPrice = ref('--')
|
||||||
const priceChange = ref(0)
|
const priceChange = ref(0)
|
||||||
|
const showQuickTrade = ref(false)
|
||||||
|
const qtSymbol = ref('')
|
||||||
|
const qtSide = ref('')
|
||||||
|
const qtPrice = ref(0)
|
||||||
|
|
||||||
const priceChangeClass = computed(() => {
|
const priceChangeClass = computed(() => {
|
||||||
return priceChange.value > 0 ? 'color-up' : priceChange.value < 0 ? 'color-down' : ''
|
return priceChange.value > 0 ? 'color-up' : priceChange.value < 0 ? 'color-down' : ''
|
||||||
})
|
})
|
||||||
|
const isCryptoMarket = computed(() => currentMarket.value === 'Crypto')
|
||||||
|
|
||||||
|
const tt = (key, fallback, params) => {
|
||||||
|
const translated = proxy.$t(key, params)
|
||||||
|
return translated !== key ? translated : fallback
|
||||||
|
}
|
||||||
|
|
||||||
const timeframe = ref('1D')
|
const timeframe = ref('1D')
|
||||||
const activeIndicators = ref([])
|
const activeIndicators = ref([])
|
||||||
@@ -799,6 +847,14 @@ export default {
|
|||||||
priceChange.value = change
|
priceChange.value = change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openQuickTrade = () => {
|
||||||
|
if (!isCryptoMarket.value || !currentSymbol.value) return
|
||||||
|
qtSymbol.value = currentSymbol.value
|
||||||
|
qtSide.value = ''
|
||||||
|
qtPrice.value = Number(String(currentPrice.value || '').replace(/,/g, '')) || 0
|
||||||
|
showQuickTrade.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 处理图表重试事件
|
// 处理图表重试事件
|
||||||
const handleChartRetry = () => {
|
const handleChartRetry = () => {
|
||||||
// KlineChart 组件会自己处理重试,这里可以添加额外的逻辑
|
// KlineChart 组件会自己处理重试,这里可以添加额外的逻辑
|
||||||
@@ -1647,6 +1703,22 @@ export default {
|
|||||||
showBacktestHistoryDrawer.value = true
|
showBacktestHistoryDrawer.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCreateStrategyFromIndicator = (indicator) => {
|
||||||
|
if (!indicator || !indicator.id) return
|
||||||
|
const query = {
|
||||||
|
open: 'from-indicator',
|
||||||
|
indicator_id: String(indicator.id)
|
||||||
|
}
|
||||||
|
if (currentMarket.value) query.market = currentMarket.value
|
||||||
|
if (currentSymbol.value) query.symbol = currentSymbol.value
|
||||||
|
if (timeframe.value) query.timeframe = timeframe.value
|
||||||
|
proxy.$router.push({ path: '/trading-assistant', query })
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToIndicatorMarket = () => {
|
||||||
|
proxy.$router.push('/indicator-community')
|
||||||
|
}
|
||||||
|
|
||||||
const handleViewBacktestRun = (run) => {
|
const handleViewBacktestRun = (run) => {
|
||||||
selectedBacktestRun.value = run
|
selectedBacktestRun.value = run
|
||||||
showBacktestRunViewer.value = true
|
showBacktestRunViewer.value = true
|
||||||
@@ -1968,6 +2040,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
userId,
|
userId,
|
||||||
|
tt,
|
||||||
klineChart,
|
klineChart,
|
||||||
searchSymbol,
|
searchSymbol,
|
||||||
symbolSuggestions,
|
symbolSuggestions,
|
||||||
@@ -1979,6 +2052,12 @@ currentMarket,
|
|||||||
currentPrice,
|
currentPrice,
|
||||||
priceChange,
|
priceChange,
|
||||||
priceChangeClass,
|
priceChangeClass,
|
||||||
|
isCryptoMarket,
|
||||||
|
showQuickTrade,
|
||||||
|
qtSymbol,
|
||||||
|
qtSide,
|
||||||
|
qtPrice,
|
||||||
|
openQuickTrade,
|
||||||
timeframe,
|
timeframe,
|
||||||
loadingWatchlist,
|
loadingWatchlist,
|
||||||
activeIndicators,
|
activeIndicators,
|
||||||
@@ -2042,6 +2121,8 @@ getMarketColor,
|
|||||||
showBacktestRunViewer,
|
showBacktestRunViewer,
|
||||||
selectedBacktestRun,
|
selectedBacktestRun,
|
||||||
handleViewBacktestRun,
|
handleViewBacktestRun,
|
||||||
|
handleCreateStrategyFromIndicator,
|
||||||
|
goToIndicatorMarket,
|
||||||
// 发布到社区相关
|
// 发布到社区相关
|
||||||
showPublishModal,
|
showPublishModal,
|
||||||
publishIndicator,
|
publishIndicator,
|
||||||
@@ -2196,6 +2277,18 @@ getMarketColor,
|
|||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qt-header-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border-color: rgba(24, 144, 255, 0.24);
|
||||||
|
color: #1890ff;
|
||||||
|
background: rgba(24, 144, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.symbol-info {
|
.symbol-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -2579,6 +2672,13 @@ getMarketColor,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.create-strategy-icon {
|
||||||
|
color: #fa8c16;
|
||||||
|
&:hover {
|
||||||
|
color: #ffa940;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.status-icon {
|
&.status-icon {
|
||||||
&.status-normal {
|
&.status-normal {
|
||||||
color: #52c41a;
|
color: #52c41a;
|
||||||
@@ -2995,6 +3095,12 @@ getMarketColor,
|
|||||||
color: #f6465d;
|
color: #f6465d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qt-header-btn {
|
||||||
|
color: #40a9ff;
|
||||||
|
background: rgba(24, 144, 255, 0.14);
|
||||||
|
border-color: rgba(24, 144, 255, 0.28);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3198,6 +3304,14 @@ getMarketColor,
|
|||||||
color: #d3adf7;
|
color: #d3adf7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.create-strategy-icon {
|
||||||
|
color: #faad14;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #ffc53d;
|
||||||
|
}
|
||||||
|
}
|
||||||
&.backtest-history-icon {
|
&.backtest-history-icon {
|
||||||
color: #5cdbd3;
|
color: #5cdbd3;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -1999,6 +1999,7 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
this.loadStrategies()
|
this.loadStrategies()
|
||||||
this.loadUserNotificationSettings()
|
this.loadUserNotificationSettings()
|
||||||
|
this.handleRouteCreateStrategyPrefill()
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
this.stopEquityPolling()
|
this.stopEquityPolling()
|
||||||
@@ -2591,6 +2592,52 @@ export default {
|
|||||||
this.loadExchangeCredentials()
|
this.loadExchangeCredentials()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
async handleRouteCreateStrategyPrefill () {
|
||||||
|
const query = (this.$route && this.$route.query) || {}
|
||||||
|
if (query.open !== 'from-indicator') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleCreateStrategy()
|
||||||
|
await this.$nextTick()
|
||||||
|
await Promise.all([
|
||||||
|
this.loadIndicators(),
|
||||||
|
this.loadWatchlist(),
|
||||||
|
this.loadExchangeCredentials()
|
||||||
|
])
|
||||||
|
|
||||||
|
const indicatorId = query.indicator_id ? String(query.indicator_id) : ''
|
||||||
|
const market = query.market || 'Crypto'
|
||||||
|
const symbol = query.symbol || ''
|
||||||
|
const timeframe = query.timeframe || '1D'
|
||||||
|
const fullSymbol = symbol ? (String(symbol).includes(':') ? String(symbol) : `${market}:${symbol}`) : ''
|
||||||
|
|
||||||
|
this.selectedMarketCategory = market
|
||||||
|
if (fullSymbol) {
|
||||||
|
this.handleMultiSymbolChange([fullSymbol])
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldValues = {
|
||||||
|
timeframe
|
||||||
|
}
|
||||||
|
if (indicatorId) {
|
||||||
|
fieldValues.indicator_id = indicatorId
|
||||||
|
}
|
||||||
|
this.form.setFieldsValue(fieldValues)
|
||||||
|
|
||||||
|
if (indicatorId) {
|
||||||
|
await this.handleIndicatorChange(indicatorId)
|
||||||
|
const indicator = this.availableIndicators.find(ind => String(ind.id) === indicatorId)
|
||||||
|
if (indicator) {
|
||||||
|
const strategyName = fullSymbol ? `${indicator.name} ${symbol}` : indicator.name
|
||||||
|
this.form.setFieldsValue({
|
||||||
|
strategy_name: strategyName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$router.replace({ path: this.$route.path, query: {} }).catch(() => {})
|
||||||
|
},
|
||||||
handleEditStrategy (strategy) {
|
handleEditStrategy (strategy) {
|
||||||
// 如果策略正在运行,提示用户先停止
|
// 如果策略正在运行,提示用户先停止
|
||||||
if (strategy.status === 'running') {
|
if (strategy.status === 'running') {
|
||||||
|
|||||||
Reference in New Issue
Block a user