Skip to content
Join the waitlistWaitlist

Event schema for the monitor

The events a running Guard publishes on its local API, for the monitor page, Telegram alerts and your own tools.

Guard’s own address, local only (127.0.0.1:8547 by default), over HTTP:

  • GET /guard/events?since=N: the events after sequence number N, oldest first (the last 1,000 are kept in memory). Poll it; pass the last seq you saw.
  • GET /guard/decision?nonce=N: Guard’s decision on one request, by the nonce the bot signed it with, and what was sent for it. Read from the journal on disk, so it reaches back to Guard’s first day.
  • GET /guard/status: the state now (mode, kill switch, risk state, alerts, rules).

Read-only: nothing sent to them changes Guard. The one write path is the kill switch (POST /guard/kill, signed with a client key): it can only pull, never release.

Every event has:

FieldTypeMeaning
seqinteger1, 2, 3, … over the journal’s life; a gap means you missed events
at_msintegerUTC epoch milliseconds
kindstringone of the kinds below

The schema is version 1 (the started event and the status carry schema: 1). Fields may be added within a version, never removed or changed in meaning. Money is a decimal string, never a JSON number: "0.03144", not 0.03144.

Guard started: schema, version, mode (paper, testnet, mainnet), account, rules (the zr1_ code), clients (the client addresses).

One per request to /exchange (HTTP or WebSocket), also when it was refused before it could be judged.

{"seq":42,"at_ms":1791000000000,"kind":"decision","via":"http",
"client":"0x14791697260e4c9a71f18484c9f997b308e59325","signed_as":"testnet","nonce":1791000000000,
"action":"order","verdict":"resize","code":"resized",
"text":"a loss at the stop within 2% of equity and open risk within 6%",
"changes":["limit price 3150 pulled in to 3015, 0.5% from the mid 3000",
"size 10 cut to 2.5537 ETH for a loss at the stop within 2% of equity and open risk within 6%",
"a stop loss at 2940 attached (2% from the price)"],
"request":{"type":"order","orders":[…],"grouping":"na"},
"pre":[{"type":"updateLeverage","asset":1,"isCross":false,"leverage":4}],
"forward":{"type":"order","orders":[…],"grouping":"normalTpsl"},
"post":[]}
  • verdict: allow, resize (something changed: see changes) or veto. code: Veto and reason codes.
  • client, signed_as, nonce are null for a request whose signature did not check out.
  • pre, forward, post: what Guard sends, or would send in paper mode: the leverage first, the action, then cancelling Guard’s own stop once a tighter one of the bot’s rests.

What went to Hyperliquid and what came back (never in paper mode): decision (the decision’s seq), nonce (Guard’s own), ok, reply (the venue’s answer).

Whenever the risk engine’s state changes: state (active, or halted_for_day / stopped with their details), equity, open_positions, discrepancies, journal_error.

A daily loss stop, drawdown halt or the kill switch closed everything: reason, actions (the cancels and closes), problems (what could not be priced), sent (false in paper mode).

Positions without a market stop covering them in full: Guard placed its own stop (actions), closed a position whose stop the venue refused, or in paper mode would have. coins, actions, problems, sent.

The kill switch latched: reason (from zunder-guard kill, the kill file, or POST /guard/kill with the client that pulled it).

text: something went wrong outside a request (reading the account, a refused close). Never contains a key; addresses only.

Guard stopped: reason (a signal).

seq 41 started paper, rules zr1_…
seq 42 decision order → resize (size 10 cut to 2.5537 ETH, stop 2940 attached)
seq 43 sent ok, filled 2.5537 @ 3015
seq 44 decision order → veto [market_not_allowed]
seq 45 decision modify → veto [stop_loosened] (2940 to 2900 would loosen it)
seq 46 protect ETH: Guard's stop at 2940 for a position opened in the app
seq 47 risk halted_for_day, equity 1880
seq 48 flatten daily loss stop: 1 cancel, 1 close

This page as plain Markdown, for people and LLMs: /docs/reference/events.md