The 3-Day Echo: How Foreign Money Really Moves TLKM
Ask any retail trader on Indonesian StockTwits what happens when foreign institutions dump massive amounts of TLKM, and the answer comes back almost unanimously:
The price will drop. Immediately.
It’s the conventional wisdom. Foreign sells on Monday morning, red candle by Monday close. Cause, then effect. Clean and simple.
But what if the market’s memory doesn’t work that way?
What if the real damage — or the real alpha — doesn’t show up until days after the trigger is pulled?
In this post, I’ll walk through a microstructure analysis of TLKM (PT Telkom Indonesia) to test whether foreign net flow actually leads price action. Using a Granger-Causality test on six years of daily data, I found something the spot-trading crowd is missing: a hidden echo effect in the order flow. The true impact of foreign buying or selling doesn’t fire on D-Day. It lies dormant, then detonates with peak statistical significance exactly three days later.
Here’s how I got there.
The Setup
Hypothesis
- H₀ (Null): Foreign net flow has zero predictive impact on TLKM’s price returns.
- H₁ (Alternative): Foreign net flow Granger-causes price returns.
Data
- Asset: TLKM (PT Telkom Indonesia Tbk)
- Source: IDX (Indonesia Stock Exchange) — daily OHLCV plus foreign buy/sell volumes
- Period: January 2020 – April 2026
- Stack: Python (
pandas,statsmodels,scipy)
tlkm_df = df.query('StockCode == "TLKM"').copy()
tlkm_df['Date'] = pd.to_datetime(tlkm_df['Date'])
tlkm_df = tlkm_df.sort_values('Date').set_index('Date')
tlkm_df.info()
<class 'pandas.DataFrame'>
DatetimeIndex: 1517 entries, 2020-01-02 to 2026-04-21
Data columns (total 47 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
...
22 ForeignSell 1517 non-null float64
23 ForeignBuy 1517 non-null float64
...
That’s 1,517 trading days — a healthy sample spanning the COVID crash, the post-pandemic recovery, the 2022 rate-hike cycle, and the consolidation regime that followed.
A Quick Detour: What Granger-Causality Actually Tests
Before running the test, it’s worth being honest about what it doesn’t prove. Granger-Causality, in plain English, asks:
Does knowing the past values of variable X help me predict the future values of variable Y better than if I only used Y’s own past?
If the answer is yes, we say X “Granger-causes” Y. But there are three traps that catch most analysts who treat this test casually.
Trap 1: The Rooster and the Sun
A rooster crows every morning right before sunrise. Run a Granger-Causality test on that data and the math will gleefully conclude that the rooster’s crowing causes the sun to rise. The past values of the crow perfectly predict the future values of the sunrise.
The test measures sequence and correlation, not mechanism. We are not proving the foreign desk causes the move — only that it systematically precedes it. That’s still tradeable. Just don’t oversell it at the next quant meetup.
Trap 2: Stationarity Is Non-Negotiable
You cannot run Granger-Causality on raw, trending data. The math assumes stationarity — a stable mean and variance over time.
- ❌ Wrong: Raw TLKM closing prices (3,200 → 3,400 → 3,650 → …)
- ✅ Right: Log returns or daily percent change (+0.8%, −1.2%, +0.4%, …)
Skip this step and the test will hallucinate relationships out of pure trend.
Trap 3: Lag Selection
The test takes a parameter p — how many past time steps to look back. Test only lag 1 and you might miss the signal entirely. Test lag 5 and a strong relationship suddenly appears. The honest move is to scan a range of lags and let the statistics speak, which is exactly what we’ll do.
Feature Engineering
The closing price is non-stationary, so we transform it into daily returns. The dataset gives us ForeignBuy and ForeignSell separately, so we derive net foreign flow and normalize it by total volume to get a unitless ratio that’s comparable across days of wildly different liquidity.
In our test:
- X (independent):
net_foreign_ratio— the foreign desk’s footprint, scaled. - Y (dependent):
returns— TLKM’s daily percent change.
tlkm_df['net_foreign'] = tlkm_df['ForeignBuy'] - tlkm_df['ForeignSell']
tlkm_df['net_foreign_ratio'] = tlkm_df['net_foreign'] / tlkm_df['Volume']
tlkm_df['returns'] = tlkm_df['Close'].pct_change()
Running the Test
granger_data = tlkm_df[['returns', 'net_foreign_ratio']].dropna()
grangercausalitytests(granger_data, maxlag=10, verbose=True)
Note: The
dropna()is not cosmetic. Granger-Causality fits a regression under the hood, and a single NaN propagates through the entire lag matrix, silently corrupting your test statistics. Always clean before testing.
The output (trimmed to the SSR-based F-test for clarity):
Granger Causality
number of lags (no zero) 1
ssr based F test: F=2.1804, p=0.1400, df_denom=1512, df_num=1
Granger Causality
number of lags (no zero) 2
ssr based F test: F=3.7939, p=0.0227, df_denom=1509, df_num=2
Granger Causality
number of lags (no zero) 3
ssr based F test: F=3.8428, p=0.0094, df_denom=1506, df_num=3
Granger Causality
number of lags (no zero) 4
ssr based F test: F=2.8930, p=0.0212, df_denom=1503, df_num=4
Granger Causality
number of lags (no zero) 5
ssr based F test: F=2.4810, p=0.0301, df_denom=1500, df_num=5
Granger Causality
number of lags (no zero) 6
ssr based F test: F=2.2222, p=0.0386, df_denom=1497, df_num=6
Granger Causality
number of lags (no zero) 7
ssr based F test: F=1.9579, p=0.0575, df_denom=1494, df_num=7
Granger Causality
number of lags (no zero) 8
ssr based F test: F=1.8728, p=0.0604, df_denom=1491, df_num=8
Granger Causality
number of lags (no zero) 9
ssr based F test: F=1.6209, p=0.1040, df_denom=1488, df_num=9
Granger Causality
number of lags (no zero) 10
ssr based F test: F=1.4753, p=0.1426, df_denom=1485, df_num=10
The Story the P-Values Tell
A p-value below 0.05 is our threshold for statistical significance — it means there’s less than a 5% chance the predictive relationship we observed is just luck.
| Lag (days) | p-value | Verdict |
|---|---|---|
| 1 | 0.140 | ❌ Not significant |
| 2 | 0.023 | ✅ Significant |
| 3 | 0.009 | 🎯 Peak signal |
| 4 | 0.021 | ✅ Significant |
| 5 | 0.030 | ✅ Significant |
| 6 | 0.039 | ✅ Significant |
| 7 | 0.058 | ⚠️ Marginal |
| 8 | 0.060 | ⚠️ Marginal |
| 9 | 0.104 | ❌ Not significant |
| 10 | 0.143 | ❌ Not significant |
{
"data": [
{
"x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"y": [0.14, 0.023, 0.009, 0.021, 0.030, 0.039, 0.058, 0.060, 0.104, 0.143],
"type": "bar",
"marker": {
"color": "steelblue",
"opacity": 0.7
}
}
],
"layout": {
"title": "Granger Causality p-values: Net Foreign Ratio → Returns",
"xaxis": { "title": "Lag (Days)" },
"yaxis": { "title": "p-value" },
"shapes": [
{
"type": "line",
"x0": 0, "x1": 10,
"y0": 0.05, "y1": 0.05,
"line": { "color": "red", "width": 2, "dash": "dashdot" }
},
{
"type": "line",
"x0": 0, "x1": 10,
"y0": 0.01, "y1": 0.01,
"line": { "color": "orange", "width": 2, "dash": "dashdot" }
}
],
"annotations": [
{ "x": 10, "y": 0.05, "text": "α = 0.05", "showarrow": false, "xanchor": "left" },
{ "x": 10, "y": 0.01, "text": "α = 0.01", "showarrow": false, "xanchor": "left" }
]
}
}
The shape of this curve is the entire story. It’s not flat. It’s not monotonic. It’s a wave — and the wave has a clear arc.
Act I: The Silence (Lag 1)
On the very next trading day after a foreign flow event, there is nothing. The p-value of 0.140 is statistical noise. If your model — or your intraday discretionary trigger — fires on T+1 based on yesterday’s foreign tape, you are trading on a coin flip dressed up as a signal.
This is the most counterintuitive result of the entire study, and it directly contradicts the retail thesis that prices react instantly.
Act II: The Awakening (Lags 2–6)
Then the wave hits.
Starting on Lag 2 (p = 0.023), the relationship snaps into statistical significance. By Lag 3, the signal peaks at p = 0.009 — well below even the strict α = 0.01 threshold. This is the sweet spot. Three days after the foreign desk acts, the market has fully priced in the information, and the directional impact is at its most predictable.
The signal stays significant for three more days, gradually weakening but holding the line through Lag 6.
Act III: The Decay (Lags 7+)
By Lag 7, the p-value crosses back over 0.05 and the edge begins evaporating. By Lag 9 and 10, the signal is gone. The market has fully absorbed the event. Whatever asymmetric information or positioning pressure the foreign flow represented has been arbitraged away.
This decay pattern — silence, climax, dissipation — is exactly what you’d expect from a market that’s neither perfectly efficient nor perfectly inefficient. Information takes time to propagate, and that propagation has a measurable half-life.
Why a 3-Day Lag, Specifically?
The 72-hour delay isn’t arbitrary, and it isn’t magic. A few microstructure mechanisms plausibly drive it:
- T+2 settlement. Indonesian equities settle on T+2. Foreign flow reported today represents trades whose cash and custody implications cascade into Day 2 and Day 3, when local desks rebalance against fresh foreign positioning data.
- Information diffusion. Foreign institutions often have research-driven views that local participants haven’t yet seen. Domestic funds typically need a day or two to catch up via brokerage notes, news flow, or simply observing the tape.
- Momentum confirmation. Local momentum traders rarely act on a single day’s flow. They wait for two-to-three days of confirmation before committing — which is, mechanically, when the cascade kicks in.
The point isn’t to prove which of these dominates. It’s that something structural is happening on a consistent 3-day cycle, and it shows up cleanly in 1,517 days of data.
Conclusion: Trade the Echo, Not the Bang
So — does foreign net flow actually move TLKM?
The answer depends entirely on your stopwatch.
If you measure the impact strictly on T+1, you are forced to accept H₀. There is no immediate, statistically significant relationship. The market doesn’t react the way the conventional retail wisdom claims.
But widen the window, and H₀ shatters. From T+2 through T+6, the data overwhelmingly supports H₁: foreign net flow Granger-causes TLKM’s price returns, with peak significance landing exactly three days after the event.
Trading TLKM on foreign flow isn’t a game of reflexes. It’s a game of patience. The trigger that fires on T+1 is trading noise. The real alpha lives in the echo — and the echo arrives on Day 3.
For practitioners, this reframes the engineering problem entirely. The feature isn’t net_foreign_ratio[t]. It’s net_foreign_ratio[t-3]. And the model that ignores the lag structure is throwing away the only edge the data is actually offering.
Caveats and Honest Limitations
A responsible quant adds the asterisks:
- Granger ≠ true causality. This test proves predictive sequencing, not mechanism. Both variables could be reacting to a common driver (commodity prices, IDR/USD moves, regional risk sentiment) on slightly offset timelines.
- Pooled regime risk. The 2020–2026 sample blends multiple regimes (COVID shock, recovery, rate cycles). The 3-day lag may be a weighted average across regimes that behave differently in isolation. A rolling-window or regime-conditional test is the natural follow-up.
- Single-asset finding. This is TLKM. Whether the same lag structure holds for BBCA, ASII, or the broader LQ45 is an open question — and a useful one to answer before generalizing.
The next post in this series will run the same test across all LQ45 constituents to see whether the 3-day echo is a TLKM quirk or a market-wide signature. Stay tuned.
Author: Andri Syahputra Muda Code and notebook available on request.