├── .eslintrc.json ├── .github ├── FUNDING.yml ├── workflow-settings.json └── workflows │ ├── npmpublish.yml │ ├── templates-readme.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── README.md ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── classes │ ├── OrderBook.html │ └── OrderBooksStore.html ├── functions │ └── OrderBookLevel.html ├── index.html ├── interfaces │ └── OrderBookOptions.html ├── modules.html └── types │ └── OrderBookLevelState.html ├── index.js ├── jsconfig.js ├── package-lock.json ├── package.json ├── samples ├── README.md ├── binance.ts ├── bybit.ts ├── package-lock.json ├── package.json └── slippage-test.ts ├── src ├── OrderBook.ts ├── OrderBookLevel.ts ├── OrderBooksStore.ts └── index.ts ├── tsconfig.json ├── tsconfig.samples.json └── typedoc.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "plugins": ["@typescript-eslint"], 4 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 5 | "parser": "@typescript-eslint/parser", 6 | "parserOptions": { 7 | "files": ["*.ts", "*.tsx"], // Your TypeScript files extension 8 | "project": ["tsconfig.json", "tsconfig.samples.json"], 9 | "projectFolderIgnoreList": ["/node_modules/", "/dist/", "/scripts/"] 10 | }, 11 | "rules": { 12 | // Enabled: typescript 13 | "no-param-reassign": ["error"], 14 | "@typescript-eslint/await-thenable": "error", 15 | "@typescript-eslint/no-unused-vars": "off", 16 | "@typescript-eslint/no-unused-expressions": "error", 17 | "@typescript-eslint/no-var-requires": "error", 18 | "@typescript-eslint/prefer-namespace-keyword": "error", 19 | "@typescript-eslint/no-floating-promises": "off", 20 | "@typescript-eslint/no-inferrable-types": "off", 21 | "@typescript-eslint/promise-function-async": "off", 22 | "@typescript-eslint/no-use-before-define": "off", 23 | "@typescript-eslint/no-non-null-assertion": "off", 24 | "@typescript-eslint/ban-types": "off", 25 | "@typescript-eslint/explicit-module-boundary-types": "off", 26 | 27 | // Enabled: eslint 28 | "default-case": "error", 29 | "no-return-await": "error", 30 | "eqeqeq": ["error", "smart"], 31 | "id-denylist": ["error", "any", "Undefined", "undefined"], 32 | "id-match": "error", 33 | "no-caller": "error", 34 | "no-cond-assign": "error", 35 | "no-invalid-this": "error", 36 | "no-debugger": "error", 37 | "no-eval": "error", 38 | "no-new-wrappers": "error", 39 | "no-redeclare": "error", 40 | "prefer-const": "error", 41 | "prefer-object-spread": "error", 42 | "radix": "error", 43 | "sort-imports": [ 44 | "error", 45 | { 46 | "ignoreCase": true, 47 | "ignoreDeclarationSort": true 48 | } 49 | ], 50 | 51 | // Disabled: eslint 52 | "guard-for-in": "off", 53 | "no-empty": "off", 54 | "no-magic-numbers": "off", 55 | "no-null/no-null": "off", 56 | "no-shadow": "off", 57 | "no-trailing-spaces": "off", 58 | "no-underscore-dangle": "off", 59 | "no-var": "off", 60 | "no-unused-vars": "off", 61 | "no-case-declarations": "off", 62 | "no-constant-condition": "off", 63 | "no-dupe-class-members": "off" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [tiagosiebler] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflow-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "EXCLUDE_MESSAGES": [ 3 | "update package version", 4 | "update packages", 5 | "update wp version", 6 | "trigger workflow", 7 | "update TOC" 8 | ], 9 | "PROJECT": "Backlog", 10 | "ISSUE_COLUMN": "To do", 11 | "PR_COLUMN": "In progress", 12 | "PR_BODY_TITLE": "## Changes", 13 | "TOC_FOLDING": "1", 14 | "TOC_MAX_HEADER_LEVEL": "3", 15 | "TOC_TITLE": "Summary" 16 | } -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Publish to NPM 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | publish-npm: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Package Version Updated 16 | uses: MontyD/package-json-updated-action@1.0.1 17 | id: version-updated 18 | with: 19 | path: package.json 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - uses: actions/checkout@v4 24 | if: steps.version-updated.outputs.has-updated 25 | 26 | - uses: actions/setup-node@v4 27 | if: steps.version-updated.outputs.has-updated 28 | with: 29 | node-version: 20.9.0 30 | registry-url: https://registry.npmjs.org/ 31 | 32 | - run: npm ci --ignore-scripts 33 | if: steps.version-updated.outputs.has-updated 34 | - run: npm run clean 35 | if: steps.version-updated.outputs.has-updated 36 | - run: npm run build 37 | if: steps.version-updated.outputs.has-updated 38 | - run: npm publish --ignore-scripts 39 | if: steps.version-updated.outputs.has-updated 40 | env: 41 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 42 | -------------------------------------------------------------------------------- /.github/workflows/templates-readme.yml: -------------------------------------------------------------------------------- 1 | name: Update README.md templates 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write # Grant write permissions to the contents 8 | 9 | env: 10 | FILE_NAME: README.md 11 | 12 | jobs: 13 | update-readme: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version-file: '.nvmrc' 23 | registry-url: https://registry.npmjs.org/ 24 | cache: 'npm' 25 | 26 | - name: Install 27 | run: npm ci --ignore-scripts 28 | 29 | - name: Get the current date and time 30 | id: datetime 31 | run: echo "BRANCH_NAME=$(date +'actions_templates_%Y_%m_%d_%H%M%S')" >> $GITHUB_ENV 32 | 33 | - name: Create update-template.sh script 34 | run: | 35 | cat << 'EOF' > update-template.sh 36 | #!/bin/bash 37 | 38 | TEMPLATE_VALUE=$(curl -s $TEMPLATE_URL) 39 | 40 | perl -0777 -i -pe " 41 | my \$tag = quotemeta(''); 42 | my \$end_tag = quotemeta(''); 43 | my \$replacement = '${TEMPLATE_VALUE}'; 44 | 45 | # Match the tag, then any amount of whitespace (including newlines), then the replacement, then any amount of whitespace, then the end tag. 46 | s/(\$tag)(\s*)(.*?)(\s*)(\$end_tag)/\$1\n\$replacement\n\$5/s; 47 | " "$FILE_NAME" 48 | 49 | EOF 50 | chmod +x update-template.sh 51 | cat update-template.sh 52 | 53 | - name: Fetch and update RELATED PROJECTS template 54 | run: ./update-template.sh 55 | env: 56 | TEMPLATE_URL: https://raw.githubusercontent.com/wiki/tiagosiebler/awesome-crypto-examples/Related-projects.md 57 | TEMPLATE_TAG: related_projects 58 | 59 | - name: Fetch and update CONTRIBUTIONS template 60 | run: ./update-template.sh 61 | env: 62 | TEMPLATE_URL: https://raw.githubusercontent.com/wiki/tiagosiebler/awesome-crypto-examples/Contributions-%26-Thanks.md 63 | TEMPLATE_TAG: contributions 64 | 65 | - name: Fetch and update STAR HISTORY template 66 | run: ./update-template.sh 67 | env: 68 | TEMPLATE_URL: https://raw.githubusercontent.com/wiki/tiagosiebler/awesome-crypto-examples/Star-History.md 69 | TEMPLATE_TAG: star_history 70 | 71 | - name: Check for changes before running linter 72 | run: git diff 73 | 74 | - name: Check for changes | PR URL HERE 75 | id: commitIfChanged 76 | run: | 77 | npx prettier -w README.md 78 | if git diff --quiet; then 79 | echo "No changes to commit" 80 | exit 0 81 | fi 82 | git config --global user.name 'github-actions[bot]' 83 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 84 | git checkout -b ${{ env.BRANCH_NAME }} 85 | git add $FILE_NAME 86 | git commit -m 'chore(): ${{ env.FILE_NAME }} template sections' 87 | git push origin ${{ env.BRANCH_NAME }} 88 | env: 89 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 90 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Build & Test" 2 | 3 | on: [push] 4 | 5 | # on: 6 | # # pull_request: 7 | # # branches: 8 | # # - "master" 9 | # push: 10 | # branches: 11 | 12 | jobs: 13 | build: 14 | name: "Build & Test" 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: "Checkout source code" 19 | uses: actions/checkout@v3 20 | 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: 18 24 | cache: 'npm' 25 | 26 | - name: Install 27 | run: npm ci --ignore-scripts 28 | 29 | - name: Build 30 | run: npm run build 31 | 32 | # - name: Test 33 | # run: npm run test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | samples -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OrderBooks Store [![npm version](https://img.shields.io/npm/v/orderbooks.svg)][1] [![npm size](https://img.shields.io/bundlephobia/min/orderbooks.svg)][1] [![npm downloads](https://img.shields.io/npm/dt/orderbooks.svg)][1] 2 | 3 | [![CodeFactor](https://www.codefactor.io/repository/github/tiagosiebler/orderbooks/badge)](https://www.codefactor.io/repository/github/tiagosiebler/orderbooks) 4 | 5 | [1]: https://www.npmjs.com/package/orderbooks 6 | 7 | A minimal set of utilities for handling orderbook snapshots and delta updates, with bybit examples. 8 | 9 | ## Issues & Discussion 10 | 11 | - Issues? Check the [issues tab](https://github.com/tiagosiebler/orderbooks/issues). 12 | - Discuss & collaborate with other node devs? Join our [Node.js Algo Traders](https://t.me/nodetraders) engineering community on telegram. 13 | 14 | ## Documentation 15 | 16 | - [TSDoc Documentation (generated using typedoc via npm module)](https://tsdocs.dev/docs/orderbooks) 17 | 18 | 19 | 20 | ## Related projects 21 | 22 | Check out my related JavaScript/TypeScript/Node.js projects: 23 | 24 | - Try my REST API & WebSocket SDKs: 25 | - [Bybit-api Node.js SDK](https://www.npmjs.com/package/bybit-api) 26 | - [Okx-api Node.js SDK](https://www.npmjs.com/package/okx-api) 27 | - [Binance Node.js SDK](https://www.npmjs.com/package/binance) 28 | - [Gateio-api Node.js SDK](https://www.npmjs.com/package/gateio-api) 29 | - [Bitget-api Node.js SDK](https://www.npmjs.com/package/bitget-api) 30 | - [Kucoin-api Node.js SDK](https://www.npmjs.com/package/kucoin-api) 31 | - [Coinbase-api Node.js SDK](https://www.npmjs.com/package/coinbase-api) 32 | - [Bitmart-api Node.js SDK](https://www.npmjs.com/package/bitmart-api) 33 | - Try my misc utilities: 34 | - [OrderBooks Node.js](https://www.npmjs.com/package/orderbooks) 35 | - [Crypto Exchange Account State Cache](https://www.npmjs.com/package/accountstate) 36 | - Check out my examples: 37 | - [awesome-crypto-examples Node.js](https://github.com/tiagosiebler/awesome-crypto-examples) 38 | 39 | 40 | 41 | 42 | ## Project Contributions 43 | 44 | Contributions are very welcome, I will review any incoming pull requests. See the issues tab for todo items. 45 | 46 | ## Features 47 | 48 | - Handle snapshot and delta orderbook events. 49 | - Track multiple symbol orderbooks. 50 | - Easily access best bid/ask prices. 51 | - Conveniently access the difference between the best bid and ask prices, with the spread represented in basis point units. 52 | - Easily keep orderbook depth trimmed to max depth. 53 | - Tiny module with 0 external dependencies. 54 | 55 | ## Installation 56 | 57 | ``` 58 | npm install -save orderbooks 59 | ``` 60 | 61 | ## Usage 62 | 63 | ### Tracking 64 | 65 | - Import books store & level 66 | 67 | ```javascript 68 | const { OrderBooksStore, OrderBookLevel } = require('orderbooks'); 69 | ``` 70 | 71 | - Create instance of orderbooks store, to store multiple order books for a broker 72 | 73 | ```javascript 74 | // all options are optional 75 | const options = { 76 | // output traces on any events sent handled by book 77 | traceLog: true, 78 | 79 | // check current timestamp > last timestamp, else deny processing event 80 | checkTimestamps: false, 81 | 82 | // max size of orderbook (e.g 50 == 25 bids & 25 asks). Defaults to 250. 83 | maxDepth: 50, 84 | }; 85 | 86 | const OrderBooks = new OrderBooksStore(options); 87 | ``` 88 | 89 | - Feed snapshot and delta updates into OrderBooks.handle() methods. 90 | 91 | ## Examples 92 | 93 | See the [./samples/](./samples/) folder for more. 94 | 95 | ### Real Example - Binance 96 | 97 | See [./samples/binance.ts](./samples/binance.ts) 98 | 99 | ### Real Example - Bybit 100 | 101 | - Import modules 102 | - Prepare OrderBooks store instance 103 | - Connect to OrderBooks websockets 104 | - Map event properties to expected key:value pairs 105 | - Feed mapped snapshot and delta events into OrderBooks.handle() methods 106 | 107 | See [./samples/bybit.ts](./samples/bybit.ts) 108 | 109 | Example output with `print()` calls to output book state to console: 110 | 111 | ``` 112 | ---------- BTCUSD ask:bid 9240:9239.5 & spread: 0.01% 113 | ┌─────────┬──────────┬────────┬────────┬─────────┐ 114 | │ (index) │ symbol │ price │ side │ qty │ 115 | ├─────────┼──────────┼────────┼────────┼─────────┤ 116 | │ 0 │ 'BTCUSD' │ 9252 │ 'Sell' │ 132623 │ 117 | │ 1 │ 'BTCUSD' │ 9251.5 │ 'Sell' │ 82221 │ 118 | │ 2 │ 'BTCUSD' │ 9251 │ 'Sell' │ 34974 │ 119 | │ 3 │ 'BTCUSD' │ 9250.5 │ 'Sell' │ 12842 │ 120 | │ 4 │ 'BTCUSD' │ 9250 │ 'Sell' │ 550687 │ 121 | │ 5 │ 'BTCUSD' │ 9249.5 │ 'Sell' │ 63371 │ 122 | │ 6 │ 'BTCUSD' │ 9249 │ 'Sell' │ 200127 │ 123 | │ 7 │ 'BTCUSD' │ 9248.5 │ 'Sell' │ 129099 │ 124 | │ 8 │ 'BTCUSD' │ 9248 │ 'Sell' │ 209061 │ 125 | │ 9 │ 'BTCUSD' │ 9247.5 │ 'Sell' │ 30722 │ 126 | │ 10 │ 'BTCUSD' │ 9247 │ 'Sell' │ 165469 │ 127 | │ 11 │ 'BTCUSD' │ 9246.5 │ 'Sell' │ 97780 │ 128 | │ 12 │ 'BTCUSD' │ 9246 │ 'Sell' │ 95342 │ 129 | │ 13 │ 'BTCUSD' │ 9245.5 │ 'Sell' │ 41319 │ 130 | │ 14 │ 'BTCUSD' │ 9245 │ 'Sell' │ 227242 │ 131 | │ 15 │ 'BTCUSD' │ 9244.5 │ 'Sell' │ 167586 │ 132 | │ 16 │ 'BTCUSD' │ 9244 │ 'Sell' │ 237029 │ 133 | │ 17 │ 'BTCUSD' │ 9243.5 │ 'Sell' │ 103426 │ 134 | │ 18 │ 'BTCUSD' │ 9243 │ 'Sell' │ 126357 │ 135 | │ 19 │ 'BTCUSD' │ 9242.5 │ 'Sell' │ 165034 │ 136 | │ 20 │ 'BTCUSD' │ 9242 │ 'Sell' │ 264286 │ 137 | │ 21 │ 'BTCUSD' │ 9241.5 │ 'Sell' │ 261200 │ 138 | │ 22 │ 'BTCUSD' │ 9241 │ 'Sell' │ 233533 │ 139 | │ 23 │ 'BTCUSD' │ 9240.5 │ 'Sell' │ 399512 │ 140 | │ 24 │ 'BTCUSD' │ 9240 │ 'Sell' │ 1397987 │ 141 | │ 25 │ 'BTCUSD' │ 9239.5 │ 'Buy' │ 1132 │ 142 | │ 26 │ 'BTCUSD' │ 9239 │ 'Buy' │ 234214 │ 143 | │ 27 │ 'BTCUSD' │ 9238.5 │ 'Buy' │ 58320 │ 144 | │ 28 │ 'BTCUSD' │ 9238 │ 'Buy' │ 17094 │ 145 | │ 29 │ 'BTCUSD' │ 9237.5 │ 'Buy' │ 50980 │ 146 | │ 30 │ 'BTCUSD' │ 9237 │ 'Buy' │ 13449 │ 147 | │ 31 │ 'BTCUSD' │ 9236.5 │ 'Buy' │ 2608 │ 148 | │ 32 │ 'BTCUSD' │ 9236 │ 'Buy' │ 53742 │ 149 | │ 33 │ 'BTCUSD' │ 9235.5 │ 'Buy' │ 106681 │ 150 | │ 34 │ 'BTCUSD' │ 9235 │ 'Buy' │ 48653 │ 151 | │ 35 │ 'BTCUSD' │ 9234.5 │ 'Buy' │ 76188 │ 152 | │ 36 │ 'BTCUSD' │ 9234 │ 'Buy' │ 215664 │ 153 | │ 37 │ 'BTCUSD' │ 9233.5 │ 'Buy' │ 169265 │ 154 | │ 38 │ 'BTCUSD' │ 9233 │ 'Buy' │ 30296 │ 155 | │ 39 │ 'BTCUSD' │ 9232.5 │ 'Buy' │ 196676 │ 156 | │ 40 │ 'BTCUSD' │ 9232 │ 'Buy' │ 82840 │ 157 | │ 41 │ 'BTCUSD' │ 9231.5 │ 'Buy' │ 105854 │ 158 | │ 42 │ 'BTCUSD' │ 9231 │ 'Buy' │ 1671 │ 159 | │ 43 │ 'BTCUSD' │ 9230.5 │ 'Buy' │ 25909 │ 160 | │ 44 │ 'BTCUSD' │ 9230 │ 'Buy' │ 146198 │ 161 | │ 45 │ 'BTCUSD' │ 9229.5 │ 'Buy' │ 95941 │ 162 | │ 46 │ 'BTCUSD' │ 9229 │ 'Buy' │ 61212 │ 163 | │ 47 │ 'BTCUSD' │ 9228.5 │ 'Buy' │ 76966 │ 164 | │ 48 │ 'BTCUSD' │ 9228 │ 'Buy' │ 93996 │ 165 | │ 49 │ 'BTCUSD' │ 9227.5 │ 'Buy' │ 44058 │ 166 | └─────────┴──────────┴────────┴────────┴─────────┘ 167 | ``` 168 | 169 | ## Accessing State 170 | 171 | Access orderbook state using the OrderBooksStore. 172 | 173 | ```javascript 174 | const btcOrderBook = OrderBooks.getBook('BTCUSD'); 175 | 176 | // Get an array dump of the current orderbook state (similar to what you see on exchange websites) 177 | const btcOrderBookState = btcOrderBook.getBookState(); 178 | console.log('Current book state: ', JSON.stringify(btcOrderBookState)); 179 | 180 | const bestBid = btcOrderBook.getBestBid(); 181 | // bestBid = 9239.5 182 | 183 | const secondBestBid = btcOrderBook.getBestBid(1); 184 | // secondBestBid = 9239 185 | 186 | const bestAsk = btcOrderBook.getBestAsk(); 187 | // bestAsk = 9040 188 | 189 | const secondBestAsk = btcOrderBook.getBestAsk(1); 190 | // secondBestAsk = 9040.5 191 | 192 | const currentSpread = btcORderBook.getSpreadPercent(); 193 | // currentSpread = 0.01 194 | ``` 195 | 196 | ## Utility Methods 197 | 198 | The following ultity methods are exposed for each book: 199 | 200 | ```javascript 201 | const btcOrderBook = OrderBooks.getBook('BTCUSD'); 202 | 203 | // console.log current orderbook state 204 | btcOrderBook.print(); 205 | 206 | // clear current orderbook to free memory 207 | btcOrderBook.reset(); 208 | ``` 209 | 210 | 211 | 212 | ## Contributions & Thanks 213 | 214 | Have my projects helped you? Share the love, there are many ways you can show your thanks: 215 | 216 | - Star & share my projects. 217 | - Are my projects useful? Sponsor me on Github and support my effort to maintain & improve them: https://github.com/sponsors/tiagosiebler 218 | - Have an interesting project? Get in touch & invite me to it. 219 | - Or buy me all the coffee: 220 | - ETH(ERC20): `0xA3Bda8BecaB4DCdA539Dc16F9C54a592553Be06C` 221 | 222 | 223 | 224 | 225 | 226 | ## Star History 227 | 228 | [![Star History Chart](https://api.star-history.com/svg?repos=tiagosiebler/bybit-api,tiagosiebler/okx-api,tiagosiebler/binance,tiagosiebler/bitget-api,tiagosiebler/bitmart-api,tiagosiebler/gateio-api,tiagosiebler/kucoin-api,tiagosiebler/coinbase-api,tiagosiebler/orderbooks,tiagosiebler/accountstate,tiagosiebler/awesome-crypto-examples&type=Date)](https://star-history.com/#tiagosiebler/bybit-api&tiagosiebler/okx-api&tiagosiebler/binance&tiagosiebler/bitget-api&tiagosiebler/bitmart-api&tiagosiebler/gateio-api&tiagosiebler/kucoin-api&tiagosiebler/coinbase-api&tiagosiebler/orderbooks&tiagosiebler/accountstate&tiagosiebler/awesome-crypto-examples&Date) 229 | 230 | 231 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #001080; 3 | --dark-hl-0: #9CDCFE; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #0000FF; 7 | --dark-hl-2: #569CD6; 8 | --light-hl-3: #0070C1; 9 | --dark-hl-3: #4FC1FF; 10 | --light-hl-4: #795E26; 11 | --dark-hl-4: #DCDCAA; 12 | --light-hl-5: #A31515; 13 | --dark-hl-5: #CE9178; 14 | --light-hl-6: #008000; 15 | --dark-hl-6: #6A9955; 16 | --light-hl-7: #098658; 17 | --dark-hl-7: #B5CEA8; 18 | --light-hl-8: #000000; 19 | --dark-hl-8: #C8C8C8; 20 | --light-code-background: #FFFFFF; 21 | --dark-code-background: #1E1E1E; 22 | } 23 | 24 | @media (prefers-color-scheme: light) { :root { 25 | --hl-0: var(--light-hl-0); 26 | --hl-1: var(--light-hl-1); 27 | --hl-2: var(--light-hl-2); 28 | --hl-3: var(--light-hl-3); 29 | --hl-4: var(--light-hl-4); 30 | --hl-5: var(--light-hl-5); 31 | --hl-6: var(--light-hl-6); 32 | --hl-7: var(--light-hl-7); 33 | --hl-8: var(--light-hl-8); 34 | --code-background: var(--light-code-background); 35 | } } 36 | 37 | @media (prefers-color-scheme: dark) { :root { 38 | --hl-0: var(--dark-hl-0); 39 | --hl-1: var(--dark-hl-1); 40 | --hl-2: var(--dark-hl-2); 41 | --hl-3: var(--dark-hl-3); 42 | --hl-4: var(--dark-hl-4); 43 | --hl-5: var(--dark-hl-5); 44 | --hl-6: var(--dark-hl-6); 45 | --hl-7: var(--dark-hl-7); 46 | --hl-8: var(--dark-hl-8); 47 | --code-background: var(--dark-code-background); 48 | } } 49 | 50 | :root[data-theme='light'] { 51 | --hl-0: var(--light-hl-0); 52 | --hl-1: var(--light-hl-1); 53 | --hl-2: var(--light-hl-2); 54 | --hl-3: var(--light-hl-3); 55 | --hl-4: var(--light-hl-4); 56 | --hl-5: var(--light-hl-5); 57 | --hl-6: var(--light-hl-6); 58 | --hl-7: var(--light-hl-7); 59 | --hl-8: var(--light-hl-8); 60 | --code-background: var(--light-code-background); 61 | } 62 | 63 | :root[data-theme='dark'] { 64 | --hl-0: var(--dark-hl-0); 65 | --hl-1: var(--dark-hl-1); 66 | --hl-2: var(--dark-hl-2); 67 | --hl-3: var(--dark-hl-3); 68 | --hl-4: var(--dark-hl-4); 69 | --hl-5: var(--dark-hl-5); 70 | --hl-6: var(--dark-hl-6); 71 | --hl-7: var(--dark-hl-7); 72 | --hl-8: var(--dark-hl-8); 73 | --code-background: var(--dark-code-background); 74 | } 75 | 76 | .hl-0 { color: var(--hl-0); } 77 | .hl-1 { color: var(--hl-1); } 78 | .hl-2 { color: var(--hl-2); } 79 | .hl-3 { color: var(--hl-3); } 80 | .hl-4 { color: var(--hl-4); } 81 | .hl-5 { color: var(--hl-5); } 82 | .hl-6 { color: var(--hl-6); } 83 | .hl-7 { color: var(--hl-7); } 84 | .hl-8 { color: var(--hl-8); } 85 | pre, code { background: var(--code-background); } 86 | -------------------------------------------------------------------------------- /docs/assets/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | "use strict";(()=>{var Pe=Object.create;var ne=Object.defineProperty;var Ie=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var _e=Object.getPrototypeOf,Re=Object.prototype.hasOwnProperty;var Me=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var Fe=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Oe(e))!Re.call(t,i)&&i!==n&&ne(t,i,{get:()=>e[i],enumerable:!(r=Ie(e,i))||r.enumerable});return t};var De=(t,e,n)=>(n=t!=null?Pe(_e(t)):{},Fe(e||!t||!t.__esModule?ne(n,"default",{value:t,enumerable:!0}):n,t));var ae=Me((se,oe)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,u],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. 3 | `,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(n+=r[u+1]*i[d+1],u+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}s.str.length==1&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),v=s.str.charAt(1),f;v in s.node.edges?f=s.node.edges[v]:(f=new t.TokenSet,s.node.edges[v]=f),s.str.length==1&&(f.final=!0),i.push({node:f,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof se=="object"?oe.exports=n():e.lunr=n()}(this,function(){return t})})()});var re=[];function G(t,e){re.push({selector:e,constructor:t})}var U=class{constructor(){this.alwaysVisibleMember=null;this.createComponents(document.body),this.ensureActivePageVisible(),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible())}createComponents(e){re.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r}}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(n&&n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let r=document.createElement("p");r.classList.add("warning"),r.textContent="This member is normally hidden due to your filter settings.",n.prepend(r)}}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent="Copied!",e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent="Copy"},100)},1e3)})})}};var ie=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var de=De(ae());async function le(t,e){if(!window.searchData)return;let n=await fetch(window.searchData),r=new Blob([await n.arrayBuffer()]).stream().pipeThrough(new DecompressionStream("gzip")),i=await new Response(r).json();t.data=i,t.index=de.Index.load(i.index),e.classList.remove("loading"),e.classList.add("ready")}function he(){let t=document.getElementById("tsd-search");if(!t)return;let e={base:t.dataset.base+"/"},n=document.getElementById("tsd-search-script");t.classList.add("loading"),n&&(n.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),n.addEventListener("load",()=>{le(e,t)}),le(e,t));let r=document.querySelector("#tsd-search input"),i=document.querySelector("#tsd-search .results");if(!r||!i)throw new Error("The input field or the result list wrapper was not found");let s=!1;i.addEventListener("mousedown",()=>s=!0),i.addEventListener("mouseup",()=>{s=!1,t.classList.remove("has-focus")}),r.addEventListener("focus",()=>t.classList.add("has-focus")),r.addEventListener("blur",()=>{s||(s=!1,t.classList.remove("has-focus"))}),Ae(t,i,r,e)}function Ae(t,e,n,r){n.addEventListener("input",ie(()=>{Ne(t,e,n,r)},200));let i=!1;n.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Ve(e,n):s.key=="Escape"?n.blur():s.key=="ArrowUp"?ue(e,-1):s.key==="ArrowDown"?ue(e,1):i=!1}),n.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!n.matches(":focus")&&s.key==="/"&&(n.focus(),s.preventDefault())})}function Ne(t,e,n,r){if(!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s;if(i){let o=i.split(" ").map(a=>a.length?`*${a}*`:"").join(" ");s=r.index.search(o)}else s=[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o`,d=ce(l.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(d+=` (score: ${s[o].score.toFixed(2)})`),l.parent&&(d=` 4 | ${ce(l.parent,i)}.${d}`);let v=document.createElement("li");v.classList.value=l.classes??"";let f=document.createElement("a");f.href=r.base+l.url,f.innerHTML=u+d,v.append(f),e.appendChild(v)}}function ue(t,e){let n=t.querySelector(".current");if(!n)n=t.querySelector(e==1?"li:first-child":"li:last-child"),n&&n.classList.add("current");else{let r=n;if(e===1)do r=r.nextElementSibling??void 0;while(r instanceof HTMLElement&&r.offsetParent==null);else do r=r.previousElementSibling??void 0;while(r instanceof HTMLElement&&r.offsetParent==null);r&&(n.classList.remove("current"),r.classList.add("current"))}}function Ve(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),e.blur()}}function ce(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(K(t.substring(s,o)),`${K(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(K(t.substring(s))),i.join("")}var Be={"&":"&","<":"<",">":">","'":"'",'"':"""};function K(t){return t.replace(/[&<>"'"]/g,e=>Be[e])}var C=class{constructor(e){this.el=e.el,this.app=e.app}};var F="mousedown",pe="mousemove",B="mouseup",J={x:0,y:0},fe=!1,ee=!1,He=!1,D=!1,me=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(me?"is-mobile":"not-mobile");me&&"ontouchstart"in document.documentElement&&(He=!0,F="touchstart",pe="touchmove",B="touchend");document.addEventListener(F,t=>{ee=!0,D=!1;let e=F=="touchstart"?t.targetTouches[0]:t;J.y=e.pageY||0,J.x=e.pageX||0});document.addEventListener(pe,t=>{if(ee&&!D){let e=F=="touchstart"?t.targetTouches[0]:t,n=J.x-(e.pageX||0),r=J.y-(e.pageY||0);D=Math.sqrt(n*n+r*r)>10}});document.addEventListener(B,()=>{ee=!1});document.addEventListener("click",t=>{fe&&(t.preventDefault(),t.stopImmediatePropagation(),fe=!1)});var X=class extends C{constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener(B,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(F,n=>this.onDocumentPointerDown(n)),document.addEventListener(B,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){D||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!D&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var te;try{te=localStorage}catch{te={getItem(){return null},setItem(){}}}var Q=te;var ve=document.head.appendChild(document.createElement("style"));ve.dataset.for="filters";var Y=class extends C{constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),ve.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } 5 | `,this.handleValueChange()}fromLocalStorage(){let e=Q.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){Q.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),document.querySelectorAll(".tsd-index-section").forEach(e=>{e.style.display="block";let n=Array.from(e.querySelectorAll(".tsd-index-link")).every(r=>r.offsetParent==null);e.style.display=n?"none":"block"})}};var Z=class extends C{constructor(e){super(e),this.summary=this.el.querySelector(".tsd-accordion-summary"),this.icon=this.summary.querySelector("svg"),this.key=`tsd-accordion-${this.summary.dataset.key??this.summary.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`;let n=Q.getItem(this.key);this.el.open=n?n==="true":this.el.open,this.el.addEventListener("toggle",()=>this.update());let r=this.summary.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)}),this.update()}update(){this.icon.style.transform=`rotate(${this.el.open?0:-90}deg)`,Q.setItem(this.key,this.el.open.toString())}};function ge(t){let e=Q.getItem("tsd-theme")||"os";t.value=e,ye(e),t.addEventListener("change",()=>{Q.setItem("tsd-theme",t.value),ye(t.value)})}function ye(t){document.documentElement.dataset.theme=t}var Le;function be(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",xe),xe())}async function xe(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let n=await(await fetch(window.navigationData)).arrayBuffer(),r=new Blob([n]).stream().pipeThrough(new DecompressionStream("gzip")),i=await new Response(r).json();Le=t.dataset.base+"/",t.innerHTML="";for(let s of i)we(s,t,[]);window.app.createComponents(t),window.app.ensureActivePageVisible()}function we(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-index-accordion`:"tsd-index-accordion",s.dataset.key=i.join("$");let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.innerHTML='',Ee(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let l=a.appendChild(document.createElement("ul"));l.className="tsd-nested-navigation";for(let u of t.children)we(u,l,i)}else Ee(t,r,t.class)}function Ee(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));r.href=Le+t.path,n&&(r.className=n),location.href===r.href&&r.classList.add("current"),t.kind&&(r.innerHTML=``),r.appendChild(document.createElement("span")).textContent=t.text}else e.appendChild(document.createElement("span")).textContent=t.text}G(X,"a[data-toggle]");G(Z,".tsd-index-accordion");G(Y,".tsd-filter-item input[type=checkbox]");var Se=document.getElementById("tsd-theme");Se&&ge(Se);var je=new U;Object.defineProperty(window,"app",{value:je});he();be();})(); 6 | /*! Bundled license information: 7 | 8 | lunr/lunr.js: 9 | (** 10 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 11 | * Copyright (C) 2020 Oliver Nightingale 12 | * @license MIT 13 | *) 14 | (*! 15 | * lunr.utils 16 | * Copyright (C) 2020 Oliver Nightingale 17 | *) 18 | (*! 19 | * lunr.Set 20 | * Copyright (C) 2020 Oliver Nightingale 21 | *) 22 | (*! 23 | * lunr.tokenizer 24 | * Copyright (C) 2020 Oliver Nightingale 25 | *) 26 | (*! 27 | * lunr.Pipeline 28 | * Copyright (C) 2020 Oliver Nightingale 29 | *) 30 | (*! 31 | * lunr.Vector 32 | * Copyright (C) 2020 Oliver Nightingale 33 | *) 34 | (*! 35 | * lunr.stemmer 36 | * Copyright (C) 2020 Oliver Nightingale 37 | * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt 38 | *) 39 | (*! 40 | * lunr.stopWordFilter 41 | * Copyright (C) 2020 Oliver Nightingale 42 | *) 43 | (*! 44 | * lunr.trimmer 45 | * Copyright (C) 2020 Oliver Nightingale 46 | *) 47 | (*! 48 | * lunr.TokenSet 49 | * Copyright (C) 2020 Oliver Nightingale 50 | *) 51 | (*! 52 | * lunr.Index 53 | * Copyright (C) 2020 Oliver Nightingale 54 | *) 55 | (*! 56 | * lunr.Builder 57 | * Copyright (C) 2020 Oliver Nightingale 58 | *) 59 | */ 60 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE4uuVipJrShRslLyL0pJLXLKz89W0lEqSCzJAAol5yQWF6cW68Ol9DJKcnOA8tmZeSlKVoZGFrU6mPqLg0vyi1LxmAJRQJRZ/gUlmfl5xQjDMvNKUovSEpORzYMqQjXQyNQMm4E+qWWpOcEliSVIDiypLEA2DqEEzUQDS3NDUyOcpiIMTCvNSwY7Cc1QVPPMTGpjAby6qFR/AQAA" -------------------------------------------------------------------------------- /docs/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAE61ZTW/bOBD9L8rVdcyhP+LcmgYLLLCLFnB3L0FQKBbTCJElQaSzLYz89yUl2ZqRhpZi+xKA1pt5w3lDcsjsgiL7Twe3D7vgNU6j4BZm81GQhhsV3AZfi0gVd1n2+jU3cZbqYBRsi8R+iFOjiudwrfR1GzN+MZvEAtdJqLWynoPgfbR3LiYwPXhfv6j16/d4o7QJN/lw51ddQ0Q2CvKwUKnhwvcEsgl/3avcvAyPAFmcR20Ky/JX9nM4NbL4ODXcdLU9UNfuGt6jUs4ENEpaGlNs1yYrerxdUWjPDHxZ0783T1nSR3VAncjy1J+cqxpz6jxesm0SffGsA9+0PEYnxmCtzD95FBp18NYXAW9yIn9n8XlIhy85xASTaVPxP5Vxn1fGBt7H1sKeyPgSplGiVmmYW9FMH2cHfRbrvUpMOIxyDz2Rr1B54vYk9aaSz+bPNFK/+nh5k4bf6OhTrD/lRfxWCTA8mjjVqjCl574oKPQi7M/2p3I6f2TFKonXvYXGGVwkEnpI9m7LbfSJtaCzorfOa8xFZmmKePM9+3vgHtJBXyyGVRypL9k27Z18G3yhCML1630cVZtyfwgt9EVisDb9s9+DTt5ptOrl2IPOOCXsKvise89+gjyP7S6OBrJVyNPZVnmhwuibKtaqXy4Gfy7zXahj/S2zZdDb6HhsPtBncO2uXtnW88gaqb5ftvVFPj/WANfBHmlQj2QRs+6hZ/F1LitHKYdfVI6zcv3usAg8lmdF09+u4giGN60dVq51HUbagM/iHNq8YuqPtrBDIuhpZLv0A9vZDvd82t0taB/5vE3X5Z36mgKO7hYwWS7EDDy+6W3E/M5V23mJ4BgeR7bZdZ3z7S54U4W2gdnfYSzHS4t8jlUSuYediteaZpuNS8Fj/e1f5fYgh6gg15Ng9DAZSRjfyOnj4+hhb1F+KH8oYcKOBAcTBAZ2BCOYjuVcEBgQmLQjOYLFeAFzApMENrWjKUc6JbCZHc04bzMCm9vRnPM2J7CFHS042ILAbuzohoPdEJgV5WHJwZY0vRNf4kRLh1IIVjBBpRAu5UJweRFUDeGybk83DkkFES7xQrLsVBPhci9Y8QSVRbj0ixmLpMoIp4BgJRRUHOFEEKyKguojnA6CFVJQicAJIVgtgWoETghgNYLWcinXC7uugGoETggAFkk1AicEsBoB1QicEMBqBFQjcEIAqxFQjcAJAaxGQDUCJwSwGgHVCJa+BQ5UIlmuIlZMSSWSwrsDUYVkqRCrumztadK3hCUVSDoVJFsekgokZ94lLKlAcu5dwpIKJJ0Kki05SQWSTgXJlpysBCrPInsIGVW9ZpSnSvU4ugt+1MfUYn8G7oKF/fP+3hxKbuTc1n1qY9O08LvAKsdbla8XpmnxGnMxb8yFj5SaE3qBzH3WuKFvLGeN5cxhR4EtUd6DewMqrZ7teVy9AaEIkCO7C/IebMv35KLXJN926R9M7YI/avrk7pXIdIpMZ0dMWxLbYm7kOkbpdK5aHzRVZCx86bbGurwVPrlbYV7fClHkSHHwKX5wku8vtsgBSjj4El51mlHVaaIZAJqBrGSXx33oQ7OM3OCyg9qNT4XqFTOpGlXkAykofLbuJX9bPgOxq2fZuPDVrr3hRNUNB2UQJbAKXkzqSUiPm8y1vO1iQjPwldLBrjN/iXIofXVAzTsFKdFM7EbX4yPb/5O0cYAK2rd3Hcy1rh4mUB7Rhgk++vpJDVmhiQN4rOoH+HLaoakscfGgnUP4cl8/tCFmLLxP6epS7t+xARWd9IXPOSGJR6eGL3HVMzSaM8q28BrV/1NszNB241vo5UtI4l5CUGmhWdZrw7fjle+1URxt6/dalCtUX+A3jzc6jtS6enFGE0aZBl95OmuTcatcoBwLdnuwTUEe5yqJUwt6eHx//x+ASQH/bSAAAA=="; -------------------------------------------------------------------------------- /docs/classes/OrderBooksStore.html: -------------------------------------------------------------------------------- 1 | OrderBooksStore | orderbooks - v1.1.9

Class OrderBooksStore<ExtraStateType>

Store for multi-symbol orderbooks, grouped into one book (OrderBook) per symbol

2 |

ExtraStateType is optional extra state you may want to store with each orderbook level, completely optional. Inject a union type if desired.

3 |

Type Parameters

  • ExtraStateType = unknown

Constructors

Properties

Methods

Constructors

Properties

books: Record<string, OrderBook<ExtraStateType>> = {}
maxDepth: number
shouldCheckTimestamp: boolean
traceLog: boolean

Methods

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/functions/OrderBookLevel.html: -------------------------------------------------------------------------------- 1 | OrderBookLevel | orderbooks - v1.1.9

Function OrderBookLevel

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/OrderBookOptions.html: -------------------------------------------------------------------------------- 1 | OrderBookOptions | orderbooks - v1.1.9

Interface OrderBookOptions

interface OrderBookOptions {
    checkTimestamps?: boolean;
    maxDepth?: number;
    traceLog?: boolean;
}

Properties

Properties

checkTimestamps?: boolean
maxDepth?: number
traceLog?: boolean

Whether to console.log when a snapshot or delta is processed

5 |

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | orderbooks - v1.1.9

orderbooks - v1.1.9

Index

Classes

Interfaces

Type Aliases

Functions

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/types/OrderBookLevelState.html: -------------------------------------------------------------------------------- 1 | OrderBookLevelState | orderbooks - v1.1.9

Type alias OrderBookLevelState<T>

OrderBookLevelState<T>: [Symbol, Price, Side, Quantity, (T[] | any)?]

Type Parameters

  • T = unknown

Generated using TypeDoc

-------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('lib/index'); -------------------------------------------------------------------------------- /jsconfig.js: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | }, 6 | "exclude": [ 7 | "**/node_modules/*", 8 | "coverage" 9 | ] 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orderbooks", 3 | "version": "1.1.12", 4 | "description": "In-memory state stores and handlers for caching multiple exchange:symbol orderbook states", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib/*", 9 | "index.js" 10 | ], 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "clean": "rimraf lib dist", 14 | "build": "tsc", 15 | "build:clean": "npm run clean && npm run build", 16 | "build:watch": "npm run clean && tsc --watch", 17 | "prepublishOnly": "npm run build:clean", 18 | "docs": "npx typedoc src/index.ts" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/tiagosiebler/orderbooks.git" 23 | }, 24 | "keywords": [ 25 | "orderbook", 26 | "orderbooks", 27 | "order", 28 | "book", 29 | "crypto", 30 | "trading", 31 | "bybit", 32 | "binance", 33 | "bitmex", 34 | "bitget", 35 | "node" 36 | ], 37 | "devDependencies": { 38 | "@babel/core": "^7.14.5", 39 | "@babel/preset-env": "^7.14.5", 40 | "@babel/preset-typescript": "^7.14.5", 41 | "@types/node": "^15.12.4", 42 | "@typescript-eslint/eslint-plugin": "^4.27.0", 43 | "@typescript-eslint/parser": "^4.26.0", 44 | "eslint": "^7.32.0", 45 | "eslint-config-prettier": "^8.3.0", 46 | "eslint-plugin-prettier": "^4.0.0", 47 | "prettier": "^2.4.1", 48 | "ts-node": "^10.8.1", 49 | "typedoc": "^0.25.7", 50 | "typescript": "^4.7.4" 51 | }, 52 | "author": "Tiago Siebler", 53 | "license": "MIT", 54 | "bugs": { 55 | "url": "https://github.com/tiagosiebler/orderbooks/issues" 56 | }, 57 | "homepage": "https://github.com/tiagosiebler/orderbooks#readme" 58 | } 59 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These are demonstrations for how this library can be used. Contributions for more exchanges are warmly welcome. 4 | 5 | ## Usage 6 | 7 | - tsc 8 | - cd into the samples folder 9 | - npm install 10 | - npm i -g ts-node 11 | - ts-node binance.ts 12 | -------------------------------------------------------------------------------- /samples/binance.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getContextFromWsKey, 3 | isWsPartialBookDepthEventFormatted, 4 | MainClient, 5 | OrderBookResponse, 6 | WebsocketClient, 7 | } from 'binance'; 8 | // import { OrderBookLevel, OrderBooksStore } from '../src'; 9 | import { OrderBookLevel, OrderBooksStore } from 'orderbooks'; 10 | 11 | const binanceWs = new WebsocketClient({ 12 | beautify: true, 13 | }); 14 | const binanceRest = new MainClient(); 15 | 16 | const OrderBooks = new OrderBooksStore({ 17 | traceLog: true, 18 | checkTimestamps: false, 19 | maxDepth: 40, 20 | }); 21 | 22 | // This example just dumps full snapshots into the orderbook store 23 | function handleOrderbookSnapshot(symbol: string, snapshot: OrderBookResponse) { 24 | // combine bids and asks 25 | const { bids, asks } = snapshot; 26 | 27 | const bidsArray = bids.map(([price, amount]) => { 28 | return OrderBookLevel(symbol, +price, 'Buy', +amount); 29 | }); 30 | 31 | const asksArray = asks.map(([price, amount]) => { 32 | return OrderBookLevel(symbol, +price, 'Sell', +amount); 33 | }); 34 | 35 | // store inititial snapshot 36 | const storedOrderbook = OrderBooks.handleSnapshot( 37 | symbol, 38 | [...bidsArray, ...asksArray], 39 | snapshot.lastUpdateId, 40 | ); 41 | 42 | // log book state to screen 43 | storedOrderbook.print(); 44 | } 45 | 46 | // connect to a websocket and relay orderbook events to handlers 47 | const symbol = 'BTCUSDT'; 48 | 49 | binanceWs.on('error', (msg) => { 50 | console.error(new Date(), `Binance WS Error`, msg); 51 | }); 52 | 53 | binanceWs.on('open', (data) => { 54 | console.log( 55 | new Date(), 56 | 'Binance WS connection opened open:', 57 | data.wsKey, 58 | data.ws.target.url, 59 | ); 60 | }); 61 | binanceWs.on('reply', (data) => { 62 | console.log( 63 | new Date(), 64 | 'Binance WS log reply: ', 65 | JSON.stringify(data, null, 2), 66 | ); 67 | }); 68 | binanceWs.on('reconnecting', (data) => { 69 | console.log( 70 | new Date(), 71 | 'Binance WS ws automatically reconnecting.... ', 72 | data?.wsKey, 73 | ); 74 | }); 75 | binanceWs.on('reconnected', (data) => { 76 | console.log(new Date(), 'Binance WS ws has reconnected ', data?.wsKey); 77 | }); 78 | 79 | binanceWs.on('formattedMessage', (data) => { 80 | // https://github.com/tiagosiebler/binance/blob/master/examples/ws-public-spot-orderbook.ts#L34 81 | if (isWsPartialBookDepthEventFormatted(data)) { 82 | const context = getContextFromWsKey(data.wsKey); 83 | 84 | if (!context?.symbol) { 85 | throw new Error(`Failed to extract context from event?`); 86 | } 87 | 88 | console.clear(); 89 | handleOrderbookSnapshot(context.symbol.toUpperCase(), data); 90 | return; 91 | } 92 | }); 93 | 94 | async function startOrderbookMonitoring() { 95 | try { 96 | const snapshot = await binanceRest.getOrderBook({ 97 | symbol, 98 | }); 99 | 100 | handleOrderbookSnapshot(symbol.toUpperCase(), snapshot); 101 | } catch (e) { 102 | console.error(`Failed to fetch orderbook snapshot via REST API`, e); 103 | throw e; 104 | } 105 | 106 | binanceWs.subscribePartialBookDepths(symbol, 20, 100, 'spot'); 107 | } 108 | 109 | startOrderbookMonitoring(); 110 | -------------------------------------------------------------------------------- /samples/bybit.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultLogger, 3 | isWsOrderbookEventV5, 4 | WebsocketClient, 5 | WSOrderbookEventV5, 6 | } from 'bybit-api'; 7 | import { 8 | OrderBookLevel, 9 | OrderBookLevelState, 10 | OrderBooksStore, 11 | } from 'orderbooks'; 12 | // import { OrderBookLevel, OrderBookLevelState, OrderBooksStore } from '../src'; 13 | 14 | const OrderBooks = new OrderBooksStore({ 15 | traceLog: true, 16 | checkTimestamps: false, 17 | }); 18 | 19 | // eslint-disable-next-line @typescript-eslint/no-empty-function 20 | DefaultLogger.silly = () => {}; 21 | 22 | // connect to a websocket and relay orderbook events to handlers 23 | const ws = new WebsocketClient({ 24 | market: 'v5', 25 | }); 26 | 27 | ws.on('update', (message) => { 28 | if (isWsOrderbookEventV5(message)) { 29 | // console.log('message', JSON.stringify(message, null, 2)); 30 | handleOrderbookUpdate(message); 31 | return; 32 | } 33 | }); 34 | 35 | ws.on('error', (message) => { 36 | console.error(`bybit ws error: `, message); 37 | }); 38 | 39 | ws.subscribeV5(['orderbook.50.BTCUSDT'], 'spot'); 40 | 41 | // parse orderbook messages, detect snapshot vs delta, and format properties using OrderBookLevel 42 | function handleOrderbookUpdate(message: WSOrderbookEventV5) { 43 | const { topic, type, data, cts } = message; 44 | const [topicKey, symbol] = topic.split('.'); 45 | 46 | const bidsArray = data.b.map(([price, amount]) => { 47 | return OrderBookLevel(symbol, +price, 'Buy', +amount); 48 | }); 49 | 50 | const asksArray = data.a.map(([price, amount]) => { 51 | return OrderBookLevel(symbol, +price, 'Sell', +amount); 52 | }); 53 | 54 | const allBidsAndAsks = [...bidsArray, ...asksArray]; 55 | 56 | if (type === 'snapshot') { 57 | // store inititial snapshot 58 | const storedOrderbook = OrderBooks.handleSnapshot( 59 | symbol, 60 | allBidsAndAsks, 61 | cts, 62 | ); 63 | 64 | // log book state to screen 65 | storedOrderbook.print(); 66 | return; 67 | } 68 | 69 | if (type === 'delta') { 70 | const upsertLevels: OrderBookLevelState[] = []; 71 | const deleteLevels: OrderBookLevelState[] = []; 72 | 73 | // Seperate "deletes" from "updates/inserts" 74 | allBidsAndAsks.forEach((level) => { 75 | const [_symbol, _price, _side, qty] = level; 76 | 77 | if (qty === 0) { 78 | deleteLevels.push(level); 79 | } else { 80 | upsertLevels.push(level); 81 | } 82 | }); 83 | 84 | // Feed delta into orderbook store 85 | const storedOrderbook = OrderBooks.handleDelta( 86 | symbol, 87 | deleteLevels, 88 | upsertLevels, 89 | [], 90 | cts, 91 | ); 92 | 93 | // log book state to screen 94 | storedOrderbook.print(); 95 | return; 96 | } 97 | 98 | console.error('unhandled orderbook update type: ', type); 99 | } 100 | -------------------------------------------------------------------------------- /samples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orderbooks-samples", 3 | "version": "1.0.0", 4 | "description": "Sample implementations with the OrderBooks module", 5 | "scripts": { 6 | "binance": "node binance.js", 7 | "bybit": "node bybit.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "private": true, 11 | "author": "Tiago Siebler", 12 | "license": "MIT", 13 | "dependencies": { 14 | "binance": "^2.15.16", 15 | "bybit-api": "^4.1.1", 16 | "orderbooks": "^1.1.6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/slippage-test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultLogger, 3 | WebsocketClient 4 | } from 'bybit-api'; 5 | import { 6 | OrderBookLevel, 7 | OrderBookLevelState, 8 | OrderBooksStore, 9 | } from '../src'; 10 | 11 | // Create orderbook store with appropriate options 12 | const OrderBooks = new OrderBooksStore({ 13 | traceLog: true, // Set to true to see orderbook updates 14 | checkTimestamps: false, 15 | maxDepth: 500, // Ensure we have enough depth for testing 16 | }); 17 | 18 | // Disable verbose logging from the Bybit client 19 | DefaultLogger.silly = () => {}; 20 | 21 | // Connect to websocket and relay orderbook events to handlers 22 | const ws = new WebsocketClient({ 23 | market: 'v5', 24 | }); 25 | 26 | // Flag to track if we've received the initial snapshot 27 | let hasReceivedSnapshot = false; 28 | 29 | // The symbol we're tracking 30 | const SYMBOL = 'BTCUSDT'; 31 | 32 | ws.on('update', (message: any) => { 33 | // Check if this is an orderbook message 34 | if (message.topic && message.topic.startsWith('orderbook.') && (message.type === 'snapshot' || message.type === 'delta')) { 35 | handleOrderbookUpdate(message); 36 | } 37 | }); 38 | 39 | ws.on('response', (response) => { 40 | console.log('Received response:', response); 41 | }); 42 | 43 | ws.on('error', (message) => { 44 | console.error(`Bybit WS error:`, message); 45 | }); 46 | 47 | // Subscribe to the orderbook for BTCUSDT (50 levels depth) 48 | ws.subscribeV5([`orderbook.500.${SYMBOL}`], 'linear'); 49 | 50 | // Parse orderbook messages and update our local orderbook 51 | function handleOrderbookUpdate(message: any) { 52 | const { topic, type, data, ts } = message; 53 | const segments = topic.split('.'); 54 | // The format is usually orderbook.DEPTH.SYMBOL 55 | const symbol = segments.length > 2 ? segments[2] : SYMBOL; 56 | 57 | console.log(`Received ${type} for ${symbol}`); 58 | 59 | if (!data || (!data.b && !data.a)) { 60 | console.error('Invalid orderbook data structure:', data); 61 | return; 62 | } 63 | 64 | const bidsArray = Array.isArray(data.b) ? data.b.map(([price, amount]: [string, string]) => { 65 | return OrderBookLevel(symbol, +price, 'Buy', +amount); 66 | }) : []; 67 | 68 | const asksArray = Array.isArray(data.a) ? data.a.map(([price, amount]: [string, string]) => { 69 | return OrderBookLevel(symbol, +price, 'Sell', +amount); 70 | }) : []; 71 | 72 | const allBidsAndAsks = [...bidsArray, ...asksArray]; 73 | 74 | console.log(`Processing ${bidsArray.length} bids and ${asksArray.length} asks`); 75 | 76 | if (type === 'snapshot') { 77 | // Store initial snapshot 78 | OrderBooks.handleSnapshot( 79 | symbol, 80 | allBidsAndAsks, 81 | ts || Date.now(), 82 | ); 83 | 84 | hasReceivedSnapshot = true; 85 | console.log(`Stored initial orderbook snapshot for ${symbol}`); 86 | 87 | return; 88 | } 89 | 90 | if (type === 'delta') { 91 | const upsertLevels: OrderBookLevelState[] = []; 92 | const deleteLevels: OrderBookLevelState[] = []; 93 | 94 | // Separate "deletes" from "updates/inserts" 95 | allBidsAndAsks.forEach((level) => { 96 | const [_symbol, _price, _side, qty] = level; 97 | 98 | if (qty === 0) { 99 | deleteLevels.push(level); 100 | } else { 101 | upsertLevels.push(level); 102 | } 103 | }); 104 | 105 | // Feed delta into orderbook store 106 | OrderBooks.handleDelta( 107 | symbol, 108 | deleteLevels, 109 | upsertLevels, 110 | [], 111 | ts || Date.now(), 112 | ); 113 | 114 | console.log(`Updated orderbook: deleted ${deleteLevels.length} levels, upserted ${upsertLevels.length} levels`); 115 | return; 116 | } 117 | 118 | console.error('Unhandled orderbook update type:', type); 119 | } 120 | 121 | // Calculate and display slippage for various order sizes after waiting for the orderbook to fill 122 | setTimeout(() => { 123 | if (!hasReceivedSnapshot) { 124 | console.error('No orderbook snapshot received yet. Try again later.'); 125 | process.exit(1); 126 | } 127 | 128 | // Get the current book state for analysis 129 | const book = OrderBooks.getBook(SYMBOL); 130 | const bookState = book.getBookState(); 131 | 132 | console.log(`Current orderbook has ${bookState.length} levels`); 133 | 134 | // Display orderbook summary 135 | console.log('\n========== ORDERBOOK SUMMARY =========='); 136 | console.log(`Symbol: ${SYMBOL}`); 137 | console.log(`Best Bid: ${book.getBestBid()}`); 138 | console.log(`Best Ask: ${book.getBestAsk()}`); 139 | console.log(`Spread: ${book.getSpreadBasisPoints()?.toFixed(2)} basis points`); 140 | console.log('=======================================\n'); 141 | 142 | // Test order sizes (in BTC) 143 | const testOrderSizes = [0.01, 0.05, 0.1, 0.5, 1, 5, 100]; 144 | 145 | // Calculate slippage for buy orders of different sizes 146 | console.log('===== BUY ORDER SLIPPAGE ====='); 147 | testOrderSizes.forEach(size => { 148 | try { 149 | const slippage = book.calculateSlippage(size, 'Buy'); 150 | if (slippage) { 151 | console.log(`Order Size: ${size} BTC`); 152 | console.log(`Execution Price: ${slippage.executionPrice.toFixed(2)} USDT`); 153 | console.log(`Slippage: ${slippage.slippagePercent.toFixed(4)}% (${slippage.slippageBasisPoints.toFixed(2)} bps)`); 154 | console.log('----------------------------'); 155 | } 156 | } catch (error) { 157 | console.log(`Order Size: ${size} BTC - ${error.message}`); 158 | console.log('----------------------------'); 159 | } 160 | }); 161 | 162 | // Calculate slippage for sell orders of different sizes 163 | console.log('\n===== SELL ORDER SLIPPAGE ====='); 164 | testOrderSizes.forEach(size => { 165 | try { 166 | const slippage = book.calculateSlippage(size, 'Sell'); 167 | if (slippage) { 168 | console.log(`Order Size: ${size} BTC`); 169 | console.log(`Execution Price: ${slippage.executionPrice.toFixed(2)} USDT`); 170 | console.log(`Slippage: ${slippage.slippagePercent.toFixed(4)}% (${slippage.slippageBasisPoints.toFixed(2)} bps)`); 171 | console.log('----------------------------'); 172 | } 173 | } catch (error) { 174 | console.log(`Order Size: ${size} BTC - ${error.message}`); 175 | console.log('----------------------------'); 176 | } 177 | }); 178 | 179 | // Close the websocket connection and exit 180 | console.log('\nTest complete, exiting...'); 181 | // Just exit the process, the WebSocket will close automatically 182 | process.exit(0); 183 | 184 | }, 15000); // Wait 15 seconds for the orderbook to fill with data 185 | 186 | console.log(`Connecting to Bybit and subscribing to ${SYMBOL} orderbook...`); 187 | console.log('Waiting 15 seconds to collect orderbook data...'); -------------------------------------------------------------------------------- /src/OrderBook.ts: -------------------------------------------------------------------------------- 1 | import { OrderBookLevelState } from './OrderBookLevel'; 2 | 3 | const EnumLevelProperty = Object.freeze({ 4 | symbol: 0, 5 | price: 1, 6 | side: 2, 7 | qty: 3, 8 | extraState: 4, 9 | }); 10 | 11 | export interface OrderBookOptions { 12 | checkTimestamps?: boolean; 13 | maxDepth?: number; 14 | /** Whether to console.log when a snapshot or delta is processed */ 15 | traceLog?: boolean; 16 | } 17 | 18 | /** 19 | * Storage helper to store/track/manipulate the current state of an symbol's orderbook 20 | * @class OrderBook 21 | */ 22 | export class OrderBook { 23 | symbol: string; 24 | book: OrderBookLevelState[]; 25 | shouldCheckTimestamps: boolean; 26 | lastUpdateTimestamp: number; 27 | maxDepth: number; 28 | 29 | constructor(symbol: string, options: OrderBookOptions = {}) { 30 | this.symbol = symbol; 31 | this.book = []; 32 | 33 | this.shouldCheckTimestamps = options.checkTimestamps === true; 34 | this.lastUpdateTimestamp = new Date().getTime(); 35 | this.maxDepth = options.maxDepth || 250; 36 | } 37 | 38 | /** 39 | * Returns a cloned copy of the current orderbook state 40 | */ 41 | public getBookState(): OrderBookLevelState[] { 42 | return structuredClone(this.book); 43 | } 44 | 45 | /** 46 | * @public Process orderbook snapshot, replacing existing book in memory 47 | * @param {OrderBookLevelState[]} data current orderbook snapshot represented as array, where each child element is a level in the orderbook 48 | * @param {number} timestamp 49 | */ 50 | public handleSnapshot( 51 | data: OrderBookLevelState[], 52 | timestamp: number = Date.now(), 53 | ): this { 54 | this.checkTimestamp(timestamp); 55 | this.book = data; 56 | return this.trimToMaxDepth().sort().trackDidUpdate(timestamp); 57 | } 58 | 59 | /** 60 | * @public Process orderbook delta change, either deleting, updating or inserting level data into the existing book. Price is used on each level to find existing index in tracked book state. 61 | * 62 | * @param {Array} [deleteDelta=[]] levels to delete 63 | * @param {Array} [upsertDelta=[]] levels to update (will automatically insert if level does not exist) 64 | * @param {Array} [insertDelta=[]] levels to insert 65 | * @param {number} timestamp 66 | */ 67 | public handleDelta( 68 | deleteDelta: OrderBookLevelState[] = [], 69 | upsertDelta: OrderBookLevelState[] = [], 70 | insertDelta: OrderBookLevelState[] = [], 71 | timestamp: number = Date.now(), 72 | ): this { 73 | this.checkTimestamp(timestamp); 74 | 75 | deleteDelta.forEach((level) => { 76 | const existingIndex = this.findIndexForSlice(level); 77 | if (existingIndex !== -1) { 78 | this.book.splice(existingIndex, 1); 79 | } 80 | }); 81 | 82 | upsertDelta.forEach((level) => { 83 | const existingIndex = this.findIndexForSlice(level); 84 | if (existingIndex !== -1) { 85 | this.replaceLevelAtIndex(existingIndex, level); 86 | } else { 87 | this.insertLevel(level); 88 | } 89 | }); 90 | 91 | insertDelta.forEach((level) => { 92 | const existingIndex = this.findIndexForSlice(level); 93 | if (existingIndex !== -1) { 94 | this.replaceLevelAtIndex(existingIndex, level); 95 | } 96 | this.insertLevel(level); 97 | }); 98 | 99 | return this.trimToMaxDepth().sort().trackDidUpdate(timestamp); 100 | } 101 | 102 | /** 103 | * @private replace item at index, mutating existing book store 104 | */ 105 | private replaceLevelAtIndex(i: number, level: OrderBookLevelState): this { 106 | this.book.splice(i, 1, level); 107 | return this; 108 | } 109 | 110 | /** 111 | * @private insert item, mutating existing book store 112 | */ 113 | private insertLevel(level: OrderBookLevelState): this { 114 | this.book.push(level); 115 | return this; 116 | } 117 | 118 | /** 119 | * @private find index of level in book, using "price" property as primary key 120 | * @param {object} level 121 | * @returns {number} index of level in book, if found, else -1 122 | */ 123 | private findIndexForSlice(level: OrderBookLevelState): number { 124 | return this.book.findIndex( 125 | (e) => e[EnumLevelProperty.price] === level[EnumLevelProperty.price], 126 | ); 127 | } 128 | 129 | /** 130 | * @public throw error if current timestamp is older than last updated timestamp 131 | * @param {number} timestamp 132 | */ 133 | public checkTimestamp(timestamp: number) { 134 | if (!this.shouldCheckTimestamps) { 135 | return false; 136 | } 137 | if (this.lastUpdateTimestamp > timestamp) { 138 | throw new Error( 139 | `Received data older than last tick: ${{ 140 | lastUpdate: this.lastUpdateTimestamp, 141 | currentUpdate: timestamp, 142 | }}`, 143 | ); 144 | } 145 | } 146 | 147 | /** Sort orderbook in memory, lowest price last, highest price first */ 148 | private sort(): this { 149 | // sorts with lowest price last, highest price first 150 | this.book.sort( 151 | (a, b) => b[EnumLevelProperty.price] - a[EnumLevelProperty.price], 152 | ); 153 | return this; 154 | } 155 | 156 | /** trim orderbook in place to max depth, evenly across both sides */ 157 | private trimToMaxDepth(): this { 158 | const book = this.book; 159 | const maxDepth = this.maxDepth; 160 | if (book.length <= maxDepth) { 161 | return this; 162 | } 163 | 164 | const count = book.reduce( 165 | (acc, level) => { 166 | if (level[EnumLevelProperty.side] === 'Sell') { 167 | acc.sells++; 168 | return acc; 169 | } 170 | acc.buys++; 171 | return acc; 172 | }, 173 | { buys: 0, sells: 0 }, 174 | ); 175 | 176 | const maxPerSide = +(maxDepth / 2).toFixed(0); 177 | 178 | const buysToTrim = count.buys - maxPerSide; 179 | const sellsToTrim = count.sells - maxPerSide; 180 | 181 | this.sort() 182 | .trimSideCount(buysToTrim, false) 183 | .trimSideCount(sellsToTrim, true); 184 | 185 | return this; 186 | } 187 | 188 | /** 189 | * Trim edges of orderbook to total target 190 | * 191 | * @param {number} [totalToTrim=0] 192 | * @param {boolean} shouldTrimTop - if true, trim from array beginning (top = sells) else from array end (bottom = buys) 193 | */ 194 | private trimSideCount( 195 | totalToTrim: number = 0, 196 | shouldTrimTop?: boolean, 197 | ): this { 198 | if (totalToTrim <= 0) { 199 | return this; 200 | } 201 | 202 | const book = this.book; 203 | if (shouldTrimTop) { 204 | book.splice(0, totalToTrim); 205 | return this; 206 | } 207 | 208 | book.splice(book.length - totalToTrim - 1, totalToTrim); 209 | return this; 210 | } 211 | 212 | /** Track last updated timestamp */ 213 | private trackDidUpdate(timestamp: number = new Date().getTime()): this { 214 | this.lastUpdateTimestamp = timestamp; 215 | return this; 216 | } 217 | 218 | /** dump orderbook state to console */ 219 | public print() { 220 | // console.clear(); 221 | console.log( 222 | `---------- ${ 223 | this.symbol 224 | } ask:bid ${this.getBestAsk()}:${this.getBestBid()} & spread: ${this.getSpreadBasisPoints()?.toFixed( 225 | 5, 226 | )} bp`, 227 | ); 228 | 229 | // Map the book to a new format for console.table 230 | const formattedBook = this.book.map((level) => ({ 231 | symbol: level[EnumLevelProperty.symbol], 232 | price: level[EnumLevelProperty.price], 233 | side: level[EnumLevelProperty.side], 234 | qty: level[EnumLevelProperty.qty], 235 | })); 236 | console.table(formattedBook); 237 | 238 | return this; 239 | } 240 | 241 | /** empty current orderbook store to free memory */ 242 | public reset() { 243 | this.book = []; 244 | return this; 245 | } 246 | 247 | /** 248 | * get lowest sell order 249 | * @param {number} [offset=0] offset from array centre (should be positive) 250 | * @returns {number} lowest seller price 251 | */ 252 | public getBestAsk(offset: number = 0): number | null { 253 | const sellSide = this.book.filter( 254 | (e) => e[EnumLevelProperty.side] === 'Sell', 255 | ); 256 | const index = sellSide.length - 1 - offset; 257 | const bottomSell = sellSide[Math.abs(index)]; 258 | return bottomSell ? bottomSell[EnumLevelProperty.price] : null; 259 | } 260 | 261 | /** 262 | * get highest buy order price 263 | * @param {number} [offset=0] offset from array centre (should be positive) 264 | * @returns {number} highest buyer price 265 | */ 266 | public getBestBid(offset: number = 0): number | null { 267 | const buySide = this.book.filter( 268 | (e) => e[EnumLevelProperty.side] === 'Buy', 269 | ); 270 | const topBuy = buySide[Math.abs(offset)]; 271 | return topBuy ? topBuy[EnumLevelProperty.price] : null; 272 | } 273 | 274 | /** 275 | * get current bid/ask spread percentage 276 | * @param {number} [n=0] offset from centre of book 277 | * @returns {number} percentage spread between best bid & ask 278 | */ 279 | public getSpreadPercent(n = 0): number | null { 280 | const ask = this.getBestAsk(n); 281 | const bid = this.getBestBid(n); 282 | 283 | if (!bid || !ask) { 284 | return null; 285 | } 286 | return (1 - bid / ask) * 100; 287 | } 288 | 289 | /** 290 | * get current bid/ask spread in basis points 291 | * @param {number} [n=0] offset from centre of book 292 | * @returns {number} spread between best bid & ask in basis points 293 | */ 294 | public getSpreadBasisPoints(n = 0): number | null { 295 | const ask = this.getBestAsk(n); 296 | const bid = this.getBestBid(n); 297 | 298 | if (!bid || !ask) { 299 | return null; 300 | } 301 | // calculate spread in basis points 302 | return (1 - bid / ask) * 10000; 303 | } 304 | 305 | /** 306 | * Calculate expected slippage for a market order of a given size 307 | * @param {number} baseOrderSize - The size of the order in base units 308 | * @param {string} side - 'Buy' or 'Sell' side of the order 309 | * @returns {{ executionPrice: number, slippagePercent: number, slippageBasisPoints: number } | null} - The expected execution price and slippage 310 | */ 311 | public getEstimatedSlippage(baseOrderSize: number, side: 'Buy' | 'Sell'): { executionPrice: number, slippagePercent: number, slippageBasisPoints: number } | null { 312 | if (baseOrderSize <= 0) { 313 | throw new Error('Order size is not positive!'); 314 | } 315 | 316 | // Filter the book to get only the levels for the relevant side 317 | // For a buy order, we need the sell levels; for a sell order, we need the buy levels 318 | const relevantLevels = this.book.filter( 319 | (level) => level[EnumLevelProperty.side] === (side === 'Buy' ? 'Sell' : 'Buy') 320 | ); 321 | 322 | if (relevantLevels.length === 0) { 323 | throw new Error('No relevant levels found in orderbook!'); 324 | } 325 | 326 | // Sort the levels by price (ascending for buy orders, descending for sell orders) 327 | const sortedLevels = [...relevantLevels].sort((a, b) => { 328 | return side === 'Buy' 329 | ? a[EnumLevelProperty.price] - b[EnumLevelProperty.price] // Buy orders fill from lowest ask to highest 330 | : b[EnumLevelProperty.price] - a[EnumLevelProperty.price]; // Sell orders fill from highest bid to lowest 331 | }); 332 | 333 | let remainingSize = baseOrderSize; 334 | let totalCost = 0; 335 | 336 | // Simulate filling the order level by level 337 | for (const level of sortedLevels) { 338 | const price = level[EnumLevelProperty.price]; 339 | const availableQty = level[EnumLevelProperty.qty]; 340 | 341 | const fillQty = Math.min(remainingSize, availableQty); 342 | totalCost += fillQty * price; 343 | remainingSize -= fillQty; 344 | 345 | if (remainingSize <= 0) { 346 | break; 347 | } 348 | } 349 | 350 | // If we couldn't fill the entire order, return null 351 | if (remainingSize > 0) { 352 | throw new Error('Could not fill the entire order'); 353 | } 354 | 355 | // Calculate the average execution price 356 | const executionPrice = totalCost / baseOrderSize; 357 | 358 | // Calculate slippage relative to the best price 359 | const bestPrice = side === 'Buy' ? this.getBestAsk() : this.getBestBid(); 360 | 361 | if (!bestPrice) { 362 | return null; 363 | } 364 | 365 | // Calculate slippage percentage 366 | const slippagePercent = side === 'Buy' 367 | ? ((executionPrice / bestPrice) - 1) * 100 // For buys, execution price is higher than best price 368 | : ((bestPrice / executionPrice) - 1) * 100; // For sells, execution price is lower than best price 369 | 370 | // Calculate slippage in basis points 371 | const slippageBasisPoints = slippagePercent * 100; 372 | 373 | return { 374 | executionPrice, 375 | slippagePercent, 376 | slippageBasisPoints 377 | }; 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/OrderBookLevel.ts: -------------------------------------------------------------------------------- 1 | type Symbol = string; 2 | type Price = number; 3 | type Side = 'Buy' | 'Sell'; 4 | type Quantity = number; 5 | 6 | export type OrderBookLevelState = [ 7 | Symbol, 8 | Price, 9 | Side, 10 | Quantity, 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 12 | (T[] | any)?, 13 | ]; 14 | 15 | /** 16 | * One level in orderbook 17 | * @param {string} symbol 18 | * @param {number} price 19 | * @param {string} [side='Buy'|'Sell'] 20 | * @param {number} qty asset at this level 21 | */ 22 | export function OrderBookLevel( 23 | symbol: string, 24 | price: number, 25 | side: Side, 26 | qty: Quantity, 27 | ...extraState: T[] 28 | ) { 29 | const level: OrderBookLevelState = [symbol, price, side, qty, undefined]; 30 | if (extraState.length) { 31 | level.push(extraState); 32 | } 33 | return level; 34 | } 35 | -------------------------------------------------------------------------------- /src/OrderBooksStore.ts: -------------------------------------------------------------------------------- 1 | import { OrderBook, OrderBookOptions } from './OrderBook'; 2 | import { OrderBookLevelState } from './OrderBookLevel'; 3 | 4 | /** 5 | * Store for multi-symbol orderbooks, grouped into one book (OrderBook) per symbol 6 | * 7 | * `ExtraStateType` is optional extra state you may want to store with each orderbook level, completely optional. Inject a union type if desired. 8 | * @class OrderBooksStore 9 | */ 10 | export class OrderBooksStore { 11 | books: Record> = {}; 12 | traceLog: boolean; 13 | shouldCheckTimestamp: boolean; 14 | maxDepth: number; 15 | 16 | constructor(options?: OrderBookOptions) { 17 | this.books = {}; 18 | this.traceLog = options?.traceLog === true; 19 | this.shouldCheckTimestamp = options?.checkTimestamps === true; 20 | this.maxDepth = options?.maxDepth || 250; 21 | } 22 | 23 | /** 24 | * Get the current orderbook store for a symbol. Automatically initialised (empty), if none exists yet. 25 | * @param {string} symbol 26 | * @returns {OrderBook} created for symbol if not already tracked 27 | */ 28 | public getBook(symbol: string): OrderBook { 29 | if (this.books[symbol]) { 30 | return this.books[symbol]; 31 | } 32 | 33 | this.books[symbol] = new OrderBook(symbol, { 34 | checkTimestamps: this.shouldCheckTimestamp, 35 | maxDepth: this.maxDepth, 36 | }); 37 | 38 | return this.books[symbol]; 39 | } 40 | 41 | /** 42 | * @public Store/replace existing orderbook state in-memory 43 | * 44 | * @param {string} symbol 45 | * @param {Array} data current orderbook snapshot represented as array, where each child element is a level in the orderbook 46 | * @param {number} timestamp 47 | * @returns {OrderBook} store instance that handled this event 48 | */ 49 | public handleSnapshot( 50 | symbol: string, 51 | data: OrderBookLevelState[], 52 | timestamp: number = Date.now(), 53 | ): OrderBook { 54 | if (this.traceLog) { 55 | console.log('handleSnapshot ', symbol, timestamp); 56 | } 57 | return this.getBook(symbol).handleSnapshot(data, timestamp); 58 | } 59 | 60 | /** 61 | * @public Update existing orderbook state in-memory 62 | * 63 | * @param {string} symbol 64 | * @param {Array} deleteLevels - array with levels to delete 65 | * @param {Array} updateLevels - array with levels to update 66 | * @param {Array} insertLevels - array with levels to insert 67 | * @param {number} timestamp 68 | * @returns {OrderBook} store instance that handled this event 69 | */ 70 | public handleDelta( 71 | symbol: string, 72 | deleteLevels: OrderBookLevelState[] | undefined, 73 | updateLevels: OrderBookLevelState[] | undefined, 74 | insertLevels: OrderBookLevelState[] | undefined, 75 | timestamp: number = Date.now(), 76 | ): OrderBook { 77 | if (this.traceLog) { 78 | console.log('handleDelta ', symbol, timestamp); 79 | } 80 | return this.getBook(symbol).handleDelta( 81 | deleteLevels, 82 | updateLevels, 83 | insertLevels, 84 | timestamp, 85 | ); 86 | } 87 | 88 | /** 89 | * Calculate expected slippage for a market order of a given size for a specific symbol 90 | * @param {string} symbol - The trading symbol 91 | * @param {number} orderSize - The size of the order in base units 92 | * @param {string} side - 'Buy' or 'Sell' side of the order 93 | * @returns {{ executionPrice: number, slippagePercent: number, slippageBasisPoints: number } | null} - The expected execution price and slippage 94 | */ 95 | public getEstimatedSlippage( 96 | symbol: string, 97 | baseOrderSize: number, 98 | side: 'Buy' | 'Sell', 99 | ) { 100 | return this.getBook(symbol).getEstimatedSlippage(baseOrderSize, side); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './OrderBook'; 2 | export * from './OrderBookLevel'; 3 | export * from './OrderBooksStore'; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "target": "es6", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "removeComments": false, 10 | "noEmitOnError": true, 11 | "noImplicitAny": false, 12 | "strictNullChecks": true, 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "esModuleInterop": true, 16 | "lib": ["es2017", "dom"], 17 | "baseUrl": ".", 18 | "outDir": "lib" 19 | }, 20 | "include": ["src/**/*", "src/.ts"], 21 | "exclude": [ 22 | "node_modules", 23 | "**/node_modules/*", 24 | "coverage", 25 | "docs", 26 | "samples" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.samples.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "target": "es6", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "removeComments": false, 10 | "noEmitOnError": true, 11 | "noImplicitAny": false, 12 | "strictNullChecks": true, 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "esModuleInterop": true, 16 | "lib": ["es2017", "dom"], 17 | "baseUrl": ".", 18 | "outDir": "lib" 19 | }, 20 | "include": ["src/**/*", "src/.ts", "samples/**/*"], 21 | "exclude": ["node_modules", "**/node_modules/*", "coverage", "doc"] 22 | } 23 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | // Comments are supported, like tsconfig.json 3 | "entryPoints": ["src/index.ts"], 4 | "out": "docs", 5 | "includeVersion": true, 6 | "sourceLinkExternal": true, 7 | "sidebarLinks": { 8 | "Orderbooks NPM": "https://npmjs.com/orderbooks" 9 | }, 10 | "navigation": { 11 | "includeCategories": true, 12 | "includeGroups": false, 13 | "includeFolders": true 14 | }, 15 | "categorizeByGroup": false 16 | } 17 | --------------------------------------------------------------------------------