0c9bc3b328
New futures_quote(commodity, delivery?) tool wraps the new /api/data/futures endpoint: reports latest price, today's session open, prior settle, and both moves (since open and on the day). With a delivery month it resolves the listed contract; without it, the continuous nearby. Adds client.futures(), fmt_futures(), tests, and a CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
107 lines
5.3 KiB
Markdown
107 lines
5.3 KiB
Markdown
# Changelog
|
||
|
||
Notes for clients/agents that consume the ag-bids MCP tools and the underlying
|
||
`ag-monitor` `/api/data/*` HTTP API. Newest first.
|
||
|
||
## 2026-05-29 — Futures price + change tool
|
||
|
||
### New MCP tool
|
||
|
||
- **`futures_quote(commodity, delivery?)`** — CBOT futures price and change for a
|
||
grain. Reports the latest price, today's **session open**, the prior day's
|
||
close, and **both** moves: **change since open** and **change on the day**
|
||
(vs the previous settle). With `delivery` (e.g. `"Jul 2026"`) it resolves the
|
||
listed contract that month prices against (e.g. `ZCN26`); without it, the
|
||
continuous nearby.
|
||
|
||
### API changes (`ag-monitor`)
|
||
|
||
- **`GET /api/data/futures?commodity=<grain>&delivery=<label?>`** — new endpoint.
|
||
Returns `{ commodity, delivery, contract, symbol, quote }` where `quote` is
|
||
`{ settle_date, open_cents, last_cents, prev_close_cents,
|
||
change_since_open_cents, change_on_day_cents, fetched_at }` (or `null` if no
|
||
data yet for that contract). `commodity` must be `corn`/`soy`/`wheat`.
|
||
- **`futures_quotes` table** gained an `open_cents` column; the futures scraper
|
||
now stores the session **Open** alongside the close. Rows captured before this
|
||
change have `open_cents = NULL`, so `change_since_open_cents` is `null` for
|
||
those until the next session is scraped.
|
||
- Futures now also pull at **:30 during the CBOT day session** (on top of the
|
||
hourly :00), so `last` and the changes track the session every ~30 min.
|
||
|
||
### Example questions → tool calls
|
||
|
||
| Ask | Call |
|
||
|---|---|
|
||
| Corn Jul futures and how far it's moved today | `futures_quote(commodity="corn", delivery="Jul 2026")` |
|
||
| Soy nearby futures, change on the day | `futures_quote(commodity="soy")` |
|
||
|
||
## 2026-05-29 — Flexible history + basis movement
|
||
|
||
### New MCP tools
|
||
|
||
- **`basis_movement(commodity?, source?, delivery?, days=30)`** — aggregated
|
||
basis trend, **one headline line per crop**. Averages every matching
|
||
(elevator × delivery) series to `avg basis first → last` over the window and
|
||
reports how far it moved. This is the cheap "how is basis moving overall"
|
||
view. All filters optional; `commodity` (if given) must be `corn`/`soy`/`wheat`.
|
||
- Positive move = **cash strengthened** vs futures (basis up); negative = weaker.
|
||
- Aggregation is intentional: divergent elevators can net to "flat". When the
|
||
aggregate looks flat or surprising, call `basis_detail` to see per-elevator
|
||
splits.
|
||
|
||
- **`basis_detail(commodity?, source?, delivery?, days=30)`** — the drill-down
|
||
for `basis_movement`. **One row per (elevator, crop, delivery)** series with
|
||
basis `first → last` and the move. Same optional filters.
|
||
|
||
Both tools do the aggregation **server-side (in the MCP)** and return compact
|
||
markdown — they do not dump every raw sample — to keep token usage low. Pattern:
|
||
call `basis_movement` first, then `basis_detail` (optionally filtered) only when
|
||
you need the breakdown.
|
||
|
||
### Changed MCP tools
|
||
|
||
- **`price_history`** — `commodity` is now **optional** (was required). Omit it
|
||
to span every crop. Output now groups by **(elevator, crop, delivery)**,
|
||
surfaces **basis first → last** in each series summary, and adds a **Futures**
|
||
column to the raw points table. Every filter (`commodity`, `source`,
|
||
`delivery`, `days`) is optional and AND'd — pivot per elevator, per crop, per
|
||
delivery, or any combination.
|
||
- Raw per-row points are still only included when the window has ≤ ~60 samples;
|
||
wider queries return the per-series summaries only.
|
||
|
||
- **`latest_prices`** — added the **`kind`** filter (`grain` | `fertilizer`) for
|
||
parity with the API. `commodity`, `source`, `delivery`, `kind` all optional.
|
||
|
||
### API changes (`ag-monitor`)
|
||
|
||
- **`GET /api/data/history`** — `commodity` query param is now **optional**.
|
||
When omitted, returns history across all crops. `source_id`, `delivery`, and
|
||
`days` (1–730, default 30) remain optional. Response shape is unchanged:
|
||
`{ "commodity": <str|null>, "days": <int>, "rows": [...] }`. Each row carries
|
||
`fetched_at, source_id, source_name, commodity, delivery, bid_cents,
|
||
basis_cents, futures_cents` so callers can pivot client-side.
|
||
- `GET /api/data/latest` already supported optional `commodity`, `source`,
|
||
`delivery`, `kind` — unchanged.
|
||
|
||
### Conventions / gotchas
|
||
|
||
- All money fields are **integer cents**. `bid_cents`/`futures_cents` render as
|
||
`$/bu` (4 dp) for grain and `$/ton` (2 dp) for fertilizer; `basis_cents`
|
||
renders as a signed dollar value (2 dp), e.g. `-0.14`.
|
||
- Basis is only meaningful for grain — the basis tools skip non-grain rows and
|
||
any series with no basis on file.
|
||
- `source` filtering is by **exact** elevator display name (e.g.
|
||
`"Mercer Landmark — Minster"`). Use `list_sources` to get exact names.
|
||
|
||
### Example questions → tool calls
|
||
|
||
| Ask | Call |
|
||
|---|---|
|
||
| Basis movement overall, last 7 days | `basis_movement(days=7)` |
|
||
| Corn basis movement across all elevators | `basis_movement(commodity="corn")` |
|
||
| Basis movement at one elevator | `basis_movement(source="Mercer Landmark — Minster")` |
|
||
| Per-elevator basis breakdown for corn | `basis_detail(commodity="corn")` |
|
||
| Last 7 days of history from elevator X for corn | `price_history(commodity="corn", source="…", days=7)` |
|
||
| All-crop price history at one elevator | `price_history(source="…")` |
|
||
| Latest grain bids only | `latest_prices(kind="grain")` |
|