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.
52 lines (51 loc) • 3.1 kB
JavaScript
/**
* # series/types — the value model of the series layer (RUNTIME §2–§3)
*
* Two small, load-bearing types and the units bridge shared by every file in this module.
*
* - {@link UNKNOWN} — the single sentinel for "insufficient data / unresolvable". It is a
* value, distinct from `false`: a series with too little history reads UNKNOWN, never a
* guess (RUNTIME §2), and a trigger over an UNKNOWN operand evaluates UNKNOWN, never
* silently false (RUNTIME §3). Only a definite `true` fires.
* - {@link Resolved} — what a series resolves to: a `number` (a market/org scalar), a
* `string` (a symbolic state value like a plan-state or regime tag), or {@link UNKNOWN}.
* - {@link TriState} — the result of evaluating a trigger: `true | false | UNKNOWN`.
*
* All time is injected (RUNTIME §0): {@link durationMs} is the one place a `Window` or
* `Duration` value+unit is turned into milliseconds, so every window/held/within/esc span
* measures identically.
*/
// ─────────────────────────────────────────────────────────────────────────────
// UNKNOWN — the third truth value / the missing scalar
// ─────────────────────────────────────────────────────────────────────────────
/** The sole "insufficient data / unresolvable" sentinel. A unique symbol so it can never
* be confused with a real string value, a `0`, or a `false`. */
export const UNKNOWN = Symbol("kestrel.UNKNOWN");
/** Is `x` the UNKNOWN sentinel? */
export function isUnknown(x) {
return x === UNKNOWN;
}
/** Is `x` a resolved *number* (not a string, not UNKNOWN)? */
export function isNumber(x) {
return typeof x === "number";
}
// ─────────────────────────────────────────────────────────────────────────────
// Units bridge — the one time→ms conversion (RUNTIME §0: all time injected)
// ─────────────────────────────────────────────────────────────────────────────
/** Milliseconds per {@link TimeUnit}. Calendar units use fixed nominal spans (a month is
* 30d, a quarter 90d, a year 365d) — windows are trailing measurement horizons, not
* calendar arithmetic, so a fixed nominal span is the honest, deterministic choice. */
const MS = {
s: 1_000,
m: 60_000,
h: 3_600_000,
d: 86_400_000,
w: 604_800_000,
mo: 2_592_000_000, // 30d
q: 7_776_000_000, // 90d
y: 31_536_000_000, // 365d
};
/** Convert a `{ value, unit }` window/duration to milliseconds. */
export function durationMs(value, unit) {
return value * MS[unit];
}