Add futures_quote tool: CBOT price + change since open + on day
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>
This commit is contained in:
@@ -70,6 +70,10 @@ def best(commodity: str) -> dict:
|
||||
return _get("/api/data/best", commodity=commodity)
|
||||
|
||||
|
||||
def futures(commodity: str, delivery: str | None = None) -> dict:
|
||||
return _get("/api/data/futures", commodity=commodity, delivery=delivery)
|
||||
|
||||
|
||||
def inputs(product: str | None = None) -> dict:
|
||||
return _get("/api/data/inputs", product=product)
|
||||
|
||||
|
||||
@@ -90,6 +90,41 @@ def fmt_best(commodity: str, payload: dict) -> str:
|
||||
)
|
||||
|
||||
|
||||
# ---------- futures quote + change ----------
|
||||
|
||||
|
||||
def fmt_futures(payload: dict) -> str:
|
||||
commodity = payload.get("commodity")
|
||||
delivery = payload.get("delivery")
|
||||
contract = payload.get("contract")
|
||||
q = payload.get("quote")
|
||||
scope = f"{commodity} {contract}" + (f" ({delivery})" if delivery else " (continuous nearby)")
|
||||
if not q:
|
||||
return f"### CBOT futures — {scope}\n\nNo futures quote on file yet for this contract.\n"
|
||||
|
||||
last = q.get("last_cents")
|
||||
open_c = q.get("open_cents")
|
||||
prev = q.get("prev_close_cents")
|
||||
d_open = q.get("change_since_open_cents")
|
||||
d_day = q.get("change_on_day_cents")
|
||||
|
||||
lines = [f"### CBOT futures — {scope}", ""]
|
||||
lines.append(f"- **Last**: {_bu(last)} _(settle/last for {q.get('settle_date') or '?'})_")
|
||||
if open_c is not None:
|
||||
lines.append(f"- **Open**: {_bu(open_c)}")
|
||||
if prev is not None:
|
||||
lines.append(f"- **Prev close**: {_bu(prev)}")
|
||||
if d_open is not None:
|
||||
lines.append(f"- **Change since open**: {_delta_arrow(d_open)} {_basis(d_open)}")
|
||||
else:
|
||||
lines.append("- **Change since open**: — (no open captured yet)")
|
||||
if d_day is not None:
|
||||
lines.append(f"- **Change on day**: {_delta_arrow(d_day)} {_basis(d_day)}")
|
||||
else:
|
||||
lines.append("- **Change on day**: — (no prior settle yet)")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# ---------- inputs / fertilizer ----------
|
||||
|
||||
|
||||
|
||||
@@ -64,6 +64,31 @@ def best_local_bid(
|
||||
return fmt.fmt_best(commodity, payload)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def futures_quote(
|
||||
commodity: Annotated[
|
||||
str, Field(description="Grain: 'corn', 'soy' (soybeans), or 'wheat'.")
|
||||
],
|
||||
delivery: Annotated[
|
||||
str | None,
|
||||
Field(description="Delivery month (e.g. 'Jul 2026') to resolve the listed "
|
||||
"contract. Omit for the continuous nearby."),
|
||||
] = None,
|
||||
) -> str:
|
||||
"""CBOT futures price and change for a commodity (optionally one delivery month).
|
||||
|
||||
Reports the latest price, today's session open, the prior day's close, and
|
||||
both moves: change since the open and change on the day (vs the previous
|
||||
settle). With `delivery` it resolves the listed contract that month prices
|
||||
against; without it, the continuous nearby."""
|
||||
cm = commodity.strip().lower()
|
||||
with track("futures_quote", commodity=cm, delivery=delivery):
|
||||
if cm not in VALID_GRAIN:
|
||||
return f"`commodity` must be one of: {sorted(VALID_GRAIN)}"
|
||||
payload = client.futures(commodity=cm, delivery=delivery)
|
||||
return fmt.fmt_futures(payload)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def current_lime_price() -> str:
|
||||
"""Latest lime prices on file across all sources. Lime is rarely posted on
|
||||
|
||||
Reference in New Issue
Block a user