A zero-backend market instrument. Maintains a 240-sample window, reads public candles, computes momentum, applies a discrete Lotka-Volterra interaction, and emits predation pressure. No private database, no hidden server state, no parameter that cannot be inspected.
app/lotka/kernel.ts
verbatim from the public repository
// app/lotka/kernel.ts
// lotka — zero-backend predator-prey market tracker
// runs in the browser after public candles are fetched by a tiny API route.
// no database. no private state. every number below is visible.
export const WINDOW = 240;
type Candle = {
time: number;
close: number;
volume: number;
};
type LotkaPoint = {
minute: string;
crypto: number;
equity: number;
pressure: number;
};
const ALPHA = 0.045; // prey growth term
const BETA = 0.038; // predation drag
const DELTA = 0.041; // predator gain from interaction
const GAMMA = 0.030; // predator decay term
const INTERACTION_SCALE = 5200;
function clamp(value: number, min: number, max: number) {
return Math.min(max, Math.max(min, value));
}
function sigmoid(value: number) {
return 1 / (1 + Math.exp(-value));
}
export function momentum(candles: Candle[], base: number) {
const logs = candles.map((candle) => Math.log1p(candle.volume));
const averageVolume = logs.reduce((sum, value) => sum + value, 0) / logs.length;
let state = base;
return candles.map((candle, index) => {
if (index === 0) return state;
const previous = candles[index - 1];
const returnPct = ((candle.close - previous.close) / previous.close) * 100;
const volumeImpulse = logs[index] / Math.max(0.0001, averageVolume) - 1;
state = clamp(
state * 0.92 + base * 0.08 + returnPct * 9.5 + volumeImpulse * 5.5,
8,
112,
);
return state;
});
}
export function lotkaStep(crypto: number, equity: number) {
const interaction = (crypto * equity) / INTERACTION_SCALE;
const nextEquity = clamp(
equity + ALPHA * equity - BETA * interaction,
8,
112,
);
const nextCrypto = clamp(
crypto + DELTA * interaction - GAMMA * crypto,
8,
112,
);
const pressure = clamp(
sigmoid((nextCrypto - nextEquity) / 12 + interaction / 46) * 100,
4,
98,
);
return { nextCrypto, nextEquity, pressure };
}
export function buildSeries(cryptoCandles: Candle[], equityCandles: Candle[]): LotkaPoint[] {
const cryptoMomentum = momentum(cryptoCandles.slice(-WINDOW), 52);
const equityMomentum = momentum(equityCandles.slice(-WINDOW), 48);
return cryptoMomentum.map((crypto, index) => {
const equity = equityMomentum[index];
const interaction = (crypto * equity) / INTERACTION_SCALE;
const pressure = clamp(
sigmoid((crypto - equity) / 12 + interaction / 46) * 100,
4,
98,
);
return {
minute: new Date(cryptoCandles[index].time).toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
}),
crypto: Number(crypto.toFixed(3)),
equity: Number(equity.toFixed(3)),
pressure: Number(pressure.toFixed(3)),
};
});
}
// thesis:
// the token is not the source of the measurement.
// the token is the cultural handle attached to a public measurement.
// fork the code, replace the candles, rerun the same kernel.