Multi-strategy UI

Adds a "Strategies (multi)" panel: add/remove strategy rows (strategy, symbol,
timeframe, per-strategy params and weight), start all, stop all and kill all,
plus a per-strategy status table (allocation, equity, P&L, halted). Polls
/api/bot/status alongside the existing status. Includes component tests.
This commit is contained in:
matitos committed 2026-09-22 11:59:21 +00:00
1 parent 3229a0e2aa
commit ce2b11a803
6 files changed
+499 -1

No files matched your search

+58 -1
View File
@@ -4,6 +4,7 @@ import * as api from './api'
import BacktestPanel from './components/BacktestPanel.vue'
import EquityChart from './components/EquityChart.vue'
import EventLog from './components/EventLog.vue'
import MultiStrategyPanel from './components/MultiStrategyPanel.vue'
import OrdersTable from './components/OrdersTable.vue'
import PositionsTable from './components/PositionsTable.vue'
import StatusCards from './components/StatusCards.vue'
@@ -31,6 +32,7 @@ const status = ref({
const strategies = ref([])
const strategiesLoading = ref(true)
const presets = ref([])
const multiStatus = ref({ running: false, strategies: [], total_pnl: 0, equity: 0 })
const positions = ref([])
const orders = ref([])
const events = ref([])
@@ -106,10 +108,51 @@ async function handleStart(payload) {
}
}
async function refreshMulti() {
multiStatus.value = await api.getMultiStatus()
}
async function handleStartMany(payload) {
busy.value = true
try {
multiStatus.value = await api.startMany(payload)
status.value = await api.getStatus()
await refreshAll()
} catch (error) {
status.value = { ...status.value, error: error.response?.data?.detail || error.message }
} finally {
busy.value = false
}
}
async function handleStopMany() {
busy.value = true
try {
await api.stopBot()
status.value = await api.getStatus()
await refreshMulti()
await refreshAll()
} finally {
busy.value = false
}
}
async function handleKillMany() {
busy.value = true
try {
status.value = await api.killBot('manual kill switch (all)')
await refreshMulti()
await refreshAll()
} finally {
busy.value = false
}
}
async function handleStop() {
busy.value = true
try {
status.value = await api.stopBot()
await refreshMulti()
await refreshAll()
} finally {
busy.value = false
@@ -193,6 +236,7 @@ onMounted(() => {
// Initial loads are best-effort.
api.getStatus().then((s) => (status.value = s)).catch(() => {})
refreshAll().catch(() => {})
refreshMulti().catch(() => {})
// The strategy list and presets rarely change, so load them once but keep
// retrying until they arrive: a single failed first request used to leave the
@@ -203,7 +247,10 @@ onMounted(() => {
// Poll fast enough that the UI feels live even when it is the only channel.
pollTimer = setInterval(() => refreshAll().catch(() => {}), 3000)
statusTimer = setInterval(
() => api.getStatus().then((s) => (status.value = s)).catch(() => {}),
() => {
api.getStatus().then((s) => (status.value = s)).catch(() => {})
refreshMulti().catch(() => {})
},
3000,
)
// Retry the static lists until non-empty (covers a failed first load and a
@@ -279,6 +326,16 @@ onUnmounted(() => {
@delete-preset="handleDeletePreset"
/>
<MultiStrategyPanel
:strategies="strategies"
:multi-status="multiStatus"
:busy="busy"
:loading="strategiesLoading"
@start-many="handleStartMany"
@stop="handleStopMany"
@kill="handleKillMany"
/>
<BacktestPanel :strategies="strategies" :status="status" :loading="strategiesLoading" />
<SweepPanel :strategies="strategies" :status="status" :loading="strategiesLoading" />