Sander Korf
  • Portfolio
  • Resume

Sander Korf

Dutch Full Stack AI Engineer helping businesses cut costs through intelligent automation. 13+ years turning expensive manual processes into systems that work 24/7. Based in Amsterdam, available for freelance projects.

Navigation

  • Portfolio
  • Resume

Legal

  • Cookies
  • Privacy Policy
  • Terms and Conditions

Blog

Algolia

  • Algolia InstantSearch INP and object identity
  • Algolia merch by Sanity _type, not title
  • Algolia InstantSearch search key, not admin

Vercel

  • Next.js empty Suspense fallbacks wreck CLS P95
  • Vercel Workflows: skip closed eToro legs

eToro

  • eToro deploy vs rotate: idle cash not rotate
  • eToro search 404: query fields, not ticker
  • eToro v2 opens at-most-once, settlement
  • eToro LLM overlay ±10pp: critic cannot veto

Next.js

  • next-intl useTranslate skip links above fold
  • next-intl owns Klaviyo nl-NL email copy

Firebase & Expo

  • Staging deep links must not hijack production
  • Turn off Worklets Bundle Mode for EAS SHA-1
  • Reclaim Firebase orphan without password wipe

Centra & Klaviyo

  • Centra to Klaviyo tags need a plugin contract

About

  • Full Stack AI Engineer in Amsterdam

© 2026 Sander Korf. All rights reserved

94719489

  • Applied AI
  1. Home
  2. Blog
  3. eToro deploy vs rotate: idle cash not rotate

eToro deploy vs rotate: idle cash not rotate

Idle cash and rotation are different calls on a live book. I deploy when needed, park leftovers in indexes, and name the fee when a skip is real.

Sander KorfPublished August 12, 20268 min read
etorotrading

This is a live eToro trading workflow

An allocator that talks to eToro. Cash, indexes, overnight what-ifs, skip reasons. No storefront. No cart. If the gate is wrong, idle cash sits, leftover weight loops, or a fee hurdle pretends there was no delta.

What they would notice if it failed

A book that stays in cash while the job returns 200. Or a bot that buys the core, hits a cap, parks leftover as cash, then buys again. Same account. Same night. That is the failure, not a clever risk policy.

Sitting in eToro cash is not a rotate

I shipped an eToro allocator whose net-edge gate treated sitting in cash like a fee-sensitive rotate. Same dollar floor. Same skip path. Same shouldRebalance flag.

On a small live eToro book, a 10% tilt could not clear that floor. The flag stayed false. The skip reason even lied: no_significant_delta. There was a delta. Cash. The gate just refused to name it.

The economics helper compared notional turnover to a dollar floor sized for a bigger book. A 10% tilt on a small book is a few units of cash. The floor was a hard dollar. Math said skip. Reality said deploy.

I had mixed two questions into one boolean.

  1. Is the book empty enough that not buying is itself the cost?
  2. Is this tilt worth the round-trip fees?

Those are not the same decision. A rotate can wait. Idle cash cannot. Fee hurdles belong on rotates. They do not belong on an empty, or nearly empty, book. Not buying is a position. It just does not show up as a name weight.

Force deploy vs hurdle rotate

Idle cash at or above 15% of equity, or zero positions, must buy the core book. That path bypasses the dollar floor. Sitting out is the expensive move. Transaction cost is not the veto.

Rotate still earns its keep. It needs a max-name delta and a hurdle of max(equity × 0.03%, cost × 1.1, regime floor). A cheaper sleeve gets a lower floor so an equity-sized hurdle cannot block a cheap trade.

I keep the rotate path honest: no max-name delta, no trade. Hurdle fails, no trade. Force-deploy ignores both. That is the point of the split.

export const CASH_DEPLOY_THRESHOLD = 0.15;
 
export function shouldForceCashDeploy(allocation: Allocation): boolean {
	if (countPositivePositions(allocation) === 0) return true;
	return impliedCashWeight(allocation) >= CASH_DEPLOY_THRESHOLD;
}
 
export function shouldExecuteAfterLiveEconomics(input: GateInput): boolean {
	if (shouldForceCashDeploy(input.fullBookLiveAllocation)) return true;
	return input.economics.meetsNetEdgeThreshold;
}

Cash gate

Idle cash at 15% or an empty book must deploy. Rotate still needs the fee hurdle.

10%

Force-deploy line at 15%.

Decision

Skip

Book is invested enough, and the tilt does not clear the fee hurdle.

Drag idle cash. At 15% or on an empty book, the gate forces a deploy. Below that, rotate only if the tilt clears the fee hurdle. Otherwise skip.

Measure eToro cash on the full book, not a lane

I split rebalance into an all lane and a crypto lane. The crypto lane only sees BTC and ETH. That filter is correct for eToro orders. It is wrong for cash.

I fed those lane-filtered weights into impliedCashWeight. An empty crypto slice then looks like 100% cash. The crypto lane force-deploys every run, even when the equity book is fully invested. The bot keeps buying because the slice is empty, not because I am sitting in cash.

The inverse bug is just as real. A small BTC/ETH sleeve on a full book must still force a crypto buy when that sleeve sits below the floor. Crisis is the exception. Everything else should top up the sleeve.

Cash-deploy and sleeve-deploy always see fullBookLiveAllocation. Lane filters apply to what I may trade, not to whether the eToro book is empty.

// full book, never lane-filtered weights
if (shouldForceCashDeploy(input.fullBookLiveAllocation)) return true;
if (input.lane === 'crypto' && shouldForceCryptoSleeveDeploy(input.fullBookLiveAllocation, input.regime)) {
	return true;
}

shouldForceCashDeploy asks: is the book idle? shouldForceCryptoSleeveDeploy asks: is the sleeve below the floor on that same full book? Mix those two and I will either spam deploy on a full book, or sit still while a sleeve starves.

Leftover eToro weight is an index, not a cash pile

I used to treat leftover eToro weight as cash. After name caps at 25%, high-risk caps, and overlay clamps, whatever did not survive those rules sat as implied cash. The book looked conservative. It was unfinished math.

That leftover then tripped the 15% deploy gate on the next run. The gate bought the core. The core hit the caps. Caps parked cash. Cash retriggered deploy. Again. And again. A control loop that fights itself is not a risk control. It is a bug wearing a policy badge.

High-stress runs made it louder. A safe-haven sleeve that was not fully invested fought the cash-deploy gate on every pass. If you park residual as cash, you teach the next run that it is under-deployed.

The fix is boring. After caps and clamps, leftover is an index. fillIndexLeftover(allocation, 1) measures shortfall against a fully invested floor, then dumps it into SPY and QQQ. Dust gets pruned.

export function fillIndexLeftover(allocation: Allocation, investedFloor = 1): Allocation {
	const leftover = investedFloor - sumAllocationWeights(allocation);
	if (leftover <= DUST_WEIGHT) return allocation;
	const spyAdd = leftover * SPY_LEFTOVER_SHARE;
	next.SPY = (next.SPY ?? 0) + spyAdd;
	next.QQQ = (next.QQQ ?? 0) + (leftover - spyAdd);
	return pruneDustWeights(next);
}

Tests assert the core is fully invested. They do not require SPY or QQQ to be present. Top-ranked names can already fill the eToro book. Cash is a decision. Residual weight after clamps is unfinished math. Finish it into an index, then the deploy gate has nothing to chew on.

eToro what-if overnight is hold cost, not an open ticket

I asked eToro what-if for a rotate. The reply looked like one number. It was not.

Markup, market spread, transaction fee: cash you pay now to open. Overnight fee, weekend fee, stamp duty: cash you pay later if you hold. One payload. Two clocks.

First pass was lazy. I replaced the entire modeled round-trip cost with the what-if total. What-if only prices opens. Close and trim vanished. Overnight sat in the open hurdle, so a rotate that would pay those fees either way looked more expensive than sitting still. I shipped that number into the rotate gate. Names that should have flipped stayed put. The book looked disciplined. It was just badly labeled.

Open-ticket is markup plus market spread plus transaction fee. That is the hurdle.

Hold cost is logged, not hurdled. Overnight, weekend, stamp duty. You pay them if you keep the name. They are not a reason to skip the rotate.

Modeled close and trim always get added back. The what-if never priced them.

const OPEN_TICKET_COST_TYPES = new Set(['markup', 'marketSpread', 'transactionFee']);
 
estimatedTransactionCost = etoroOpenTicketCost + modeledCloseTransactionCost;
// overnight/weekend/stamp duty → holdCostNotional, not the hurdle

Per-symbol eToro what-if failures fall back to the model rate for that name only. If every what-if fails, I keep the full model total. A zero is not a quote, and a zero hurdle will happily rotate you into nothing.

What-if buckets

Local toy book. Naive dumps the whole quote into the rotate hurdle. Split keeps overnight out of the gate.

Accounting
What-if health
Open-ticket, hold, and close costs by symbol
SymbolOpen ticketHold (logged)Modeled closeHurdle
ACME$12.10$0.00$0.00$12.10
BETA$9.60$0.00$0.00$9.60

Rotate hurdle: $21.70

Hold cost logged, not hurdled: $0.00

Naive: overnight sits in the ticket and close is gone.

Your eToro skip reason is lying

Ops could not tell "weights barely moved" from "we wanted to trade but fees ate the edge." Both landed in no_significant_delta. The dashboard skip streak climbed. Zero eToro fills. Same sleepy string.

The economics object already knew both facts: meetsDeltaThreshold and meetsNetEdgeThreshold. We just threw the second one away. The fork only runs after cash-deploy has already been allowed through.

export function skipReasonWhenNotRebalancing(economics: Economics): RebalanceSkipReason {
	if (economics?.meetsDeltaThreshold && !economics.meetsNetEdgeThreshold) {
		return 'net_edge_below_cost_hurdle';
	}
	return 'no_significant_delta';
}

no_significant_delta stays for the tiny tilt. net_edge_below_cost_hurdle is the fee-gate: the bot wanted size, eToro would have taken the order, fees ate the edge, so no fill.

Skip reason fork

Cash-deploy already passed. Flip the two economics flags. The skip string is what the dashboard and Slack histogram read. `net_edge_below_cost_hurdle` means no eToro fill.

skipReasonWhenNotRebalancing

net_edge_below_cost_hurdle

Weights moved enough. Fees ate the edge. net_edge_below_cost_hurdle. No eToro fill. Not a quiet book.

The daily summary now prints implied cash percent, a skip histogram, modeled-versus-eToro cost drift, and a rotating-light if there are zero verified eToro fills for two weekdays while the kill switch is off. Quiet book with the switch off is not a vibe. It is an alarm.

Tests at the real eToro book size

The golden tests lived on oversized fantasies. At that scale a 10% tilt always cleared the floor. On the live eToro book it never did. I now pin fixtures to the live shape: a few names, real cash, a 10% tilt that used to vanish under the floor.

If your fee gate cannot tell "must deploy" from "nice to rotate", it will keep a live eToro book in cash forever. Measure cash on the full book. Finish leftover into an index. Keep overnight out of the ticket. Name the skip.


Happy coding!
Sander