← Back to portfolio

Enumerate, Then Select

Our team, the Weldon Warriors, finished 6th in Canada and in the top 3% worldwide at WorldQuant's International Quant Championship. This is an honest account of how, because the method was not sophisticated, and that was the point. We enumerated primitive alphas at scale, let the platform's gates do the selecting, and spent our actual creativity on mapping the scoring system's boundaries. It turned out the boundaries were the game.

The competition

The IQC runs on WorldQuant's BRAIN platform. You write alphas, short expressions over market data fields that produce a daily score per stock, and the platform simulates them into a portfolio: go long what your expression ranks high, short what it ranks low, rebalance daily. An alpha that survives simulation gets judged on Sharpe, fitness, turnover, and a correlation check against everything you've already submitted. Your team's score is built from the alphas that clear every gate.

The expression language is small. Rank, delta, decay, a few time-series operators, a large catalogue of data fields. The romantic version of this competition is a room of people having insights about markets. That's not what we did.

We tried being quants first

For the first stretch we did what you're supposed to do. We read field descriptions, imagined economic stories, and hand-built branches with names like sales-and-EPS growth, analyst revisions, news events, options carry, low-volatility quality, model-score composites. Our lessons file from that era is a graveyard: news alphas too sparse and too reactive, options carry flat with too much drawdown for the edge, size-bucket quality regime-fragile, and the model-heavy stacks not just noisy but directionally wrong. Out of six researched families, one worked.

Here's the thing about researching alphas on BRAIN that took me a while to say out loud. You don't get the data. You get the data's descriptions: a catalogue of thousands of fields, each with a sentence or two about what it supposedly measures. "Researching" an alpha means reading those sentences, imagining a relation, and writing an expression that encodes the story. But you can't see the relations. The story lives entirely in your head, and the first honest evidence for or against it is the simulation itself. Which means the careful researcher and the person guessing are running the same experiment. One of them just spent an hour on a narrative first. Our graveyard was the proof.

Six hand-researched alpha families and their verdicts; only one survived THE RESEARCH ERA · SIX FAMILIES, ONE SURVIVOR sales / EPS growth SURVIVED news / events too sparse, too reactive options carry flat, drawdown outran the edge low-vol quality regime-fragile analyst revisions below the bar model-score stacks directionally wrong weeks of narrative, one keeper. This is the scoreboard that talked us into enumeration.
The research era, verdicts straight from our lessons file. Dashed means dead; the warm one didn't just fail, it was confidently backwards.

So we got cheeky

Once we admitted the simulation was the only real test, the strategy wrote itself, and WorldQuant had already pointed at it with their own push toward automated research: stop rationing simulations, and stop pretending the narrative step was doing work.

The purest version of what we built is a folder containing 1,557 alphas that are all the same alpha. One per data field, each one literally:

group_neutralize(rank(<field>), subindustry)

No thesis beyond "this field, ranked." Simulate every field in the catalogue through that one template and you stop guessing which descriptions matter, because the platform tells you. The primitives that showed signal became parents: mutation scripts perturbed the winners, hybrid generators crossed them, config sweeps walked neutralization, winsorization, decay, and truncation around anything promising, and regime and composite layers stacked the survivors into things worth submitting.

By the end we had written 5,959 alphas. Of those, 143 got promoted to the pool worth submitting from, a 2.4% survival rate, which sounds brutal until you remember that each of the 5,816 corpses cost us roughly nothing. The best survivor carried a Sharpe of 2.59, the median promoted alpha 1.59, both comfortably over the platform's 1.25 bar. And because everything lived in per-author folders, the repo also preserves an honest census of enthusiasm: one teammate's folder holds 514 candidates, another's holds 14. Both of them were load-bearing. Enumeration doesn't care how many alphas you personally typed.

All 5,959 alphas drawn to scale, with the 143 promoted survivors magnified EVERY ALPHA WE WROTE · TO SCALE 1,557 single-field primitives 4,402 mutations · hybrids · config sweeps · composites 0 2,000 4,000 5,959 DETAIL · 27.8× MAGNIFICATION 143 promoted · 2.4% survival best Sharpe 2.59 median 1.59 platform bar 1.25 the other 5,816 cost roughly nothing, which is the entire trick
The factory's output, drawn honestly. At true scale the survivors are a 14-pixel sliver, so the detail view magnifies them 27.8×, the way an engineering drawing would.

The loop ran overnight without us. A bounded auto-research harness mixed ranked carry-forward candidates, expression mutations, and field-template proposals (an LLM lane pitched in ideas too), simulated them in concurrent batches through the BRAIN API, and wrote everything to a durable log. We'd go to bed, and the factory would keep asking the platform questions. The submission gates, Sharpe above 1.25, fitness above 1, turnover between 1% and 70%, weren't results we hoped for. They were design targets the generators aimed at.

Animated grid of cells being swept: most fade out as failures, a few turn solid teal as survivors
The overnight loop, artist's impression. Teal survives; everything else flickers and fades to compost for the next generation. The pass rate is exactly to scale, and the counter runs the real totals.

Any single alpha in that pool would not impress a quant, and from the outside we looked like a team writing beginner-shaped expressions. But the unit of play was never the single alpha. It was the pipeline. When evaluation is cheap, selection is strict, and the search space is made of unverifiable stories, enumeration beats inspiration, and every hour not spent polishing one clever narrative was an hour the factory spent testing hundreds of artless candidates. The craft moved into the machinery, and into understanding exactly what the gates measured.

The scoring system was the real opponent

Everything that felt like an edge came from experiments on the platform itself, not on the market. Three findings carried most of our end-game, and each one was established the same way: form a hypothesis about how scoring works, spend one cheap probe on it, and only then commit simulation budget.

Correlated alphas aren't dead, they're upgradeable

An alpha that correlates above 0.7 with something already in your pool fails the self-correlation gate. That reads like "this slot is closed forever." It isn't: a variant of an already-submitted alpha can replace it if its Sharpe is at least 10% better than the live one. That turns your own submitted pool into farmland. We ran config sweeps over alphas we had already banked, hunting the neutralization or decay setting that cleared the 10% bar, and resubmitted the winners. Points without needing a single new idea.

The 12 hours we spent proving ourselves wrong

The platform lets you run the same expression on different stock universes, the top 3000 names, the top 2000, and so on. If self-correlation were computed per universe, every alpha we had could be cloned into the smaller universes for free slots. We bet a 12-hour, 346-simulation sweep on that hypothesis before testing it properly, which was the wrong order, and the platform told us so: a top-2000 twin of a live top-3000 alpha came back with a self-correlation of 0.9219, rejected. The gate is computed across universes. Running the same expression on a subset of the same stocks produces a 0.92 to 0.95 correlated copy of itself, every time.

TOP2000 nests inside TOP3000, so its clone lands at 0.9219 self-correlation, far past the 0.70 gate THE GEOMETRY TOP3000 the live alpha's universe TOP2000 the "twin": same expression, a subset of the same stocks areas to scale · all 2,000 of its names are shared THE VERDICT · SELF-CORRELATION 0 0.5 1.0 0.70 gate REJECTED measured twin: 0.9219 before the market gets a vote
Left: the twin's entire book already lives inside the original, areas drawn to scale. Right: where it landed against the gate. Twelve hours and 346 simulations to measure a number the geometry already knew.

The sweep was a waste, but the autopsy wasn't, because it forced the right question: if universe isn't a real boundary in the scoring system, what is? The answer was delay. Alphas trading on same-day versus previous-day data are scored separately, and they don't even share a catalogue: roughly 8,600 fields exist at delay 1 and only about 2,400 at delay 0, which makes delay-0 a genuinely different game rather than a costume change. That's where the fresh slots actually lived, so we pointed the same primitive template at the delay-0 catalogue and enumerated a second, orthogonal-by-construction pool. One cheap probe, submitted first, would have found all of this for the price of a single simulation. That ordering, probe the boundary before betting budget on it, became our rule for everything after.

It was an engineering problem wearing a finance costume

The quiet decision that got us furthest was refusing to treat any of this as a sequence of one-off experiments. Everything became a pipeline stage with an artifact, and every artifact was a file you could diff:

The alpha factory: generators feed a candidate pool through a concurrent runner to the rate-limited simulator, results flow back through gates to promotion and submission, and winners feed the generators again THE REPO · RUNS UNATTENDED OVERNIGHT WORLDQUANT · RATE-LIMITED GENERATORS primitives · 1,557 mutate winners hybrids · composites config sweeps LLM proposal lane candidate pool 5,959 alpha files + dated manifests concurrent sim runner bounded · dry-run first BRAIN simulator the only oracle every question costs budget results log append-only · grep-able gates corr < 0.70 · Sharpe > 1.25 turnover 1% to 70% promoted · 143 portfolio selector picks submission log winners become parents
The factory. Solid arrows run every night; the dashed one is the compounding loop, yesterday's survivors seeding today's generators. The warm box is the part we didn't control, which is why everything to its left exists.

None of that is clever. All of it compounds. A hand researcher's output is capped by their attention, and their process resets to zero every morning. A pipeline's output is capped by simulation budget, and it wakes up smarter than yesterday because yesterday's results are sitting in a manifest feeding today's generation. By the end, "do research" meant editing a generator and going to bed. The competition ran for months, and compounding is exactly the thing you want running for months.

I want to be honest about what this actually looked like from inside, though, because "pipeline" makes it sound sterile and it was not. It looked like Discord at three in the morning:

teammate A 3:10 AM

made a pr for my branch

so many changes that the screen to create a pr lags in real time

oh god console got spammed for like 15 straight seconds on the files it created when I swapped to your branch

me 3:14 AM

My bad

teammate A 3:15 AM

quite funny

ok so i'll sim the field_sim_list_20260613 alphas, I assume you have a separate list you're going through

That exchange has everything in it. A pull request so bloated with generated alphas that GitHub's UI lags in real time. A checkout that spams the console for fifteen straight seconds with files a generator wrote while nobody watched. An apology for it, three words, because the volume was the plan. And then, without a beat, the actual coordination: dated sim lists, split between us, each person grinding their half of the queue through the simulator. Nobody in that conversation is having an insight about markets. They're running a factory at 3 AM and dividing the shifts, and the next message in the log is us trying to schedule a meeting for 3:30 the next day. The pipeline was machinery, but the machinery was tended.

It also changed what a mistake cost. The 346-simulation universe sweep stung, but it was one bad batch in a system that ran thousands, logged in the same place as everything else, and it bought a rule the machine then enforced. When your process is files and gates instead of vibes, even the failures become infrastructure.

The smartest thing we built

All that API spelunking did find one door left open. The platform never shows you the market data, but for any alpha you've simulated it will hand you that alpha's results: PnL, daily PnL, Sharpe, turnover, yearly stats, as clean recordsets. Most people look at those as charts on the website. We fetched them systematically for every alpha we ever ran, which meant we could recreate the platform's exact graphs locally, run our own drawdown analysis on the same numbers, and, far more importantly, compute things the website never shows you, like the full correlation matrix of our entire pool.

That's the trick in one sentence: the oracle's answers were reusable. Every time a primitive got simulated, we saved its daily PnL curve to disk. That cache turned out to be a superpower, because our composites were weighted sums of ranked primitives, and the PnL of a sum is approximately the sum of the PnLs. Approximately: the real composite gets winsorized and re-neutralized, which is nonlinear, so the linear sum is only a proxy. But we checked the proxy against eight composites we actually submitted and it ran conservative-but-accurate, which is exactly the kind of wrong you want in a screener.

Three cached primitive PnL curves, their linear-sum proxy, and the simulator's slightly higher answer PRICING A COMPOSITE WITHOUT THE SIMULATOR · ILLUSTRATIVE SHAPES, REAL MECHANISM cached primitive PnLs (free, on disk) linear-sum proxy (dashed) what BRAIN said the proxy sits under the truth: winsorize + re-neutralize add back a little. Wrong in the safe direction. cumulative PnL
The screener in one picture. Sum the cached curves, get a conservative preview of the real simulation, and only spend budget when the preview clears the bar.

So the workflow became: enumerate thousands of candidate composites, price every one of them locally, for free, in seconds using cached primitive PnLs, predict both its Sharpe and its correlation against our already-submitted pool, and send only the shortlist to the real simulator. We had effectively built a cheap, slightly pessimistic copy of the oracle out of the oracle's own past answers. The same cache powered a second trick: when our submitted pool got saturated in one cluster (everything earnings-and-analyst flavored was correlated with everything else), we segmented the composite search by primitive family and ranked the non-earnings mechanisms first, hunting orthogonality by construction instead of by luck.

Animated scatter of candidate composites by predicted Sharpe and predicted self-correlation; only dots landing in the low-correlation, high-Sharpe window stay lit
The pre-screen running. Every dot is a composite priced locally in milliseconds; the lit window is "uncorrelated enough and strong enough to be worth a real simulation." Most of the pool never touches the oracle at all.

The last refinement was deciding where the budget itself should go. Late in the run we had four teammates' simulation quotas and a few thousand sims left, so we built a conversion tracker: parse every result we'd ever logged, bucket it by mechanism, expression architecture, and neutralization, and compute the live pass rate of each cell against the gates. Then everyone spent their quota in the cells that were actually converting instead of sampling blind. It's the least glamorous kind of intelligence, a pivot table, and it probably bought more points per simulation than anything else in the final weeks.

Pass rate per mechanism-by-neutralization cell, with the best cell ringed as the place to spend simulations CONVERSION TRACKER · PASS RATE PER CELL · ILLUSTRATIVE VALUES 11% 8% 5% 2% 9% 7% 4% 2% 6% 9% 3% 1% 4% 3% 2% 1% 1% 1% 0% 0% earnings analyst est. price/volume fundamentals news subindustry industry sector market the ring is where the next thousand sims went. Blind sampling died the day this table existed.
The pivot table that ran the endgame: live pass rate of every mechanism × neutralization cell against the gates, so quota flowed to cells that actually convert.

And the very last stage was deliberately human. Three of us ran sim lists in parallel; everything that passed flowed to one teammate whose whole job was screening the survivors and choosing the order they got submitted in. Order sounds like a detail, and it isn't, because the correlation gate is checked against what's already submitted. Seat one alpha and you may have just killed a better one that correlated with it. Which of your candidates survive is path-dependent, so we searched orderings against the local correlation matrix and submitted the sequence that seated the most value, with a person sanity-checking every seat before it was spent.

Two submission orders of the same three alphas seat different subsets, so order changes the final score SAME THREE ALPHAS, TWO ORDERS · CORRELATION A↔C = 0.75, EVERYTHING ELSE CLEAR submit A first A Sharpe 1.8 B Sharpe 1.5 C blocked by A (0.75) seated value: 3.3 submit C first C Sharpe 2.1 B Sharpe 1.5 A blocked by C (0.75) seated value: 3.6 same candidates, same gates. The only difference is the order, and it's worth 0.3 of Sharpe. Multiply by a whole pool.
Why one person owned submission order. The correlation gate is path-dependent: whichever twin gets seated first kills the other, so the optimizer searched orderings against our local correlation matrix before anything was spent.

Dead ends, engineering division

The narrative graveyard from earlier had a quieter engineering wing, and it deserves its own honesty:

What I'd do differently

The big three first, then the quieter ones the repo itself testifies to.

Start enumerating on day one. We spent our opening weeks doing narrative research out of respect for the problem, and the graveyard shows what that respect purchased. The 1,557-primitive sweep was the single highest-information action we took all competition, it required no ideas at all, and nothing about it depended on anything we learned first. It should have been the first thing we ran, with hand research reserved for the neighborhoods the sweep said were alive.

Build the local pre-filter before scaling, not after. The platform's simulator was our only oracle, and it was the scarce resource everything else queued behind. Late in the run we started scaffolding a local shadow simulator to cheaply screen candidates before spending real budget on them. Built early, that multiplies your effective simulation budget for the whole competition instead of the last stretch. The general rule: when one oracle is rate-limited, your first engineering project is a cheaper, worse copy of it.

Map every scoring boundary in week one. We learned the resubmit rule, the correlation scope, and the delay separation months in, each for real stakes. A dozen deliberate probe submissions at the start, designed to answer "where are the walls?", would have cost a day of budget and reshaped everything we aimed the factory at. We treated the scoring system as terrain to trip over, and it should have been the first thing we surveyed.

Price the multiple-testing tax. This is the honest counterweight to the whole essay. When you simulate thousands of candidates and keep whatever clears Sharpe 1.25, some survivors are real and some just got lucky in-sample, and the gate cannot tell them apart because it doesn't know how many times you tried. Enumeration manufactures false positives at scale, and we never priced that in: promotion meant gate-clearance, full stop. A team running our playbook properly raises its own internal bar above the platform's, or demands stability across sub-periods, for exactly the reason you'd correct any experiment you ran six thousand times.

Many no-skill alphas cross the Sharpe gate by luck in-sample, then fall back toward their true Sharpe out of sample; real ones stay THE MULTIPLE-TESTING TAX · SYNTHETIC, BUT THIS IS THE MECHANISM in-sample (what the gate sees) fresh sample (what's real) gate 1.25 the bar we should have used 0 1.0 2.0 3.0 measured Sharpe → real signal, stays put lucky
Every dot is a candidate; the top row is what the gate judged, the bottom row is the same alphas on data they haven't seen. The warm dots crossed the gate by luck and slide back below it. In this simulation nearly half the gate-passers are luck, and nothing in our pipeline would have noticed.

One artifact contract for every lane. A script exists in the repo purely because the auto-research lane never exported its recordsets, which made 25 of its promotes invisible to the proxy eval until someone backfilled them by hand. Every generator writing the same layout from day one would have made the cache-and-prescreen machinery fully automatic instead of mostly automatic. Integration debt is quiet right up until it hides a quarter of your promotions from your own optimizer.

Build the conversion tracker in the midgame, not the endgame. It earned its keep in the final weeks, but every result it needed had been sitting in the logs for months. EV-directed budget allocation from the halfway mark is probably worth more than any single alpha family we found.

Point the LLM lane at templates, not alphas. We had it proposing individual candidates, the lowest-leverage place to put it. If the teams above us really won on richer template grammars, then generating template families to enumerate over is exactly what that lane was positioned to do, and we used it as one more candidate faucet instead.

What the teams above us were probably doing

Five teams in Canada beat us, and a few hundred worldwide, and I think about them the way you think about the runner who lapped you: with curiosity, not conspiracy. Everyone had the same data, the same operators, the same gates. So where does a real gap come from?

My honest guess is that the best teams were running our playbook plus the thing we skipped. Enumeration finds fields that work; it cannot invent a mechanism that isn't a rank of something. A team with genuine market intuition doesn't waste that intuition hand-writing individual alphas, they spend it designing better templates, so their factory enumerates over a richer grammar than ours: cross-sectional structures, group dynamics, interactions we never put in the pool. Same machine, better fuel. Our conversion tracker could tell us which of our cells converted; it could never tell us about the cells we didn't know existed.

The other gap is almost certainly time-in-environment. We discovered that delay-0 was a separate scoring world with its own field catalogue late, and mostly mined delay-1. A team that mapped those boundaries in week one, the survey we filed under "what I'd do differently", had months to fill environments we entered in the final stretch. Nothing exotic, no secret alpha, just the compounding we believed in, started earlier and pointed at more of the map. Which is oddly comforting: the teams above us are most likely evidence that our approach works, executed by people who committed to it sooner.

What I actually think we learned

The result says top 3% in the world, and I want to be precise about what it's evidence of, because it isn't evidence that we can predict markets. Thousands of teams shared the same data, the same operators, and the same gates. The variance between them was mostly in how well they understood the system they were being scored by, and how much of their process they had turned into machinery instead of taste.

That's a general lesson and I keep meeting it. Every competition has two layers: the nominal problem, here "find market signal", and the scoring function stapled on top of it. People pour their effort into the first layer because it's the respectable one. But the scoring function is smaller, fully observable, and it pays to be studied. Every boundary we mapped, the 10% resubmit rule, the cross-universe correlation scope, the delay separation, was worth more points than any individual alpha we wrote.

So when someone asks how a team places 6th in Canada with alphas a first-week intern could read, the answer is that the alphas were never the product. The product was a machine that asked the platform six thousand small questions, kept the 143 answers it liked, and a team that had learned, eventually the hard way, to ask the cheap questions first.