quant_test / web_development /frontend /src /views /StrategiesView.vue
lucky-loster's picture
Upload folder using huggingface_hub
590a501 verified
Raw
History Blame Contribute Delete
8.42 kB
<template>
<div>
<el-card shadow="never" style="border: 1px solid var(--border-color); margin-bottom: 16px;">
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600;">可用策略</span>
<el-button type="primary" size="small" @click="showAddDialog = true"><el-icon><Plus /></el-icon> 添加策略</el-button>
</div>
</template>
<el-row :gutter="16">
<el-col :span="8" v-for="s in availableStrategies" :key="s.type">
<div style="padding: 16px; background: var(--bg-secondary); border-radius: 8px; border: 1px solid var(--border-color);">
<div style="font-size: 16px; font-weight: 600; margin-bottom: 8px; color: var(--accent-blue);">{{ s.name }}</div>
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 12px; line-height: 1.5;">{{ s.description }}</div>
<div style="font-size: 11px; color: var(--text-secondary);">默认参数: {{ JSON.stringify(s.default_params) }}</div>
</div>
</el-col>
</el-row>
</el-card>
<el-card shadow="never" style="border: 1px solid var(--border-color);">
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600;">策略实例</span>
<el-button size="small" text @click="loadStrategies"><el-icon><Refresh /></el-icon> 刷新</el-button>
</div>
</template>
<div v-if="strategies.length === 0" style="text-align: center; padding: 40px; color: var(--text-secondary);">暂无策略实例</div>
<el-table v-else :data="strategies" stripe style="width: 100%" size="small">
<el-table-column prop="strategy_id" label="策略ID" width="160" />
<el-table-column prop="strategy_type" label="类型" width="140" />
<el-table-column prop="symbol" label="合约" width="120" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 'RUNNING' ? 'success' : row.status === 'ERROR' ? 'danger' : 'info'" size="small" effect="dark">
{{ row.status === 'RUNNING' ? '运行中' : row.status === 'ERROR' ? '异常' : '已停止' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="参数">
<template #default="{ row }"><span style="font-size: 12px; color: var(--text-secondary);">{{ JSON.stringify(row.params) }}</span></template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-button v-if="row.status !== 'RUNNING'" size="small" type="success" text @click="startStrategy(row.strategy_id)">启动</el-button>
<el-button v-if="row.status === 'RUNNING'" size="small" type="warning" text @click="stopStrategy(row.strategy_id)">停止</el-button>
<el-button size="small" type="info" text @click="viewSignals(row.strategy_id)">信号</el-button>
<el-button size="small" type="danger" text @click="removeStrategy(row.strategy_id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="showAddDialog" title="添加策略" width="480px">
<el-form label-position="top">
<el-form-item label="策略类型">
<el-select v-model="newStrategy.strategy_type" style="width: 100%;" @change="onTypeChange">
<el-option v-for="s in availableStrategies" :key="s.type" :label="s.name" :value="s.type" />
</el-select>
</el-form-item>
<el-form-item label="分类筛选">
<el-select v-model="dialogCategory" style="width: 100%;" placeholder="全部" @change="onDialogCategoryChange">
<el-option label="全部" value="" />
<el-option v-for="cat in store.categoryList.slice(1)" :key="cat.key" :label="cat.label" :value="cat.key" />
</el-select>
</el-form-item>
<el-form-item label="合约">
<el-select v-model="newStrategy.symbol" style="width: 100%;" filterable>
<el-option v-for="c in dialogContracts" :key="c.symbol" :label="`${c.name} (${c.exchange})`" :value="c.symbol" />
</el-select>
</el-form-item>
<el-form-item v-for="(val, key) in newStrategy.params" :key="key" :label="paramLabel(key)">
<el-input-number v-model="newStrategy.params[key]" :precision="typeof val === 'number' && val % 1 !== 0 ? 2 : 0" style="width: 100%;" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">取消</el-button>
<el-button type="primary" @click="addStrategy">确认添加</el-button>
</template>
</el-dialog>
<el-dialog v-model="showSignals" title="策略信号" width="600px">
<el-table :data="signals" stripe style="width: 100%" size="small" :max-height="400">
<el-table-column prop="timestamp" label="时间" width="180" />
<el-table-column label="类型" width="80">
<template #default="{ row }"><el-tag :type="row.type === 'BUY' ? 'success' : 'danger'" size="small">{{ row.type }}</el-tag></template>
</el-table-column>
<el-table-column label="价格" width="100">
<template #default="{ row }">{{ row.price?.toFixed(2) }}</template>
</el-table-column>
<el-table-column prop="reason" label="原因" />
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { strategyApi } from '../api'
import { useTradingStore } from '../stores/trading'
const store = useTradingStore()
const availableStrategies = ref([])
const strategies = ref([])
const showAddDialog = ref(false)
const showSignals = ref(false)
const signals = ref([])
const dialogCategory = ref('')
const dialogContracts = computed(() => {
if (!dialogCategory.value) return store.symbolList
return store.symbolList.filter(c => c.category === dialogCategory.value)
})
function onDialogCategoryChange() {
const list = dialogContracts.value
if (list.length > 0 && !list.find(c => c.symbol === newStrategy.value.symbol)) {
newStrategy.value.symbol = list[0].symbol
}
}
const newStrategy = ref({ strategy_id: '', strategy_type: 'ma_crossover', symbol: '螺纹钢', params: { fast_period: 5, slow_period: 20, quantity: 1 } })
const PARAM_LABELS = { fast_period: '快线周期', slow_period: '慢线周期', quantity: '下单数量', period: '计算周期', std_dev: '标准差倍数', lookback: '回看周期', k1: 'K1系数', k2: 'K2系数' }
function paramLabel(key) { return PARAM_LABELS[key] || key }
function onTypeChange(type) {
const s = availableStrategies.value.find(s => s.type === type)
if (s) newStrategy.value.params = { ...s.default_params }
}
async function loadAvailable() { try { const { data } = await strategyApi.getAvailable(); availableStrategies.value = data } catch (e) { console.error(e) } }
async function loadStrategies() { try { const { data } = await strategyApi.getStrategies(); strategies.value = data } catch (e) { console.error(e) } }
async function addStrategy() {
try { await strategyApi.addStrategy(newStrategy.value); ElMessage.success('策略添加成功'); showAddDialog.value = false; loadStrategies() }
catch (e) { ElMessage.error(e.response?.data?.detail || '添加失败') }
}
async function startStrategy(id) { try { await strategyApi.startStrategy(id); ElMessage.success('策略已启动'); loadStrategies() } catch (e) { ElMessage.error('启动失败') } }
async function stopStrategy(id) { try { await strategyApi.stopStrategy(id); ElMessage.success('策略已停止'); loadStrategies() } catch (e) { ElMessage.error('停止失败') } }
async function removeStrategy(id) { try { await strategyApi.removeStrategy(id); ElMessage.success('策略已删除'); loadStrategies() } catch (e) { ElMessage.error('删除失败') } }
async function viewSignals(id) { try { const { data } = await strategyApi.getSignals(id); signals.value = data; showSignals.value = true } catch (e) { console.error(e) } }
onMounted(() => { loadAvailable(); loadStrategies(); store.fetchCategories(); store.fetchContracts() })
</script>