- GET /api/metrics renders Prometheus text (equity, P&L, per-strategy equity/pnl/allocation/halted, info) with no client library. - In-memory ring log handler (app/logs.py) exposed at GET /api/logs and a dashboard "Server logs" panel; Runtime mirrors bot events into the logger. - docs/runbook.md covers deploys, the kill switch, resetting paper state and going live. Adds tests for metrics rendering and the log ring.
111 lines
4.1 KiB
JavaScript
111 lines
4.1 KiB
JavaScript
import axios from 'axios'
|
|
|
|
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)
|
|
export const getOrders = () => http.get('/orders').then((r) => r.data)
|
|
export const getEvents = () => http.get('/events').then((r) => r.data)
|
|
export const getEquity = () => http.get('/equity').then((r) => r.data)
|
|
export const getLogs = (limit = 100) =>
|
|
http.get('/logs', { params: { limit } }).then((r) => r.data)
|
|
|
|
export const startBot = (payload) => http.post('/bot/start', payload).then((r) => r.data)
|
|
export const stopBot = () => http.post('/bot/stop').then((r) => r.data)
|
|
export const killBot = (reason = 'manual kill switch') =>
|
|
http.post('/bot/kill', { reason }).then((r) => r.data)
|
|
export const closePosition = (id, reason = 'manual') =>
|
|
http.post(`/positions/${id}/close`, { reason }).then((r) => r.data)
|
|
export const placeOrder = (payload) => http.post('/orders', payload).then((r) => r.data)
|
|
export const runBacktest = (payload) =>
|
|
http.post('/backtest', payload, { timeout: 60000 }).then((r) => r.data)
|
|
|
|
export const runSweep = (payload) =>
|
|
http.post('/sweep', payload, { timeout: 120000 }).then((r) => r.data)
|
|
|
|
export const getPresets = () => http.get('/presets').then((r) => r.data)
|
|
export const createPreset = (payload) => http.post('/presets', payload).then((r) => r.data)
|
|
export const deletePreset = (id) => http.delete(`/presets/${id}`).then((r) => r.data)
|
|
|
|
export function openSocket({ onMessage, onOpen, onClose, onError }) {
|
|
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'
|
|
const url = `${proto}://${window.location.host}/api/ws`
|
|
let socket
|
|
let retryTimer
|
|
let closed = false
|
|
let attempt = 0
|
|
|
|
const schedule = () => {
|
|
if (closed) return
|
|
// Retry at 1s, then back off up to 30s so a persistently failing socket
|
|
// does not hammer the server while a brief blip recovers immediately.
|
|
const delay = Math.min(1000 * 2 ** attempt, 30000)
|
|
attempt += 1
|
|
retryTimer = setTimeout(connect, delay)
|
|
}
|
|
|
|
const connect = () => {
|
|
if (closed) return
|
|
let opened = false
|
|
let reported = false
|
|
console.debug('[ws] connecting to', url)
|
|
socket = new WebSocket(url)
|
|
|
|
// Report at most one failure per attempt (Safari often fires error *and*
|
|
// close for the same bad handshake).
|
|
const reportFailure = (code, detail) => {
|
|
if (reported) return
|
|
reported = true
|
|
onError?.(code, detail)
|
|
}
|
|
|
|
// Some mobile browsers leave a blocked upgrade hanging without firing
|
|
// onclose/onerror. Treat a slow handshake as a failure so the caller can
|
|
// fall back to polling instead of waiting forever.
|
|
const openTimeout = setTimeout(() => {
|
|
if (!opened) {
|
|
reportFailure(0, 'handshake timed out')
|
|
socket.close()
|
|
}
|
|
}, 5000)
|
|
|
|
socket.onopen = () => {
|
|
opened = true
|
|
clearTimeout(openTimeout)
|
|
attempt = 0
|
|
console.debug('[ws] open')
|
|
onOpen?.()
|
|
}
|
|
socket.onmessage = (event) => onMessage?.(JSON.parse(event.data))
|
|
socket.onclose = (event) => {
|
|
clearTimeout(openTimeout)
|
|
console.debug('[ws] close', event.code, event.reason)
|
|
onClose?.()
|
|
if (!opened) {
|
|
// 1008 = policy violation (token rejected); 1006 = abnormal close.
|
|
reportFailure(event.code, event.reason)
|
|
}
|
|
schedule()
|
|
}
|
|
socket.onerror = (event) => {
|
|
clearTimeout(openTimeout)
|
|
if (!opened) reportFailure(0, event?.message || 'connection error')
|
|
socket.close()
|
|
}
|
|
}
|
|
connect()
|
|
|
|
return {
|
|
close() {
|
|
closed = true
|
|
clearTimeout(retryTimer)
|
|
socket?.close()
|
|
},
|
|
}
|
|
}
|