{"id":49675519,"url":"https://github.com/atoceano/exchange-router-service","last_synced_at":"2026-05-07T02:01:59.529Z","repository":{"id":349633897,"uuid":"1202257568","full_name":"atOCEANO/exchange-router-service","owner":"atOCEANO","description":"Drop-in container that normalizes public crypto exchange market data into a unified interface.","archived":false,"fork":false,"pushed_at":"2026-05-07T00:07:41.000Z","size":1415,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-07T01:32:38.382Z","etag":null,"topics":["docker","exchange-api","market-data-api","microservice","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/atOCEANO.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-05T20:03:54.000Z","updated_at":"2026-05-07T00:02:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/atOCEANO/exchange-router-service","commit_stats":null,"previous_names":["atoceano/exchange-router-service"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/atOCEANO/exchange-router-service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atOCEANO%2Fexchange-router-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atOCEANO%2Fexchange-router-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atOCEANO%2Fexchange-router-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atOCEANO%2Fexchange-router-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atOCEANO","download_url":"https://codeload.github.com/atOCEANO/exchange-router-service/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atOCEANO%2Fexchange-router-service/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32719572,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-07T00:29:05.620Z","status":"online","status_checked_at":"2026-05-07T02:00:07.170Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","exchange-api","market-data-api","microservice","python"],"created_at":"2026-05-07T02:01:54.712Z","updated_at":"2026-05-07T02:01:59.516Z","avatar_url":"https://github.com/atOCEANO.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1\u003eOCEΛNO \u003csmall\u003e\u003ccode\u003eexchange-router-service\u003c/code\u003e\u003c/small\u003e\u003c/h1\u003e\n\n\n\u003cdiv style=\"padding-top: 0px;\"\u003e\n  \u003ca href=\"https://www.python.org/downloads/\"\u003e\u003cimg src=\"https://img.shields.io/badge/python-3.8+-blue.svg\" alt=\"Python 3.8+\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://fastapi.tiangolo.com/\"\u003e\u003cimg src=\"https://img.shields.io/badge/FastAPI-0.123.0-05998b.svg?logo=fastapi\u0026logoColor=white\" alt=\"FastAPI\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"License: MIT\" /\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\u003csub\u003e\n  \u003cb\u003eIntroduction\u003c/b\u003e \u0026nbsp;•\u0026nbsp; \n  \u003ca href=\".Documentation/API_Reference.md\"\u003eAPI Reference\u003c/a\u003e \u0026nbsp;•\u0026nbsp; \n  \u003ca href=\".Documentation/Python_SDK.md\"\u003ePython SDK\u003c/a\u003e \u0026nbsp;•\u0026nbsp; \n  \u003ca href=\".Documentation/Exchange_Notes.md\"\u003eExchange Notes\u003c/a\u003e \u0026nbsp;•\u0026nbsp; \n  \u003ca href=\".Documentation/System_Architecture.md\"\u003eSystem Architecture\u003c/a\u003e \u0026nbsp;•\u0026nbsp; \n  \u003ca href=\".Documentation/Validator_Guide.md\"\u003eValidator Guide\u003c/a\u003e \u0026nbsp;•\u0026nbsp; \n  \u003ca href=\".Documentation/Contributor_Guide.md\"\u003eContributor Guide\u003c/a\u003e\n\u003c/sub\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n## Introduction\n\nThe `exchange-router-service` is an async API gateway for public cryptocurrency market data. It sits in front of multiple exchanges and normalizes their REST and WebSocket APIs into a single consistent schema. You write one client instead of many.\n\nThe service handles the parts that every exchange integration ends up needing: pagination for large historical pulls, request-weight throttling to prevent IP bans, and persistent connection management for WebSocket streams. Adding a new exchange is a matter of dropping in a new adapter, with no changes to the routing core.\n\n**Stateless and keyless.** The service handles public market data only. It places no orders, holds no API keys, manages no accounts, and persists nothing to disk. If you need authentication or private endpoints, this is not it.\n\n\u003cbr\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\".Documentation/imgs/204644.png\" alt=\"Exchange Router Architecture\" width=\"90%\" /\u003e\n  \u003cp style=\"margin: 0;\"\u003e\u003ci\u003eArchitecture: Client -\u003e Router -\u003e Multiple Exchanges\u003c/i\u003e\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\nClients talk to one endpoint, the router routes requests to the right exchange adapter, and the adapter normalizes the response into a schema that is identical across exchanges. REST and WebSocket both sit on the same port, and the same adapter instance serves both, so a client written against Binance spot works against Bybit linear with a single path change. The schema is uniform; some unit semantics are not. See [Exchange Notes](.Documentation/Exchange_Notes.md) before comparing values across markets; each exchange's section calls out the fields whose units differ.\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## Supported Exchanges\n\n\u003ctable style=\"width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 0.9em;\"\u003e\n  \u003cthead\u003e\n    \u003ctr style=\"border-bottom: 2px solid #30363d; background: transparent;\"\u003e\n      \u003cth style=\"padding: 12px; text-align: left;\"\u003eExchange\u003c/th\u003e\n      \u003cth style=\"padding: 12px; text-align: left;\"\u003eMarket\u003c/th\u003e\n      \u003cth style=\"padding: 12px; text-align: center;\"\u003eOHLCV\u003c/th\u003e\n      \u003cth style=\"padding: 12px; text-align: center;\"\u003eTicker\u003c/th\u003e\n      \u003cth style=\"padding: 12px; text-align: center;\"\u003eDOM\u003c/th\u003e\n      \u003cth style=\"padding: 12px; text-align: center;\"\u003eTrades\u003c/th\u003e\n      \u003cth style=\"padding: 12px; text-align: center;\"\u003eOI / FR\u003c/th\u003e\n      \u003cth style=\"padding: 12px; text-align: center;\"\u003eL/S Ratio\u003c/th\u003e\n      \u003cth style=\"padding: 12px; text-align: left;\"\u003eWebSocket Streams\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003c!-- Binance --\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd rowspan=\"3\" style=\"padding: 12px; vertical-align: middle; text-align: left; border-right: 1px solid #30363d;\"\u003e\n        \u003cimg src=\".Documentation/imgs/exchanges/binance_logo.png\" height=\"24\" /\u003e\n      \u003c/td\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eSpot\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Agg Trades, Orderbook\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eLinear\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Agg Trades, Orderbook, Mark Price, Liquidations\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 2px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eInverse\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Agg Trades, Orderbook, Mark Price, Liquidations\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003c!-- Bybit --\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd rowspan=\"3\" style=\"padding: 12px; vertical-align: middle; text-align: left; border-right: 1px solid #30363d;\"\u003e\n        \u003cimg src=\".Documentation/imgs/exchanges/bybit_logo.png\" height=\"24\" /\u003e\n      \u003c/td\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eSpot\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eLinear\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook, Liquidations\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 2px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eInverse\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook, Liquidations\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003c!-- Kraken --\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd rowspan=\"3\" style=\"padding: 12px; vertical-align: middle; text-align: left; border-right: 1px solid #30363d;\"\u003e\n        \u003cimg src=\".Documentation/imgs/exchanges/kraken_logo.png\" height=\"24\" /\u003e\n      \u003c/td\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eSpot\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eLinear\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook, Mark Price\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 2px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eInverse\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook, Mark Price\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003c!-- OKX --\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd rowspan=\"3\" style=\"padding: 12px; vertical-align: middle; text-align: left; border-right: 1px solid #30363d;\"\u003e\n        \u003cimg src=\".Documentation/imgs/exchanges/okx_logo.png\" height=\"24\" /\u003e\n      \u003c/td\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eSpot\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eLinear\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook, Mark Price, Liquidations\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 2px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eInverse\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook, Mark Price, Liquidations\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003c!-- KuCoin --\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd rowspan=\"3\" style=\"padding: 12px; vertical-align: middle; text-align: left; border-right: 1px solid #30363d;\"\u003e\n        \u003cimg src=\".Documentation/imgs/exchanges/kucoin_logo.png\" height=\"24\" /\u003e\n      \u003c/td\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eSpot\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eLinear\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook, Mark Price\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr style=\"border-bottom: 1px solid #30363d;\"\u003e\n      \u003ctd style=\"padding: 8px 12px; opacity: 0.8;\"\u003eInverse\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center;\"\u003e[x]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: center; opacity: 0.3;\"\u003e[ ]\u003c/td\u003e\n      \u003ctd style=\"padding: 8px; text-align: left; font-size: 0.85em; max-width: 250px;\"\u003eTicker, Book Ticker, Trades, Orderbook, Mark Price\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003csmall\u003e\u003ci\u003e*WebSocket streams provide real-time updates for the listed channels. See the \u003ca href=\".Documentation/API_Reference.md#websocket-streams\"\u003eAPI Reference\u003c/a\u003e for full channel specs and subscription payloads.\u003c/i\u003e\u003c/small\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## Quick Start\n\nThe service runs as a stateless Docker container.\n\n```bash\n# Clone and enter the directory\ngit clone https://github.com/atOCEANO/exchange-router-service.git\ncd exchange-router-service\n\n# Configure environment\ncp .env.example .env\n\n# Launch the service\ndocker-compose up -d --build\n\n# Verify the service is running (if this fails, check logs with: docker-compose logs -f)\ncurl http://localhost:8040/status\n\n# Fetch Binance Spot Ticker\ncurl http://localhost:8040/binance/spot/ticker/BTCUSDT\n```\n\n\u003cbr\u003e\n\nThe only knob exposed at deploy time is the host port:\n\n| Variable | Required | Default | Purpose |\n| :--- | :--- | :--- | :--- |\n| `EXCHANGE_ROUTER_SERVICE_PORT` | Yes | `8040` | Host port that Docker publishes. The container always binds `8040` internally. Change this if `8040` is already taken on the host, or if you run multiple router instances on the same machine. |\n\n\u003cbr\u003e\n\nThis variable lives in `.env` and is consumed by `docker-compose.yml` in the `ports` mapping. There is no configuration file for adapters, upstream URLs, timeouts, or rate-limit thresholds. Those are defined in code, per adapter, and changing them means editing the adapter and rebuilding the container. This is intentional, the router ships as a single immutable image and should behave identically across deployments.\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## Python SDK\n\nA thin async client over the router's REST and WebSocket interfaces. Historical and time-series methods return `pandas.DataFrame` objects indexed by datetime; point-in-time snapshots (ticker, book ticker, orderbook, mark price) return plain dicts. See [Exchange Notes](.Documentation/Exchange_Notes.md) for fields whose units vary across exchanges.\n\n```bash\npip install git+https://github.com/atOCEANO/exchange-router-service.git#subdirectory=client\n```\n\n```python\nfrom exchange_router_client import ExchangeRouterClient\n\nclient = ExchangeRouterClient(\"http://localhost:8040\")\n\ndf = await client.get_candles(\"binance\", \"spot\", \"BTCUSDT\", interval=\"1h\", limit=500)\nprint(df.tail())\n\nawait client.close()\n```\n\nFetch candles for every symbol on an exchange in one call:\n\n```python\nfrom exchange_router_client import ExchangeRouterClient\n\nclient = ExchangeRouterClient(\"http://localhost:8040\")\n\n# Discover every active Binance spot market\nall_markets = await client.get_markets(exchange=\"binance\", market_type=\"spot\")\n\nall_markets[:5]\n# ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT']\n\nf\"Total spot markets: {len(all_markets)}\"\n# 'Total spot markets: 517'\n\n# Fetch 1000 daily candles for all of them, 5 requests at a time\ndata_map = await client.fetch_multi_candles(\n    exchange=\"binance\",\n    market_type=\"spot\",\n    symbols=all_markets,\n    interval=\"1d\",\n    limit=1000,\n    max_concurrent=5,\n)\n\nf\"Fetched {len(data_map)} markets\"\n# 'Fetched 517 markets'\n\ndata_map[\"BTCUSDT\"].tail()\n#                        open      high       low     close       volume\n# datetime\n# 2025-04-21  84389.97  85300.00  83500.00  84500.01  21345.82\n# ...\n\nawait client.close()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatoceano%2Fexchange-router-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatoceano%2Fexchange-router-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatoceano%2Fexchange-router-service/lists"}