Go-live gate: refuse unsafe live trading

Setting MODE=live alone no longer starts trading. check_live_ready requires an
active risk limit, MAX_POSITION_PCT < 1.0, LIVE_CONFIRMATION exactly
"I UNDERSTAND THE RISK", a minimum balance, and exchange credentials. Enforced
in Runtime.start_many (both start paths) and surfaced at GET /api/live-check;
the dashboard shows a banner and blocks the start buttons. Adds tests.
This commit is contained in:
matitos committed 2026-09-22 12:55:12 +00:00
1 parent b60cddf13d
commit 0173e46cfb
10 files changed
+241 -1

No files matched your search

+31 -1
View File
@@ -33,6 +33,7 @@ const strategies = ref([])
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 positions = ref([])
const orders = ref([])
const events = ref([])
@@ -97,6 +98,13 @@ async function handleDeletePreset(id) {
}
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)
@@ -112,7 +120,18 @@ async function refreshMulti() {
multiStatus.value = await api.getMultiStatus()
}
async function refreshLiveCheck() {
liveCheck.value = await api.getLiveCheck()
}
async function handleStartMany(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 {
multiStatus.value = await api.startMany(payload)
@@ -249,7 +268,8 @@ onMounted(() => {
statusTimer = setInterval(
() => {
api.getStatus().then((s) => (status.value = s)).catch(() => {})
refreshMulti().catch(() => {})
refreshMulti().catch(() => {})
refreshLiveCheck().catch(() => {})
},
3000,
)
@@ -307,6 +327,16 @@ onUnmounted(() => {
flattened. Restart the bot to resume (limits reset).
</div>
<div
v-if="liveCheck.mode === 'live' && !liveCheck.ok"
class="rounded-lg border border-amber-500 bg-amber-500/15 p-3 text-sm text-amber-100"
>
<p class="font-semibold">Live trading is blocked by the go-live checks:</p>
<ul class="mt-1 list-disc pl-5">
<li v-for="(b, i) in liveCheck.blockers" :key="i">{{ b }}</li>
</ul>
</div>
<div v-if="status.error" class="rounded-lg border border-rose-500/40 bg-rose-500/10 p-3 text-sm text-rose-200">
{{ status.error }}
</div>
+1
View File
@@ -5,6 +5,7 @@ const http = axios.create({ baseURL: '/api', timeout: 10000 })
export const getVersion = () => http.get('/version').then((r) => r.data)
export const getStatus = () => http.get('/status').then((r) => r.data)
export const getMultiStatus = () => http.get('/bot/status').then((r) => r.data)
export const getLiveCheck = () => http.get('/live-check').then((r) => r.data)
export const startMany = (payload) => http.post('/bot/start-many', payload).then((r) => r.data)
export const getStrategies = () => http.get('/strategies').then((r) => r.data)
export const getPositions = () => http.get('/positions').then((r) => r.data)