DEV Community

Headless Oracle
Headless Oracle

Posted on • Originally published at headlessoracle.com

Market Hours APIs Are Not Enough for Autonomous Agents

Every developer building a trading agent checks market hours. Almost none check them correctly.

The standard approach: call an API, parse the response, check if a flag says is_open: true. Then proceed.

This works when a human is in the loop. It fails silently when an autonomous agent is running at 3am.


What the standard approach misses

A market data API tells you what the market data provider believes is true. It doesn't prove it.

Four things can go wrong:

1. Stale data. A cached response from 45 minutes ago says the market is open. The market closed 40 minutes ago. The cache TTL was set to 1 hour. Your agent executes into a closed book.

2. No authentication on the market state. Anyone — including a compromised service or a man-in-the-middle — can return is_open: true. The response has no signature. Your agent cannot tell the difference between a live authoritative response and a forged or stale one.

3. Ambiguous failure modes. The API call fails with a 503. What does your agent do? Most implementations default open ("I couldn't check, so I'll proceed"). That's the wrong default. The safe assumption when you can't verify state is: don't proceed.

4. DST transitions. Your API uses UTC offsets. The exchange observes daylight saving time. On March 8, the US springs forward. For 21 days, the US/UK DST offset compresses from 5 hours to 4 hours. If your API uses hardcoded UTC offsets, it's wrong for three weeks every spring and fall.


What autonomous agents actually need

An agent that executes trades without human oversight needs three things a standard market hours API cannot provide:

Cryptographic proof. The response must be signed so the agent can verify it wasn't forged, intercepted, or replayed from a previous session. Ed25519 is the right primitive here — compact signatures, deterministic, composable into multi-party schemes.

A TTL. The agent needs to know when the attestation expires. A receipt that says "NYSE is OPEN" is only meaningful for the next 60 seconds. After that, the agent must re-fetch. A receipt without an expiry is a receipt that can be used indefinitely — including after the market has moved.

Fail-closed semantics. If the agent can't reach the oracle, or if the oracle returns an ambiguous state, the safe default is CLOSED. Not open. Not "retry." Halt execution until the state can be verified.

A boolean is_open flag satisfies none of these requirements.


The signed attestation model

A signed market receipt looks like this:

{
  "mic": "XNYS",
  "status": "OPEN",
  "issued_at": "2026-04-04T14:30:00.000Z",
  "expires_at": "2026-04-04T14:31:00.000Z",
  "issuer": "headlessoracle.com",
  "schema_version": "v5.0",
  "source": "SCHEDULE",
  "receipt_mode": "live",
  "signature": "a7f3b2...c9d4"
}
Enter fullscreen mode Exit fullscreen mode

The agent does four things with this:

  1. Verify the Ed25519 signature against the oracle's public key
  2. Check that expires_at is in the future
  3. Check that status === "OPEN"
  4. If any step fails — halt

The practical difference

Here's the naive version:

import requests

def is_market_open(symbol: str) -> bool:
    r = requests.get(f"https://some-api.com/market-hours/{symbol}")
    return r.json().get("is_open", False)
Enter fullscreen mode Exit fullscreen mode

If this returns True, your agent proceeds. If the API is down, it returns False and your agent doesn't proceed — which is actually safe. But if the API returns stale data, if the response is cached at an intermediate layer, or if the TTL logic is wrong, you're trading blind.

Here's the safe version:

from headless_oracle import OracleClient, verify

oracle = OracleClient()

def safe_to_trade(mic: str) -> bool:
    receipt = oracle.get_status(mic)

    # Step 1: Verify cryptographic signature
    if not verify(receipt):
        return False  # Signature invalid — fail closed

    # Step 2: Check TTL
    # (verify() already checks expires_at — if expired, returns False)

    # Step 3: Check status
    return receipt["status"] == "OPEN"
    # UNKNOWN and HALTED both return False — fail closed
Enter fullscreen mode Exit fullscreen mode

The difference is 4 lines. The second version is provably correct. The first version is probably correct, most of the time.

"Probably correct, most of the time" is fine when a human can catch the exception at 3am. It's not fine when no human is watching.


When this matters more than you think

The cost of getting market state wrong isn't just a bad fill. It's:

  • Executing a trade that can't settle (T+1/T+2 windows)
  • Triggering a circuit breaker with an order at a halted price
  • Trading into a pre-market session with 1/100th the liquidity
  • Executing through a DST transition window where the market is technically open but the clearing systems are running on reduced staffing

These aren't theoretical. They're the exact scenarios that the signed receipt model was designed to handle.


The three-line integration

from headless_oracle import OracleClient, verify

oracle = OracleClient()  # Auto-provisions a free sandbox key
receipt = oracle.get_status("XNYS")
assert verify(receipt) and receipt["status"] == "OPEN", "Market not verified open — trade blocked"
Enter fullscreen mode Exit fullscreen mode

Free sandbox key at headlessoracle.com/v5/sandbox. No signup required.


The oracle covers 28 exchanges across 6 regions. Ed25519 signatures. 60-second TTL. MCP server for Claude Desktop, Cursor, and any MCP-compatible agent.

Full documentation: headlessoracle.com/docs


I'm building Headless Oracle — headlessoracle.com

Top comments (0)