2 Commits

Author SHA1 Message Date
mpabi
b2365e7f91 feat(api): allow new chart timeframes in staging 2026-03-29 14:03:12 +02:00
mpabi
ebeb773015 feat(bot): add control-plane schema and hasura tracking 2026-03-23 23:51:27 +01:00
3 changed files with 88 additions and 1 deletions

View File

@@ -744,7 +744,7 @@ function computeCandleFlowMovesFromTicks({ candle, bucketSeconds, points, rows,
}
function parseTimeframeToSeconds(tf) {
const v = String(tf || '').trim().toLowerCase();
const v = normalizeChartTimeframe(tf);
if (!v) return 60;
const m = v.match(/^(\d+)(s|m|h|d)$/);
if (!m) throw new Error(`invalid_tf`);
@@ -755,6 +755,40 @@ function parseTimeframeToSeconds(tf) {
return num * mult;
}
const SUPPORTED_CHART_TIMEFRAMES = new Set([
'1s',
'5s',
'15s',
'30s',
'1m',
'5m',
'15m',
'30m',
'1h',
'4h',
'8h',
'12h',
'1d',
]);
function normalizeChartTimeframe(tf) {
const raw = String(tf || '').trim();
if (!raw) return '1m';
const aliased =
raw === '1D' || raw === '24h' || raw === '24H'
? '1d'
: raw === '1H'
? '1h'
: raw.toLowerCase();
if (!SUPPORTED_CHART_TIMEFRAMES.has(aliased)) {
throw new Error('invalid_tf');
}
return aliased;
}
function fillForwardCandles(candles, { bucketSeconds, limit, nowSec }) {
if (!Array.isArray(candles) || candles.length === 0) return [];
if (!Number.isFinite(bucketSeconds) || bucketSeconds <= 0) return candles;

View File

@@ -92,6 +92,9 @@ async function main() {
const PUBLIC_DLOB_SOURCE_HEADER = 'X-Hasura-Dlob-Source';
const apiTokensTable = { schema: 'public', name: 'api_tokens' };
const botConfigTable = { schema: 'public', name: 'bot_config' };
const botStateTable = { schema: 'public', name: 'bot_state' };
const botEventsTable = { schema: 'public', name: 'bot_events' };
const source = 'default';
const baseTicks = { schema: 'public', name: 'drift_ticks' };
@@ -460,6 +463,12 @@ async function main() {
'raw',
], { publicFilter: dlobPublicFilter });
// Bot control-plane tables (tracked; permissions are intentionally not created here by default).
for (const t of [botConfigTable, botStateTable, botEventsTable]) {
await metadataIgnore({ type: 'pg_untrack_table', args: { source, table: t } });
await metadata({ type: 'pg_track_table', args: { source, table: t } });
}
// Return table type for candle functions (needed for Hasura to track the function).
await metadataIgnore({ type: 'pg_track_table', args: { source, table: candlesReturnTable } });

View File

@@ -111,6 +111,50 @@ ALTER TABLE api_tokens ADD COLUMN IF NOT EXISTS scopes TEXT[] NOT NULL DEFAULT A
CREATE INDEX IF NOT EXISTS api_tokens_revoked_at_idx
ON api_tokens (revoked_at);
-- Bot control-plane (desired state) + executor telemetry (state/events).
--
-- MVP intent:
-- - `bot_config`: desired state (mode/market/limits/kill switch)
-- - `bot_state`: heartbeat + last error/action
-- - `bot_events`: append-only audit log
CREATE TABLE IF NOT EXISTS public.bot_config (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL UNIQUE,
market_name TEXT NOT NULL,
market_type TEXT NOT NULL DEFAULT 'perp',
mode TEXT NOT NULL DEFAULT 'off',
kill_switch BOOLEAN NOT NULL DEFAULT FALSE,
params JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS bot_config_updated_at_desc_idx
ON public.bot_config (updated_at DESC);
CREATE TABLE IF NOT EXISTS public.bot_state (
bot_id UUID PRIMARY KEY REFERENCES public.bot_config(id) ON DELETE CASCADE,
last_heartbeat_at TIMESTAMPTZ,
last_action_at TIMESTAMPTZ,
last_error TEXT,
state JSONB,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS bot_state_updated_at_desc_idx
ON public.bot_state (updated_at DESC);
CREATE TABLE IF NOT EXISTS public.bot_events (
id BIGSERIAL PRIMARY KEY,
bot_id UUID NOT NULL REFERENCES public.bot_config(id) ON DELETE CASCADE,
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
type TEXT NOT NULL,
payload JSONB
);
CREATE INDEX IF NOT EXISTS bot_events_bot_ts_desc_idx
ON public.bot_events (bot_id, ts DESC);
-- Compute OHLC candles from `drift_ticks` for a symbol and bucket size.
-- Exposed via Hasura (track function) and used by trade-api to compute indicators server-side.
-- Hasura tracks functions only if they return SETOF a table/view type.