Showing preview only (9,194K chars total). Download the full file or copy to clipboard to get everything.
Repository: tardis-dev/tardis-node
Branch: master
Commit: cd69e6ecab40
Files: 141
Total size: 8.7 MB
Directory structure:
gitextract_eif38n1w/
├── .github/
│ └── workflows/
│ ├── ci.yaml
│ ├── npm_audit.yaml
│ └── publish.yaml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── ADD_NEW_EXCHANGE.md
├── AGENTS.md
├── ARCHITECTURE.md
├── CLAUDE.md
├── LICENSE
├── README.md
├── example.js
├── package.json
├── src/
│ ├── apikeyaccessinfo.ts
│ ├── binarysplit.ts
│ ├── clearcache.ts
│ ├── combine.ts
│ ├── computable/
│ │ ├── booksnapshot.ts
│ │ ├── computable.ts
│ │ ├── index.ts
│ │ └── tradebar.ts
│ ├── consts.ts
│ ├── debug.ts
│ ├── downloaddatasets.ts
│ ├── exchangedetails.ts
│ ├── filter.ts
│ ├── handy.ts
│ ├── index.ts
│ ├── instrumentinfo.ts
│ ├── mappers/
│ │ ├── ascendex.ts
│ │ ├── binance.ts
│ │ ├── binancedex.ts
│ │ ├── binanceeuropeanoptions.ts
│ │ ├── bitfinex.ts
│ │ ├── bitflyer.ts
│ │ ├── bitget.ts
│ │ ├── bitmex.ts
│ │ ├── bitnomial.ts
│ │ ├── bitstamp.ts
│ │ ├── blockchaincom.ts
│ │ ├── bybit.ts
│ │ ├── bybitspot.ts
│ │ ├── coinbase.ts
│ │ ├── coinbaseinternational.ts
│ │ ├── coinflex.ts
│ │ ├── cryptocom.ts
│ │ ├── cryptofacilities.ts
│ │ ├── delta.ts
│ │ ├── deribit.ts
│ │ ├── dydx.ts
│ │ ├── dydxv4.ts
│ │ ├── ftx.ts
│ │ ├── gateio.ts
│ │ ├── gateiofutures.ts
│ │ ├── gemini.ts
│ │ ├── hitbtc.ts
│ │ ├── huobi.ts
│ │ ├── hyperliquid.ts
│ │ ├── index.ts
│ │ ├── kraken.ts
│ │ ├── kucoin.ts
│ │ ├── kucoinfutures.ts
│ │ ├── mapper.ts
│ │ ├── okex.ts
│ │ ├── okexspreads.ts
│ │ ├── phemex.ts
│ │ ├── poloniex.ts
│ │ ├── serum.ts
│ │ ├── upbit.ts
│ │ └── woox.ts
│ ├── options.ts
│ ├── orderbook.ts
│ ├── realtimefeeds/
│ │ ├── ascendex.ts
│ │ ├── binance.ts
│ │ ├── binancedex.ts
│ │ ├── binanceeuropeanoptions.ts
│ │ ├── bitfinex.ts
│ │ ├── bitflyer.ts
│ │ ├── bitget.ts
│ │ ├── bitmex.ts
│ │ ├── bitnomial.ts
│ │ ├── bitstamp.ts
│ │ ├── blockchaincom.ts
│ │ ├── bybit.ts
│ │ ├── coinbase.ts
│ │ ├── coinbaseinternational.ts
│ │ ├── coinflex.ts
│ │ ├── cryptocom.ts
│ │ ├── cryptofacilities.ts
│ │ ├── delta.ts
│ │ ├── deribit.ts
│ │ ├── dydx.ts
│ │ ├── dydx_v4.ts
│ │ ├── ftx.ts
│ │ ├── gateio.ts
│ │ ├── gateiofutures.ts
│ │ ├── gemini.ts
│ │ ├── hitbtc.ts
│ │ ├── huobi.ts
│ │ ├── hyperliquid.ts
│ │ ├── index.ts
│ │ ├── kraken.ts
│ │ ├── kucoin.ts
│ │ ├── kucoinfutures.ts
│ │ ├── mango.ts
│ │ ├── okex.ts
│ │ ├── okexspreads.ts
│ │ ├── phemex.ts
│ │ ├── poloniex.ts
│ │ ├── realtimefeed.ts
│ │ ├── serum.ts
│ │ ├── staratlas.ts
│ │ ├── upbit.ts
│ │ └── woox.ts
│ ├── replay.ts
│ ├── stream.ts
│ ├── types.ts
│ └── worker.ts
├── test/
│ ├── __snapshots__/
│ │ ├── combine.test.ts.snap
│ │ ├── compute.test.ts.snap
│ │ ├── mappers.test.ts.snap
│ │ └── replay.test.ts.snap
│ ├── binance-futures-split.live.test.ts
│ ├── binance-openinterest.live.test.ts
│ ├── binarysplit.test.ts
│ ├── combine.test.ts
│ ├── compute.test.ts
│ ├── downloaddatasets.test.ts
│ ├── gate-io-futures-decimal.live.test.ts
│ ├── httpclient.test.ts
│ ├── live.ts
│ ├── mappers.test.ts
│ ├── orderbook.test.ts
│ ├── package-exports.test.ts
│ ├── replay.test.ts
│ ├── setup.js
│ ├── stream.test.ts
│ └── tsconfig.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yaml
================================================
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
ci:
name: CI
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['25.8.2']
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Use Node.js v${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies And Compile TS
run: npm ci --ignore-scripts
- name: Verify Registry Signatures
run: npm audit signatures
- name: Audit Production Dependencies
run: npm audit --omit=dev --audit-level=critical
- name: Check Code Format
run: npm run check-format
- name: Run Tests
run: npm run test
================================================
FILE: .github/workflows/npm_audit.yaml
================================================
name: Full NPM Audit
on:
schedule:
- cron: '25 3 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
audit:
name: Full NPM Audit Report
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Use Node.js v25.8.2
uses: actions/setup-node@v6
with:
node-version: 25.8.2
- name: Generate Full Audit Report
id: audit
run: |
set +e
npm audit --package-lock-only --json > npm-audit.json
exit_code=$?
set -e
if [ ! -f npm-audit.json ]; then
echo '{}' > npm-audit.json
fi
echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"
- name: Upload Audit Report
uses: actions/upload-artifact@v4
with:
name: npm-audit-report
path: npm-audit.json
- name: Summarize Audit Report
env:
AUDIT_EXIT_CODE: ${{ steps.audit.outputs.exit_code }}
run: |
node --input-type=module <<'EOF'
import fs from 'node:fs';
const report = JSON.parse(fs.readFileSync('npm-audit.json', 'utf8'));
const vulnerabilities = report.metadata?.vulnerabilities ?? {};
const lines = [
`Full npm audit exit code: ${process.env.AUDIT_EXIT_CODE}`,
`info: ${vulnerabilities.info ?? 0}`,
`low: ${vulnerabilities.low ?? 0}`,
`moderate: ${vulnerabilities.moderate ?? 0}`,
`high: ${vulnerabilities.high ?? 0}`,
`critical: ${vulnerabilities.critical ?? 0}`,
`total: ${vulnerabilities.total ?? 0}`,
'',
'Download the npm-audit-report artifact for the full JSON report.'
];
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${lines.join('\n')}\n`);
EOF
================================================
FILE: .github/workflows/publish.yaml
================================================
name: Publish New Release To NPM
on:
release:
# This specifies that the build will be triggered when we publish a release
types: [published]
permissions:
id-token: write
contents: write
jobs:
publish:
name: Publish New Release To NPM
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.event.release.target_commitish }}
- name: Use Node.js v25.8.2
uses: actions/setup-node@v6
with:
node-version: 25.8.2
registry-url: https://registry.npmjs.org/
- name: Install Dependencies And Compile TS
run: npm ci --ignore-scripts
- name: Verify Registry Signatures
run: npm audit signatures
- name: Audit Production Dependencies
run: npm audit --omit=dev --audit-level=critical
- name: Configure Git
run: |
git config --global user.name "GitHub Release Bot"
git config --global user.email "deploy@tardis.dev"
- name: Update package version
run: npm version ${{ github.event.release.tag_name }}
- name: Run Tests
run: npm run test
- name: Publish Package
run: npm publish
- name: Push Version Changes To GitHub
run: git push
================================================
FILE: .gitignore
================================================
node_modules
/dist
/*.log
.tardis-cache
*.tsbuildinfo
cache
bench/
.DS_Store
================================================
FILE: .npmrc
================================================
min-release-age=1
allow-git=none
================================================
FILE: .prettierignore
================================================
package.json
package-lock.json
yarn.lock
dist
================================================
FILE: .prettierrc
================================================
{
"printWidth": 140,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"endOfLine": "lf"
}
================================================
FILE: ADD_NEW_EXCHANGE.md
================================================
# Adding a New Exchange
## Overview
Adding an exchange to tardis-node requires three things: mappers (transform raw exchange messages into normalized types), a real-time feed (WebSocket connection), and constant definitions.
## Workflow
### 1. Add exchange constants
In `src/consts.ts`:
- Add exchange ID to the exchanges array
- Add channel info (list of available channels for the exchange)
### 2. Create mappers
Create `src/mappers/{exchange}.ts`. Each mapper class implements the Mapper interface — look at existing mapper implementations to find an exchange with a similar message format.
Mappers to implement depend on what the exchange provides: trades, book changes, tickers, derivative tickers, liquidations, book tickers, etc.
Register mapper factory in `src/mappers/index.ts`.
### 3. Create real-time feed
Create `src/realtimefeeds/{exchange}.ts`. Extend `RealTimeFeedBase` with:
- WebSocket URL
- Subscription message format
- Any exchange-specific hooks (decompression, heartbeat handling, error filtering)
Register in `src/realtimefeeds/index.ts`.
### 4. Test
Run tests and validation — see AGENTS.md for the full checklist.
## Decision Points
- **Date-based mapper versioning** — If the exchange changed its API format at some point, you may need different mapper implementations for different time periods. Look at existing examples in `src/mappers/index.ts` for the pattern.
- **Multi-connection feeds** — Some exchanges need multiple WebSocket connections. The base class supports this via `MultiConnectionRealTimeFeedBase`.
- **Decompression** — Some exchanges compress WebSocket messages. Override the decompress hook if needed.
- **Filter optimization** — The base class has `optimizeFilters()` for normalizing subscription filters. Override if the exchange needs special handling.
================================================
FILE: AGENTS.md
================================================
# tardis-node
Public npm package (`tardis-dev`). Provides async iterator API for historical replay and real-time streaming of cryptocurrency market data, with exchange-specific mappers for normalization.
## Build & Test
```bash
npm run build # tsc
npm test # build + jest
npm run check-format # prettier check
```
## Editing Rules
- Keep backward compatibility for public API signatures — this is a published npm package
- Maintain cache key stability (filters are normalized/sorted intentionally)
- Preserve memory-safe streaming behavior (avoid large in-memory buffering)
- Exchange additions must update realtime feed + mapper tables consistently
- **Format after every edit** — run `npx prettier --write` on modified files after each change
## Validation
- `npm run build && npm test`
- `npm run check-format`
## Operational Docs
- [ARCHITECTURE.md](ARCHITECTURE.md) — async iterators, replay pipeline, mapper system, order book
- [ADD_NEW_EXCHANGE.md](ADD_NEW_EXCHANGE.md) — add mappers and realtime feed for a new exchange
## Publishing
Published via GitHub Actions (`publish.yaml`). Do not publish manually unless explicitly requested.
## Keeping Docs Current
When you change code, check if any docs in this repo become stale as a result — if so, update them. When following a workflow doc, if the steps don't match reality, fix the doc so the next run is better.
================================================
FILE: ARCHITECTURE.md
================================================
# Architecture
tardis-node provides a unified async iterator API for consuming cryptocurrency market data. Two primary modes: **replay** (historical) and **stream** (real-time), both sharing the same normalized data types and mapper infrastructure.
## Core Design
Every data source produces an `AsyncIterableIterator`. This applies uniformly to raw replay, raw streaming, normalized replay, normalized streaming, combined streams, and computed/derived data.
## Replay Pipeline
```
Main Thread Worker Thread
│ │
│── Start replay ──→ │
│ Fetch data slice from API
│ Cache to disk (.gz file)
│ ←── message (sliceKey, path) ── │
│ Fetch next slice...
│ │
Read cached file from disk │
Decompress (gunzip) │
Split by newlines │
Parse JSON messages │
Yield {localTimestamp, message} │
```
Worker thread pre-fetches and caches slices while the main thread processes the current one. This keeps I/O and CPU pipelined.
## Real-time Streaming
`RealTimeFeedBase` manages WebSocket connections to exchanges. Handles connection lifecycle (connect, subscribe, validate, reconnect on failure). Exchange-specific feeds extend the base class with subscription formats and message handling.
## Mapper System
Mappers transform raw exchange messages into normalized types (trades, book changes, tickers, liquidations, etc.). Each exchange has mapper classes registered in `src/mappers/index.ts`.
Some exchanges have date-based mapper versioning — different mapper implementations for different time periods when the exchange changed its API format.
## Key Abstractions
- **`combine()`** — Merges multiple async iterables into one, ordered by timestamp. Enables cross-exchange data feeds.
- **`compute()`** — Wraps an async iterable and produces derived data (book snapshots, trade bars) via computables.
- **`OrderBook`** — Full limit order book reconstruction from incremental updates, using a Red-Black Tree for efficient price level management.
## Configuration
Exchange definitions and channel info in `src/consts.ts`. Mapper and feed registrations in their respective `index.ts` files.
================================================
FILE: CLAUDE.md
================================================
@AGENTS.md
================================================
FILE: LICENSE
================================================
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: README.md
================================================
# tardis-dev
[](https://www.npmjs.org/package/tardis-dev)
<br/>
Node.js `tardis-dev` library provides convenient access to tick-level real-time and historical cryptocurrency market data both in exchange native and normalized formats. Instead of callbacks it relies on [async iteration (for await ...of)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) enabling composability features like [seamless switching between real-time data streaming and historical data replay](https://docs.tardis.dev/node-client/normalization#seamless-switching-between-real-time-streaming-and-historical-market-data-replay) or [computing derived data locally](https://docs.tardis.dev/node-client/normalization#computing-derived-data-locally).
<br/>
```javascript
import { replayNormalized, normalizeTrades, normalizeBookChanges } from 'tardis-dev'
const messages = replayNormalized(
{
exchange: 'binance',
symbols: ['btcusdt'],
from: '2024-03-01',
to: '2024-03-02'
},
normalizeTrades,
normalizeBookChanges
)
for await (const message of messages) {
console.log(message)
}
```
<br/>
## Features
- historical tick-level [market data replay](https://docs.tardis.dev/node-client/replaying-historical-data) backed by [tardis.dev HTTP API](https://docs.tardis.dev/api/http-api-reference#data-feeds-exchange) — includes full order book depth snapshots plus incremental updates, tick-by-tick trades, historical open interest, funding, index, mark prices, liquidations and more
<br/>
- consolidated [real-time data streaming API](https://docs.tardis.dev/node-client/streaming-real-time-data) connecting directly to exchanges' public WebSocket APIs
<br/>
- support for both [exchange-native and normalized market data](https://docs.tardis.dev/faq/data) formats (unified format for accessing market data across all supported exchanges — normalized trades, order book and ticker data)
<br/>
- [seamless switching between real-time streaming and historical market data replay](https://docs.tardis.dev/node-client/normalization#seamless-switching-between-real-time-streaming-and-historical-market-data-replay) thanks to [`async iterables`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) providing unified way of consuming data messages
<br/>
- transparent historical local data caching \(cached data is stored on disk per slice in compressed format and decompressed on demand when reading the data\)
<br/>
- support for many cryptocurrency exchanges — see [docs.tardis.dev](https://docs.tardis.dev) for the full list
<br/>
- automatic closed connections and stale connections reconnection logic for real-time streams
<br/>
- [combining multiple exchanges feeds into single one](https://docs.tardis.dev/node-client/normalization#combining-data-streams) via [`combine`](https://docs.tardis.dev/node-client/normalization#combining-data-streams) helper function — synchronized historical market data replay and consolidated real-time data streaming from multiple exchanges
<br/>
- [computing derived data locally](https://docs.tardis.dev/node-client/normalization#computing-derived-data-locally) like order book imbalance, custom trade bars, book snapshots and more via [`compute`](https://docs.tardis.dev/node-client/normalization#computing-derived-data-locally) helper function and `computables`, e.g., volume based bars, top 20 levels order book snapshots taken every 10 ms etc.
<br/>
- [full limit order book reconstruction](https://docs.tardis.dev/node-client/normalization#limit-order-book-reconstruction) both for real-time and historical data via `OrderBook` object
<br/>
- fast and lightweight architecture — low memory footprint and no heavy in-memory buffering
<br/>
- [extensible mapping logic](https://docs.tardis.dev/node-client/normalization#modifying-built-in-and-adding-custom-normalizers) that allows adjusting normalized formats for specific needs
<br/>
- [built-in TypeScript support](https://docs.tardis.dev/node-client/quickstart#es-modules-and-typescript)
<br/>
<br/>
<br/>
## Installation
Requires Node.js v24+ installed.
```bash
npm install tardis-dev --save
```
`tardis-dev` is ESM-only. Examples in this README use ES modules and top-level await. Save snippets as `.mjs` or set `"type": "module"` in your `package.json`.
<br/>
<br/>
## Documentation
### [See official docs](https://docs.tardis.dev/node-client/quickstart).
<br/>
<br/>
## Examples
### Real-time spread across multiple exchanges
Example showing how to quickly display real-time spread and best bid/ask info across multiple exchanges at once. It can be easily adapted to do the same for historical data \(`replayNormalized` instead of `streamNormalized`).
```javascript
import { streamNormalized, normalizeBookChanges, combine, compute, computeBookSnapshots } from 'tardis-dev'
const exchangesToStream = [
{ exchange: 'bitmex', symbols: ['XBTUSD'] },
{ exchange: 'deribit', symbols: ['BTC-PERPETUAL'] },
{ exchange: 'cryptofacilities', symbols: ['PI_XBTUSD'] }
]
// for each specified exchange call streamNormalized for it
// so we have multiple real-time streams for all specified exchanges
const realTimeStreams = exchangesToStream.map((e) => {
return streamNormalized(e, normalizeBookChanges)
})
// combine all real-time message streams into one
const messages = combine(...realTimeStreams)
// create book snapshots with depth1 that are produced
// every time best bid/ask info is changed
// effectively computing real-time quotes
const realTimeQuoteComputable = computeBookSnapshots({
depth: 1,
interval: 0,
name: 'realtime_quote'
})
// compute real-time quotes for combines real-time messages
const messagesWithQuotes = compute(messages, realTimeQuoteComputable)
const spreads = {}
// print spreads info every 100ms
setInterval(() => {
console.clear()
console.log(spreads)
}, 100)
// update spreads info real-time
for await (const message of messagesWithQuotes) {
if (message.type === 'book_snapshot') {
spreads[message.exchange] = {
spread: message.asks[0].price - message.bids[0].price,
bestBid: message.bids[0],
bestAsk: message.asks[0]
}
}
}
```
<br/>
### Seamless switching between real-time streaming and historical market data replay
Example showing simple pattern of providing `async iterable` of market data messages to the function that can process them no matter if it's is real-time or historical market data. That effectively enables having the same 'data pipeline' for backtesting and live trading.
```javascript
import { replayNormalized, streamNormalized, normalizeTrades, compute, computeTradeBars } from 'tardis-dev'
const historicalMessages = replayNormalized(
{
exchange: 'binance',
symbols: ['btcusdt'],
from: '2024-03-01',
to: '2024-03-02'
},
normalizeTrades
)
const realTimeMessages = streamNormalized(
{
exchange: 'binance',
symbols: ['btcusdt']
},
normalizeTrades
)
async function produceVolumeBasedTradeBars(messages) {
const withVolumeTradeBars = compute(
messages,
computeTradeBars({
kind: 'volume',
interval: 1 // aggregate by 1 BTC traded volume
})
)
for await (const message of withVolumeTradeBars) {
if (message.type === 'trade_bar') {
console.log(message.name, message)
}
}
}
await produceVolumeBasedTradeBars(historicalMessages)
// or for real time data
// await produceVolumeBasedTradeBars(realTimeMessages)
```
<br/>
### Stream real-time market data in exchange native data format
```javascript
import { stream } from 'tardis-dev'
const messages = stream({
exchange: 'binance',
filters: [
{ channel: 'trade', symbols: ['btcusdt'] },
{ channel: 'depth', symbols: ['btcusdt'] }
]
})
for await (const { localTimestamp, message } of messages) {
console.log(localTimestamp, message)
}
```
<br/>
### Replay historical market data in exchange native data format
```javascript
import { replay } from 'tardis-dev'
const messages = replay({
exchange: 'binance',
filters: [
{ channel: 'trade', symbols: ['btcusdt'] },
{ channel: 'depth', symbols: ['btcusdt'] }
],
from: '2024-03-01',
to: '2024-03-02'
})
for await (const { localTimestamp, message } of messages) {
console.log(localTimestamp, message)
}
```
<br/>
<br/>
## See the [tardis-dev docs](https://docs.tardis.dev/node-client/quickstart) for more examples.
================================================
FILE: example.js
================================================
import { replayNormalized, streamNormalized, normalizeTrades, compute, computeTradeBars } from 'tardis-dev'
const historicalMessages = replayNormalized(
{
exchange: 'binance',
symbols: ['btcusdt'],
from: '2024-03-01',
to: '2024-03-02'
},
normalizeTrades
)
const realTimeMessages = streamNormalized(
{
exchange: 'binance',
symbols: ['btcusdt']
},
normalizeTrades
)
async function produceVolumeBasedTradeBars(messages) {
// aggregate by 1 BTC traded volume
const withVolumeTradeBars = compute(messages, computeTradeBars({ kind: 'volume', interval: 1 }))
for await (const message of withVolumeTradeBars) {
if (message.type === 'trade_bar') {
console.log(message.name, message)
}
}
}
await produceVolumeBasedTradeBars(historicalMessages)
// or for real time data
// await produceVolumeBasedTradeBars(realTimeMessages)
================================================
FILE: package.json
================================================
{
"name": "tardis-dev",
"version": "16.0.0",
"engines": {
"node": ">=25"
},
"devEngines": {
"runtime": {
"name": "node",
"version": ">=25"
},
"packageManager": {
"name": "npm",
"version": ">=11.11.1"
}
},
"description": "Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js",
"main": "dist/index.js",
"source": "src/index.ts",
"types": "dist/index.d.ts",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"repository": "tardis-dev/tardis-node",
"homepage": "https://docs.tardis.dev/api/node-js",
"scripts": {
"build": "tsc -p tsconfig.json",
"precommit": "lint-staged",
"test": "npm run build && node --experimental-vm-modules ./node_modules/jest/bin/jest.js --forceExit --runInBand",
"prepare": "npm run build",
"format": "prettier --write .",
"check-format": "prettier --check ."
},
"files": [
"src",
"dist",
"example.js"
],
"keywords": [
"cryptocurrency data feed",
"market data",
"api client",
"crypto markets data replay",
"historical data",
"real-time cryptocurrency market data feed",
"historical cryptocurrency prices",
"cryptocurrency api",
"real-time normalized WebSocket cryptocurrency markets data",
"normalized cryptocurrency market data API",
"order book reconstruction",
"market data normalization",
"cryptocurrency api",
"cryptocurrency",
"orderbook",
"exchange",
"websocket",
"realtime",
"bitmex",
"binance",
"trading",
"high granularity order book data"
],
"license": "MPL-2.0",
"dependencies": {
"bintrees": "^1.0.2",
"debug": "^4.3.3",
"follow-redirects": "^1.15.9",
"https-proxy-agent": "^8.0.0",
"p-map": "^7.0.4",
"socks-proxy-agent": "^9.0.0",
"ws": "^8.18.3"
},
"devDependencies": {
"@types/bintrees": "^1.0.6",
"@types/debug": "^4.1.7",
"@types/follow-redirects": "^1.14.4",
"@types/jest": "^29.0.0",
"@types/node": "^25.3.5",
"@types/ws": "^8.18.1",
"jest": "^29.0.0",
"lint-staged": "^12.1.3",
"prettier": "^2.5.1",
"ts-jest": "^29.4.0",
"typescript": "^5.9.3"
},
"lint-staged": {
"*.{ts}": [
"prettier --write",
"git add"
]
},
"jest": {
"extensionsToTreatAsEsm": [
".ts"
],
"moduleNameMapper": {
"^(\\.{1,2}/.*)\\.js$": "$1"
},
"transform": {
"\\.(ts|tsx)?$": [
"ts-jest",
{
"useESM": true,
"tsconfig": "./test/tsconfig.json"
}
]
},
"testEnvironment": "node",
"setupFiles": [
"./test/setup.js"
]
},
"runkitExampleFilename": "example.js"
}
================================================
FILE: src/apikeyaccessinfo.ts
================================================
import { getJSON } from './handy.ts'
import { getOptions } from './options.ts'
import { Exchange } from './types.ts'
export async function getApiKeyAccessInfo(apiKey?: string) {
const options = getOptions()
const apiKeyToCheck = apiKey || options.apiKey
const { data } = await getJSON<ApiKeyAccessInfo>(`${options.endpoint}/api-key-info`, {
headers: {
Authorization: `Bearer ${apiKeyToCheck}`
}
})
return data
}
export type ApiKeyAccessInfo = {
exchange: Exchange
accessType: string
from: string
to: string
symbols: string[]
dataPlan: string
}[]
================================================
FILE: src/binarysplit.ts
================================================
import { Transform } from 'stream'
import type { TransformCallback } from 'stream'
// Inspired by https://github.com/maxogden/binary-split/blob/master/index.js
export class BinarySplitStream extends Transform {
private readonly _NEW_LINE_BYTE: number
private _buffered?: Buffer
constructor() {
super({
readableObjectMode: true
})
this._NEW_LINE_BYTE = 10
this._buffered = undefined
}
_transform(chunk: Buffer, _: string, callback: TransformCallback) {
let chunkStart = 0
if (this._buffered !== undefined) {
const firstNewLineIndex = chunk.indexOf(this._NEW_LINE_BYTE)
if (firstNewLineIndex === -1) {
this._buffered = Buffer.concat([this._buffered, chunk])
callback()
return
}
this.push(Buffer.concat([this._buffered, chunk.subarray(0, firstNewLineIndex)]))
this._buffered = undefined
chunkStart = firstNewLineIndex + 1
}
let offset = chunkStart
let lineStart = chunkStart
while (true) {
const newLineIndex = chunk.indexOf(this._NEW_LINE_BYTE, offset)
if (newLineIndex === -1) {
break
}
this.push(chunk.subarray(lineStart, newLineIndex))
offset = newLineIndex + 1
lineStart = offset
}
this._buffered = lineStart < chunk.length ? chunk.subarray(lineStart) : undefined
callback()
}
}
================================================
FILE: src/clearcache.ts
================================================
import { rmSync } from 'node:fs'
import { rm } from 'node:fs/promises'
import { debug } from './debug.ts'
import { getOptions } from './options.ts'
import { Filter, Exchange } from './types.ts'
import { sha256, optimizeFilters, doubleDigit } from './handy.ts'
export async function clearCache(exchange?: Exchange, filters?: Filter<any>[], year?: number, month?: number, day?: number) {
try {
const dirToRemove = getDirToRemove(exchange, filters, year, month, day)
debug('clearing cache dir: %s', dirToRemove)
await rm(dirToRemove, { force: true, recursive: true })
debug('cleared cache dir: %s', dirToRemove)
} catch (e) {
debug('clearing cache dir error: %o', e)
}
}
export function clearCacheSync(exchange?: Exchange, filters?: Filter<any>[], year?: number, month?: number, day?: number) {
try {
const dirToRemove = getDirToRemove(exchange, filters, year, month, day)
debug('clearing cache (sync) dir: %s', dirToRemove)
rmSync(dirToRemove, { force: true, recursive: true })
debug('cleared cache(sync) dir: %s', dirToRemove)
} catch (e) {
debug('clearing cache (sync) dir error: %o', e)
}
}
function getDirToRemove(exchange?: Exchange, filters?: Filter<any>[], year?: number, month?: number, day?: number) {
const options = getOptions()
let dirToRemove = `${options.cacheDir}/feeds`
if (exchange !== undefined) {
dirToRemove += `/${exchange}`
}
if (filters !== undefined) {
dirToRemove += `/${sha256(optimizeFilters(filters))}`
}
if (year !== undefined) {
dirToRemove += `/${year}`
}
if (month !== undefined) {
dirToRemove += `/${doubleDigit(month)}`
}
if (day !== undefined) {
dirToRemove += `/${doubleDigit(day)}`
}
return dirToRemove
}
================================================
FILE: src/combine.ts
================================================
import { PassThrough } from 'stream'
import { once } from 'events'
type NextMessageResultWithIndex = {
index: number
result: IteratorResult<Combinable, Combinable>
}
type Combinable = { localTimestamp: Date }
const DATE_MAX = new Date(8640000000000000)
type OffsetMS = number | ((message: Combinable) => number)
async function nextWithIndex(
iterator: AsyncIterableIterator<Combinable> | { stream: AsyncIterableIterator<Combinable>; offsetMS: OffsetMS },
index: number
): Promise<NextMessageResultWithIndex> {
if ('offsetMS' in iterator) {
const result = await iterator.stream.next()
if (!result.done) {
const offsetMS = typeof iterator.offsetMS === 'function' ? iterator.offsetMS(result.value) : iterator.offsetMS
if (offsetMS !== 0) {
result.value.localTimestamp.setUTCMilliseconds(result.value.localTimestamp.getUTCMilliseconds() + offsetMS)
}
}
return {
result,
index
}
} else {
const result = await iterator.next()
return {
result,
index
}
}
}
function findOldestResult(oldest: NextMessageResultWithIndex, current: NextMessageResultWithIndex) {
if (oldest.result.done) {
return oldest
}
if (current.result.done) {
return current
}
const currentTimestamp = current.result.value.localTimestamp.valueOf()
const oldestTimestamp = oldest.result.value.localTimestamp.valueOf()
if (currentTimestamp < oldestTimestamp) {
return current
}
if (currentTimestamp === oldestTimestamp) {
const currentTimestampMicroSeconds = current.result.value.localTimestamp.μs || 0
const oldestTimestampMicroSeconds = oldest.result.value.localTimestamp.μs || 0
if (currentTimestampMicroSeconds < oldestTimestampMicroSeconds) {
return current
}
}
return oldest
}
// combines multiple iterators from for example multiple exchanges
// works both for real-time and historical data
export async function* combine<
T extends AsyncIterableIterator<Combinable>[] | { stream: AsyncIterableIterator<Combinable>; offsetMS: OffsetMS }[]
>(
...iteratorsPayload: T
): AsyncIterableIterator<
T extends AsyncIterableIterator<infer U>[] ? U : T extends { stream: AsyncIterableIterator<infer Z> }[] ? Z : never
> {
const iterators = iteratorsPayload.map((payload) => {
if ('stream' in payload) {
return payload.stream
}
return payload
})
if (iterators.length === 0) {
return
}
// decide based on first provided iterator if we're dealing with real-time or historical data streams
if ((iterators[0] as any).__realtime__) {
const combinedStream = new PassThrough({
objectMode: true,
highWaterMark: 8096
})
combinedStream.setMaxListeners(iterators.length + 1)
iterators.forEach(async function writeMessagesToCombinedStream(messages) {
for await (const message of messages) {
if (combinedStream.destroyed) {
return
}
if (!combinedStream.write(message)) {
// Handle backpressure on write
await once(combinedStream, 'drain')
}
}
})
for await (const message of combinedStream) {
yield message
}
} else {
return yield* combineHistorical(iteratorsPayload) as any
}
}
async function* combineHistorical(
iterators: AsyncIterableIterator<Combinable>[] | { stream: AsyncIterableIterator<Combinable>; offsetMS: OffsetMS }[]
) {
try {
// wait for all results to resolve
const results = await Promise.all(iterators.map(nextWithIndex))
let aliveIteratorsCount = results.length
do {
// if we're dealing with historical data replay
// and need to return combined messages iterable sorted by local timestamp in ascending order
// find resolved one that is the 'oldest'
const oldestResult = results.reduce(findOldestResult, results[0])
const { result, index } = oldestResult
if (result.done) {
aliveIteratorsCount--
// we don't want finished iterators to every be considered 'oldest' again
// hence provide them with result that has local timestamp set to DATE_MAX
// and that is not done
results[index].result = {
done: false,
value: {
localTimestamp: DATE_MAX
}
}
} else {
// yield oldest value and replace with next value from iterable for given index
yield result.value
results[index] = await nextWithIndex(iterators[index], index)
}
} while (aliveIteratorsCount > 0)
} finally {
for (let iterator of iterators) {
;(iterator as any).return()
}
}
}
================================================
FILE: src/computable/booksnapshot.ts
================================================
import { decimalPlaces } from '../handy.ts'
import { OrderBook, OnLevelRemovedCB } from '../orderbook.ts'
import { BookChange, BookPriceLevel, BookSnapshot, Optional } from '../types.ts'
import { Computable } from './computable.ts'
type BookSnapshotComputableOptions = {
name?: string
depth: number
grouping?: number
interval: number
removeCrossedLevels?: boolean
onCrossedLevelRemoved?: OnLevelRemovedCB
}
export const computeBookSnapshots =
(options: BookSnapshotComputableOptions): (() => Computable<BookSnapshot>) =>
() =>
new BookSnapshotComputable(options)
const emptyBookLevel = {
price: undefined,
amount: undefined
}
const levelsChanged = (level1: Optional<BookPriceLevel>, level2: Optional<BookPriceLevel>) => {
if (level1.amount !== level2.amount) {
return true
}
if (level1.price !== level2.price) {
return true
}
return false
}
class BookSnapshotComputable implements Computable<BookSnapshot> {
public readonly sourceDataTypes = ['book_change']
private _bookChanged = false
private _initialized = false
private readonly _type = 'book_snapshot'
private readonly _orderBook: OrderBook
private readonly _depth: number
private readonly _interval: number
private readonly _name: string
private readonly _grouping: number | undefined
private readonly _groupingDecimalPlaces: number | undefined
private _lastUpdateTimestamp: Date = new Date(-1)
private _bids: Optional<BookPriceLevel>[] = []
private _asks: Optional<BookPriceLevel>[] = []
constructor({ depth, name, interval, removeCrossedLevels, grouping, onCrossedLevelRemoved }: BookSnapshotComputableOptions) {
this._depth = depth
this._interval = interval
this._grouping = grouping
this._groupingDecimalPlaces = this._grouping ? decimalPlaces(this._grouping) : undefined
this._orderBook = new OrderBook({
removeCrossedLevels,
onCrossedLevelRemoved
})
// initialize all bids/asks levels to empty ones
for (let i = 0; i < this._depth; i++) {
this._bids[i] = emptyBookLevel
this._asks[i] = emptyBookLevel
}
if (name === undefined) {
this._name = `${this._type}_${depth}${this._grouping ? `_grouped${this._grouping}` : ''}_${interval}ms`
} else {
this._name = name
}
}
public *compute(bookChange: BookChange) {
if (this._hasNewSnapshot(bookChange.timestamp)) {
yield this._getSnapshot(bookChange)
}
this._update(bookChange)
// check again after the update as book snapshot with interval set to 0 (real-time) could have changed
// or it's initial snapshot
if (this._hasNewSnapshot(bookChange.timestamp)) {
yield this._getSnapshot(bookChange)
if (this._initialized === false) {
this._initialized = true
}
}
}
public _hasNewSnapshot(timestamp: Date): boolean {
if (this._bookChanged === false) {
return false
}
// report new snapshot anytime book changed
if (this._interval === 0) {
return true
}
// report new snapshot for book snapshots with interval for initial snapshot
if (this._initialized === false) {
return true
}
const currentTimestampTimeBucket = this._getTimeBucket(timestamp)
const snapshotTimestampBucket = this._getTimeBucket(this._lastUpdateTimestamp)
if (currentTimestampTimeBucket > snapshotTimestampBucket) {
// set timestamp to end of snapshot 'interval' period
this._lastUpdateTimestamp = new Date((snapshotTimestampBucket + 1) * this._interval)
return true
}
return false
}
public _update(bookChange: BookChange) {
this._orderBook.update(bookChange)
if (this._grouping !== undefined) {
this._updateSideGrouped(this._orderBook.bids(), this._bids, this._getGroupedPriceForBids)
this._updateSideGrouped(this._orderBook.asks(), this._asks, this._getGroupedPriceForAsks)
} else {
this._updatedNotGrouped()
}
this._lastUpdateTimestamp = bookChange.timestamp
}
private _updatedNotGrouped() {
const bidsIterable = this._orderBook.bids()
const asksIterable = this._orderBook.asks()
for (let i = 0; i < this._depth; i++) {
const bidLevelResult = bidsIterable.next()
const newBid = bidLevelResult.done ? emptyBookLevel : bidLevelResult.value
if (levelsChanged(this._bids[i], newBid)) {
this._bids[i] = { ...newBid }
this._bookChanged = true
}
const askLevelResult = asksIterable.next()
const newAsk = askLevelResult.done ? emptyBookLevel : askLevelResult.value
if (levelsChanged(this._asks[i], newAsk)) {
this._asks[i] = { ...newAsk }
this._bookChanged = true
}
}
}
private _getGroupedPriceForBids = (price: number) => {
const pow = Math.pow(10, this._groupingDecimalPlaces!)
const pricePow = price * pow
const groupPow = this._grouping! * pow
const remainder = (pricePow % groupPow) / pow
return (pricePow - remainder * pow) / pow
}
private _getGroupedPriceForAsks = (price: number) => {
const pow = Math.pow(10, this._groupingDecimalPlaces!)
const pricePow = price * pow
const groupPow = this._grouping! * pow
const remainder = (pricePow % groupPow) / pow
return (pricePow - remainder * pow + (remainder > 0 ? groupPow : 0)) / pow
}
private _updateSideGrouped(
newLevels: IterableIterator<BookPriceLevel>,
existingGroupedLevels: Optional<BookPriceLevel>[],
getGroupedPriceForLevel: (price: number) => number
) {
let currentGroupedPrice: number | undefined = undefined
let aggAmount = 0
let currentDepth = 0
for (const notGroupedLevel of newLevels) {
const groupedPrice = getGroupedPriceForLevel(notGroupedLevel.price)
if (currentGroupedPrice == undefined) {
currentGroupedPrice = groupedPrice
}
if (currentGroupedPrice != groupedPrice) {
const groupedLevel = {
price: currentGroupedPrice,
amount: aggAmount
}
if (levelsChanged(existingGroupedLevels[currentDepth], groupedLevel)) {
existingGroupedLevels[currentDepth] = groupedLevel
this._bookChanged = true
}
currentDepth++
if (currentDepth === this._depth) {
break
}
currentGroupedPrice = groupedPrice
aggAmount = 0
}
aggAmount += notGroupedLevel.amount
}
if (currentDepth < this._depth && aggAmount > 0) {
const groupedLevel = {
price: currentGroupedPrice,
amount: aggAmount
}
if (levelsChanged(existingGroupedLevels[currentDepth], groupedLevel)) {
existingGroupedLevels[currentDepth] = groupedLevel
this._bookChanged = true
}
}
}
public _getSnapshot(bookChange: BookChange) {
const snapshot: BookSnapshot = {
type: this._type as any,
symbol: bookChange.symbol,
exchange: bookChange.exchange,
name: this._name,
depth: this._depth,
interval: this._interval,
grouping: this._grouping,
bids: [...this._bids],
asks: [...this._asks],
timestamp: this._lastUpdateTimestamp,
localTimestamp: bookChange.localTimestamp
}
this._bookChanged = false
return snapshot
}
private _getTimeBucket(timestamp: Date) {
return Math.floor(timestamp.valueOf() / this._interval)
}
}
================================================
FILE: src/computable/computable.ts
================================================
import { Disconnect, Exchange, NormalizedData } from '../types.ts'
export type Computable<T extends NormalizedData> = {
readonly sourceDataTypes: string[]
compute(message: NormalizedData): IterableIterator<T>
}
export type ComputableFactory<T extends NormalizedData> = () => Computable<T>
async function* _compute<T extends ComputableFactory<any>[], U extends NormalizedData | Disconnect>(
messages: AsyncIterableIterator<U>,
...computables: T
): AsyncIterableIterator<T extends ComputableFactory<infer Z>[] ? (U extends Disconnect ? U | Z | Disconnect : U | Z) : never> {
try {
const factory = new Computables(computables)
for await (const message of messages) {
// always pass through source message
yield message as any
if (message.type === 'disconnect') {
// reset all computables for given exchange if we've received disconnect for it
factory.reset(message.exchange)
continue
}
const normalizedMessage = message as NormalizedData
const id = normalizedMessage.name !== undefined ? `${normalizedMessage.symbol}:${normalizedMessage.name}` : normalizedMessage.symbol
const computablesMap = factory.getOrCreate(normalizedMessage.exchange, id)
const computables = computablesMap[normalizedMessage.type]
if (!computables) continue
for (const computable of computables) {
for (const computedMessage of computable.compute(normalizedMessage)) {
yield computedMessage
}
}
}
} finally {
messages.return!()
}
}
export function compute<T extends ComputableFactory<any>[], U extends NormalizedData | Disconnect>(
messages: AsyncIterableIterator<U>,
...computables: T
): AsyncIterableIterator<T extends ComputableFactory<infer Z>[] ? (U extends Disconnect ? U | Z | Disconnect : U | Z) : never> {
let _iterator = _compute(messages, ...computables)
if ((messages as any).__realtime__ === true) {
;(_iterator as any).__realtime__ = true
}
return _iterator
}
class Computables {
private _computables: {
[ex in Exchange]?: {
[key: string]: { [dataType: string]: Computable<any>[] }
}
} = {}
constructor(private readonly _computablesFactories: ComputableFactory<any>[]) {}
getOrCreate(exchange: Exchange, id: string) {
if (this._computables[exchange] === undefined) {
this._computables[exchange] = {}
}
if (this._computables[exchange]![id] === undefined) {
this._computables[exchange]![id] = createComputablesMap(this._computablesFactories.map((c) => c()))
}
return this._computables[exchange]![id]!
}
reset(exchange: Exchange) {
this._computables[exchange] = undefined
}
}
function createComputablesMap(computables: Computable<any>[]) {
return computables.reduce((acc, computable) => {
computable.sourceDataTypes.forEach((dataType) => {
const existing = acc[dataType]
if (!existing) {
acc[dataType] = [computable]
} else {
acc[dataType].push(computable)
}
})
return acc
}, {} as { [dataType: string]: Computable<any>[] })
}
================================================
FILE: src/computable/index.ts
================================================
export * from './booksnapshot.ts'
export * from './computable.ts'
export * from './tradebar.ts'
================================================
FILE: src/computable/tradebar.ts
================================================
import { BookChange, NormalizedData, Trade, TradeBar, Writeable } from '../types.ts'
import { Computable } from './computable.ts'
const DATE_MIN = new Date(-1)
type BarKind = 'time' | 'volume' | 'tick'
type TradeBarComputableOptions = { kind: BarKind; interval: number; name?: string }
export const computeTradeBars =
(options: TradeBarComputableOptions): (() => Computable<TradeBar>) =>
() =>
new TradeBarComputable(options)
const kindSuffix: { [key in BarKind]: string } = {
tick: 'ticks',
time: 'ms',
volume: 'vol'
}
class TradeBarComputable implements Computable<TradeBar> {
// use book_change messages as workaround for issue when time passes for new bar to be produced but there's no trades,
// so logic `compute` would not execute
// assumption is that if one subscribes to book changes too then there's pretty good chance that
// even if there are no trades, there's plenty of book changes that trigger computing new trade bar if time passess
public readonly sourceDataTypes = ['trade']
private _inProgressBar: Writeable<TradeBar>
private readonly _kind: BarKind
private readonly _interval: number
private readonly _name: string
private readonly _type = 'trade_bar'
constructor({ kind, interval, name }: TradeBarComputableOptions) {
this._kind = kind
this._interval = interval
if (name === undefined) {
this._name = `${this._type}_${interval}${kindSuffix[kind]}`
} else {
this._name = name
}
this._inProgressBar = {} as any
this._reset()
}
public *compute(message: Trade | BookChange) {
// first check if there is a new trade bar for new timestamp for time based trade bars
if (this._hasNewBar(message.timestamp)) {
yield this._computeBar(message)
}
if (message.type !== 'trade') {
return
}
// update in progress trade bar with new data
this._update(message)
// and check again if there is a new trade bar after the update (volume/tick based trade bars)
if (this._hasNewBar(message.timestamp)) {
yield this._computeBar(message)
}
}
private _computeBar(message: NormalizedData) {
this._inProgressBar.localTimestamp = message.localTimestamp
this._inProgressBar.symbol = message.symbol
this._inProgressBar.exchange = message.exchange
const tradeBar: TradeBar = { ...this._inProgressBar }
this._reset()
return tradeBar
}
private _hasNewBar(timestamp: Date): boolean {
// privided timestamp is an exchange trade timestamp in that case
// we bucket based on exchange timestamps when bucketing by time not by localTimestamp
if (this._inProgressBar.trades === 0) {
return false
}
if (this._kind === 'time') {
// TODO: push initial bar at a start?
const currentTimestampTimeBucket = this._getTimeBucket(timestamp)
const openTimestampTimeBucket = this._getTimeBucket(this._inProgressBar.openTimestamp)
if (currentTimestampTimeBucket > openTimestampTimeBucket) {
// set the timestamp to the end of the period of given bucket
this._inProgressBar.timestamp = new Date((openTimestampTimeBucket + 1) * this._interval)
return true
}
return false
}
if (this._kind === 'volume') {
return this._inProgressBar.volume >= this._interval
}
if (this._kind === 'tick') {
return this._inProgressBar.trades >= this._interval
}
return false
}
private _update(trade: Trade) {
const inProgressBar = this._inProgressBar
const isNotOpenedYet = inProgressBar.trades === 0
// some exchanges (like dydx) sometimes publish trade data out of order (older trades published after newer ones)
const tradeIsInCorrectTimeOrder = trade.timestamp.valueOf() >= inProgressBar.timestamp.valueOf()
if (isNotOpenedYet) {
inProgressBar.open = trade.price
inProgressBar.openTimestamp = trade.timestamp
}
if (inProgressBar.high < trade.price) {
inProgressBar.high = trade.price
}
if (inProgressBar.low > trade.price) {
inProgressBar.low = trade.price
}
if (tradeIsInCorrectTimeOrder) {
inProgressBar.close = trade.price
inProgressBar.closeTimestamp = trade.timestamp
inProgressBar.timestamp = trade.timestamp
}
inProgressBar.buyVolume += trade.side === 'buy' ? trade.amount : 0
inProgressBar.sellVolume += trade.side === 'sell' ? trade.amount : 0
inProgressBar.trades += 1
inProgressBar.vwap = (inProgressBar.vwap * inProgressBar.volume + trade.price * trade.amount) / (inProgressBar.volume + trade.amount)
// volume needs to be updated after vwap otherwise vwap calc will go wrong
inProgressBar.volume += trade.amount
}
private _reset() {
const barToReset = this._inProgressBar
barToReset.type = this._type
barToReset.symbol = ''
barToReset.exchange = '' as any
barToReset.name = this._name
barToReset.interval = this._interval
barToReset.kind = this._kind
barToReset.open = 0
barToReset.high = Number.MIN_SAFE_INTEGER
barToReset.low = Number.MAX_SAFE_INTEGER
barToReset.close = 0
barToReset.volume = 0
barToReset.buyVolume = 0
barToReset.sellVolume = 0
barToReset.trades = 0
barToReset.vwap = 0
barToReset.openTimestamp = DATE_MIN
barToReset.closeTimestamp = DATE_MIN
barToReset.localTimestamp = DATE_MIN
barToReset.timestamp = DATE_MIN
}
private _getTimeBucket(timestamp: Date) {
return Math.floor(timestamp.valueOf() / this._interval)
}
}
================================================
FILE: src/consts.ts
================================================
export const EXCHANGES = [
'bitmex',
'deribit',
'binance-futures',
'binance-delivery',
'binance-european-options',
'binance',
'ftx',
'okex-futures',
'okex-options',
'okex-swap',
'okex',
'okex-spreads',
'huobi-dm',
'huobi-dm-swap',
'huobi-dm-linear-swap',
'huobi',
'bitfinex-derivatives',
'bitfinex',
'coinbase',
'coinbase-international',
'cryptofacilities',
'kraken',
'bitstamp',
'gemini',
'poloniex',
'bybit',
'bybit-spot',
'bybit-options',
'phemex',
'delta',
'ftx-us',
'binance-us',
'gate-io-futures',
'gate-io',
'okcoin',
'bitflyer',
'hitbtc',
'coinflex',
'binance-jersey',
'binance-dex',
'upbit',
'ascendex',
'dydx',
'dydx-v4',
'serum',
'mango',
'huobi-dm-options',
'star-atlas',
'crypto-com',
'kucoin',
'kucoin-futures',
'bitnomial',
'woo-x',
'blockchain-com',
'bitget',
'bitget-futures',
'hyperliquid'
] as const
const BINANCE_CHANNELS = ['trade', 'aggTrade', 'ticker', 'depth', 'depthSnapshot', 'bookTicker', 'recentTrades', 'borrowInterest'] as const
const BINANCE_DEX_CHANNELS = ['trades', 'marketDiff', 'depthSnapshot', 'ticker'] as const
const BITFINEX_CHANNELS = ['trades', 'book', 'raw_book', 'ticker'] as const
const BITMEX_CHANNELS = [
'trade',
'orderBookL2',
'liquidation',
'connected',
'announcement',
'chat',
'publicNotifications',
'instrument',
'settlement',
'funding',
'insurance',
'orderBookL2_25',
'orderBook10',
'quote',
'quoteBin1m',
'quoteBin5m',
'quoteBin1h',
'quoteBin1d',
'tradeBin1m',
'tradeBin5m',
'tradeBin1h',
'tradeBin1d'
] as const
const BITSTAMP_CHANNELS = ['live_trades', 'live_orders', 'diff_order_book'] as const
const COINBASE_CHANNELS = [
'match',
'subscriptions',
'received',
'open',
'done',
'change',
'l2update',
'ticker',
'snapshot',
'last_match',
'full_snapshot',
'rfq_matches'
] as const
const DERIBIT_CHANNELS = [
'book',
'deribit_price_index',
'deribit_price_ranking',
'deribit_volatility_index',
'estimated_expiration_price',
'markprice.options',
'perpetual',
'trades',
'ticker',
'quote',
'platform_state',
'instrument.state.any'
] as const
const KRAKEN_CHANNELS = ['trade', 'ticker', 'book', 'spread'] as const
const OKEX_CHANNELS = [
'spot/trade',
'spot/depth',
'spot/depth_l2_tbt',
'spot/ticker',
'system/status',
'margin/interest_rate',
// v5
'trades',
'trades-all',
'books-l2-tbt',
'bbo-tbt',
'books',
'tickers',
'interest-rate-loan-quota',
'vip-interest-rate-loan-quota',
'status',
'instruments',
'taker-volume',
'public-struc-block-trades',
'liquidations',
'loan-ratio',
'public-block-trades'
] as const
const OKCOIN_CHANNELS = [
'spot/trade',
'spot/depth',
'spot/depth_l2_tbt',
'spot/ticker',
'system/status',
'trades',
'books',
'bbo-tbt',
'tickers'
] as const
const OKEX_FUTURES_CHANNELS = [
'futures/trade',
'futures/depth',
'futures/depth_l2_tbt',
'futures/ticker',
'futures/mark_price',
'futures/liquidation',
'index/ticker',
'system/status',
'information/sentiment',
'information/long_short_ratio',
'information/margin',
// v5
'trades',
'trades-all',
'books-l2-tbt',
'bbo-tbt',
'books',
'tickers',
'open-interest',
'mark-price',
'price-limit',
'status',
'instruments',
'index-tickers',
'long-short-account-ratio',
'taker-volume',
'liquidations',
'public-struc-block-trades',
'liquidation-orders',
'estimated-price',
'long-short-account-ratio-contract',
'long-short-account-ratio-contract-top-trader',
'long-short-position-ratio-contract-top-trader',
'public-block-trades',
'taker-volume-contract'
] as const
const OKEX_SWAP_CHANNELS = [
'swap/trade',
'swap/depth',
'swap/depth_l2_tbt',
'swap/ticker',
'swap/funding_rate',
'swap/mark_price',
'swap/liquidation',
'index/ticker',
'system/status',
'information/sentiment',
'information/long_short_ratio',
'information/margin',
//v5
'trades',
'trades-all',
'books-l2-tbt',
'bbo-tbt',
'books',
'tickers',
'open-interest',
'mark-price',
'price-limit',
'funding-rate',
'status',
'instruments',
'index-tickers',
'long-short-account-ratio',
'taker-volume',
'liquidations',
'public-struc-block-trades',
'liquidation-orders',
'long-short-account-ratio-contract',
'long-short-account-ratio-contract-top-trader',
'long-short-position-ratio-contract-top-trader',
'public-block-trades',
'taker-volume-contract'
] as const
const OKEX_OPTIONS_CHANNELS = [
'option/trade',
'option/depth',
'option/depth_l2_tbt',
'option/ticker',
'option/summary',
'option/instruments',
'index/ticker',
'system/status',
'option/trades',
//v5
'trades',
'trades-all',
'books-l2-tbt',
'bbo-tbt',
'books',
'tickers',
'opt-summary',
'status',
'instruments',
'index-tickers',
'open-interest',
'mark-price',
'price-limit',
'public-struc-block-trades',
'option-trades',
'estimated-price',
'public-block-trades'
] as const
const COINFLEX_CHANNELS = ['futures/depth', 'trade', 'ticker'] as const
const CRYPTOFACILITIES_CHANNELS = ['trade', 'trade_snapshot', 'book', 'book_snapshot', 'ticker', 'heartbeat'] as const
const FTX_CHANNELS = [
'orderbook',
'trades',
'instrument',
'markets',
'orderbookGrouped',
'lendingRate',
'borrowRate',
'borrowSummary',
'ticker',
'leveragedTokenInfo',
'busy'
] as const
const GEMINI_CHANNELS = ['trade', 'l2_updates', 'auction_open', 'auction_indicative', 'auction_result'] as const
const BITFLYER_CHANNELS = ['lightning_executions', 'lightning_board_snapshot', 'lightning_board', 'lightning_ticker'] as const
const BINANCE_FUTURES_CHANNELS = [
'trade',
'aggTrade',
'ticker',
'depth',
'markPrice',
'premiumIndex',
'depthSnapshot',
'bookTicker',
'forceOrder',
'openInterest',
'fundingInfo',
'insuranceBalance',
'recentTrades',
'compositeIndex',
'topLongShortAccountRatio',
'topLongShortPositionRatio',
'globalLongShortAccountRatio',
'takerlongshortRatio',
'!contractInfo',
'assetIndex'
] as const
const BINANCE_DELIVERY_CHANNELS = [
'trade',
'aggTrade',
'ticker',
'depth',
'markPrice',
'indexPrice',
'depthSnapshot',
'bookTicker',
'forceOrder',
'openInterest',
'fundingInfo',
'recentTrades',
'topLongShortAccountRatio',
'topLongShortPositionRatio',
'globalLongShortAccountRatio',
'takerBuySellVol',
'!contractInfo'
] as const
const BITFINEX_DERIV_CHANNELS = ['trades', 'book', 'raw_book', 'status', 'liquidations', 'ticker'] as const
const HUOBI_CHANNELS = ['depth', 'detail', 'trade', 'bbo', 'mbp', 'etp', 'mbp.20'] as const
const HUOBI_DM_CHANNELS = [
'depth',
'detail',
'trade',
'bbo',
'basis',
'liquidation_orders',
'contract_info',
'open_interest',
'elite_account_ratio',
'elite_position_ratio'
] as const
const HUOBI_DM_SWAP_CHANNELS = [
'depth',
'detail',
'trade',
'bbo',
'basis',
'funding_rate',
'liquidation_orders',
'contract_info',
'open_interest',
'elite_account_ratio',
'elite_position_ratio'
] as const
const HUOBI_DM_LINEAR_SWAP_CHANNELS = [
'depth',
'detail',
'trade',
'bbo',
'basis',
'funding_rate',
'liquidation_orders',
'contract_info',
'open_interest',
'elite_account_ratio',
'elite_position_ratio'
] as const
const PHEMEX_CHANNELS = ['book', 'orderbook_p', 'trades', 'trades_p', 'market24h', 'spot_market24h', 'perp_market24h_pack_p'] as const
const BYBIT_CHANNELS = [
'trade',
'instrument_info',
'orderBookL2_25',
'insurance',
'orderBook_200',
'liquidation',
'trade',
'instrument_info',
'orderBookL2_25',
'insurance',
'orderBook_200',
'liquidation',
'long_short_ratio',
'orderbook.1',
'orderbook.50',
'orderbook.500',
'publicTrade',
'tickers',
'liquidation',
'allLiquidation',
'orderbook.1000'
] as const
const BYBIT_OPTIONS_CHANNELS = ['orderbook.25', 'orderbook.100', 'publicTrade', 'tickers']
const HITBTC_CHANNELS = ['updateTrades', 'snapshotTrades', 'snapshotOrderbook', 'updateOrderbook'] as const
const FTX_US_CHANNELS = ['orderbook', 'trades', 'markets', 'orderbookGrouped', 'ticker'] as const
const DELTA_CHANNELS = [
'l2_orderbook',
'recent_trade',
'recent_trade_snapshot',
'mark_price',
'spot_price',
'funding_rate',
'product_updates',
'announcements',
'all_trades',
'v2/ticker',
'l1_orderbook',
'l2_updates',
'spot_30mtwap_price'
] as const
const GATE_IO_CHANNELS = ['trades', 'depth', 'ticker', 'book_ticker', 'order_book_update', 'obu'] as const
const GATE_IO_FUTURES_CHANNELS = ['trades', 'order_book', 'tickers', 'book_ticker'] as const
const POLONIEX_CHANNELS = ['price_aggregated_book', 'trades', 'ticker', 'book_lv2'] as const
const UPBIT_CHANNELS = ['trade', 'orderbook', 'ticker'] as const
const ASCENDEX_CHANNELS = ['trades', 'depth-realtime', 'depth-snapshot-realtime', 'bbo', 'futures-pricing-data'] as const
const DYDX_CHANNELS = ['v3_trades', 'v3_orderbook', 'v3_markets'] as const
const DYDX_V4_CHANNELS = ['v4_trades', 'v4_orderbook', 'v4_markets'] as const
const SERUM_CHANNELS = [
'recent_trades',
'trade',
'quote',
'l2snapshot',
'l2update',
'l3snapshot',
'open',
'fill',
'change',
'done'
] as const
const MANGO_CHANNELS = [
'recent_trades',
'trade',
'quote',
'l2snapshot',
'l2update',
'l3snapshot',
'open',
'fill',
'change',
'done'
] as const
const HUOBI_DM_OPTIONS_CHANNELS = ['trade', 'detail', 'depth', 'bbo', 'open_interest', 'option_market_index', 'option_index'] as const
const BYBIT_SPOT_CHANNELS = ['trade', 'bookTicker', 'depth', 'orderbook.1', 'orderbook.50', 'publicTrade', 'tickers', 'lt', 'orderbook.200']
const CRYPTO_COM_CHANNELS = ['trade', 'book', 'ticker', 'settlement', 'index', 'mark', 'funding', 'estimatedfunding']
const KUCOIN_CHANNELS = ['market/ticker', 'market/snapshot', 'market/level2', 'market/match', 'market/level2Snapshot']
const BITNOMIAL_CHANNELS = ['trade', 'level', 'book', 'block', 'status']
const WOOX_CHANNELS = [
'trade',
'orderbook',
'orderbookupdate',
'ticker',
'bbo',
'indexprice',
'markprice',
'openinterest',
'estfundingrate'
]
const BLOCKCHAIN_COM_CHANNELS = ['trades', 'l2', 'l3', 'ticker']
const BINANCE_EUROPEAN_OPTIONS_CHANNELS = [
'trade',
'depth100',
'index',
'markPrice',
'ticker',
'openInterest',
'optionTrade',
'optionTicker',
'depth20',
'bookTicker',
'optionIndexPrice',
'optionMarkPrice',
'optionOpenInterest',
'!optionSymbol'
]
const OKEX_SPREADS_CHANNELS = ['sprd-public-trades', 'sprd-bbo-tbt', 'sprd-books5', 'sprd-tickers']
const KUCOIN_FUTURES_CHANNELS = [
'contractMarket/execution',
'contractMarket/level2',
'contractMarket/level2Snapshot',
'contractMarket/tickerV2',
'contract/instrument',
'contract/details',
'contractMarket/snapshot'
]
const BITGET_CHANNELS = ['trade', 'books1', 'books15']
const BITGET_FUTURES_CHANNELS = ['trade', 'books1', 'books15', 'ticker']
const COINBASE_INTERNATIONAL_CHANNELS = ['INSTRUMENTS', 'MATCH', 'FUNDING', 'RISK', 'LEVEL1', 'LEVEL2', 'CANDLES_ONE_MINUTE']
const HYPERLIQUID_CHANNELS = ['l2Book', 'trades', 'activeAssetCtx', 'activeSpotAssetCtx', 'bbo']
export const EXCHANGE_CHANNELS_INFO = {
bitmex: BITMEX_CHANNELS,
coinbase: COINBASE_CHANNELS,
'coinbase-international': COINBASE_INTERNATIONAL_CHANNELS,
deribit: DERIBIT_CHANNELS,
cryptofacilities: CRYPTOFACILITIES_CHANNELS,
bitstamp: BITSTAMP_CHANNELS,
kraken: KRAKEN_CHANNELS,
okex: OKEX_CHANNELS,
'okex-swap': OKEX_SWAP_CHANNELS,
'okex-futures': OKEX_FUTURES_CHANNELS,
'okex-options': OKEX_OPTIONS_CHANNELS,
binance: BINANCE_CHANNELS,
'binance-jersey': BINANCE_CHANNELS,
'binance-dex': BINANCE_DEX_CHANNELS,
'binance-us': BINANCE_CHANNELS,
bitfinex: BITFINEX_CHANNELS,
ftx: FTX_CHANNELS,
'ftx-us': FTX_US_CHANNELS,
gemini: GEMINI_CHANNELS,
bitflyer: BITFLYER_CHANNELS,
'binance-futures': BINANCE_FUTURES_CHANNELS,
'binance-delivery': BINANCE_DELIVERY_CHANNELS,
'bitfinex-derivatives': BITFINEX_DERIV_CHANNELS,
huobi: HUOBI_CHANNELS,
'huobi-dm': HUOBI_DM_CHANNELS,
'huobi-dm-swap': HUOBI_DM_SWAP_CHANNELS,
'huobi-dm-linear-swap': HUOBI_DM_LINEAR_SWAP_CHANNELS,
bybit: BYBIT_CHANNELS,
'bybit-spot': BYBIT_SPOT_CHANNELS,
'bybit-options': BYBIT_OPTIONS_CHANNELS,
okcoin: OKCOIN_CHANNELS,
hitbtc: HITBTC_CHANNELS,
coinflex: COINFLEX_CHANNELS,
phemex: PHEMEX_CHANNELS,
delta: DELTA_CHANNELS,
'gate-io': GATE_IO_CHANNELS,
'gate-io-futures': GATE_IO_FUTURES_CHANNELS,
poloniex: POLONIEX_CHANNELS,
upbit: UPBIT_CHANNELS,
ascendex: ASCENDEX_CHANNELS,
dydx: DYDX_CHANNELS,
'dydx-v4': DYDX_V4_CHANNELS,
serum: SERUM_CHANNELS,
'star-atlas': SERUM_CHANNELS,
'huobi-dm-options': HUOBI_DM_OPTIONS_CHANNELS,
mango: MANGO_CHANNELS,
'crypto-com': CRYPTO_COM_CHANNELS,
kucoin: KUCOIN_CHANNELS,
bitnomial: BITNOMIAL_CHANNELS,
'woo-x': WOOX_CHANNELS,
'blockchain-com': BLOCKCHAIN_COM_CHANNELS,
'binance-european-options': BINANCE_EUROPEAN_OPTIONS_CHANNELS,
'okex-spreads': OKEX_SPREADS_CHANNELS,
'kucoin-futures': KUCOIN_FUTURES_CHANNELS,
bitget: BITGET_CHANNELS,
'bitget-futures': BITGET_FUTURES_CHANNELS,
hyperliquid: HYPERLIQUID_CHANNELS
}
================================================
FILE: src/debug.ts
================================================
import dbg from 'debug'
export const debug = dbg('tardis-dev')
================================================
FILE: src/downloaddatasets.ts
================================================
import { existsSync } from 'node:fs'
import pMap from 'p-map'
import { debug } from './debug.ts'
import { DatasetType } from './exchangedetails.ts'
import { addDays, doubleDigit, download, parseAsUTCDate, sequence } from './handy.ts'
import { getOptions } from './options.ts'
import { Exchange } from './types.ts'
const CONCURRENCY_LIMIT = 20
const MILLISECONDS_IN_SINGLE_DAY = 24 * 60 * 60 * 1000
const DEFAULT_DOWNLOAD_DIR = './datasets'
const options = getOptions()
export async function downloadDatasets(downloadDatasetsOptions: DownloadDatasetsOptions) {
const { exchange, dataTypes, from, to, symbols } = downloadDatasetsOptions
const apiKey = downloadDatasetsOptions.apiKey !== undefined ? downloadDatasetsOptions.apiKey : options.apiKey
const downloadDir = downloadDatasetsOptions.downloadDir !== undefined ? downloadDatasetsOptions.downloadDir : DEFAULT_DOWNLOAD_DIR
const format = downloadDatasetsOptions.format !== undefined ? downloadDatasetsOptions.format : 'csv'
const getFilename = downloadDatasetsOptions.getFilename !== undefined ? downloadDatasetsOptions.getFilename : getFilenameDefault
const skipIfExists = downloadDatasetsOptions.skipIfExists === undefined ? true : downloadDatasetsOptions.skipIfExists
// in case someone provided 'api/exchange' symbol, transform it to symbol that is accepted by datasets API
const datasetsSymbols = symbols.map((s) => s.replace(/\/|:/g, '-').toUpperCase())
for (const symbol of datasetsSymbols) {
for (const dataType of dataTypes) {
const { daysCountToFetch, startDate } = getDownloadDateRange(downloadDatasetsOptions)
const startTimestamp = new Date().valueOf()
debug('dataset download started for %s %s %s from %s to %s', exchange, dataType, symbol, from, to)
if (daysCountToFetch > 1) {
// start with downloading last day of the range, validates is API key has access to the end range of requested data
await downloadDataSet(
getDownloadOptions({
exchange,
symbol,
apiKey,
downloadDir,
dataType,
format,
getFilename,
date: addDays(startDate, daysCountToFetch - 1)
}),
skipIfExists
)
}
// then download the first day of the range, validates is API key has access to the start range of requested data
await downloadDataSet(
getDownloadOptions({
exchange,
symbol,
apiKey,
downloadDir,
dataType,
format,
getFilename,
date: startDate
}),
skipIfExists
)
// download the rest concurrently up to the CONCURRENCY_LIMIT
await pMap(
sequence(daysCountToFetch - 1, 1), // this will produce Iterable sequence from 1 to daysCountToFetch - 1 (as we already downloaded data for the first and last day)
(offset) =>
downloadDataSet(
getDownloadOptions({
exchange,
symbol,
apiKey,
downloadDir,
dataType,
format,
getFilename,
date: addDays(startDate, offset)
}),
skipIfExists
),
{ concurrency: CONCURRENCY_LIMIT }
)
const elapsedSeconds = (new Date().valueOf() - startTimestamp) / 1000
debug('dataset download finished for %s %s %s from %s to %s, time: %s seconds', exchange, dataType, symbol, from, to, elapsedSeconds)
}
}
}
async function downloadDataSet(downloadOptions: DownloadOptions, skipIfExists: boolean) {
if (skipIfExists && existsSync(downloadOptions.downloadPath)) {
debug('dataset %s already exists, skipping download', downloadOptions.downloadPath)
return
} else {
return await download(downloadOptions)
}
}
function getDownloadOptions({
apiKey,
exchange,
dataType,
date,
symbol,
format,
downloadDir,
getFilename
}: {
exchange: Exchange
dataType: DatasetType
symbol: string
date: Date
format: string
apiKey: string
downloadDir: string
getFilename: (options: GetFilenameOptions) => string
}): DownloadOptions {
const year = date.getUTCFullYear()
const month = doubleDigit(date.getUTCMonth() + 1)
const day = doubleDigit(date.getUTCDate())
const url = `${options.datasetsEndpoint}/${exchange}/${dataType}/${year}/${month}/${day}/${encodeURIComponent(symbol)}.${format}.gz`
const filename = getFilename({
dataType,
date,
exchange,
format,
symbol
})
const downloadPath = `${downloadDir}/${filename}`
return {
url,
downloadPath,
userAgent: options._userAgent,
apiKey
}
}
type DownloadOptions = Parameters<typeof download>[0]
export function sanitizeForFilename(s: string) {
return s.replace(/[:\\/?*<>|"]/g, '-')
}
function getFilenameDefault({ exchange, dataType, format, date, symbol }: GetFilenameOptions) {
return `${exchange}_${dataType}_${date.toISOString().split('T')[0]}_${sanitizeForFilename(symbol)}.${format}.gz`
}
function getDownloadDateRange({ from, to }: DownloadDatasetsOptions) {
if (!from || isNaN(Date.parse(from))) {
throw new Error(`Invalid "from" argument: ${from}. Please provide valid date string.`)
}
if (!to || isNaN(Date.parse(to))) {
throw new Error(`Invalid "to" argument: ${to}. Please provide valid date string.`)
}
const toDate = parseAsUTCDate(to)
const fromDate = parseAsUTCDate(from)
const daysCountToFetch = Math.floor((toDate.getTime() - fromDate.getTime()) / MILLISECONDS_IN_SINGLE_DAY)
if (daysCountToFetch < 1) {
throw new Error(`Invalid "to" and "from" arguments combination. Please provide "to" day that is later than "from" day.`)
}
return {
startDate: fromDate,
daysCountToFetch
}
}
type GetFilenameOptions = {
exchange: Exchange
dataType: DatasetType
symbol: string
date: Date
format: string
}
type DownloadDatasetsOptions = {
exchange: Exchange
dataTypes: DatasetType[]
symbols: string[]
from: string
to: string
format?: 'csv'
apiKey?: string
downloadDir?: string
getFilename?: (options: GetFilenameOptions) => string
skipIfExists?: boolean
}
================================================
FILE: src/exchangedetails.ts
================================================
import { getJSON } from './handy.ts'
import { getOptions } from './options.ts'
import { Exchange, FilterForExchange } from './types.ts'
export async function getExchangeDetails<T extends Exchange>(exchange: T) {
const options = getOptions()
const { data } = await getJSON(`${options.endpoint}/exchanges/${exchange}`)
return data as ExchangeDetails<T>
}
export type SymbolType = 'spot' | 'future' | 'perpetual' | 'option' | 'combo'
export type DatasetType =
| 'trades'
| 'incremental_book_L2'
| 'quotes'
| 'derivative_ticker'
| 'options_chain'
| 'book_snapshot_25'
| 'book_snapshot_5'
| 'liquidations'
| 'book_ticker'
export type Stats = {
trades: number
bookChanges: number
}
type Datasets = {
formats: ['csv']
exportedFrom: string
exportedUntil: string
stats: Stats
symbols: {
id: string
type: SymbolType
availableSince: string
availableTo?: string
dataTypes: DatasetType[]
}[]
}
type ChannelDetails = {
name: string
description: string
frequency: string
frequencySource: string
exchangeDocsUrl?: string
sourceFor?: string[]
availableSince: string
availableTo?: string
apiVersion?: string
additionalInfo?: string
generated?: true
}
type DataCenter = {
host: string
regionId: string
location: string
}
type DataCollectionDetails = {
recorderDataCenter: DataCenter
recorderDataCenterChanges?: {
until: string
dataCenter: DataCenter
}[]
wssConnection?: {
url: string
apiVersion?: string
proxiedViaCloudflare?: boolean
}
wssConnectionChanges?: {
until: string
url?: string
apiVersion?: string
proxiedViaCloudflare?: boolean
}[]
exchangeDataCenter?: DataCenter
exchangeDataCenterChanges?: {
until: string
dataCenter: DataCenter
}[]
}
export type ExchangeDetailsBase<T extends Exchange> = {
id: T
name: string
enabled: boolean
delisted?: boolean
availableSince: string
availableTo?: string
availableChannels: FilterForExchange[T]['channel'][]
availableSymbols: {
id: string
type: SymbolType
availableSince: string
availableTo?: string
name?: string
}[]
incidentReports: {
from: string
to: string
status: 'resolved' | 'wontfix' | 'unresolved'
details: string
}[]
channelDetails: ChannelDetails[]
apiDocsUrl?: string
dataCollectionDetails?: DataCollectionDetails
datasets: Datasets
}
type ExchangeDetails<T extends Exchange> = ExchangeDetailsBase<T>
================================================
FILE: src/filter.ts
================================================
import { NormalizedData, Disconnect, Trade } from './types.ts'
import { CappedSet } from './handy.ts'
export async function* filter<T extends NormalizedData | Disconnect>(messages: AsyncIterableIterator<T>, filter: (message: T) => boolean) {
for await (const message of messages) {
if (filter(message)) {
yield message
}
}
}
export function uniqueTradesOnly<T extends NormalizedData | Disconnect>(
{
maxWindow,
onDuplicateFound,
skipStaleOlderThanSeconds
}: {
maxWindow: number
skipStaleOlderThanSeconds?: number
onDuplicateFound?: (trade: Trade) => void
} = {
maxWindow: 500
}
) {
const perSymbolQueues = {} as {
[key: string]: CappedSet<string>
}
return (message: T) => {
// pass trough any message that is not a trade
if (message.type !== 'trade') {
return true
} else {
const trade = message as unknown as Trade
// pass trough trades that can't be uniquely identified
// ignore index trades
if (trade.id === undefined || trade.symbol.startsWith('.')) {
return true
} else {
let alreadySeenTrades = perSymbolQueues[trade.symbol]
if (alreadySeenTrades === undefined) {
perSymbolQueues[trade.symbol] = new CappedSet<string>(maxWindow)
alreadySeenTrades = perSymbolQueues[trade.symbol]
}
const isDuplicate = alreadySeenTrades.has(trade.id)
const isStale =
skipStaleOlderThanSeconds !== undefined &&
trade.localTimestamp.valueOf() - trade.timestamp.valueOf() > skipStaleOlderThanSeconds * 1000
if (isDuplicate || isStale) {
if (onDuplicateFound !== undefined) {
onDuplicateFound(trade)
}
// refresh duplicated key position so it's added back at the beginning of the queue
alreadySeenTrades.remove(trade.id)
alreadySeenTrades.add(trade.id)
return false
} else {
alreadySeenTrades.add(trade.id)
return true
}
}
}
}
}
================================================
FILE: src/handy.ts
================================================
import crypto, { createHash } from 'crypto'
import { createWriteStream, mkdirSync, rmSync } from 'node:fs'
import { rename } from 'node:fs/promises'
import type { RequestOptions, Agent } from 'https'
import followRedirects from 'follow-redirects'
import * as httpsProxyAgentPkg from 'https-proxy-agent'
import path from 'path'
import { debug } from './debug.ts'
import { Mapper } from './mappers/index.ts'
import { Disconnect, Exchange, Filter, FilterForExchange } from './types.ts'
import * as socksProxyAgentPkg from 'socks-proxy-agent'
const { http, https } = followRedirects
const { HttpsProxyAgent } = httpsProxyAgentPkg
const { SocksProxyAgent } = socksProxyAgentPkg
export function parseAsUTCDate(val: string) {
// Treat date-only and minute-level strings as UTC instead of local time.
if (val.endsWith('Z') === false) {
val += 'Z'
}
const date = new Date(val)
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes()))
}
export function wait(delayMS: number) {
return new Promise((resolve) => {
setTimeout(resolve, delayMS)
})
}
export function getRandomString() {
return crypto.randomBytes(24).toString('hex')
}
export function formatDateToPath(date: Date) {
const year = date.getUTCFullYear()
const month = doubleDigit(date.getUTCMonth() + 1)
const day = doubleDigit(date.getUTCDate())
const hour = doubleDigit(date.getUTCHours())
const minute = doubleDigit(date.getUTCMinutes())
return `${year}/${month}/${day}/${hour}/${minute}`
}
export function doubleDigit(input: number) {
return input < 10 ? '0' + input : '' + input
}
export function sha256(obj: object) {
return createHash('sha256').update(JSON.stringify(obj)).digest('hex')
}
export function addMinutes(date: Date, minutes: number) {
return new Date(date.getTime() + minutes * 60000)
}
export function addDays(date: Date, days: number) {
return new Date(date.getTime() + days * 60000 * 1440)
}
export function* sequence(end: number, seed = 0) {
let current = seed
while (current < end) {
yield current
current += 1
}
return
}
export const ONE_SEC_IN_MS = 1000
export class HttpError extends Error {
constructor(public readonly status: number, public readonly responseText: string, public readonly url: string) {
super(`HttpError: status code: ${status}, response text: ${responseText}`)
}
}
class HttpClientError extends Error {
constructor(public readonly response: HttpResponse, public readonly method: string, public readonly url: string) {
super(`HTTP ${method} ${url} failed with status ${response.statusCode}`)
}
}
export function* take(iterable: Iterable<any>, length: number) {
if (length === 0) {
return
}
for (const item of iterable) {
yield item
length--
if (length === 0) {
return
}
}
}
export async function* normalizeMessages(
exchange: Exchange,
symbols: string[] | undefined,
messages: AsyncIterableIterator<{ localTimestamp: Date; message: any } | undefined>,
mappers: Mapper<any, any>[],
createMappers: (localTimestamp: Date) => Mapper<any, any>[],
withDisconnectMessages: boolean | undefined,
filter?: (symbol: string) => boolean,
currentTimestamp?: Date | undefined
) {
let previousLocalTimestamp: Date | undefined = currentTimestamp
let mappersForExchange: Mapper<any, any>[] | undefined = mappers
if (mappersForExchange.length === 0) {
throw new Error(`Can't normalize data without any normalizers provided`)
}
for await (const messageWithTimestamp of messages) {
if (messageWithTimestamp === undefined) {
// we received undefined meaning Websocket disconnection
// lets create new mappers with clean state for 'new connection'
mappersForExchange = undefined
// if flag withDisconnectMessages is set, yield disconnect message
if (withDisconnectMessages === true && previousLocalTimestamp !== undefined) {
const disconnect: Disconnect = {
type: 'disconnect',
exchange,
localTimestamp: previousLocalTimestamp,
symbols
}
yield disconnect as any
}
continue
}
if (mappersForExchange === undefined) {
mappersForExchange = createMappers(messageWithTimestamp.localTimestamp)
}
previousLocalTimestamp = messageWithTimestamp.localTimestamp
for (const mapper of mappersForExchange) {
if (mapper.canHandle(messageWithTimestamp.message)) {
const mappedMessages = mapper.map(messageWithTimestamp.message, messageWithTimestamp.localTimestamp)
if (!mappedMessages) {
continue
}
for (const message of mappedMessages) {
if (filter === undefined) {
yield message
} else if (filter(message.symbol)) {
yield message
}
}
}
}
}
}
export function getFilters<T extends Exchange>(mappers: Mapper<T, any>[], symbols?: string[]) {
const filters = mappers.flatMap((mapper) => mapper.getFilters(symbols))
const deduplicatedFilters = filters.reduce((prev, current) => {
const matchingExisting = prev.find((c) => c.channel === current.channel)
if (matchingExisting !== undefined) {
if (matchingExisting.symbols !== undefined && current.symbols) {
for (let symbol of current.symbols) {
if (matchingExisting.symbols.includes(symbol) === false) {
matchingExisting.symbols.push(symbol)
}
}
} else if (current.symbols) {
matchingExisting.symbols = [...current.symbols]
}
} else {
prev.push(current)
}
return prev
}, [] as FilterForExchange[T][])
return deduplicatedFilters
}
export function* batch(symbols: string[], batchSize: number) {
for (let i = 0; i < symbols.length; i += batchSize) {
yield symbols.slice(i, i + batchSize)
}
}
export function* batchObjects<T>(payload: T[], batchSize: number) {
for (let i = 0; i < payload.length; i += batchSize) {
yield payload.slice(i, i + batchSize)
}
}
export function parseμs(dateString: string): number {
// check if we have ISO 8601 format date string, e.g: 2019-06-01T00:03:03.1238784Z or 2020-07-22T00:09:16.836773Z
// or 2020-03-01T00:00:24.893456+00:00
if (dateString.length === 27 || dateString.length === 28 || dateString.length === 32 || dateString.length === 30) {
return Number(dateString.slice(23, 26))
}
return 0
}
export function optimizeFilters(filters: Filter<any>[]) {
// deduplicate filters (if the channel was provided multiple times)
const optimizedFilters = filters.reduce((prev, current) => {
const matchingExisting = prev.find((c) => c.channel === current.channel)
if (matchingExisting) {
// both previous and current have symbols let's merge them
if (matchingExisting.symbols && current.symbols) {
matchingExisting.symbols.push(...current.symbols)
} else if (current.symbols) {
matchingExisting.symbols = [...current.symbols]
}
} else {
prev.push(current)
}
return prev
}, [] as Filter<any>[])
// sort filters in place to improve local disk cache ratio (no matter filters order if the same filters are provided will hit the cache)
optimizedFilters.sort((f1, f2) => {
if (f1.channel < f2.channel) {
return -1
}
if (f1.channel > f2.channel) {
return 1
}
return 0
})
// sort and deduplicate filters symbols
optimizedFilters.forEach((filter) => {
if (filter.symbols) {
filter.symbols = [...new Set(filter.symbols)].sort()
}
})
return optimizedFilters
}
const httpsAgent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 10 * ONE_SEC_IN_MS,
maxSockets: 120
})
export const httpsProxyAgent: Agent | undefined =
process.env.HTTP_PROXY !== undefined
? new HttpsProxyAgent(process.env.HTTP_PROXY)
: process.env.SOCKS_PROXY !== undefined
? new SocksProxyAgent(process.env.SOCKS_PROXY)
: undefined
const DEFAULT_FETCH_RETRY_LIMIT = 2
type HttpRetryOptions =
| number
| {
limit?: number
statusCodes?: number[]
maxRetryAfter?: number
}
type HttpRequestOptions = {
headers?: Record<string, string>
body?: string | object
timeout?: number
retry?: HttpRetryOptions
}
type HttpResponse = {
statusCode: number
headers: Record<string, string>
body: string
}
type JSONResponse<T> = {
data: T
headers: Record<string, string>
statusCode: number
}
type RetrySettings = {
limit: number
maxRetryAfter?: number
statusCodes?: Set<number>
}
function getRetrySettings(method: string, retry?: HttpRetryOptions): RetrySettings {
const retryOptions = typeof retry === 'object' ? retry : undefined
const retryEnabled = method === 'GET' || retry !== undefined
const limit = typeof retry === 'number' ? retry : retryOptions?.limit ?? (retryEnabled ? DEFAULT_FETCH_RETRY_LIMIT : 0)
return {
limit,
maxRetryAfter: retryOptions?.maxRetryAfter,
statusCodes: retryOptions?.statusCodes ? new Set(retryOptions.statusCodes) : undefined
}
}
function parseResponseHeaders(headers: Headers) {
return Object.fromEntries(headers.entries())
}
function parseNodeResponseHeaders(headers: Record<string, string | string[] | undefined>) {
return Object.fromEntries(
Object.entries(headers).flatMap(([key, value]) => {
if (value === undefined) {
return []
}
return [[key.toLowerCase(), Array.isArray(value) ? value.join(', ') : value]]
})
)
}
function createHttpResponse(statusCode: number, headers: Record<string, string>, body: string): HttpResponse {
return {
statusCode,
headers,
body
}
}
function prepareRequest(method: string, options: HttpRequestOptions) {
if (options.body === undefined) {
return {
headers: options.headers,
body: undefined
}
}
const headers = { ...options.headers }
const body = typeof options.body === 'string' ? options.body : JSON.stringify(options.body)
if (method !== 'GET' && headers['Content-Type'] === undefined && headers['content-type'] === undefined) {
headers['Content-Type'] = 'application/json'
}
return {
headers,
body
}
}
function getRetryAfterDelayMS(headers: Record<string, string>, maxRetryAfter?: number) {
const retryAfterHeader = headers['retry-after']
if (retryAfterHeader === undefined) {
return
}
const parsedSeconds = Number.parseFloat(retryAfterHeader)
let delayMS: number | undefined
if (Number.isFinite(parsedSeconds)) {
delayMS = parsedSeconds * ONE_SEC_IN_MS
} else {
const parsedDate = Date.parse(retryAfterHeader)
if (Number.isFinite(parsedDate)) {
delayMS = parsedDate - Date.now()
}
}
if (delayMS === undefined || delayMS < 0) {
return
}
if (maxRetryAfter !== undefined && delayMS > maxRetryAfter) {
return
}
return delayMS
}
function getRetryDelayMS(attempt: number, headers: Record<string, string>, maxRetryAfter?: number) {
const retryAfterDelayMS = getRetryAfterDelayMS(headers, maxRetryAfter)
if (retryAfterDelayMS !== undefined) {
return retryAfterDelayMS
}
return Math.min(250 * 2 ** (attempt - 1), 5000)
}
function isRetryableStatus(statusCode: number, retrySettings: RetrySettings) {
if (retrySettings.statusCodes !== undefined) {
return retrySettings.statusCodes.has(statusCode)
}
return statusCode === 408 || statusCode === 429 || statusCode >= 500
}
function shouldRetryHttpStatus(attempt: number, response: HttpResponse, retrySettings: RetrySettings) {
return attempt <= retrySettings.limit && isRetryableStatus(response.statusCode, retrySettings)
}
function shouldRetryHttpError(attempt: number, retrySettings: RetrySettings) {
return attempt <= retrySettings.limit
}
async function requestViaFetch(method: string, url: string, options: HttpRequestOptions): Promise<HttpResponse> {
const controller = new AbortController()
const timeoutMS = options.timeout
const timeoutId = timeoutMS !== undefined ? setTimeout(() => controller.abort(), timeoutMS) : undefined
const preparedRequest = prepareRequest(method, options)
try {
const response = await fetch(url, {
method,
headers: preparedRequest.headers,
body: preparedRequest.body,
signal: controller.signal
})
const body = await response.text()
return createHttpResponse(response.status, parseResponseHeaders(response.headers), body)
} catch (error) {
if (controller.signal.aborted) {
throw new Error('Request timed out')
}
throw error
} finally {
if (timeoutId !== undefined) {
clearTimeout(timeoutId)
}
}
}
async function requestViaProxy(method: string, url: string, options: HttpRequestOptions): Promise<HttpResponse> {
const requestClient = new URL(url).protocol === 'http:' ? http : https
const preparedRequest = prepareRequest(method, options)
return await new Promise<HttpResponse>((resolve, reject) => {
const request = requestClient
.request(
url,
{
method,
agent: httpsProxyAgent,
headers: preparedRequest.headers,
timeout: options.timeout
},
(response) => {
response.setEncoding('utf8')
let body = ''
response.on('error', reject)
response.on('data', (chunk) => (body += chunk))
response.on('end', () => {
resolve(createHttpResponse(response.statusCode ?? 0, parseNodeResponseHeaders(response.headers), body))
})
}
)
.on('error', reject)
.on('timeout', () => {
reject(new Error('Request timed out'))
request.destroy()
})
if (preparedRequest.body !== undefined) {
request.write(preparedRequest.body)
}
request.end()
})
}
async function request(method: string, url: string, options: HttpRequestOptions = {}) {
const retrySettings = getRetrySettings(method, options.retry)
for (let attempt = 1; ; attempt += 1) {
try {
const response =
httpsProxyAgent === undefined ? await requestViaFetch(method, url, options) : await requestViaProxy(method, url, options)
if (response.statusCode >= 200 && response.statusCode < 300) {
return response
}
if (shouldRetryHttpStatus(attempt, response, retrySettings)) {
await wait(getRetryDelayMS(attempt, response.headers, retrySettings.maxRetryAfter))
continue
}
throw new HttpClientError(response, method, url)
} catch (error) {
if (error instanceof HttpClientError) {
throw error
}
if (shouldRetryHttpError(attempt, retrySettings)) {
await wait(Math.min(250 * 2 ** (attempt - 1), 5000))
continue
}
throw error
}
}
}
async function requestJSON<T>(method: string, url: string, options?: HttpRequestOptions): Promise<JSONResponse<T>> {
const response = await request(method, url, options)
return {
data: JSON.parse(response.body) as T,
headers: response.headers,
statusCode: response.statusCode
}
}
export function getJSON<T>(url: string, options?: HttpRequestOptions) {
return requestJSON<T>('GET', url, options)
}
export function postJSON<T>(url: string, options?: HttpRequestOptions) {
return requestJSON<T>('POST', url, options)
}
export async function download({
apiKey,
downloadPath,
url,
userAgent,
appendContentEncodingExtension = false,
acceptEncoding = 'gzip'
}: {
url: string
downloadPath: string
userAgent: string
apiKey: string
appendContentEncodingExtension?: boolean
acceptEncoding?: string
}) {
const httpRequestOptions = {
agent: httpsProxyAgent !== undefined ? httpsProxyAgent : httpsAgent,
timeout: 90 * ONE_SEC_IN_MS,
headers: {
'Accept-Encoding': acceptEncoding,
'User-Agent': userAgent,
Authorization: apiKey ? `Bearer ${apiKey}` : ''
}
}
const MAX_ATTEMPTS = 30
let attempts = 0
while (true) {
// simple retry logic when fetching from the network...
attempts++
try {
const addRetryAttempt = attempts - 1 > 0 && url.endsWith('gz')
if (addRetryAttempt) {
return await _downloadFile(httpRequestOptions, `${url}?retryAttempt=${attempts - 1}`, downloadPath, appendContentEncodingExtension)
} else {
return await _downloadFile(httpRequestOptions, url, downloadPath, appendContentEncodingExtension)
}
} catch (error) {
const unsupportedDataFeedEncoding = error instanceof Error && error.message.startsWith('Unsupported data feed content encoding')
const badOrUnauthorizedRequest =
error instanceof HttpError &&
((error.status === 400 && error.message.includes('ISO 8601 format') === false) || error.status === 401)
const tooManyRequests = error instanceof HttpError && error.status === 429
const internalServiceError = error instanceof HttpError && error.status === 500
// do not retry when we've got bad or unauthorized request or enough attempts
if (unsupportedDataFeedEncoding || badOrUnauthorizedRequest || attempts === MAX_ATTEMPTS) {
throw error
}
const randomIngridient = Math.random() * 500
const attemptsDelayMS = Math.min(Math.pow(2, attempts) * ONE_SEC_IN_MS, 120 * ONE_SEC_IN_MS)
let nextAttemptDelayMS = randomIngridient + attemptsDelayMS
if (tooManyRequests) {
// when too many requests received wait one minute
nextAttemptDelayMS += 60 * ONE_SEC_IN_MS
}
if (internalServiceError) {
nextAttemptDelayMS = nextAttemptDelayMS * 2
}
debug('download file error: %o, next attempt delay: %d, url %s, path: %s', error, nextAttemptDelayMS, url, downloadPath)
await wait(nextAttemptDelayMS)
}
}
}
const tmpFileCleanups = new Map<string, () => void>()
export function cleanTempFiles() {
tmpFileCleanups.forEach((cleanup) => cleanup())
}
async function _downloadFile(requestOptions: RequestOptions, url: string, downloadPath: string, appendContentEncodingExtension: boolean) {
// first ensure that directory where we want to download file exists
mkdirSync(path.dirname(downloadPath), { recursive: true })
// create write file stream that we'll write data into - first as unconfirmed temp file
const tmpFilePath = `${downloadPath}${crypto.randomBytes(8).toString('hex')}.unconfirmed`
const fileWriteStream = createWriteStream(tmpFilePath)
const cleanup = () => {
try {
fileWriteStream.destroy()
rmSync(tmpFilePath, { force: true })
} catch {}
}
tmpFileCleanups.set(tmpFilePath, cleanup)
let finalDownloadPath = downloadPath
try {
// based on https://github.com/nodejs/node/issues/28172 - only reliable way to consume response stream and avoiding all the 'gotchas'
await new Promise<void>((resolve, reject) => {
const req = https
.get(url, requestOptions, (res) => {
const { statusCode } = res
if (statusCode !== 200) {
// read the error response text and throw it as an HttpError
res.setEncoding('utf8')
let body = ''
res.on('error', reject)
res.on('data', (chunk) => (body += chunk))
res.on('end', () => {
reject(new HttpError(statusCode!, body, url))
})
} else {
if (appendContentEncodingExtension) {
const contentEncoding = asSingleHeaderValue(res.headers['content-encoding'])
if (contentEncoding === 'zstd') {
finalDownloadPath = `${downloadPath}.zst`
} else if (contentEncoding === undefined || contentEncoding === 'gzip') {
finalDownloadPath = `${downloadPath}.gz`
} else {
reject(new Error(`Unsupported data feed content encoding: ${contentEncoding}`))
return
}
}
// consume the response stream by writing it to the file
res
.on('error', reject)
.on('aborted', () => reject(new Error('Request aborted')))
.pipe(fileWriteStream)
.on('error', reject)
.on('finish', () => {
if (res.complete) {
resolve()
} else {
reject(new Error('The connection was terminated while the message was still being sent'))
}
})
}
})
.on('error', reject)
.on('timeout', () => {
debug('download file request timeout, %s', url)
reject(new Error('Request timed out'))
req.destroy()
})
})
// finally when saving from the network to file has succeded, rename tmp file to normal name
// then we're sure that responses is 100% saved and also even if different process was doing the same we're good
await rename(tmpFilePath, finalDownloadPath)
return {
downloadPath: finalDownloadPath
}
} finally {
tmpFileCleanups.delete(tmpFilePath)
cleanup()
}
}
function asSingleHeaderValue(headerValue: string | string[] | undefined) {
if (Array.isArray(headerValue)) {
return headerValue[0]
}
return headerValue
}
export class CircularBuffer<T> {
private _buffer: T[] = []
private _index: number = 0
constructor(private readonly _bufferSize: number) {}
append(value: T) {
const isFull = this._buffer.length === this._bufferSize
let poppedValue
if (isFull) {
poppedValue = this._buffer[this._index]
}
this._buffer[this._index] = value
this._index = (this._index + 1) % this._bufferSize
return poppedValue
}
*items() {
for (let i = 0; i < this._buffer.length; i++) {
const index = (this._index + i) % this._buffer.length
yield this._buffer[index]
}
}
get count() {
return this._buffer.length
}
clear() {
this._buffer = []
this._index = 0
}
}
export class CappedSet<T> {
private _set = new Set<T>()
constructor(private readonly _maxSize: number) {}
public has(value: T) {
return this._set.has(value)
}
public add(value: T) {
if (this._set.size >= this._maxSize) {
this._set.delete(this._set.keys().next().value!)
}
this._set.add(value)
}
public remove(value: T) {
this._set.delete(value)
}
public size() {
return this._set.size
}
}
function hasFraction(n: number) {
return Math.abs(Math.round(n) - n) > 1e-10
}
// https://stackoverflow.com/a/44815797
export function decimalPlaces(n: number) {
let count = 0
// multiply by increasing powers of 10 until the fractional part is ~ 0
while (hasFraction(n * 10 ** count) && isFinite(10 ** count)) count++
return count
}
export function asNumberIfValid(val: string | number | undefined | null) {
if (val === undefined || val === null) {
return
}
var asNumber = Number(val)
if (isNaN(asNumber) || isFinite(asNumber) === false) {
return
}
if (asNumber === 0) {
return
}
return asNumber
}
export function upperCaseSymbols(symbols?: string[]) {
if (symbols !== undefined) {
return symbols.map((s) => s.toUpperCase())
}
return
}
export function lowerCaseSymbols(symbols?: string[]) {
if (symbols !== undefined) {
return symbols.map((s) => s.toLowerCase())
}
return
}
export const fromMicroSecondsToDate = (micros: number) => {
const isMicroseconds = micros > 1e15 // Check if the number is likely in microseconds
if (!isMicroseconds) {
return new Date(micros)
}
const timestamp = new Date(micros / 1000)
timestamp.μs = micros % 1000
return timestamp
}
export function onlyUnique(value: string, index: number, array: string[]) {
return array.indexOf(value) === index
}
================================================
FILE: src/index.ts
================================================
export * from './apikeyaccessinfo.ts'
export * from './clearcache.ts'
export * from './combine.ts'
export * from './computable/index.ts'
export * from './consts.ts'
export * from './exchangedetails.ts'
export * from './mappers/index.ts'
export { init } from './options.ts'
export * from './orderbook.ts'
export * from './realtimefeeds/index.ts'
export * from './replay.ts'
export * from './stream.ts'
export * from './downloaddatasets.ts'
export * from './types.ts'
export * from './filter.ts'
export * from './instrumentinfo.ts'
================================================
FILE: src/instrumentinfo.ts
================================================
import { getOptions } from './options.ts'
import type { SymbolType } from './exchangedetails.ts'
import type { Exchange } from './types.ts'
import { getJSON } from './handy.ts'
export async function getInstrumentInfo(exchange: Exchange): Promise<InstrumentInfo[]>
export async function getInstrumentInfo(exchange: Exchange | Exchange[], filter: InstrumentInfoFilter): Promise<InstrumentInfo[]>
export async function getInstrumentInfo(exchange: Exchange, symbol: string): Promise<InstrumentInfo>
export async function getInstrumentInfo(exchange: Exchange | Exchange[], filterOrSymbol?: InstrumentInfoFilter | string) {
if (Array.isArray(exchange)) {
const exchanges = exchange
const results = await Promise.all(exchanges.map((e) => getInstrumentInfoForExchange(e, filterOrSymbol)))
return results.flat()
} else {
return getInstrumentInfoForExchange(exchange, filterOrSymbol)
}
}
async function getInstrumentInfoForExchange(exchange: Exchange, filterOrSymbol?: InstrumentInfoFilter | string) {
const options = getOptions()
let url = `${options.endpoint}/instruments/${exchange}`
if (typeof filterOrSymbol === 'string') {
url += `/${encodeURIComponent(filterOrSymbol)}`
} else if (typeof filterOrSymbol === 'object') {
url += `?filter=${encodeURIComponent(JSON.stringify(filterOrSymbol))}`
}
try {
const { data } = await getJSON(url, {
headers: { Authorization: `Bearer ${options.apiKey}` }
})
return data
} catch (e: any) {
// expose 400 error message from server
if (e.response?.statusCode === 400) {
let err: { code: Number; message: string }
try {
err = JSON.parse(e.response.body)
} catch {
throw e
}
throw err ? new Error(`${err.message} (${err.code})`) : e
} else {
throw e
}
}
}
type InstrumentInfoFilter = {
baseCurrency?: string | string[]
quoteCurrency?: string | string[]
type?: SymbolType | SymbolType[]
contractType?: ContractType | ContractType[]
active?: boolean
}
export type ContractType =
| 'move'
| 'linear_future'
| 'inverse_future'
| 'quanto_future'
| 'linear_perpetual'
| 'inverse_perpetual'
| 'quanto_perpetual'
| 'put_option'
| 'call_option'
| 'turbo_put_option'
| 'turbo_call_option'
| 'spread'
| 'interest_rate_swap'
| 'repo'
| 'index'
export interface InstrumentInfo {
/** symbol id */
id: string
/** dataset symbol id, may differ from id */
datasetId?: string
/** exchange id */
exchange: string
/** normalized, so for example bitmex XBTUSD has base currency set to BTC not XBT */
baseCurrency: string
/** normalized, so for example bitfinex BTCUST has quote currency set to USDT, not UST */
quoteCurrency: string
type: SymbolType
/** derivative contract type */
contractType?: ContractType
/** indicates if the instrument can currently be traded. */
active: boolean
/** date in ISO format */
availableSince: string
/** date in ISO format */
availableTo?: string
/** date in ISO format, when the instrument was first listed on the exchange */
listing?: string
/** in ISO format, only for futures and options */
expiry?: string
/** expiration schedule type */
expirationType?: 'daily' | 'weekly' | 'next_week' | 'quarter' | 'next_quarter'
/** the underlying index for derivatives */
underlyingIndex?: string
/** price tick size, price precision can be calculated from it */
priceIncrement: number
/** amount tick size, amount/size precision can be calculated from it */
amountIncrement: number
/** min order size */
minTradeAmount: number
/** minimum notional value */
minNotional?: number
/** consider it as illustrative only, as it depends in practice on account traded volume levels, different categories, VIP levels, owning exchange currency etc */
makerFee: number
/** consider it as illustrative only, as it depends in practice on account traded volume levels, different categories, VIP levels, owning exchange currency etc */
takerFee: number
/** only for derivatives */
inverse?: boolean
/** only for derivatives */
contractMultiplier?: number
/** only for quanto instruments */
quanto?: boolean
/** only for quanto instruments as settlement currency is different base/quote currency */
settlementCurrency?: string
/** strike price, only for options */
strikePrice?: number
/** option type, only for options */
optionType?: 'call' | 'put'
/** margin mode */
marginMode?: 'isolated' | 'cross'
/** whether margin trading is supported (spot) */
margin?: boolean
/** if this instrument is an alias for another */
aliasFor?: string
/** historical changes to instrument parameters */
changes?: {
until: string
priceIncrement?: number
amountIncrement?: number
contractMultiplier?: number
minTradeAmount?: number
makerFee?: number
takerFee?: number
quanto?: boolean
inverse?: boolean
settlementCurrency?: string
underlyingIndex?: string
contractType?: ContractType
quoteCurrency?: string
type?: string
}[]
}
================================================
FILE: src/mappers/ascendex.ts
================================================
import { upperCaseSymbols } from '../handy.ts'
import { BookChange, BookTicker, DerivativeTicker, Trade } from '../types.ts'
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
export class AscendexTradesMapper implements Mapper<'ascendex', Trade> {
canHandle(message: AscendexTrade) {
return message.m === 'trades'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'trades',
symbols
} as const
]
}
*map(message: AscendexTrade, localTimestamp: Date): IterableIterator<Trade> {
for (let trade of message.data) {
yield {
type: 'trade',
symbol: message.symbol,
exchange: 'ascendex',
id: undefined,
price: Number(trade.p),
amount: Number(trade.q),
side: trade.bm === true ? 'sell' : 'buy',
timestamp: new Date(trade.ts),
localTimestamp: localTimestamp
}
}
}
}
export class AscendexBookChangeMapper implements Mapper<'ascendex', BookChange> {
canHandle(message: AscendexDepthRealTime | AscendexDepthRealTimeSnapshot) {
return message.m === 'depth-realtime' || message.m === 'depth-snapshot-realtime'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'depth-realtime',
symbols
} as const,
{
channel: 'depth-snapshot-realtime',
symbols
} as const
]
}
*map(message: AscendexDepthRealTime | AscendexDepthRealTimeSnapshot, localTimestamp: Date): IterableIterator<BookChange> {
if (!message.symbol || !message.data.bids || !message.data.asks) {
return
}
yield {
type: 'book_change',
symbol: message.symbol,
exchange: 'ascendex',
isSnapshot: message.m === 'depth-snapshot-realtime',
bids: message.data.bids.map(this.mapBookLevel),
asks: message.data.asks.map(this.mapBookLevel),
timestamp: message.data.ts > 0 ? new Date(message.data.ts) : localTimestamp,
localTimestamp
}
}
protected mapBookLevel(level: AscendexPriceLevel) {
const price = Number(level[0])
const amount = Number(level[1])
return { price, amount }
}
}
export class AscendexDerivativeTickerMapper implements Mapper<'ascendex', DerivativeTicker> {
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
canHandle(message: AscendexFuturesData | AscendexTrade) {
return message.m === 'futures-pricing-data' || message.m === 'trades'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'futures-pricing-data',
symbols: [] as string[]
} as const,
{
channel: 'trades',
symbols
} as const
]
}
*map(message: AscendexFuturesData | AscendexTrade, localTimestamp: Date): IterableIterator<DerivativeTicker> {
if (message.m === 'trades') {
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(message.symbol, 'ascendex')
pendingTickerInfo.updateLastPrice(Number(message.data[message.data.length - 1].p))
return
}
for (const futuresData of message.con) {
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(futuresData.s, 'ascendex')
pendingTickerInfo.updateIndexPrice(Number(futuresData.ip))
pendingTickerInfo.updateMarkPrice(Number(futuresData.mp))
pendingTickerInfo.updateOpenInterest(Number(futuresData.oi))
pendingTickerInfo.updateTimestamp(new Date(futuresData.t))
pendingTickerInfo.updateFundingTimestamp(new Date(futuresData.f))
pendingTickerInfo.updateFundingRate(Number(futuresData.r))
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp)
}
}
}
}
export class AscendexBookTickerMapper implements Mapper<'ascendex', BookTicker> {
canHandle(message: AscendexTicker) {
return message.m === 'bbo'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'bbo',
symbols
} as const
]
}
*map(message: AscendexTicker, localTimestamp: Date): IterableIterator<BookTicker> {
const ask = message.data.ask
const bid = message.data.bid
yield {
type: 'book_ticker',
symbol: message.symbol,
exchange: 'ascendex',
askAmount: ask !== undefined && ask[1] !== undefined ? Number(ask[1]) : undefined,
askPrice: ask !== undefined && ask[0] !== undefined ? Number(ask[0]) : undefined,
bidPrice: bid !== undefined && bid[0] !== undefined ? Number(bid[0]) : undefined,
bidAmount: bid !== undefined && bid[1] !== undefined ? Number(bid[1]) : undefined,
timestamp: new Date(message.data.ts),
localTimestamp: localTimestamp
}
}
}
type AscendexTrade = {
m: 'trades'
symbol: string
data: [{ p: string; q: string; ts: number; bm: boolean; seqnum: number }]
}
type AscendexPriceLevel = [string, string]
type AscendexDepthRealTime = {
m: 'depth-realtime'
symbol: 'XRP/USDT'
data: { ts: 1621814400204; seqnum: 39862426; asks: AscendexPriceLevel[]; bids: AscendexPriceLevel[] }
}
type AscendexDepthRealTimeSnapshot = {
m: 'depth-snapshot-realtime'
symbol: 'XRP/USDT'
data: {
ts: 0
seqnum: 39862426
asks: AscendexPriceLevel[]
bids: AscendexPriceLevel[]
}
}
type AscendexTicker = { m: 'bbo'; symbol: string; data: { ts: number; bid?: AscendexPriceLevel; ask?: AscendexPriceLevel } }
type AscendexFuturesData = {
m: 'futures-pricing-data'
con: [
{
t: 1621814404114
s: 'BTC-PERP'
mp: '34878.075977904'
ip: '34697.17'
oi: '80.6126'
r: '0.000093633'
f: 1621843200000
fi: 28800000
}
]
}
================================================
FILE: src/mappers/binance.ts
================================================
import { debug } from '../debug.ts'
import { CircularBuffer, fromMicroSecondsToDate, lowerCaseSymbols } from '../handy.ts'
import { BookChange, BookTicker, DerivativeTicker, Exchange, FilterForExchange, Liquidation, Trade } from '../types.ts'
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
// https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md
export class BinanceTradesMapper
implements Mapper<'binance' | 'binance-jersey' | 'binance-us' | 'binance-futures' | 'binance-delivery', Trade>
{
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.endsWith('@trade')
}
getFilters(symbols?: string[]) {
symbols = lowerCaseSymbols(symbols)
return [
{
channel: 'trade',
symbols
} as const
]
}
*map(binanceTradeResponse: BinanceResponse<BinanceTradeData>, localTimestamp: Date) {
const binanceTrade = binanceTradeResponse.data
const isOffBookTrade = binanceTrade.X === 'INSURANCE_FUND' || binanceTrade.X === 'ADL' || binanceTrade.X === 'NA'
if (isOffBookTrade) {
return
}
const trade: Trade = {
type: 'trade',
symbol: binanceTrade.s,
exchange: this._exchange,
id: String(binanceTrade.t),
price: Number(binanceTrade.p),
amount: Number(binanceTrade.q),
side: binanceTrade.m ? 'sell' : 'buy',
timestamp: fromMicroSecondsToDate(binanceTrade.T),
localTimestamp: localTimestamp
}
yield trade
}
}
export class BinanceBookChangeMapper
implements Mapper<'binance' | 'binance-jersey' | 'binance-us' | 'binance-futures' | 'binance-delivery', BookChange>
{
protected readonly symbolToDepthInfoMapping: {
[key: string]: LocalDepthInfo
} = {}
constructor(protected readonly exchange: Exchange, protected readonly ignoreBookSnapshotOverlapError: boolean) {}
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.includes('@depth')
}
getFilters(symbols?: string[]) {
symbols = lowerCaseSymbols(symbols)
return [
{
channel: 'depth',
symbols
} as const,
{
channel: 'depthSnapshot',
symbols
} as const
]
}
*map(message: BinanceResponse<BinanceDepthData | BinanceDepthSnapshotData>, localTimestamp: Date) {
const symbol = message.stream.split('@')[0].toUpperCase()
if (this.symbolToDepthInfoMapping[symbol] === undefined) {
this.symbolToDepthInfoMapping[symbol] = {
bufferedUpdates: new CircularBuffer<BinanceDepthData>(2000)
}
}
const symbolDepthInfo = this.symbolToDepthInfoMapping[symbol]
const snapshotAlreadyProcessed = symbolDepthInfo.snapshotProcessed
// first check if received message is snapshot and process it as such if it is
if (message.data.lastUpdateId !== undefined) {
// if we've already received 'manual' snapshot, ignore if there is another one
if (snapshotAlreadyProcessed) {
return
}
// produce snapshot book_change
const binanceDepthSnapshotData = message.data
// mark given symbol depth info that has snapshot processed
symbolDepthInfo.lastUpdateId = binanceDepthSnapshotData.lastUpdateId
symbolDepthInfo.snapshotProcessed = true
// if there were any depth updates buffered, let's proccess those by adding to or updating the initial snapshot
for (const update of symbolDepthInfo.bufferedUpdates.items()) {
const bookChange = this.mapBookDepthUpdate(update, localTimestamp)
if (bookChange !== undefined) {
for (const bid of update.b) {
const matchingBid = binanceDepthSnapshotData.bids.find((b) => b[0] === bid[0])
if (matchingBid !== undefined) {
matchingBid[1] = bid[1]
} else {
binanceDepthSnapshotData.bids.push(bid)
}
}
for (const ask of update.a) {
const matchingAsk = binanceDepthSnapshotData.asks.find((a) => a[0] === ask[0])
if (matchingAsk !== undefined) {
matchingAsk[1] = ask[1]
} else {
binanceDepthSnapshotData.asks.push(ask)
}
}
}
}
// remove all buffered updates
symbolDepthInfo.bufferedUpdates.clear()
const bookChange: BookChange = {
type: 'book_change',
symbol,
exchange: this.exchange,
isSnapshot: true,
bids: binanceDepthSnapshotData.bids.map(this.mapBookLevel),
asks: binanceDepthSnapshotData.asks.map(this.mapBookLevel),
timestamp: binanceDepthSnapshotData.T !== undefined ? fromMicroSecondsToDate(binanceDepthSnapshotData.T) : localTimestamp,
localTimestamp
}
yield bookChange
} else if (snapshotAlreadyProcessed) {
// snapshot was already processed let's map the message as normal book_change
const bookChange = this.mapBookDepthUpdate(message.data as BinanceDepthData, localTimestamp)
if (bookChange !== undefined) {
yield bookChange
}
} else {
const binanceDepthUpdateData = message.data as BinanceDepthData
symbolDepthInfo.bufferedUpdates.append(binanceDepthUpdateData)
}
}
protected mapBookDepthUpdate(binanceDepthUpdateData: BinanceDepthData, localTimestamp: Date): BookChange | undefined {
// we can safely assume here that depthContext and lastUpdateId aren't null here as this is method only works
// when we've already processed the snapshot
const depthContext = this.symbolToDepthInfoMapping[binanceDepthUpdateData.s]!
const lastUpdateId = depthContext.lastUpdateId!
// Drop any event where u is <= lastUpdateId in the snapshot
if (binanceDepthUpdateData.u <= lastUpdateId) {
return
}
// The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1.
if (!depthContext.validatedFirstUpdate) {
// if there is new instrument added it can have empty book at first and that's normal
const bookSnapshotIsEmpty = lastUpdateId == -1
if ((binanceDepthUpdateData.U <= lastUpdateId + 1 && binanceDepthUpdateData.u >= lastUpdateId + 1) || bookSnapshotIsEmpty) {
depthContext.validatedFirstUpdate = true
} else {
const message = `Book depth snaphot has no overlap with first update, update ${JSON.stringify(
binanceDepthUpdateData
)}, lastUpdateId: ${lastUpdateId}, exchange ${this.exchange}`
if (this.ignoreBookSnapshotOverlapError) {
depthContext.validatedFirstUpdate = true
debug(message)
} else {
throw new Error(message)
}
}
}
return {
type: 'book_change',
symbol: binanceDepthUpdateData.s,
exchange: this.exchange,
isSnapshot: false,
bids: binanceDepthUpdateData.b.map(this.mapBookLevel),
asks: binanceDepthUpdateData.a.map(this.mapBookLevel),
timestamp: fromMicroSecondsToDate(binanceDepthUpdateData.E),
localTimestamp: localTimestamp
}
}
protected mapBookLevel(level: BinanceBookLevel) {
const price = Number(level[0])
const amount = Number(level[1])
return { price, amount }
}
}
export class BinanceFuturesBookChangeMapper
extends BinanceBookChangeMapper
implements Mapper<'binance-futures' | 'binance-delivery', BookChange>
{
constructor(protected readonly exchange: Exchange, protected readonly ignoreBookSnapshotOverlapError: boolean) {
super(exchange, ignoreBookSnapshotOverlapError)
}
protected mapBookDepthUpdate(binanceDepthUpdateData: BinanceFuturesDepthData, localTimestamp: Date): BookChange | undefined {
// we can safely assume here that depthContext and lastUpdateId aren't null here as this is method only works
// when we've already processed the snapshot
const depthContext = this.symbolToDepthInfoMapping[binanceDepthUpdateData.s]!
const lastUpdateId = depthContext.lastUpdateId!
// based on https://binanceapitest.github.io/Binance-Futures-API-doc/wss/#how-to-manage-a-local-order-book-correctly
// Drop any event where u is < lastUpdateId in the snapshot
if (binanceDepthUpdateData.u < lastUpdateId) {
return
}
// The first processed should have U <= lastUpdateId AND u >= lastUpdateId
if (!depthContext.validatedFirstUpdate) {
if (binanceDepthUpdateData.U <= lastUpdateId && binanceDepthUpdateData.u >= lastUpdateId) {
depthContext.validatedFirstUpdate = true
} else {
const message = `Book depth snaphot has no overlap with first update, update ${JSON.stringify(
binanceDepthUpdateData
)}, lastUpdateId: ${lastUpdateId}, exchange ${this.exchange}`
if (this.ignoreBookSnapshotOverlapError) {
depthContext.validatedFirstUpdate = true
debug(message)
} else {
throw new Error(message)
}
}
}
return {
type: 'book_change',
symbol: binanceDepthUpdateData.s,
exchange: this.exchange,
isSnapshot: false,
bids: binanceDepthUpdateData.b.map(this.mapBookLevel),
asks: binanceDepthUpdateData.a.map(this.mapBookLevel),
timestamp: fromMicroSecondsToDate(binanceDepthUpdateData.E),
localTimestamp: localTimestamp
}
}
}
export class BinanceFuturesDerivativeTickerMapper implements Mapper<'binance-futures' | 'binance-delivery', DerivativeTicker> {
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
private readonly _indexPrices = new Map<string, number>()
constructor(protected readonly exchange: Exchange) {}
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return (
message.stream.includes('@markPrice') ||
message.stream.endsWith('@ticker') ||
message.stream.endsWith('@openInterest') ||
message.stream.includes('@indexPrice')
)
}
getFilters(symbols?: string[]): FilterForExchange['binance-futures' | 'binance-delivery'][] {
symbols = lowerCaseSymbols(symbols)
const filters = [
{
channel: 'markPrice',
symbols
} as const,
{
channel: 'ticker',
symbols
} as const,
{
channel: 'openInterest',
symbols
} as const
]
if (this.exchange === 'binance-delivery') {
// index channel requires index symbol
filters.push({
channel: 'indexPrice' as any,
symbols: symbols !== undefined ? symbols.map((s) => s.split('_')[0]) : undefined
})
}
return filters
}
*map(
message: BinanceResponse<
BinanceFuturesMarkPriceData | BinanceFuturesTickerData | BinanceFuturesOpenInterestData | BinanceFuturesIndexPriceData
>,
localTimestamp: Date
): IterableIterator<DerivativeTicker> {
if (message.data.e === 'indexPriceUpdate') {
this._indexPrices.set(message.data.i, Number(message.data.p))
} else {
const symbol = 's' in message.data ? message.data.s : message.data.symbol
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(symbol, this.exchange)
const lastIndexPrice = this._indexPrices.get(symbol.split('_')[0])
if (lastIndexPrice !== undefined) {
pendingTickerInfo.updateIndexPrice(lastIndexPrice)
}
if (message.data.e === 'markPriceUpdate') {
if ('r' in message.data && message.data.r !== '' && message.data.T !== 0) {
// only perpetual futures have funding rate info in mark price
// delivery futures sometimes send empty ('') r value
pendingTickerInfo.updateFundingRate(Number(message.data.r))
pendingTickerInfo.updateFundingTimestamp(new Date(message.data.T!))
}
if (message.data.i !== undefined) {
pendingTickerInfo.updateIndexPrice(Number(message.data.i))
}
pendingTickerInfo.updateMarkPrice(Number(message.data.p))
pendingTickerInfo.updateTimestamp(fromMicroSecondsToDate(message.data.E))
}
if (message.data.e === '24hrTicker') {
pendingTickerInfo.updateLastPrice(Number(message.data.c))
pendingTickerInfo.updateTimestamp(fromMicroSecondsToDate(message.data.E))
}
if ('openInterest' in message.data) {
pendingTickerInfo.updateOpenInterest(Number(message.data.openInterest))
}
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp)
}
}
}
}
export class BinanceLiquidationsMapper implements Mapper<'binance-futures' | 'binance-delivery', Liquidation> {
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.endsWith('@forceOrder')
}
getFilters(symbols?: string[]) {
symbols = lowerCaseSymbols(symbols)
return [
{
channel: 'forceOrder',
symbols
} as const
]
}
*map(binanceTradeResponse: BinanceResponse<BinanceFuturesForceOrderData>, localTimestamp: Date) {
const binanceLiquidation = binanceTradeResponse.data.o
// not sure if order status can be different to 'FILLED' for liquidations in practice, but...
if (binanceLiquidation.X !== 'FILLED') {
return
}
const liquidation: Liquidation = {
type: 'liquidation',
symbol: binanceLiquidation.s,
exchange: this._exchange,
id: undefined,
price: Number(binanceLiquidation.p),
amount: Number(binanceLiquidation.z), // Order Filled Accumulated Quantity
side: binanceLiquidation.S === 'SELL' ? 'sell' : 'buy',
timestamp: fromMicroSecondsToDate(binanceLiquidation.T),
localTimestamp: localTimestamp
}
yield liquidation
}
}
export class BinanceBookTickerMapper implements Mapper<'binance-futures' | 'binance-delivery' | 'binance', BookTicker> {
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.endsWith('@bookTicker')
}
getFilters(symbols?: string[]) {
symbols = lowerCaseSymbols(symbols)
return [
{
channel: 'bookTicker',
symbols
} as const
]
}
*map(binanceBookTickerResponse: BinanceResponse<BinanceBookTickerData>, localTimestamp: Date) {
const binanceBookTicker = binanceBookTickerResponse.data
const ticker: BookTicker = {
type: 'book_ticker',
symbol: binanceBookTicker.s,
exchange: this._exchange,
askAmount: binanceBookTicker.A !== undefined ? Number(binanceBookTicker.A) : undefined,
askPrice: binanceBookTicker.a !== undefined ? Number(binanceBookTicker.a) : undefined,
bidPrice: binanceBookTicker.b !== undefined ? Number(binanceBookTicker.b) : undefined,
bidAmount: binanceBookTicker.B !== undefined ? Number(binanceBookTicker.B) : undefined,
timestamp: binanceBookTicker.E !== undefined ? fromMicroSecondsToDate(binanceBookTicker.E) : localTimestamp,
localTimestamp: localTimestamp
}
yield ticker
}
}
type BinanceResponse<T> = {
stream: string
data: T
}
type BinanceTradeData = {
s: string
t: number
p: string
q: string
T: number
m: true
X?: 'INSURANCE_FUND' | 'MARKET' | 'ADL' | 'NA'
}
type BinanceBookLevel = [string, string]
type BinanceDepthData = {
lastUpdateId: undefined
E: number
s: string
U: number
u: number
b: BinanceBookLevel[]
a: BinanceBookLevel[]
}
// T is the time that updated in matching engine, while E is when pushing out from ws server
type BinanceFuturesDepthData = BinanceDepthData & {
pu: number
T: number
}
type BinanceDepthSnapshotData = {
lastUpdateId: number
bids: BinanceBookLevel[]
asks: BinanceBookLevel[]
T?: number
}
type LocalDepthInfo = {
bufferedUpdates: CircularBuffer<BinanceDepthData>
snapshotProcessed?: boolean
lastUpdateId?: number
validatedFirstUpdate?: boolean
}
type BinanceFuturesMarkPriceData = {
e: 'markPriceUpdate'
s: string // Symbol
E: number // Event time
p: string // Mark price
r?: string // Funding rate
T?: number // Next funding time
i?: string
}
type BinanceFuturesTickerData = {
e: '24hrTicker'
E: number // Event time
s: string // Symbol
c: string // Last price
}
type BinanceFuturesOpenInterestData = {
e: undefined
symbol: string
openInterest: string
}
type BinanceFuturesIndexPriceData = {
e: 'indexPriceUpdate' // Event type
E: 1591261236000 // Event time
i: string // Pair
p: string // Index Price
}
type BinanceFuturesForceOrderData = {
o: {
s: string // Symbol
S: string // Side
q: string // Original Quantity
p: string // Price
ap: string // Average Price
X: 'FILLED' // Order Status
l: '0.014' // Order Last Filled Quantity
T: 1568014460893 // Order Trade Time
z: string // Order Filled Accumulated Quantity
}
}
type BinanceBookTickerData = {
u: number // order book updateId
s: string // symbol
b: string // best bid price
B: string // best bid qty
a: string // best ask price
A: string // best ask qty
E?: number // transaction time
}
================================================
FILE: src/mappers/binancedex.ts
================================================
import { upperCaseSymbols } from '../handy.ts'
import { BookChange, BookTicker, Trade } from '../types.ts'
import { Mapper } from './mapper.ts'
// https://docs.binance.org/api-reference/dex-api/ws-streams.html
export const binanceDexTradesMapper: Mapper<'binance-dex', Trade> = {
canHandle(message: BinanceDexResponse<any>) {
return message.stream === 'trades'
},
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'trades',
symbols
}
]
},
*map(binanceDexTradeResponse: BinanceDexResponse<BinanceDexTradeData>, localTimestamp: Date): IterableIterator<Trade> {
for (const binanceDexTrade of binanceDexTradeResponse.data) {
yield {
type: 'trade',
symbol: binanceDexTrade.s,
exchange: 'binance-dex',
id: binanceDexTrade.t,
price: Number(binanceDexTrade.p),
amount: Number(binanceDexTrade.q),
side: binanceDexTrade.tt === 2 ? 'sell' : 'buy',
timestamp: new Date(Math.floor(binanceDexTrade.T / 1000000)),
localTimestamp: localTimestamp
}
}
}
}
const mapBookLevel = (level: BinanceDexBookLevel) => {
const price = Number(level[0])
const amount = Number(level[1])
return { price, amount }
}
export const binanceDexBookChangeMapper: Mapper<'binance-dex', BookChange> = {
canHandle(message: BinanceDexResponse<any>) {
return message.stream === 'marketDiff' || message.stream === 'depthSnapshot'
},
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'depthSnapshot',
symbols
},
{
channel: 'marketDiff',
symbols
}
]
},
*map(
message: BinanceDexResponse<BinanceDexDepthSnapshotData | BinanceDexMarketDiffData>,
localTimestamp: Date
): IterableIterator<BookChange> {
if ('symbol' in message.data) {
// we've got snapshot message
yield {
type: 'book_change',
symbol: message.data.symbol,
exchange: 'binance-dex',
isSnapshot: true,
bids: message.data.bids.map(mapBookLevel),
asks: message.data.asks.map(mapBookLevel),
timestamp: localTimestamp,
localTimestamp
}
} else {
// we've got update
yield {
type: 'book_change',
symbol: message.data.s,
exchange: 'binance-dex',
isSnapshot: false,
bids: message.data.b.map(mapBookLevel),
asks: message.data.a.map(mapBookLevel),
timestamp: localTimestamp,
localTimestamp
}
}
}
}
export const binanceDexBookTickerMapper: Mapper<'binance-dex', BookTicker> = {
canHandle(message: BinanceDexResponse<any>) {
return message.stream === 'ticker'
},
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'ticker',
symbols
}
]
},
*map(binanceDexTradeResponse: BinanceDexResponse<BinanceDexTickerData>, localTimestamp: Date): IterableIterator<BookTicker> {
const binanceDexTicker = binanceDexTradeResponse.data
const ticker: BookTicker = {
type: 'book_ticker',
symbol: binanceDexTicker.s,
exchange: 'binance-dex',
askAmount: binanceDexTicker.A !== undefined ? Number(binanceDexTicker.A) : undefined,
askPrice: binanceDexTicker.a !== undefined ? Number(binanceDexTicker.a) : undefined,
bidPrice: binanceDexTicker.b !== undefined ? Number(binanceDexTicker.b) : undefined,
bidAmount: binanceDexTicker.B !== undefined ? Number(binanceDexTicker.B) : undefined,
timestamp: binanceDexTicker.E !== undefined ? new Date(binanceDexTicker.E * 1000) : localTimestamp,
localTimestamp: localTimestamp
}
yield ticker
}
}
type BinanceDexResponse<T> = {
stream: string
data: T
}
type BinanceDexTradeData = {
s: string // Symbol
t: string // Trade ID
p: string // Price
q: string // Quantity
T: number // Trade time
tt: number //tiekertype 0: Unknown 1: SellTaker 2: BuyTaker 3: BuySurplus 4: SellSurplus 5: Neutral
}[]
type BinanceDexBookLevel = [string, string]
type BinanceDexDepthSnapshotData = {
symbol: string
bids: BinanceDexBookLevel[]
asks: BinanceDexBookLevel[]
}
type BinanceDexMarketDiffData = {
E: number // Event time
s: string // Symbol
b: BinanceDexBookLevel[]
a: BinanceDexBookLevel[]
}
type BinanceDexTickerData = {
e: '24hrTicker' // Event type
E: 123456789 // Event time
s: 'ABC_0DX-BNB' // Symbol
b: '0.0024' // Best bid price
B: '10' // Best bid quantity
a: '0.0026' // Best ask price
A: '100' // Best ask quantity
}
================================================
FILE: src/mappers/binanceeuropeanoptions.ts
================================================
import { asNumberIfValid, lowerCaseSymbols, upperCaseSymbols } from '../handy.ts'
import { BookChange, BookTicker, OptionSummary, Trade } from '../types.ts'
import { Mapper } from './mapper.ts'
// https://binance-docs.github.io/apidocs/voptions/en/#websocket-market-streams
export class BinanceEuropeanOptionsTradesMapper implements Mapper<'binance-european-options', Trade> {
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.endsWith('@trade')
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'trade',
symbols
} as const
]
}
*map(binanceTradeResponse: BinanceResponse<BinanceOptionsTradeData>, localTimestamp: Date) {
const trade: Trade = {
type: 'trade',
symbol: binanceTradeResponse.data.s,
exchange: 'binance-european-options',
id: binanceTradeResponse.data.t,
price: Number(binanceTradeResponse.data.p),
amount: Number(binanceTradeResponse.data.q),
side: binanceTradeResponse.data.S === '-1' ? 'sell' : 'buy',
timestamp: new Date(binanceTradeResponse.data.T),
localTimestamp: localTimestamp
}
yield trade
}
}
export class BinanceEuropeanOptionsTradesMapperV2 implements Mapper<'binance-european-options', Trade> {
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.endsWith('@optionTrade')
}
getFilters(symbols?: string[]) {
symbols = lowerCaseSymbols(symbols)
return [
{
channel: 'optionTrade',
symbols
} as const
]
}
*map(binanceTradeResponse: BinanceResponse<BinanceOptionsTradeDataV2>, localTimestamp: Date) {
const trade: Trade = {
type: 'trade',
symbol: binanceTradeResponse.data.s,
exchange: 'binance-european-options',
id: String(binanceTradeResponse.data.t),
price: Number(binanceTradeResponse.data.p),
amount: Number(binanceTradeResponse.data.q),
side: binanceTradeResponse.data.m ? 'sell' : 'buy',
timestamp: new Date(binanceTradeResponse.data.T),
localTimestamp: localTimestamp
}
yield trade
}
}
export class BinanceEuropeanOptionsBookChangeMapper implements Mapper<'binance-european-options', BookChange> {
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.includes('@depth100')
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'depth100',
symbols
} as const
]
}
*map(message: BinanceResponse<BinanceOptionsDepthData>, localTimestamp: Date) {
const bookChange: BookChange = {
type: 'book_change',
symbol: message.data.s,
exchange: 'binance-european-options',
isSnapshot: true,
bids: message.data.b.map(this.mapBookLevel),
asks: message.data.a.map(this.mapBookLevel),
timestamp: message.data.E !== undefined ? new Date(message.data.E) : new Date(message.data.T),
localTimestamp
}
yield bookChange
}
protected mapBookLevel(level: BinanceBookLevel) {
const price = Number(level[0])
const amount = Number(level[1])
return { price, amount }
}
}
export class BinanceEuropeanOptionsBookChangeMapperV2 implements Mapper<'binance-european-options', BookChange> {
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.includes('@depth20')
}
getFilters(symbols?: string[]) {
symbols = lowerCaseSymbols(symbols)
return [
{
channel: 'depth20',
symbols
} as const
]
}
*map(message: BinanceResponse<BinanceOptionsDepthDataV2>, localTimestamp: Date) {
const bookChange: BookChange = {
type: 'book_change',
symbol: message.data.s,
exchange: 'binance-european-options',
isSnapshot: true,
bids: message.data.b.map(this.mapBookLevel),
asks: message.data.a.map(this.mapBookLevel),
timestamp: new Date(message.data.T),
localTimestamp
}
yield bookChange
}
protected mapBookLevel(level: BinanceBookLevel) {
const price = Number(level[0])
const amount = Number(level[1])
return { price, amount }
}
}
export class BinanceEuropeanOptionsBookTickerMapper implements Mapper<'binance-european-options', BookTicker> {
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.endsWith('@bookTicker')
}
getFilters(symbols?: string[]) {
symbols = lowerCaseSymbols(symbols)
return [
{
channel: 'bookTicker',
symbols
} as const
]
}
*map(message: BinanceResponse<BinanceOptionsBookTickerData>, localTimestamp: Date) {
const bestBidPrice = Number(message.data.b)
const bestBidAmount = Number(message.data.B)
const bestAskPrice = Number(message.data.a)
const bestAskAmount = Number(message.data.A)
const bookTicker: BookTicker = {
type: 'book_ticker',
symbol: message.data.s,
exchange: 'binance-european-options',
bidPrice: bestBidPrice > 0 ? bestBidPrice : undefined,
bidAmount: bestBidAmount > 0 ? bestBidAmount : undefined,
askPrice: bestAskPrice > 0 ? bestAskPrice : undefined,
askAmount: bestAskAmount > 0 ? bestAskAmount : undefined,
timestamp: new Date(message.data.T),
localTimestamp
}
yield bookTicker
}
}
export class BinanceEuropeanOptionSummaryMapper implements Mapper<'binance-european-options', OptionSummary> {
private readonly _indexPrices = new Map<string, number>()
private readonly _openInterests = new Map<string, number>()
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return message.stream.endsWith('@ticker') || message.stream.endsWith('@index') || message.stream.includes('@openInterest')
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
const indexes =
symbols !== undefined
? symbols.map((s) => {
const symbolParts = s.split('-')
return `${symbolParts[0]}USDT`
})
: undefined
const underlyings =
symbols !== undefined
? symbols.map((s) => {
const symbolParts = s.split('-')
return `${symbolParts[0]}`
})
: undefined
return [
{
channel: 'ticker',
symbols
} as const,
{
channel: 'index',
symbols: indexes
} as const,
{
channel: 'openInterest',
symbols: underlyings
} as const
]
}
*map(
message: BinanceResponse<BinanceOptionsTickerData | BinanceOptionsIndexData | BinanceOptionsOpenInterestData[]>,
localTimestamp: Date
) {
if (message.stream.endsWith('@index')) {
const lastIndexPrice = Number((message.data as any).p)
if (lastIndexPrice > 0) {
this._indexPrices.set((message.data as any).s, lastIndexPrice)
}
return
}
if (message.stream.includes('@openInterest')) {
for (let data of message.data as BinanceOptionsOpenInterestData[]) {
const openInterest = Number(data.o)
if (openInterest >= 0) {
this._openInterests.set(data.s, openInterest)
}
}
return
}
const optionInfo = message.data as BinanceOptionsTickerData
const [base, expiryPart, strikePrice, optionType] = optionInfo.s.split('-')
const expirationDate = new Date(`20${expiryPart.slice(0, 2)}-${expiryPart.slice(2, 4)}-${expiryPart.slice(4, 6)}Z`)
expirationDate.setUTCHours(8)
const isPut = optionType === 'P'
const underlyingIndex = `${base}USDT`
let bestBidPrice = asNumberIfValid(optionInfo.bo)
if (bestBidPrice === 0) {
bestBidPrice = undefined
}
let bestBidAmount = asNumberIfValid(optionInfo.bq)
if (bestBidAmount === 0) {
bestBidAmount = undefined
}
let bestAskPrice = asNumberIfValid(optionInfo.ao)
if (bestAskPrice === 0) {
bestAskPrice = undefined
}
let bestAskAmount = asNumberIfValid(optionInfo.aq)
if (bestAskAmount === 0) {
bestAskAmount = undefined
}
let bestBidIV = bestBidPrice !== undefined ? asNumberIfValid(optionInfo.b) : undefined
if (bestBidIV === -1) {
bestBidIV = undefined
}
let bestAskIV = bestAskPrice !== undefined ? asNumberIfValid(optionInfo.a) : undefined
if (bestAskIV === -1) {
bestAskIV = undefined
}
const optionSummary: OptionSummary = {
type: 'option_summary',
symbol: optionInfo.s,
exchange: 'binance-european-options',
optionType: isPut ? 'put' : 'call',
strikePrice: Number(strikePrice),
expirationDate,
bestBidPrice,
bestBidAmount,
bestBidIV,
bestAskPrice,
bestAskAmount,
bestAskIV,
lastPrice: asNumberIfValid(optionInfo.c),
openInterest: this._openInterests.get(optionInfo.s),
markPrice: asNumberIfValid(optionInfo.mp),
markIV: undefined,
delta: asNumberIfValid(optionInfo.d),
gamma: asNumberIfValid(optionInfo.g),
vega: asNumberIfValid(optionInfo.v),
theta: asNumberIfValid(optionInfo.t),
rho: undefined,
underlyingPrice: this._indexPrices.get(underlyingIndex),
underlyingIndex,
timestamp: new Date(optionInfo.E),
localTimestamp: localTimestamp
}
yield optionSummary
}
}
export class BinanceEuropeanOptionSummaryMapperV2 implements Mapper<'binance-european-options', OptionSummary> {
private readonly _lastPrices = new Map<string, number>()
private readonly _openInterests = new Map<string, number>()
canHandle(message: BinanceResponse<any>) {
if (message.stream === undefined) {
return false
}
return (
message.stream.endsWith('@optionMarkPrice') ||
message.stream.endsWith('@optionTicker') ||
message.stream.includes('@optionOpenInterest')
)
}
getFilters(symbols?: string[]) {
symbols = lowerCaseSymbols(symbols)
const underlyings =
symbols !== undefined
? symbols.map((s) => {
const symbolParts = s.split('-')
return `${symbolParts[0]}usdt`
})
: undefined
return [
{
channel: 'optionMarkPrice',
symbols: underlyings
} as const,
{
channel: 'optionTicker',
symbols
} as const,
{
channel: 'optionOpenInterest',
symbols: underlyings
} as const
]
}
*map(
message: BinanceResponse<BinanceOptionsMarkPriceData[] | BinanceOptionsTickerData | BinanceOptionsOpenInterestDataV2[]>,
localTimestamp: Date
) {
// Handle optionTicker messages to track last prices
if (message.stream.endsWith('@optionTicker')) {
const tickerData = message.data as BinanceOptionsTickerData
const lastPrice = Number(tickerData.c)
if (lastPrice > 0) {
this._lastPrices.set(tickerData.s, lastPrice)
}
return
}
// Handle optionOpenInterest messages to track open interest
if (message.stream.includes('@optionOpenInterest')) {
const openInterestArray = message.data as BinanceOptionsOpenInterestDataV2[]
for (let oi of openInterestArray) {
const openInterest = Number(oi.o)
if (openInterest >= 0) {
this._openInterests.set(oi.s, openInterest)
}
}
return
}
// optionMarkPrice contains all data needed: greeks, IV, best bid/ask, mark price, and index price
const markPriceArray = message.data as BinanceOptionsMarkPriceData[]
for (let markData of markPriceArray) {
const [base, expiryPart, strikePrice, optionType] = markData.s.split('-')
const expirationDate = new Date(`20${expiryPart.slice(0, 2)}-${expiryPart.slice(2, 4)}-${expiryPart.slice(4, 6)}Z`)
expirationDate.setUTCHours(8)
const isPut = optionType === 'P'
const underlyingIndex = `${base}USDT`
let bestBidPrice = asNumberIfValid(markData.bo)
if (bestBidPrice === 0) {
bestBidPrice = undefined
}
let bestBidAmount = asNumberIfValid(markData.bq)
if (bestBidAmount === 0) {
bestBidAmount = undefined
}
let bestAskPrice = asNumberIfValid(markData.ao)
if (bestAskPrice === 0) {
bestAskPrice = undefined
}
let bestAskAmount = asNumberIfValid(markData.aq)
if (bestAskAmount === 0) {
bestAskAmount = undefined
}
let bestBidIV = bestBidPrice !== undefined ? asNumberIfValid(markData.b) : undefined
if (bestBidIV === -1) {
bestBidIV = undefined
}
let bestAskIV = bestAskPrice !== undefined ? asNumberIfValid(markData.a) : undefined
if (bestAskIV === -1) {
bestAskIV = undefined
}
const markPrice = asNumberIfValid(markData.mp)
const markIV = asNumberIfValid(markData.vo)
const delta = asNumberIfValid(markData.d)
const gamma = asNumberIfValid(markData.g)
const vega = asNumberIfValid(markData.v)
const theta = asNumberIfValid(markData.t)
const underlyingPrice = asNumberIfValid(markData.i) // Index price is included in mark price data
const optionSummary: OptionSummary = {
type: 'option_summary',
symbol: markData.s,
exchange: 'binance-european-options',
optionType: isPut ? 'put' : 'call',
strikePrice: Number(strikePrice),
expirationDate,
bestBidPrice,
bestBidAmount,
bestBidIV,
bestAskPrice,
bestAskAmount,
bestAskIV,
lastPrice: this._lastPrices.get(markData.s),
openInterest: this._openInterests.get(markData.s),
markPrice,
markIV,
delta,
gamma,
vega,
theta,
rho: undefined,
underlyingPrice,
underlyingIndex,
timestamp: new Date(markData.E),
localTimestamp: localTimestamp
}
yield optionSummary
}
}
}
type BinanceResponse<T> = {
stream: string
data: T
}
type BinanceOptionsTradeData = {
e: 'trade'
E: 1696118408137
s: 'DOGE-231006-0.06-C'
t: '15'
p: '2.64'
q: '0.01'
b: '4647850284614262784'
a: '4719907951072796672'
T: 1696118408134
S: '-1'
}
type BinanceOptionsDepthData = {
e: 'depth'
E: 1696118400038
T: 1696118399082
s: 'BTC-231027-34000-C'
u: 1925729
pu: 1925729
b: [['60', '7.31'], ['55', '2.5'], ['50', '15'], ['45', '15'], ['40', '34.04']]
a: [['65', '8.28'], ['70', '38.88'], ['75', '15'], ['1200', '0.01'], ['4660', '0.42']]
}
type BinanceOptionsTickerData = {
e: '24hrTicker'
E: 1696118400043
T: 1696118400000
s: 'BNB-231013-200-P'
o: '1'
h: '1'
l: '0.9'
c: '0.9'
V: '11.08'
A: '9.97'
P: '-0.1'
p: '-0.1'
Q: '11'
F: '0'
L: '8'
n: 1
bo: '1'
ao: '1.7'
bq: '50'
aq: '50'
b: '0.35929501'
a: '0.43317497'
d: '-0.16872899'
t: '-0.16779034'
g: '0.0153237'
v: '0.09935076'
vo: '0.41658748'
mp: '1.5'
hl: '37.1'
ll: '0.1'
eep: '0'
}
type BinanceOptionsIndexData = { e: 'index'; E: 1696118400040; s: 'BNBUSDT'; p: '214.6133998' }
type BinanceOptionsOpenInterestData = { e: 'openInterest'; E: 1696118400042; s: 'XRP-231006-0.46-P'; o: '39480.0'; h: '20326.64319' }
type BinanceBookLevel = [string, string]
// V2 Types for new format (Dec 17, 2025+)
type BinanceOptionsTradeDataV2 = {
e: 'trade'
E: number // event time
T: number // trade completed time
s: string // option symbol
t: number // trade ID
p: string // price
q: string // quantity
X: 'MARKET' | 'BLOCK' // trade type
S: 'BUY' | 'SELL' // direction
m: boolean // is buyer market maker
}
type BinanceOptionsDepthDataV2 = {
e: 'depthUpdate'
E: number // event time
T: number // transaction time
s: string // symbol
U: number // first update ID
u: number // final update ID
pu: number // previous final update ID
b: [string, string][] // bids
a: [string, string][] // asks
}
type BinanceOptionsBookTickerData = {
e: 'bookTicker'
u: number // order book update ID
s: string // symbol
b: string // best bid price
B: string // best bid qty
a: string // best ask price
A: string // best ask qty
T: number // transaction time
E: number // event time
}
type BinanceOptionsOpenInterestDataV2 = {
e: 'openInterest'
E: number // event time
s: string // symbol
o: string // open interest (quantity)
h: string // open interest in notional value (USD)
}
type BinanceOptionsMarkPriceData = {
s: string // option symbol
mp: string // mark price
E: number // event time
e: 'markPrice'
i: string // index price
P: string // premium
bo: string // best bid price
ao: string // best ask price
bq: string // best bid quantity
aq: string // best ask quantity
b: string // bid IV
a: string // ask IV
hl: string // high limit price
ll: string // low limit price
vo: string // mark IV
rf: string // risk free rate
d: string // delta
t: string // theta
g: string // gamma
v: string // vega
}
================================================
FILE: src/mappers/bitfinex.ts
================================================
import { upperCaseSymbols } from '../handy.ts'
import { BookChange, BookTicker, DerivativeTicker, Exchange, FilterForExchange, Liquidation, Trade } from '../types.ts'
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
// https://docs.bitfinex.com/v2/docs/ws-general
export class BitfinexTradesMapper implements Mapper<'bitfinex' | 'bitfinex-derivatives', Trade> {
private readonly _channelIdToSymbolMap: Map<number, string> = new Map()
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BitfinexMessage) {
// non sub messages are provided as arrays
if (Array.isArray(message)) {
// first test if message itself provides channel name and if so if it's trades
const channelName = message[message.length - 2]
if (typeof channelName === 'string') {
return channelName === 'trades'
}
// otherwise use channel to id mapping
return this._channelIdToSymbolMap.get(message[0]) !== undefined
}
// store mapping between channel id and symbols
if (message.event === 'subscribed') {
const isTradeChannel = message.channel === 'trades'
if (isTradeChannel) {
this._channelIdToSymbolMap.set(message.chanId, message.pair)
}
}
return false
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'trades',
symbols
} as const
]
}
*map(message: BitfinexTrades, localTimestamp: Date) {
const symbolFromMessage = message[message.length - 1]
const symbol = typeof symbolFromMessage === 'string' ? symbolFromMessage : this._channelIdToSymbolMap.get(message[0])
// ignore if we don't have matching symbol
if (symbol === undefined) {
return
}
// ignore heartbeats
if (message[1] === 'hb') {
return
}
// ignore snapshots
if (message[1] !== 'te') {
return
}
const [id, timestamp, amount, price] = message[2]
const trade: Trade = {
type: 'trade',
symbol,
exchange: this._exchange,
id: String(id),
price,
amount: Math.abs(amount),
side: amount < 0 ? 'sell' : 'buy',
timestamp: new Date(timestamp),
localTimestamp: localTimestamp
}
yield trade
}
}
export class BitfinexBookChangeMapper implements Mapper<'bitfinex' | 'bitfinex-derivatives', BookChange> {
private readonly _channelIdToSymbolMap: Map<number, string> = new Map()
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BitfinexMessage) {
// non sub messages are provided as arrays
if (Array.isArray(message)) {
// first test if message itself provides channel name and if so if it's a book
const channelName = message[message.length - 2]
if (typeof channelName === 'string') {
return channelName === 'book'
}
// otherwise use channel to id mapping
return this._channelIdToSymbolMap.get(message[0]) !== undefined
}
// store mapping between channel id and symbols
if (message.event === 'subscribed') {
const isBookP0Channel = message.channel === 'book' && message.prec === 'P0'
if (isBookP0Channel) {
this._channelIdToSymbolMap.set(message.chanId, message.pair)
}
}
return false
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'book',
symbols
} as const
]
}
*map(message: BitfinexBooks, localTimestamp: Date) {
const symbolFromMessage = message[message.length - 1]
const symbol = typeof symbolFromMessage === 'string' ? symbolFromMessage : this._channelIdToSymbolMap.get(message[0])
// ignore if we don't have matching symbol
if (symbol === undefined) {
return
}
// ignore heartbeats
if (message[1] === 'hb') {
return
}
const isSnapshot = Array.isArray(message[1][0])
const bookLevels = (isSnapshot ? message[1] : [message[1]]) as BitfinexBookLevel[]
const asks = bookLevels.filter((level) => level[2] < 0)
const bids = bookLevels.filter((level) => level[2] > 0)
const bookChange: BookChange = {
type: 'book_change',
symbol,
exchange: this._exchange,
isSnapshot,
bids: bids.map(this._mapBookLevel),
asks: asks.map(this._mapBookLevel),
timestamp: new Date(message[3]),
localTimestamp: localTimestamp
}
yield bookChange
}
private _mapBookLevel(level: BitfinexBookLevel) {
const [price, count, bitfinexAmount] = level
const amount = count === 0 ? 0 : Math.abs(bitfinexAmount)
return { price, amount }
}
}
export class BitfinexDerivativeTickerMapper implements Mapper<'bitfinex-derivatives', DerivativeTicker> {
private readonly _channelIdToSymbolMap: Map<number, string> = new Map()
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
canHandle(message: BitfinexMessage) {
// non sub messages are provided as arrays
if (Array.isArray(message)) {
// first test if message itself provides channel name and if so if it's a status
const channelName = message[message.length - 2]
if (typeof channelName === 'string') {
return channelName === 'status'
}
// otherwise use channel to id mapping
return this._channelIdToSymbolMap.get(message[0]) !== undefined
}
// store mapping between channel id and symbols
if (message.event === 'subscribed') {
const isDerivStatusChannel = message.channel === 'status' && message.key && message.key.startsWith('deriv:')
if (isDerivStatusChannel) {
this._channelIdToSymbolMap.set(message.chanId, message.key!.replace('deriv:t', ''))
}
}
return false
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'status',
symbols
} as const
]
}
*map(message: BitfinexStatusMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> {
const symbolFromMessage = message[message.length - 1]
const symbol = typeof symbolFromMessage === 'string' ? symbolFromMessage : this._channelIdToSymbolMap.get(message[0])
// ignore if we don't have matching symbol
if (symbol === undefined) {
return
}
// ignore heartbeats
if (message[1] === 'hb') {
return
}
const statusInfo = message[1]
// https://docs.bitfinex.com/v2/reference#ws-public-status
const fundingRate = statusInfo[11]
const indexPrice = statusInfo[3]
const lastPrice = statusInfo[2]
const markPrice = statusInfo[14]
const openInterest = statusInfo[17]
const nextFundingTimestamp = statusInfo[7]
const predictedFundingRate = statusInfo[8]
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(symbol, 'bitfinex-derivatives')
pendingTickerInfo.updateFundingRate(fundingRate)
pendingTickerInfo.updateFundingTimestamp(nextFundingTimestamp !== undefined ? new Date(nextFundingTimestamp) : undefined)
pendingTickerInfo.updatePredictedFundingRate(predictedFundingRate)
pendingTickerInfo.updateIndexPrice(indexPrice)
pendingTickerInfo.updateLastPrice(lastPrice)
pendingTickerInfo.updateMarkPrice(markPrice)
pendingTickerInfo.updateOpenInterest(openInterest)
pendingTickerInfo.updateTimestamp(new Date(message[3]))
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp)
}
}
}
export class BitfinexLiquidationsMapper implements Mapper<'bitfinex-derivatives', Liquidation> {
private _liquidationsChannelId: number | undefined = undefined
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BitfinexMessage) {
// non sub messages are provided as arrays
if (Array.isArray(message)) {
// first test if message itself provides channel name and if so if it's liquidations
const channelName = message[message.length - 2]
if (typeof channelName === 'string') {
return channelName === 'liquidations'
}
// otherwise use channel id
return this._liquidationsChannelId === message[0]
}
// store liquidation channel id
if (message.event === 'subscribed') {
const isLiquidationsChannel = message.channel === 'status' && message.key === 'liq:global'
if (isLiquidationsChannel) {
this._liquidationsChannelId = message.chanId
}
}
return false
}
getFilters() {
// liquidations channel is global, not per symbol
return [
{
channel: 'liquidations'
} as const
]
}
*map(message: BitfinexLiquidation, localTimestamp: Date) {
// ignore heartbeats
if (message[1] === 'hb') {
return
}
if (!message[1]) {
return
}
// see https://docs.bitfinex.com/reference#ws-public-status
for (let bitfinexLiquidation of message[1]) {
const isInitialLiquidationTrigger = bitfinexLiquidation[8] === 0
// process only initial liquidation triggers not subsequent 'matches', assumption here is that
// there's only single initial liquidation trigger but there can be multiple matches for single liquidation
if (isInitialLiquidationTrigger) {
const id = String(bitfinexLiquidation[1])
const timestamp = new Date(bitfinexLiquidation[2])
const symbol = bitfinexLiquidation[4].replace('t', '')
const price = bitfinexLiquidation[6]
const amount = bitfinexLiquidation[5]
const liquidation: Liquidation = {
type: 'liquidation',
symbol,
exchange: this._exchange,
id,
price,
amount: Math.abs(amount),
side: amount < 0 ? 'buy' : 'sell',
timestamp,
localTimestamp: localTimestamp
}
yield liquidation
}
}
}
}
export class BitfinexBookTickerMapper implements Mapper<'bitfinex' | 'bitfinex-derivatives', BookTicker> {
private readonly _channelIdToSymbolMap: Map<number, string> = new Map()
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BitfinexMessage) {
// non sub messages are provided as arrays
if (Array.isArray(message)) {
// first test if message itself provides channel name and if so if it's trades
const channelName = message[message.length - 2]
if (typeof channelName === 'string') {
return channelName === 'ticker'
}
// otherwise use channel to id mapping
return this._channelIdToSymbolMap.get(message[0]) !== undefined
}
// store mapping between channel id and symbols
if (message.event === 'subscribed') {
const isTicker = message.channel === 'ticker' && message.pair !== undefined
if (isTicker) {
this._channelIdToSymbolMap.set(message.chanId, message.pair)
}
}
return false
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'ticker',
symbols
} as const
]
}
*map(message: BitfinexTicker, localTimestamp: Date) {
const symbolFromMessage = message[message.length - 1]
const symbol = typeof symbolFromMessage === 'string' ? symbolFromMessage : this._channelIdToSymbolMap.get(message[0])
if (symbol === undefined) {
return
}
// ignore heartbeats
if (message[1] === 'hb') {
return
}
// ignore funding tickers
const tickerData = message[1]
if (tickerData.length > 11) {
return
}
const [bidPrice, bidAmount, askPrice, askAmount] = tickerData
const ticker: BookTicker = {
type: 'book_ticker',
symbol,
exchange: this._exchange,
askAmount,
askPrice,
bidPrice,
bidAmount,
timestamp: new Date(message[3]),
localTimestamp: localTimestamp
}
yield ticker
}
}
type BitfinexMessage =
| {
event: 'subscribed'
channel: FilterForExchange['bitfinex-derivatives']['channel']
chanId: number
pair: string
prec: string
key?: string
}
| Array<any>
type BitfinexHeartbeat = [number, 'hb']
type BitfinexTrades = [number, 'te' | any[], [number, number, number, number]] | BitfinexHeartbeat
type BitfinexBookLevel = [number, number, number]
type BitfinexBooks = [number, BitfinexBookLevel | BitfinexBookLevel[], number, number] | BitfinexHeartbeat
type BitfinexStatusMessage = [number, (number | undefined)[], number, number] | BitfinexHeartbeat
type BitfinexLiquidation =
| [number, ['pos', number, number, null, string, number, number, null, number, number, null, number][]]
| BitfinexHeartbeat
type BitfinexTicker =
| [
CHANNEL_ID: number,
ITEMS:
| [
BID: number,
BID_SIZE: number,
ASK: number,
ASK_SIZE: number,
DAILY_CHANGE: number,
DAILY_CHANGE_RELATIVE: number,
LAST_PRICE: number,
VOLUME: number,
HIGH: number,
LOW: number
]
| [
BID: number,
BID_SIZE: number,
ASK: number,
ASK_SIZE: number,
DAILY_CHANGE: number,
DAILY_CHANGE_RELATIVE: number,
LAST_PRICE: number,
VOLUME: number,
HIGH: number,
LOW: number,
EXTRA: null
],
SEQ_ID: number,
TIMESTAMP: number
]
| BitfinexHeartbeat
================================================
FILE: src/mappers/bitflyer.ts
================================================
import { parseμs, upperCaseSymbols } from '../handy.ts'
import { BookChange, BookTicker, Trade } from '../types.ts'
import { Mapper } from './mapper.ts'
export const bitflyerTradesMapper: Mapper<'bitflyer', Trade> = {
canHandle(message: BitflyerExecutions | BitflyerBoard) {
return message.params.channel.startsWith('lightning_executions')
},
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'lightning_executions',
symbols
}
]
},
*map(bitflyerExecutions: BitflyerExecutions, localTimestamp: Date) {
const symbol = bitflyerExecutions.params.channel.replace('lightning_executions_', '')
for (const execution of bitflyerExecutions.params.message) {
const timestamp = new Date(execution.exec_date)
timestamp.μs = parseμs(execution.exec_date)
const trade: Trade = {
type: 'trade',
symbol,
exchange: 'bitflyer',
id: String(execution.id),
price: execution.price,
amount: execution.size,
side: execution.side === 'BUY' ? 'buy' : execution.side === 'SELL' ? 'sell' : 'unknown',
timestamp,
localTimestamp: localTimestamp
}
yield trade
}
}
}
const mapBookLevel = ({ price, size }: BitflyerBookLevel) => {
return { price, amount: size }
}
export class BitflyerBookChangeMapper implements Mapper<'bitflyer', BookChange> {
private readonly _snapshotsInfo: Map<string, boolean> = new Map()
canHandle(message: BitflyerExecutions | BitflyerBoard) {
return message.params.channel.startsWith('lightning_board')
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'lightning_board_snapshot',
symbols
} as const,
{
channel: 'lightning_board',
symbols
} as const
]
}
*map(bitflyerBoard: BitflyerBoard, localTimestamp: Date): IterableIterator<BookChange> {
const channel = bitflyerBoard.params.channel
const isSnapshot = channel.startsWith('lightning_board_snapshot_')
const symbol = isSnapshot ? channel.replace('lightning_board_snapshot_', '') : channel.replace('lightning_board_', '')
if (this._snapshotsInfo.has(symbol) === false) {
if (isSnapshot) {
this._snapshotsInfo.set(symbol, true)
} else {
// skip change messages until we've received book snapshot
return
}
}
yield {
type: 'book_change',
symbol,
exchange: 'bitflyer',
isSnapshot,
bids: bitflyerBoard.params.message.bids.map(mapBookLevel),
asks: bitflyerBoard.params.message.asks.map(mapBookLevel),
timestamp: localTimestamp,
localTimestamp
}
}
}
export const bitflyerBookTickerMapper: Mapper<'bitflyer', BookTicker> = {
canHandle(message: BitflyerTicker) {
return message.params.channel.startsWith('lightning_ticker')
},
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'lightning_ticker',
symbols
}
]
},
*map(bitflyerTickerMessage: BitflyerTicker, localTimestamp: Date) {
const symbol = bitflyerTickerMessage.params.channel.replace('lightning_ticker_', '')
const bitflyerTicker = bitflyerTickerMessage.params.message
const timestamp = new Date(bitflyerTicker.timestamp)
timestamp.μs = parseμs(bitflyerTicker.timestamp)
const ticker: BookTicker = {
type: 'book_ticker',
symbol,
exchange: 'bitflyer',
askAmount: bitflyerTicker.best_ask_size,
askPrice: bitflyerTicker.best_ask,
bidPrice: bitflyerTicker.best_bid,
bidAmount: bitflyerTicker.best_bid_size,
timestamp,
localTimestamp: localTimestamp
}
yield ticker
}
}
type BitflyerExecutions = {
method: 'channelMessage'
params: {
channel: string
message: {
id: number
side: 'SELL' | 'BUY'
price: number
size: number
exec_date: string
}[]
}
}
type BitflyerBookLevel = {
price: number
size: number
}
type BitflyerBoard = {
method: 'channelMessage'
params: {
channel: string
message: {
bids: BitflyerBookLevel[]
asks: BitflyerBookLevel[]
}
}
}
type BitflyerTicker = {
method: 'channelMessage'
params: {
channel: 'lightning_ticker_ETH_JPY'
message: {
product_code: 'ETH_JPY'
state: 'RUNNING'
timestamp: '2021-09-01T00:00:00.2115808Z'
tick_id: 2830807
best_bid: 376592.0
best_ask: 376676.0
best_bid_size: 0.01
best_ask_size: 0.4
total_bid_depth: 5234.4333389
total_ask_depth: 1511.52678
market_bid_size: 0.0
market_ask_size: 0.0
ltp: 376789.0
volume: 37853.5120461
volume_by_product: 37853.5120461
}
}
}
================================================
FILE: src/mappers/bitget.ts
================================================
import { upperCaseSymbols } from '../handy.ts'
import { BookChange, BookTicker, DerivativeTicker, Exchange, Trade } from '../types.ts'
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
export class BitgetTradesMapper implements Mapper<'bitget' | 'bitget-futures', Trade> {
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BitgetTradeMessage) {
return message.arg.channel === 'trade' && message.action === 'update'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'trade',
symbols
} as const
]
}
*map(message: BitgetTradeMessage, localTimestamp: Date): IterableIterator<Trade> {
for (let trade of message.data) {
yield {
type: 'trade',
symbol: message.arg.instId,
exchange: this._exchange,
id: trade.tradeId,
price: Number(trade.price),
amount: Number(trade.size),
side: trade.side === 'buy' ? 'buy' : 'sell',
timestamp: new Date(Number(trade.ts)),
localTimestamp: localTimestamp
}
}
}
}
function mapPriceLevel(level: [string, string]) {
return {
price: Number(level[0]),
amount: Number(level[1])
}
}
export class BitgetBookChangeMapper implements Mapper<'bitget' | 'bitget-futures', BookChange> {
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BitgetOrderbookMessage) {
return message.arg.channel === 'books15' && message.action === 'snapshot'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'books15',
symbols
} as const
]
}
*map(message: BitgetOrderbookMessage, localTimestamp: Date): IterableIterator<BookChange> {
for (let orderbookData of message.data) {
yield {
type: 'book_change',
symbol: message.arg.instId,
exchange: this._exchange,
isSnapshot: message.action === 'snapshot',
bids: orderbookData.bids.map(mapPriceLevel),
asks: orderbookData.asks.map(mapPriceLevel),
timestamp: new Date(Number(orderbookData.ts)),
localTimestamp
}
}
}
}
export class BitgetBookTickerMapper implements Mapper<'bitget' | 'bitget-futures', BookTicker> {
constructor(private readonly _exchange: Exchange) {}
canHandle(message: BitgetBBoMessage) {
return message.arg.channel === 'books1' && message.action === 'snapshot'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: `books1` as const,
symbols
}
]
}
*map(message: BitgetBBoMessage, localTimestamp: Date): IterableIterator<BookTicker> {
for (const bboMessage of message.data) {
const ticker: BookTicker = {
type: 'book_ticker',
symbol: message.arg.instId,
exchange: this._exchange,
askAmount: bboMessage.asks[0] ? Number(bboMessage.asks[0][1]) : undefined,
askPrice: bboMessage.asks[0] ? Number(bboMessage.asks[0][0]) : undefined,
bidPrice: bboMessage.bids[0] ? Number(bboMessage.bids[0][0]) : undefined,
bidAmount: bboMessage.bids[0] ? Number(bboMessage.bids[0][1]) : undefined,
timestamp: new Date(Number(bboMessage.ts)),
localTimestamp: localTimestamp
}
yield ticker
}
}
}
export class BitgetDerivativeTickerMapper implements Mapper<'bitget-futures', DerivativeTicker> {
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
canHandle(message: BitgetTickerMessage) {
return message.arg.channel === 'ticker' && message.action === 'snapshot'
}
getFilters(symbols?: string[]) {
return [
{
channel: 'ticker',
symbols
} as const
]
}
*map(message: BitgetTickerMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> {
for (const tickerMessage of message.data) {
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(tickerMessage.symbol, 'bitget-futures')
pendingTickerInfo.updateIndexPrice(Number(tickerMessage.indexPrice))
pendingTickerInfo.updateMarkPrice(Number(tickerMessage.markPrice))
pendingTickerInfo.updateOpenInterest(Number(tickerMessage.holdingAmount))
pendingTickerInfo.updateLastPrice(Number(tickerMessage.lastPr))
pendingTickerInfo.updateTimestamp(new Date(Number(tickerMessage.ts)))
if (tickerMessage.nextFundingTime !== '0') {
pendingTickerInfo.updateFundingTimestamp(new Date(Number(tickerMessage.nextFundingTime)))
pendingTickerInfo.updateFundingRate(Number(tickerMessage.fundingRate))
}
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp)
}
}
}
}
type BitgetTradeMessage = {
action: 'update'
arg: { instType: 'SPOT'; channel: 'trade'; instId: 'OPUSDT' }
data: [{ ts: '1730332800983'; price: '1.717'; size: '56.16'; side: 'buy'; tradeId: '1235670816495050754' }]
ts: 1730332800989
}
type BitgetOrderbookMessage = {
action: 'snapshot'
arg: { instType: 'SPOT'; channel: 'books15'; instId: 'GEMSUSDT' }
data: [
{
asks: [['0.22816', '155.25']]
bids: [['0.22785', '73.41']]
checksum: 0
ts: '1730963759993'
}
]
ts: 1730963759997
}
type BitgetBBoMessage = {
action: 'snapshot'
arg: { instType: 'SPOT'; channel: 'books1'; instId: 'METISUSDT' }
data: [{ asks: [['44.90', '0.6927']]; bids: [['44.82', '3.5344']]; checksum: 0; ts: '1730332859988' }]
ts: 1730332859989
}
type BitgetTickerMessage = {
action: 'snapshot'
arg: { instType: 'COIN-FUTURES'; channel: 'ticker'; instId: 'BTCUSD' }
data: [
{
instId: 'BTCUSD'
lastPr: '72331.5'
bidPr: '72331.5'
askPr: '72331.8'
bidSz: '7.296'
askSz: '0.02'
open24h: '72047.8'
high24h: '72934.8'
low24h: '71422.8'
change24h: '-0.00561'
fundingRate: '0.000116'
nextFundingTime: string
markPrice: string
indexPrice: string
holdingAmount: string
baseVolume: '7543.376'
quoteVolume: '544799876.924'
openUtc: '72335.3'
symbolType: '1'
symbol: 'BTCUSD'
deliveryPrice: '0'
ts: '1730332823217'
}
]
ts: 1730332823220
}
================================================
FILE: src/mappers/bitmex.ts
================================================
import { asNumberIfValid, upperCaseSymbols } from '../handy.ts'
import { BookChange, BookPriceLevel, BookTicker, DerivativeTicker, FilterForExchange, Liquidation, Trade } from '../types.ts'
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
// https://www.bitmex.com/app/wsAPI
export const bitmexTradesMapper: Mapper<'bitmex', Trade> = {
canHandle(message: BitmexDataMessage) {
return message.table === 'trade' && message.action === 'insert'
},
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'trade',
symbols
}
]
},
*map(bitmexTradesMessage: BitmexTradesMessage, localTimestamp: Date) {
for (const bitmexTrade of bitmexTradesMessage.data) {
const trade: Trade = {
type: 'trade',
symbol: bitmexTrade.symbol,
exchange: 'bitmex',
id: bitmexTrade.trdMatchID,
price: bitmexTrade.price,
amount: bitmexTrade.size,
side: bitmexTrade.side !== undefined ? (bitmexTrade.side === 'Buy' ? 'buy' : 'sell') : 'unknown',
timestamp: new Date(bitmexTrade.timestamp),
localTimestamp: localTimestamp
}
yield trade
}
}
}
export class BitmexBookChangeMapper implements Mapper<'bitmex', BookChange> {
private readonly _idToPriceLevelMap: Map<number, number> = new Map()
canHandle(message: BitmexDataMessage) {
return message.table === 'orderBookL2'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'orderBookL2',
symbols
} as const
]
}
*map(bitmexOrderBookL2Message: BitmexOrderBookL2Message, localTimestamp: Date): IterableIterator<BookChange> {
let bitmexBookMessagesGrouppedBySymbol
// only partial messages can contain different symbols (when subscribed via {"op": "subscribe", "args": ["orderBookL2"]} for example)
if (bitmexOrderBookL2Message.action === 'partial') {
bitmexBookMessagesGrouppedBySymbol = bitmexOrderBookL2Message.data.reduce(
(prev, current) => {
if (prev[current.symbol]) {
prev[current.symbol].push(current)
} else {
prev[current.symbol] = [current]
}
return prev
},
{} as {
[key: string]: typeof bitmexOrderBookL2Message.data
}
)
if (bitmexOrderBookL2Message.data.length === 0 && bitmexOrderBookL2Message.filter?.symbol !== undefined) {
const emptySnapshot: BookChange = {
type: 'book_change',
symbol: bitmexOrderBookL2Message.filter?.symbol!,
exchange: 'bitmex',
isSnapshot: true,
bids: [],
asks: [],
timestamp: localTimestamp,
localTimestamp: localTimestamp
}
yield emptySnapshot
}
} else {
// in case of other messages types BitMEX always returns data for single symbol
bitmexBookMessagesGrouppedBySymbol = {
[bitmexOrderBookL2Message.data[0].symbol]: bitmexOrderBookL2Message.data
}
}
for (let symbol in bitmexBookMessagesGrouppedBySymbol) {
const bids: BookPriceLevel[] = []
const asks: BookPriceLevel[] = []
let latestBitmexTimestamp: Date | undefined = undefined
for (const item of bitmexBookMessagesGrouppedBySymbol[symbol]) {
if (item.timestamp !== undefined) {
const priceLevelTimestamp = new Date(item.timestamp)
if (latestBitmexTimestamp === undefined) {
latestBitmexTimestamp = priceLevelTimestamp
} else {
if (priceLevelTimestamp.valueOf() > latestBitmexTimestamp.valueOf()) {
latestBitmexTimestamp = priceLevelTimestamp
}
}
}
// https://www.bitmex.com/app/restAPI#OrderBookL2
if (item.price !== undefined) {
// store the mapping from id to price level if price is specified
// only partials and inserts have price set
this._idToPriceLevelMap.set(item.id, item.price)
}
const price = this._idToPriceLevelMap.get(item.id)
const amount = item.size || 0 // delete messages do not have size specified
// if we still don't have a price it means that there was an update before partial message - let's skip it
if (price === undefined) {
continue
}
if (item.side === 'Buy') {
bids.push({ price, amount })
} else {
asks.push({ price, amount })
}
// remove meta info for deleted level
if (bitmexOrderBookL2Message.action === 'delete') {
this._idToPriceLevelMap.delete(item.id)
}
}
const isSnapshot = bitmexOrderBookL2Message.action === 'partial'
if (bids.length > 0 || asks.length > 0 || isSnapshot) {
const bookChange: BookChange = {
type: 'book_change',
symbol,
exchange: 'bitmex',
isSnapshot,
bids,
asks,
timestamp: latestBitmexTimestamp !== undefined ? latestBitmexTimestamp : localTimestamp,
localTimestamp: localTimestamp
}
yield bookChange
}
}
}
}
export class BitmexDerivativeTickerMapper implements Mapper<'bitmex', DerivativeTicker> {
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
canHandle(message: BitmexDataMessage) {
return message.table === 'instrument'
}
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'instrument',
symbols
} as const
]
}
*map(message: BitmexInstrumentsMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> {
for (const bitmexInstrument of message.data) {
// process instrument messages only if:
// - we already have seen their 'partials' or already have 'pending info'
// - and instruments aren't settled or unlisted already
const isOpen = bitmexInstrument.state === undefined || bitmexInstrument.state === 'Open' || bitmexInstrument.state === 'Closed'
const isPartial = message.action === 'partial'
const hasPendingInfo = this.pendingTickerInfoHelper.hasPendingTickerInfo(bitmexInstrument.symbol)
if ((isPartial || hasPendingInfo) && isOpen) {
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(bitmexInstrument.symbol, 'bitmex')
pendingTickerInfo.updateFundingRate(bitmexInstrument.fundingRate)
pendingTickerInfo.updatePredictedFundingRate(bitmexInstrument.indicativeFundingRate)
pendingTickerInfo.updateFundingTimestamp(
bitmexInstrument.fundingTimestamp ? new Date(bitmexInstrument.fundingTimestamp) : undefined
)
pendingTickerInfo.updateIndexPrice(bitmexInstrument.indicativeSettlePrice)
pendingTickerInfo.updateMarkPrice(bitmexInstrument.markPrice)
pendingTickerInfo.updateOpenInterest(bitmexInstrument.openInterest)
pendingTickerInfo.updateLastPrice(bitmexInstrument.lastPrice)
if (bitmexInstrument.timestamp !== undefined) {
pendingTickerInfo.updateTimestamp(new Date(bitmexInstrument.timestamp))
}
if (pendingTickerInfo.hasChanged()) {
yield pendingTickerInfo.getSnapshot(localTimestamp)
}
}
}
}
}
export const bitmexLiquidationsMapper: Mapper<'bitmex', Liquidation> = {
canHandle(message: BitmexDataMessage) {
return message.table === 'liquidation' && message.action === 'insert'
},
getFilters(symbols?: string[]) {
symbols = upperCaseSymbols(symbols)
return [
{
channel: 'liquidation',
symbols
}
]
},
*map(bitmexLiquiationsMessage: BitmexLiquidation, localTimestamp: Date) {
for (const bitmexLiquidation of bitmexLiquiationsMessage.data) {
const liquidation: Liquidation = {
type: 'liquidation',
symbol: bitmexLiquidation.symbol,
exchange: 'bitmex',
id: bitmexLiquidation.orderID,
price: bitmexLiquidation.price,
amount: bitmexLiquidation.leavesQty,
side: bitmexLiquidation.side === 'Buy' ? 'buy' : 'sell',
timestamp: localTimestamp,
localTimestamp: localTimestamp
}
yield liquidation
}
}
}
export const bitmexBookTickerMapper: Mapper<'bitmex', BookTicker> = {
canHandle
gitextract_eif38n1w/ ├── .github/ │ └── workflows/ │ ├── ci.yaml │ ├── npm_audit.yaml │ └── publish.yaml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── ADD_NEW_EXCHANGE.md ├── AGENTS.md ├── ARCHITECTURE.md ├── CLAUDE.md ├── LICENSE ├── README.md ├── example.js ├── package.json ├── src/ │ ├── apikeyaccessinfo.ts │ ├── binarysplit.ts │ ├── clearcache.ts │ ├── combine.ts │ ├── computable/ │ │ ├── booksnapshot.ts │ │ ├── computable.ts │ │ ├── index.ts │ │ └── tradebar.ts │ ├── consts.ts │ ├── debug.ts │ ├── downloaddatasets.ts │ ├── exchangedetails.ts │ ├── filter.ts │ ├── handy.ts │ ├── index.ts │ ├── instrumentinfo.ts │ ├── mappers/ │ │ ├── ascendex.ts │ │ ├── binance.ts │ │ ├── binancedex.ts │ │ ├── binanceeuropeanoptions.ts │ │ ├── bitfinex.ts │ │ ├── bitflyer.ts │ │ ├── bitget.ts │ │ ├── bitmex.ts │ │ ├── bitnomial.ts │ │ ├── bitstamp.ts │ │ ├── blockchaincom.ts │ │ ├── bybit.ts │ │ ├── bybitspot.ts │ │ ├── coinbase.ts │ │ ├── coinbaseinternational.ts │ │ ├── coinflex.ts │ │ ├── cryptocom.ts │ │ ├── cryptofacilities.ts │ │ ├── delta.ts │ │ ├── deribit.ts │ │ ├── dydx.ts │ │ ├── dydxv4.ts │ │ ├── ftx.ts │ │ ├── gateio.ts │ │ ├── gateiofutures.ts │ │ ├── gemini.ts │ │ ├── hitbtc.ts │ │ ├── huobi.ts │ │ ├── hyperliquid.ts │ │ ├── index.ts │ │ ├── kraken.ts │ │ ├── kucoin.ts │ │ ├── kucoinfutures.ts │ │ ├── mapper.ts │ │ ├── okex.ts │ │ ├── okexspreads.ts │ │ ├── phemex.ts │ │ ├── poloniex.ts │ │ ├── serum.ts │ │ ├── upbit.ts │ │ └── woox.ts │ ├── options.ts │ ├── orderbook.ts │ ├── realtimefeeds/ │ │ ├── ascendex.ts │ │ ├── binance.ts │ │ ├── binancedex.ts │ │ ├── binanceeuropeanoptions.ts │ │ ├── bitfinex.ts │ │ ├── bitflyer.ts │ │ ├── bitget.ts │ │ ├── bitmex.ts │ │ ├── bitnomial.ts │ │ ├── bitstamp.ts │ │ ├── blockchaincom.ts │ │ ├── bybit.ts │ │ ├── coinbase.ts │ │ ├── coinbaseinternational.ts │ │ ├── coinflex.ts │ │ ├── cryptocom.ts │ │ ├── cryptofacilities.ts │ │ ├── delta.ts │ │ ├── deribit.ts │ │ ├── dydx.ts │ │ ├── dydx_v4.ts │ │ ├── ftx.ts │ │ ├── gateio.ts │ │ ├── gateiofutures.ts │ │ ├── gemini.ts │ │ ├── hitbtc.ts │ │ ├── huobi.ts │ │ ├── hyperliquid.ts │ │ ├── index.ts │ │ ├── kraken.ts │ │ ├── kucoin.ts │ │ ├── kucoinfutures.ts │ │ ├── mango.ts │ │ ├── okex.ts │ │ ├── okexspreads.ts │ │ ├── phemex.ts │ │ ├── poloniex.ts │ │ ├── realtimefeed.ts │ │ ├── serum.ts │ │ ├── staratlas.ts │ │ ├── upbit.ts │ │ └── woox.ts │ ├── replay.ts │ ├── stream.ts │ ├── types.ts │ └── worker.ts ├── test/ │ ├── __snapshots__/ │ │ ├── combine.test.ts.snap │ │ ├── compute.test.ts.snap │ │ ├── mappers.test.ts.snap │ │ └── replay.test.ts.snap │ ├── binance-futures-split.live.test.ts │ ├── binance-openinterest.live.test.ts │ ├── binarysplit.test.ts │ ├── combine.test.ts │ ├── compute.test.ts │ ├── downloaddatasets.test.ts │ ├── gate-io-futures-decimal.live.test.ts │ ├── httpclient.test.ts │ ├── live.ts │ ├── mappers.test.ts │ ├── orderbook.test.ts │ ├── package-exports.test.ts │ ├── replay.test.ts │ ├── setup.js │ ├── stream.test.ts │ └── tsconfig.json └── tsconfig.json
SYMBOL INDEX (1582 symbols across 107 files)
FILE: example.js
function produceVolumeBasedTradeBars (line 21) | async function produceVolumeBasedTradeBars(messages) {
FILE: src/apikeyaccessinfo.ts
function getApiKeyAccessInfo (line 5) | async function getApiKeyAccessInfo(apiKey?: string) {
type ApiKeyAccessInfo (line 18) | type ApiKeyAccessInfo = {
FILE: src/binarysplit.ts
class BinarySplitStream (line 5) | class BinarySplitStream extends Transform {
method constructor (line 9) | constructor() {
method _transform (line 18) | _transform(chunk: Buffer, _: string, callback: TransformCallback) {
FILE: src/clearcache.ts
function clearCache (line 8) | async function clearCache(exchange?: Exchange, filters?: Filter<any>[], ...
function clearCacheSync (line 22) | function clearCacheSync(exchange?: Exchange, filters?: Filter<any>[], ye...
function getDirToRemove (line 36) | function getDirToRemove(exchange?: Exchange, filters?: Filter<any>[], ye...
FILE: src/combine.ts
type NextMessageResultWithIndex (line 4) | type NextMessageResultWithIndex = {
type Combinable (line 9) | type Combinable = { localTimestamp: Date }
constant DATE_MAX (line 11) | const DATE_MAX = new Date(8640000000000000)
type OffsetMS (line 13) | type OffsetMS = number | ((message: Combinable) => number)
function nextWithIndex (line 15) | async function nextWithIndex(
function findOldestResult (line 44) | function findOldestResult(oldest: NextMessageResultWithIndex, current: N...
FILE: src/computable/booksnapshot.ts
type BookSnapshotComputableOptions (line 6) | type BookSnapshotComputableOptions = {
class BookSnapshotComputable (line 37) | class BookSnapshotComputable implements Computable<BookSnapshot> {
method constructor (line 54) | constructor({ depth, name, interval, removeCrossedLevels, grouping, on...
method compute (line 78) | public *compute(bookChange: BookChange) {
method _hasNewSnapshot (line 96) | public _hasNewSnapshot(timestamp: Date): boolean {
method _update (line 124) | public _update(bookChange: BookChange) {
method _updatedNotGrouped (line 137) | private _updatedNotGrouped() {
method _updateSideGrouped (line 178) | private _updateSideGrouped(
method _getSnapshot (line 231) | public _getSnapshot(bookChange: BookChange) {
method _getTimeBucket (line 251) | private _getTimeBucket(timestamp: Date) {
FILE: src/computable/computable.ts
type Computable (line 3) | type Computable<T extends NormalizedData> = {
type ComputableFactory (line 8) | type ComputableFactory<T extends NormalizedData> = () => Computable<T>
function compute (line 45) | function compute<T extends ComputableFactory<any>[], U extends Normalize...
class Computables (line 58) | class Computables {
method constructor (line 65) | constructor(private readonly _computablesFactories: ComputableFactory<...
method getOrCreate (line 67) | getOrCreate(exchange: Exchange, id: string) {
method reset (line 79) | reset(exchange: Exchange) {
function createComputablesMap (line 84) | function createComputablesMap(computables: Computable<any>[]) {
FILE: src/computable/tradebar.ts
constant DATE_MIN (line 4) | const DATE_MIN = new Date(-1)
type BarKind (line 6) | type BarKind = 'time' | 'volume' | 'tick'
type TradeBarComputableOptions (line 8) | type TradeBarComputableOptions = { kind: BarKind; interval: number; name...
class TradeBarComputable (line 21) | class TradeBarComputable implements Computable<TradeBar> {
method constructor (line 34) | constructor({ kind, interval, name }: TradeBarComputableOptions) {
method compute (line 48) | public *compute(message: Trade | BookChange) {
method _computeBar (line 67) | private _computeBar(message: NormalizedData) {
method _hasNewBar (line 79) | private _hasNewBar(timestamp: Date): boolean {
method _update (line 111) | private _update(trade: Trade) {
method _reset (line 144) | private _reset() {
method _getTimeBucket (line 170) | private _getTimeBucket(timestamp: Date) {
FILE: src/consts.ts
constant EXCHANGES (line 1) | const EXCHANGES = [
constant BINANCE_CHANNELS (line 61) | const BINANCE_CHANNELS = ['trade', 'aggTrade', 'ticker', 'depth', 'depth...
constant BINANCE_DEX_CHANNELS (line 62) | const BINANCE_DEX_CHANNELS = ['trades', 'marketDiff', 'depthSnapshot', '...
constant BITFINEX_CHANNELS (line 63) | const BITFINEX_CHANNELS = ['trades', 'book', 'raw_book', 'ticker'] as const
constant BITMEX_CHANNELS (line 65) | const BITMEX_CHANNELS = [
constant BITSTAMP_CHANNELS (line 90) | const BITSTAMP_CHANNELS = ['live_trades', 'live_orders', 'diff_order_boo...
constant COINBASE_CHANNELS (line 92) | const COINBASE_CHANNELS = [
constant DERIBIT_CHANNELS (line 107) | const DERIBIT_CHANNELS = [
constant KRAKEN_CHANNELS (line 122) | const KRAKEN_CHANNELS = ['trade', 'ticker', 'book', 'spread'] as const
constant OKEX_CHANNELS (line 124) | const OKEX_CHANNELS = [
constant OKCOIN_CHANNELS (line 150) | const OKCOIN_CHANNELS = [
constant OKEX_FUTURES_CHANNELS (line 162) | const OKEX_FUTURES_CHANNELS = [
constant OKEX_SWAP_CHANNELS (line 201) | const OKEX_SWAP_CHANNELS = [
constant OKEX_OPTIONS_CHANNELS (line 241) | const OKEX_OPTIONS_CHANNELS = [
constant COINFLEX_CHANNELS (line 272) | const COINFLEX_CHANNELS = ['futures/depth', 'trade', 'ticker'] as const
constant CRYPTOFACILITIES_CHANNELS (line 274) | const CRYPTOFACILITIES_CHANNELS = ['trade', 'trade_snapshot', 'book', 'b...
constant FTX_CHANNELS (line 276) | const FTX_CHANNELS = [
constant GEMINI_CHANNELS (line 290) | const GEMINI_CHANNELS = ['trade', 'l2_updates', 'auction_open', 'auction...
constant BITFLYER_CHANNELS (line 292) | const BITFLYER_CHANNELS = ['lightning_executions', 'lightning_board_snap...
constant BINANCE_FUTURES_CHANNELS (line 294) | const BINANCE_FUTURES_CHANNELS = [
constant BINANCE_DELIVERY_CHANNELS (line 317) | const BINANCE_DELIVERY_CHANNELS = [
constant BITFINEX_DERIV_CHANNELS (line 337) | const BITFINEX_DERIV_CHANNELS = ['trades', 'book', 'raw_book', 'status',...
constant HUOBI_CHANNELS (line 339) | const HUOBI_CHANNELS = ['depth', 'detail', 'trade', 'bbo', 'mbp', 'etp',...
constant HUOBI_DM_CHANNELS (line 341) | const HUOBI_DM_CHANNELS = [
constant HUOBI_DM_SWAP_CHANNELS (line 354) | const HUOBI_DM_SWAP_CHANNELS = [
constant HUOBI_DM_LINEAR_SWAP_CHANNELS (line 368) | const HUOBI_DM_LINEAR_SWAP_CHANNELS = [
constant PHEMEX_CHANNELS (line 382) | const PHEMEX_CHANNELS = ['book', 'orderbook_p', 'trades', 'trades_p', 'm...
constant BYBIT_CHANNELS (line 384) | const BYBIT_CHANNELS = [
constant BYBIT_OPTIONS_CHANNELS (line 408) | const BYBIT_OPTIONS_CHANNELS = ['orderbook.25', 'orderbook.100', 'public...
constant HITBTC_CHANNELS (line 410) | const HITBTC_CHANNELS = ['updateTrades', 'snapshotTrades', 'snapshotOrde...
constant FTX_US_CHANNELS (line 412) | const FTX_US_CHANNELS = ['orderbook', 'trades', 'markets', 'orderbookGro...
constant DELTA_CHANNELS (line 414) | const DELTA_CHANNELS = [
constant GATE_IO_CHANNELS (line 430) | const GATE_IO_CHANNELS = ['trades', 'depth', 'ticker', 'book_ticker', 'o...
constant GATE_IO_FUTURES_CHANNELS (line 431) | const GATE_IO_FUTURES_CHANNELS = ['trades', 'order_book', 'tickers', 'bo...
constant POLONIEX_CHANNELS (line 432) | const POLONIEX_CHANNELS = ['price_aggregated_book', 'trades', 'ticker', ...
constant UPBIT_CHANNELS (line 433) | const UPBIT_CHANNELS = ['trade', 'orderbook', 'ticker'] as const
constant ASCENDEX_CHANNELS (line 434) | const ASCENDEX_CHANNELS = ['trades', 'depth-realtime', 'depth-snapshot-r...
constant DYDX_CHANNELS (line 435) | const DYDX_CHANNELS = ['v3_trades', 'v3_orderbook', 'v3_markets'] as const
constant DYDX_V4_CHANNELS (line 436) | const DYDX_V4_CHANNELS = ['v4_trades', 'v4_orderbook', 'v4_markets'] as ...
constant SERUM_CHANNELS (line 437) | const SERUM_CHANNELS = [
constant MANGO_CHANNELS (line 450) | const MANGO_CHANNELS = [
constant HUOBI_DM_OPTIONS_CHANNELS (line 463) | const HUOBI_DM_OPTIONS_CHANNELS = ['trade', 'detail', 'depth', 'bbo', 'o...
constant BYBIT_SPOT_CHANNELS (line 465) | const BYBIT_SPOT_CHANNELS = ['trade', 'bookTicker', 'depth', 'orderbook....
constant CRYPTO_COM_CHANNELS (line 467) | const CRYPTO_COM_CHANNELS = ['trade', 'book', 'ticker', 'settlement', 'i...
constant KUCOIN_CHANNELS (line 469) | const KUCOIN_CHANNELS = ['market/ticker', 'market/snapshot', 'market/lev...
constant BITNOMIAL_CHANNELS (line 471) | const BITNOMIAL_CHANNELS = ['trade', 'level', 'book', 'block', 'status']
constant WOOX_CHANNELS (line 473) | const WOOX_CHANNELS = [
constant BLOCKCHAIN_COM_CHANNELS (line 485) | const BLOCKCHAIN_COM_CHANNELS = ['trades', 'l2', 'l3', 'ticker']
constant BINANCE_EUROPEAN_OPTIONS_CHANNELS (line 486) | const BINANCE_EUROPEAN_OPTIONS_CHANNELS = [
constant OKEX_SPREADS_CHANNELS (line 503) | const OKEX_SPREADS_CHANNELS = ['sprd-public-trades', 'sprd-bbo-tbt', 'sp...
constant KUCOIN_FUTURES_CHANNELS (line 505) | const KUCOIN_FUTURES_CHANNELS = [
constant BITGET_CHANNELS (line 515) | const BITGET_CHANNELS = ['trade', 'books1', 'books15']
constant BITGET_FUTURES_CHANNELS (line 516) | const BITGET_FUTURES_CHANNELS = ['trade', 'books1', 'books15', 'ticker']
constant COINBASE_INTERNATIONAL_CHANNELS (line 517) | const COINBASE_INTERNATIONAL_CHANNELS = ['INSTRUMENTS', 'MATCH', 'FUNDIN...
constant HYPERLIQUID_CHANNELS (line 519) | const HYPERLIQUID_CHANNELS = ['l2Book', 'trades', 'activeAssetCtx', 'act...
constant EXCHANGE_CHANNELS_INFO (line 521) | const EXCHANGE_CHANNELS_INFO = {
FILE: src/downloaddatasets.ts
constant CONCURRENCY_LIMIT (line 9) | const CONCURRENCY_LIMIT = 20
constant MILLISECONDS_IN_SINGLE_DAY (line 10) | const MILLISECONDS_IN_SINGLE_DAY = 24 * 60 * 60 * 1000
constant DEFAULT_DOWNLOAD_DIR (line 11) | const DEFAULT_DOWNLOAD_DIR = './datasets'
function downloadDatasets (line 15) | async function downloadDatasets(downloadDatasetsOptions: DownloadDataset...
function downloadDataSet (line 90) | async function downloadDataSet(downloadOptions: DownloadOptions, skipIfE...
function getDownloadOptions (line 99) | function getDownloadOptions({
type DownloadOptions (line 141) | type DownloadOptions = Parameters<typeof download>[0]
function sanitizeForFilename (line 143) | function sanitizeForFilename(s: string) {
function getFilenameDefault (line 147) | function getFilenameDefault({ exchange, dataType, format, date, symbol }...
function getDownloadDateRange (line 151) | function getDownloadDateRange({ from, to }: DownloadDatasetsOptions) {
type GetFilenameOptions (line 174) | type GetFilenameOptions = {
type DownloadDatasetsOptions (line 182) | type DownloadDatasetsOptions = {
FILE: src/exchangedetails.ts
function getExchangeDetails (line 5) | async function getExchangeDetails<T extends Exchange>(exchange: T) {
type SymbolType (line 13) | type SymbolType = 'spot' | 'future' | 'perpetual' | 'option' | 'combo'
type DatasetType (line 15) | type DatasetType =
type Stats (line 26) | type Stats = {
type Datasets (line 31) | type Datasets = {
type ChannelDetails (line 45) | type ChannelDetails = {
type DataCenter (line 59) | type DataCenter = {
type DataCollectionDetails (line 65) | type DataCollectionDetails = {
type ExchangeDetailsBase (line 89) | type ExchangeDetailsBase<T extends Exchange> = {
type ExchangeDetails (line 120) | type ExchangeDetails<T extends Exchange> = ExchangeDetailsBase<T>
FILE: src/filter.ts
function uniqueTradesOnly (line 12) | function uniqueTradesOnly<T extends NormalizedData | Disconnect>(
FILE: src/handy.ts
function parseAsUTCDate (line 16) | function parseAsUTCDate(val: string) {
function wait (line 25) | function wait(delayMS: number) {
function getRandomString (line 31) | function getRandomString() {
function formatDateToPath (line 35) | function formatDateToPath(date: Date) {
function doubleDigit (line 45) | function doubleDigit(input: number) {
function sha256 (line 49) | function sha256(obj: object) {
function addMinutes (line 53) | function addMinutes(date: Date, minutes: number) {
function addDays (line 57) | function addDays(date: Date, days: number) {
constant ONE_SEC_IN_MS (line 71) | const ONE_SEC_IN_MS = 1000
class HttpError (line 73) | class HttpError extends Error {
method constructor (line 74) | constructor(public readonly status: number, public readonly responseTe...
class HttpClientError (line 79) | class HttpClientError extends Error {
method constructor (line 80) | constructor(public readonly response: HttpResponse, public readonly me...
function getFilters (line 160) | function getFilters<T extends Exchange>(mappers: Mapper<T, any>[], symbo...
function parseμs (line 196) | function parseμs(dateString: string): number {
function optimizeFilters (line 206) | function optimizeFilters(filters: Filter<any>[]) {
constant DEFAULT_FETCH_RETRY_LIMIT (line 261) | const DEFAULT_FETCH_RETRY_LIMIT = 2
type HttpRetryOptions (line 263) | type HttpRetryOptions =
type HttpRequestOptions (line 271) | type HttpRequestOptions = {
type HttpResponse (line 278) | type HttpResponse = {
type JSONResponse (line 284) | type JSONResponse<T> = {
type RetrySettings (line 290) | type RetrySettings = {
function getRetrySettings (line 296) | function getRetrySettings(method: string, retry?: HttpRetryOptions): Ret...
function parseResponseHeaders (line 308) | function parseResponseHeaders(headers: Headers) {
function parseNodeResponseHeaders (line 312) | function parseNodeResponseHeaders(headers: Record<string, string | strin...
function createHttpResponse (line 324) | function createHttpResponse(statusCode: number, headers: Record<string, ...
function prepareRequest (line 332) | function prepareRequest(method: string, options: HttpRequestOptions) {
function getRetryAfterDelayMS (line 353) | function getRetryAfterDelayMS(headers: Record<string, string>, maxRetryA...
function getRetryDelayMS (line 382) | function getRetryDelayMS(attempt: number, headers: Record<string, string...
function isRetryableStatus (line 391) | function isRetryableStatus(statusCode: number, retrySettings: RetrySetti...
function shouldRetryHttpStatus (line 399) | function shouldRetryHttpStatus(attempt: number, response: HttpResponse, ...
function shouldRetryHttpError (line 403) | function shouldRetryHttpError(attempt: number, retrySettings: RetrySetti...
function requestViaFetch (line 407) | async function requestViaFetch(method: string, url: string, options: Htt...
function requestViaProxy (line 436) | async function requestViaProxy(method: string, url: string, options: Htt...
function request (line 474) | async function request(method: string, url: string, options: HttpRequest...
function requestJSON (line 507) | async function requestJSON<T>(method: string, url: string, options?: Htt...
function getJSON (line 517) | function getJSON<T>(url: string, options?: HttpRequestOptions) {
function postJSON (line 521) | function postJSON<T>(url: string, options?: HttpRequestOptions) {
function download (line 525) | async function download({
function cleanTempFiles (line 596) | function cleanTempFiles() {
function _downloadFile (line 600) | async function _downloadFile(requestOptions: RequestOptions, url: string...
function asSingleHeaderValue (line 682) | function asSingleHeaderValue(headerValue: string | string[] | undefined) {
class CircularBuffer (line 690) | class CircularBuffer<T> {
method constructor (line 693) | constructor(private readonly _bufferSize: number) {}
method append (line 695) | append(value: T) {
method items (line 707) | *items() {
method count (line 714) | get count() {
method clear (line 718) | clear() {
class CappedSet (line 724) | class CappedSet<T> {
method constructor (line 726) | constructor(private readonly _maxSize: number) {}
method has (line 728) | public has(value: T) {
method add (line 732) | public add(value: T) {
method remove (line 739) | public remove(value: T) {
method size (line 743) | public size() {
function hasFraction (line 748) | function hasFraction(n: number) {
function decimalPlaces (line 752) | function decimalPlaces(n: number) {
function asNumberIfValid (line 759) | function asNumberIfValid(val: string | number | undefined | null) {
function upperCaseSymbols (line 777) | function upperCaseSymbols(symbols?: string[]) {
function lowerCaseSymbols (line 784) | function lowerCaseSymbols(symbols?: string[]) {
function onlyUnique (line 804) | function onlyUnique(value: string, index: number, array: string[]) {
FILE: src/instrumentinfo.ts
function getInstrumentInfo (line 10) | async function getInstrumentInfo(exchange: Exchange | Exchange[], filter...
function getInstrumentInfoForExchange (line 21) | async function getInstrumentInfoForExchange(exchange: Exchange, filterOr...
type InstrumentInfoFilter (line 54) | type InstrumentInfoFilter = {
type ContractType (line 62) | type ContractType =
type InstrumentInfo (line 79) | interface InstrumentInfo {
FILE: src/mappers/ascendex.ts
class AscendexTradesMapper (line 5) | class AscendexTradesMapper implements Mapper<'ascendex', Trade> {
method canHandle (line 6) | canHandle(message: AscendexTrade) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 20) | *map(message: AscendexTrade, localTimestamp: Date): IterableIterator<T...
class AscendexBookChangeMapper (line 37) | class AscendexBookChangeMapper implements Mapper<'ascendex', BookChange> {
method canHandle (line 38) | canHandle(message: AscendexDepthRealTime | AscendexDepthRealTimeSnapsh...
method getFilters (line 42) | getFilters(symbols?: string[]) {
method map (line 56) | *map(message: AscendexDepthRealTime | AscendexDepthRealTimeSnapshot, l...
method mapBookLevel (line 73) | protected mapBookLevel(level: AscendexPriceLevel) {
class AscendexDerivativeTickerMapper (line 80) | class AscendexDerivativeTickerMapper implements Mapper<'ascendex', Deriv...
method canHandle (line 83) | canHandle(message: AscendexFuturesData | AscendexTrade) {
method getFilters (line 87) | getFilters(symbols?: string[]) {
method map (line 101) | *map(message: AscendexFuturesData | AscendexTrade, localTimestamp: Dat...
class AscendexBookTickerMapper (line 125) | class AscendexBookTickerMapper implements Mapper<'ascendex', BookTicker> {
method canHandle (line 126) | canHandle(message: AscendexTicker) {
method getFilters (line 130) | getFilters(symbols?: string[]) {
method map (line 140) | *map(message: AscendexTicker, localTimestamp: Date): IterableIterator<...
type AscendexTrade (line 159) | type AscendexTrade = {
type AscendexPriceLevel (line 165) | type AscendexPriceLevel = [string, string]
type AscendexDepthRealTime (line 167) | type AscendexDepthRealTime = {
type AscendexDepthRealTimeSnapshot (line 173) | type AscendexDepthRealTimeSnapshot = {
type AscendexTicker (line 184) | type AscendexTicker = { m: 'bbo'; symbol: string; data: { ts: number; bi...
type AscendexFuturesData (line 186) | type AscendexFuturesData = {
FILE: src/mappers/binance.ts
class BinanceTradesMapper (line 8) | class BinanceTradesMapper
method constructor (line 11) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 13) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 21) | getFilters(symbols?: string[]) {
method map (line 32) | *map(binanceTradeResponse: BinanceResponse<BinanceTradeData>, localTim...
class BinanceBookChangeMapper (line 56) | class BinanceBookChangeMapper
method constructor (line 63) | constructor(protected readonly exchange: Exchange, protected readonly ...
method canHandle (line 65) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 73) | getFilters(symbols?: string[]) {
method map (line 88) | *map(message: BinanceResponse<BinanceDepthData | BinanceDepthSnapshotD...
method mapBookDepthUpdate (line 165) | protected mapBookDepthUpdate(binanceDepthUpdateData: BinanceDepthData,...
method mapBookLevel (line 209) | protected mapBookLevel(level: BinanceBookLevel) {
class BinanceFuturesBookChangeMapper (line 216) | class BinanceFuturesBookChangeMapper
method constructor (line 220) | constructor(protected readonly exchange: Exchange, protected readonly ...
method mapBookDepthUpdate (line 224) | protected mapBookDepthUpdate(binanceDepthUpdateData: BinanceFuturesDep...
class BinanceFuturesDerivativeTickerMapper (line 268) | class BinanceFuturesDerivativeTickerMapper implements Mapper<'binance-fu...
method constructor (line 272) | constructor(protected readonly exchange: Exchange) {}
method canHandle (line 274) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 287) | getFilters(symbols?: string[]): FilterForExchange['binance-futures' | ...
method map (line 316) | *map(
class BinanceLiquidationsMapper (line 364) | class BinanceLiquidationsMapper implements Mapper<'binance-futures' | 'b...
method constructor (line 365) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 367) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 375) | getFilters(symbols?: string[]) {
method map (line 386) | *map(binanceTradeResponse: BinanceResponse<BinanceFuturesForceOrderDat...
class BinanceBookTickerMapper (line 409) | class BinanceBookTickerMapper implements Mapper<'binance-futures' | 'bin...
method constructor (line 410) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 412) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 420) | getFilters(symbols?: string[]) {
method map (line 431) | *map(binanceBookTickerResponse: BinanceResponse<BinanceBookTickerData>...
type BinanceResponse (line 450) | type BinanceResponse<T> = {
type BinanceTradeData (line 455) | type BinanceTradeData = {
type BinanceBookLevel (line 465) | type BinanceBookLevel = [string, string]
type BinanceDepthData (line 467) | type BinanceDepthData = {
type BinanceFuturesDepthData (line 479) | type BinanceFuturesDepthData = BinanceDepthData & {
type BinanceDepthSnapshotData (line 484) | type BinanceDepthSnapshotData = {
type LocalDepthInfo (line 491) | type LocalDepthInfo = {
type BinanceFuturesMarkPriceData (line 498) | type BinanceFuturesMarkPriceData = {
type BinanceFuturesTickerData (line 508) | type BinanceFuturesTickerData = {
type BinanceFuturesOpenInterestData (line 515) | type BinanceFuturesOpenInterestData = {
type BinanceFuturesIndexPriceData (line 521) | type BinanceFuturesIndexPriceData = {
type BinanceFuturesForceOrderData (line 528) | type BinanceFuturesForceOrderData = {
type BinanceBookTickerData (line 542) | type BinanceBookTickerData = {
FILE: src/mappers/binancedex.ts
method canHandle (line 8) | canHandle(message: BinanceDexResponse<any>) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(binanceDexTradeResponse: BinanceDexResponse<BinanceDexTradeData>, l...
method canHandle (line 47) | canHandle(message: BinanceDexResponse<any>) {
method getFilters (line 51) | getFilters(symbols?: string[]) {
method map (line 65) | *map(
method canHandle (line 98) | canHandle(message: BinanceDexResponse<any>) {
method getFilters (line 102) | getFilters(symbols?: string[]) {
method map (line 112) | *map(binanceDexTradeResponse: BinanceDexResponse<BinanceDexTickerData>, ...
type BinanceDexResponse (line 132) | type BinanceDexResponse<T> = {
type BinanceDexTradeData (line 137) | type BinanceDexTradeData = {
type BinanceDexBookLevel (line 147) | type BinanceDexBookLevel = [string, string]
type BinanceDexDepthSnapshotData (line 149) | type BinanceDexDepthSnapshotData = {
type BinanceDexMarketDiffData (line 155) | type BinanceDexMarketDiffData = {
type BinanceDexTickerData (line 162) | type BinanceDexTickerData = {
FILE: src/mappers/binanceeuropeanoptions.ts
class BinanceEuropeanOptionsTradesMapper (line 7) | class BinanceEuropeanOptionsTradesMapper implements Mapper<'binance-euro...
method canHandle (line 8) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 16) | getFilters(symbols?: string[]) {
method map (line 27) | *map(binanceTradeResponse: BinanceResponse<BinanceOptionsTradeData>, l...
class BinanceEuropeanOptionsTradesMapperV2 (line 44) | class BinanceEuropeanOptionsTradesMapperV2 implements Mapper<'binance-eu...
method canHandle (line 45) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 53) | getFilters(symbols?: string[]) {
method map (line 64) | *map(binanceTradeResponse: BinanceResponse<BinanceOptionsTradeDataV2>,...
class BinanceEuropeanOptionsBookChangeMapper (line 81) | class BinanceEuropeanOptionsBookChangeMapper implements Mapper<'binance-...
method canHandle (line 82) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 90) | getFilters(symbols?: string[]) {
method map (line 101) | *map(message: BinanceResponse<BinanceOptionsDepthData>, localTimestamp...
method mapBookLevel (line 116) | protected mapBookLevel(level: BinanceBookLevel) {
class BinanceEuropeanOptionsBookChangeMapperV2 (line 123) | class BinanceEuropeanOptionsBookChangeMapperV2 implements Mapper<'binanc...
method canHandle (line 124) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 132) | getFilters(symbols?: string[]) {
method map (line 143) | *map(message: BinanceResponse<BinanceOptionsDepthDataV2>, localTimesta...
method mapBookLevel (line 158) | protected mapBookLevel(level: BinanceBookLevel) {
class BinanceEuropeanOptionsBookTickerMapper (line 165) | class BinanceEuropeanOptionsBookTickerMapper implements Mapper<'binance-...
method canHandle (line 166) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 174) | getFilters(symbols?: string[]) {
method map (line 185) | *map(message: BinanceResponse<BinanceOptionsBookTickerData>, localTime...
class BinanceEuropeanOptionSummaryMapper (line 207) | class BinanceEuropeanOptionSummaryMapper implements Mapper<'binance-euro...
method canHandle (line 211) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 219) | getFilters(symbols?: string[]) {
method map (line 254) | *map(
class BinanceEuropeanOptionSummaryMapperV2 (line 357) | class BinanceEuropeanOptionSummaryMapperV2 implements Mapper<'binance-eu...
method canHandle (line 361) | canHandle(message: BinanceResponse<any>) {
method getFilters (line 373) | getFilters(symbols?: string[]) {
method map (line 400) | *map(
type BinanceResponse (line 516) | type BinanceResponse<T> = {
type BinanceOptionsTradeData (line 521) | type BinanceOptionsTradeData = {
type BinanceOptionsDepthData (line 534) | type BinanceOptionsDepthData = {
type BinanceOptionsTickerData (line 545) | type BinanceOptionsTickerData = {
type BinanceOptionsIndexData (line 579) | type BinanceOptionsIndexData = { e: 'index'; E: 1696118400040; s: 'BNBUS...
type BinanceOptionsOpenInterestData (line 581) | type BinanceOptionsOpenInterestData = { e: 'openInterest'; E: 1696118400...
type BinanceBookLevel (line 583) | type BinanceBookLevel = [string, string]
type BinanceOptionsTradeDataV2 (line 586) | type BinanceOptionsTradeDataV2 = {
type BinanceOptionsDepthDataV2 (line 599) | type BinanceOptionsDepthDataV2 = {
type BinanceOptionsBookTickerData (line 611) | type BinanceOptionsBookTickerData = {
type BinanceOptionsOpenInterestDataV2 (line 623) | type BinanceOptionsOpenInterestDataV2 = {
type BinanceOptionsMarkPriceData (line 631) | type BinanceOptionsMarkPriceData = {
FILE: src/mappers/bitfinex.ts
class BitfinexTradesMapper (line 7) | class BitfinexTradesMapper implements Mapper<'bitfinex' | 'bitfinex-deri...
method constructor (line 10) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 12) | canHandle(message: BitfinexMessage) {
method getFilters (line 36) | getFilters(symbols?: string[]) {
method map (line 47) | *map(message: BitfinexTrades, localTimestamp: Date) {
class BitfinexBookChangeMapper (line 83) | class BitfinexBookChangeMapper implements Mapper<'bitfinex' | 'bitfinex-...
method constructor (line 86) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 88) | canHandle(message: BitfinexMessage) {
method getFilters (line 112) | getFilters(symbols?: string[]) {
method map (line 123) | *map(message: BitfinexBooks, localTimestamp: Date) {
method _mapBookLevel (line 157) | private _mapBookLevel(level: BitfinexBookLevel) {
class BitfinexDerivativeTickerMapper (line 165) | class BitfinexDerivativeTickerMapper implements Mapper<'bitfinex-derivat...
method canHandle (line 169) | canHandle(message: BitfinexMessage) {
method getFilters (line 194) | getFilters(symbols?: string[]) {
method map (line 205) | *map(message: BitfinexStatusMessage, localTimestamp: Date): IterableIt...
class BitfinexLiquidationsMapper (line 248) | class BitfinexLiquidationsMapper implements Mapper<'bitfinex-derivatives...
method constructor (line 251) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 253) | canHandle(message: BitfinexMessage) {
method getFilters (line 277) | getFilters() {
method map (line 286) | *map(message: BitfinexLiquidation, localTimestamp: Date) {
class BitfinexBookTickerMapper (line 325) | class BitfinexBookTickerMapper implements Mapper<'bitfinex' | 'bitfinex-...
method constructor (line 328) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 330) | canHandle(message: BitfinexMessage) {
method getFilters (line 354) | getFilters(symbols?: string[]) {
method map (line 365) | *map(message: BitfinexTicker, localTimestamp: Date) {
type BitfinexMessage (line 402) | type BitfinexMessage =
type BitfinexHeartbeat (line 413) | type BitfinexHeartbeat = [number, 'hb']
type BitfinexTrades (line 414) | type BitfinexTrades = [number, 'te' | any[], [number, number, number, nu...
type BitfinexBookLevel (line 415) | type BitfinexBookLevel = [number, number, number]
type BitfinexBooks (line 416) | type BitfinexBooks = [number, BitfinexBookLevel | BitfinexBookLevel[], n...
type BitfinexStatusMessage (line 418) | type BitfinexStatusMessage = [number, (number | undefined)[], number, nu...
type BitfinexLiquidation (line 420) | type BitfinexLiquidation =
type BitfinexTicker (line 424) | type BitfinexTicker =
FILE: src/mappers/bitflyer.ts
method canHandle (line 6) | canHandle(message: BitflyerExecutions | BitflyerBoard) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 21) | *map(bitflyerExecutions: BitflyerExecutions, localTimestamp: Date) {
class BitflyerBookChangeMapper (line 49) | class BitflyerBookChangeMapper implements Mapper<'bitflyer', BookChange> {
method canHandle (line 52) | canHandle(message: BitflyerExecutions | BitflyerBoard) {
method getFilters (line 56) | getFilters(symbols?: string[]) {
method map (line 71) | *map(bitflyerBoard: BitflyerBoard, localTimestamp: Date): IterableIter...
method canHandle (line 99) | canHandle(message: BitflyerTicker) {
method getFilters (line 103) | getFilters(symbols?: string[]) {
method map (line 114) | *map(bitflyerTickerMessage: BitflyerTicker, localTimestamp: Date) {
type BitflyerExecutions (line 138) | type BitflyerExecutions = {
type BitflyerBookLevel (line 152) | type BitflyerBookLevel = {
type BitflyerBoard (line 157) | type BitflyerBoard = {
type BitflyerTicker (line 168) | type BitflyerTicker = {
FILE: src/mappers/bitget.ts
class BitgetTradesMapper (line 5) | class BitgetTradesMapper implements Mapper<'bitget' | 'bitget-futures', ...
method constructor (line 6) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 8) | canHandle(message: BitgetTradeMessage) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(message: BitgetTradeMessage, localTimestamp: Date): IterableItera...
function mapPriceLevel (line 40) | function mapPriceLevel(level: [string, string]) {
class BitgetBookChangeMapper (line 46) | class BitgetBookChangeMapper implements Mapper<'bitget' | 'bitget-future...
method constructor (line 47) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 49) | canHandle(message: BitgetOrderbookMessage) {
method getFilters (line 53) | getFilters(symbols?: string[]) {
method map (line 64) | *map(message: BitgetOrderbookMessage, localTimestamp: Date): IterableI...
class BitgetBookTickerMapper (line 80) | class BitgetBookTickerMapper implements Mapper<'bitget' | 'bitget-future...
method constructor (line 81) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 83) | canHandle(message: BitgetBBoMessage) {
method getFilters (line 87) | getFilters(symbols?: string[]) {
method map (line 98) | *map(message: BitgetBBoMessage, localTimestamp: Date): IterableIterato...
class BitgetDerivativeTickerMapper (line 119) | class BitgetDerivativeTickerMapper implements Mapper<'bitget-futures', D...
method canHandle (line 122) | canHandle(message: BitgetTickerMessage) {
method getFilters (line 126) | getFilters(symbols?: string[]) {
method map (line 135) | *map(message: BitgetTickerMessage, localTimestamp: Date): IterableIter...
type BitgetTradeMessage (line 158) | type BitgetTradeMessage = {
type BitgetOrderbookMessage (line 165) | type BitgetOrderbookMessage = {
type BitgetBBoMessage (line 179) | type BitgetBBoMessage = {
type BitgetTickerMessage (line 186) | type BitgetTickerMessage = {
FILE: src/mappers/bitmex.ts
method canHandle (line 8) | canHandle(message: BitmexDataMessage) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(bitmexTradesMessage: BitmexTradesMessage, localTimestamp: Date) {
class BitmexBookChangeMapper (line 42) | class BitmexBookChangeMapper implements Mapper<'bitmex', BookChange> {
method canHandle (line 45) | canHandle(message: BitmexDataMessage) {
method getFilters (line 49) | getFilters(symbols?: string[]) {
method map (line 60) | *map(bitmexOrderBookL2Message: BitmexOrderBookL2Message, localTimestam...
class BitmexDerivativeTickerMapper (line 161) | class BitmexDerivativeTickerMapper implements Mapper<'bitmex', Derivativ...
method canHandle (line 164) | canHandle(message: BitmexDataMessage) {
method getFilters (line 168) | getFilters(symbols?: string[]) {
method map (line 179) | *map(message: BitmexInstrumentsMessage, localTimestamp: Date): Iterabl...
method canHandle (line 214) | canHandle(message: BitmexDataMessage) {
method getFilters (line 218) | getFilters(symbols?: string[]) {
method map (line 229) | *map(bitmexLiquiationsMessage: BitmexLiquidation, localTimestamp: Date) {
method canHandle (line 249) | canHandle(message: BitmexDataMessage) {
method getFilters (line 253) | getFilters(symbols?: string[]) {
method map (line 264) | *map(bitmexQuoteMessage: BitmexQuote, localTimestamp: Date) {
type BitmexDataMessage (line 284) | type BitmexDataMessage = {
type BitmexTradesMessage (line 289) | type BitmexTradesMessage = BitmexDataMessage & {
type BitmexInstrument (line 302) | type BitmexInstrument = {
type BitmexInstrumentsMessage (line 315) | type BitmexInstrumentsMessage = BitmexDataMessage & {
type BitmexOrderBookL2Message (line 320) | type BitmexOrderBookL2Message = BitmexDataMessage & {
type BitmexLiquidation (line 333) | type BitmexLiquidation = BitmexDataMessage & {
type BitmexQuote (line 344) | type BitmexQuote = BitmexDataMessage & {
FILE: src/mappers/bitnomial.ts
method canHandle (line 6) | canHandle(message: BitnomialTrade) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 21) | *map(message: BitnomialTrade, localTimestamp: Date): IterableIterator<Tr...
class BitnomialBookChangMapper (line 43) | class BitnomialBookChangMapper implements Mapper<'bitnomial', BookChange> {
method canHandle (line 44) | canHandle(message: BitnomialBookMessage) {
method getFilters (line 48) | getFilters(symbols?: string[]) {
method map (line 63) | *map(message: BitnomialBookMessage, localTimestamp: Date): IterableIte...
type BitnomialTrade (line 97) | type BitnomialTrade = {
type BookLevel (line 107) | type BookLevel = [number, number]
type BitnomialBookMessage (line 109) | type BitnomialBookMessage =
FILE: src/mappers/bitstamp.ts
method canHandle (line 8) | canHandle(message: BitstampTrade | BitstampDiffOrderBook | BitstampDiffO...
method getFilters (line 16) | getFilters(symbols?: string[]) {
method map (line 27) | *map(bitstampTradeResponse: BitstampTrade, localTimestamp: Date): Iterab...
class BitstampBookChangeMapper (line 48) | class BitstampBookChangeMapper implements Mapper<'bitstamp', BookChange> {
method canHandle (line 51) | canHandle(message: BitstampTrade | BitstampDiffOrderBook | BitstampDif...
method getFilters (line 59) | getFilters(symbols?: string[]) {
method map (line 70) | *map(message: BitstampDiffOrderBookSnapshot | BitstampDiffOrderBook, l...
method _mapBookDepthUpdate (line 133) | private _mapBookDepthUpdate(
method _mapBookLevel (line 164) | private _mapBookLevel(level: BitstampBookLevel) {
type BitstampTrade (line 171) | type BitstampTrade = {
type BitstampBookLevel (line 182) | type BitstampBookLevel = [string, string]
type BitstampDiffOrderBook (line 184) | type BitstampDiffOrderBook = {
type BitstampDiffOrderBookSnapshot (line 195) | type BitstampDiffOrderBookSnapshot = {
type LocalDepthInfo (line 206) | type LocalDepthInfo = {
FILE: src/mappers/blockchaincom.ts
class BlockchainComTradesMapper (line 5) | class BlockchainComTradesMapper implements Mapper<'blockchain-com', Trad...
method canHandle (line 6) | canHandle(message: BlockchainComTradeMessage) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 20) | *map(message: BlockchainComTradeMessage, localTimestamp: Date): Iterab...
class BlockchainComBookChangeMapper (line 35) | class BlockchainComBookChangeMapper implements Mapper<'blockchain-com', ...
method canHandle (line 36) | canHandle(message: BlockchainComL2Message) {
method getFilters (line 40) | getFilters(symbols?: string[]) {
method map (line 50) | *map(message: BlockchainComL2Message, localTimestamp: Date): IterableI...
method mapBookLevel (line 63) | protected mapBookLevel(level: { px: number; qty: number }) {
type BlockchainComTradeMessage (line 68) | type BlockchainComTradeMessage = {
type BlockchainComL2Message (line 80) | type BlockchainComL2Message =
FILE: src/mappers/bybit.ts
class BybitV5TradesMapper (line 7) | class BybitV5TradesMapper implements Mapper<'bybit' | 'bybit-spot' | 'by...
method constructor (line 8) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 10) | canHandle(message: BybitV5Trade) {
method getFilters (line 18) | getFilters(symbols?: string[]) {
method map (line 29) | *map(message: BybitV5Trade, localTimestamp: Date): IterableIterator<Tr...
class BybitV5BookChangeMapper (line 46) | class BybitV5BookChangeMapper implements Mapper<'bybit' | 'bybit-spot' |...
method constructor (line 47) | constructor(protected readonly _exchange: Exchange, private readonly _...
method canHandle (line 49) | canHandle(message: BybitV5OrderBookMessage) {
method getFilters (line 56) | getFilters(symbols?: string[]) {
method map (line 66) | *map(message: BybitV5OrderBookMessage, localTimestamp: Date) {
method _mapBookLevel (line 79) | private _mapBookLevel(level: [string, string]) {
class BybitV5BookTickerMapper (line 84) | class BybitV5BookTickerMapper implements Mapper<'bybit' | 'bybit-spot', ...
method constructor (line 94) | constructor(protected readonly _exchange: Exchange) {}
method canHandle (line 96) | canHandle(message: BybitV5OrderBookMessage) {
method getFilters (line 103) | getFilters(symbols?: string[]) {
method map (line 113) | *map(message: BybitV5OrderBookMessage, localTimestamp: Date) {
class BybitV5DerivativeTickerMapper (line 154) | class BybitV5DerivativeTickerMapper implements Mapper<'bybit', Derivativ...
method canHandle (line 157) | canHandle(message: BybitV5DerivTickerMessage) {
method getFilters (line 165) | getFilters(symbols?: string[]) {
method map (line 176) | *map(message: BybitV5DerivTickerMessage, localTimestamp: Date): Iterab...
class BybitV5LiquidationsMapper (line 213) | class BybitV5LiquidationsMapper implements Mapper<'bybit', Liquidation> {
method constructor (line 214) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 215) | canHandle(message: BybitV5LiquidationMessage) {
method getFilters (line 223) | getFilters(symbols?: string[]) {
method map (line 234) | *map(message: BybitV5LiquidationMessage, localTimestamp: Date): Iterab...
class BybitV5AllLiquidationsMapper (line 254) | class BybitV5AllLiquidationsMapper implements Mapper<'bybit', Liquidatio...
method constructor (line 255) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 256) | canHandle(message: BybitV5AllLiquidationMessage) {
method getFilters (line 264) | getFilters(symbols?: string[]) {
method map (line 275) | *map(message: BybitV5AllLiquidationMessage, localTimestamp: Date): Ite...
class BybitV5OptionSummaryMapper (line 294) | class BybitV5OptionSummaryMapper implements Mapper<'bybit-options', Opti...
method canHandle (line 295) | canHandle(message: BybitV5OptionTickerMessage) {
method getFilters (line 303) | getFilters(symbols?: string[]) {
method map (line 314) | *map(message: BybitV5OptionTickerMessage, localTimestamp: Date) {
class BybitTradesMapper (line 364) | class BybitTradesMapper implements Mapper<'bybit', Trade> {
method constructor (line 365) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 366) | canHandle(message: BybitDataMessage) {
method getFilters (line 374) | getFilters(symbols?: string[]) {
method map (line 385) | *map(message: BybitTradeDataMessage, localTimestamp: Date): IterableIt...
class BybitBookChangeMapper (line 409) | class BybitBookChangeMapper implements Mapper<'bybit', BookChange> {
method constructor (line 410) | constructor(protected readonly _exchange: Exchange, private readonly _...
method canHandle (line 412) | canHandle(message: BybitDataMessage) {
method getFilters (line 424) | getFilters(symbols?: string[]) {
method map (line 444) | *map(message: BybitBookSnapshotDataMessage | BybitBookSnapshotUpdateMe...
method _mapBookLevel (line 472) | private _mapBookLevel(level: BybitBookLevel) {
class BybitDerivativeTickerMapper (line 477) | class BybitDerivativeTickerMapper implements Mapper<'bybit', DerivativeT...
method canHandle (line 480) | canHandle(message: BybitDataMessage) {
method getFilters (line 488) | getFilters(symbols?: string[]) {
method map (line 499) | *map(message: BybitInstrumentDataMessage, localTimestamp: Date): Itera...
class BybitLiquidationsMapper (line 575) | class BybitLiquidationsMapper implements Mapper<'bybit', Liquidation> {
method constructor (line 576) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 577) | canHandle(message: BybitDataMessage) {
method getFilters (line 585) | getFilters(symbols?: string[]) {
method map (line 596) | *map(message: BybitLiquidationMessage | BybitLiquidationNativeMessage,...
type BybitV5Trade (line 633) | type BybitV5Trade =
type BybitV5OrderBookMessage (line 667) | type BybitV5OrderBookMessage = {
type BybitV5DerivTickerMessage (line 680) | type BybitV5DerivTickerMessage = {
type BybitV5LiquidationMessage (line 702) | type BybitV5LiquidationMessage = {
type BybitV5AllLiquidationMessage (line 715) | type BybitV5AllLiquidationMessage = {
type BybitV5OptionTickerMessage (line 722) | type BybitV5OptionTickerMessage = {
type BybitDataMessage (line 756) | type BybitDataMessage = {
type BybitTradeDataMessage (line 760) | type BybitTradeDataMessage =
type BybitBookLevel (line 788) | type BybitBookLevel = {
type BybitBookSnapshotDataMessage (line 794) | type BybitBookSnapshotDataMessage = BybitDataMessage & {
type BybitBookSnapshotUpdateMessage (line 801) | type BybitBookSnapshotUpdateMessage = BybitDataMessage & {
type BybitInstrumentUpdate (line 812) | type BybitInstrumentUpdate = {
type BybitInstrumentDataMessage (line 861) | type BybitInstrumentDataMessage =
type BybitLiquidationMessage (line 872) | type BybitLiquidationMessage = BybitDataMessage & {
type BybitLiquidationNativeMessage (line 884) | type BybitLiquidationNativeMessage = BybitDataMessage & {
FILE: src/mappers/bybitspot.ts
class BybitSpotTradesMapper (line 5) | class BybitSpotTradesMapper implements Mapper<'bybit-spot', Trade> {
method constructor (line 6) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 7) | canHandle(message: BybitSpotTradeMessage) {
method getFilters (line 11) | getFilters(symbols?: string[]) {
method map (line 22) | *map(message: BybitSpotTradeMessage, localTimestamp: Date): IterableIt...
class BybitSpotBookChangeMapper (line 38) | class BybitSpotBookChangeMapper implements Mapper<'bybit-spot', BookChan...
method constructor (line 39) | constructor(protected readonly _exchange: Exchange) {}
method canHandle (line 41) | canHandle(message: BybitSpotDepthMessage) {
method getFilters (line 45) | getFilters(symbols?: string[]) {
method map (line 55) | *map(message: BybitSpotDepthMessage, localTimestamp: Date) {
method _mapBookLevel (line 68) | private _mapBookLevel(level: [string, string]) {
class BybitSpotBookTickerMapper (line 73) | class BybitSpotBookTickerMapper implements Mapper<'bybit-spot', BookTick...
method constructor (line 74) | constructor(protected readonly _exchange: Exchange) {}
method canHandle (line 76) | canHandle(message: BybitSpotBookTickerMessage) {
method getFilters (line 80) | getFilters(symbols?: string[]) {
method map (line 90) | *map(message: BybitSpotBookTickerMessage, localTimestamp: Date) {
type BybitSpotBookTickerMessage (line 107) | type BybitSpotBookTickerMessage = {
type BybitSpotTradeMessage (line 113) | type BybitSpotTradeMessage = {
type BybitSpotDepthMessage (line 119) | type BybitSpotDepthMessage = {
FILE: src/mappers/coinbase.ts
method canHandle (line 8) | canHandle(message: CoinbaseTrade | CoinbaseLevel2Snapshot | CoinbaseLeve...
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(message: CoinbaseTrade, localTimestamp: Date): IterableIterator<Tra...
class CoinbaseBookChangMapper (line 66) | class CoinbaseBookChangMapper implements Mapper<'coinbase', BookChange> {
method canHandle (line 69) | canHandle(message: CoinbaseTrade | CoinbaseLevel2Snapshot | CoinbaseLe...
method getFilters (line 73) | getFilters(symbols?: string[]) {
method map (line 88) | *map(message: CoinbaseLevel2Update | CoinbaseLevel2Snapshot, localTime...
method canHandle (line 142) | canHandle(message: CoinbaseTicker) {
method getFilters (line 146) | getFilters(symbols?: string[]) {
method map (line 157) | *map(message: CoinbaseTicker, localTimestamp: Date): IterableIterator<Bo...
type CoinbaseTrade (line 180) | type CoinbaseTrade = {
type CoinbaseSnapshotBookLevel (line 190) | type CoinbaseSnapshotBookLevel = [string, string]
type CoinbaseLevel2Snapshot (line 192) | type CoinbaseLevel2Snapshot = {
type CoinbaseUpdateBookLevel (line 200) | type CoinbaseUpdateBookLevel = ['buy' | 'sell', string, string]
type CoinbaseLevel2Update (line 202) | type CoinbaseLevel2Update = {
type CoinbaseTicker (line 209) | type CoinbaseTicker =
FILE: src/mappers/coinbaseinternational.ts
method canHandle (line 6) | canHandle(message: CoinbaseInternationalTradeMessage) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 21) | *map(message: CoinbaseInternationalTradeMessage, localTimestamp: Date): ...
class CoinbaseInternationalBookChangMapper (line 61) | class CoinbaseInternationalBookChangMapper implements Mapper<'coinbase-i...
method canHandle (line 62) | canHandle(message: CoinbaseInternationalLevel2Snapshot | CoinbaseInter...
method getFilters (line 66) | getFilters(symbols?: string[]) {
method map (line 77) | *map(
method canHandle (line 120) | canHandle(message: CoinbaseInternationalLevel1Message) {
method getFilters (line 124) | getFilters(symbols?: string[]) {
method map (line 135) | *map(message: CoinbaseInternationalLevel1Message, localTimestamp: Date):...
class CoinbaseInternationalDerivativeTickerMapper (line 156) | class CoinbaseInternationalDerivativeTickerMapper implements Mapper<'coi...
method canHandle (line 159) | canHandle(message: CoinbaseInternationalTradeMessage | CoinbaseInterna...
method getFilters (line 180) | getFilters(symbols?: string[]) {
method map (line 199) | *map(
type CoinbaseInternationalTradeMessage (line 247) | type CoinbaseInternationalTradeMessage = {
type CoinbaseInternationalSnapshotBookLevel (line 259) | type CoinbaseInternationalSnapshotBookLevel = [string, string]
type CoinbaseInternationalLevel2Snapshot (line 261) | type CoinbaseInternationalLevel2Snapshot = {
type CoinbaseInternationalUpdateBookLevel (line 271) | type CoinbaseInternationalUpdateBookLevel = ['BUY' | 'SELL', string, str...
type CoinbaseInternationalLevel2Update (line 273) | type CoinbaseInternationalLevel2Update = {
type CoinbaseInternationalLevel1Message (line 282) | type CoinbaseInternationalLevel1Message =
type CoinbaseInternationalRiskMessage (line 306) | type CoinbaseInternationalRiskMessage = {
type CoinbaseInternationalFundingMessage (line 320) | type CoinbaseInternationalFundingMessage = {
FILE: src/mappers/coinflex.ts
method canHandle (line 8) | canHandle(message: CoinflexTrades) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(coinflexTrades: CoinflexTrades, localTimestamp: Date): IterableIter...
method canHandle (line 48) | canHandle(message: CoinflexBookDepthMessage) {
method getFilters (line 52) | getFilters(symbols?: string[]) {
method map (line 63) | *map(depthMessage: CoinflexBookDepthMessage, localTimestamp: Date): Iter...
class CoinflexDerivativeTickerMapper (line 79) | class CoinflexDerivativeTickerMapper implements Mapper<'coinflex', Deriv...
method canHandle (line 82) | canHandle(message: CoinflexTickerMessage) {
method getFilters (line 86) | getFilters(symbols?: string[]) {
method map (line 97) | *map(message: CoinflexTickerMessage, localTimestamp: Date): IterableIt...
type CoinflexTrades (line 126) | type CoinflexTrades = {
type CoinflexBookLevel (line 140) | type CoinflexBookLevel = [number | string, number | string]
type CoinflexBookDepthMessage (line 142) | type CoinflexBookDepthMessage = {
type CoinflexTickerMessage (line 155) | type CoinflexTickerMessage = {
FILE: src/mappers/cryptocom.ts
class CryptoComTradesMapper (line 5) | class CryptoComTradesMapper implements Mapper<'crypto-com', Trade> {
method constructor (line 6) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 7) | canHandle(message: CryptoComTradeMessage) {
method getFilters (line 11) | getFilters(symbols?: string[]) {
method map (line 22) | *map(message: CryptoComTradeMessage, localTimestamp: Date): IterableIt...
class CryptoComBookChangeMapper (line 43) | class CryptoComBookChangeMapper implements Mapper<'crypto-com', BookChan...
method constructor (line 44) | constructor(protected readonly _exchange: Exchange) {}
method canHandle (line 46) | canHandle(message: CryptoComBookMessage) {
method getFilters (line 50) | getFilters(symbols?: string[]) {
method map (line 60) | *map(message: CryptoComBookMessage, localTimestamp: Date) {
method _mapBookLevel (line 80) | private _mapBookLevel(level: [number | string, number | string]) {
class CryptoComBookTickerMapper (line 85) | class CryptoComBookTickerMapper implements Mapper<'crypto-com', BookTick...
method constructor (line 86) | constructor(protected readonly _exchange: Exchange) {}
method canHandle (line 88) | canHandle(message: CryptoComTickerMessage) {
method getFilters (line 92) | getFilters(symbols?: string[]) {
method map (line 102) | *map(message: CryptoComTickerMessage, localTimestamp: Date) {
class CryptoComDerivativeTickerMapper (line 122) | class CryptoComDerivativeTickerMapper implements Mapper<'crypto-com', De...
method constructor (line 126) | constructor(protected readonly exchange: Exchange) {}
method canHandle (line 128) | canHandle(message: CryptoComDerivativesTickerMessage | CryptoComIndexM...
method getFilters (line 154) | getFilters(symbols?: string[]) {
method map (line 187) | *map(
type CryptoComTradeMessage (line 246) | type CryptoComTradeMessage =
type CryptoComBookMessage (line 277) | type CryptoComBookMessage =
type CryptoComTickerMessage (line 335) | type CryptoComTickerMessage =
type CryptoComDerivativesTickerMessage (line 364) | type CryptoComDerivativesTickerMessage =
type CryptoComIndexMessage (line 448) | type CryptoComIndexMessage = {
type CryptoComMarkPriceMessage (line 460) | type CryptoComMarkPriceMessage = {
type CryptoComFundingMessage (line 472) | type CryptoComFundingMessage = {
type CryptoComEstFundingMessage (line 484) | type CryptoComEstFundingMessage = {
FILE: src/mappers/cryptofacilities.ts
method canHandle (line 8) | canHandle(message: CryptofacilitiesTrade | CryptofacilitiesTicker | Cryp...
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(trade: CryptofacilitiesTrade, localTimestamp: Date): IterableIterat...
method canHandle (line 43) | canHandle(message: CryptofacilitiesTrade | CryptofacilitiesTicker | Cryp...
method getFilters (line 47) | getFilters(symbols?: string[]) {
method map (line 62) | *map(message: CryptofacilitiesBookSnapshot | CryptofacilitiesBookUpdate,...
class CryptofacilitiesDerivativeTickerMapper (line 97) | class CryptofacilitiesDerivativeTickerMapper implements Mapper<'cryptofa...
method constructor (line 98) | constructor(private readonly _useRelativeFundingRate: boolean) {}
method canHandle (line 100) | canHandle(message: CryptofacilitiesTrade | CryptofacilitiesTicker | Cr...
method getFilters (line 104) | getFilters(symbols?: string[]) {
method map (line 115) | *map(ticker: CryptofacilitiesTicker, localTimestamp: Date): IterableIt...
method canHandle (line 145) | canHandle(message: CryptofacilitiesTrade | CryptofacilitiesTicker | Cryp...
method getFilters (line 149) | getFilters(symbols?: string[]) {
method map (line 160) | *map(liquidationTrade: CryptofacilitiesTrade, localTimestamp: Date): Ite...
method canHandle (line 176) | canHandle(message: CryptofacilitiesTicker) {
method getFilters (line 180) | getFilters(symbols?: string[]) {
method map (line 191) | *map(cryptofacilitiesTicker: CryptofacilitiesTicker, localTimestamp: Dat...
type CryptofacilitiesTrade (line 210) | type CryptofacilitiesTrade = {
type CryptofacilitiesTicker (line 222) | type CryptofacilitiesTicker =
type CryptofacilitiesBookLevel (line 273) | type CryptofacilitiesBookLevel = {
type CryptofacilitiesBookSnapshot (line 278) | type CryptofacilitiesBookSnapshot = {
type CryptofacilitiesBookUpdate (line 287) | type CryptofacilitiesBookUpdate = {
FILE: src/mappers/delta.ts
class DeltaTradesMapper (line 5) | class DeltaTradesMapper implements Mapper<'delta', Trade> {
method constructor (line 6) | constructor(private _useV2Channels: boolean) {}
method canHandle (line 8) | canHandle(message: DeltaTrade) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(message: DeltaTrade, localTimestamp: Date): IterableIterator<Trad...
class DeltaBookChangeMapper (line 52) | class DeltaBookChangeMapper implements Mapper<'delta', BookChange> {
method constructor (line 53) | constructor(private readonly _useL2UpdatesChannel: boolean) {}
method canHandle (line 55) | canHandle(message: DeltaL2OrderBook | DeltaL2UpdateMessage) {
method getFilters (line 62) | getFilters(symbols?: string[]) {
method map (line 82) | *map(message: DeltaL2OrderBook | DeltaL2UpdateMessage, localTimestamp:...
class DeltaDerivativeTickerMapper (line 112) | class DeltaDerivativeTickerMapper implements Mapper<'delta', DerivativeT...
method constructor (line 113) | constructor(private _useV2Channels: boolean) {}
method canHandle (line 117) | canHandle(message: DeltaTrade | DeltaMarkPrice | DeltaFundingRate) {
method getFilters (line 125) | getFilters(symbols?: string[]) {
method map (line 144) | *map(message: DeltaTrade | DeltaMarkPrice | DeltaFundingRate, localTim...
class DeltaBookTickerMapper (line 176) | class DeltaBookTickerMapper implements Mapper<'delta', BookTicker> {
method canHandle (line 177) | canHandle(message: DeltaL1Message) {
method getFilters (line 181) | getFilters(symbols?: string[]) {
method map (line 192) | *map(message: DeltaL1Message, localTimestamp: Date) {
type DeltaTrade (line 209) | type DeltaTrade = {
type DeltaBookLevel (line 218) | type DeltaBookLevel = {
type DeltaL2OrderBook (line 223) | type DeltaL2OrderBook = {
type DeltaMarkPrice (line 231) | type DeltaMarkPrice = {
type DeltaFundingRate (line 238) | type DeltaFundingRate = {
type DeltaL2Level (line 247) | type DeltaL2Level = [string, string]
type DeltaL2UpdateMessage (line 248) | type DeltaL2UpdateMessage =
type DeltaL1Message (line 270) | type DeltaL1Message = {
FILE: src/mappers/deribit.ts
function deribitCasing (line 7) | function deribitCasing(symbols?: string[]) {
method canHandle (line 28) | canHandle(message: any) {
method getFilters (line 37) | getFilters(symbols?: string[]) {
method map (line 48) | *map(message: DeribitTradesMessage, localTimestamp: Date): IterableItera...
method canHandle (line 73) | canHandle(message: any) {
method getFilters (line 82) | getFilters(symbols?: string[]) {
method map (line 93) | *map(message: DeribitBookMessage, localTimestamp: Date): IterableIterato...
class DeribitDerivativeTickerMapper (line 114) | class DeribitDerivativeTickerMapper implements Mapper<'deribit', Derivat...
method canHandle (line 117) | canHandle(message: any) {
method getFilters (line 126) | getFilters(symbols?: string[]) {
method map (line 137) | *map(message: DeribitTickerMessage, localTimestamp: Date): IterableIte...
class DeribitOptionSummaryMapper (line 154) | class DeribitOptionSummaryMapper implements Mapper<'deribit', OptionSumm...
method getFilters (line 155) | getFilters(symbols?: string[]) {
method canHandle (line 166) | canHandle(message: any) {
method map (line 178) | *map(message: DeribitOptionTickerMessage, localTimestamp: Date) {
method canHandle (line 235) | canHandle(message: any) {
method getFilters (line 244) | getFilters(symbols?: string[]) {
method map (line 255) | *map(message: DeribitTradesMessage, localTimestamp: Date): IterableItera...
method canHandle (line 283) | canHandle(message: any) {
method getFilters (line 292) | getFilters(symbols?: string[]) {
method map (line 303) | *map(message: DeribitTickerMessage, localTimestamp: Date): IterableItera...
type DeribitMessage (line 324) | type DeribitMessage = {
type DeribitTradesMessage (line 330) | type DeribitTradesMessage = DeribitMessage & {
type DeribitBookLevel (line 345) | type DeribitBookLevel = ['new' | 'change' | 'delete', number, number]
type DeribitBookMessage (line 347) | type DeribitBookMessage = DeribitMessage & {
type DeribitTickerMessage (line 360) | type DeribitTickerMessage = DeribitMessage & {
type DeribitOptionTickerMessage (line 379) | type DeribitOptionTickerMessage = DeribitTickerMessage & {
FILE: src/mappers/dydx.ts
class DydxTradesMapper (line 5) | class DydxTradesMapper implements Mapper<'dydx', Trade> {
method canHandle (line 6) | canHandle(message: DyDxTrade) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 21) | *map(message: DyDxTrade, localTimestamp: Date): IterableIterator<Trade> {
class DydxBookChangeMapper (line 38) | class DydxBookChangeMapper implements Mapper<'dydx', BookChange> {
method canHandle (line 42) | canHandle(message: DyDxOrderbookSnapshot | DyDxOrderBookUpdate) {
method getFilters (line 46) | getFilters(symbols?: string[]) {
method map (line 57) | *map(message: DyDxOrderbookSnapshot | DyDxOrderBookUpdate, localTimest...
class DydxDerivativeTickerMapper (line 151) | class DydxDerivativeTickerMapper implements Mapper<'dydx', DerivativeTic...
method canHandle (line 154) | canHandle(message: DydxMarketsSnapshot | DyDxMarketsUpdate | DyDxTrade) {
method getFilters (line 158) | getFilters(symbols?: string[]) {
method map (line 173) | *map(message: DydxMarketsSnapshot | DyDxMarketsUpdate | DyDxTrade, loc...
type DyDxTrade (line 215) | type DyDxTrade = {
type DyDxOrderbookSnapshot (line 226) | type DyDxOrderbookSnapshot = {
type DyDxOrderBookUpdate (line 238) | type DyDxOrderBookUpdate = {
type DydxMarketsSnapshot (line 251) | type DydxMarketsSnapshot = {
type DyDxMarketsUpdate (line 288) | type DyDxMarketsUpdate = {
FILE: src/mappers/dydxv4.ts
class DydxV4TradesMapper (line 5) | class DydxV4TradesMapper implements Mapper<'dydx-v4', Trade> {
method canHandle (line 6) | canHandle(message: DyDxTrade) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 21) | *map(message: DyDxTrade, localTimestamp: Date): IterableIterator<Trade> {
function mapSnapshotPriceLevel (line 38) | function mapSnapshotPriceLevel(level: { price: string; size: string }) {
function mapUpdatePriceLevel (line 45) | function mapUpdatePriceLevel(level: [string, string]) {
class DydxV4BookChangeMapper (line 51) | class DydxV4BookChangeMapper implements Mapper<'dydx-v4', BookChange> {
method canHandle (line 52) | canHandle(message: DyDxOrderbookSnapshot | DyDxOrderBookUpdate) {
method getFilters (line 56) | getFilters(symbols?: string[]) {
method map (line 67) | *map(message: DyDxOrderbookSnapshot | DyDxOrderBookUpdate, localTimest...
class DydxV4DerivativeTickerMapper (line 102) | class DydxV4DerivativeTickerMapper implements Mapper<'dydx-v4', Derivati...
method canHandle (line 105) | canHandle(message: DydxMarketsSnapshot | DyDxMarketsUpdate | DyDxTrade) {
method getFilters (line 109) | getFilters(symbols?: string[]) {
method map (line 124) | *map(message: DydxMarketsSnapshot | DyDxMarketsUpdate | DyDxTrade, loc...
class DydxV4LiquidationsMapper (line 174) | class DydxV4LiquidationsMapper implements Mapper<'dydx-v4', Liquidation> {
method canHandle (line 175) | canHandle(message: DyDxTrade) {
method getFilters (line 179) | getFilters(symbols?: string[]) {
method map (line 190) | *map(message: DyDxTrade, localTimestamp: Date): IterableIterator<Liqui...
type DyDxTrade (line 209) | type DyDxTrade = {
type DyDxOrderbookSnapshot (line 230) | type DyDxOrderbookSnapshot = {
type DyDxOrderBookUpdate (line 242) | type DyDxOrderBookUpdate = {
type DydxMarketsSnapshot (line 252) | type DydxMarketsSnapshot = {
type DydxMarketsSnapshotContent (line 263) | type DydxMarketsSnapshotContent = {
type DyDxMarketsUpdate (line 287) | type DyDxMarketsUpdate =
type OraclePriceInfo (line 315) | type OraclePriceInfo = { oraclePrice: string; effectiveAt: string; effec...
type DydxMarketTradeUpdate (line 317) | type DydxMarketTradeUpdate = {
FILE: src/mappers/ftx.ts
class FTXTradesMapper (line 7) | class FTXTradesMapper implements Mapper<'ftx' | 'ftx-us', Trade> {
method constructor (line 8) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 10) | canHandle(message: FtxTrades | FtxOrderBook) {
method getFilters (line 18) | getFilters(symbols?: string[]) {
method map (line 29) | *map(ftxTrades: FtxTrades, localTimestamp: Date): IterableIterator<Tra...
class FTXBookChangeMapper (line 56) | class FTXBookChangeMapper implements Mapper<'ftx' | 'ftx-us', BookChange> {
method constructor (line 57) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 59) | canHandle(message: FtxTrades | FtxOrderBook) {
method getFilters (line 67) | getFilters(symbols?: string[]) {
method map (line 78) | *map(ftxOrderBook: FtxOrderBook, localTimestamp: Date): IterableIterat...
class FTXDerivativeTickerMapper (line 100) | class FTXDerivativeTickerMapper implements Mapper<'ftx', DerivativeTicke...
method constructor (line 103) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 105) | canHandle(message: FTXInstrument) {
method getFilters (line 113) | getFilters(symbols?: string[]) {
method map (line 124) | *map(message: FTXInstrument, localTimestamp: Date): IterableIterator<D...
class FTXLiquidationsMapper (line 159) | class FTXLiquidationsMapper implements Mapper<'ftx', Liquidation> {
method canHandle (line 160) | canHandle(message: FtxTrades | FtxOrderBook) {
method getFilters (line 168) | getFilters(symbols?: string[]) {
method map (line 179) | *map(ftxTrades: FtxTrades, localTimestamp: Date): IterableIterator<Liq...
class FTXBookTickerMapper (line 201) | class FTXBookTickerMapper implements Mapper<'ftx' | 'ftx-us', BookTicker> {
method constructor (line 202) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 204) | canHandle(message: FTXTicker) {
method getFilters (line 212) | getFilters(symbols?: string[]) {
method map (line 223) | *map(ftxTicker: FTXTicker, localTimestamp: Date): IterableIterator<Boo...
type FtxTrades (line 245) | type FtxTrades = {
type FtxBookLevel (line 259) | type FtxBookLevel = [number, number]
type FtxOrderBook (line 261) | type FtxOrderBook = {
type FTXInstrument (line 268) | type FTXInstrument = {
type FTXTicker (line 286) | type FTXTicker = {
FILE: src/mappers/gateio.ts
class GateIOV4OrderBookV2ChangeMapper (line 6) | class GateIOV4OrderBookV2ChangeMapper implements Mapper<'gate-io', BookC...
method constructor (line 7) | constructor(protected readonly exchange: Exchange) {}
method canHandle (line 9) | canHandle(message: GateV4OrderBookV2Message) {
method getFilters (line 13) | getFilters(symbols?: string[]) {
method map (line 24) | *map(message: GateV4OrderBookV2Message, localTimestamp: Date) {
method mapBookLevel (line 44) | protected mapBookLevel(level: [string, string]) {
method extractSymbolFromStream (line 50) | private extractSymbolFromStream(streamName: string): string {
class GateIOV4BookChangeMapper (line 58) | class GateIOV4BookChangeMapper implements Mapper<'gate-io', BookChange> {
method constructor (line 63) | constructor(protected readonly exchange: Exchange, protected readonly ...
method canHandle (line 65) | canHandle(message: GateV4OrderBookUpdate | Gatev4OrderBookSnapshot) {
method getFilters (line 76) | getFilters(symbols?: string[]) {
method map (line 87) | *map(message: GateV4OrderBookUpdate | Gatev4OrderBookSnapshot, localTi...
method mapBookDepthUpdate (line 164) | protected mapBookDepthUpdate(depthUpdateData: DepthData, localTimestam...
method mapBookLevel (line 209) | protected mapBookLevel(level: [string, string]) {
class GateIOV4BookTickerMapper (line 216) | class GateIOV4BookTickerMapper implements Mapper<'gate-io', BookTicker> {
method constructor (line 217) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 219) | canHandle(message: GateV4BookTicker) {
method getFilters (line 230) | getFilters(symbols?: string[]) {
method map (line 241) | *map(bookTickerResponse: GateV4BookTicker, localTimestamp: Date) {
class GateIOV4TradesMapper (line 260) | class GateIOV4TradesMapper implements Mapper<'gate-io', Trade> {
method constructor (line 261) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 263) | canHandle(message: GateV4Trade) {
method getFilters (line 274) | getFilters(symbols?: string[]) {
method map (line 285) | *map(tradesMessage: GateV4Trade, localTimestamp: Date): IterableIterat...
class GateIOTradesMapper (line 302) | class GateIOTradesMapper implements Mapper<'gate-io', Trade> {
method constructor (line 305) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 307) | canHandle(message: any) {
method getFilters (line 311) | getFilters(symbols?: string[]) {
method map (line 322) | *map(tradesMessage: GateIOTrades, localTimestamp: Date): IterableItera...
class GateIOBookChangeMapper (line 361) | class GateIOBookChangeMapper implements Mapper<'gate-io', BookChange> {
method constructor (line 362) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 364) | canHandle(message: any) {
method getFilters (line 368) | getFilters(symbols?: string[]) {
method map (line 379) | *map(depthMessage: GateIODepth, localTimestamp: Date): IterableIterato...
type GateIOTrade (line 400) | type GateIOTrade = {
type GateIOTrades (line 408) | type GateIOTrades = {
type GateIODepthLevel (line 412) | type GateIODepthLevel = [string, string]
type GateIODepth (line 414) | type GateIODepth = {
type GateV4Trade (line 428) | type GateV4Trade = {
type GateV4BookTicker (line 444) | type GateV4BookTicker = {
type Gatev4OrderBookSnapshot (line 452) | type Gatev4OrderBookSnapshot = {
type GateV4OrderBookUpdate (line 466) | type GateV4OrderBookUpdate = {
type LocalDepthInfo (line 484) | type LocalDepthInfo = {
type DepthData (line 491) | type DepthData = {
type GateV4OrderBookV2Message (line 501) | type GateV4OrderBookV2Message = {
FILE: src/mappers/gateiofutures.ts
class GateIOFuturesTradesMapper (line 7) | class GateIOFuturesTradesMapper implements Mapper<'gate-io-futures', Tra...
method constructor (line 8) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 10) | canHandle(message: any) {
method getFilters (line 14) | getFilters(symbols?: string[]) {
method map (line 25) | *map(tradesMessage: GateIOFuturesTrades, localTimestamp: Date): Iterab...
class GateIOFuturesBookChangeMapper (line 52) | class GateIOFuturesBookChangeMapper implements Mapper<'gate-io-futures',...
method constructor (line 53) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 55) | canHandle(message: GateIOFuturesOrderBookSnapshot | GateIOFuturesOrder...
method getFilters (line 59) | getFilters(symbols?: string[]) {
method map (line 70) | *map(depthMessage: GateIOFuturesOrderBookSnapshot | GateIOFuturesOrder...
class GateIOFuturesDerivativeTickerMapper (line 111) | class GateIOFuturesDerivativeTickerMapper implements Mapper<'gate-io-fut...
method canHandle (line 114) | canHandle(message: GateIOFuturesTicker) {
method getFilters (line 118) | getFilters(symbols?: string[]) {
method map (line 129) | *map(message: GateIOFuturesTicker, localTimestamp: Date): IterableIter...
class GateIOFuturesBookTickerMapper (line 158) | class GateIOFuturesBookTickerMapper implements Mapper<'gate-io-futures',...
method constructor (line 159) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 161) | canHandle(message: any) {
method getFilters (line 165) | getFilters(symbols?: string[]) {
method map (line 176) | *map(gateIoFuturesBookTickerMessage: GateIOFuturesBookTicker, localTim...
type GateIOFuturesTrade (line 200) | type GateIOFuturesTrade = {
type GateIOFuturesTrades (line 209) | type GateIOFuturesTrades = {
type GateIOFuturesSnapshotLevel (line 217) | type GateIOFuturesSnapshotLevel = { p: string; s: number | string }
type GateIOFuturesOrderBookSnapshot (line 219) | type GateIOFuturesOrderBookSnapshot = {
type GateIOFuturesOrderBookUpdate (line 232) | type GateIOFuturesOrderBookUpdate = {
type GateIOFuturesTicker (line 244) | type GateIOFuturesTicker = {
type GateIOFuturesBookTicker (line 273) | type GateIOFuturesBookTicker = {
FILE: src/mappers/gemini.ts
method canHandle (line 8) | canHandle(message: GeminiL2Updates | GeminiTrade) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(geminiTrade: GeminiTrade, localTimestamp: Date): IterableIterator<T...
method canHandle (line 46) | canHandle(message: GeminiL2Updates | GeminiTrade) {
method getFilters (line 50) | getFilters(symbols?: string[]) {
method map (line 61) | *map(geminiL2Updates: GeminiL2Updates, localTimestamp: Date): IterableIt...
type GeminiBookLevel (line 76) | type GeminiBookLevel = ['buy' | 'sell', string, string]
type GeminiL2Updates (line 78) | type GeminiL2Updates = {
type GeminiTrade (line 85) | type GeminiTrade = {
FILE: src/mappers/hitbtc.ts
method canHandle (line 8) | canHandle(message: HitBtcTradesMessage) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(message: HitBtcTradesMessage, localTimestamp: Date): IterableIterat...
method canHandle (line 47) | canHandle(message: HitBtcBookMessage) {
method getFilters (line 55) | getFilters(symbols?: string[]) {
method map (line 70) | *map(message: HitBtcBookMessage, localTimestamp: Date): IterableIterator...
type HitBtcMessage (line 85) | type HitBtcMessage = {
type HitBtcTradesMessage (line 89) | type HitBtcTradesMessage = HitBtcMessage & {
type HitBtcBookLevel (line 103) | type HitBtcBookLevel = {
type HitBtcBookMessage (line 108) | type HitBtcBookMessage = HitBtcMessage & {
FILE: src/mappers/huobi.ts
class HuobiTradesMapper (line 8) | class HuobiTradesMapper
method constructor (line 11) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 12) | canHandle(message: HuobiDataMessage) {
method getFilters (line 19) | getFilters(symbols?: string[]) {
method map (line 30) | *map(message: HuobiTradeDataMessage, localTimestamp: Date): IterableIt...
class HuobiBookChangeMapper (line 49) | class HuobiBookChangeMapper
method constructor (line 52) | constructor(protected readonly _exchange: Exchange) {}
method canHandle (line 54) | canHandle(message: HuobiDataMessage) {
method getFilters (line 62) | getFilters(symbols?: string[]) {
method map (line 73) | *map(message: HuobiDepthDataMessage, localTimestamp: Date) {
method _mapBookLevel (line 95) | private _mapBookLevel(level: HuobiBookLevel) {
function isSnapshot (line 100) | function isSnapshot(message: HuobiMBPDataMessage | HuobiMBPSnapshot): me...
class HuobiMBPBookChangeMapper (line 104) | class HuobiMBPBookChangeMapper implements Mapper<'huobi', BookChange> {
method constructor (line 109) | constructor(protected readonly _exchange: Exchange) {}
method canHandle (line 111) | canHandle(message: any) {
method getFilters (line 120) | getFilters(symbols?: string[]) {
method map (line 131) | *map(message: HuobiMBPDataMessage | HuobiMBPSnapshot, localTimestamp: ...
method _mapMBPUpdate (line 205) | private _mapMBPUpdate(message: HuobiMBPDataMessage, symbol: string, lo...
method _mapBookLevel (line 225) | private _mapBookLevel(level: HuobiBookLevel) {
function normalizeSymbols (line 230) | function normalizeSymbols(symbols?: string[]) {
class HuobiDerivativeTickerMapper (line 244) | class HuobiDerivativeTickerMapper implements Mapper<'huobi-dm' | 'huobi-...
method constructor (line 247) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 249) | canHandle(message: any) {
method getFilters (line 261) | getFilters(symbols?: string[]) {
method map (line 285) | *map(
class HuobiLiquidationsMapper (line 325) | class HuobiLiquidationsMapper implements Mapper<'huobi-dm' | 'huobi-dm-s...
method constructor (line 329) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 331) | canHandle(message: HuobiLiquidationOrder | HuobiContractInfo) {
method getFilters (line 343) | getFilters(symbols?: string[]) {
method _updateContractCodeToSymbolMap (line 379) | private _updateContractCodeToSymbolMap(message: HuobiContractInfo) {
method map (line 385) | *map(message: HuobiLiquidationOrder, localTimestamp: Date): IterableIt...
class HuobiOptionsSummaryMapper (line 413) | class HuobiOptionsSummaryMapper implements Mapper<'huobi-dm-options', Op...
method canHandle (line 417) | canHandle(message: HuobiOpenInterestDataMessage | HuobiOptionsIndexMes...
method getFilters (line 425) | getFilters(symbols?: string[]) {
method map (line 450) | *map(
class HuobiBookTickerMapper (line 523) | class HuobiBookTickerMapper implements Mapper<'huobi' | 'huobi-dm' | 'hu...
method constructor (line 524) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 526) | canHandle(message: HuobiDataMessage) {
method getFilters (line 533) | getFilters(symbols?: string[]) {
method map (line 544) | *map(message: HuobiBBOMessage, localTimestamp: Date): IterableIterator...
type HuobiDataMessage (line 582) | type HuobiDataMessage = {
type HuobiTradeDataMessage (line 586) | type HuobiTradeDataMessage = HuobiDataMessage & {
type HuobiBookLevel (line 599) | type HuobiBookLevel = [number, number]
type HuobiDepthDataMessage (line 601) | type HuobiDepthDataMessage = HuobiDataMessage &
type HuobiBasisDataMessage (line 621) | type HuobiBasisDataMessage = HuobiDataMessage & {
type HuobiFundingRateNotification (line 629) | type HuobiFundingRateNotification = {
type HuobiOpenInterestDataMessage (line 641) | type HuobiOpenInterestDataMessage = HuobiDataMessage & {
type HuobiMBPDataMessage (line 648) | type HuobiMBPDataMessage = HuobiDataMessage & {
type HuobiMBPSnapshot (line 658) | type HuobiMBPSnapshot = {
type MBPInfo (line 668) | type MBPInfo = {
type HuobiLiquidationOrder (line 673) | type HuobiLiquidationOrder = {
type HuobiContractInfo (line 688) | type HuobiContractInfo = {
type HuobiOptionsOpenInterestMessage (line 699) | type HuobiOptionsOpenInterestMessage = {
type HuobiOptionsIndexMessage (line 718) | type HuobiOptionsIndexMessage = {
type HuobiOptionsMarketIndexMessage (line 725) | type HuobiOptionsMarketIndexMessage = {
type HuobiBBOMessage (line 750) | type HuobiBBOMessage =
FILE: src/mappers/hyperliquid.ts
constant KILO_SYMBOLS (line 4) | const KILO_SYMBOLS = ['kPEPE', 'kSHIB', 'kBONK', 'kFLOKI', 'kLUNC', 'kDO...
function getApiSymbolId (line 6) | function getApiSymbolId(symbol: string) {
function getSymbols (line 19) | function getSymbols(symbols?: string[]) {
class HyperliquidTradesMapper (line 25) | class HyperliquidTradesMapper implements Mapper<'hyperliquid', Trade> {
method canHandle (line 28) | canHandle(message: HyperliquidTradeMessage) {
method getFilters (line 32) | getFilters(symbols?: string[]) {
method map (line 43) | *map(message: HyperliquidTradeMessage, localTimestamp: Date): Iterable...
function mapHyperliquidLevel (line 64) | function mapHyperliquidLevel(level: HyperliquidWsLevel) {
class HyperliquidBookChangeMapper (line 70) | class HyperliquidBookChangeMapper implements Mapper<'hyperliquid', BookC...
method canHandle (line 71) | canHandle(message: HyperliquidWsBookMessage) {
method getFilters (line 75) | getFilters(symbols?: string[]) {
method map (line 86) | *map(message: HyperliquidWsBookMessage, localTimestamp: Date): Iterabl...
class HyperliquidBookTickerMapper (line 100) | class HyperliquidBookTickerMapper implements Mapper<'hyperliquid', BookT...
method canHandle (line 101) | canHandle(message: HyperliquidBboMessage) {
method getFilters (line 105) | getFilters(symbols?: string[]) {
method map (line 116) | *map(message: HyperliquidBboMessage, localTimestamp: Date): IterableIt...
class HyperliquidDerivativeTickerMapper (line 137) | class HyperliquidDerivativeTickerMapper implements Mapper<'hyperliquid',...
method canHandle (line 140) | canHandle(message: HyperliquidContextMessage) {
method getFilters (line 144) | getFilters(symbols?: string[]) {
method map (line 155) | *map(message: HyperliquidContextMessage, localTimestamp: Date): Iterab...
type HyperliquidTradeMessage (line 183) | type HyperliquidTradeMessage = {
type HyperliquidWsBookMessage (line 198) | type HyperliquidWsBookMessage = {
type HyperliquidWsLevel (line 207) | type HyperliquidWsLevel = {
type HyperliquidBboMessage (line 213) | type HyperliquidBboMessage = {
type HyperliquidContextMessage (line 222) | type HyperliquidContextMessage = {
FILE: src/mappers/index.ts
constant THREE_MINUTES_IN_MS (line 151) | const THREE_MINUTES_IN_MS = 3 * 60 * ONE_SEC_IN_MS
constant OKEX_V5_API_SWITCH_DATE (line 160) | const OKEX_V5_API_SWITCH_DATE = new Date('2021-12-23T00:00:00.000Z')
constant OKEX_V5_TBT_BOOK_TICKER_RELEASE_DATE (line 161) | const OKEX_V5_TBT_BOOK_TICKER_RELEASE_DATE = new Date('2022-05-06T00:00:...
constant POLONIEX_V2_API_SWITCH_DATE (line 170) | const POLONIEX_V2_API_SWITCH_DATE = new Date('2022-08-02T00:00:00.000Z')
constant BYBIT_V5_API_SWITCH_DATE (line 192) | const BYBIT_V5_API_SWITCH_DATE = new Date('2023-04-05T00:00:00.000Z')
constant BYBIT_V5_API_ALL_LIQUIDATION_SUPPORT_DATE (line 194) | const BYBIT_V5_API_ALL_LIQUIDATION_SUPPORT_DATE = new Date('2025-02-26T0...
constant OKCOIN_V5_API_SWITCH_DATE (line 204) | const OKCOIN_V5_API_SWITCH_DATE = new Date('2023-04-27T00:00:00.000Z')
constant GATE_IO_V4_API_SWITCH_DATE (line 209) | const GATE_IO_V4_API_SWITCH_DATE = new Date('2023-04-29T00:00:00.000Z')
constant GATE_IO_V4_ORDER_BOOK_V2_SWITCH_DATE (line 210) | const GATE_IO_V4_ORDER_BOOK_V2_SWITCH_DATE = new Date('2025-08-01T00:00:...
constant BINANCE_EUROPEAN_OPTIONS_V2_API_SWITCH_DATE (line 232) | const BINANCE_EUROPEAN_OPTIONS_V2_API_SWITCH_DATE = new Date('2025-12-17...
FILE: src/mappers/kraken.ts
method canHandle (line 8) | canHandle(message: KrakenTrades) {
method getFilters (line 17) | getFilters(symbols?: string[]) {
method map (line 28) | *map(message: KrakenTrades, localTimestamp: Date): IterableIterator<Trad...
method canHandle (line 68) | canHandle(message: KrakenBookSnapshot | KrakenBookUpdate) {
method getFilters (line 77) | getFilters(symbols?: string[]) {
method map (line 88) | *map(message: KrakenBookSnapshot | KrakenBookUpdate, localTimestamp: Dat...
method canHandle (line 126) | canHandle(message: KrakenSpread) {
method getFilters (line 135) | getFilters(symbols?: string[]) {
method map (line 146) | *map(message: KrakenSpread, localTimestamp: Date): IterableIterator<Book...
type KrakenTrades (line 174) | type KrakenTrades = [number, [string, string, string, 's' | 'b', string,...
type KrakenBookLevel (line 175) | type KrakenBookLevel = [string, string, string]
type KrakenBookSnapshot (line 176) | type KrakenBookSnapshot = [
type KrakenBookUpdate (line 186) | type KrakenBookUpdate =
type KrakenSpread (line 214) | type KrakenSpread = [
FILE: src/mappers/kucoin.ts
class KucoinTradesMapper (line 6) | class KucoinTradesMapper implements Mapper<'kucoin', Trade> {
method constructor (line 7) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 8) | canHandle(message: KucoinTradeMessage) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 23) | *map(message: KucoinTradeMessage, localTimestamp: Date): IterableItera...
class KucoinBookChangeMapper (line 44) | class KucoinBookChangeMapper implements Mapper<'kucoin', BookChange> {
method constructor (line 49) | constructor(protected readonly _exchange: Exchange, private readonly i...
method canHandle (line 51) | canHandle(message: KucoinLevel2SnapshotMessage | KucoinLevel2UpdateMes...
method getFilters (line 55) | getFilters(symbols?: string[]) {
method map (line 69) | *map(message: KucoinLevel2SnapshotMessage | KucoinLevel2UpdateMessage,...
method mapBookDepthUpdate (line 157) | protected mapBookDepthUpdate(l2UpdateMessage: KucoinLevel2UpdateMessag...
method mapBookLevel (line 210) | private mapBookLevel(level: [string, string, string?]) {
method nonZeroLevels (line 216) | private nonZeroLevels(level: BookPriceLevel) {
class KucoinBookTickerMapper (line 221) | class KucoinBookTickerMapper implements Mapper<'kucoin', BookTicker> {
method constructor (line 222) | constructor(protected readonly _exchange: Exchange) {}
method canHandle (line 224) | canHandle(message: KucoinTickerMessage) {
method getFilters (line 228) | getFilters(symbols?: string[]) {
method map (line 238) | *map(message: KucoinTickerMessage, localTimestamp: Date) {
type KucoinTickerMessage (line 257) | type KucoinTickerMessage = {
type KucoinTradeMessage (line 273) | type KucoinTradeMessage = {
type LocalDepthInfo (line 291) | type LocalDepthInfo = {
type KucoinLevel2SnapshotMessage (line 298) | type KucoinLevel2SnapshotMessage = {
type KucoinLevel2UpdateMessage (line 312) | type KucoinLevel2UpdateMessage =
FILE: src/mappers/kucoinfutures.ts
class KucoinFuturesTradesMapper (line 6) | class KucoinFuturesTradesMapper implements Mapper<'kucoin-futures', Trad...
method canHandle (line 7) | canHandle(message: KucoinFuturesTradeMessage) {
method getFilters (line 11) | getFilters(symbols?: string[]) {
method map (line 22) | *map(message: KucoinFuturesTradeMessage, localTimestamp: Date): Iterab...
class KucoinFuturesBookChangeMapper (line 41) | class KucoinFuturesBookChangeMapper implements Mapper<'kucoin-futures', ...
method constructor (line 46) | constructor(private readonly ignoreBookSnapshotOverlapError: boolean) {}
method canHandle (line 48) | canHandle(message: KucoinFuturesLevel2SnapshotMessage | KucoinFuturesL...
method getFilters (line 52) | getFilters(symbols?: string[]) {
method map (line 66) | *map(message: KucoinFuturesLevel2SnapshotMessage | KucoinFuturesLevel2...
method mapBookDepthUpdate (line 157) | protected mapBookDepthUpdate(l2UpdateMessage: KucoinFuturesLevel2Updat...
method mapBookLevel (line 218) | private mapBookLevel(level: [number, number]) {
method mapChange (line 222) | private mapChange(change: string) {
class KucoinFuturesBookTickerMapper (line 232) | class KucoinFuturesBookTickerMapper implements Mapper<'kucoin-futures', ...
method canHandle (line 233) | canHandle(message: KucoinFuturesTickerMessage) {
method getFilters (line 237) | getFilters(symbols?: string[]) {
method map (line 247) | *map(message: KucoinFuturesTickerMessage, localTimestamp: Date) {
class KucoinFuturesDerivativeTickerMapper (line 270) | class KucoinFuturesDerivativeTickerMapper implements Mapper<'kucoin-futu...
method canHandle (line 275) | canHandle(message: KucoinFuturesTickerMessage) {
method getFilters (line 284) | getFilters(symbols?: string[]) {
method map (line 302) | *map(message: KucoinFuturesInstrumentMessage | KucoinFuturesTradeMessa...
type KucoinFuturesTradeMessage (line 348) | type KucoinFuturesTradeMessage = {
type LocalDepthInfo (line 368) | type LocalDepthInfo = {
type KucoinFuturesLevel2SnapshotMessage (line 375) | type KucoinFuturesLevel2SnapshotMessage = {
type KucoinFuturesLevel2UpdateMessage (line 390) | type KucoinFuturesLevel2UpdateMessage = {
type KucoinFuturesTickerMessage (line 398) | type KucoinFuturesTickerMessage = {
type KucoinFuturesInstrumentMessage (line 414) | type KucoinFuturesInstrumentMessage =
FILE: src/mappers/mapper.ts
type Mapper (line 3) | type Mapper<T extends Exchange, U extends NormalizedData> = {
type MapperFactory (line 11) | type MapperFactory<T extends Exchange, U extends NormalizedData> = (exch...
type Writeable (line 13) | type Writeable<T> = { -readonly [P in keyof T]: T[P] }
class PendingTickerInfoHelper (line 17) | class PendingTickerInfoHelper {
method getPendingTickerInfo (line 20) | public getPendingTickerInfo(symbol: string, exchange: Exchange) {
method hasPendingTickerInfo (line 29) | public hasPendingTickerInfo(symbol: string) {
class PendingDerivativeTickerInfo (line 34) | class PendingDerivativeTickerInfo {
method constructor (line 38) | constructor(symbol: string, exchange: Exchange) {
method getCurrentFundingTimestamp (line 57) | public getCurrentFundingTimestamp() {
method updateOpenInterest (line 61) | public updateOpenInterest(openInterest: number | undefined | null) {
method updateMarkPrice (line 72) | public updateMarkPrice(markPrice: number | undefined | null) {
method updateFundingRate (line 83) | public updateFundingRate(fundingRate: number | undefined | null) {
method updatePredictedFundingRate (line 94) | public updatePredictedFundingRate(predictedFundingRate: number | undef...
method updateFundingTimestamp (line 105) | public updateFundingTimestamp(fundingTimestamp: Date | undefined | nul...
method updateIndexPrice (line 119) | public updateIndexPrice(indexPrice: number | undefined | null) {
method updateLastPrice (line 130) | public updateLastPrice(lastPrice: number | undefined | null) {
method updateTimestamp (line 141) | public updateTimestamp(timestamp: Date) {
method hasChanged (line 147) | public hasChanged() {
method getSnapshot (line 151) | public getSnapshot(localTimestamp: Date): DerivativeTicker {
FILE: src/mappers/okex.ts
class OkexV5TradesMapper (line 8) | class OkexV5TradesMapper implements Mapper<OKEX_EXCHANGES, Trade> {
method constructor (line 9) | constructor(private readonly _exchange: Exchange, private readonly _us...
method canHandle (line 11) | canHandle(message: any) {
method getFilters (line 18) | getFilters(symbols?: string[]) {
method map (line 38) | *map(okexTradesMessage: OkexV5TradeMessage | OkexV5TradesAllMessage, l...
class OkexV5BookChangeMapper (line 62) | class OkexV5BookChangeMapper implements Mapper<OKEX_EXCHANGES, BookChang...
method constructor (line 65) | constructor(private readonly _exchange: Exchange, usePublicBooksChanne...
method canHandle (line 69) | canHandle(message: any) {
method _getBooksChannelName (line 81) | private _getBooksChannelName(usePublicBooksChannel: boolean) {
method getFilters (line 102) | getFilters(symbols?: string[]) {
method map (line 113) | *map(okexDepthDataMessage: OkexV5BookMessage, localTimestamp: Date): I...
class OkexV5BookTickerMapper (line 139) | class OkexV5BookTickerMapper implements Mapper<OKEX_EXCHANGES, BookTicke...
method constructor (line 140) | constructor(private readonly _exchange: Exchange, private readonly _us...
method canHandle (line 142) | canHandle(message: any) {
method getFilters (line 154) | getFilters(symbols?: string[]) {
method map (line 173) | map(message: OkexV5TickerMessage | OkexBBOTbtData, localTimestamp: Dat...
method _mapFromTbtTicker (line 181) | private *_mapFromTbtTicker(message: OkexBBOTbtData, localTimestamp: Da...
method _mapFromTicker (line 208) | private *_mapFromTicker(message: OkexV5TickerMessage, localTimestamp: ...
class OkexV5DerivativeTickerMapper (line 229) | class OkexV5DerivativeTickerMapper implements Mapper<'okex-futures' | 'o...
method constructor (line 237) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 239) | canHandle(message: any) {
method getFilters (line 249) | getFilters(symbols?: string[]) {
method map (line 277) | *map(
class OkexV5LiquidationsMapper (line 360) | class OkexV5LiquidationsMapper implements Mapper<OKEX_EXCHANGES, Liquida...
method constructor (line 362) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 364) | canHandle(message: any) {
method getFilters (line 371) | getFilters(symbols?: string[]) {
method map (line 386) | *map(
class OkexV5OptionSummaryMapper (line 431) | class OkexV5OptionSummaryMapper implements Mapper<'okex-options', Option...
method canHandle (line 439) | canHandle(message: any) {
method getFilters (line 452) | getFilters(symbols?: string[]) {
method map (line 487) | *map(
type OkexV5TradeMessage (line 596) | type OkexV5TradeMessage = {
type OkexV5TradesAllMessage (line 601) | type OkexV5TradesAllMessage = {
type OkexV5BookLevel (line 606) | type OkexV5BookLevel = [string, string, string, string]
type OkexV5BookMessage (line 608) | type OkexV5BookMessage =
type OkexV5TickerMessage (line 626) | type OkexV5TickerMessage = {
type OkexV5OpenInterestMessage (line 650) | type OkexV5OpenInterestMessage = {
type OkexV5MarkPriceMessage (line 655) | type OkexV5MarkPriceMessage = {
type OkexV5IndexTickerMessage (line 660) | type OkexV5IndexTickerMessage = {
type OkexV5FundingRateMessage (line 676) | type OkexV5FundingRateMessage = {
type OkexV5LiquidationMessage (line 681) | type OkexV5LiquidationMessage = {
type OkexV5LiquidationOrderMessage (line 686) | type OkexV5LiquidationOrderMessage = {
type OkexV5SummaryMessage (line 699) | type OkexV5SummaryMessage = {
class OkexTradesMapper (line 728) | class OkexTradesMapper implements Mapper<OKEX_EXCHANGES, Trade> {
method constructor (line 729) | constructor(private readonly _exchange: Exchange, private readonly _ma...
method canHandle (line 731) | canHandle(message: OkexDataMessage) {
method getFilters (line 735) | getFilters(symbols?: string[]) {
method map (line 746) | *map(okexTradesMessage: OKexTradesDataMessage, localTimestamp: Date): ...
class OkexBookChangeMapper (line 772) | class OkexBookChangeMapper implements Mapper<OKEX_EXCHANGES, BookChange> {
method constructor (line 773) | constructor(
method canHandle (line 779) | canHandle(message: OkexDataMessage) {
method getFilters (line 785) | getFilters(symbols?: string[]) {
method map (line 812) | *map(okexDepthDataMessage: OkexDepthDataMessage, localTimestamp: Date)...
class OkexDerivativeTickerMapper (line 838) | class OkexDerivativeTickerMapper implements Mapper<'okex-futures' | 'oke...
method constructor (line 843) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 845) | canHandle(message: OkexDataMessage) {
method getFilters (line 851) | getFilters(symbols?: string[]) {
method map (line 863) | *map(
class OkexOptionSummaryMapper (line 901) | class OkexOptionSummaryMapper implements Mapper<'okex-options', OptionSu...
method canHandle (line 905) | canHandle(message: OkexDataMessage) {
method getFilters (line 909) | getFilters(symbols?: string[]) {
method map (line 932) | *map(message: OkexOptionSummaryData | OkexIndexData, localTimestamp: D...
class OkexLiquidationsMapper (line 993) | class OkexLiquidationsMapper implements Mapper<OKEX_EXCHANGES, Liquidati...
method constructor (line 994) | constructor(private readonly _exchange: Exchange, private readonly _ma...
method canHandle (line 996) | canHandle(message: OkexDataMessage) {
method getFilters (line 1000) | getFilters(symbols?: string[]) {
method map (line 1011) | *map(okexLiquidationDataMessage: OkexLiqudationDataMessage, localTimes...
class OkexBookTickerMapper (line 1029) | class OkexBookTickerMapper implements Mapper<OKEX_EXCHANGES, BookTicker> {
method constructor (line 1030) | constructor(private readonly _exchange: Exchange, private readonly _ma...
method canHandle (line 1032) | canHandle(message: OkexDataMessage) {
method getFilters (line 1036) | getFilters(symbols?: string[]) {
method map (line 1047) | *map(message: OkexTickersMessage, localTimestamp: Date): IterableItera...
type OkexDataMessage (line 1068) | type OkexDataMessage = {
type OKexTradesDataMessage (line 1072) | type OKexTradesDataMessage = {
type OkexLiqudationDataMessage (line 1084) | type OkexLiqudationDataMessage = {
type OkexTickersMessage (line 1095) | type OkexTickersMessage = {
type OkexFundingRateMessage (line 1108) | type OkexFundingRateMessage = {
type OkexMarkPriceMessage (line 1118) | type OkexMarkPriceMessage = {
type OkexDepthDataMessage (line 1126) | type OkexDepthDataMessage = {
type OkexBookLevel (line 1136) | type OkexBookLevel = [number | string, number | string, number | string,...
type OKEX_EXCHANGES (line 1138) | type OKEX_EXCHANGES = 'okex' | 'okcoin' | 'okex-futures' | 'okex-swap' |...
type OKEX_MARKETS (line 1140) | type OKEX_MARKETS = 'spot' | 'swap' | 'futures' | 'option'
type OkexIndexData (line 1142) | type OkexIndexData = {
type OkexOptionSummaryData (line 1152) | type OkexOptionSummaryData = {
type OkexBBOTbtData (line 1179) | type OkexBBOTbtData = {
FILE: src/mappers/okexspreads.ts
class OkexSpreadsTradesMapper (line 5) | class OkexSpreadsTradesMapper implements Mapper<'okex-spreads', Trade> {
method canHandle (line 6) | canHandle(message: any) {
method getFilters (line 13) | getFilters(symbols?: string[]) {
method map (line 24) | *map(okexTradesMessage: OkexSpreadTradeMessage, localTimestamp: Date):...
class OkexSpreadsBookChangeMapper (line 48) | class OkexSpreadsBookChangeMapper implements Mapper<'okex-spreads', Book...
method canHandle (line 49) | canHandle(message: any) {
method getFilters (line 57) | getFilters(symbols?: string[]) {
method map (line 67) | *map(okexDepthDataMessage: OkexSpreadBookMessage, localTimestamp: Date...
class OkexSpreadsBookTickerMapper (line 89) | class OkexSpreadsBookTickerMapper implements Mapper<'okex-spreads', Book...
method canHandle (line 90) | canHandle(message: any) {
method getFilters (line 98) | getFilters(symbols?: string[]) {
method map (line 109) | *map(message: OkexSpreadBBOMessage, localTimestamp: Date): IterableIte...
type OkexSpreadTradeMessage (line 142) | type OkexSpreadTradeMessage = {
type OkexSpreadBookLevel (line 156) | type OkexSpreadBookLevel = [string, string, string, string]
type OkexSpreadBookMessage (line 158) | type OkexSpreadBookMessage = {
type OkexSpreadBBOMessage (line 169) | type OkexSpreadBBOMessage = {
FILE: src/mappers/phemex.ts
function getPriceScale (line 14) | function getPriceScale(symbol: string) {
function getQtyScale (line 22) | function getQtyScale(symbol: string) {
constant COINS_STARTING_WITH_S (line 30) | const COINS_STARTING_WITH_S = [
function getInstrumentType (line 90) | function getInstrumentType(symbol: string) {
function getApiSymbolId (line 102) | function getApiSymbolId(symbolId: string) {
function getSymbols (line 118) | function getSymbols(symbols: string[]) {
method canHandle (line 129) | canHandle(message: PhemexTradeMessage) {
method getFilters (line 133) | getFilters(symbols?: string[]) {
method map (line 165) | *map(message: PhemexTradeMessage, localTimestamp: Date): IterableIterato...
function mapPerpBookLevel (line 211) | function mapPerpBookLevel([price, amount]: [string, string]) {
method canHandle (line 219) | canHandle(message: PhemexBookMessage) {
method getFilters (line 223) | getFilters(symbols?: string[]) {
method map (line 254) | *map(message: PhemexBookMessage, localTimestamp: Date): IterableIterator...
class PhemexDerivativeTickerMapper (line 284) | class PhemexDerivativeTickerMapper implements Mapper<'phemex', Derivativ...
method canHandle (line 287) | canHandle(message: PhemexTicker) {
method getFilters (line 291) | getFilters(symbols?: string[]) {
method map (line 322) | *map(message: PhemexTicker, localTimestamp: Date): IterableIterator<De...
type PhemexTradeMessage (line 370) | type PhemexTradeMessage =
type PhemexBookLevel (line 383) | type PhemexBookLevel = [number, number]
type PhemexBookMessage (line 385) | type PhemexBookMessage =
type PhemexTicker (line 408) | type PhemexTicker =
FILE: src/mappers/poloniex.ts
class PoloniexV2TradesMapper (line 5) | class PoloniexV2TradesMapper implements Mapper<'poloniex', Trade> {
method canHandle (line 6) | canHandle(message: any) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 21) | *map(message: PoloniexV2TradesMessage, localTimestamp: Date): Iterable...
class PoloniexV2BookChangeMapper (line 44) | class PoloniexV2BookChangeMapper implements Mapper<'poloniex', BookChang...
method canHandle (line 45) | canHandle(message: any) {
method getFilters (line 49) | getFilters(symbols?: string[]) {
method map (line 60) | *map(message: PoloniexV2BookMessage, localTimestamp: Date): IterableIt...
class PoloniexTradesMapper (line 78) | class PoloniexTradesMapper implements Mapper<'poloniex', Trade> {
method canHandle (line 81) | canHandle(message: PoloniexPriceAggreatedMessage) {
method getFilters (line 97) | getFilters(symbols?: string[]) {
method map (line 108) | *map(message: PoloniexPriceAggreatedMessage, localTimestamp: Date): It...
class PoloniexBookChangeMapper (line 154) | class PoloniexBookChangeMapper implements Mapper<'poloniex', BookChange> {
method canHandle (line 157) | canHandle(message: PoloniexPriceAggreatedMessage) {
method getFilters (line 173) | getFilters(symbols?: string[]) {
method map (line 184) | *map(message: PoloniexPriceAggreatedMessage, localTimestamp: Date): It...
type PoloniexBookSnapshot (line 230) | type PoloniexBookSnapshot = ['i', { currencyPair: string; orderBook: [{ ...
type PoloniexBookUpdate (line 231) | type PoloniexBookUpdate = ['o', 0 | 1, string, string]
type PoloniexTrade (line 232) | type PoloniexTrade = ['t', string, 1 | 0, string, string, number]
type PoloniexPriceAggreatedMessage (line 234) | type PoloniexPriceAggreatedMessage = [number, number, (PoloniexBookSnaps...
type PoloniexV2SubscribeMessage (line 236) | type PoloniexV2SubscribeMessage = {
type PoloniexV2TradesMessage (line 242) | type PoloniexV2TradesMessage = {
type PoloniexV2BookMessage (line 258) | type PoloniexV2BookMessage =
FILE: src/mappers/serum.ts
class SerumTradesMapper (line 5) | class SerumTradesMapper implements Mapper<'serum' | 'star-atlas', Trade> {
method constructor (line 6) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 8) | canHandle(message: SerumVialTrade) {
method getFilters (line 12) | getFilters(symbols?: string[]) {
method map (line 25) | *map(message: SerumVialTrade, localTimestamp: Date): IterableIterator<...
class SerumBookChangeMapper (line 40) | class SerumBookChangeMapper implements Mapper<'serum' | 'star-atlas', Bo...
method constructor (line 41) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 43) | canHandle(message: SerumVialL2Snapshot | SerumVialL2Update) {
method getFilters (line 47) | getFilters(symbols?: string[]) {
method map (line 64) | *map(message: SerumVialL2Snapshot | SerumVialL2Update, localTimestamp:...
method mapBookLevel (line 77) | protected mapBookLevel(level: SerumVialPriceLevel) {
class SerumBookTickerMapper (line 84) | class SerumBookTickerMapper implements Mapper<'serum' | 'star-atlas', Bo...
method constructor (line 85) | constructor(private readonly _exchange: Exchange) {}
method canHandle (line 87) | canHandle(message: SerumVialQuote) {
method getFilters (line 91) | getFilters(symbols?: string[]) {
method map (line 104) | *map(message: SerumVialQuote, localTimestamp: Date): IterableIterator<...
type SerumVialTrade (line 121) | type SerumVialTrade =
type SerumVialPriceLevel (line 147) | type SerumVialPriceLevel = [string, string]
type SerumVialL2Snapshot (line 149) | type SerumVialL2Snapshot = {
type SerumVialL2Update (line 159) | type SerumVialL2Update = {
type SerumVialQuote (line 169) | type SerumVialQuote = {
FILE: src/mappers/upbit.ts
class UpbitTradesMapper (line 5) | class UpbitTradesMapper implements Mapper<'upbit', Trade> {
method canHandle (line 6) | canHandle(message: UpbitTrade) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 21) | *map(message: UpbitTrade, localTimestamp: Date): IterableIterator<Trad...
class UpbitBookChangeMapper (line 36) | class UpbitBookChangeMapper implements Mapper<'upbit', BookChange> {
method canHandle (line 37) | canHandle(message: UpbitOrderBook) {
method getFilters (line 41) | getFilters(symbols?: string[]) {
method map (line 52) | *map(message: UpbitOrderBook, localTimestamp: Date): IterableIterator<...
type UpbitTrade (line 84) | type UpbitTrade = {
type UpbitOrderBook (line 101) | type UpbitOrderBook = {
FILE: src/mappers/woox.ts
method canHandle (line 6) | canHandle(message: WooxTradeMessage) {
method getFilters (line 10) | getFilters(symbols?: string[]) {
method map (line 21) | *map(message: WooxTradeMessage, localTimestamp: Date): IterableIterator<...
class WooxBookChangeMapper (line 42) | class WooxBookChangeMapper implements Mapper<'woo-x', BookChange> {
method canHandle (line 45) | canHandle(message: WooxOrderbookMessage | WooxOrderbookupdateMessage) {
method getFilters (line 57) | getFilters(symbols?: string[]) {
method map (line 72) | *map(message: WooxOrderbookMessage | WooxOrderbookupdateMessage, local...
method _mapBookDepthUpdate (line 122) | private _mapBookDepthUpdate(
method _mapBookLevel (line 144) | private _mapBookLevel(level: [number, number]) {
class WooxDerivativeTickerMapper (line 152) | class WooxDerivativeTickerMapper implements Mapper<'woo-x', DerivativeTi...
method canHandle (line 156) | canHandle(message: WooxTradeMessage | WooxEstFundingRate | WooxMarkPri...
method getFilters (line 172) | getFilters(symbols?: string[]) {
method map (line 199) | *map(message: any, localTimestamp: Date): IterableIterator<DerivativeT...
class WooxBookTickerMapper (line 239) | class WooxBookTickerMapper implements Mapper<'woo-x', BookTicker> {
method canHandle (line 240) | canHandle(message: WooxTradeMessage) {
method getFilters (line 244) | getFilters(symbols?: string[]) {
method map (line 255) | *map(wooxBBOMessage: WooxBBOMessage, localTimestamp: Date) {
type LocalDepthInfo (line 275) | type LocalDepthInfo = {
type WooxTradeMessage (line 281) | type WooxTradeMessage = {
type WooxOrderbookupdateMessage (line 287) | type WooxOrderbookupdateMessage = {
type WooxOrderbookMessage (line 298) | type WooxOrderbookMessage = {
type WooxBBOMessage (line 311) | type WooxBBOMessage = {
type WooxMarkPrice (line 317) | type WooxMarkPrice = { topic: 'PERP_BTC_USDT@markprice'; ts: 16744320000...
type WooxEstFundingRate (line 319) | type WooxEstFundingRate = {
type WooxIndexPrice (line 325) | type WooxIndexPrice = { topic: 'SPOT_BTC_USDT@indexprice'; ts: 167443200...
type WooxOpenInterest (line 326) | type WooxOpenInterest = { topic: 'PERP_BTC_USDT@openinterest'; ts: 16744...
FILE: src/options.ts
function init (line 19) | function init(initOptions: Partial<Options> = {}) {
function getOptions (line 23) | function getOptions() {
type Options (line 27) | type Options = {
type DataFeedCompression (line 36) | type DataFeedCompression = 'gzip' | 'zstd'
FILE: src/orderbook.ts
type OnLevelRemovedCB (line 5) | type OnLevelRemovedCB = (
class OrderBook (line 13) | class OrderBook {
method constructor (line 21) | constructor({
method update (line 29) | public update(bookChange: BookChange) {
method bestBid (line 47) | public bestBid() {
method bestAsk (line 56) | public bestAsk() {
method _removeCrossedLevelsIfNeeded (line 65) | private _removeCrossedLevelsIfNeeded(bookChange: BookChange) {
method _removeBestAsk (line 103) | private _removeBestAsk() {
method _removeBestBid (line 116) | private _removeBestBid() {
method bids (line 129) | public *bids(): IterableIterator<BookPriceLevel> {
method asks (line 139) | public *asks(): IterableIterator<BookPriceLevel> {
function applyPriceLevelChanges (line 150) | function applyPriceLevelChanges(tree: RBTreeType<BookPriceLevel>, priceL...
FILE: src/realtimefeeds/ascendex.ts
class AscendexRealTimeFeed (line 5) | class AscendexRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 8) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 37) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 45) | protected messageIsHeartbeat(msg: any) {
method provideManualSnapshots (line 49) | protected async provideManualSnapshots(filters: Filter<string>[], shou...
FILE: src/realtimefeeds/binance.ts
constant DEFAULT_OPEN_INTEREST_MIN_AVAILABLE_WEIGHT_BUFFER (line 15) | const DEFAULT_OPEN_INTEREST_MIN_AVAILABLE_WEIGHT_BUFFER = 100
constant DEFAULT_OPEN_INTEREST_POLLING_INTERVAL_MS (line 16) | const DEFAULT_OPEN_INTEREST_POLLING_INTERVAL_MS = 5 * 1000
constant OPEN_INTEREST_BATCH_SIZE (line 17) | const OPEN_INTEREST_BATCH_SIZE = 10
constant OPEN_INTEREST_REQUEST_WEIGHT (line 18) | const OPEN_INTEREST_REQUEST_WEIGHT = 1
constant OPEN_INTEREST_POLLING_RECOVERY_MS (line 19) | const OPEN_INTEREST_POLLING_RECOVERY_MS = 1000
constant OPEN_INTEREST_MAX_POLLING_INTERVAL_MS (line 20) | const OPEN_INTEREST_MAX_POLLING_INTERVAL_MS = 60 * 1000
constant BINANCE_FUTURES_PUBLIC_CHANNELS (line 21) | const BINANCE_FUTURES_PUBLIC_CHANNELS = new Set(['bookTicker', 'depth', ...
constant BINANCE_FUTURES_DEFAULT_WS_BASE_URL (line 22) | const BINANCE_FUTURES_DEFAULT_WS_BASE_URL = 'wss://fstream.binance.com'
constant BINANCE_FUTURES_PUBLIC_STREAM_PATH (line 23) | const BINANCE_FUTURES_PUBLIC_STREAM_PATH = '/public/stream'
constant BINANCE_FUTURES_MARKET_STREAM_PATH (line 24) | const BINANCE_FUTURES_MARKET_STREAM_PATH = '/market/stream'
type BinanceFuturesStreamPath (line 26) | type BinanceFuturesStreamPath = typeof BINANCE_FUTURES_PUBLIC_STREAM_PAT...
function parseBinanceWeightHeader (line 28) | function parseBinanceWeightHeader(headerValue: string | undefined) {
function getExchangeScopedNumberEnv (line 38) | function getExchangeScopedNumberEnv(exchange: string, suffix: string, fa...
function getExchangeScopedWssUrlEnv (line 51) | function getExchangeScopedWssUrlEnv(exchange: string) {
function normalizeBinanceSplitWsBaseUrl (line 57) | function normalizeBinanceSplitWsBaseUrl(wssUrl: string) {
function getBinanceFuturesWebSocketUrl (line 64) | function getBinanceFuturesWebSocketUrl(exchange: string, streamPath: Bin...
function getBinanceRequestWeightLimit (line 71) | function getBinanceRequestWeightLimit(exchange: string, exchangeInfo: an...
function getBinanceAvailableWeight (line 86) | function getBinanceAvailableWeight(weightLimit: number, usedWeight: numb...
function getDelayToNextMinuteMS (line 90) | function getDelayToNextMinuteMS() {
method _getRealTimeFeeds (line 102) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[]...
class BinanceFuturesOpenInterestClient (line 129) | class BinanceFuturesOpenInterestClient extends PoolingClientBase {
method constructor (line 137) | constructor(
method getPoolingDelayMS (line 161) | protected getPoolingDelayMS() {
method poolDataToStream (line 165) | protected async poolDataToStream(outputStream: Writable) {
method _waitForAvailableWeight (line 236) | private async _waitForAvailableWeight() {
method _initializeRateLimitInfo (line 261) | private async _initializeRateLimitInfo() {
method _getBatchSize (line 270) | private _getBatchSize() {
method _notifyError (line 276) | private _notifyError(error: unknown) {
method _updateUsedWeight (line 286) | private _updateUsedWeight(usedWeight: number | undefined, fallbackIncr...
class BinanceSingleConnectionRealTimeFeed (line 298) | class BinanceSingleConnectionRealTimeFeed extends RealTimeFeedBase {
method constructor (line 299) | constructor(
method mapToSubscribeMessages (line 312) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 333) | protected messageIsError(message: any): boolean {
method provideManualSnapshots (line 350) | protected async provideManualSnapshots(filters: Filter<string>[], shou...
class BinanceFuturesSingleConnectionRealTimeFeed (line 442) | class BinanceFuturesSingleConnectionRealTimeFeed extends BinanceSingleCo...
method constructor (line 443) | constructor(
method getWebSocketUrl (line 465) | protected async getWebSocketUrl() {
class BinanceRealTimeFeed (line 470) | class BinanceRealTimeFeed extends BinanceRealTimeFeedBase {
class BinanceJerseyRealTimeFeed (line 481) | class BinanceJerseyRealTimeFeed extends BinanceRealTimeFeedBase {
class BinanceUSRealTimeFeed (line 492) | class BinanceUSRealTimeFeed extends BinanceRealTimeFeedBase {
class BinanceFuturesRealTimeFeed (line 503) | class BinanceFuturesRealTimeFeed extends BinanceRealTimeFeedBase {
method _getRealTimeFeeds (line 514) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>...
class BinanceDeliveryRealTimeFeed (line 556) | class BinanceDeliveryRealTimeFeed extends BinanceRealTimeFeedBase {
FILE: src/realtimefeeds/binancedex.ts
class BinanceDexRealTimeFeed (line 5) | class BinanceDexRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 9) | protected mapToSubscribeMessages(filters: Filter<string>[]) {
method messageIsError (line 25) | protected messageIsError(message: any): boolean {
method provideManualSnapshots (line 33) | protected async provideManualSnapshots(filters: Filter<string>[], shou...
FILE: src/realtimefeeds/binanceeuropeanoptions.ts
class BinanceEuropeanOptionsRealTimeFeed (line 5) | class BinanceEuropeanOptionsRealTimeFeed extends MultiConnectionRealTime...
method _getRealTimeFeeds (line 6) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>...
class BinanceEuropeanOptionsSingleFeed (line 41) | class BinanceEuropeanOptionsSingleFeed extends RealTimeFeedBase {
method constructor (line 42) | constructor(
method mapToSubscribeMessages (line 53) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 125) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/bitfinex.ts
constant TIMESTAMP (line 3) | const TIMESTAMP = 32768
constant SEQ_ALL (line 4) | const SEQ_ALL = 65536
class BitfinexRealTimeFeed (line 6) | class BitfinexRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 9) | protected mapToSubscribeMessages(filters: Filter<string>[]) {
method messageIsError (line 76) | protected messageIsError(message: any) {
method messageIsHeartbeat (line 80) | protected messageIsHeartbeat(message: any) {
FILE: src/realtimefeeds/bitflyer.ts
class BitflyerRealTimeFeed (line 4) | class BitflyerRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 26) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/bitget.ts
method mapToSubscribeMessages (line 9) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 34) | protected messageIsError(message: any): boolean {
class BitgetRealTimeFeed (line 41) | class BitgetRealTimeFeed extends BitgetRealTimeFeedBase {
method getInstType (line 42) | getInstType(_: string) {
class BitgetFuturesRealTimeFeed (line 47) | class BitgetFuturesRealTimeFeed extends BitgetRealTimeFeedBase {
method getInstType (line 48) | getInstType(symbol: string) {
FILE: src/realtimefeeds/bitmex.ts
class BitmexRealTimeFeed (line 5) | class BitmexRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 8) | protected mapToSubscribeMessages(filters: Filter<string>[]) {
method messageIsError (line 32) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/bitnomial.ts
class BitnomialRealTimeFeed (line 4) | class BitnomialRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 11) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 54) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/bitstamp.ts
class BitstampRealTimeFeed (line 5) | class BitstampRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 9) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 28) | protected messageIsError(message: any): boolean {
method provideManualSnapshots (line 40) | protected async provideManualSnapshots(filters: Filter<string>[], shou...
FILE: src/realtimefeeds/blockchaincom.ts
class BlockchainComRealTimeFeed (line 5) | class BlockchainComRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 9) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 29) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 33) | protected messageIsHeartbeat(msg: any) {
method onConnected (line 36) | protected async onConnected() {
FILE: src/realtimefeeds/bybit.ts
class BybitRealTimeDataFeed (line 5) | class BybitRealTimeDataFeed extends MultiConnectionRealTimeFeedBase {
method _getRealTimeFeeds (line 6) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>...
method _only (line 26) | private _only(filter: (symbol: string) => boolean) {
method mapToSubscribeMessages (line 47) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 68) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 76) | protected messageIsHeartbeat(msg: any) {
class BybitLinearRealTimeDataFeed (line 81) | class BybitLinearRealTimeDataFeed extends BybitSingleConnectionRealTimeD...
class BybitInverseRealTimeDataFeed (line 85) | class BybitInverseRealTimeDataFeed extends BybitSingleConnectionRealTime...
class BybitSpotRealTimeDataFeed (line 89) | class BybitSpotRealTimeDataFeed extends BybitSingleConnectionRealTimeDat...
class BybitOptionsRealTimeDataFeed (line 93) | class BybitOptionsRealTimeDataFeed extends BybitSingleConnectionRealTime...
method mapToSubscribeMessages (line 96) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
FILE: src/realtimefeeds/coinbase.ts
class CoinbaseRealTimeFeed (line 5) | class CoinbaseRealTimeFeed extends RealTimeFeedBase {
method wssURL (line 11) | protected get wssURL() {
method mapToSubscribeMessages (line 23) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method getAuthParams (line 91) | private getAuthParams() {
method messageIsError (line 106) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/coinbaseinternational.ts
class CoinbaseInternationalRealTimeFeed (line 5) | class CoinbaseInternationalRealTimeFeed extends RealTimeFeedBase {
method wssURL (line 11) | protected get wssURL() {
method mapToSubscribeMessages (line 15) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method getAuthParams (line 41) | private getAuthParams() {
method messageIsError (line 57) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/coinflex.ts
class CoinflexRealTimeFeed (line 4) | class CoinflexRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 26) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/cryptocom.ts
class CryptoComRealTimeFeed (line 4) | class CryptoComRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 34) | protected messageIsError(message: any): boolean {
method onMessage (line 38) | protected onMessage(msg: any) {
method messageIsHeartbeat (line 46) | protected messageIsHeartbeat(msg: any) {
FILE: src/realtimefeeds/cryptofacilities.ts
class CryptofacilitiesRealTimeFeed (line 4) | class CryptofacilitiesRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 23) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 27) | protected messageIsHeartbeat(message: any): boolean {
FILE: src/realtimefeeds/delta.ts
class DeltaRealTimeFeed (line 4) | class DeltaRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]) {
method messageIsError (line 24) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 32) | protected messageIsHeartbeat(msg: any) {
FILE: src/realtimefeeds/deribit.ts
class DeribitRealTimeDataFeed (line 5) | class DeribitRealTimeDataFeed extends RealTimeFeedBase {
method wssURL (line 6) | protected get wssURL() {
method mapToSubscribeMessages (line 18) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 47) | protected messageIsError(message: any): boolean {
method onConnected (line 51) | protected async onConnected() {
method messageIsHeartbeat (line 80) | protected messageIsHeartbeat(msg: any) {
method onMessage (line 84) | protected onMessage(msg: any) {
FILE: src/realtimefeeds/dydx.ts
class DydxRealTimeFeed (line 4) | class DydxRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 37) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/dydx_v4.ts
class DydxV4RealTimeFeed (line 4) | class DydxV4RealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 36) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/ftx.ts
method _getRealTimeFeeds (line 11) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[]...
class FtxSingleConnectionRealTimeFeed (line 29) | class FtxSingleConnectionRealTimeFeed extends RealTimeFeedBase {
method constructor (line 30) | constructor(
method mapToSubscribeMessages (line 40) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 58) | protected messageIsError(message: any): boolean {
method isIgnoredError (line 62) | protected isIgnoredError(message: any) {
method messageIsHeartbeat (line 71) | protected messageIsHeartbeat(msg: any) {
class FTXInstrumentInfoClient (line 76) | class FTXInstrumentInfoClient extends PoolingClientBase {
method constructor (line 77) | constructor(
method poolDataToStream (line 86) | protected async poolDataToStream(outputStream: Writable) {
class FtxRealTimeFeed (line 129) | class FtxRealTimeFeed extends FTXRealTimeFeedBase {
class FtxUSRealTimeFeed (line 134) | class FtxUSRealTimeFeed extends FTXRealTimeFeedBase {
FILE: src/realtimefeeds/gateio.ts
class GateIORealTimeFeed (line 4) | class GateIORealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 8) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 39) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/gateiofutures.ts
class GateIOFuturesRealTimeFeed (line 4) | class GateIOFuturesRealTimeFeed extends MultiConnectionRealTimeFeedBase {
method _getRealTimeFeeds (line 5) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>...
method _only (line 25) | private _only(filter: (symbol: string) => boolean) {
class GateIOFuturesSingleConnectionRealTimeFeed (line 43) | class GateIOFuturesSingleConnectionRealTimeFeed extends RealTimeFeedBase {
method constructor (line 47) | constructor(
method mapToSubscribeMessages (line 58) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 84) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/gemini.ts
class GeminiRealTimeFeed (line 4) | class GeminiRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 34) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/hitbtc.ts
class HitBtcRealTimeFeed (line 4) | class HitBtcRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 12) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 59) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/huobi.ts
method _getRealTimeFeeds (line 15) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[]...
method getOpenInterestURLPath (line 73) | protected getOpenInterestURLPath(symbol: string) {
class HuobiMarketDataRealTimeFeed (line 78) | class HuobiMarketDataRealTimeFeed extends RealTimeFeedBase {
method constructor (line 79) | constructor(
method mapToSubscribeMessages (line 90) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method provideManualSnapshots (line 111) | protected async provideManualSnapshots(filters: Filter<string>[], shou...
method messageIsError (line 142) | protected messageIsError(message: any): boolean {
method isIgnoredError (line 149) | protected isIgnoredError(message: any) {
method onMessage (line 158) | protected onMessage(message: any) {
method messageIsHeartbeat (line 166) | protected messageIsHeartbeat(message: any) {
class HuobiNotificationsRealTimeFeed (line 171) | class HuobiNotificationsRealTimeFeed extends RealTimeFeedBase {
method constructor (line 172) | constructor(
method mapToSubscribeMessages (line 182) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 206) | protected messageIsError(message: any): boolean {
method onMessage (line 219) | protected onMessage(message: any) {
method messageIsHeartbeat (line 228) | protected messageIsHeartbeat(message: any) {
class HuobiOpenInterestClient (line 233) | class HuobiOpenInterestClient extends PoolingClientBase {
method constructor (line 234) | constructor(
method poolDataToStream (line 243) | protected async poolDataToStream(outputStream: Writable) {
class HuobiOptionsMarketIndexClient (line 273) | class HuobiOptionsMarketIndexClient extends PoolingClientBase {
method constructor (line 274) | constructor(exchange: string, private readonly _httpURL: string, priva...
method poolDataToStream (line 278) | protected async poolDataToStream(outputStream: Writable) {
class HuobiOptionsIndexClient (line 308) | class HuobiOptionsIndexClient extends PoolingClientBase {
method constructor (line 309) | constructor(exchange: string, private readonly _httpURL: string, priva...
method poolDataToStream (line 313) | protected async poolDataToStream(outputStream: Writable) {
class HuobiRealTimeFeed (line 343) | class HuobiRealTimeFeed extends HuobiRealTimeFeedBase {
class HuobiDMRealTimeFeed (line 354) | class HuobiDMRealTimeFeed extends HuobiRealTimeFeedBase {
method getOpenInterestURLPath (line 371) | protected getOpenInterestURLPath(symbol: string) {
class HuobiDMSwapRealTimeFeed (line 380) | class HuobiDMSwapRealTimeFeed extends HuobiRealTimeFeedBase {
method getOpenInterestURLPath (line 390) | protected getOpenInterestURLPath(symbol: string) {
class HuobiDMLinearSwapRealTimeFeed (line 395) | class HuobiDMLinearSwapRealTimeFeed extends HuobiRealTimeFeedBase {
method getOpenInterestURLPath (line 405) | protected getOpenInterestURLPath(symbol: string) {
class HuobiDMOptionsRealTimeFeed (line 410) | class HuobiDMOptionsRealTimeFeed extends HuobiRealTimeFeedBase {
method getOpenInterestURLPath (line 420) | protected getOpenInterestURLPath(symbol: string) {
FILE: src/realtimefeeds/hyperliquid.ts
class HyperliquidRealTimeFeed (line 4) | class HyperliquidRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 27) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 31) | protected messageIsHeartbeat(message: any): boolean {
FILE: src/realtimefeeds/index.ts
function getRealTimeFeedFactory (line 120) | function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {
function createRealTimeFeed (line 128) | function createRealTimeFeed(
function setRealTimeFeedFactory (line 139) | function setRealTimeFeedFactory(exchange: Exchange, realTimeFeed: RealTi...
FILE: src/realtimefeeds/kraken.ts
class KrakenRealTimeFeed (line 4) | class KrakenRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 30) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 34) | protected messageIsHeartbeat(message: any): boolean {
FILE: src/realtimefeeds/kucoin.ts
class KucoinRealTimeFeed (line 5) | class KucoinRealTimeFeed extends RealTimeFeedBase {
method getWebSocketUrl (line 9) | protected async getWebSocketUrl() {
method mapToSubscribeMessages (line 15) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method provideManualSnapshots (line 33) | protected async provideManualSnapshots(filters: Filter<string>[], shou...
method messageIsError (line 63) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 74) | protected messageIsHeartbeat(msg: any) {
FILE: src/realtimefeeds/kucoinfutures.ts
class KucoinFuturesRealTimeFeed (line 15) | class KucoinFuturesRealTimeFeed extends MultiConnectionRealTimeFeedBase {
method _getRealTimeFeeds (line 18) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>...
class KucoinFuturesSingleConnectionRealTimeFeed (line 32) | class KucoinFuturesSingleConnectionRealTimeFeed extends RealTimeFeedBase {
method constructor (line 33) | constructor(
method getWebSocketUrl (line 44) | protected async getWebSocketUrl() {
method mapToSubscribeMessages (line 50) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method provideManualSnapshots (line 67) | protected async provideManualSnapshots(filters: Filter<string>[], shou...
method messageIsError (line 95) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 106) | protected messageIsHeartbeat(msg: any) {
class KucoinFuturesContractDetailsClient (line 111) | class KucoinFuturesContractDetailsClient extends PoolingClientBase {
method constructor (line 112) | constructor(exchange: string, private readonly _httpURL: string) {
method poolDataToStream (line 116) | protected async poolDataToStream(outputStream: Writable) {
FILE: src/realtimefeeds/mango.ts
class MangoRealTimeFeed (line 3) | class MangoRealTimeFeed extends SerumRealTimeFeed {
FILE: src/realtimefeeds/okex.ts
class OkexRealTimeFeed (line 6) | class OkexRealTimeFeed extends MultiConnectionRealTimeFeedBase {
method _getRealTimeFeeds (line 7) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>...
class OkexSingleRealTimeFeed (line 22) | class OkexSingleRealTimeFeed extends RealTimeFeedBase {
method constructor (line 23) | constructor(
method secondsSinceEpoch (line 35) | private secondsSinceEpoch() {
method onConnected (line 39) | protected async onConnected() {
method mapToSubscribeMessages (line 60) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 91) | protected messageIsError(message: any): boolean {
method isIgnoredError (line 95) | protected isIgnoredError(message: any) {
class OKCoinRealTimeFeed (line 104) | class OKCoinRealTimeFeed extends OkexSingleRealTimeFeed {
method constructor (line 105) | constructor(exchange: string, filters: Filter<string>[], timeoutInterv...
class OkexOptionsRealTimeFeed (line 110) | class OkexOptionsRealTimeFeed extends MultiConnectionRealTimeFeedBase {
method _getRealTimeFeeds (line 111) | protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>...
class OkexOptionsSingleRealTimeFeed (line 132) | class OkexOptionsSingleRealTimeFeed extends OkexSingleRealTimeFeed {
method constructor (line 133) | constructor(
method _channelRequiresIndexNotSymbol (line 145) | private _channelRequiresIndexNotSymbol(channel: string) {
method mapToSubscribeMessages (line 151) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
FILE: src/realtimefeeds/okexspreads.ts
class OkexSpreadsRealTimeFeed (line 6) | class OkexSpreadsRealTimeFeed extends RealTimeFeedBase {
method secondsSinceEpoch (line 11) | private secondsSinceEpoch() {
method onConnected (line 15) | protected async onConnected() {
method mapToSubscribeMessages (line 36) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 61) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/phemex.ts
class PhemexRealTimeFeed (line 4) | class PhemexRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 18) | protected mapToSubscribeMessages(filters: Filter<string>[]) {
method messageIsError (line 49) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/poloniex.ts
class PoloniexRealTimeFeed (line 4) | class PoloniexRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]) {
method messageIsHeartbeat (line 27) | protected messageIsHeartbeat(msg: any) {
method messageIsError (line 31) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/realtimefeed.ts
type RealTimeFeed (line 9) | type RealTimeFeed = {
type RealTimeFeedIterable (line 20) | type RealTimeFeedIterable = AsyncIterable<any>
method [Symbol.asyncIterator] (line 23) | [Symbol.asyncIterator]() {
method constructor (line 39) | constructor(
method getWebSocketUrl (line 63) | protected async getWebSocketUrl() {
method _stream (line 70) | private async *_stream() {
method send (line 212) | protected send(msg: any) {
method isIgnoredError (line 228) | protected isIgnoredError(_message: any) {
method messageIsHeartbeat (line 232) | protected messageIsHeartbeat(_msg: any) {
method provideManualSnapshots (line 236) | protected async provideManualSnapshots(_filters: Filter<string>[], _shou...
method onMessage (line 238) | protected onMessage(_msg: any) {}
method onConnected (line 240) | protected async onConnected() {}
method _monitorConnectionIfStale (line 244) | private _monitorConnectionIfStale() {
method _sendPeriodicPing (line 267) | private _sendPeriodicPing() {
method constructor (line 329) | constructor(
method [Symbol.asyncIterator] (line 336) | [Symbol.asyncIterator]() {
method _stream (line 340) | private async *_stream() {
method constructor (line 380) | constructor(exchange: string, private readonly _poolingIntervalSeconds: ...
method [Symbol.asyncIterator] (line 384) | [Symbol.asyncIterator]() {
method getPoolingDelayMS (line 390) | protected getPoolingDelayMS() {
method _startPooling (line 394) | private async _startPooling(outputStream: Writable) {
method _stream (line 419) | private async *_stream() {
FILE: src/realtimefeeds/serum.ts
class SerumRealTimeFeed (line 4) | class SerumRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 14) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 61) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/staratlas.ts
class StarAtlasRealTimeFeed (line 3) | class StarAtlasRealTimeFeed extends SerumRealTimeFeed {
FILE: src/realtimefeeds/upbit.ts
class UpbitRealTimeFeed (line 4) | class UpbitRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 7) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method messageIsError (line 32) | protected messageIsError(message: any): boolean {
FILE: src/realtimefeeds/woox.ts
class WooxRealTimeFeed (line 5) | class WooxRealTimeFeed extends RealTimeFeedBase {
method mapToSubscribeMessages (line 8) | protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
method provideManualSnapshots (line 27) | protected async provideManualSnapshots(filters: Filter<string>[], shou...
method messageIsError (line 55) | protected messageIsError(message: any): boolean {
method messageIsHeartbeat (line 59) | protected messageIsHeartbeat(message: any): boolean {
method onMessage (line 63) | protected onMessage(msg: any) {
FILE: src/replay.ts
type MapperOutput (line 16) | type MapperOutput<T> = T extends MapperFactory<any, infer U> ? U : never
type ReplayNormalizedMessage (line 17) | type ReplayNormalizedMessage<U extends readonly MapperFactory<any, any>[...
function cleanupSlice (line 231) | async function cleanupSlice(slicePath: string) {
function parseReplayMicroseconds (line 239) | function parseReplayMicroseconds(bufferLine: Buffer) {
function terminateWorker (line 244) | async function terminateWorker(worker: Worker, waitTimeout: number) {
function replayNormalized (line 260) | function replayNormalized<T extends Exchange, U extends MapperFactory<T,...
function validateReplayOptions (line 305) | function validateReplayOptions<T extends Exchange>(exchange: T, from: st...
function validateReplayNormalizedOptions (line 341) | function validateReplayNormalizedOptions(fromDate: Date, normalizers: Ma...
class ReliableWorker (line 350) | class ReliableWorker extends EventEmitter {
method constructor (line 354) | constructor(private readonly _payload: WorkerJobPayload) {
method _initWorker (line 360) | private _initWorker() {
method getUnderlyingWorker (line 391) | public getUnderlyingWorker() {
type ReplayOptions (line 396) | type ReplayOptions<T extends Exchange, U extends boolean = false, Z exte...
type ReplayNormalizedOptions (line 409) | type ReplayNormalizedOptions<T extends Exchange, U extends boolean = fal...
FILE: src/stream.ts
type MapperOutput (line 7) | type MapperOutput<T> = T extends MapperFactory<any, infer U> ? U : never
type StreamNormalizedMessage (line 8) | type StreamNormalizedMessage<U extends readonly MapperFactory<any, any>[...
function stream (line 41) | function stream<T extends Exchange, U extends boolean = false>({
function validateStreamOptions (line 117) | function validateStreamOptions(filters: Filter<string>[]) {
type StreamOptions (line 131) | type StreamOptions<T extends Exchange, U extends boolean = false> = {
type StreamNormalizedOptions (line 139) | type StreamNormalizedOptions<T extends Exchange, U extends boolean = fal...
function streamNormalized (line 147) | function streamNormalized<T extends Exchange, U extends MapperFactory<T,...
FILE: src/types.ts
type Exchange (line 3) | type Exchange = (typeof EXCHANGES)[number]
type FilterForExchange (line 5) | type FilterForExchange = { [key in Exchange]: Filter<(typeof EXCHANGE_CH...
type Filter (line 7) | type Filter<T> = {
type Writeable (line 12) | type Writeable<T> = { -readonly [P in keyof T]: T[P] }
type NormalizedData (line 14) | type NormalizedData = {
type Trade (line 23) | type Trade = {
type BookPriceLevel (line 35) | type BookPriceLevel = {
type BookChange (line 40) | type BookChange = {
type DerivativeTicker (line 52) | type DerivativeTicker = {
type BookTicker (line 68) | type BookTicker = {
type OptionSummary (line 82) | type OptionSummary = NormalizedData & {
type Liquidation (line 113) | type Liquidation = {
type Disconnect (line 125) | type Disconnect = {
type TradeBar (line 132) | type TradeBar = {
type BookSnapshot (line 156) | type BookSnapshot = {
type Date (line 172) | interface Date {
type Optional (line 177) | type Optional<T> = { [P in keyof T]: T[P] | undefined }
FILE: src/worker.ts
function getDataFeedSlices (line 27) | async function getDataFeedSlices(payload: WorkerJobPayload) {
function getDataFeedSlice (line 94) | async function getDataFeedSlice(
type WorkerMessage (line 140) | type WorkerMessage = {
type WorkerJobPayload (line 145) | type WorkerJobPayload = {
type WorkerSignal (line 158) | const enum WorkerSignal {
FILE: test/binarysplit.test.ts
function collectLines (line 4) | async function collectLines(chunks: Buffer[]) {
FILE: test/downloaddatasets.test.ts
constant LIVE_DATASET (line 8) | const LIVE_DATASET: Parameters<typeof downloadDatasets>[0] = {
function createTempDir (line 16) | function createTempDir() {
FILE: test/httpclient.test.ts
function createFetchMock (line 4) | function createFetchMock(...responses: Response[]) {
FILE: test/mappers.test.ts
method map (line 124) | map(message: any, localTimestamp: Date) {
Copy disabled (too large)
Download .json
Condensed preview — 141 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (10,251K chars).
[
{
"path": ".github/workflows/ci.yaml",
"chars": 820,
"preview": "name: CI\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n\njobs:\n ci:\n name: C"
},
{
"path": ".github/workflows/npm_audit.yaml",
"chars": 1866,
"preview": "name: Full NPM Audit\n\non:\n schedule:\n - cron: '25 3 * * *'\n workflow_dispatch:\n\npermissions:\n contents: read\n\njobs"
},
{
"path": ".github/workflows/publish.yaml",
"chars": 1302,
"preview": "name: Publish New Release To NPM\n\non:\n release:\n # This specifies that the build will be triggered when we publish a"
},
{
"path": ".gitignore",
"chars": 77,
"preview": "node_modules\n/dist\n/*.log\n.tardis-cache\n*.tsbuildinfo\ncache\nbench/\n.DS_Store\n"
},
{
"path": ".npmrc",
"chars": 33,
"preview": "min-release-age=1\nallow-git=none\n"
},
{
"path": ".prettierignore",
"chars": 45,
"preview": "package.json\npackage-lock.json\nyarn.lock\ndist"
},
{
"path": ".prettierrc",
"chars": 112,
"preview": "{\n \"printWidth\": 140,\n \"semi\": false,\n \"singleQuote\": true,\n \"trailingComma\": \"none\",\n \"endOfLine\": \"lf\"\n}\n"
},
{
"path": "ADD_NEW_EXCHANGE.md",
"chars": 1821,
"preview": "# Adding a New Exchange\n\n## Overview\n\nAdding an exchange to tardis-node requires three things: mappers (transform raw ex"
},
{
"path": "AGENTS.md",
"chars": 1403,
"preview": "# tardis-node\n\nPublic npm package (`tardis-dev`). Provides async iterator API for historical replay and real-time stream"
},
{
"path": "ARCHITECTURE.md",
"chars": 2371,
"preview": "# Architecture\n\ntardis-node provides a unified async iterator API for consuming cryptocurrency market data. Two primary "
},
{
"path": "CLAUDE.md",
"chars": 11,
"preview": "@AGENTS.md\n"
},
{
"path": "LICENSE",
"chars": 16725,
"preview": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\""
},
{
"path": "README.md",
"chars": 8512,
"preview": "# tardis-dev\n\n[](https://www.npmjs.org/package/tardis-dev)\n\n<br/>"
},
{
"path": "example.js",
"chars": 879,
"preview": "import { replayNormalized, streamNormalized, normalizeTrades, compute, computeTradeBars } from 'tardis-dev'\n\nconst histo"
},
{
"path": "package.json",
"chars": 2890,
"preview": "{\n \"name\": \"tardis-dev\",\n \"version\": \"16.0.0\",\n \"engines\": {\n \"node\": \">=25\"\n },\n \"devEngines\": {\n \"runtime\":"
},
{
"path": "src/apikeyaccessinfo.ts",
"chars": 586,
"preview": "import { getJSON } from './handy.ts'\nimport { getOptions } from './options.ts'\nimport { Exchange } from './types.ts'\n\nex"
},
{
"path": "src/binarysplit.ts",
"chars": 1366,
"preview": "import { Transform } from 'stream'\nimport type { TransformCallback } from 'stream'\n\n// Inspired by https://github.com/ma"
},
{
"path": "src/clearcache.ts",
"chars": 1754,
"preview": "import { rmSync } from 'node:fs'\nimport { rm } from 'node:fs/promises'\nimport { debug } from './debug.ts'\nimport { getOp"
},
{
"path": "src/combine.ts",
"chars": 4641,
"preview": "import { PassThrough } from 'stream'\nimport { once } from 'events'\n\ntype NextMessageResultWithIndex = {\n index: number\n"
},
{
"path": "src/computable/booksnapshot.ts",
"chars": 7392,
"preview": "import { decimalPlaces } from '../handy.ts'\nimport { OrderBook, OnLevelRemovedCB } from '../orderbook.ts'\nimport { BookC"
},
{
"path": "src/computable/computable.ts",
"chars": 3109,
"preview": "import { Disconnect, Exchange, NormalizedData } from '../types.ts'\n\nexport type Computable<T extends NormalizedData> = {"
},
{
"path": "src/computable/index.ts",
"chars": 96,
"preview": "export * from './booksnapshot.ts'\nexport * from './computable.ts'\nexport * from './tradebar.ts'\n"
},
{
"path": "src/computable/tradebar.ts",
"chars": 5539,
"preview": "import { BookChange, NormalizedData, Trade, TradeBar, Writeable } from '../types.ts'\nimport { Computable } from './compu"
},
{
"path": "src/consts.ts",
"chars": 13280,
"preview": "export const EXCHANGES = [\n 'bitmex',\n 'deribit',\n 'binance-futures',\n 'binance-delivery',\n 'binance-european-optio"
},
{
"path": "src/debug.ts",
"chars": 64,
"preview": "import dbg from 'debug'\n\nexport const debug = dbg('tardis-dev')\n"
},
{
"path": "src/downloaddatasets.ts",
"chars": 6194,
"preview": "import { existsSync } from 'node:fs'\nimport pMap from 'p-map'\nimport { debug } from './debug.ts'\nimport { DatasetType } "
},
{
"path": "src/exchangedetails.ts",
"chars": 2477,
"preview": "import { getJSON } from './handy.ts'\nimport { getOptions } from './options.ts'\nimport { Exchange, FilterForExchange } fr"
},
{
"path": "src/filter.ts",
"chars": 2050,
"preview": "import { NormalizedData, Disconnect, Trade } from './types.ts'\nimport { CappedSet } from './handy.ts'\n\nexport async func"
},
{
"path": "src/handy.ts",
"chars": 23811,
"preview": "import crypto, { createHash } from 'crypto'\nimport { createWriteStream, mkdirSync, rmSync } from 'node:fs'\nimport { rena"
},
{
"path": "src/index.ts",
"chars": 530,
"preview": "export * from './apikeyaccessinfo.ts'\nexport * from './clearcache.ts'\nexport * from './combine.ts'\nexport * from './comp"
},
{
"path": "src/instrumentinfo.ts",
"chars": 5093,
"preview": "import { getOptions } from './options.ts'\nimport type { SymbolType } from './exchangedetails.ts'\nimport type { Exchange "
},
{
"path": "src/mappers/ascendex.ts",
"chars": 5812,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker, Trade } from '../types"
},
{
"path": "src/mappers/binance.ts",
"chars": 17415,
"preview": "import { debug } from '../debug.ts'\nimport { CircularBuffer, fromMicroSecondsToDate, lowerCaseSymbols } from '../handy.t"
},
{
"path": "src/mappers/binancedex.ts",
"chars": 4663,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, Trade } from '../types.ts'\nimport { Mapp"
},
{
"path": "src/mappers/binanceeuropeanoptions.ts",
"chars": 17315,
"preview": "import { asNumberIfValid, lowerCaseSymbols, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, Optio"
},
{
"path": "src/mappers/bitfinex.ts",
"chars": 13499,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker, Exchange, FilterForExc"
},
{
"path": "src/mappers/bitflyer.ts",
"chars": 4836,
"preview": "import { parseμs, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, Trade } from '../types.ts'\nimpo"
},
{
"path": "src/mappers/bitget.ts",
"chars": 6315,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker, Exchange, Trade } from"
},
{
"path": "src/mappers/bitmex.ts",
"chars": 10732,
"preview": "import { asNumberIfValid, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookPriceLevel, BookTicker, Derivat"
},
{
"path": "src/mappers/bitnomial.ts",
"chars": 3049,
"preview": "import { parseμs, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, Trade } from '../types.ts'\nimport { Mapper "
},
{
"path": "src/mappers/bitstamp.ts",
"chars": 6713,
"preview": "import { lowerCaseSymbols } from '../handy.ts'\nimport { BookChange, Trade } from '../types.ts'\nimport { Mapper } from '."
},
{
"path": "src/mappers/blockchaincom.ts",
"chars": 2610,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, Trade } from '../types.ts'\nimport { Mapper } from '."
},
{
"path": "src/mappers/bybit.ts",
"chars": 26301,
"preview": "import { asNumberIfValid, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker, Excha"
},
{
"path": "src/mappers/bybitspot.ts",
"chars": 3842,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, Exchange, BookTicker, Trade } from '../types.ts'\nimp"
},
{
"path": "src/mappers/coinbase.ts",
"chars": 6550,
"preview": "import { parseμs, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookPriceLevel, BookTicker, Trade } from '."
},
{
"path": "src/mappers/coinbaseinternational.ts",
"chars": 9436,
"preview": "import { addMinutes, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookPriceLevel, BookTicker, DerivativeTi"
},
{
"path": "src/mappers/coinflex.ts",
"chars": 4098,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, DerivativeTicker, Trade } from '../types.ts'\nimport "
},
{
"path": "src/mappers/cryptocom.ts",
"chars": 13112,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, Exchange, BookTicker, Trade, DerivativeTicker } from"
},
{
"path": "src/mappers/cryptofacilities.ts",
"chars": 8656,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker, Liquidation, Trade } f"
},
{
"path": "src/mappers/delta.ts",
"chars": 7698,
"preview": "import { fromMicroSecondsToDate, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker"
},
{
"path": "src/mappers/deribit.ts",
"chars": 11098,
"preview": "import { asNumberIfValid } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker, Liquidation, OptionSumm"
},
{
"path": "src/mappers/dydx.ts",
"chars": 9487,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookPriceLevel, DerivativeTicker, Trade } from '../t"
},
{
"path": "src/mappers/dydxv4.ts",
"chars": 9718,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, DerivativeTicker, Liquidation, Trade } from '../type"
},
{
"path": "src/mappers/ftx.ts",
"chars": 7636,
"preview": "import { asNumberIfValid, parseμs, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTick"
},
{
"path": "src/mappers/gateio.ts",
"chars": 14275,
"preview": "import { debug } from '../debug.ts'\nimport { CircularBuffer, fromMicroSecondsToDate, upperCaseSymbols } from '../handy.t"
},
{
"path": "src/mappers/gateiofutures.ts",
"chars": 8252,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, DerivativeTicker, Exchange, Trade, BookTicker } from"
},
{
"path": "src/mappers/gemini.ts",
"chars": 2326,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, Trade } from '../types.ts'\nimport { Mapper } from '."
},
{
"path": "src/mappers/hitbtc.ts",
"chars": 2654,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, Trade } from '../types.ts'\nimport { Mapper } from '."
},
{
"path": "src/mappers/huobi.ts",
"chars": 22331,
"preview": "import { asNumberIfValid, CircularBuffer, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, Derivat"
},
{
"path": "src/mappers/hyperliquid.ts",
"chars": 6192,
"preview": "import { BookChange, BookTicker, DerivativeTicker, Trade } from '../types.ts'\nimport { Mapper, PendingTickerInfoHelper }"
},
{
"path": "src/mappers/index.ts",
"chars": 29989,
"preview": "import { ONE_SEC_IN_MS } from '../handy.ts'\nimport { BookChange, DerivativeTicker, Liquidation, OptionSummary, BookTicke"
},
{
"path": "src/mappers/kraken.ts",
"chars": 5296,
"preview": "import { asNumberIfValid, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, Trade } from '../types."
},
{
"path": "src/mappers/kucoin.ts",
"chars": 10714,
"preview": "import { debug } from '../debug.ts'\nimport { CircularBuffer, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, "
},
{
"path": "src/mappers/kucoinfutures.ts",
"chars": 15420,
"preview": "import { debug } from '../debug.ts'\nimport { asNumberIfValid, CircularBuffer, upperCaseSymbols } from '../handy.ts'\nimpo"
},
{
"path": "src/mappers/mapper.ts",
"chars": 4653,
"preview": "import { DerivativeTicker, Exchange, FilterForExchange, NormalizedData } from '../types.ts'\n\nexport type Mapper<T extend"
},
{
"path": "src/mappers/okex.ts",
"chars": 35176,
"preview": "import { asNumberIfValid, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker, Excha"
},
{
"path": "src/mappers/okexspreads.ts",
"chars": 4454,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, Trade } from '../types.ts'\nimport { Mapp"
},
{
"path": "src/mappers/phemex.ts",
"chars": 11265,
"preview": "import { Mapper, PendingTickerInfoHelper } from './mapper.ts'\nimport { Trade, BookChange, DerivativeTicker } from '../ty"
},
{
"path": "src/mappers/poloniex.ts",
"chars": 7312,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, Trade } from '../types.ts'\nimport { Mapper } from '."
},
{
"path": "src/mappers/serum.ts",
"chars": 4592,
"preview": "import { asNumberIfValid, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, Exchange, Trade } from "
},
{
"path": "src/mappers/upbit.ts",
"chars": 2628,
"preview": "import { fromMicroSecondsToDate, upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookPriceLevel, Trade } from"
},
{
"path": "src/mappers/woox.ts",
"chars": 10069,
"preview": "import { upperCaseSymbols } from '../handy.ts'\nimport { BookChange, BookTicker, DerivativeTicker, Exchange, FilterForExc"
},
{
"path": "src/options.ts",
"chars": 973,
"preview": "import { createRequire } from 'module'\nimport os from 'os'\nimport path from 'path'\n\nconst require = createRequire(import"
},
{
"path": "src/orderbook.ts",
"chars": 5171,
"preview": "import { RBTree } from 'bintrees'\nimport type { RBTree as RBTreeType } from 'bintrees'\nimport { BookChange, BookPriceLev"
},
{
"path": "src/realtimefeeds/ascendex.ts",
"chars": 1892,
"preview": "import { wait } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed"
},
{
"path": "src/realtimefeeds/binance.ts",
"chars": 18479,
"preview": "import { Writable } from 'stream'\nimport { batch, getJSON, wait } from '../handy.ts'\nimport { Filter } from '../types.ts"
},
{
"path": "src/realtimefeeds/binancedex.ts",
"chars": 1764,
"preview": "import { getJSON } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimef"
},
{
"path": "src/realtimefeeds/binanceeuropeanoptions.ts",
"chars": 4986,
"preview": "import { onlyUnique } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { MultiConnectionRealTimeFeedBase, "
},
{
"path": "src/realtimefeeds/bitfinex.ts",
"chars": 2230,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\nconst TIMESTAMP = 32768\nconst "
},
{
"path": "src/realtimefeeds/bitflyer.ts",
"chars": 1205,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class BitflyerRealTime"
},
{
"path": "src/realtimefeeds/bitget.ts",
"chars": 1505,
"preview": "import { batchObjects } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './real"
},
{
"path": "src/realtimefeeds/bitmex.ts",
"chars": 1110,
"preview": "import { batch } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefee"
},
{
"path": "src/realtimefeeds/bitnomial.ts",
"chars": 1710,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class BitnomialRealTim"
},
{
"path": "src/realtimefeeds/bitstamp.ts",
"chars": 1865,
"preview": "import { getJSON } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimef"
},
{
"path": "src/realtimefeeds/blockchaincom.ts",
"chars": 1213,
"preview": "import { wait } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed"
},
{
"path": "src/realtimefeeds/bybit.ts",
"chars": 4058,
"preview": "import { batch } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase, MultiConnectionRealT"
},
{
"path": "src/realtimefeeds/coinbase.ts",
"chars": 3511,
"preview": "import crypto from 'crypto'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\ne"
},
{
"path": "src/realtimefeeds/coinbaseinternational.ts",
"chars": 2105,
"preview": "import crypto from 'crypto'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\ne"
},
{
"path": "src/realtimefeeds/coinflex.ts",
"chars": 821,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class CoinflexRealTime"
},
{
"path": "src/realtimefeeds/cryptocom.ts",
"chars": 1402,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class CryptoComRealTim"
},
{
"path": "src/realtimefeeds/cryptofacilities.ts",
"chars": 940,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class Cryptofacilities"
},
{
"path": "src/realtimefeeds/delta.ts",
"chars": 937,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class DeltaRealTimeFee"
},
{
"path": "src/realtimefeeds/deribit.ts",
"chars": 2629,
"preview": "import { wait } from '../handy.ts'\nimport { Filter, FilterForExchange } from '../types.ts'\nimport { RealTimeFeedBase } f"
},
{
"path": "src/realtimefeeds/dydx.ts",
"chars": 1118,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class DydxRealTimeFeed"
},
{
"path": "src/realtimefeeds/dydx_v4.ts",
"chars": 1041,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class DydxV4RealTimeFe"
},
{
"path": "src/realtimefeeds/ftx.ts",
"chars": 4089,
"preview": "import { Writable } from 'stream'\n\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase, PoolingClientBase, Mu"
},
{
"path": "src/realtimefeeds/gateio.ts",
"chars": 1382,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class GateIORealTimeFe"
},
{
"path": "src/realtimefeeds/gateiofutures.ts",
"chars": 2868,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase, MultiConnectionRealTimeFeedBase } from './realtimefeed.t"
},
{
"path": "src/realtimefeeds/gemini.ts",
"chars": 947,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class GeminiRealTimeFe"
},
{
"path": "src/realtimefeeds/hitbtc.ts",
"chars": 1935,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class HitBtcRealTimeFe"
},
{
"path": "src/realtimefeeds/huobi.ts",
"chars": 12752,
"preview": "import { unzipSync } from 'zlib'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase, MultiConnectionRealTime"
},
{
"path": "src/realtimefeeds/hyperliquid.ts",
"chars": 1056,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class HyperliquidRealT"
},
{
"path": "src/realtimefeeds/index.ts",
"chars": 5535,
"preview": "import { Exchange, Filter } from '../types.ts'\nimport {\n BinanceFuturesRealTimeFeed,\n BinanceJerseyRealTimeFeed,\n Bin"
},
{
"path": "src/realtimefeeds/kraken.ts",
"chars": 964,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class KrakenRealTimeFe"
},
{
"path": "src/realtimefeeds/kucoin.ts",
"chars": 2377,
"preview": "import { getRandomString, getJSON, postJSON, wait } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { Rea"
},
{
"path": "src/realtimefeeds/kucoinfutures.ts",
"chars": 4242,
"preview": "import { Writable } from 'stream'\nimport { getJSON, getRandomString, postJSON } from '../handy.ts'\nimport { Filter } fro"
},
{
"path": "src/realtimefeeds/mango.ts",
"chars": 163,
"preview": "import { SerumRealTimeFeed } from './serum.ts'\n\nexport class MangoRealTimeFeed extends SerumRealTimeFeed {\n protected w"
},
{
"path": "src/realtimefeeds/okex.ts",
"chars": 5904,
"preview": "import crypto from 'crypto'\nimport { wait } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { MultiConnec"
},
{
"path": "src/realtimefeeds/okexspreads.ts",
"chars": 1669,
"preview": "import crypto from 'crypto'\nimport { wait } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFee"
},
{
"path": "src/realtimefeeds/phemex.ts",
"chars": 1487,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class PhemexRealTimeFe"
},
{
"path": "src/realtimefeeds/poloniex.ts",
"chars": 897,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class PoloniexRealTime"
},
{
"path": "src/realtimefeeds/realtimefeed.ts",
"chars": 13417,
"preview": "import dbg from 'debug'\nimport WebSocket, { createWebSocketStream } from 'ws'\nimport type { ClientRequestArgs } from 'ht"
},
{
"path": "src/realtimefeeds/serum.ts",
"chars": 2028,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class SerumRealTimeFee"
},
{
"path": "src/realtimefeeds/staratlas.ts",
"chars": 175,
"preview": "import { SerumRealTimeFeed } from './serum.ts'\n\nexport class StarAtlasRealTimeFeed extends SerumRealTimeFeed {\n protect"
},
{
"path": "src/realtimefeeds/upbit.ts",
"chars": 882,
"preview": "import { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed.ts'\n\nexport class UpbitRealTimeFee"
},
{
"path": "src/realtimefeeds/woox.ts",
"chars": 1861,
"preview": "import { wait } from '../handy.ts'\nimport { Filter } from '../types.ts'\nimport { RealTimeFeedBase } from './realtimefeed"
},
{
"path": "src/replay.ts",
"chars": 14953,
"preview": "import { createReadStream } from 'node:fs'\nimport { rm } from 'node:fs/promises'\nimport { EventEmitter } from 'events'\ni"
},
{
"path": "src/stream.ts",
"chars": 5283,
"preview": "import { debug } from './debug.ts'\nimport { getFilters, normalizeMessages } from './handy.ts'\nimport { MapperFactory } f"
},
{
"path": "src/types.ts",
"chars": 4299,
"preview": "import { EXCHANGES, EXCHANGE_CHANNELS_INFO } from './consts.ts'\n\nexport type Exchange = (typeof EXCHANGES)[number]\n\nexpo"
},
{
"path": "src/worker.ts",
"chars": 6158,
"preview": "import dbg from 'debug'\nimport { existsSync } from 'node:fs'\nimport pMap from 'p-map'\nimport { isMainThread, parentPort,"
},
{
"path": "test/__snapshots__/combine.test.ts.snap",
"chars": 3460786,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`combine(...asyncIterators) should correctly combine iterables based"
},
{
"path": "test/__snapshots__/compute.test.ts.snap",
"chars": 4109014,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`compute(messages, types) should compute correct trade bars based on"
},
{
"path": "test/__snapshots__/mappers.test.ts.snap",
"chars": 254906,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`map binance-european-options messages 1`] = `\n[\n {\n \"amount\": 0"
},
{
"path": "test/__snapshots__/replay.test.ts.snap",
"chars": 329089,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`replay replays normalized data for each supported exchange: binance"
},
{
"path": "test/binance-futures-split.live.test.ts",
"chars": 2290,
"preview": "import { normalizeBookChanges, normalizeBookTickers, normalizeDerivativeTickers, normalizeTrades, streamNormalized } fro"
},
{
"path": "test/binance-openinterest.live.test.ts",
"chars": 1874,
"preview": "import { normalizeDerivativeTickers, streamNormalized } from '../dist/index.js'\nimport { describeLive } from './live.js'"
},
{
"path": "test/binarysplit.test.ts",
"chars": 1432,
"preview": "import { Readable } from 'stream'\nimport { BinarySplitStream } from '../dist/binarysplit.js'\n\nasync function collectLine"
},
{
"path": "test/combine.test.ts",
"chars": 3160,
"preview": "import { combine, normalizeBookChanges, normalizeTrades, replayNormalized } from '../dist/index.js'\n\ndescribe('combine(."
},
{
"path": "test/compute.test.ts",
"chars": 7679,
"preview": "import {\n BookChange,\n compute,\n computeBookSnapshots,\n computeTradeBars,\n normalizeBookChanges,\n normalizeTrades,"
},
{
"path": "test/downloaddatasets.test.ts",
"chars": 3046,
"preview": "import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport os from 'os'\nimport path f"
},
{
"path": "test/gate-io-futures-decimal.live.test.ts",
"chars": 2188,
"preview": "import { normalizeBookChanges, normalizeBookTickers, normalizeTrades, streamNormalized } from '../dist/index.js'\nimport "
},
{
"path": "test/httpclient.test.ts",
"chars": 3793,
"preview": "import { jest } from '@jest/globals'\nimport { getJSON, postJSON } from '../dist/handy.js'\n\nfunction createFetchMock(...r"
},
{
"path": "test/live.ts",
"chars": 144,
"preview": "export const shouldRunLiveTests = process.env.RUN_LIVE_TESTS === '1'\n\nexport const describeLive = shouldRunLiveTests ? d"
},
{
"path": "test/mappers.test.ts",
"chars": 277329,
"preview": "import {\n Exchange,\n Mapper,\n normalizeBookChanges,\n normalizeDerivativeTickers,\n normalizeTrades,\n normalizeOptio"
},
{
"path": "test/orderbook.test.ts",
"chars": 5216,
"preview": "import { OrderBook } from '../dist/index.js'\n\ndescribe('orderbook', () => {\n test('should update levels', () => {\n c"
},
{
"path": "test/package-exports.test.ts",
"chars": 1799,
"preview": "import { cpSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'\nimport os from 'os'\nimport "
},
{
"path": "test/replay.test.ts",
"chars": 13549,
"preview": "import {\n clearCache,\n Exchange,\n EXCHANGES,\n normalizeBookChanges,\n normalizeDerivativeTickers,\n normalizeTrades,"
},
{
"path": "test/setup.js",
"chars": 278,
"preview": "var origToISOString = Date.prototype.toISOString\nDate.prototype.toISOString = function () {\n const isoTimestamp = origT"
},
{
"path": "test/stream.test.ts",
"chars": 6773,
"preview": "import {\n compute,\n computeBookSnapshots,\n computeTradeBars,\n Exchange,\n EXCHANGES,\n getExchangeDetails,\n normali"
},
{
"path": "test/tsconfig.json",
"chars": 246,
"preview": "{\n \"extends\": \"../tsconfig.json\",\n \"compilerOptions\": {\n \"rootDir\": \"..\",\n \"isolatedModules\": true,\n \"module\""
},
{
"path": "tsconfig.json",
"chars": 643,
"preview": "{\n \"include\": [\"src\"],\n \"compilerOptions\": {\n \"rootDir\": \"src\",\n \"allowSyntheticDefaultImports\": true,\n \"reso"
}
]
About this extraction
This page contains the full source code of the tardis-dev/tardis-node GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 141 files (8.7 MB), approximately 2.3M tokens, and a symbol index with 1582 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.