| name | using-visual-chart-annotations |
| description | How to read Wayfinder Shells frontend state, create chart panes, and add TradingView annotations or overlays to the default live chart or agent-created workspace charts. |
| metadata | {"tags":"wayfinder, shells, opencode, frontend, charts, annotations, overlays"} |
TL;DR
Read the current Shells chart id, then write chart changes through the chart workspace API. The same annotation tool works for the default live chart and for agent-created workspace charts.
Typical flow (switch default market):
1. visual_set_active_market(query="PENGU perp")
-> switches the live chart, order book, trades, and trade ticket together
2. chart_id = data["frontend_context"]["active_market_request"]["market_id"]
3. visual_add_workspace_chart_annotation(
chart_id=chart_id,
type="marker",
config={"time": "2026-05-21T06:00:00Z", "price": 0.035, "shape": "flag", "color": "#22c55e"}
)
Typical flow (default chart):
1. visual_get_frontend_context()
-> {"ok": true, "data": {"frontend_context": {"chart": {"id": "hl-perp-BTC", "market_id": "hl-perp-BTC", "market_type": "hl-perp"}}}}
2. chart_id = data["frontend_context"]["chart"]["id"]
3. visual_add_workspace_chart_annotation(
chart_id=chart_id,
type="horizontal_line",
config={"price": 73500, "color": "#ef4444", "label": "Support"}
)
4. The annotation appears on the user's default chart.
Typical flow (agent-created visual pane):
1. visual_search_chart_series(query="BTC ETH relative performance")
-> copy compatible `source` objects and each result's `shape.default_y`
2. visual_create_chart(
chart_id="btc-eth-relative",
title="BTC vs ETH",
kind="line",
lookback_days=90,
series=[
{"id": "btc", "label": "BTC", "source": <BTC source>, "x": "ts", "y": "price_usd"},
{"id": "eth", "label": "ETH", "source": <ETH source>, "x": "ts", "y": "price_usd"}
],
transforms=[{"type": "rebase", "base": 100}]
)
3. visual_set_active_chart(chart_id="btc-eth-relative")
4. visual_add_workspace_chart_annotation(
chart_id="btc-eth-relative",
type="text_label",
config={"time": "2026-05-21T06:00:00Z", "price": 120, "text": "Relative breakout"}
)
MCP tools
| Tool | Args | Use |
|---|
visual_get_frontend_context | none | Read current default chart context and workspace |
visual_search_chart_series | query, kind?, venue?, market_type?, limit? | Discover supported chart datasets and their column shapes |
visual_set_active_market | query?, market_id?, market_type?, chain_id?, clear_workspace? | Switch the default chart/trading context to one tradable market |
visual_preview_series | series, kind?, transforms?, lookback_days?, limit? | Dry-run resolve series before charting: y-range, unit, counts, sample points |
visual_create_chart | chart_id, title, kind, series, transforms?, overlays?, lookback_days?, limit?, layout?, context_market_id?, indicators? | Validate, create, or replace a visual pane |
visual_set_active_chart | chart_id | Focus an existing workspace chart |
visual_add_workspace_chart_annotation | chart_id, type, config, annotation_id? | Add one TradingView annotation to a default or workspace chart |
visual_add_workspace_chart_overlay | chart_id, overlay | Add a raw overlay, usually bulk event_markers |
visual_add_workspace_chart_series | chart_id, series | Add or replace a data series on an existing workspace chart |
visual_set_chart_indicators | chart_id, indicators | Replace the TradingView studies on a live or workspace chart ([] clears) |
visual_clear_chart_workspace | none | Clear agent-created charts and default-chart annotations |
All gate on is_opencode_instance() and return {"ok": false, "error": {"code": "not_opencode_instance"}} when run outside Shells.
Chart panes
Use visual_set_active_market when the user asks to show, switch to, or open one tradable market such as "show AAVE", "switch to PENGU perp", "open POL spot", or "show this Polymarket market". It is the one-call path that updates the default chart and the rest of the trading context.
Use visual_create_chart when the user asks for a new visual pane, comparison, APY/funding chart, table, or custom derived visualization, not when they only want to switch or annotate the live chart.
If visual_create_chart returns ok: false, do not tell the user the chart is ready. Read the error, pick a compatible source/kind, and retry.
| Chart kind | Use |
|---|
price_candle | Primary market price chart. Use {"type": "market_price", "market_id": "..."} or a Hyperliquid perp dataset_series returned by search. |
line | One or more time series, such as relative performance or APYs over time. |
bar | Ranked or latest categorical values. |
table | Tabular data. |
Supported source types:
market_price: {"type": "market_price", "market_id": "hl-perp-btc"}
dataset_series: returned by visual_search_chart_series; preferred for known backend datasets, including the current Delta Lab registry-backed series
delta_lab_asset: {"type": "delta_lab_asset", "symbol": "USDC", "series": "lending", "venue"?: "...", "basis"?: true}. Legacy fallback only; use a returned dataset_series source when available.
inline: {"type": "inline", "points": [{...}]}
Single-series time-series workspace charts render in TradingView. Multi-series
comparisons currently use the workspace line renderer; keep using line plus
rebase for relative performance charts.
For tradable perp charts, pass context_market_id so the shell switches the
order book, trades, and trade ticket while keeping the workspace chart active:
{
"chart_id": "pengu-price",
"title": "PENGU Perp",
"kind": "price_candle",
"context_market_id": "hl-perp-pengu",
"series": [
{
"id": "pengu-price",
"label": "PENGU",
"source": {"type": "dataset_series", "dataset_id": "hyperliquid.perp.price", "params": {"coin": "PENGU", "interval": "1h"}},
"x": "ts",
"y": "price_usd"
}
]
}
Supported transforms: filter, latest_by, top_n, rebase, pct_change, scale, multiply, ratio, spread, moving_average. Prefer rebase(base=100) for relative performance across different units.
Transforms can live on the chart or on a single series. Copy any default_transforms from a search result into the series transforms, then add metric conversions. Use series-level transforms when only one dataset needs conversion. Chart-level transforms hit every series unless you scope them with "series_ids": ["..."].
Scaling a ratio: put scale on the ratio transform itself — {"type": "ratio", "left": "a", "right": "b", "scale": 1e6, "label_suffix": "(×10⁶)", "unit": "ratio"} — which scales only the derived series. Never use a chart-level scale for this: it multiplies the sources before division, so it corrupts them and mathematically cancels out of the ratio.
Delta Lab APY/rate fields are decimal fractions. 0.12 means 12%, not 0.12%.
For Pendle/lending/Boros/yield APY or APR fields such as implied_apy, underlying_apy, supply_apr, borrow_apr, fixed_rate_mark, and floating_rate_oracle, convert to display percent:
{"type": "scale", "factor": 100, "unit": "%", "label_suffix": "(%)"}
For annualizing hourly funding to display percent:
{
"id": "btc-funding",
"label": "BTC Funding",
"source": {"type": "dataset_series", "dataset_id": "hyperliquid.perp.funding", "params": {"coin": "BTC"}},
"x": "ts",
"y": "funding_rate",
"unit": "%",
"axis": "right",
"transforms": [{"type": "scale", "factor": 876000, "unit": "%", "label_suffix": "(annualized %)"}]
}
Series can set color and axis (left or right). Keep related units on the same axis; use a right axis only when overlaying unrelated units.
Dataset selection
Always search known datasets before inventing or fetching your own data.
- If the user asks for a single tradable token/perp/spot/prediction market, use
visual_set_active_market first. Do not search chart datasets or create a workspace chart for simple market switches.
- Use
visual_search_chart_series with the user intent/assets first. Do not pass kind by default; inspect returned kind, shape.default_y, shape.columns, and shape.supported_chart_kinds to decide whether to use the candidate.
- Prefer a common source family across compared series. For example, use Hyperliquid perp prices for BTC and ETH together; do not mix Hyperliquid BTC with CoinGecko ETH unless there is no common source.
- For asset price/performance requests, prefer Hyperliquid perp price series over spot/fallback price series unless the user explicitly asks for spot. Prefer registry-returned Delta Lab
dataset_series sources for lending/yield/Boros/Pendle/funding research series, CoinGecko only as broad spot price fallback, and DeFiLlama for current ranked yield tables/bars.
- Pass
kind only to narrow a known data family (funding, yield, price) or a large result set. Do not pass chart kinds such as line as the first search because that hides useful candidate metadata.
- Use Polymarket-specific tools/API for prediction markets. Do not route Polymarket discovery through chart-series search.
- Use
inline only when the registry does not expose the needed data. If using inline data, keep it small and describe the columns in the chart label or nearby message.
- Set
lookback_days on visual_create_chart when the user gives a time window. Use 90 for "3 months".
- When a chart represents a tradable Hyperliquid perp, set
context_market_id to hl-perp-<symbol-lowercase> unless the tool result
already provides a more specific market id.
Registry results include:
source: copy this into the chart series spec.
shape.default_y: use this as the series y field unless a different column is intentional.
shape.columns: read this before choosing transforms such as latest_by, top_n, ratio, or spread.
Annotation types
type | config |
|---|
horizontal_line | price, color?, label? |
vertical_line | time (unix sec), color?, label? |
marker | time, price?, shape? (arrow_up / arrow_down / flag / icon / emoji), color? |
range | from_time?, to_time?, from_price, to_price, color? |
text_label | time, price, text, color? |
trend | from: {time, price}, to: {time, price}, color?, label? |
For bulk catalyst markers on the live/default chart, prefer a single event marker
overlay:
{
"type": "event_markers",
"id": "zec-catalysts",
"data": [
{
"time": "2026-05-21T06:00:00Z",
"price": 690,
"label": "ZEC hits $690",
"color": "#ef4444",
"shape": "flag"
}
]
}
Use data as the canonical event array. Legacy markers is accepted, but new
calls should not use it.
TradingView indicators
visual_set_chart_indicators(chart_id, indicators) applies native TradingView
studies to the live/default chart or a workspace chart. Replace semantics:
the list you pass becomes the chart's full indicator set; [] clears. Current
indicators are readable at chart_workspace.defaultIndicators[chart_id].
[
{"name": "ema", "inputs": {"length": 21}},
{"name": "bollinger", "inputs": {"length": 20, "mult": 2}},
{"name": "rsi"}
]
| Alias | TradingView study | Pane | Params (defaults) |
|---|
sma | Moving Average | price overlay | length (9), source ("close") |
ema | Moving Average Exponential | price overlay | length (9), source ("close") |
bollinger | Bollinger Bands | price overlay | length (20), mult (2) |
supertrend | SuperTrend | price overlay | length (10, ATR period), factor (3) |
vwap | VWAP | price overlay | anchor ("Session"/"Week"/"Month"/"Quarter"/"Year"), source ("hlc3") |
rsi | Relative Strength Index | own sub-pane | length (14) |
macd | MACD | own sub-pane | fast (12), slow (26), signal (9) |
atr | Average True Range | own sub-pane | length (14) |
stochastic | Stochastic | own sub-pane | k_length (14), k_smoothing (1), d_smoothing (3) |
volume | Volume | own sub-pane | ma_length (20), show_ma (false) |
Names are case-insensitive; the canonical study names are also accepted. Unknown
names return a 400 listing the supported set. Use the friendly param names from
the table — the backend translates them to the study's actual TradingView input
ids (several built-ins use positional in_0/in_1 ids). Unrecognized input
keys pass through untouched, so exact TV input ids and advanced inputs
(smoothingLine, maType, …) also work. Omit inputs entirely for
TradingView defaults — prefer that unless the user asked for specific
parameters. Optional forceOverlay overrides the pane default; optional id
makes an entry stable across replaces. visual_create_chart accepts the same
list via indicators.
Indicators render only on TradingView-backed charts: the live market chart,
price_candle charts, and single-series time-series line charts. Bar, table,
and multi-series recharts panels ignore them.
Previewing data before charting
visual_preview_series(series, kind?, transforms?, lookback_days?, limit?)
dry-run resolves the exact series/transforms payload you would pass to
visual_create_chart — nothing is saved. The compact response carries, per
series: points, first_x/last_x, unit, y_first/y_last/y_min/y_max,
and sample_head/sample_tail point arrays. Use it to catch unit and scaling
mistakes (decimal APYs, unreadably tiny ratios) before the chart exists. The
same automatic percent-scaling of known rate fields applied by
visual_create_chart is applied here, so the preview matches the render.
Gotchas
marker does not accept label. Use text_label for annotated points.
- Use ISO-8601 strings for
time values when possible. The renderer also accepts unix seconds, but do not hand-compute timestamps.
- For default chart annotations, use the exact
frontend_context.chart.id.
- For workspace charts, use the
chart_id passed to visual_create_chart.
- Default chart annotations are stored in
chart_workspace.defaultAnnotations; workspace chart annotations are stored in the chart's overlays.
- After writing default chart overlays, verify
chart_workspace.defaultAnnotations[chart_id] contains the expected overlay and, for event markers, that its data array has the expected count.
- Chart workspace state is scoped to the current Shells instance, not the user vault.