Skip to content

feat: add GET /spreads/:asset route to calculate bid-ask spreads for … - #95

Open
Johnsource-hub wants to merge 2 commits into
Miracle656:mainfrom
Johnsource-hub:spreads-endpoint
Open

feat: add GET /spreads/:asset route to calculate bid-ask spreads for …#95
Johnsource-hub wants to merge 2 commits into
Miracle656:mainfrom
Johnsource-hub:spreads-endpoint

Conversation

@Johnsource-hub

Copy link
Copy Markdown

#closes
#93

New route added and registered.
Basic error handling implemented.
Documentation and inline comments included.
Tests (to be added) cover functionality.
No new dependencies introduced.

@drips-wave

drips-wave Bot commented Jun 25, 2026

Copy link
Copy Markdown

@Johnsource-hub Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Miracle656 Miracle656 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling #93 — the route skeleton is a good start, but a few things need fixing before it can merge:

1. The bid/ask are inverted (correctness bug). Right now bid = MAX(price) and ask = MIN(price), so bid ≥ ask always, which makes spreadBps = ((ask - bid) / ask) * 10000 always negative. A bid-ask spread needs ask ≥ bid. Also, taking max/min over all historical trade prices isn't a bid-ask spread — that's a price range. A real spread needs quote/order-book data (best bid vs best ask) or, if you only have trade points, it should be defined and named accordingly. Please rework the calculation so it returns a meaningful, non-negative spread.

2. It isn't actually registered. src/index.ts wires up routes via registerRESTRoutes/registerPriceRoutes/registerCandleRoutes/etc., but there's no registerSpreadsRoutes(app) call (and no import). As-is the endpoint is unreachable — please add the import + await registerSpreadsRoutes(app) alongside the others.

3. Check the SQL column names. Unquoted assetA = $1 OR assetB = $1 folds to asseta/assetb in Postgres and won't match the actual columns. Look at how the other raw queries reference price_points and match that exactly (quote the identifier if the column is camelCase).

4. Add the test. The checklist notes tests are still to be added — please include at least one covering a normal spread and the empty/no-data case.

Once the spread math is correct, the route is registered, and there's a test, this is good to go. 👍

@Johnsource-hub

Copy link
Copy Markdown
Author

everything done

@Miracle656

Copy link
Copy Markdown
Owner

Thanks for the update — the bid/ask sign is fixed now (bid = MIN, ask = MAX, so spreadBps is non-negative). Two of my earlier points are still open, though:

  • Still not registered. There's no registerSpreadsRoutes(app) (or import) in src/index.ts, so GET /spreads/:asset is unreachable. Please wire it up alongside the other route registrations.
  • No test. Please add at least one test covering a normal spread and the empty/no-data case.

Also note it's still computing max−min over all price points, which is a price range rather than a true bid-ask spread — fine if that's the intent, but worth documenting it as such. Once it's registered and tested I'll re-review.

@Miracle656 Miracle656 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry this has sat so long. Re-reviewed against current main — the two blockers from before are both still open, and one is a correctness bug rather than a style point.

1. The route is never registered

The PR adds src/routes/spreads.ts exporting registerSpreadsRoutes, and nothing calls it. registerSpreadsRoutes appears nowhere in src/index.ts or src/api/*, so GET /spreads/:asset returns 404 — the code cannot run as shipped.

2. The query mixes price directions, so the numbers are not comparable

PricePoint stores a directional price: assetA, assetB, and one price meaning A denominated in B. This filter takes both sides:

WHERE "asset_a" = $1 OR "asset_b" = $1

So for XLM it pools rows where price ≈ 0.2 (XLM priced in USDC) with rows where price ≈ 5 (USDC priced in XLM). Those are reciprocals of each other, and MIN/MAX across the mixture produces a "spread" in the thousands of bps that reflects nothing real.

Fixing it means either constraining to one direction (asset_a = $1), or inverting the price on rows where the asset is the counter before aggregating.

3. MIN/MAX is a price range, not a bid/ask

Even with direction fixed, MIN(price) and MAX(price) over the whole table are the lowest and highest prices ever recorded — a historical range. A bid/ask spread is the best buy and best sell available right now, from the order book. Two consequences:

  • The value drifts permanently wider as history accumulates, and never recovers.
  • There is no time bound at all, so a price from months ago sets today's "bid".

If the intent is an order-book spread, it needs SDEX offer data rather than price_points. If the intent is observed price dispersion over a window, that is a legitimate and useful endpoint — but it should say so, take a window parameter, and not be called bid/ask.

Also

No test accompanies the route, and spreadBps divides by ask without guarding the bid > ask case that the direction mixing can produce.

Happy to help scope this if you would rather narrow it to the dispersion version — that is a smaller, honest endpoint and it would land.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants