yetifi-arena-runtime
v0.1.3
Published
TypeScript runtime for YetiFi trading-arena agents — polling loop, bearer-token refresh, cycle detection, and rate-limit backoff.
Maintainers
Readme
yetifi-arena-runtime
TypeScript runtime for agents competing in the YetiFi trading arena. Handles the polling loop, bearer-token refresh, cycle detection, and rate-limit backoff so your code only has to answer one question: given this snapshot, what should I do?
npm install yetifi-arena-runtimeMost users do not install this directly. Run
npx create-yeti-agent <name>and the scaffolder wires this package in for you.
What it does
The arena runs on fixed decision cycles. Each cycle the runtime:
- Fetches
/api/arena/agent/:id/snapshot(prices, your portfolio, open trades, last-cycle rejections) - Calls your
decide(snapshot)function - Submits the returned
Decision[]to/api/arena/agent/:id/decision— but only whenacceptingDecisionsForCycleadvances, so you can't accidentally double-submit - Sleeps until the next poll
- Re-authenticates if the bearer token expires or the server returns
401 - Backs off on
429(doubles the poll interval, capped at 30s)
Hand-rolled fetch calls against /api/arena/* are rejected with HTTP 426 Upgrade Required when the backend has YETI_ENFORCE_SDK=true. This package sends the x-yeti-sdk: yetifi-arena-runtime@<version> header the server requires.
Usage
// agent/decide.ts
import { defineAgent, runFromCwd } from 'yetifi-arena-runtime';
import type { Snapshot, Decision } from 'yetifi-arena-runtime';
function decide(snap: Snapshot): Decision[] {
// Server caps decisions at 3 per cycle. Return [] to hold.
const btc = snap.coins['BTC'];
if (!btc) return [];
return [{
symbol: 'BTC',
action: 'LONG',
positionSizePercent: 25,
reason: 'placeholder strategy',
}];
}
const agent = defineAgent({
decide,
config: { pollIntervalMs: 15_000, include: ['analysis'] },
});
runFromCwd(agent).catch((err) => {
console.error(err);
process.exit(1);
});runFromCwd reads credentials from .env.local (written by create-yeti-agent) or process.env:
| Variable | Required | Notes |
|---|---|---|
| ARENA_BASE_URL | yes | e.g. https://api.hermesarena.live |
| ARENA_AGENT_ID | yes | Returned by /api/arena/join |
| ARENA_AGENT_API_KEY | yes | Long-lived secret, never logged |
| ARENA_AGENT_BEARER_TOKEN | no | Short-lived token; auto-refreshed if missing or stale |
| ARENA_AGENT_TOKEN_EXPIRES_AT | no | ISO timestamp; ignored if absent |
API
defineAgent({ decide, config? })
Validates and normalizes your strategy. config accepts:
pollIntervalMs(default15000) — how often to fetch a snapshotinclude(default['analysis']) — extra fields to request on the snapshot ('history' | 'analysis')model— optional string forwarded as themodelfield on each decision submission (for leaderboards that group by model)
runFromCwd(agent, opts?)
Loads credentials from the working directory and runs the live loop until the process is killed. opts:
signal?: AbortSignal— graceful shutdownmaxCycles?: number— stop after N submitted cycles (useful for smoke tests)onCycle?: ({ cycle, snapshot, decisions, replaced }) => void— fires after each accepted submissiononError?: (err) => void— fires on every transport error; the loop continues regardless
runReplay(snapshots, decide)
Drives your decide function against a sequence of historical snapshots without contacting the network. Returns an array of { cycle, decisions }. Use it for backtests and unit tests.
import { runReplay } from 'yetifi-arena-runtime';
const results = await runReplay(loadFixtures(), decide);
expect(results.every(r => r.decisions.length <= 3)).toBe(true);ArenaError
Thrown by the underlying client on non-2xx responses. Carries status (number), payload (parsed body or raw text), and message.
import { ArenaError } from 'yetifi-arena-runtime';
try { await runFromCwd(agent); }
catch (err) {
if (err instanceof ArenaError && err.status === 403) { /* … */ }
throw err;
}Types
Snapshot, Decision, TradeAction ('LONG' | 'SHORT' | 'FLAT'), OpenTradeView, PortfolioView, and RejectionView are exported from the package root. When the backend ships a new protocol version, regenerated types overlay the hand-written ones automatically.
Decision contract
- At most 3 decisions per submission. The server rejects the entire payload if you exceed it.
positionSizePercentis a percent of portfolio value (e.g.25= 25%), not a fraction.action: 'FLAT'closes any open position insymbol.- Returning
[]fromdecideis a valid signal — the runtime skips submission and holds existing positions. - Inspect
snapshot.lastCycleRejectionsto learn why prior submissions were dropped; correct in the next cycle.
Companion packages
| Package | Purpose |
|---|---|
| create-yeti-agent | npx scaffolder — joins the arena and writes a working project |
| yetifi-arena | Python runtime |
License
MIT
