Open Source
The metric I designed was the same number twice
Edy Cu DEV Community
3 views
I set out to build one metric. Halfway in, the algebra told me it was a number I already had.
This is what that looked like, what I built instead, and the three things CoinMarketCap's DEX API does that nobody documents. Everything here runs keyless — no key, no signup, no pip install.
Live: https://elephant.edycu.dev
Repo: https://github.com/edycutjong/elephant (MIT)
The problem I actually cared about
Every DEX flow tool reduces the tape to one signed number: buy volume minus sell volume. Net flow.
That number is a sum, and summing is lossy. Consider two markets:
One desk sells $1.1M into 6,100 buyers taking $180 each.
Six thousand traders sell to six thousand traders, all similar size.
Both net to roughly zero. Both print "balanced". One is a desk distributing into retail; the other is churn. The metric that is supposed to warn you cannot tell them apart, and it is the metric on almost every screen.
So I wanted a second number that could.
The metric I designed
CoinMarketCap's DEX pair object gives you volume and trade count, per side. So divide: volume ÷ count is the average ticket for that side. If buyers average $180 and sellers average $27,000, one side is a desk and the other is a crowd — even when net flow reads zero.
I liked it. It uses four fields nobody divides, it needs no extra call, and it produces a single ratio.
It is the same number twice.
ticket_ratio = (buyVol / nBuy) / (sellVol / nSell)
= (buyVol / sellVol) x (nSell / nBuy)
Look at the first factor. buyVol / sellVol is net flow, expressed as a quotient instead of a difference. So when net flow is flat — the only case where a hidden asymmetry is worth finding — that factor goes to 1, and:
ticket_ratio -> nSell / nBuy
The ticket ratio degenerates into the count ratio. At exactly the moment I needed two independent signals, the two field families collapse into one.
I did not spot this on paper. I spotted it after sweeping a few hundred tokens and noticing that my ticket ratios and my count ratios agreed to within a few percent on every row that mattered. The algebra was the explanation, not the discovery.
What actually works: the maker address
There is a different endpoint, and it changes the problem completely.
/v1/dex/tokens/transactions returns the individual swaps — and each one carries tp (side), v (USD value) and ma, the maker address.
That last field is the whole thing. It turns "one desk or a crowd?" from an inference into a count. You do not need a proxy for concentration when you can measure concentration directly:
def split(swaps):
"""The whole product: split by side, then measure each side."""
sides = {
"buy": {"v": [], "by_maker": {}, "swaps_by_maker": {}},
"sell": {"v": [], "by_maker": {}, "swaps_by_maker": {}},
}
for s in swaps:
tp = str(s.get("tp", "")).lower()
if tp not in sides:
continue
try:
v = float(s.get("v"))
except (TypeError, ValueError):
continue
ma = s.get("ma")
side = sides[tp]
side["v"].append(v)
side["by_maker"][ma] = side["by_maker"].get(ma, 0.0) + v
side["swaps_by_maker"][ma] = side["swaps_by_maker"].get(ma, 0) + 1
Sum USD per maker, per side. The largest wallet's share of its own side is the number. No model, no heuristic, no threshold I had to invent.
The result
AUSD on Ethereum, 800 swaps, captured 2026-09-07:
Net flow
+2.1% — "balanced"
Sell side
one wallet is 62.0% — $25,459,824 in 12 swaps
Buy side
212 distinct wallets, largest 11.7%
Same 800 swaps. The sum said balanced; the split said one desk selling to two hundred people.
Run it yourself — this costs 0 credits and needs no key:
git clone https://github.com/edycutjong/elephant.git && cd elephant
python3 scripts/split_tape.py \
--address 0x00000000efe302beaa2b3e6e1b18d08d69a9012a --symbol AUSD --pages 8
Standard library only. Your numbers will differ from mine, because the 800-swap window moves with the market — which is how you know it is live and not a fixture.
Three things the API does that nothing documents
1. The pagination cursor is on the envelope, not the last row. data.lastId sits on the response object, not on the final swap. Read it off the row and you silently get page one forever — which looks like a thin token rather than a bug. That cost me a day.
2. Throttling arrives as HTTP 500 as often as 429. The anonymous tier is rate-limited per IP and reports the same condition two ways. Until I handled both, a rate limit was indistinguishable from an outage, and the tool blamed tokens for infrastructure failures. It now backs off across both and exits 75 (EX_TEMPFAIL) on an exhausted quota, so a caller can tell "try again in a minute" from "this is broken".
3. Three of the four CORS headers are sent. A successful keyless response carries access-control-allow-headers, access-control-allow-methods and a ten-minute access-control-max-age — and omits access-control-allow-origin, the only one browsers gate on. The preflight succeeds, the response arrives, and the browser discards a body it already holds. It does not read like a decision to keep browsers out; it reads like a CORS config with one header missing.
Honest limitations
A run measures a window (pages x 100 swaps), not 24 hours. Deeper windows change the number.
A wallet is not an entity. The share is a floor when one entity holds many wallets, and inflated when a router pools many users. This is the real weakness and no amount of data fixes it.
The web page is a dated snapshot, because of trap 3 above. The live capability is the CLI.
It is a screen, not a verdict. "One wallet holds 62% of this side" is a fact. "You are being distributed into" is an interpretation, and the tool does not make it for you.
What I took from it
The instinct to build a new metric is strong, and the check that costs five minutes — write it as a formula and see what it reduces to in the case you care about — is one I skipped. Two fields that look independent are not independent if one of them is a rearrangement of the thing you are conditioning on.
The fix was not a better model. It was noticing that the API already published the one field that made the question answerable directly, and reading swaps instead of summaries.
Code, receipts, and a full write-up of the API findings: https://github.com/edycutjong/elephant
Read original: https://dev.to/edycutjong/the-metric-i-designed-was-the-same-number-twice-173g
← Previous
The Code Review Interview: I Hand Candidates a Broken PR
Next →
I Didn’t Have a Developer Website, So I Built One.
Related
NextAuth / Auth.js Database Schema Explained
Open Source
9
DEV Community
Filtered should never mean deleted
Open Source
7
DEV Community
Catch Bad Validation Tags at Compile Time with checkerlint
Open Source
7
Dev.to (EN Zone)
Does open source matter if you still depend on someone else’s infrastructure?
Open Source
7
Reddit r/selfhosted
Comments0
No comments yet — be the first