Skip to main content

August 12, 2026

eToro LLM overlay ±10pp: critic cannot veto

A live eToro book went to cash when the model rejected the prompt. I keep a fully invested core, cap tilts at ten points, and ignore a critic on idle cash.

Sander Korf4 min read
etorollmtypescript

The live eToro allocator feeds a real book: regime core, name weights, and an LLM overlay that was supposed to nudge sleeves, not own the portfolio. The operator watching the account saw a holiday: zero names, zero invested, while golden tests stayed green. A reject or a sparse overlay had parked the book in cash. The eToro book was the only review that mattered.

I let an LLM write the book itself: regime labels, ticker picks, weights. When the model was fluent, the allocation looked sharp. When it was sparse, or when it rejected the prompt, missing names defaulted to nothing. Nothing plus nothing looked like a deliberate cash position.

Golden tests 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. That is how I learned the model could veto the book without anyone noticing in CI.

The deterministic core owns the book

The deterministic layer now owns the eToro book. A regime plus momentum core prints a 100% invested allocation before the model speaks.

In crisis the core still prints an invested safe-haven book. I am not parking in cash and calling it defensive. In normal regimes it is top momentum names plus a small crypto sleeve. Cash is not a regime output. It is what shows up when the overlay deletes names.

The LLM may only tilt each name ±10pp. Names it invents enter at most 10pp. When the model named the ticker, I still take side and leverage from it. The weight still comes from the clamp against the core.

Leftover after the clamp is poured back into the book via fillIndexLeftover. Fallback is the core. The overlay cannot delete the portfolio.

Clamp, then fill

applyLlmOverlay 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.

In the demo below, drag the overlay sliders toward zero or invent a ticker. Watch the total weight: it stays at 100%. The deterministic layer prints the book; the model only gets a leash.

Core vs overlay clamp

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%.

  1. overlay 40% → book 40%
  2. overlay 30% → book 30%
  3. overlay 20% → book 20%
  4. overlay 10% → book 10%
  5. overlay 0% → book 0%
  6. overlay 0% → book 0%
  7. overlay 0% → book 0%
Raw overlay sum
100%
Blended book
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);
}

Reject cannot park idle eToro cash

I also wired a risk-manager critic into the same allocator. On an already invested book, reject means hold current instead of churning for a nervous model. That part made sense to me.

On an empty book, or one 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.

Prompt lines like "you cannot veto a cash deploy" did not stick run to run. The TypeScript branch is what stopped the deadlock.

If shouldForceCashDeploy(current) is true and the critic says reject, I ignore the overlay, emit the deterministic core, force shouldRebalance true, and cap conviction so the deploy stays modest.

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.

Toggle empty book versus invested in the second demo, then hit reject. On cash-heavy books the core ships and rebalance is forced. On invested books current holds.

Critic overlay vs the code path

Local demo. Same branch as production: reject cannot veto a cash deploy.
Current book
Critic verdict
shouldForceCashDeploy: trueshouldRebalance: trueconviction: 0.4

Current

  • cash100%
  • equity0%
  • bonds0%

Deterministic core

  • cash10%
  • equity55%
  • bonds35%

Critic overlay

  • cash5%
  • equity72%
  • bonds23%

Emitted allocation

  • cash10%
  • equity55%
  • bonds35%

Force cash deploy. Ignore the overlay. Emit the deterministic core.

Neither the overlay nor the critic gets to park an eToro book in cash, because the branch that owns shouldRebalance lives in TypeScript, not in a system prompt.


Happy coding! Sander