Documentation
What it measures, how to run it yourself, what the record looks like on disk, and what it cannot tell you. Written so that someone who has never seen the repository can reproduce a number on this site.
Egress answers one question: what does it cost to leave a tokenized US stock position, at a stated size, right now. Entering is easy and every venue advertises it. Leaving is the half nobody prices, and on a tokenized equity it is the half that moves, because the share underneath stops trading at the New York close while the token does not.
The project is two halves that never touch. A crawler reads every listed instrument on a fixed interval and appends what it saw to an append-only record. Everything else - the phase table, the validation, the estimator, this site - reads that record afterwards. Collection never imports interpretation, so a change in how a number is read can never change what was recorded.
No credentials anywhere. Every venue endpoint used is public. The crawler runs unattended on a shared runner with nothing to leak. The only secret in the project is the reader key for the desk, and it lives in the serverless function, never in a page.
Python 3.10 or newer, tested on 3.10 through 3.13. No runtime dependencies at all - the standard library does the HTTP, the gzip and the CSV. Ruff and mypy are dev tools, not requirements.
git clone https://github.com/Ritapossible/Egress.git
cd Egress
# one pass over the whole listed universe, written to state/
python -m egress.crawl --once
# what the record holds so far
python -m egress.crawl --coverage
# what leaving 25,000 USDT of TSLA costs, from the live book
python -c "from egress import exitcost; print(exitcost.for_symbol('RTSLAUSDT', 25000))"
# rebuild this site into docs/
python -m egress.page
A continuous record is a long loop rather than a long
process: python -m egress.crawl --loop --hours 6 takes a
snapshot every 5 minutes for six hours and
exits cleanly, which is what the scheduled job on the repository runs.
Twelve modules, each with one job, listed in the order data moves through them.
| Module | What it does |
|---|---|
| config | Paths, endpoints and the few constants. No key appears here or anywhere else. |
| market | The only module that speaks HTTP. A venue that will not answer raises a typed MarketUnavailable, never a bare exception. |
| universe | Classifies every listed instrument by the venue's own symbolType field. Guessing from the symbol name is wrong for dozens of symbols. |
| sessions | Turns a timestamp into open, pre, post, overnight, weekend or holiday. Pure arithmetic, no network. |
| store | The append-only record. Rows are written first and the snapshot is entered in the manifest second, so a run killed halfway leaves data no reader will trust. |
| crawl | The loop. Reads the universe, writes a snapshot, sleeps. It never imports sessions or exitcost: collection is separate from interpretation. |
| exitcost | Walks the book from the mid and returns what an exit costs, with a floor where the book ran out and a bound where slicing is involved. |
| validate | Holds displayed size against printed volume, and refuses to compare a symbol whose own two volume feeds disagree. |
| llm | The reader. Plain English in, a ticker and a size out - never a price, never a cost. Its output is validated against a shape and clamped before anything uses it. |
| desk | The question answerer. The reader chooses a ticker and a size; every number after that is computed. One venue round trip per question, so the quote, the plan and the max-exit search describe one same book. |
| facts | Reads the whole record and assembles the fact set every page renders from. Nothing here fetches; it only interprets what the crawler stored. |
| page | Generates this site. Every measured number is interpolated from facts, never typed, so a figure cannot go stale without the record going stale with it. |
The reader compiles, the code computes. The language model is only ever asked for a symbol and a notional. It is never asked for a price, a spread or a cost, and it cannot select a ticker that is not listed on the venue right now - resolution is a lookup against the live instrument list, so an invented ticker fails loudly instead of being priced.
The record lives in state/. Each snapshot is one
gzipped CSV under state/snapshots/, and
state/manifest.jsonl holds one JSON line per completed
snapshot. A snapshot absent from the manifest is skipped on read even if its
file exists.
| Column | Meaning |
|---|---|
| snap_ts | Epoch milliseconds the snapshot was started, stamped once for every row in it. |
| symbol | Venue symbol, for example RTSLAUSDT or BTCUSDT. |
| bid | Best bid at the moment of the read. |
| ask | Best ask at the moment of the read. |
| bid_size | Size resting at the best bid, in base units. |
| ask_size | Size resting at the best ask, in base units. |
| last | Last traded price reported by the ticker feed. |
| turnover24h | Rolling 24h quote turnover for the symbol. |
| venue_ts | The venue's own timestamp for the quote, which is what phase is decided from. |
Current record: 8,581 snapshots, 26,834,943 rows, 142 gaps in coverage, across 3,602 listed instruments (3,019 tokenized stocks, 581 crypto pairs, 2 metals).
Silence is not zero. A symbol missing from a snapshot means the venue did not report it, not that its spread was nothing. Missing rows are absent from every median rather than counted as a value.
What "3,019 tokenized stocks" counts. Rows with
symbolType == "stock" from
GET /api/v3/market/instruments?category=SPOT — the venue's
own classification, not ours. All 3,019 are status: online,
all are USDT-quoted, and there are 3,019 distinct base coins, so no
symbol is counted twice.
Why another count will disagree. The v2 endpoint most
people reach for, /api/v2/spot/public/symbols, returns the same
2,710 rows but carries no symbolType field at all. Without
it the only way to split the list is the ticker prefix, and that is wrong in
both directions: 25 ordinary crypto pairs start with R
(RAYUSDT, ROSEUSDT, RLCUSDT —
iExec, not Royal Caribbean) and two tokenized stocks carry no prefix
(PRESPCXUSDT, PREOPAIUSDT). The prefix heuristic
returns 2,150. Ours returns 3,019 because it reads the field.
Two are wired, and both are on the Ask path.
bitget-mcp-server answers equity_price_quote behind every
desk answer — it is where the basis against the listed share comes
from, and without it this site could price an exit but not say what the
position was worth. bitget-signal is asked
news_feed/latest for the name in the question, on a six-second
fuse, and every answer carries what it said. It cannot move the
number. The cost comes from the venue's own book; the news block is
built after that number is already fixed, and a test asserts the whole
answer is identical whether the service replies, returns nothing, or dies.
Today it returns nothing, and the desk prints that rather than a
briefing assembled out of an empty feed.
bitget-signal, probed 2026-10-22T10:44:48+00:00. 3 of its catalog calls answer; 0 of its live-data calls return anything. The aggregator lists 44 feeds and reports zero articles from all of them, and crypto_price fails with the service’s own ConnectTimeout — so the Skill is running and its outbound fetches are not.
It would not help here even if it worked. Of those 44 feeds, the ones that could carry a US stock story at all are: cnbc. The rest are crypto, tech and general news. This cannot explain why a tokenized stock is wide; it is a control, not an explanation, and it is on this page as a measured result rather than a logo.
| Call | Kind | Result |
|---|---|---|
news_feed / sources | catalog | answered — 44 feeds |
news_feed / latest | live | answered — 0 articles across 44 feeds |
macro_indicators / series_list | catalog | answered — 18 entries |
cross_asset / assets_list | catalog | answered — 15 entries |
crypto_price / price | live | no answer Error executing tool crypto_price: ConnectTimeout('') |
One endpoint, used by the desk on the front page. It exists because a reader key cannot live in a browser.
POST /api/ask
Content-Type: application/json
"q": "What does leaving 40,000 USDT of TSLA cost?"
The response states a symbol, the compiled size, the quote walked off the live book, a slicing bound, and a plain-English reading. Every failure is a stated answer rather than an exception: an unreadable question, a ticker that is not listed, or a venue that will not answer each come back as a sentence saying so. Bodies over 4 KB are refused.
Estimated, never observed. Every figure the endpoint
returns is marked estimated. It is what the displayed book says
an exit would cost, not a fill anyone received.
| Command | What it does |
|---|---|
| crawl --once | One snapshot of the whole listed universe. |
| crawl --loop --hours N | Snapshot every five minutes for N hours, then exit. |
| crawl --interval S | Override the interval, in seconds. |
| crawl --universe | Refresh the instrument list and print the counts by type. |
| crawl --coverage | What the record holds, and where the gaps are. |
| python -m egress.page | Rebuild every page in docs/ from the record. |
Stated here rather than buried, because a research tool that hides its limits is a marketing tool.
| Term | Meaning here |
|---|---|
| bp | Basis point, one hundredth of a percent. 100 bp is 1%. |
| mid | Midpoint of the best bid and best ask. The reference every cost on this site is measured from. |
| touch | The top of the book - the best bid and best ask and the size resting on each. |
| floor | A cost marked >: the displayed book ran out before the position did, so the true cost is at least this. |
| phase | open, pre, post, overnight, weekend or holiday, decided from the venue's own timestamp against US equity market hours. Pre is 04:00-09:30 ET and post is 16:00-20:00 ET: the reference venue quotes in both, thinly, so they are not the same regime as the genuinely shut hours. |
| listed 30d+ | A name listed on the venue for at least thirty days, by its own launchTime. The headline medians cover these; recently listed names are reported beside them, because a median over whatever is listed today moves with the venue's listing calendar rather than with its liquidity. |
| usual for this name | A quote compared against that same symbol's own recent quotes in the same phase, never against a cost or against other symbols. A median spread is not a cost, so the two are never divided by one another. |
| control | The crypto pairs. Same venue, same engine, same fees, no reason to care whether New York is open. |
| tokenized stock | An instrument the
venue reports with symbolType stock, tracking a US listed
share. |