From 50b22df94815b619b8244fe83e097e24169e9362 Mon Sep 17 00:00:00 2001 From: matitos Date: Tue, 22 Sep 2026 17:57:10 +0000 Subject: [PATCH] Merge the single and multi strategy panels into one The single "Strategy" panel and "Strategies (multi)" were redundant: the multi panel with one row is exactly the single-strategy behaviour. Remove StrategyPanel and make "Strategies" the one trading panel. Presets move into it (load adds a row, per-row Save), and the header now reports "N strategies" when several run instead of only the first. Deletes the obsolete component and its test; adds preset tests to the multi-strategy suite. --- frontend/src/App.vue | 85 ++++++------------- .../src/components/MultiStrategyPanel.vue | 43 +++++++++- frontend/src/components/StrategyPanel.vue | 58 ------------- frontend/src/components/StrategyRowEditor.vue | 7 +- frontend/src/multi-strategy.test.js | 54 +++++++++++- frontend/src/strategy-panel.test.js | 48 ----------- 6 files changed, 119 insertions(+), 176 deletions(-) delete mode 100644 frontend/src/components/StrategyPanel.vue delete mode 100644 frontend/src/strategy-panel.test.js diff --git a/frontend/src/App.vue b/frontend/src/App.vue index eb71018..4b8d9eb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -9,7 +9,6 @@ import MultiStrategyPanel from './components/MultiStrategyPanel.vue' import OrdersTable from './components/OrdersTable.vue' import PositionsTable from './components/PositionsTable.vue' import StatusCards from './components/StatusCards.vue' -import StrategyPanel from './components/StrategyPanel.vue' import SweepPanel from './components/SweepPanel.vue' const status = ref({ @@ -35,6 +34,27 @@ const strategiesLoading = ref(true) const presets = ref([]) const multiStatus = ref({ running: false, strategies: [], total_pnl: 0, equity: 0 }) const liveCheck = ref({ ok: true, mode: 'paper', blockers: [] }) + +const runningLabel = computed(() => { + const list = multiStatus.value.strategies || [] + if (!list.length) return 'Idle' + if (list.length === 1) { + return `Running: ${list[0].strategy_name || list[0].strategy_key}` + } + return `Running: ${list.length} strategies` +}) + +const scopeLabel = computed(() => { + const list = multiStatus.value.strategies || [] + if (!list.length) { + return status.value.symbol ? `${status.value.symbol} ${status.value.timeframe}` : '' + } + const first = list[0] + const sameScope = list.every( + (s) => s.symbol === first.symbol && s.timeframe === first.timeframe, + ) + return sameScope ? `${first.symbol} ${first.timeframe}` : `${list.length} pairs` +}) const positions = ref([]) const orders = ref([]) const events = ref([]) @@ -98,25 +118,6 @@ async function handleDeletePreset(id) { await loadPresets() } -async function handleStart(payload) { - if (liveCheck.value.mode === 'live' && !liveCheck.value.ok) { - status.value = { - ...status.value, - error: 'Live trading blocked: ' + liveCheck.value.blockers.join('; '), - } - return - } - busy.value = true - try { - status.value = await api.startBot(payload) - await refreshAll() - } catch (error) { - status.value = { ...status.value, error: error.response?.data?.detail || error.message } - } finally { - busy.value = false - } -} - async function refreshMulti() { multiStatus.value = await api.getMultiStatus() } @@ -168,29 +169,6 @@ async function handleKillMany() { } } -async function handleStop() { - busy.value = true - try { - status.value = await api.stopBot() - await refreshMulti() - await refreshAll() - } finally { - busy.value = false - } -} - -async function handleKill() { - busy.value = true - try { - status.value = await api.killBot('manual kill switch') - await refreshAll() - } catch (error) { - status.value = { ...status.value, error: error.response?.data?.detail || error.message } - } finally { - busy.value = false - } -} - async function handleClose(id) { try { status.value = await api.closePosition(id) @@ -314,9 +292,8 @@ onUnmounted(() => {
- Running: {{ status.strategy_name }} - Idle - · {{ status.symbol }} {{ status.timeframe }} + {{ runningLabel }} + · {{ scopeLabel }}
@@ -344,27 +321,17 @@ onUnmounted(() => { - - diff --git a/frontend/src/components/MultiStrategyPanel.vue b/frontend/src/components/MultiStrategyPanel.vue index caf3459..d33d414 100644 --- a/frontend/src/components/MultiStrategyPanel.vue +++ b/frontend/src/components/MultiStrategyPanel.vue @@ -7,10 +7,11 @@ import StrategyRowEditor from './StrategyRowEditor.vue' const props = defineProps({ strategies: { type: Array, required: true }, multiStatus: { type: Object, required: true }, + presets: { type: Array, default: () => [] }, busy: { type: Boolean, default: false }, loading: { type: Boolean, default: false }, }) -const emit = defineEmits(['start-many', 'stop', 'kill']) +const emit = defineEmits(['start-many', 'stop', 'kill', 'save-preset', 'delete-preset']) const startingBalance = ref(10000) // Each row: { strategy_key, symbol, timeframe, weight, params } @@ -64,6 +65,32 @@ function removeRow(index) { rows.value.splice(index, 1) } +// Load a saved preset as an additional row. +function loadPreset(event) { + const preset = props.presets.find((p) => p.id === Number(event.target.value)) + event.target.value = '' + if (!preset) return + rows.value.push({ + strategy_key: preset.strategy_key, + symbol: preset.symbol, + timeframe: preset.timeframe, + weight: 1, + params: { ...preset.params }, + }) +} + +function saveRow(row) { + const name = window.prompt('Preset name', row.strategy_key) + if (name === null) return + emit('save-preset', { + name: name || row.strategy_key, + strategy_key: row.strategy_key, + symbol: row.symbol, + timeframe: row.timeframe, + params: row.params, + }) +} + function start() { emit('start-many', { strategies: rows.value.map((r) => ({ @@ -85,7 +112,7 @@ function confirmKill() {