Hunting foreign-flow alpha on KOSPI
This is the first in a series of research notes from my quantitative-research workbench. I keep the charts, blotters, and per-config blowups behind a local dashboard for my own use — these posts are the story version, written for anyone curious.
The question
KIS publishes daily 외국인 (foreign-investor) net-buy data per stock, post-close. That’s the most-watched flow in Korean retail trading: foreigners buy, the stock goes up; foreigners dump, it falls. It’s lore. But how much of that lore actually shows up in returns that you could have traded?
For the answer to mean anything, “tradeable” has to be defined strictly:
- No lookahead. Day-T net-buy is only confirmed after the close. The earliest you can act is T+1 open or T+1 VWAP. No closing-cross entries on T using T’s own data.
- Strict stock-level execution. Integer share counts, 100M KRW initial cash, 1.5 bps fee, 23 bps sales tax, 5 bps slippage on each side, capped at 1% of the stock’s daily volume.
- Beat KODEX 200. Not equal-weighted KOSPI. Not the universe mean. Just the index ETF you could have parked the same cash in.
A surprising amount of “alpha” research evaporates the moment you fold any one of those three into the spec.
Round 1 — six raw flow signals
The base sweep is six no-lookahead signals built only from foreign-flow data:
- net-buy (KRW), normalized by ADV20
- net-buy z-score over a rolling window
- 5-day net-buy momentum
- 5-day net-buy acceleration
- net-buy persistence (consecutive positive days)
- net-buy concentration (share of universe net-buy that landed on this stock)
For each signal, I sweep across:
- entry:
next_openvsnext_vwap - hold: 1, 2, 3, 5, 10, 20, 60 trading days
- basket: top 5%, 10%, 20%, or integer top-N
- cost grid: 0, 1.5, 5, 10, 15 bps round-trip
Decile diagnostics are encouraging — top-decile averages clearly outpace bottom-decile by signal.
The signal-by-hold heatmap is where it gets specific: the alpha concentrates in the 10-day hold band.
Foreign flow is a multi-day signal, not an intraday one — KIS’s confirmed daily net-buy gets digested into price over a week, not a session. At a realistic 1.5 bps cost it survives. At 15 bps round-trip the 10-day band still survives, but the 1- and 2-day bands are dead.
So far, so good.
Then I drop the synthetic basket sim and run the stock-level version: integer shares, real frictions, 1% participation cap. And the best raw-flow strategy still doesn’t beat KODEX 200 over the ~one-year sample. Buy-and-hold the index — you’re up huge. Trade the smartest single foreign-flow signal — you’re up less.
That was the first hard moment.
Round 2 — combine flow with price momentum
Pure flow ranking is too one-dimensional. KOSPI 2024–2025 was a strong index regime; holding the ETF is a hard benchmark to clear with a long-only stock-picking strategy that turns over.
So I ran a 5,280-config creative search mixing flow with classic price momentum:
rank(60d return) + rank(foreign net-buy)rank(20d return) + rank(foreign acceleration)- ensemble averages of the above
- sector-leadership weighted versions
That’s where the first KODEX-beaters showed up.
The pattern is consistent: foreign flow tells you which stocks, momentum tells you which regime. Neither half on its own beats the index in this sample. The product does.
A second sweep — 9,600 configs across equal, rank_exp, signal_positive, and liquidity_sqrt sizing — pushed the headline number further.
But liquidity_sqrt is exactly the sizing scheme that tilts toward large-cap stocks, so part of the improvement is almost certainly a market-cap factor exposure rather than new alpha.
That decomposition is on the open-questions list.
Round 3 — stealing from the /05 research line
A parallel project — /research/05_beat_kodex200 — was finding KODEX-beating stock-level baskets from a different angle: KOSPI200 trend regime gates, sector rotation, foreign+institution agreement.
The 04a sweep folds five of those ideas back into the foreign-flow track:
sig_05_sector_rotation_foreign— gate to top sectors by sector momentum + sector flow, then rank stocks by foreign flow inside those sectorssig_05_agreement_momentum— require foreign and institution net-buying on the same day, plus positive 20-day momentumsig_05_sector_capped_composite— max two names per sector, to break concentrationsig_05_regime_foreign_sector— only trade when KODEX 200 is above its 60-day SMAsig_05_short_cover_foreign— heavily-shorted stocks where short-sale pressure is falling and foreigners are buying (only where KIS short-sale silver coverage exists)
Most of these don’t dominate the leaderboard, but they hold up well under the stress filters: drawdown, fill model, KODEX-relative excess, slippage. They’re the kind of robustness props that matter when the in-sample winner is one regime change away from breaking.
The current candidate
Before fixing the basket size: the top-N sweep tells a clear story.
Top-1 has the highest headline return but ugly single-name risk. The Sharpe sweet spot lives between top-3 and top-8 — that’s where the candidate basket lands.
After ~15K backtests across the deep, focused, and rebalance sweeps, the current promoted candidate is:
signal: sig_ensemble_primary_accel
entry: next_vwap
hold: 5 trading days
basket: top 3
sizing: signal_positive (weight_param=1.0)
sig_ensemble_primary_accel combines rank(60d momentum) + rank(foreign net-buy) with rank(20d momentum) + rank(foreign acceleration).
Two momentum horizons, two flow horizons, simple averaged ranks.
The 5-day hold matches the heatmap result: short enough to rotate the book, long enough to absorb the multi-day signal.
It survives the validation step in validation.py:
- chronological 4-split test stays positive in each window
- 21-day block-bootstrap puts the lower CI band above the equal-weight universe
- factor regressions vs KODEX 200, equal-weight universe, ADV-size, and momentum proxies still leave a positive intercept
The walk-forward test is the next gate — and it’s the one that should be hardest to pass.
Caveats — the honest list
Before you (or I) treat any of this as a conclusion:
- The sample is one trading year, in a strong KOSPI200 uptrend. That’s the worst possible regime for evaluating long-only stock-picking against the index.
- Multiple-comparisons bias is real. I picked the top of a 5,280-config creative sweep, then a 9,600-config weighted sizing sweep. Walk-forward is the only honest answer to that.
- No survivorship correction yet. The universe is point-in-time on the day it runs, but I haven’t built historical universe membership / delisting logic, so backfilled symbols may be biased upward.
- The “intraday oracle” run cheats on purpose. It uses the final daily KIS rank from inside the day. That run is a ceiling, not a tradeable strategy — I run it separately to know what’s possible if the provisional 13:00–15:00 KIS snapshot turns out to be stable enough.
liquidity_sqrtmay be a factor, not alpha. The lift from that sizing scheme on the original /04 dashboard could be a large-cap factor exposure rather than new edge. Need to neutralize cap × sector × liquidity before claiming the full headline number.
If a strategy has a positive intercept on the equal-weight universe and KODEX 200, and holds up under block-bootstrap, and survives walk-forward, and doesn’t decompose into known factors — then it’s worth running with real money. Two of those four are still TODO.
What’s next
- Walk-forward the current candidate over rolling 6-month windows.
- Decompose: is the alpha foreign-flow specific, or is it a generic 60d-momentum tilt with a flow rebalance trigger?
- Capacity & turnover: at top-3 / 5-day holds, what does this look like at $10M? At $100M?
- Stand up a KIS provisional-snapshot collector at 13:00 / 14:00 / 15:00 and measure how stable the foreign-flow rank is intraday — if it’s stable enough, parts of the “oracle” run become tradeable.
- Replay the candidate on 5-minute bars: T+1 open vs first-30-min VWAP vs first-60-min VWAP vs full-day VWAP vs close auction.