TypeScript core. LLM tilt. Live eToro account. If the model owns the book, a reject or a sparse overlay is cash. The person watching the account sees a holiday, not a tilt.
Zero names. Zero invested. A critic that "said no" and parked the portfolio. Golden tests still green. The eToro book is the only review that mattered.
This TypeScript allocator feeds a live eToro book. I let an LLM write that book. Not a comment on it. The book. Regime labels, ticker picks, weights. When the model was fluent, the allocation looked sharp. When it was sparse, or when it rejected the prompt, the book went empty.
Empty is not a tilt. Empty is cash. A reject output froze the account at zero invested. A half-finished overlay did the same: missing names defaulted to nothing, and nothing plus nothing is a holiday, not a strategy.
Golden tests never caught it. They checked that a happy-path overlay blended. They never asserted a fully invested core. Zero names passed. The suite was green while the portfolio was parked.
The deterministic layer now owns the eToro book. Always. A regime plus momentum core prints a 100% invested allocation before the model speaks.
Crisis: invested safe-haven. Not cash. The rule still puts you in the market. Normal: top momentum names plus a small crypto sleeve.
The LLM may only tilt each name ±10pp. Names it invents enter at most 10pp. Side and leverage still come from the model when it named the ticker. Weight does not.
Leftover after the clamp is not cash. I pour it back into the book via fillIndexLeftover. Fallback is the core. The overlay cannot delete the portfolio.
applyLlmOverlay is TypeScript, not a prompt. It walks the union of core and overlay symbols. Each weight is clamped into [core − 10pp, core + 10pp], floored at zero. Dust gets pruned. Whatever is missing from 100% goes back into the book. That blended book is what I send to eToro.
Solid bar is the blended book. Filled dot is core. Hollow dot is the raw overlay. Shaded band is the ±10pp leash. Leftover after clamp returns to the book, not cash.
Core is top momentum plus a 10% crypto sleeve and always sums to 100%.
export const LLM_OVERLAY_MAX_DELTA = 0.1; // ±10pp
export function applyLlmOverlay(core: Allocation, overlay: Allocation): Allocation {
const blended: Allocation = {};
for (const symbol of new Set([...Object.keys(core), ...Object.keys(overlay)])) {
const coreWeight = core[symbol] ?? 0;
const lo = Math.max(0, coreWeight - LLM_OVERLAY_MAX_DELTA);
const hi = coreWeight + LLM_OVERLAY_MAX_DELTA;
const clamped = Math.min(hi, Math.max(lo, overlay[symbol] ?? 0));
if (clamped > 0.0001) blended[symbol] = clamped;
}
return fillIndexLeftover(pruneDustWeights(blended), 1);
}Drag the sliders. Zero the overlay. Invent a ticker. The sum stays one. Deterministic code prints the book. The model gets a leash.
I also wired a risk-manager critic into the same eToro allocator. One job: look at a proposal and say accept, soften, or reject. On a book that is already invested, reject is the right call. Hold current. Do not churn for a model that got nervous.
On an empty eToro book, or a book sitting in cash, reject is a trap. The critic says no. The system keeps cash. The next run starts from cash. The critic says no again. Deadlock.
I tried to fix it in the prompt. "You cannot veto a cash deploy." The model ignored it on the next run, and the run after that. Prompt text is advice. The code path is the contract.
If shouldForceCashDeploy(current) is true and the critic says reject, ignore the overlay. Emit the deterministic core. Force shouldRebalance true. Cap conviction so the deploy is modest, not a swing.
if (critic.action === 'reject') {
if (shouldForceCashDeploy(currentAllocation)) {
return { allocation: coreAllocation, shouldRebalance: true };
}
return { allocation: currentAllocation, shouldRebalance: false };
}Reject may hold current only when the book is already invested. Soften blends 60% overlay with 40% core, then re-clamps every sleeve to plus or minus ten percentage points. The critic can pull you. It cannot yank you off the rails.
Critic overlay vs the code path
Current
Deterministic core
Critic overlay
Emitted allocation
Force cash deploy. Ignore the overlay. Emit the deterministic core.
Toggle an empty book versus an invested one. Hit reject. Cash-heavy: core ships, rebalance is forced. Invested: current holds.
Models do not keep promises they cannot see in the control flow. A sentence in a system prompt is a suggestion with a temperature. A branch in TypeScript is a gate. The overlay tilts. The critic comments. Neither one gets to park an eToro book in cash.
Happy coding!
Sander