kestrel.markets
Version:
A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.
200 lines (198 loc) • 8.87 kB
JavaScript
// src/cli/render/sim.ts
function money(n) {
const r = Math.round(n * 1e6) / 1e6;
return `$${r}`;
}
function simStoryLines(story) {
const lines = [];
lines.push(`kestrel sim · ${story.canonicalSlug}`);
if (story.viaAlias)
lines.push(`alias ${story.requestedSlug} → ${story.canonicalSlug}`);
lines.push(`scenario ${story.title} — ${story.instrument} · ${story.period.start}..${story.period.end} · ${story.frameCount} frames`);
lines.push(`briefing ${story.instrument} percept context available (${story.frameCount} frames)`);
lines.push(`strategy ${story.strategyLabel}`);
lines.push(`operation ${story.operation.id} · ${story.operation.status}`);
const budget = story.receipts.budgetRemaining === null ? "—" : money(story.receipts.budgetRemaining);
lines.push(`receipts ${story.receipts.meteredCount} metered · budget remaining ${budget}`);
lines.push(`evidence manifest=${story.evidence.manifestRef.artifact_id} bus=${story.evidence.busRef.artifact_id} grade=${story.evidence.gradeRef.artifact_id} calls=${story.evidence.callRefs.length}`);
if (story.metrics === null) {
lines.push(`grade settling — retry shortly`);
lines.push(`proof ${story.proofUrl} — settling — retry shortly`);
} else {
lines.push(`grade order_count=${story.metrics.orderCount} fill_count=${story.metrics.fillCount} realized_pnl=${story.metrics.realizedPnl}`);
lines.push(`proof ${story.proofUrl}`);
}
return lines;
}
function renderSimStory(story, _ctx) {
process.stdout.write(simStoryLines(story).join(`
`) + `
`);
}
function neverFiredLine(reason) {
return `why no orders fired — ${reason}`;
}
function planNeverFiredReason(plan, orders) {
const fired = plan.lifecycle.some((s) => s.state === "fired" || s.state === "managing");
if (fired)
return;
const placed = orders.some((o) => o.plan_instance === plan.plan_instance);
if (placed)
return;
for (let i = plan.lifecycle.length - 1;i >= 0; i--) {
const reason = plan.lifecycle[i].reason;
if (reason !== undefined)
return reason.startsWith("ttl — ") ? reason.slice("ttl — ".length) : reason;
}
return;
}
function neverFiredDigestLines(report) {
const lines = [];
for (const p of report.plans) {
const why = planNeverFiredReason(p, report.orders);
if (why !== undefined)
lines.push(`why ${p.plan} placed no orders — ${why}`);
}
return lines;
}
function demoLessonObject(lesson) {
return {
bundled_example: true,
plan_label: lesson.planLabel,
plan_source: lesson.planSource,
swap_in: lesson.swapInCli
};
}
function demoLessonLines(lesson) {
const lines = [];
lines.push("");
lines.push(`demo plan ${lesson.planLabel}`);
lines.push(" this run used a BUNDLED EXAMPLE plan, not your strategy — it is what makes the");
lines.push(" grade above a real, filled trade instead of an empty ($0, no-orders) session:");
for (const l of lesson.planSource.split(`
`))
lines.push(` ${l}`);
lines.push(" the `grade` line above is exactly what this example did (a naive one-share");
lines.push(" buy-and-hold — an honest result, never dressed up as alpha). author your own:");
lines.push(` ${lesson.swapInCli}`);
return lines;
}
function renderDemoLesson(lesson) {
return demoLessonLines(lesson).join(`
`);
}
function standDownLessonObject(lesson) {
return {
stand_down_demo: true,
reason: "no --plans supplied — this run used the labeled stand-down baseline, which arms no orders",
example_plan_source: lesson.examplePlanSource,
swap_in: lesson.swapInCli,
prove: lesson.proveCli
};
}
function standDownLessonLines(lesson) {
const lines = [];
lines.push("");
lines.push("stand-down no --plans supplied — this run used the labeled stand-down baseline BY DESIGN");
lines.push(" nothing is broken: with no strategy authored there is nothing to arm, so the grade");
lines.push(" above is an honest $0 / no-orders result — a plan-less stand-down, not a failure.");
lines.push(" author a strategy and pass it with --plans to place real orders. a minimal example:");
for (const l of lesson.examplePlanSource.split(`
`))
lines.push(` ${l}`);
lines.push(" save that to a file, then run it on this scenario:");
lines.push(` ${lesson.swapInCli}`);
lines.push(" or run a bundled example that trades out of the box, no plan to write:");
lines.push(` ${lesson.proveCli}`);
return lines;
}
function renderStandDownLesson(lesson) {
return standDownLessonLines(lesson).join(`
`);
}
function paidBoundaryNudgeObject(nudge) {
return {
free_run: true,
reason: "this run was free — nothing was owed; the paid boundary (a signed, settleable Offer) is opt-in via --budget",
reach_paid_boundary: `npx kestrel.markets sim ${nudge.scenarioSlug} --budget 0`
};
}
function paidBoundaryNudgeLines(nudge) {
const lines = [];
lines.push("");
lines.push("paid path this run was FREE — metered against the anonymous trial, nothing owed, no card");
lines.push(" the paid boundary certifies a run against real spend: pass --budget <amount> and a metered");
lines.push(" run pauses at the Spend boundary and returns the price as a signed, settleable Offer (data,");
lines.push(" never auto-charged) — the same 402 an agent reaches. see it on this scenario:");
lines.push(` npx kestrel.markets sim ${nudge.scenarioSlug} --budget 0`);
return lines;
}
function renderPaidBoundaryNudge(nudge) {
return paidBoundaryNudgeLines(nudge).join(`
`);
}
function renderSimMenu(menu, _ctx) {
const lines = [];
const allFree = menu.scenarios.length > 0 && menu.scenarios.every((s) => s.free);
lines.push(`kestrel sim — run a curated market scenario${allFree ? ", free (anonymous trial)" : ""}`);
lines.push("usage: kestrel sim <scenario-slug> [--plans <file>]");
lines.push("");
lines.push("scenarios:");
const wSlug = Math.max(8, ...menu.scenarios.map((s) => s.slug.length));
const wTitle = Math.max(5, ...menu.scenarios.map((s) => s.title.length));
for (const s of menu.scenarios) {
const price = s.free ? "free" : "paid";
lines.push(` ${s.slug.padEnd(wSlug)} ${s.title.padEnd(wTitle)} ${s.instrument.padEnd(5)} ${price} ${s.artifactId}`);
}
const aliasKeys = Object.keys(menu.aliases);
if (aliasKeys.length > 0) {
lines.push("");
lines.push("aliases:");
for (const k of aliasKeys)
lines.push(` ${k} → ${menu.aliases[k]}`);
}
lines.push("");
const first = menu.scenarios[0]?.slug ?? "meme-stock-short-squeeze";
lines.push(`run one: kestrel sim ${first}`);
process.stdout.write(lines.join(`
`) + `
`);
}
function sessionReportLines(report) {
const s = report.session;
const t = report.totals;
const lines = [];
lines.push(`kestrel session · ${s.date} · ${s.instruments.join(",")} · ${s.mode} · ${s.fill_model}`);
const h = t.headline;
const chans = [`floor=${money(t.realized_floor_usd)}`, `expected=${money(t.expected_usd)}`];
if (t.sampled_usd !== undefined)
chans.push(`sampled=${money(t.sampled_usd)}`);
lines.push(`grade headline=${h.channel} ${money(h.usd)} · ${chans.join(" ")}`);
if (h.tainted)
lines.push(` tainted ${h.reasons.join("; ")}`);
const m = t.settle_mark;
const staleTag = m.stale ? " STALE" : "";
const uncertainTag = m.mark_uncertain ? " mark_uncertain" : "";
lines.push(`settle mark=${money(m.px)} source=${m.source}${staleTag}${uncertainTag} · premium_spent=${money(t.premium_spent)}`);
lines.push(`plans ${report.plans.length}`);
for (const p of report.plans) {
const arc = p.lifecycle.map((step) => step.outcome !== undefined ? `${step.state}(${step.outcome}${step.reason !== undefined ? `:${step.reason}` : ""})` : step.state).join(" → ");
lines.push(` ${p.plan} ${arc === "" ? "—" : arc} ⇒ ${p.final_state}`);
const why = planNeverFiredReason(p, report.orders);
if (why !== undefined)
lines.push(` why ${why}`);
}
lines.push(`orders ${report.orders.length}`);
for (const o of report.orders) {
const leg = o.strike !== undefined && o.right !== undefined ? `${o.strike}${o.right}` : "SPOT";
const outcome = o.filled ? `filled@${o.fillPx} floorPnl=${money(o.floorPnl)}` : o.cancelled === true ? "cancelled" : `resting pFill=${o.pFill}`;
lines.push(` ${o.side} ${o.qty} ${o.instrument} ${leg} @ ${o.px} ${o.role} ${outcome} (${o.annotation})`);
}
return lines;
}
function renderEpisodeReport(report, _ctx) {
process.stdout.write(sessionReportLines(report).join(`
`) + `
`);
}
export { renderSimStory, neverFiredLine, neverFiredDigestLines, demoLessonObject, renderDemoLesson, standDownLessonObject, renderStandDownLesson, paidBoundaryNudgeObject, renderPaidBoundaryNudge, renderSimMenu, renderEpisodeReport };