Output Charts

Blog

Have your agent hand back a dashboard link, not a wall of logs

When a script or AI agent finishes a run, make its last step a push to a dashboard. The team reads one link instead of scrolling the transcript.

An agent finishes a backtest at 2 a.m. and leaves you four thousand lines of output. Somewhere in there is the only line that matters: did the strategy make money, and how badly did it hurt along the way? You find it, paste it into chat, and someone asks for the drawdown. Back to the logs.

The output of a run is changing. It used to be a diff for engineers or a transcript for whoever ran it. Now more people want something they can just open: a page with the result, the same page every time, current after every run.

What usually happens instead

  • The transcript. Complete, honest and unreadable. Nobody outside the session opens it twice.
  • An HTML file the agent wrote. It looks great, it's stuck at the moment it was written, and it lives on one machine.
  • A tool-specific shared view. Handy, but tied to the tool that made it and often only visible to people on that team.
  • A BI tool. Now the results need a warehouse, a schema and a connector, all for a dozen numbers that come out of one script.

There's a simpler split. The run already knows the numbers, and a dashboard only needs to draw them. So the last step of the run should be sending the numbers somewhere that draws them.

The pattern

On Output Charts, each visual on a dashboard holds one JSON document. A run ends by replacing those documents with PUT requests. The dashboard's link never changes, and anyone who has it open sees the new numbers within 15 seconds.

For a backtest, four visuals do most of the work:

  • A KPI for the headline number: return, Sharpe, whatever you trust.
  • A line for the equity curve.
  • A table of the runs you compared.
  • A markdown note the agent writes: what it tried, and what it would try next.

API push is part of Solo ($10 a month, up to five keys). On Free you can paste the same documents into the editor by hand, which works fine for a run a week.

The documents

The KPI:

{ "label": "Net return", "value": 0.184, "format": "percent", "delta": -0.021, "deltaFormat": "percent", "deltaLabel": "vs last run" }

percent multiplies by 100, so 0.184 shows as 18.4%.

The equity curve:

{
  "xKey": "day",
  "curve": "linear",
  "series": [
    { "key": "strategy", "label": "Strategy" },
    { "key": "benchmark", "label": "Buy and hold" }
  ],
  "data": [
    { "day": "2026-09-01", "strategy": 10000, "benchmark": 10000 },
    { "day": "2026-09-02", "strategy": 10140, "benchmark": 10060 },
    { "day": "2026-09-03", "strategy": 9980, "benchmark": 10110 }
  ]
}

The x values are strings and the series values are numbers. Each row holds exactly those keys and nothing else. Up to 500 rows, so downsample a long minute-by-minute curve to daily before you send it.

The notes:

{ "source": "## Run 14\n\n- Tightened the stop to 1.5 ATR\n- Drawdown down, return down a bit more\n- Next: try the same stop on the 4h bars" }

The push

Here it is in Python. It runs as the last step of the backtest, whether a person or an agent started it:

import os
import requests

KEY = os.environ["OC_KEY"]

def push(url, document):
    r = requests.put(url, json=document,
                     headers={"Authorization": f"Bearer {KEY}"}, timeout=10)
    if r.status_code == 422:
        raise ValueError(r.json().get("details"))
    r.raise_for_status()

push(os.environ["OC_RETURN_URL"], {
    "label": "Net return", "value": round(net_return, 4), "format": "percent",
    "delta": round(net_return - last_return, 4), "deltaFormat": "percent",
    "deltaLabel": "vs last run",
})

push(os.environ["OC_EQUITY_URL"], {
    "xKey": "day", "curve": "linear",
    "series": [{"key": "strategy", "label": "Strategy"},
               {"key": "benchmark", "label": "Buy and hold"}],
    "data": [{"day": d, "strategy": round(s, 2), "benchmark": round(b, 2)}
             for d, s, b in equity_rows],
})

push(os.environ["OC_NOTES_URL"], {"source": run_notes})

net_return, last_return, equity_rows and run_notes are whatever your run produced. Each visual's push URL comes from Push from code → Copy push URL in the editor.

If an agent drives the run, put one line in its instructions: when the run is finished, call push_results.py. Then its final message can be the dashboard link.

Things to know

  • Give the agent its own key. Keys start with oc_live_ and you can revoke them one at a time. If an agent misbehaves, revoke its key and nothing else breaks.
  • A push replaces the document. Send the whole curve each time, not just the new day. Re-running is safe, because the last push wins.
  • 60 pushes a minute per key. If a loop pushes on every tick, it will hit that limit. Push once per run instead.
  • The dashboard draws what it's sent. It won't recompute your Sharpe ratio or catch a lookahead bug. Garbage in, beautifully drawn garbage out.
  • A 422 names the field. Reading data[3].strategy Expected number beats wondering why the chart is blank.

Who sees it

Keep it private, share it view-only by email with the people who should see it, or publish it as a link that opens for anyone without an account. Publish only what you'd be fine seeing on the open internet.

Try it

Paste the documents above into JSON to chart to see them drawn. Then create a free dashboard, and turn on Solo when you want the run to push by itself.

More from the blog