Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ KAP_RATE_LIMIT_RPS=2
KAP_TIMEOUT_S=10
KAP_USER_AGENT=trailingedge/0.1 (research)

# Optional proxy pool for the KAP backfill. The WAF throttles per source IP (~50 requests,
# then a block that refills in ~2 min), so a rotating pool of IPs removes the block stall:
# a spent IP is parked to refill while another serves at full speed. Comma-separated URLs,
# or put one per line in a gitignored proxies.txt. Residential proxies survive far better
# than datacenter ones here. Leave unset for a single direct connection (default).
# KAP_PROXIES=http://user:pass@host1:port,http://user:pass@host2:port

# TSG (Ticaret Sicil Gazetesi) - semi-automatic scraping.
# The scraper opens a visible browser; you sign in and solve the login
# CAPTCHA by hand once, then the run proceeds automatically. No credentials
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,6 @@ ehthumbs.db
.python-version
pip-log.txt
pip-delete-this-directory.txt

# Proxy list for the KAP scraper rotation pool - a credential, never commit.
proxies.txt*
58 changes: 36 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,33 @@ disclosure is *public*, returns are measured in excess of XU100 over the same he
interval, and the round-trip cost is estimated per trade from that stock's own OHLC
(Abdi-Ranaldo 2017) rather than assumed as a flat fee.

Full 2015-2026 history, **2,279 survivorship-clean insider-cluster events**:

| Horizon | N | Gross AR | Cost | **Net AR** | t (net) |
|---|---:|---:|---:|---:|---:|
| 5d | 1,070 | +0.66% | 3.37% | **−2.71%** | −12.91 |
| 20d | 1,070 | +2.02% | 3.37% | **−1.35%** | −3.31 |
| 60d | 1,071 | +2.18% | 3.37% | **−1.20%** | −1.86 |

**The signal is real. The gross abnormal return is significantly positive at every
horizon** (20d: +2.07%, t = 5.36, N = 1,079). **And it is not tradeable**, because insider
clusters fire in illiquid small caps whose bid-ask spread is wider than the alpha: the
median round trip costs 1.93%, the upper quartile 4.34%. Nothing survives crossing it
twice. At 60 days the net loss is no longer statistically distinguishable from zero
(t = −1.86) - which buys nothing: the point estimate is still negative, and "you might
merely break even after three months" is not an edge either.
| 5d | 2,279 | +0.41% | 4.19% | **−3.78%** | −21.29 |
| 20d | 2,279 | +1.58% | 4.19% | **−2.61%** | −7.76 |
| 60d | 2,236 | +2.61% | 4.19% | **−1.58%** | −2.57 |

**The pooled gross signal is real** (20d: +1.58%, t = 5.2, `EDGE_DETECTED`) **and not
tradeable** — insider clusters fire in illiquid small caps whose bid-ask spread (median
round trip 2.33%) is wider than the alpha. Net negative at every horizon.

**But the full history says something sharper than "not tradeable".** Split by regime, the
gross signal was strong in 2015-2018 (+2.36% at 20d, t = 6.5) and has **decayed to nothing
in 2021-2026** (−0.67%, t = −1.0 — indistinguishable from zero, before costs):

| Regime | 20d Gross AR | t (gross) | 20d Net AR |
|---|---:|---:|---:|
| 2015-2018 | +2.36% | 6.46 | −1.00% |
| 2019-2020 | +3.34% | 3.34 | −5.01% |
| **2021-2026** | **−0.67%** | **−1.00** | −5.44% |

So the pooled number is carried entirely by the early era. Insiders' disclosed purchases
predicted abnormal returns in 2015-2018 — returns you still could not capture after the
spread — and in the regime that matters to a trader today they no longer predict them at
all. The edge was real, uncapturable, and has since decayed. (2019-2020 is a COVID
small-cap-mania artefact on a tiny, extremely illiquid sample, not a strategy.)

That is the whole finding, and it is why this repository exists. A gross number is not
an edge; an edge is what is left after the market takes its cut.
Expand Down Expand Up @@ -95,12 +109,12 @@ verdict standing.
`INSUFFICIENT_POWER` below ~784 events and `SURVIVORSHIP_BIASED` when too many
clusters cannot be priced. Both gates fired during this work, and both were right.

> **What is claimed, precisely:** a statistically strong *gross* abnormal return
> (20d: +2.07%, t = 5.36, N = 1,079, survivorship-clean) that does **not** survive a
> per-trade cost estimate. The window is 2015-2018 - a single regime - so the result is
> not yet regime-conditional, and that is stated rather than glossed. Remaining gaps are
> in [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md#6-still-open), not left for a
> reader to discover.
> **What is claimed, precisely:** across the full 2015-2026 history (N = 2,279,
> survivorship-clean), a *gross* abnormal return that does **not** survive a per-trade cost
> estimate at any horizon — and that, split by regime, was statistically strong in 2015-2018
> (20d +2.36%, t = 6.5) and has **decayed to zero in 2021-2026** (−0.67%, t = −1.0). The edge
> was real, uncapturable, and is now gone. Remaining gaps are in
> [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md), not left for a reader to discover.

## Türkçe özet

Expand Down Expand Up @@ -173,13 +187,13 @@ python scripts/net_of_cost.py # the one that decides it
```
=== Abnormal return, NET of round-trip cost (order 25,000 TRY) ===
spread: Abdi-Ranaldo (2017) from the stock's own OHLC, per trade
dropped (no cost estimate): 27
round-trip cost: median 1.93% p25 1.19% p75 4.34%
dropped (no cost estimate): 36
round-trip cost: median 2.33% p25 1.34% p75 4.76%

HORIZON N GROSS AR% COST% NET AR% HIT% 95% CI t VERDICT
5d 1070 0.66 3.37 -2.71 26.9 [24.3, 29.7] -12.91 LOSES MONEY (net)
20d 1070 2.02 3.37 -1.35 41.3 [38.4, 44.3] -3.31 LOSES MONEY (net)
60d 1071 2.18 3.37 -1.20 44.4 [41.5, 47.4] -1.86 NO EDGE (net)
5d 2280 0.41 4.19 -3.78 26.1 [24.4, 28.0] -21.29 LOSES MONEY (net)
20d 2279 1.58 4.19 -2.61 39.8 [37.8, 41.8] -7.76 LOSES MONEY (net)
60d 2236 2.61 4.19 -1.58 42.4 [40.4, 44.5] -2.57 LOSES MONEY (net)
```

The spread is not a parameter. It is estimated for each trade from the 30 sessions of
Expand Down
82 changes: 82 additions & 0 deletions docs/ALTERNATIVE_ALPHA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Alternative alpha on Borsa İstanbul — a rigorous search, and where it lands

The core project shows that disclosed-insider *clusters* predict a real gross abnormal return
that does not survive the spread, and that the signal decayed after 2020 (see
[`METHODOLOGY.md`](METHODOLOGY.md)). This document is the log of a wider search for a
capturable edge on the same survivorship-clean 2015-2026 data — what was tried, what it
showed, and the one thing that actually worked before it too decayed.

The discipline is the same throughout: entry at t+1 after **public** information, abnormal
return in excess of XU100, per-trade round-trip cost (Abdi-Ranaldo spread + Kyle impact +
commission/BSMV), and — where a candidate looked alive — **out-of-sample / point-in-time**
validation, because the fastest way to fool yourself here is an in-sample t-stat.

## What was tested, and what it showed

| Candidate | Result |
|---|---|
| **Insider clusters (pooled)** | Gross +1.6% @20d (t=5.2), **net −2.6%** — uncapturable; illiquid names, spread wider than alpha |
| **Liquid subset of the cluster signal** | Cost falls with liquidity, but the gross signal falls with it too; top-decile net +1.3% is not significant (t=0.7) |
| **Holding vs listed-subsidiary pairs** | Looked strong in-sample (KCHOL t=13) but **collapsed** on non-overlapping, regime-split re-test (t≈1.9, inconsistent) — the t was overlap inflation |
| **Bonus (bedelsiz) anticipation** | Big run-up into the ex-date (+9.6% mean), but conditioned on the **public board-decision date** the capturable window is nil (median 0%, liquid names negative). The move is pre-announcement |
| **Tender / contract wins** (KAP "İhale Süreci / Sonucu", "Yeni İş İlişkisi") | **Pre-announcement run-up +2.26%, t=7.86** — the leak is real and measurable — but the post-announcement (tradeable) window has a negative median and is zero in liquid names |
| **Insider buys before those announcements** | 9.4% vs 9.2% placebo — **1.02×**, the leak is *not* visible in disclosed insider data |
| **Abnormal volume before announcements** | Median 0.96× baseline — the leak's footprint is **sparse**, present in a minority of events |
| **Insider sells** | Informative: gross **−2.3% (t=−4.0)** — an avoid signal, but shorting these names is restricted and cost-heavy |
| **Named-insider persistence** | Top-quartile insiders of 2015-2020 returned **−12.3% out-of-sample** in 2021-2026 — past performance anti-predicts; pure regression to the mean |
| **Share-class arbitrage** (KRDMA/B/D, ISATR/BTR/CTR …) | Only a handful of true pairs; none significant (Kardemir t=−1.5) |

The recurring shape: the alpha is real and it is **pre-announcement** — it accrues to whoever
is positioned before the public disclosure. Every strategy built on the public signal, traded
after the fact, either dies to the spread or lives only in illiquid names. This is the
"çakallık" made quantitative: the tender-win run-up at t=7.86 is direct evidence that someone
trades ahead of the news, and the leak is invisible in the disclosed insider data and only
sparsely in volume.

## The one thing that worked: connected institutional insiders

If the edge is in *who is positioned early*, the network is the place to look. Splitting
disclosed insider **buys** by the actor's connectivity — how many distinct companies they have
traded, computed **point-in-time** — and by whether the actor is an institution (holding /
fund / bank) rather than an individual (`scripts/network_signal.py`):

The **mean** looked spectacular — and was a trap:

regime institutional hub, MEAN net MEDIAN net liquid* MEDIAN net
2015-2018 +22.3% t=4.40 -0.7% -2.7% (t=1.33, n.s.)
2019-2020 +5.3% t=3.10 +0.3% -2.5%
2021-2026 -7.3% t=-2.59 -4.3% -4.7%
*liquid = ADV >= 1,000,000 TRY, i.e. names you could actually trade

**This is a self-caught false positive, and worth keeping as the cautionary tale it is.** The
+22.3% mean in 2015-2018 is driven almost entirely by a handful of extreme outliers on one
near-untradeable instrument: ISKUR (İş Bankası founder shares) prints returns of +873% but
trades **~32,000 TRY a day** — a 25,000 TRY order is most of a day's volume, so the return is
uncapturable in principle. Strip the top 10 events and the hub mean falls from 13.8% to 6.2%;
the whole result rests on 17 actors and 41 tickers. The **median** hub trade **loses money
after cost in every regime**, and once the untradeable names are filtered out the mean is
insignificant even in the best period (t=1.33). Point-in-time connectivity and regime splits
were done correctly; the error was trusting the mean on skewed, illiquid data — the exact trap
this project exists to avoid, committed by the person cataloguing it.

The one robust piece is the mirror image: a coordinated **pack** — three or more different
insiders piling into one name within 20 days — **underperforms**, sharply in the recent regime
(net −10%, t=−6.7). Many insiders buying at once is late-stage crowd, and it reads as a
contrarian tell, not a pack of wolves to follow.

## Where this leaves it

There is **no exception**. Every candidate here — insider clusters, holding pairs, bonus and
tender events, insider-buy leakage, named persistence, share classes, and the network signal —
fails once held to the same standard: non-overlapping / out-of-sample where relevant, cost per
trade, the **median** rather than the outlier-inflated mean, and a **liquidity filter** that
removes names you cannot actually trade. Retail-accessible, public-signal alpha on BIST
equities is, on this survivorship-clean 2015-2026 evidence, structurally absent.

The alpha itself is real and it is **pre-announcement**: the tender-win run-up at t=7.86 is
direct, quantitative evidence that someone positions ahead of the public disclosure. Public
data can *detect* that (the run-up, the sparse volume footprint) but cannot *time* or capture
it — and the disclosed-insider record does not reveal who does. Capturing it would require
information or infrastructure that is either non-public (illegal) or unavailable to a retail
account. That is the honest, defensible finding, and it is worth more than any of the false
edges that did not survive contact with the median.
Loading
Loading