Add per-symbol close position and clear orders actions to demo trading

The API queues the request in Redis and the executor applies it on its
next pass, so it never races reconciliation. Both actions cancel every
open order on the symbol, including manual ones, and market-close any
position. Plans end as manual_close or manual_cancel without queueing
a new analysis. Closing also stops trading so the engine does not
reopen right away.
This commit is contained in:
eezd committed 2026-10-16 13:25:59 +08:00
1 parent ac1e278b3b
commit e3242f2b7a
4 files changed
+107 -9

No files matched your search

+7
View File
@@ -253,6 +253,8 @@ export const CLOSE_REASON_LABELS: Record<string, string> = {
external: "外部平仓",
stop_failed: "止损挂单失败",
close: "市价平仓",
manual_close: "一键平仓",
manual_cancel: "清除挂单",
// 计划 3.1 之前的旧原因,历史仓位里还会出现
invalidation: "短线失效条件触发",
replaced: "被新计划替换",
@@ -319,6 +321,11 @@ export function requestAnalysis(symbol: string): Promise<void> {
return http.post(`/trading/symbols/${symbol}/analyze`)
}
/** 一键平仓(同时停止行情交易)、清除挂单:由执行器在几秒内执行,结果写入时间线 */
export function manualAction(symbol: string, action: "close" | "cancel"): Promise<void> {
return http.post(`/trading/symbols/${symbol}/manual/${action}`)
}
export function listAnalyses(params: { symbol?: string, limit?: number }): Promise<Analysis[]> {
return http.get("/trading/analyses", { params })
}
+36 -2
View File
@@ -3,7 +3,7 @@ import type { Effort, Provider } from "@/apis/settings"
import type { Analysis, OrderRow, SnapshotPosition, TradingEvent, TradingStatus, TradingSymbol } from "@/apis/trading"
import { Clock, Edit, Plus } from "@element-plus/icons-vue"
import { EFFORTS, listProviders } from "@/apis/settings"
import { deleteSymbol, getStatus, getSymbolPlan, isOwnOrder, listAnalyses, listEvents, listSymbols, patchConfig, requestAnalysis, startTrading, stopTrading } from "@/apis/trading"
import { deleteSymbol, getStatus, getSymbolPlan, isOwnOrder, listAnalyses, listEvents, listSymbols, manualAction, patchConfig, requestAnalysis, startTrading, stopTrading } from "@/apis/trading"
import { usePersisted } from "@/utils/storage"
import { formatTime } from "@/utils/text"
import AddSymbolDialog from "./components/AddSymbolDialog.vue"
@@ -231,6 +231,27 @@ async function changeEffort(effort: Effort | null) {
status.value = await patchConfig({ reasoning_effort: effort })
}
async function closePosition(symbol: string) {
await ElMessageBox.confirm(
`撤销 ${symbol} 的全部挂单(含止损、止盈和手动挂的单),市价平掉持仓,并停止行情交易(所有币不再做新的分析)。`,
"一键平仓",
{ type: "warning", confirmButtonText: "平仓" }
)
await manualAction(symbol, "close")
ElMessage.success("已提交,几秒内执行,结果见时间线")
await refresh()
}
async function clearOrders() {
await ElMessageBox.confirm(
`撤销 ${selected.value} 的全部挂单,包括止损、止盈和手动挂的单。有持仓时会一并市价平仓,程序不保留没有止损的仓位。行情交易不停止。`,
"清除挂单",
{ type: "warning", confirmButtonText: "清除" }
)
await manualAction(selected.value, "cancel")
ElMessage.success("已提交,几秒内执行,结果见时间线")
}
async function remove(s: TradingSymbol) {
await ElMessageBox.confirm(`把 ${s.symbol} 移出交易列表?历史分析和时间线会保留。`, "删除确认", { type: "warning" })
await deleteSymbol(s.symbol)
@@ -454,6 +475,14 @@ onBeforeUnmount(() => clearInterval(timer))
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ row }">
<el-button v-if="symbols.some(s => s.symbol === row.symbol)" link type="danger" @click="closePosition(row.symbol)">
平仓
</el-button>
<span v-else class="font-sans text-slate-400" title="不在交易列表中,程序不管理,请到币安网页处理">-</span>
</template>
</el-table-column>
</el-table>
</el-card>
@@ -508,7 +537,12 @@ onBeforeUnmount(() => clearInterval(timer))
<div class="flex flex-col gap-4">
<el-card v-if="current" shadow="never" body-class="flex flex-col gap-2">
<template #header>
当前挂单({{ symbolOrders.length }})
<div class="flex items-center justify-between">
当前挂单({{ symbolOrders.length }})
<el-button link type="danger" :disabled="!symbolOrders.length" @click="clearOrders">
清除挂单
</el-button>
</div>
</template>
<div
v-for="o in symbolOrders" :key="`${o.client_id}-${o.time}`"