├── .circleci └── config.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .travis.yml ├── .vscode └── launch.json ├── CONTRIBUTING.md ├── README.md ├── assets └── ccex-api-sample-structure.png ├── gulpfile.ts ├── jest.config.js ├── package.json ├── samples ├── react-ccex-api │ ├── .gitignore │ ├── README.md │ ├── images.d.ts │ ├── package.json │ ├── post-install.js │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.css │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── components │ │ │ ├── ChangeColor │ │ │ │ ├── ChangeColor.css │ │ │ │ ├── ChangeColor.tsx │ │ │ │ └── index.ts │ │ │ ├── Percent │ │ │ │ ├── Percent.css │ │ │ │ ├── Percent.tsx │ │ │ │ └── index.ts │ │ │ ├── Ticker │ │ │ │ ├── Ticker.css │ │ │ │ ├── Ticker.test.tsx │ │ │ │ ├── Ticker.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ └── registerServiceWorker.ts │ ├── tsconfig.json │ ├── tsconfig.prod.json │ ├── tsconfig.test.json │ ├── tslint.json │ └── yarn.lock └── webpack-chart │ ├── lib │ └── charting_library │ │ ├── charting_library.min.d.ts │ │ ├── charting_library.min.js │ │ ├── datafeed-api.d.ts │ │ └── static │ │ ├── bundles │ │ ├── 13.416855bb3e77f54b85bc.js │ │ ├── crosshair.6c091f7d5427d0c5e6d9dc3a90eb2b20.cur │ │ ├── dot.ed68e83c16f77203e73dbc4c3a7c7fa1.cur │ │ ├── ds-property-pages.1d6127a623f51513ea4f.js │ │ ├── editobjectdialog.25fa62e6b4f8125e697e.js │ │ ├── eraser.0579d40b812fa2c3ffe72e5803a6e14c.cur │ │ ├── go-to-date-dialog-impl.62cf944ccbbc265f35d6.js │ │ ├── grab.bc156522a6b55a60be9fae15c14b66c5.cur │ │ ├── grabbing.1c0862a8a8c0fb02885557bc97fdafe7.cur │ │ ├── ie-fallback-logos.1e0142e3b30300ec0153.js │ │ ├── lazy-jquery-ui.8a95a81e0b032b2d76df.js │ │ ├── lazy-velocity.97588d47c84409f2bc4b.js │ │ ├── library.21c43e3cd2e427bd8522.js │ │ ├── library.a8de6f8cf4dda6895071c6ec45f900d9.css │ │ ├── lt-pane-views.58e294a05422f60f49f9.js │ │ ├── objecttreedialog.e036091976a3fed3c8c9.js │ │ ├── propertypagesfactory.54b21a18753b2d8c83c2.js │ │ ├── symbol-info-dialog-impl.a3697f845094a08295ab.js │ │ ├── take-chart-image-dialog-impl.53880c50752269d86d3a.js │ │ ├── vendors.a94ef44ed5c201cefcf6ad7460788c1a.css │ │ ├── vendors.c0e9ee356e307b4a0cd1.js │ │ └── zoom.e21f24dd632c7069139bc47ae89c54b5.cur │ │ ├── fonts │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ │ ├── images │ │ ├── balloon.png │ │ ├── bar-loader.gif │ │ ├── button-bg.png │ │ ├── charting_library │ │ │ ├── logo-widget-copyright-faded.png │ │ │ └── logo-widget-copyright.png │ │ ├── controlll.png │ │ ├── delayed.png │ │ ├── dialogs │ │ │ ├── checkbox.png │ │ │ ├── close-flat.png │ │ │ ├── large-slider-handle.png │ │ │ ├── linewidth-slider.png │ │ │ └── opacity-slider.png │ │ ├── icons.png │ │ ├── prediction-clock-black.png │ │ ├── prediction-clock-white.png │ │ ├── prediction-failure-white.png │ │ ├── prediction-success-white.png │ │ ├── select-bg.png │ │ ├── sidetoolbar │ │ │ ├── instruments.png │ │ │ └── toolgroup.png │ │ ├── svg │ │ │ ├── chart │ │ │ │ ├── bucket2.svg │ │ │ │ ├── font.svg │ │ │ │ ├── large-slider-handle.svg │ │ │ │ └── pencil2.svg │ │ │ └── question-mark-rounded.svg │ │ ├── tvcolorpicker-bg-gradient.png │ │ ├── tvcolorpicker-bg.png │ │ ├── tvcolorpicker-check.png │ │ ├── tvcolorpicker-sprite.png │ │ └── warning-icon.png │ │ ├── js │ │ └── external │ │ │ └── spin.min.js │ │ ├── localization │ │ └── translations │ │ │ ├── ar.json │ │ │ ├── cs.json │ │ │ ├── da_DK.json │ │ │ ├── de.json │ │ │ ├── el.json │ │ │ ├── en.json │ │ │ ├── es.json │ │ │ ├── et_EE.json │ │ │ ├── fa.json │ │ │ ├── fr.json │ │ │ ├── he_IL.json │ │ │ ├── hu_HU.json │ │ │ ├── id_ID.json │ │ │ ├── it.json │ │ │ ├── ja.json │ │ │ ├── ko.json │ │ │ ├── ms_MY.json │ │ │ ├── nl_NL.json │ │ │ ├── no.json │ │ │ ├── pl.json │ │ │ ├── pt.json │ │ │ ├── ro.json │ │ │ ├── ru.json │ │ │ ├── sk_SK.json │ │ │ ├── sv.json │ │ │ ├── th.json │ │ │ ├── tr.json │ │ │ ├── vi.json │ │ │ ├── widgets-copyrights.json │ │ │ ├── widgets-copyrights.json.example │ │ │ ├── zh.json │ │ │ └── zh_TW.json │ │ └── tv-chart.82ee311dc10bb182c736.html │ ├── package.json │ ├── post-install.js │ ├── src │ ├── datafeed.ts │ ├── index.html │ └── main.ts │ ├── tsconfig.json │ ├── webpack-prod.config.ts │ ├── webpack.config.ts │ └── yarn.lock ├── src ├── common │ ├── fetch-rxjs.ts │ ├── index.ts │ ├── pubnub-rxjs.ts │ ├── websocket-rxjs.mock.ts │ └── websocket-rxjs.ts ├── exchanges │ ├── binance │ │ ├── README.md │ │ ├── api-key-test.ts.org │ │ ├── api-private │ │ │ ├── binance-api-private-signed.ts │ │ │ ├── binance-api-private.spec.ts │ │ │ ├── binance-api-private.ts │ │ │ └── internal │ │ │ │ ├── functions-test.ts │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── binance-api.ts │ │ ├── binance-common.ts │ │ ├── binance-types.ts │ │ ├── candlestick │ │ │ ├── binance-candlestick.spec.ts │ │ │ ├── binance-candlestick.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── orderbook │ │ │ ├── binance-orderbook.spec.ts │ │ │ ├── binance-orderbook.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── ticker │ │ │ ├── binance-ticker.spec.ts │ │ │ ├── binance-ticker.ts │ │ │ ├── index.ts │ │ │ ├── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ │ └── test-helpers │ │ │ │ ├── index.ts │ │ │ │ └── mock-websocket.ts │ │ ├── trade │ │ │ ├── binance-trade.spec.ts │ │ │ ├── binance-trade.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── user-stream │ │ │ ├── binance-user-stream.spec.ts │ │ │ ├── binance-user-stream.ts │ │ │ └── internal │ │ │ │ └── types.ts │ │ └── websocket │ │ │ ├── binance-websocket.ts │ │ │ ├── binance-websocket.type.ts │ │ │ └── index.ts │ ├── bitbank │ │ ├── README.md │ │ ├── bitbank-api.spec.ts │ │ ├── bitbank-api.ts │ │ ├── bitbank-common.ts │ │ ├── candlestick │ │ │ ├── bitbank-candlestick.spec.ts │ │ │ ├── bitbank-candlestick.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.spec.ts │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── orderbook │ │ │ ├── bitbank-orderbook.spec.ts │ │ │ ├── bitbank-orderbook.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── ticker │ │ │ ├── bitbank-ticker.spec.ts │ │ │ ├── bitbank-ticker.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ └── trade │ │ │ ├── bitbank-trade.spec.ts │ │ │ ├── bitbank-trade.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ ├── functions.ts │ │ │ └── types.ts │ ├── bitfinex │ │ ├── README.md │ │ ├── bitfinex-api.ts │ │ ├── bitfinex-common.ts │ │ ├── candlestick │ │ │ ├── bitfinex-candlestick.spec.ts │ │ │ ├── bitfinex-candlestick.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── orderbook │ │ │ ├── bitfinex-orderbook.spec.ts │ │ │ ├── bitfinex-orderbook.ts │ │ │ ├── index.ts │ │ │ ├── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ │ └── test-helpers │ │ │ │ ├── index.ts │ │ │ │ └── mock-websocket.ts │ │ ├── ticker │ │ │ ├── bitfinex-ticker.spec.ts │ │ │ ├── bitfinex-ticker.ts │ │ │ ├── index.ts │ │ │ ├── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ │ └── test-helpers │ │ │ │ ├── index.ts │ │ │ │ └── mock-websocket.ts │ │ ├── trade │ │ │ ├── bitfinex-trade.spec.ts │ │ │ ├── bitfinex-trade.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ └── websocket │ │ │ ├── bitfinex-websocket-old.ts │ │ │ ├── bitfinex-websocket.spec.ts │ │ │ ├── bitfinex-websocket.ts │ │ │ ├── bitfinex-websocket.type.ts │ │ │ ├── index.ts │ │ │ └── test-helpers │ │ │ ├── index.ts │ │ │ └── mock-websocket.ts │ ├── bitmex │ │ ├── README.md │ │ ├── bitmex-api.ts │ │ ├── bitmex-common.ts │ │ ├── candlestick │ │ │ ├── bitmex-candlestick.spec.ts │ │ │ ├── bitmex-candlestick.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── orderbook │ │ │ ├── bitmex-orderbook.spec.ts │ │ │ ├── bitmex-orderbook.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ ├── ticker │ │ │ ├── bitmex-ticker.spec.ts │ │ │ ├── bitmex-ticker.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── trade │ │ │ ├── bitmex-trade.spec.ts │ │ │ ├── bitmex-trade.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ └── websocket │ │ │ ├── bitmex-websocket.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ ├── coinbase │ │ ├── README.md │ │ ├── candlestick │ │ │ ├── coinbase-candlestick.spec.ts │ │ │ ├── coinbase-candlestick.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── coinbase-api.ts │ │ ├── coinbase-common.ts │ │ ├── coinbase-common.types.ts │ │ ├── coinbase-websocket.ts │ │ ├── index.ts │ │ ├── orderbook │ │ │ ├── coinbase-orderbook.spec.ts │ │ │ ├── coinbase-orderbook.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ ├── ticker │ │ │ ├── coinbase-ticker.spec.ts │ │ │ ├── coinbase-ticker.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ │ ├── functions.ts │ │ │ │ └── types.ts │ │ └── trade │ │ │ ├── coinbase-trade.spec.ts │ │ │ ├── coinbase-trade.ts │ │ │ ├── index.ts │ │ │ └── internal │ │ │ ├── functions.ts │ │ │ └── types.ts │ ├── coincheck │ │ ├── candlestick │ │ │ └── coincheck-candlestick.ts │ │ ├── coincheck-api.ts │ │ ├── coincheck-functions.ts │ │ ├── coincheck-types.ts │ │ ├── index.ts │ │ ├── orderbook │ │ │ └── coincheck-orderbook.ts │ │ └── ticker │ │ │ ├── coincheck-ticker.spec.ts │ │ │ └── coincheck-ticker.ts │ ├── exchange-api.abstract.ts │ ├── exchange-api.test.ts │ ├── exchange-default.options.ts │ ├── exchange-test.functions.ts │ ├── exchange-types.ts │ ├── exchange-websocket.abstract.ts │ ├── helper.functions.ts │ ├── index.ts │ └── sample │ │ ├── candlestick │ │ ├── index.ts │ │ ├── internal │ │ │ ├── functions.ts │ │ │ └── types.ts │ │ └── sample-candlestick.ts │ │ ├── index.ts │ │ ├── orderbook │ │ ├── index.ts │ │ ├── internal │ │ │ ├── functions.ts │ │ │ └── types.ts │ │ └── sample-orderbook.ts │ │ ├── sample-api.ts │ │ ├── sample-common.ts │ │ ├── ticker │ │ ├── index.ts │ │ ├── internal │ │ │ ├── functions.ts │ │ │ └── types.ts │ │ ├── sample-ticker.spec.ts │ │ └── sample-ticker.ts │ │ └── trade │ │ ├── index.ts │ │ ├── internal │ │ ├── functions.ts │ │ └── types.ts │ │ └── sample-trade.ts ├── global.type.ts ├── helpers │ ├── index.ts │ └── update-orderbook │ │ ├── upate-orderbook.spec.ts │ │ └── update-orderbook.ts ├── index.ts └── lib │ └── isomorphic-ws │ ├── browser.js │ ├── index.d.ts │ ├── node.js │ └── package.json ├── test └── binance │ └── user-account.ts ├── tsconfig.backup.json ├── tsconfig.json ├── tslint.json ├── typings.d.ts └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:8.11 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: npm install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run build! 37 | - run: npm run build 38 | # - run: npm test 39 | 40 | 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # package lock file 9 | package-lock.json 10 | 11 | # dependencies 12 | node_modules 13 | */node_modules 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | 31 | # misc 32 | /.sass-cache 33 | /connect.lock 34 | /coverage 35 | /libpeerconnection.log 36 | npm-debug.log 37 | testem.log 38 | /typings 39 | 40 | # e2e 41 | /e2e/*.js 42 | /e2e/*.map 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | 48 | api-key-test.json 49 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": true, 9 | "arrowParens": "always", 10 | "parser": "typescript" 11 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | 6 | cache: 7 | directories: 8 | - "node_modules" 9 | 10 | script: 11 | - npm run build 12 | # - npm test 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "node", 3 | "request": "launch", 4 | "name": "Ccex-api test", 5 | "runtimeArgs": [ 6 | "-r", 7 | "ts-node/register" 8 | ], 9 | "args": [ 10 | "${workspaceFolder}/test/binance.ts" 11 | ] 12 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | ## Good practice 4 | 5 | ## Code of conduct 6 | 7 | ## Test 8 | -------------------------------------------------------------------------------- /assets/ccex-api-sample-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/assets/ccex-api-sample-structure.png -------------------------------------------------------------------------------- /gulpfile.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as ts from 'gulp-typescript'; 3 | import * as tsconfig from './tsconfig.json'; 4 | 5 | // compile ts 6 | gulp.task('compile', () => { 7 | return gulp.src(['src/**/*.ts']) 8 | .pipe(ts((tsconfig).compilerOptions)) 9 | .pipe(gulp.dest('dist')); 10 | }); 11 | 12 | // copy src/lib into dist 13 | gulp.task('copy:lib', () => { 14 | return gulp.src(['src/lib/**/*'], { base: 'src' }) 15 | .pipe(gulp.dest('dist')); 16 | }); 17 | 18 | // copy README.md package.json into dist 19 | gulp.task('copy', () => { 20 | return gulp.src(['README.md', 'package.json']) 21 | .pipe(gulp.dest('dist')); 22 | }); 23 | 24 | // build dist 25 | gulp.task('build', gulp.series('compile', 'copy:lib', 'copy')); 26 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rootDir: '.', 3 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], 4 | modulePathIgnorePatterns: ['dist'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: './tsconfig.json', 8 | }, 9 | }, 10 | transform: { 11 | '^.+\\.ts$': 'ts-jest', 12 | }, 13 | testMatch: ['/src/**/*.spec.ts'], 14 | // testMatch: ['/src/*/bitbank/**/*.spec.ts'], 15 | // "testRegex": "/src/.*binance.*\\.(test|spec).(ts)$", 16 | testURL: 'http://localhost/', 17 | collectCoverageFrom: ['src/**/*.{js,ts}', '!**/node_modules/**', '!**/vendor/**'], 18 | coveragePathIgnorePatterns: ['/node_modules/'], 19 | coverageReporters: ['json', 'lcov'], 20 | verbose: true, 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ccex-api", 3 | "version": "0.0.17", 4 | "description": "Crypto currencies exchanges api implementation using typescript and rxjs", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "build": "gulp build", 9 | "build:publish": "npm run build && npm publish dist", 10 | "format": "prettier --config .prettierrc --write src/**/*.ts", 11 | "lint": "tslint -c tslint.json --project tsconfig.json \"src/**/*.ts\"", 12 | "test-mocha": "mocha -r ts-node/register ./src/**/*.spec.ts", 13 | "test": "jest --runInBand --detectOpenHandles", 14 | "test-w": "npm test -- --watch --onlyChanged" 15 | }, 16 | "keywords": [ 17 | "crypto", 18 | "crypto currency", 19 | "cryptocurrency", 20 | "cryptocurrencies", 21 | "bitcoin", 22 | "exchange", 23 | "realtime api", 24 | "websocket api", 25 | "websocket", 26 | "pubnub", 27 | "bitfinex", 28 | "binance", 29 | "coinbase", 30 | "bitbank" 31 | ], 32 | "repository": "git@github.com:dang1412/ccex-api.git", 33 | "author": "Dang Thanh Tung", 34 | "license": "MIT", 35 | "dependencies": { 36 | "@types/node-fetch": "^2.3.5", 37 | "@types/pubnub": "^4.0.5", 38 | "@types/ws": "^6.0.1", 39 | "node-fetch": "^2.6.0", 40 | "pubnub": "^4.21.7", 41 | "querystring": "^0.2.0", 42 | "rxjs": "^6.4.0", 43 | "ws": "^6.1.3" 44 | }, 45 | "devDependencies": { 46 | "@types/gulp": "^4.0.5", 47 | "@types/jest": "^24.0.3", 48 | "@types/jest-diff": "^20.0.0", 49 | "@types/node": "^11.9.3", 50 | "gulp": "^4.0.0", 51 | "gulp-typescript": "^5.0.0", 52 | "jest": "^24.1.0", 53 | "prettier": "1.16.4", 54 | "ts-jest": "^23.10.5", 55 | "ts-node": "^8.0.1", 56 | "tslint": "^5.12.1", 57 | "tslint-eslint-rules": "^5.4.0", 58 | "tslint-no-unused-expression-chai": "^0.1.4", 59 | "typescript": "^3.3.3", 60 | "vrsource-tslint-rules": "^6.0.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /samples/react-ccex-api/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /samples/react-ccex-api/README.md: -------------------------------------------------------------------------------- 1 | # Ccex-api react sample usage 2 | 3 | `ccex-api` is referenced to the `/dist` folder so you need to build before running this sample. 4 | 5 | ```sh 6 | yarn build 7 | cd sample/react-ccex-api 8 | yarn install 9 | yarn start 10 | ``` 11 | -------------------------------------------------------------------------------- /samples/react-ccex-api/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | declare module '*.jpeg' 5 | declare module '*.gif' 6 | declare module '*.bmp' 7 | declare module '*.tiff' 8 | -------------------------------------------------------------------------------- /samples/react-ccex-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactjs-ccex-api", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^3.8.3", 7 | "react": "^16.7.0", 8 | "react-dom": "^16.7.0", 9 | "react-scripts-ts": "3.1.0" 10 | }, 11 | "scripts": { 12 | "start": "set PORT=4200 && react-scripts-ts start", 13 | "build": "react-scripts-ts build", 14 | "test": "react-scripts-ts test --env=jsdom", 15 | "eject": "react-scripts-ts eject", 16 | "postinstall": "node post-install.js" 17 | }, 18 | "devDependencies": { 19 | "@types/jest": "^23.3.12", 20 | "@types/node": "^10.12.18", 21 | "@types/react": "^16.8.3", 22 | "@types/react-dom": "^16.8.1", 23 | "symlink-dir": "^2.0.2", 24 | "typescript": "^3.2.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/react-ccex-api/post-install.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create symlink folder to root's dist as ccex-api 3 | */ 4 | 5 | 'use strict' 6 | const symlinkDir = require('symlink-dir') 7 | const path = require('path') 8 | const dist = path.resolve(__dirname, '../../dist') 9 | 10 | symlinkDir(dist, 'node_modules/ccex-api') 11 | .then(result => { 12 | console.log('symlink ccex-api created', result) 13 | }) 14 | .catch(err => console.error(err)) 15 | -------------------------------------------------------------------------------- /samples/react-ccex-api/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/react-ccex-api/public/favicon.ico -------------------------------------------------------------------------------- /samples/react-ccex-api/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | 23 | 24 | React App 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /samples/react-ccex-api/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './App.css'; 3 | 4 | import { Ticker } from 'ccex-api'; 5 | import { CoinbaseApi } from 'ccex-api/exchanges/coinbase'; 6 | import { TickerCompWithStyles } from './components'; 7 | const corsProxy = 'https://api.exchangecompare.com/'; 8 | 9 | interface IAppState { 10 | ticker: Ticker 11 | } 12 | 13 | class App extends React.Component<{}, IAppState> { 14 | private exchangeApi: CoinbaseApi; 15 | 16 | constructor(props: any) { 17 | super(props); 18 | this.exchangeApi = new CoinbaseApi({ corsProxy }); 19 | this.exchangeApi.ticker$('btc_usd').subscribe(ticker => { 20 | this.setState({ ticker }); 21 | }); 22 | } 23 | 24 | public render() { 25 | const tickerComponent = this.state 26 | ? 27 | : ''; 28 | 29 | return ( 30 |
31 | {/*
32 | logo 33 |

Welcome to React

34 |
35 |

36 | To get started, edit src/App.tsx and save to reload. 37 |

*/} 38 | 39 | {tickerComponent} 40 |
41 | ); 42 | } 43 | } 44 | 45 | export default App; 46 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/ChangeColor/ChangeColor.css: -------------------------------------------------------------------------------- 1 | /* .element { 2 | -moz-transition: all 0s ease-in; 3 | -o-transition: all 0s ease-in; 4 | -webkit-transition: all 0s ease-in; 5 | transition: all 0s ease-in; 6 | color: inherit; 7 | } 8 | 9 | .highlight-up { 10 | color: #84f766; 11 | } 12 | 13 | .highlight-down { 14 | color: #ff6939; 15 | } */ 16 | 17 | .highlight-up { 18 | animation-name: animatedUpColor; 19 | animation-duration: 1.2s; 20 | } 21 | 22 | .highlight-down { 23 | animation-name: animatedDownColor; 24 | animation-duration: 1.2s; 25 | } 26 | 27 | @keyframes animatedUpColor { 28 | from { color: #84f766; } 29 | to { color: currentValue; } 30 | } 31 | 32 | @keyframes animatedDownColor { 33 | from { color: #ff6939; } 34 | to { color: currentValue; } 35 | } 36 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/ChangeColor/ChangeColor.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './ChangeColor.css'; 3 | 4 | interface IChangeColorProps { 5 | value: number; 6 | } 7 | 8 | interface IChangeColorState { 9 | stateClass: string; 10 | } 11 | 12 | export class ChangeColor extends React.Component { 13 | private timeout: NodeJS.Timeout; 14 | 15 | public componentDidUpdate(prevProps: IChangeColorProps) { 16 | if (prevProps.value < this.props.value) { 17 | this.setState({ stateClass: ' highlight-up' }); 18 | } else if (prevProps.value > this.props.value) { 19 | this.setState({ stateClass: ' highlight-down' }); 20 | } else { 21 | return; 22 | } 23 | 24 | clearTimeout(this.timeout); 25 | this.timeout = setTimeout(() => { 26 | this.setState({ stateClass: '' }); 27 | }, 1200); 28 | } 29 | 30 | public render(): JSX.Element { 31 | const stateClass = this.state ? this.state.stateClass : ''; 32 | 33 | return 34 | {this.props.value} 35 | ; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/ChangeColor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChangeColor'; 2 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/Percent/Percent.css: -------------------------------------------------------------------------------- 1 | .percent-up { 2 | color: #46ad2c; 3 | } 4 | 5 | .percent-down { 6 | color: #d4481b; 7 | } 8 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/Percent/Percent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './Percent.css'; 3 | 4 | interface IPerscentProps { 5 | change: number | undefined; 6 | } 7 | 8 | export const Percent: React.SFC = (props) => { 9 | const change = props.change; 10 | if (change === undefined) { 11 | return ; 12 | } 13 | 14 | const className = change >= 0 ? 'percent-up' : 'percent-down'; 15 | 16 | return ({(change * 100).toFixed(2)}%); 17 | } 18 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/Percent/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Percent'; 2 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/Ticker/Ticker.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/react-ccex-api/src/components/Ticker/Ticker.css -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/Ticker/Ticker.test.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/react-ccex-api/src/components/Ticker/Ticker.test.tsx -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/Ticker/Ticker.tsx: -------------------------------------------------------------------------------- 1 | // import PropTypes from 'prop-types'; 2 | import { withStyles } from '@material-ui/core'; 3 | import Button from '@material-ui/core/Button'; 4 | import Card from '@material-ui/core/Card'; 5 | import CardActions from '@material-ui/core/CardActions'; 6 | import CardContent from '@material-ui/core/CardContent'; 7 | // import Typography from '@material-ui/core/Typography'; 8 | 9 | import { Ticker } from 'ccex-api'; 10 | import * as React from 'react'; 11 | 12 | import { ChangeColor } from '../ChangeColor'; 13 | import { Percent } from '../Percent'; 14 | 15 | export interface ITickerProps { 16 | tickerData: Ticker; 17 | classes: { 18 | card: string; 19 | tableTd: string; 20 | tableTh: string; 21 | }; 22 | } 23 | 24 | const styles = { 25 | card: { 26 | minWidth: 275, 27 | }, 28 | tableTd: { 29 | color: '#666', 30 | fontSize: 16, 31 | fontWeight: 'bold' as 'bold', 32 | padding: 5, 33 | textAlign: 'right' as 'right', 34 | }, 35 | tableTh: { 36 | color: '#aaa', 37 | fontSize: 12, 38 | padding: 5, 39 | textAlign: 'left' as 'left', 40 | }, 41 | }; 42 | 43 | export const TickerComp: React.SFC = (props) => { 44 | const { classes } = props; 45 | const ticker = props.tickerData; 46 | 47 | return ( 48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
Price Volume 24
Bid{ticker.bid}Ask{ticker.ask}
Low 24h{ticker.low}High 24h{ticker.high}
73 |
74 | 75 | 76 | 77 | 78 |
79 |
80 | ); 81 | }; 82 | 83 | export const TickerCompWithStyles = withStyles(styles)(TickerComp); 84 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/Ticker/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Ticker'; 2 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Ticker'; 2 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render( 8 | , 9 | document.getElementById('root') as HTMLElement 10 | ); 11 | registerServiceWorker(); 12 | -------------------------------------------------------------------------------- /samples/react-ccex-api/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/react-ccex-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["es6", "dom"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "importHelpers": true, 18 | "strictNullChecks": true, 19 | "suppressImplicitAnyIndexErrors": true, 20 | "noUnusedLocals": true 21 | }, 22 | "exclude": [ 23 | "node_modules", 24 | "build", 25 | "scripts", 26 | "acceptance-tests", 27 | "webpack", 28 | "jest", 29 | "src/setupTests.ts", 30 | "post-install.js" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /samples/react-ccex-api/tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /samples/react-ccex-api/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /samples/react-ccex-api/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "linterOptions": { 4 | "exclude": [ 5 | "config/**/*.js", 6 | "node_modules/**/*.ts", 7 | "coverage/lcov-report/*.js" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/crosshair.6c091f7d5427d0c5e6d9dc3a90eb2b20.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/bundles/crosshair.6c091f7d5427d0c5e6d9dc3a90eb2b20.cur -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/dot.ed68e83c16f77203e73dbc4c3a7c7fa1.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/bundles/dot.ed68e83c16f77203e73dbc4c3a7c7fa1.cur -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/eraser.0579d40b812fa2c3ffe72e5803a6e14c.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/bundles/eraser.0579d40b812fa2c3ffe72e5803a6e14c.cur -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/grab.bc156522a6b55a60be9fae15c14b66c5.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/bundles/grab.bc156522a6b55a60be9fae15c14b66c5.cur -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/grabbing.1c0862a8a8c0fb02885557bc97fdafe7.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/bundles/grabbing.1c0862a8a8c0fb02885557bc97fdafe7.cur -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/ie-fallback-logos.1e0142e3b30300ec0153.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([10],{695:function(A,h,i){"use strict";Object.defineProperty(h,"__esModule",{value:!0}),h.fallbackImages={tvLogoBlack:i(1348),tvLogoWhite:i(1349)},h.logoSizes={benzingapro:{width:135,height:25},bovespa:{width:135,height:50},cme:{width:175,height:26},currencywiki:{width:135,height:25},dailyfx:{width:135,height:25},fxstreet:{width:137,height:33},investopedia:{width:135,height:23},smartlab:{width:135,height:37},lse:{width:135,height:31},arabictrader:{width:135,height:40},goldprice:{width:135,height:27},silverprice:{width:135,height:27},inbestia:{width:195,height:50},immfx:{width:122,height:26},kitco:{width:130,height:35},enbourse:{width:135,height:40},rankia:{width:65,height:17},stockwatch:{width:135,height:19},tradecapitan:{width:121,height:45}}},1348:function(A,h){A.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAAmCAMAAACmhKjHAAAAXVBMVEUAAAAAAAAAAAAFEhgrg6g4rNsznskLIy0AAAAQMkAdWXIAAAAxlb4UQFEAAAAAAAAAAAAkcI8ujbQgZYEZTWMAAAAAAAAAAAA2pNIAAAAAAAAAAAAoe50AAAA7s+Q2ucaKAAAAHnRSTlMAmU2f2fnspiWswAnms3xCE8zfxrmPhjfybV9W0hyxWOJtAAABrElEQVRIx8WVbbOCIBCFrwmKCpnvlsb//5mXXcrlxcpm7sw9nwx4hrPLgX7+XBchxPdQfU2srlP1BXZLXC1H0WlIfI3HLC+4OC+ZlG1TWnQ6wN0QY/ohzlIYqC+fuBMsa7Qj3lu/71kBa5j2VSZWpzfgmbgd8ly9bKiZ7XUsLrscyVd24dhnva8VujRU+3kxU6V+pbkw07cYE+cE1MKaVql2x7CxO4ZY9YwZcvARkwy81gFnt0sVtkbBt4o5lN8f5MqMLBFInNU1zGcKGHLQBPztKksxi8as286Iy3MzwkMulStsWXnHJ50VKUNnBQ+4TEs/QANURCvy7JEzRQeBHJwlkk6yGXHqsVEPaSD3xUw9miihdlQartNOsStx3OnuSHfQjtnyNjK1Tnrinqd830BuuTwLj4Dh81EShw0SW40SOeXMb9kLkz/DU4LgHV+LbiuPBIPxFUUQNcKrBuVFahBs/AyhVRTcQyovDqiMXDzvxpDYzscqyCl1jG6lIEOhWMN8rtic0jO8Sv1BWQehXbwH/JjiV7IejlHxv1e1jIe2o/pI9+n0XjVQ/6ZfnFlWaxVUZmcAAAAASUVORK5CYII="},1349:function(A,h){A.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAAmCAMAAACmhKjHAAAAYFBMVEUAAAD////////q9/1ux+tSvOfZ8fr////I6vid2fL///9bwOm45Pb///////////+E0O9jw+mQ1fCq3vT///////9KuOb///////////94y+3///////////////87s+R/+BgSAAAAH3RSTlMAmU2f2eymJazACeazfEITzN/GuY+G8l9WOdJyaRwwr8L7/gAAAaRJREFUSMfFld2SgyAMhZeCP4i11qpV2y7v/5ZLwrgBYls7szN7rhT5hpOQxK8/16kois+h5ia8bnP1AXYVoaa96NyLWMM+y5MA5VoZc261R+cd3BUxZVcpCQvN6R13gG2tDXXxfl+zBexRNpYWXocX4JG4DfJYPU2o+3qxGzJdjuQzu3Dto93WAlnqq+16cZ+0faaxdJ+vHCuOAnSGPee6Pm+gzu6QYtVaZsjBAycVeG0Szh8na0xNDc8151BxfpDTGVkiMOHEPa1PCRgKkoDvoTKJtejMhulkXJ67FcZJs8CRdCV392qCHVKhszLlMmviAuohItqR4+E6DDNHDu4SyaCyFXE1dYUO3Jcj5WimCvWrxnGdDYJdiAuzO1AP+jUMjyS9kwtx6y0/IlBBeGkqVavjCsYEFb8xGuDYpWPtJZwdYZQg+MBp0VF4JLfIWxRB1ABTDcJjagWfQRlaRUEfUni8QA1zsfZGL3zmuUrmNJNBVxZkKJVqVcyV4DQZw4uxb5R1ULRTNMD3iU/Jpt9H8b9XNQ27jqP4SI/vw2s1QP2bfgDhqloD84hV5AAAAABJRU5ErkJggg=="}}); -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/propertypagesfactory.54b21a18753b2d8c83c2.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([11],{162:function(e,r){"use strict";r.createInputsPropertyPage=function(e,r){var t=e.getInputsPropertyPage();return null==t?null:new t(e.properties(),r,e)},r.createStudyStrategyPropertyPage=function(e,r){var t=e.getStrategyPropertyPage();return null==t?null:new t(e.properties(),r,e)},r.createStylesPropertyPage=function(e,r){var t=e.getStylesPropertyPage();return null==t?null:new t(e.properties(),r,e)},r.createDisplayPropertyPage=function(e,r){var t=e.getDisplayPropertyPage();return null==t?null:new t(e.properties(),r,e)},r.createVisibilitiesPropertyPage=function(e,r){var t=e.getVisibilitiesPropertyPage();return null==t?null:new t(e.properties(),r,e)},r.hasInputsPropertyPage=function(e){return null!==e.getInputsPropertyPage()},r.hasStylesPropertyPage=function(e){return null!==e.getStylesPropertyPage()}}}); -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/vendors.a94ef44ed5c201cefcf6ad7460788c1a.css: -------------------------------------------------------------------------------- 1 | @keyframes highlight-animation{0%{background:transparent}to{background:#fff2cf}}@keyframes highlight-animation-theme-dark{0%{background:transparent}to{background:#194453}} -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/bundles/zoom.e21f24dd632c7069139bc47ae89c54b5.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/bundles/zoom.e21f24dd632c7069139bc47ae89c54b5.cur -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/balloon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/balloon.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/bar-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/bar-loader.gif -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/button-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/button-bg.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/charting_library/logo-widget-copyright-faded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/charting_library/logo-widget-copyright-faded.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/charting_library/logo-widget-copyright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/charting_library/logo-widget-copyright.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/controlll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/controlll.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/delayed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/delayed.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/dialogs/checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/dialogs/checkbox.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/dialogs/close-flat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/dialogs/close-flat.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/dialogs/large-slider-handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/dialogs/large-slider-handle.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/dialogs/linewidth-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/dialogs/linewidth-slider.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/dialogs/opacity-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/dialogs/opacity-slider.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/icons.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/prediction-clock-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/prediction-clock-black.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/prediction-clock-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/prediction-clock-white.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/prediction-failure-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/prediction-failure-white.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/prediction-success-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/prediction-success-white.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/select-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/select-bg.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/sidetoolbar/instruments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/sidetoolbar/instruments.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/sidetoolbar/toolgroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/sidetoolbar/toolgroup.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/svg/chart/bucket2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/svg/chart/font.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/svg/chart/large-slider-handle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/svg/chart/pencil2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/svg/question-mark-rounded.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/tvcolorpicker-bg-gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/tvcolorpicker-bg-gradient.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/tvcolorpicker-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/tvcolorpicker-bg.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/tvcolorpicker-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/tvcolorpicker-check.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/tvcolorpicker-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/tvcolorpicker-sprite.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/images/warning-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/samples/webpack-chart/lib/charting_library/static/images/warning-icon.png -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/localization/translations/widgets-copyrights.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/localization/translations/widgets-copyrights.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "default": "by {0}" 3 | } 4 | -------------------------------------------------------------------------------- /samples/webpack-chart/lib/charting_library/static/tv-chart.82ee311dc10bb182c736.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /samples/webpack-chart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-ts-chart", 3 | "version": "0.0.1", 4 | "description": "d3 typescript chart library", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config src/sample/webpack-prod.config.ts", 8 | "start": "webpack-dev-server --watch", 9 | "postinstall": "node post-install.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [ 13 | "typescript", 14 | "ccex-api", 15 | "tradingview", 16 | "d3", 17 | "chart", 18 | "pie", 19 | "line" 20 | ], 21 | "author": "dang1412", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@types/html-webpack-plugin": "^3.2.0", 25 | "@types/node": "^10.12.18", 26 | "@types/webpack": "^4.4.24", 27 | "@types/webpack-dev-server": "^3.1.1", 28 | "html-webpack-plugin": "^3.2.0", 29 | "symlink-dir": "^2.0.2", 30 | "ts-loader": "^5.3.3", 31 | "ts-node": "^8.0.1", 32 | "typescript": "^3.3.3", 33 | "webpack": "^4.29.0", 34 | "webpack-cli": "^3.2.1", 35 | "webpack-dev-server": "^3.1.14" 36 | }, 37 | "dependencies": { 38 | "@types/d3": "^5.7.0", 39 | "d3": "^5.7.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/webpack-chart/post-install.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create symlink folder to root's dist as ccex-api 3 | */ 4 | 5 | 'use strict' 6 | const symlinkDir = require('symlink-dir') 7 | const path = require('path') 8 | const dist = path.resolve(__dirname, '../../dist') 9 | 10 | symlinkDir(dist, 'node_modules/ccex-api') 11 | .then(result => { 12 | console.log('symlink ccex-api created', result) 13 | }) 14 | .catch(err => console.error(err)) 15 | -------------------------------------------------------------------------------- /samples/webpack-chart/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /samples/webpack-chart/src/main.ts: -------------------------------------------------------------------------------- 1 | import { widget } from '../lib/charting_library/charting_library.min'; 2 | import { Datafeed } from './datafeed'; 3 | 4 | function initChart(): void { 5 | const tvWidget = ((window as any).tvWidget = new widget({ 6 | // debug: true, // uncomment this line to see Library errors and warnings in the console 7 | fullscreen: true, 8 | symbol: 'btc_jpy', 9 | interval: '60', 10 | container_id: 'tv_chart_container', 11 | // BEWARE: no trailing slash is expected in feed URL 12 | datafeed: new Datafeed(), 13 | library_path: 'charting_library/', 14 | locale: 'ja', 15 | // Regression Trend-related functionality is not implemented yet, so it's hidden for a while 16 | // drawings_access: { type: 'black', tools: [{ name: 'Regression Trend' }] }, 17 | disabled_features: ['use_localstorage_for_settings'], 18 | enabled_features: ['study_templates'], 19 | charts_storage_url: 'http://saveload.tradingview.com', 20 | charts_storage_api_version: '1.1', 21 | client_id: 'tradingview.com', 22 | user_id: 'public_user_id', 23 | })); 24 | } 25 | 26 | initChart(); 27 | -------------------------------------------------------------------------------- /samples/webpack-chart/tsconfig.json: -------------------------------------------------------------------------------- 1 | { // TypeScript configuration file: provides options to the TypeScript 2 | // compiler (tsc) and makes VSCode recognize this folder as a TS project, 3 | // enabling the VSCode build tasks "tsc: build" and "tsc: watch". 4 | "compilerOptions": { 5 | "lib": ["es2015", "dom"], 6 | "target": "es5", // Compatible with older browsers 7 | "module": "commonjs", // Compatible with both Node.js and browser 8 | "moduleResolution": "node", // Tell tsc to look in node_modules for modules 9 | "sourceMap": true, // Creates *.js.map files 10 | "strict": true, // Strict types, eg. prohibits `var x=0; x=null` 11 | "alwaysStrict": true // Enable JavaScript's "use strict" mode 12 | }, 13 | "include": [ 14 | "**/*.ts" 15 | ], 16 | "exclude": [ 17 | "node_modules", 18 | "**/*.spec.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /samples/webpack-chart/webpack-prod.config.ts: -------------------------------------------------------------------------------- 1 | import * as config from './webpack.config'; 2 | 3 | const prodConfig = { ...config, mode: 'production' }; 4 | 5 | // tslint:disable-next-line:no-default-export 6 | export default prodConfig; 7 | -------------------------------------------------------------------------------- /samples/webpack-chart/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import { join, resolve } from 'path'; 2 | import { Configuration } from 'webpack'; 3 | import * as HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | 5 | const config: Configuration = { 6 | mode: 'development', 7 | entry: resolve(__dirname, 'src/main.ts'), 8 | module: { 9 | rules: [ 10 | // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` 11 | { test: /\.ts?$/, loader: 'ts-loader' }, 12 | ], 13 | }, 14 | resolve: { 15 | // Add `.ts` and `.tsx` as a resolvable extension. 16 | extensions: ['.ts', '.js'], 17 | }, 18 | output: { 19 | path: resolve(__dirname, 'dist'), 20 | filename: 'bundle.js', 21 | }, 22 | plugins: [ 23 | new HtmlWebpackPlugin({ 24 | template: resolve(__dirname, 'src/index.html'), 25 | }), 26 | ], 27 | devServer: { 28 | contentBase: join(__dirname, 'lib'), 29 | compress: true, 30 | port: 4200, 31 | }, 32 | }; 33 | 34 | // tslint:disable-next-line:no-default-export 35 | export default config; 36 | -------------------------------------------------------------------------------- /src/common/fetch-rxjs.ts: -------------------------------------------------------------------------------- 1 | import { Observable, from } from 'rxjs'; 2 | import fetch from 'node-fetch'; 3 | 4 | // note: this stream is hot (fetch function run without subscribe) 5 | export function fetchRxjs(url: string, options?: {}): Observable { 6 | // patch the error in browser running webpack built js: change the context of fetch to not window object 7 | const fetchFunc = fetch; 8 | 9 | return from(fetchFunc(url, options).then((res) => res.json())); 10 | } 11 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fetch-rxjs'; 2 | export * from './pubnub-rxjs'; 3 | export * from './websocket-rxjs'; 4 | export * from './websocket-rxjs.mock'; 5 | -------------------------------------------------------------------------------- /src/common/pubnub-rxjs.ts: -------------------------------------------------------------------------------- 1 | // RxJs wrapper for pubnub 2 | 3 | import { Observable, ReplaySubject } from 'rxjs'; 4 | import { filter } from 'rxjs/operators'; 5 | import * as Pubnub from 'pubnub'; 6 | 7 | export class PubnubRxJs { 8 | private readonly pubnub: Pubnub; 9 | // use one subject for each distinct channel 10 | private readonly channelSubjectMap: { [channel: string]: ReplaySubject } = {}; 11 | 12 | constructor(config: Pubnub.PubnubConfig) { 13 | this.pubnub = new Pubnub(config); 14 | this.addEventListener(); 15 | } 16 | 17 | /** 18 | * @param channel 19 | */ 20 | subscribeChannel(channel: string): Observable { 21 | this.pubnub.subscribe({ 22 | channels: [channel], 23 | }); 24 | 25 | return this.getChannelSubject(channel).pipe(filter((data) => !!data)); 26 | } 27 | 28 | /** 29 | * @param channel 30 | */ 31 | unsubscribeChannel(channel: string): void { 32 | this.pubnub.unsubscribe({ 33 | channels: [channel], 34 | }); 35 | } 36 | 37 | /** 38 | * Register message event listener for pubnub instance 39 | * @param {PUBNUB} pubnub 40 | */ 41 | private addEventListener(): void { 42 | this.pubnub.addListener({ 43 | status: (statusEvent: any) => { 44 | if (statusEvent.category === 'PNConnectedCategory') { 45 | // pubnub connected 46 | } 47 | }, 48 | message: (messageEvent) => { 49 | if (messageEvent && messageEvent.channel && messageEvent.message) { 50 | this.getChannelSubject(messageEvent.channel).next(messageEvent.message); 51 | } 52 | }, 53 | }); 54 | } 55 | 56 | private getChannelSubject(channel: string): ReplaySubject { 57 | if (!this.channelSubjectMap[channel]) { 58 | this.channelSubjectMap[channel] = new ReplaySubject(1); 59 | } 60 | 61 | return this.channelSubjectMap[channel]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/common/websocket-rxjs.mock.ts: -------------------------------------------------------------------------------- 1 | import { ReplaySubject, Observable } from 'rxjs'; 2 | import { WebSocketRxJs } from './websocket-rxjs'; 3 | 4 | interface MockMessage { 5 | // number in second 6 | time: number; 7 | // message payload 8 | payload: T | 'stop'; 9 | } 10 | 11 | type MockOf = { [Member in keyof Class]: Class[Member] }; 12 | 13 | export interface MockDataScheme { 14 | [key: string]: MockMessage[]; 15 | } 16 | 17 | export class WebsocketRxJsMock implements MockOf { 18 | private readonly data$ = new ReplaySubject(1); 19 | 20 | get message$(): Observable { 21 | return this.data$.asObservable(); 22 | } 23 | 24 | constructor(private readonly mockDataScheme: MockDataScheme) { 25 | if (mockDataScheme['default']) { 26 | this.receive(mockDataScheme.default); 27 | } 28 | } 29 | 30 | send(text: string): void { 31 | if (this.mockDataScheme[text]) { 32 | this.receive(this.mockDataScheme[text]); 33 | } 34 | } 35 | 36 | close(): void { 37 | this.data$.complete(); 38 | } 39 | 40 | private receive(messages: MockMessage[]): void { 41 | messages.forEach((m) => { 42 | setTimeout(() => { 43 | m.payload === 'stop' ? this.close() : this.data$.next(m.payload); 44 | }, m.time); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/common/websocket-rxjs.ts: -------------------------------------------------------------------------------- 1 | // RxJs wrapper for websocket 2 | import { Observable, ReplaySubject } from 'rxjs'; 3 | import { take, filter } from 'rxjs/operators'; 4 | import * as WebSocket from '../lib/isomorphic-ws'; 5 | 6 | export class WebSocketRxJs { 7 | private readonly webSocket: WebSocket; 8 | private readonly data$ = new ReplaySubject(1); 9 | private readonly opened$ = new ReplaySubject(1); 10 | 11 | /** 12 | * message stream 13 | */ 14 | get message$(): Observable { 15 | return this.data$.asObservable(); 16 | } 17 | 18 | constructor(url: string) { 19 | this.webSocket = new WebSocket(url); 20 | this.webSocket.onopen = () => { 21 | this.opened$.next(true); 22 | }; 23 | this.webSocket.onclose = () => { 24 | this.opened$.next(false); 25 | }; 26 | this.webSocket.onerror = () => { 27 | this.opened$.next(false); 28 | }; 29 | this.webSocket.onmessage = (e: any) => { 30 | try { 31 | const data = JSON.parse(e.data); 32 | this.data$.next(data); 33 | } catch (error) { 34 | console.error(error); 35 | } 36 | }; 37 | } 38 | 39 | /** 40 | * @param text 41 | */ 42 | send(text: string): void { 43 | // wait until socket open and send the text only once per call 44 | this.opened$ 45 | .pipe( 46 | take(1), 47 | filter((opened) => opened), 48 | ) 49 | .subscribe(() => { 50 | this.webSocket.send(text); 51 | }); 52 | } 53 | 54 | close(): void { 55 | this.webSocket.close(); 56 | this.data$.complete(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/exchanges/binance/README.md: -------------------------------------------------------------------------------- 1 | # Binance specific API -------------------------------------------------------------------------------- /src/exchanges/binance/api-key-test.ts.org: -------------------------------------------------------------------------------- 1 | { 2 | "key": "", 3 | "secret": "" 4 | } 5 | -------------------------------------------------------------------------------- /src/exchanges/binance/api-private/binance-api-private-signed.ts: -------------------------------------------------------------------------------- 1 | import * as qs from 'querystring'; 2 | import { Observable } from 'rxjs'; 3 | import * as crypto from 'crypto'; 4 | 5 | import { fetchRxjs } from '../../../common'; 6 | import { apiEndPoint } from '../binance-common'; 7 | import { BinanceAccountInformation } from './internal/types'; 8 | 9 | enum PrivateUrlSigned { 10 | account = 'api/v3/account', 11 | } 12 | 13 | export class BinanceApiPrivateSigned { 14 | private readonly key: string; 15 | private readonly secret: string; 16 | private readonly corsProxy: string; 17 | 18 | constructor(key: string, secret: string, corsProxy: string = '') { 19 | this.key = key; 20 | this.secret = secret; 21 | this.corsProxy = corsProxy; 22 | } 23 | 24 | getAccountInformation(): Observable { 25 | const params = { 26 | timestamp: Date.now(), 27 | }; 28 | 29 | const queryString = qs.stringify(params); 30 | const originUrl = `${apiEndPoint}/${PrivateUrlSigned.account}?${queryString}&signature=${this.sign(queryString)}`; 31 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 32 | 33 | const fetchOptions = { 34 | method: 'GET', // *GET, POST, PUT, DELETE, etc. 35 | headers: { 36 | 'X-MBX-APIKEY': this.key, 37 | }, 38 | // redirect: 'follow', // manual, *follow, error 39 | // referrer: 'no-referrer', // no-referrer, *client 40 | // body: JSON.stringify(data), // body data type must match 'Content-Type' header 41 | }; 42 | 43 | return fetchRxjs(url, fetchOptions); 44 | } 45 | 46 | private sign(queryString: string): string { 47 | return crypto 48 | .createHmac('sha256', this.secret) 49 | .update(queryString) 50 | .digest('hex'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/exchanges/binance/api-private/binance-api-private.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable-next-line */ 2 | const apiKeys = require('./api-key-test'); 3 | import { BinanceApiPrivate } from './binance-api-private'; 4 | import { BinanceApiPrivateSigned } from './binance-api-private-signed'; 5 | import { checkBinanceAccountInformation } from './internal/functions-test'; 6 | 7 | const binanceApiPrivateSigned = new BinanceApiPrivateSigned(apiKeys.key, apiKeys.secret); 8 | const binanceApiPrivate = new BinanceApiPrivate(apiKeys.key); 9 | 10 | describe('Test binance api private functions', () => { 11 | it('Should fetch account information', (done) => { 12 | binanceApiPrivateSigned.getAccountInformation().subscribe((accountInfo) => { 13 | checkBinanceAccountInformation(accountInfo); 14 | done(); 15 | }); 16 | }); 17 | 18 | it('Should fetch user data stream listen key', (done) => { 19 | binanceApiPrivate.getUserStreamListenKey$().subscribe((listenKey) => { 20 | expect(listenKey); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/exchanges/binance/api-private/binance-api-private.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { fetchRxjs } from '../../../common'; 3 | 4 | import { apiEndPoint } from '../binance-common'; 5 | import { BinanceUserStreamPostResponse } from './internal/types'; 6 | import { map } from 'rxjs/operators'; 7 | 8 | enum PrivateUrl { 9 | userStream = 'api/v1/userDataStream', 10 | } 11 | 12 | export class BinanceApiPrivate { 13 | private readonly key: string; 14 | private readonly corsProxy: string; 15 | 16 | constructor(key: string, corsProxy?: string) { 17 | this.key = key; 18 | this.corsProxy = corsProxy || ''; 19 | } 20 | 21 | getUserStreamListenKey$(): Observable { 22 | const originUrl = `${apiEndPoint}/${PrivateUrl.userStream}`; 23 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 24 | 25 | const fetchOptions = { 26 | method: 'POST', 27 | headers: { 28 | 'X-MBX-APIKEY': this.key, 29 | }, 30 | }; 31 | 32 | return fetchRxjs(url, fetchOptions).pipe(map((res) => res.listenKey)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/exchanges/binance/api-private/internal/functions-test.ts: -------------------------------------------------------------------------------- 1 | import { BinanceAccountInformation } from './types'; 2 | 3 | export function checkBinanceAccountInformation(binanceAccountInformation: BinanceAccountInformation): void { 4 | expect(binanceAccountInformation); 5 | expect(binanceAccountInformation.balances).not.toBeNull(); 6 | expect(binanceAccountInformation.buyerCommission).not.toBeNull(); 7 | expect(binanceAccountInformation.canDeposit).not.toBeNull(); 8 | expect(binanceAccountInformation.canTrade).not.toBeNull(); 9 | expect(binanceAccountInformation.canWithdraw).not.toBeNull(); 10 | } 11 | -------------------------------------------------------------------------------- /src/exchanges/binance/api-private/internal/functions.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/binance/api-private/internal/functions.ts -------------------------------------------------------------------------------- /src/exchanges/binance/api-private/internal/types.ts: -------------------------------------------------------------------------------- 1 | export interface AssetItem { 2 | asset: string; 3 | free: string; 4 | lock: string; 5 | } 6 | 7 | export interface BinanceAccountInformation { 8 | makerCommission: number; 9 | takerCommission: number; 10 | buyerCommission: number; 11 | sellerCommission: number; 12 | canTrade: boolean; 13 | canWithdraw: boolean; 14 | canDeposit: boolean; 15 | updateTime: number; 16 | balances: AssetItem[]; 17 | } 18 | 19 | // { 20 | // 'makerCommission': 15, 21 | // 'takerCommission': 15, 22 | // 'buyerCommission': 0, 23 | // 'sellerCommission': 0, 24 | // 'canTrade': true, 25 | // 'canWithdraw': true, 26 | // 'canDeposit': true, 27 | // 'updateTime': 123456789, 28 | // 'balances': [ 29 | // { 30 | // 'asset': 'BTC', 31 | // 'free': '4723846.89208129', 32 | // 'locked': '0.00000000' 33 | // }, 34 | // { 35 | // 'asset': 'LTC', 36 | // 'free': '4763368.68006011', 37 | // 'locked': '0.00000000' 38 | // } 39 | // ] 40 | // } 41 | 42 | export interface BinanceUserStreamPostResponse { 43 | listenKey: string; 44 | } 45 | 46 | // { 47 | // "listenKey": "pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1" 48 | // } 49 | -------------------------------------------------------------------------------- /src/exchanges/binance/binance-common.ts: -------------------------------------------------------------------------------- 1 | export const apiEndPoint = 'https://api.binance.com'; 2 | export const wsEndpoint = 'wss://stream2.binance.com:9443/ws'; 3 | 4 | // get binance pair 5 | export function binancePair(pair: string): string { 6 | return pair.replace('_', '').toLowerCase(); 7 | } 8 | -------------------------------------------------------------------------------- /src/exchanges/binance/binance-types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/binance/binance-types.ts -------------------------------------------------------------------------------- /src/exchanges/binance/candlestick/binance-candlestick.spec.ts: -------------------------------------------------------------------------------- 1 | import { timer } from 'rxjs'; 2 | import { take, skipUntil } from 'rxjs/operators'; 3 | 4 | import { checkCandleStick } from '../../exchange-test.functions'; 5 | import { BinanceWebsocket } from '../websocket'; 6 | import { BinanceCandleStick } from './binance-candlestick'; 7 | 8 | const binanceWebsocket = new BinanceWebsocket(); 9 | const binanceCandlestick = new BinanceCandleStick(binanceWebsocket); 10 | const minutesFoot = 5; 11 | const pair = 'btc_usdt'; 12 | 13 | const otherMinutesFoot = 1; 14 | const timeToStop = 1000; 15 | 16 | beforeEach(() => { 17 | binanceCandlestick.stop(pair, minutesFoot); 18 | }); 19 | 20 | describe('Test binance candlestick functions', () => { 21 | jest.setTimeout(10000); 22 | 23 | it(`should fetch ${pair} ${minutesFoot}min candles in provided time range`, async () => { 24 | const candles = await binanceCandlestick.fetchRange(pair, minutesFoot, 1529509826239 - 60000 * 60 * 24, 1529509826239); 25 | candles.forEach(checkCandleStick); 26 | }); 27 | 28 | it(`should get ${pair} ${minutesFoot}min last candle realtime`, (done) => { 29 | binanceCandlestick 30 | .stream$(pair, minutesFoot) 31 | .pipe(take(2)) 32 | .subscribe( 33 | (candle) => { 34 | checkCandleStick(candle); 35 | }, 36 | () => console.log('error'), 37 | () => { 38 | done(); 39 | }, 40 | ); 41 | }); 42 | 43 | // it(`should complete stream when stop candle socket`, (done) => { 44 | // binanceCandlestick.stream$(pair, minutesFoot).subscribe( 45 | // () => { 46 | // /**/ 47 | // }, 48 | // () => console.log('error'), 49 | // () => { 50 | // done(); 51 | // }, 52 | // ); 53 | 54 | // setTimeout(() => binanceCandlestick.stop(pair, minutesFoot), 1000); 55 | // }); 56 | 57 | it(`should not complete stream when stop same pair but different minutesFoot candle socket`, (done) => { 58 | let completeOtherCandleStream = false; 59 | 60 | binanceCandlestick.stream$(pair, otherMinutesFoot).subscribe( 61 | (candle) => { 62 | checkCandleStick(candle); 63 | }, 64 | () => console.log('error'), 65 | () => { 66 | completeOtherCandleStream = true; 67 | }, 68 | ); 69 | 70 | binanceCandlestick 71 | .stream$(pair, minutesFoot) 72 | .pipe(skipUntil(timer(timeToStop + 200))) 73 | .subscribe((candle) => { 74 | checkCandleStick(candle); 75 | expect(completeOtherCandleStream); 76 | binanceCandlestick.stop(pair, minutesFoot); 77 | done(); 78 | }); 79 | 80 | setTimeout(() => binanceCandlestick.stop(pair, otherMinutesFoot), timeToStop); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/exchanges/binance/candlestick/binance-candlestick.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { CandleStick } from '../../exchange-types'; 7 | import { BinanceWebsocket } from '../websocket'; 8 | import { BinanceRestCandle, BinanceWsCandle } from './internal/types'; 9 | import { binanceCandleStickApiUrl, adaptBinanceRestCandle, adaptBinanceWsCandle, getCandleStickChannel } from './internal/functions'; 10 | 11 | export class BinanceCandleStick { 12 | constructor(private readonly binanceWebsocket: BinanceWebsocket, private readonly corsProxy: string = '') {} 13 | 14 | async fetchRange(pair: string, minutesFoot: number, start: number, end: number): Promise { 15 | const originUrl = binanceCandleStickApiUrl(pair, minutesFoot, start, end); 16 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 17 | 18 | const raws: BinanceRestCandle[] = await fetch(url).then(res => res.json()); 19 | 20 | return raws.map(adaptBinanceRestCandle); 21 | } 22 | 23 | stream$(pair: string, minutesFoot: number): Observable { 24 | const channel = getCandleStickChannel(pair, minutesFoot); 25 | 26 | return this.binanceWebsocket.subscribeChannel(channel).pipe( 27 | map(adaptBinanceWsCandle), 28 | ); 29 | } 30 | 31 | stop(pair: string, minutesFoot: number): void { 32 | const channel = getCandleStickChannel(pair, minutesFoot); 33 | this.binanceWebsocket.unsubscribeChannel(channel); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/exchanges/binance/candlestick/index.ts: -------------------------------------------------------------------------------- 1 | export { BinanceCandleStick } from './binance-candlestick'; 2 | -------------------------------------------------------------------------------- /src/exchanges/binance/candlestick/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { CandleStick } from '../../../exchange-types'; 2 | import { apiEndPoint, binancePair } from '../../binance-common'; 3 | import { BinanceRestCandle, BinanceWsCandle } from './types'; 4 | 5 | // candlestick rest api url 6 | export function binanceCandleStickApiUrl(pair: string, minutesFoot: number, startTime?: number, endTime?: number, limit?: number): string { 7 | const symbol = binancePair(pair).toUpperCase(); 8 | const interval = getCandleInterval(minutesFoot); 9 | let url = `${apiEndPoint}/api/v1/klines?symbol=${symbol}&interval=${interval}`; 10 | 11 | // if startTime provided and no limit, calculate to use limit and drop startTime 12 | let startTimeAdjusted = startTime; 13 | let limitAdjusted = limit; 14 | if (startTime && endTime && !limit) { 15 | limitAdjusted = Math.round((endTime - startTime) / (minutesFoot * 60 * 1000)) + 1; 16 | startTimeAdjusted = 0; 17 | } 18 | 19 | if (startTimeAdjusted) { 20 | url += `&startTime=${startTimeAdjusted}`; 21 | } 22 | if (endTime) { 23 | url += `&endTime=${endTime}`; 24 | } 25 | if (limitAdjusted) { 26 | url += `&limit=${limitAdjusted}`; 27 | } 28 | 29 | return url; 30 | } 31 | 32 | // candlestick ws channel 33 | export function getCandleStickChannel(pair: string, minutesFoot: number): string { 34 | const interval = getCandleInterval(minutesFoot); 35 | 36 | return `${binancePair(pair)}@kline_${interval}`; 37 | } 38 | 39 | export function adaptBinanceRestCandle(binanceCandle: BinanceRestCandle): CandleStick { 40 | return { 41 | open: +binanceCandle[1], 42 | high: +binanceCandle[2], 43 | low: +binanceCandle[3], 44 | close: +binanceCandle[4], 45 | volume: +binanceCandle[5], 46 | timestamp: binanceCandle[0], 47 | }; 48 | } 49 | 50 | export function adaptBinanceWsCandle(binanceCandle: BinanceWsCandle): CandleStick { 51 | return { 52 | open: +binanceCandle.k.o, 53 | high: +binanceCandle.k.h, 54 | low: +binanceCandle.k.l, 55 | close: +binanceCandle.k.c, 56 | volume: +binanceCandle.k.v, 57 | timestamp: binanceCandle.k.t, 58 | }; 59 | } 60 | 61 | const minutesIntervalMap: { [key: number]: string } = { 62 | 1: '1m', 63 | 3: '3m', 64 | 5: '5m', 65 | 15: '15m', 66 | 30: '30m', 67 | 60: '1h', 68 | 120: '2h', 69 | 240: '4h', 70 | 360: '6h', 71 | 480: '8h', 72 | 720: '12h', 73 | 1440: '1d', 74 | 4320: '3d', 75 | 10080: '1w', 76 | }; 77 | 78 | function getCandleInterval(minutesFoot: number): string { 79 | return minutesIntervalMap[minutesFoot] || ''; 80 | } 81 | -------------------------------------------------------------------------------- /src/exchanges/binance/candlestick/internal/types.ts: -------------------------------------------------------------------------------- 1 | // [ 2 | // 1499040000000, // Open time 3 | // "0.01634790", // Open 4 | // "0.80000000", // High 5 | // "0.01575800", // Low 6 | // "0.01577100", // Close 7 | // "148976.11427815", // Volume 8 | // 1499644799999, // Close time 9 | // "2434.19055334", // Quote asset volume 10 | // 308, // Number of trades 11 | // "1756.87402397", // Taker buy base asset volume 12 | // "28.46694368", // Taker buy quote asset volume 13 | // "17928899.62484339" // Ignore 14 | // ] 15 | export type BinanceRestCandle = [ 16 | number, // Open time 17 | string, // Open price 18 | string, // High 19 | string, // Low 20 | string, // Close 21 | string, // Volume 22 | number, // Close time 23 | string, // Quote asset volume 24 | number, // Number of trades 25 | string, // Taker buy base asset volume 26 | string, // Taker buy quote asset volume 27 | string // Ignore 28 | ]; 29 | 30 | // { 31 | // "e": "kline", // Event type 32 | // "E": 123456789, // Event time 33 | // "s": "BNBBTC", // Symbol 34 | // "k": { 35 | // "t": 123400000, // Kline start time 36 | // "T": 123460000, // Kline close time 37 | // "s": "BNBBTC", // Symbol 38 | // "i": "1m", // Interval 39 | // "f": 100, // First trade ID 40 | // "L": 200, // Last trade ID 41 | // "o": "0.0010", // Open price 42 | // "c": "0.0020", // Close price 43 | // "h": "0.0025", // High price 44 | // "l": "0.0015", // Low price 45 | // "v": "1000", // Base asset volume 46 | // "n": 100, // Number of trades 47 | // "x": false, // Is this kline closed? 48 | // "q": "1.0000", // Quote asset volume 49 | // "V": "500", // Taker buy base asset volume 50 | // "Q": "0.500", // Taker buy quote asset volume 51 | // "B": "123456" // Ignore 52 | // } 53 | // } 54 | export interface BinanceWsCandle { 55 | e: 'kline'; 56 | E: number; // Event time 57 | s: string; // Symbol 58 | k: { 59 | t: number; // Kline start time 60 | T: number; // Kline close time 61 | s: string; // Symbol 62 | i: string; // Interval 63 | f: number; // First trade ID 64 | L: number; // Last trade ID 65 | o: string; // Open price 66 | c: string; // Close price 67 | h: string; // High price 68 | l: string; // Low price 69 | v: string; // Base asset volume 70 | n: number; // Number of trades 71 | x: boolean; // Is this kline closed? 72 | q: string; // Quote asset volume 73 | V: string; // Taker buy base asset volume 74 | Q: string; // Taker buy quote asset volume 75 | B: string; // Ignore 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /src/exchanges/binance/index.ts: -------------------------------------------------------------------------------- 1 | export { BinanceApi } from './binance-api'; 2 | -------------------------------------------------------------------------------- /src/exchanges/binance/orderbook/binance-orderbook.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkOrderbook } from '../../exchange-test.functions'; 4 | import { BinanceOrderbook } from './binance-orderbook'; 5 | import { BinanceWebsocket } from '../websocket'; 6 | 7 | describe('binanceOrderbook', () => { 8 | jest.setTimeout(10000); 9 | 10 | const binanceWebsocket = new BinanceWebsocket(); 11 | const binanceOrderbook = new BinanceOrderbook(binanceWebsocket); 12 | 13 | const markets = ['btc_usdt', 'eos_btc', 'eos_usdt']; 14 | 15 | markets.forEach((market) => { 16 | it(`should fetch orderbook ${market}`, async () => { 17 | const orderbook = await binanceOrderbook.fetch(market); 18 | checkOrderbook(orderbook); 19 | }); 20 | }); 21 | 22 | markets.forEach((market) => { 23 | it(`should get orderbook realtime ${market}`, (done) => { 24 | binanceOrderbook 25 | .stream$(market) 26 | .pipe(take(2)) 27 | .subscribe( 28 | (orderbook) => { 29 | checkOrderbook(orderbook); 30 | }, 31 | () => { 32 | /* error */ 33 | }, 34 | () => { 35 | binanceOrderbook.stop(market); 36 | done(); 37 | }, 38 | ); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/exchanges/binance/orderbook/index.ts: -------------------------------------------------------------------------------- 1 | export { BinanceOrderbook } from './binance-orderbook'; 2 | -------------------------------------------------------------------------------- /src/exchanges/binance/orderbook/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Orderbook } from '../../../exchange-types'; 2 | import { apiEndPoint, binancePair } from '../../binance-common'; 3 | import { BinanceWsUpdateOrderbook } from './types'; 4 | 5 | // orderbook rest api url 6 | export function binanceOrderbookApiUrl(pair: string, limit: number = 20): string { 7 | const symbol = binancePair(pair).toUpperCase(); 8 | 9 | return `${apiEndPoint}/api/v1/depth?limit=${limit}&symbol=${symbol}`; 10 | } 11 | 12 | // orderbook ws channel 13 | export function getOrderbookChannel(pair: string): string { 14 | return `${binancePair(pair)}@depth`; 15 | } 16 | 17 | // adapt socket orderbook 18 | export function adaptBinanceWsOrderbook(binanceOrderbook: BinanceWsUpdateOrderbook): Orderbook { 19 | return { 20 | bids: binanceOrderbook.b, 21 | asks: binanceOrderbook.a, 22 | lastUpdateId: binanceOrderbook.u, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/exchanges/binance/orderbook/internal/types.ts: -------------------------------------------------------------------------------- 1 | export interface BinanceOrderbookType { 2 | lastUpdateId: number; 3 | bids: [string, string][]; 4 | asks: [string, string][]; 5 | } 6 | 7 | // { 8 | // "e": "depthUpdate", // Event type 9 | // "E": 123456789, // Event time 10 | // "s": "BNBBTC", // Symbol 11 | // "U": 157, // First update ID in event 12 | // "u": 160, // Final update ID in event 13 | // "b": [ // Bids to be updated 14 | // [ 15 | // "0.0024", // Price level to be updated 16 | // "10" // Quantity 17 | // ] 18 | // ], 19 | // "a": [ // Asks to be updated 20 | // [ 21 | // "0.0026", // Price level to be updated 22 | // "100" // Quantity 23 | // ] 24 | // ] 25 | // } 26 | export interface BinanceWsUpdateOrderbook { 27 | // event type 'depthUpdate' 28 | e: 'depthUpdate'; 29 | // event time 30 | E: number; 31 | // symbol 'BNBBTC' 32 | s: string; 33 | // first update id 34 | U: number; 35 | // last update id 36 | u: number; 37 | // bids 38 | b: [string, string][]; 39 | // asks 40 | a: [string, string][]; 41 | } 42 | -------------------------------------------------------------------------------- /src/exchanges/binance/ticker/binance-ticker.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkTicker } from '../../exchange-test.functions'; 4 | import { BinanceTicker } from './binance-ticker'; 5 | 6 | import { MOCK_SOCKET } from './test-helpers'; 7 | import { BinanceWebsocket } from '../websocket'; 8 | 9 | const pair = 'btc_usdt'; 10 | 11 | describe('binanceTicker', () => { 12 | const binanceWebsocket = new BinanceWebsocket(); 13 | (binanceWebsocket as any).ws = MOCK_SOCKET; 14 | 15 | const binanceTicker = new BinanceTicker(binanceWebsocket); 16 | 17 | it(`should fetch ticker ${pair}`, async () => { 18 | const ticker = await binanceTicker.fetch(pair); 19 | checkTicker(ticker); 20 | }); 21 | 22 | it(`should get ticker realtime ${pair}`, async () => { 23 | const ticker = await binanceTicker.stream$(pair).pipe(take(1)).toPromise(); 24 | checkTicker(ticker); 25 | 26 | binanceTicker.stop(pair); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/exchanges/binance/ticker/binance-ticker.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { Ticker } from '../../exchange-types'; 7 | import { BinanceWsTicker, BinanceRestTicker } from './internal/types'; 8 | import { adaptBinanceWsTicker, adaptBinanceRestTicker, binanceTickerApiUrl, getTickerChannel } from './internal/functions'; 9 | import { BinanceWebsocket } from '../websocket'; 10 | 11 | export class BinanceTicker { 12 | /** 13 | * 14 | * @param binanceWebsocket 15 | * @param corsProxy 16 | */ 17 | constructor(private readonly binanceWebsocket: BinanceWebsocket, private readonly corsProxy: string = '') {} 18 | 19 | async fetch(pair: string): Promise { 20 | const originUrl = binanceTickerApiUrl(pair); 21 | const url = this.corsProxy ? `${this.corsProxy}${originUrl}` : originUrl; 22 | 23 | const rawTicker: BinanceRestTicker = await fetch(url).then(res => res.json()); 24 | 25 | return adaptBinanceRestTicker(rawTicker, pair); 26 | } 27 | 28 | stream$(pair: string): Observable { 29 | const channel = getTickerChannel(pair); 30 | 31 | return this.binanceWebsocket.subscribeChannel(channel).pipe( 32 | map((binanceTicker) => adaptBinanceWsTicker(binanceTicker, pair)), 33 | ) 34 | } 35 | 36 | stop(pair: string): void { 37 | const channel = getTickerChannel(pair); 38 | this.binanceWebsocket.unsubscribeChannel(channel); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/exchanges/binance/ticker/index.ts: -------------------------------------------------------------------------------- 1 | export { BinanceTicker } from './binance-ticker'; 2 | -------------------------------------------------------------------------------- /src/exchanges/binance/ticker/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from '../../../exchange-types'; 2 | import { wsEndpoint, apiEndPoint, binancePair } from '../../binance-common'; 3 | import { BinanceRestTicker, BinanceWsTicker } from './types'; 4 | 5 | // ticker rest api url 6 | export function binanceTickerApiUrl(pair: string): string { 7 | const symbol = binancePair(pair).toUpperCase(); 8 | 9 | return `${apiEndPoint}/api/v1/ticker/24hr?symbol=${symbol}`; 10 | } 11 | 12 | // ticker ws channel 13 | export function getTickerChannel(pair: string): string { 14 | return `${binancePair(pair)}@ticker`; 15 | } 16 | 17 | // adapt rest ticker 18 | export function adaptBinanceRestTicker(binanceTicker: BinanceRestTicker, pair: string): Ticker { 19 | return { 20 | pair, 21 | ask: +binanceTicker.askPrice, 22 | bid: +binanceTicker.bidPrice, 23 | low: +binanceTicker.lowPrice, 24 | high: +binanceTicker.highPrice, 25 | last: +binanceTicker.lastPrice, 26 | vol: +binanceTicker.volume, 27 | change24: +binanceTicker.priceChange, 28 | change24Perc: +binanceTicker.priceChangePercent / 100, 29 | timestamp: +binanceTicker.closeTime, 30 | open: +binanceTicker.openPrice, 31 | prevClose: +binanceTicker.prevClosePrice, 32 | }; 33 | } 34 | 35 | // adapt ws ticker 36 | export function adaptBinanceWsTicker(binanceTicker: BinanceWsTicker, pair: string): Ticker { 37 | return { 38 | pair, 39 | ask: +binanceTicker.a, 40 | bid: +binanceTicker.b, 41 | low: +binanceTicker.l, 42 | high: +binanceTicker.h, 43 | last: +binanceTicker.c, 44 | vol: +binanceTicker.v, 45 | change24: +binanceTicker.p, 46 | change24Perc: +binanceTicker.P / 100, 47 | timestamp: new Date(binanceTicker.E).getTime(), 48 | open: +binanceTicker.o, 49 | prevClose: +binanceTicker.x, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/exchanges/binance/ticker/test-helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mock-websocket'; 2 | -------------------------------------------------------------------------------- /src/exchanges/binance/ticker/test-helpers/mock-websocket.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketRxJsMock } from '../../../../common'; 2 | 3 | export const MOCK_SOCKET = new WebsocketRxJsMock({ 4 | '{"method":"SUBSCRIBE","params":["btcusdt@ticker"],"id":1}': [ 5 | { 6 | time: 10, 7 | payload: { 8 | stream: 'btcusdt@ticker', 9 | data: {"e":"24hrTicker","E":1574603278929,"s":"BTCUSDT","p":"-30.10000000","P":"-0.419","w":"7232.45951343","x":"7177.22000000","c":"7146.21000000","Q":"0.01931800","b":"7146.22000000","B":"0.02798300","a":"7146.98000000","A":"4.02798200","o":"7176.31000000","h":"7344.48000000","l":"7100.00000000","v":"51621.88681600","q":"373353206.40342237","O":1574516878920,"C":1574603278920,"F":208174148,"L":208563636,"n":389489}, 10 | }, 11 | }, 12 | { 13 | time: 20, 14 | payload: { 15 | stream: 'btcusdt@ticker', 16 | data: {"e":"24hrTicker","E":1574603276929,"s":"BTCUSDT","p":"-31.09000000","P":"-0.433","w":"7232.45882644","x":"7176.05000000","c":"7144.95000000","Q":"0.24034300","b":"7144.96000000","B":"0.02798200","a":"7147.25000000","A":"4.00000000","o":"7176.04000000","h":"7344.48000000","l":"7100.00000000","v":"51622.53711700","q":"373357874.21503731","O":1574516876919,"C":1574603276919,"F":208174139,"L":208563633,"n":389495}, 17 | }, 18 | }, 19 | ], 20 | '{"method":"UNSUBSCRIBE","params":["btcusdt@ticker"],"id":1}': [ 21 | { 22 | time: 100, 23 | payload: 'stop', 24 | }, 25 | ], 26 | }); 27 | -------------------------------------------------------------------------------- /src/exchanges/binance/trade/binance-trade.spec.ts: -------------------------------------------------------------------------------- 1 | import { take, bufferCount } from 'rxjs/operators'; 2 | 3 | import { checkTrades } from '../../exchange-test.functions'; 4 | import { BinanceTrade } from './binance-trade'; 5 | import { BinanceWebsocket } from '../websocket'; 6 | 7 | describe('binanceTrade', () => { 8 | jest.setTimeout(20000); 9 | 10 | const binanceWebsocket = new BinanceWebsocket(); 11 | const binanceTrade = new BinanceTrade(binanceWebsocket); 12 | 13 | const markets = ['btc_usdt', 'eth_btc']; 14 | 15 | markets.forEach((market) => { 16 | it(`should fetch rest api trades ${market}`, async () => { 17 | const trades = await binanceTrade.fetch(market, 10); 18 | checkTrades(trades); 19 | }); 20 | }); 21 | 22 | markets.forEach((market) => { 23 | it(`should get realtime trades ${market}`, (done) => { 24 | binanceTrade 25 | .stream$(market) 26 | .pipe( 27 | bufferCount(10), 28 | take(1), 29 | ) 30 | .subscribe( 31 | (trades) => { 32 | checkTrades(trades); 33 | }, 34 | () => { 35 | /* error */ 36 | }, 37 | () => { 38 | binanceTrade.stop(market); 39 | done(); 40 | }, 41 | ); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/exchanges/binance/trade/binance-trade.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { Trade } from '../../exchange-types'; 7 | import { BinanceRestTrade, BinanceWsTrade } from './internal/types'; 8 | import { adaptBinanceRestTrade, adaptBinanceWsTrade, binanceTradeApiUrl, getTradeChannel } from './internal/functions'; 9 | import { BinanceWebsocket } from '../websocket'; 10 | 11 | export class BinanceTrade { 12 | constructor(private readonly binanceWebsocket: BinanceWebsocket, private readonly corsProxy: string = '') {} 13 | 14 | // fetch trades 15 | async fetch(pair: string, limit: number = 100): Promise { 16 | const originUrl = binanceTradeApiUrl(pair, limit); 17 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 18 | 19 | const rawTrades: BinanceRestTrade[] = await fetch(url).then(res => res.json()); 20 | 21 | return rawTrades.map(adaptBinanceRestTrade); 22 | } 23 | 24 | // realtime trade 25 | stream$(pair: string): Observable { 26 | const channel = getTradeChannel(pair); 27 | 28 | return this.binanceWebsocket.subscribeChannel(channel).pipe( 29 | map(adaptBinanceWsTrade), 30 | ); 31 | } 32 | 33 | // stop realtime trade 34 | stop(pair: string): void { 35 | const channel = getTradeChannel(pair); 36 | this.binanceWebsocket.unsubscribeChannel(channel); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/exchanges/binance/trade/index.ts: -------------------------------------------------------------------------------- 1 | export { BinanceTrade } from './binance-trade'; 2 | -------------------------------------------------------------------------------- /src/exchanges/binance/trade/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Trade } from '../../../exchange-types'; 2 | import { apiEndPoint, binancePair } from '../../binance-common'; 3 | import { BinanceRestTrade, BinanceWsTrade } from './types'; 4 | 5 | // trades rest api url 6 | export function binanceTradeApiUrl(pair: string, limit: number = 100): string { 7 | const symbol = binancePair(pair).toUpperCase(); 8 | 9 | return `${apiEndPoint}/api/v1/trades?limit=${limit}&symbol=${symbol}`; 10 | } 11 | 12 | // trades ws channel 13 | export function getTradeChannel(pair: string): string { 14 | return `${binancePair(pair)}@trade`; 15 | } 16 | 17 | export function adaptBinanceRestTrade(binanceTrade: BinanceRestTrade): Trade { 18 | return { 19 | id: binanceTrade.id, 20 | price: +binanceTrade.price, 21 | amount: +binanceTrade.qty, 22 | side: binanceTrade.isBuyerMaker ? 'buy' : 'sell', 23 | timestamp: binanceTrade.time, 24 | }; 25 | } 26 | 27 | export function adaptBinanceWsTrade(binanceTrade: BinanceWsTrade): Trade { 28 | return { 29 | id: binanceTrade.t, 30 | price: +binanceTrade.p, 31 | amount: +binanceTrade.q, 32 | side: binanceTrade.m ? 'buy' : 'sell', 33 | timestamp: binanceTrade.T, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/exchanges/binance/trade/internal/types.ts: -------------------------------------------------------------------------------- 1 | export interface BinanceRestTrade { 2 | id: number; 3 | price: string; 4 | qty: string; 5 | time: number; 6 | isBuyerMaker: boolean; 7 | isBestMatch: boolean; 8 | } 9 | 10 | export interface BinanceWsTrade { 11 | e: 'trade'; // Event type 12 | E: number; // Event time 13 | s: string; // Symbol 14 | t: number; // Trade ID 15 | p: string; // Price 16 | q: string; // Quantity 17 | b: number; // Buyer order ID 18 | a: number; // Seller order ID 19 | T: number; // Trade time 20 | m: boolean; // Is the buyer the market maker? 21 | } 22 | 23 | // { 24 | // "e": "aggTrade", // Event type 25 | // "E": 123456789, // Event time 26 | // "s": "BNBBTC", // Symbol 27 | // "a": 12345, // Aggregate trade ID 28 | // "p": "0.001", // Price 29 | // "q": "100", // Quantity 30 | // "f": 100, // First trade ID 31 | // "l": 105, // Last trade ID 32 | // "T": 123456785, // Trade time 33 | // "m": true, // Is the buyer the market maker? 34 | // "M": true // Ignore 35 | // } 36 | export interface BinanceWsAggTrade { 37 | e: 'aggTrade'; // Event type 38 | E: number; // Event time 39 | s: string; // Symbol 40 | t: number; // Trade ID 41 | p: string; // Price 42 | q: string; // Quantity 43 | f: number; // First trade ID 44 | l: number; // Last trade ID 45 | T: number; // Trade time 46 | m: boolean; // Is the buyer the market maker? 47 | } 48 | -------------------------------------------------------------------------------- /src/exchanges/binance/user-stream/binance-user-stream.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable-next-line */ 2 | const apiKeys = require('../api-private/api-key-test'); 3 | import { BinanceUserStream } from './binance-user-stream'; 4 | 5 | const binanceUserStream = new BinanceUserStream(apiKeys.key); 6 | 7 | describe('binanceUserStream', () => { 8 | it('should get account information realtime', (done) => { 9 | // TODO test binanceUserStream's userDataAccount$ method 10 | // binanceUserStream.userDataAccount$().subscribe(accountInfo => { 11 | // console.log(accountInfo); 12 | // done(); 13 | // }); 14 | done(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/exchanges/binance/user-stream/binance-user-stream.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { filter, map, switchMap } from 'rxjs/operators'; 3 | 4 | import { WebSocketRxJs } from '../../../common'; 5 | import { wsEndpoint } from '../binance-common'; 6 | import { BinanceApiPrivate } from '../api-private/binance-api-private'; 7 | import { BinanceUserStreamAccount, BinanceUserStreamOrder } from './internal/types'; 8 | 9 | export class BinanceUserStream { 10 | private readonly key: string; 11 | private readonly corsProxy: string; 12 | private socket: WebSocketRxJs | null = null; 13 | 14 | constructor(key: string, corsProxy: string = '') { 15 | this.key = key; 16 | this.corsProxy = corsProxy; 17 | } 18 | 19 | userDataAccount$(): Observable { 20 | return this.userData$().pipe( 21 | filter((data) => data.e === 'outboundAccountInfo'), 22 | map((data) => data), 23 | ); 24 | } 25 | 26 | userDataOrder$(): Observable { 27 | return this.userData$().pipe( 28 | filter((data) => data.e === 'executionReport'), 29 | map((data) => data), 30 | ); 31 | } 32 | 33 | stopUserData(): void { 34 | if (this.socket) { 35 | this.socket.close(); 36 | this.socket = null; 37 | } 38 | } 39 | 40 | // TODO keep alive option 41 | private userData$(): Observable { 42 | if (!this.socket) { 43 | // request for user data stream listen key 44 | const binanceApiPrivate = new BinanceApiPrivate(this.key, this.corsProxy); 45 | 46 | return binanceApiPrivate.getUserStreamListenKey$().pipe( 47 | switchMap((listenKey) => { 48 | const channel = wsEndpoint + listenKey; 49 | console.log('channel', channel); 50 | this.socket = new WebSocketRxJs(channel); 51 | 52 | return this.socket.message$; 53 | }), 54 | ); 55 | } 56 | 57 | return this.socket.message$; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/exchanges/binance/websocket/binance-websocket.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { filter, map } from 'rxjs/operators'; 3 | 4 | import { WebSocketRxJs } from '../../../common'; 5 | import { BinanceWebsocketMessage, BinanceWebscoketRequest } from './binance-websocket.type'; 6 | 7 | const binanceWsEndpoint = 'wss://stream.binance.com:9443/stream'; 8 | 9 | export class BinanceWebsocket { 10 | private ws: WebSocketRxJs | null = null; 11 | private readonly cache = new Map>(); 12 | 13 | subscribeChannel(channel: string): Observable { 14 | if (!this.ws) { 15 | this.ws = new WebSocketRxJs(binanceWsEndpoint); 16 | } 17 | 18 | const cached$ = this.cache.get(channel); 19 | if (cached$) { 20 | return cached$; 21 | } 22 | 23 | const subcribeRequest: BinanceWebscoketRequest = { 24 | method: 'SUBSCRIBE', 25 | params: [channel], 26 | id: 1, 27 | } 28 | 29 | this.send(subcribeRequest); 30 | 31 | // TODO unsubscirbe stream 32 | 33 | const data$ = this.ws.message$.pipe( 34 | filter((message) => message.stream === channel), 35 | map((message) => message.data), 36 | ); 37 | 38 | // cache 39 | this.cache.set(channel, data$); 40 | 41 | return data$; 42 | } 43 | 44 | unsubscribeChannel(channel: string): void { 45 | const unsubcribeRequest: BinanceWebscoketRequest = { 46 | method: 'UNSUBSCRIBE', 47 | params: [channel], 48 | id: 1, 49 | } 50 | 51 | this.send(unsubcribeRequest); 52 | this.cache.delete(channel); 53 | } 54 | 55 | private send(req: BinanceWebscoketRequest): void { 56 | if (this.ws) { 57 | this.ws.send(JSON.stringify(req)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/exchanges/binance/websocket/binance-websocket.type.ts: -------------------------------------------------------------------------------- 1 | export interface BinanceWebsocketMessage { 2 | stream: string; 3 | data: T; 4 | } 5 | 6 | // https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#subscribe-to-a-stream 7 | // { 8 | // "method": "SUBSCRIBE", 9 | // "params": [ 10 | // "btcusdt@aggTrade", 11 | // "btcusdt@depth" 12 | // ], 13 | // "id": 1 14 | // } 15 | export interface BinanceWebscoketRequest { 16 | method: 'SUBSCRIBE' | 'UNSUBSCRIBE'; 17 | params: string[]; 18 | id: number; 19 | } 20 | -------------------------------------------------------------------------------- /src/exchanges/binance/websocket/index.ts: -------------------------------------------------------------------------------- 1 | export * from './binance-websocket'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/README.md: -------------------------------------------------------------------------------- 1 | # Bitbank specific API -------------------------------------------------------------------------------- /src/exchanges/bitbank/bitbank-api.spec.ts: -------------------------------------------------------------------------------- 1 | describe('bitbankApi', () => { 2 | it('should work', () => {}); 3 | }); 4 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/bitbank-common.ts: -------------------------------------------------------------------------------- 1 | export const publicUrl = 'https://public.bitbank.cc'; 2 | export const subscribeKey = 'sub-c-e12e9174-dd60-11e6-806b-02ee2ddab7fe'; 3 | 4 | export interface RawData { 5 | success: 0 | 1; 6 | data: T; 7 | } 8 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/candlestick/bitbank-candlestick.spec.ts: -------------------------------------------------------------------------------- 1 | import { BitbankCandlestick } from './bitbank-candlestick'; 2 | 3 | const bitbankCandlestick = new BitbankCandlestick(); 4 | 5 | describe('Test bitbank candlestick functions', () => { 6 | it('should get btc_jpy 5min candles', async () => { 7 | const candles = await bitbankCandlestick.fetchCandleStickRange('btc_jpy', 5, 1526917534904, 1526917534904); 8 | expect(candles.length).toBe(1); 9 | expect(candles[0].close).toBe(937642); 10 | }); 11 | 12 | it('should get approximate history price', async () => { 13 | const price = await bitbankCandlestick.getApproximateHistoryPrice('btc_jpy', 1526917534904, 1); 14 | expect(price).toBe(935270); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/candlestick/index.ts: -------------------------------------------------------------------------------- 1 | export { BitbankCandlestick } from './bitbank-candlestick'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/candlestick/internal/functions.spec.ts: -------------------------------------------------------------------------------- 1 | import { nextDateString } from './functions'; 2 | 3 | const dateStringArray = [ 4 | '20180620', 5 | '20180621', 6 | '20180622', 7 | '20180623', 8 | '20180624', 9 | '20180625', 10 | '20180626', 11 | '20180627', 12 | '20180628', 13 | '20180629', 14 | '20180630', 15 | '20180701', 16 | '20180702', 17 | '20180703', 18 | '20180704', 19 | '20180705', 20 | '20180706', 21 | '20180707', 22 | '20180708', 23 | '20180709', 24 | '20180710', 25 | '20180711', 26 | '20180712', 27 | '20180713', 28 | '20180714', 29 | '20180715', 30 | '20180716', 31 | '20180717', 32 | '20180718', 33 | '20180719', 34 | '20180720', 35 | '20180721', 36 | '20180722', 37 | ]; 38 | 39 | describe('Bitbank candlestick internal functions', () => { 40 | it('test nextDateString', () => { 41 | for (let i = 0; i < dateStringArray.length - 1; i++) { 42 | expect(nextDateString(dateStringArray[i])).toBe(dateStringArray[i + 1]); 43 | } 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/candlestick/internal/types.ts: -------------------------------------------------------------------------------- 1 | export type BitbankRawCandle = [ 2 | string, // open 3 | string, // high 4 | string, // low 5 | string, // close 6 | string, // volume 7 | number // timestamp 8 | ]; 9 | 10 | export interface BitbankRawCandlesticks { 11 | candlestick: { 12 | type: string; 13 | ohlcv: BitbankRawCandle[]; 14 | }[]; 15 | } 16 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/index.ts: -------------------------------------------------------------------------------- 1 | export { BitbankApi } from './bitbank-api'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/orderbook/bitbank-orderbook.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkOrderbook } from '../../exchange-test.functions'; 4 | import { BitbankOrderbook } from './bitbank-orderbook'; 5 | 6 | const bitbankOrderbook = new BitbankOrderbook(); 7 | 8 | describe('bitbankOrderbook', () => { 9 | jest.setTimeout(20000); 10 | 11 | const markets = ['btc_jpy', 'xrp_jpy']; 12 | markets.forEach((market) => { 13 | it(`should get depth ${market}`, (done) => { 14 | bitbankOrderbook 15 | .orderbook$(market) 16 | .pipe(take(2)) 17 | .subscribe( 18 | (orderbook) => { 19 | console.log(orderbook.asks[0], orderbook.bids[0]); 20 | checkOrderbook(orderbook); 21 | }, 22 | (e) => console.log('Error'), 23 | () => { 24 | bitbankOrderbook.stopOrderbook(market); 25 | done(); 26 | }, 27 | ); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/orderbook/bitbank-orderbook.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable, concat } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { PubnubRxJs } from '../../../common'; 7 | import { Orderbook } from '../../exchange-types'; 8 | import { publicUrl, subscribeKey, RawData } from '../bitbank-common'; 9 | 10 | export class BitbankOrderbook { 11 | private readonly pubnub: PubnubRxJs; 12 | 13 | constructor(pubnub?: PubnubRxJs) { 14 | this.pubnub = pubnub || new PubnubRxJs({ subscribeKey }); 15 | } 16 | 17 | async fetchOrderbook(pair: string): Promise { 18 | const url = `${publicUrl}/${pair}/depth`; 19 | const raw: RawData = await fetch(url).then(res => res.json()); 20 | 21 | return raw.data; 22 | } 23 | 24 | orderbook$(pair: string): Observable { 25 | return concat(this.fetchOrderbook(pair), this.pubnubOrderbook$(pair)); 26 | } 27 | 28 | stopOrderbook(pair: string): void { 29 | const channel = `depth_${pair}`; 30 | this.pubnub.unsubscribeChannel(channel); 31 | } 32 | 33 | private pubnubOrderbook$(pair: string): Observable { 34 | const channel = `depth_${pair}`; 35 | 36 | return this.pubnub.subscribeChannel>(channel).pipe(map((raw) => raw.data)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/orderbook/index.ts: -------------------------------------------------------------------------------- 1 | export { BitbankOrderbook } from './bitbank-orderbook'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/orderbook/internal/functions.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/bitbank/orderbook/internal/functions.ts -------------------------------------------------------------------------------- /src/exchanges/bitbank/orderbook/internal/types.ts: -------------------------------------------------------------------------------- 1 | export interface BitbankRawOrderbook { 2 | asks: [string, string][]; 3 | bids: [string, string][]; 4 | timestamp: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/ticker/bitbank-ticker.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkTicker } from '../../exchange-test.functions'; 4 | import { BitbankTicker } from './bitbank-ticker'; 5 | 6 | const bitbankTicker = new BitbankTicker(); 7 | 8 | describe('bitbankTicker', () => { 9 | jest.setTimeout(10000); 10 | 11 | const markets = ['btc_jpy', 'xrp_jpy']; 12 | 13 | markets.forEach((market) => { 14 | it(`should get ticker realtime ${market}`, (done) => { 15 | bitbankTicker 16 | .ticker$(market) 17 | .pipe(take(2)) 18 | .subscribe( 19 | (ticker) => { 20 | checkTicker(ticker); 21 | }, 22 | () => { 23 | /* error */ 24 | }, 25 | () => { 26 | bitbankTicker.stopTicker(market); 27 | done(); 28 | }, 29 | ); 30 | }); 31 | }); 32 | 33 | markets.forEach((market) => { 34 | it(`should fetch ticker ${market}`, async () => { 35 | const ticker = await bitbankTicker.fetchTicker(market); 36 | checkTicker(ticker); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/ticker/bitbank-ticker.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable, concat } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { PubnubRxJs } from '../../../common'; 7 | import { Ticker } from '../../exchange-types'; 8 | import { publicUrl, subscribeKey, RawData } from '../bitbank-common'; 9 | import { BitbankRawTicker } from './internal/types'; 10 | import { adaptBitbankTicker } from './internal/functions'; 11 | 12 | export class BitbankTicker { 13 | private readonly pubnub: PubnubRxJs; 14 | 15 | constructor(pubnub?: PubnubRxJs) { 16 | this.pubnub = pubnub || new PubnubRxJs({ subscribeKey }); 17 | } 18 | 19 | async fetchTicker(pair: string): Promise { 20 | const url = `${publicUrl}/${pair}/ticker`; 21 | const raw: RawData = await fetch(url).then(res => res.json()); 22 | 23 | return adaptBitbankTicker(raw.data, pair); 24 | } 25 | 26 | ticker$(pair: string): Observable { 27 | return concat(this.fetchTicker(pair), this.pubnubTicker$(pair)); 28 | } 29 | 30 | stopTicker(pair: string): void { 31 | const channel = `ticker_${pair}`; 32 | this.pubnub.unsubscribeChannel(channel); 33 | } 34 | 35 | private pubnubTicker$(pair: string): Observable { 36 | const channel = `ticker_${pair}`; 37 | 38 | return this.pubnub 39 | .subscribeChannel>(channel) 40 | .pipe(map((bitbankPubnubTicker) => adaptBitbankTicker(bitbankPubnubTicker.data, pair))); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/ticker/index.ts: -------------------------------------------------------------------------------- 1 | export { BitbankTicker } from './bitbank-ticker'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/ticker/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from '../../../exchange-types'; 2 | import { BitbankRawTicker } from './types'; 3 | 4 | export function adaptBitbankTicker(bitbankTicker: BitbankRawTicker, pair: string): Ticker { 5 | return { 6 | pair, 7 | ask: +bitbankTicker.sell, 8 | bid: +bitbankTicker.buy, 9 | low: +bitbankTicker.low, 10 | high: +bitbankTicker.high, 11 | last: +bitbankTicker.last, 12 | vol: +bitbankTicker.vol, 13 | timestamp: bitbankTicker.timestamp, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/ticker/internal/types.ts: -------------------------------------------------------------------------------- 1 | export interface BitbankRawTicker { 2 | pair: string; 3 | sell: string; 4 | buy: string; 5 | low: string; 6 | high: string; 7 | last: string; 8 | vol: string; 9 | timestamp: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/trade/bitbank-trade.spec.ts: -------------------------------------------------------------------------------- 1 | import { take, bufferCount } from 'rxjs/operators'; 2 | 3 | import { checkTrades } from '../../exchange-test.functions'; 4 | import { BitbankTrade } from './bitbank-trade'; 5 | 6 | const bitbankTrade = new BitbankTrade(); 7 | 8 | describe('bitbankTrade', () => { 9 | jest.setTimeout(10000); 10 | const markets = ['btc_jpy', 'xrp_jpy']; 11 | 12 | markets.forEach((market) => { 13 | it(`should get realtime trades ${market}`, (done) => { 14 | bitbankTrade 15 | .trade$(market) 16 | .pipe( 17 | bufferCount(2), 18 | take(1), 19 | ) 20 | .subscribe( 21 | (trades) => { 22 | checkTrades(trades); 23 | }, 24 | (e) => console.log('Error'), 25 | () => { 26 | bitbankTrade.stopTrade(market); 27 | done(); 28 | }, 29 | ); 30 | }); 31 | }); 32 | 33 | markets.forEach((market) => { 34 | it(`should fetch rest api trades ${market}`, async () => { 35 | const trades = await bitbankTrade.fetchTrades(market); 36 | checkTrades(trades, false); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/trade/bitbank-trade.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable, from } from 'rxjs'; 4 | import { map, concatMap } from 'rxjs/operators'; 5 | 6 | import { PubnubRxJs } from '../../../common'; 7 | import { Trade } from '../../exchange-types'; 8 | import { publicUrl, subscribeKey, RawData } from '../bitbank-common'; 9 | import { BitbankRawTrade } from './internal/types'; 10 | import { adaptBitbankTrade } from './internal/functions'; 11 | 12 | interface BitbankRawTrades { 13 | transactions: BitbankRawTrade[]; 14 | } 15 | 16 | export class BitbankTrade { 17 | private readonly pubnub: PubnubRxJs; 18 | 19 | constructor(pubnub?: PubnubRxJs) { 20 | this.pubnub = pubnub || new PubnubRxJs({ subscribeKey }); 21 | } 22 | 23 | async fetchTrades(pair: string): Promise { 24 | const url = `${publicUrl}/${pair}/transactions`; 25 | const raw: RawData = await fetch(url).then(res => res.json()); 26 | 27 | return raw.data.transactions.map(adaptBitbankTrade); 28 | } 29 | 30 | trade$(pair: string): Observable { 31 | const channel = `transactions_${pair}`; 32 | 33 | return this.pubnub.subscribeChannel>(channel).pipe( 34 | map((raw) => raw.data.transactions.map(adaptBitbankTrade)), 35 | // sort the trades in ascending order of timestamp (old to new one) 36 | map((trades) => trades.sort((t1, t2) => t1.timestamp - t2.timestamp)), 37 | // turn the stream of trade array into stream of single trade 38 | concatMap((trades) => { 39 | return from(trades); 40 | }), 41 | ); 42 | } 43 | 44 | stopTrade(pair: string): void { 45 | const channel = `transactions_${pair}`; 46 | this.pubnub.unsubscribeChannel(channel); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/trade/index.ts: -------------------------------------------------------------------------------- 1 | export { BitbankTrade } from './bitbank-trade'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/trade/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Trade } from '../../../exchange-types'; 2 | import { BitbankRawTrade } from './types'; 3 | 4 | export function adaptBitbankTrade(bitbankTrade: BitbankRawTrade): Trade { 5 | return { 6 | id: bitbankTrade.transaction_id, 7 | side: bitbankTrade.side, 8 | price: +bitbankTrade.price, 9 | amount: +bitbankTrade.amount, 10 | timestamp: bitbankTrade.executed_at, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/exchanges/bitbank/trade/internal/types.ts: -------------------------------------------------------------------------------- 1 | export interface BitbankRawTrade { 2 | transaction_id: number; 3 | side: 'buy' | 'sell'; 4 | price: string; 5 | amount: string; 6 | executed_at: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/README.md: -------------------------------------------------------------------------------- 1 | # Bitfinex specific API -------------------------------------------------------------------------------- /src/exchanges/bitfinex/bitfinex-common.ts: -------------------------------------------------------------------------------- 1 | export const wsEndpoint = 'wss://api.bitfinex.com/ws/2'; 2 | export const apiEndpoint = 'https://api.bitfinex.com/v2'; 3 | 4 | export function getSymbol(pair: string): string { 5 | return `t${pair.replace('_', '').toUpperCase()}`; 6 | } 7 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/candlestick/bitfinex-candlestick.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkCandleStick } from '../../exchange-test.functions'; 4 | import { BitfinexCandleStick } from './bitfinex-candlestick'; 5 | import { BitfinexWebsocket } from '../websocket'; 6 | 7 | const bitfinexWebsocket = new BitfinexWebsocket(); 8 | const bitfinexCandlestick = new BitfinexCandleStick('', bitfinexWebsocket); 9 | 10 | describe('Test bitfinex candlestick functions', () => { 11 | jest.setTimeout(30000); 12 | 13 | it('should fetch btc_usd 5min candles in time range', async () => { 14 | const candles = await bitfinexCandlestick.fetchCandleStickRange('btc_usd', 5, 1529509826239 - 60000 * 60 * 24, 1529509826239); 15 | checkCandleStick(candles[0]); 16 | }); 17 | 18 | it('should get btc_usd 5min last candle realtime', (done) => { 19 | bitfinexCandlestick 20 | .candlestick$('btc_usd', 5) 21 | .pipe(take(2)) 22 | .subscribe( 23 | (candle) => { 24 | checkCandleStick(candle); 25 | }, 26 | () => console.log('error'), 27 | async () => { 28 | await bitfinexCandlestick.stopCandleStick('btc_usd', 5); 29 | bitfinexWebsocket.destroy(); 30 | done(); 31 | }, 32 | ); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/candlestick/index.ts: -------------------------------------------------------------------------------- 1 | export { BitfinexCandleStick } from './bitfinex-candlestick'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/candlestick/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { CandleStick } from '../../../exchange-types'; 2 | import { getSymbol, apiEndpoint } from '../../bitfinex-common'; 3 | import { BitfinexRawCandleStick } from './types'; 4 | 5 | export function getCandleStickUrl( 6 | pair: string, 7 | minutesFoot: number, 8 | start?: number, 9 | end?: number, 10 | limit?: number, 11 | sort: number = -1, 12 | ): string { 13 | // https://api.bitfinex.com/v2/candles/trade::TimeFrame::Symbol/Section 14 | // `${url}/candles/trade:1m:tBTCUSD/hist`, 15 | const symbol = getSymbol(pair); 16 | const timeFrame = getCandleTimeFrame(minutesFoot); 17 | let url = `${apiEndpoint}/candles/trade:${timeFrame}:${symbol}/hist?sort=${sort}`; 18 | 19 | // if 'start' provided and no 'limit', calculate to use 'limit' and drop 'start' 20 | let limitAdjusted = limit; 21 | let startAdjusted = start; 22 | if (start && end && !limit) { 23 | limitAdjusted = Math.min(Math.round((end - start) / (minutesFoot * 60 * 1000)) + 1, 1000); 24 | startAdjusted = 0; 25 | } 26 | 27 | if (startAdjusted) { 28 | url += `&start=${startAdjusted}`; 29 | } 30 | if (end) { 31 | url += `&end=${end}`; 32 | } 33 | if (limitAdjusted) { 34 | url += `&limit=${limitAdjusted}`; 35 | } 36 | 37 | return url; 38 | } 39 | 40 | // [ 41 | // MTS, 42 | // OPEN, 43 | // CLOSE, 44 | // HIGH, 45 | // LOW, 46 | // VOLUME 47 | // ] 48 | export function adaptBitfinexRawCandleStick(bitfinexCandle: BitfinexRawCandleStick): CandleStick { 49 | return { 50 | open: +bitfinexCandle[1], 51 | close: +bitfinexCandle[2], 52 | high: +bitfinexCandle[3], 53 | low: +bitfinexCandle[4], 54 | volume: +bitfinexCandle[5], 55 | timestamp: bitfinexCandle[0], 56 | }; 57 | } 58 | 59 | // '1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D', '7D', '14D', '1M' 60 | const minutesTimeFrameMap: { [key: string]: string } = { 61 | 1: '1m', 62 | 5: '5m', 63 | 15: '15m', 64 | 30: '30m', 65 | 60: '1h', 66 | 180: '3h', 67 | 360: '6h', 68 | 720: '12h', 69 | 1440: '1D', 70 | 10080: '7D', 71 | 20160: '14D', 72 | }; 73 | 74 | export function getCandleTimeFrame(minutesFoot: number): string { 75 | return minutesTimeFrameMap[minutesFoot] || ''; 76 | } 77 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/candlestick/internal/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Candlestick 3 | */ 4 | 5 | export type BitfinexRawCandleStick = [ 6 | // MTS int millisecond time stamp 7 | number, 8 | // OPEN float First execution during the time frame 9 | number, 10 | // CLOSE float Last execution during the time frame 11 | number, 12 | // HIGH float Highest execution during the time frame 13 | number, 14 | // LOW float Lowest execution during the timeframe 15 | number, 16 | // VOLUME float Quantity of symbol traded within the timeframe 17 | number 18 | ]; 19 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/index.ts: -------------------------------------------------------------------------------- 1 | export { BitfinexApi } from './bitfinex-api'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/orderbook/bitfinex-orderbook.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkOrderbook } from '../../exchange-test.functions'; 4 | import { BitfinexOrderbook } from './bitfinex-orderbook'; 5 | import { BitfinexWebsocket } from '../websocket'; 6 | 7 | const bitfinexWebsocket = new BitfinexWebsocket(); 8 | const bitfinexOrderbook = new BitfinexOrderbook('', bitfinexWebsocket); 9 | 10 | describe('bitfinexOrderbook', () => { 11 | jest.setTimeout(30000); 12 | 13 | const markets = ['btc_usd']; 14 | 15 | markets.forEach((market) => { 16 | it(`should get orderbook realtime ${market}`, (done) => { 17 | bitfinexOrderbook 18 | .orderbook$(market) 19 | .pipe(take(1)) 20 | .subscribe( 21 | (orderbook) => { 22 | checkOrderbook(orderbook); 23 | }, 24 | (e) => console.log('Error', e), 25 | async () => { 26 | await bitfinexOrderbook.stopOrderbook(market); 27 | bitfinexWebsocket.destroy(); 28 | done(); 29 | }, 30 | ); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/orderbook/index.ts: -------------------------------------------------------------------------------- 1 | export { BitfinexOrderbook } from './bitfinex-orderbook'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/orderbook/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Orderbook } from '../../../exchange-types'; 2 | import { getSymbol, apiEndpoint } from '../../bitfinex-common'; 3 | import { BitfinexOrderbookSingleItem } from './types'; 4 | 5 | export function getOrderbookApiUrl(pair: string, prec: string = 'P0'): string { 6 | // https://api.bitfinex.com/v:version/book/:Symbol/:Precision 7 | // https://api.bitfinex.com/v2/book/tBTCUSD/P0 8 | const symbol = getSymbol(pair); 9 | const url = `${apiEndpoint}/book/${symbol}/${prec}`; 10 | 11 | return url; 12 | } 13 | 14 | // assuming that all bids and asks are in the right order (bids: DESC, asks: ASC) 15 | export function adaptBitfinexOrderbook(bitfinexOrderbook: BitfinexOrderbookSingleItem[]): Orderbook { 16 | const orderbook: Orderbook = { 17 | bids: [], 18 | asks: [], 19 | }; 20 | 21 | bitfinexOrderbook.forEach((orderbookItem) => { 22 | // if count === 0 set amount 0 23 | const amount = orderbookItem[1] > 0 ? Math.abs(orderbookItem[2]) : 0; 24 | const price = orderbookItem[0]; 25 | if (orderbookItem[2] > 0) { 26 | orderbook.bids.push([`${price}`, `${amount}`]); 27 | } else { 28 | orderbook.asks.push([`${price}`, `${amount}`]); 29 | } 30 | }); 31 | 32 | return orderbook; 33 | } 34 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/orderbook/internal/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Orderbook types 3 | */ 4 | 5 | export type BitfinexOrderbookSingleItem = [ 6 | // price 7 | number, 8 | // count 9 | number, 10 | // amount 11 | // - (> 0): bids 12 | // - (< 0): asks 13 | number 14 | ]; 15 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/orderbook/test-helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mock-websocket'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/orderbook/test-helpers/mock-websocket.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketRxJsMock } from '../../../../common'; 2 | import { WebsocketResponseI, WebsocketDataI } from '../../websocket'; 3 | 4 | export const MOCK_SOCKET = new WebsocketRxJsMock({ 5 | '{"channel":"book","symbol":"tBTCUSD","prec":"P0","freq":"F0","len":"25","event":"subscribe"}': [ 6 | { 7 | time: 1, 8 | payload: { 9 | event: 'subscribed', 10 | channel: 'book', 11 | chanId: 2032, 12 | symbol: 'tBTCUSD', 13 | prec: 'P0', 14 | freq: 'F0', 15 | len: '25', 16 | pair: 'BTCUSD', 17 | }, 18 | }, 19 | { 20 | time: 2, 21 | payload: [ 22 | 2032, 23 | [ 24 | [ 25 | 3688.7, 26 | 2, 27 | 5.13034526, 28 | ], 29 | [ 30 | 3688.1, 31 | 1, 32 | 0.5, 33 | ], 34 | [ 35 | 3695.6, 36 | 2, 37 | -1.2, 38 | ], 39 | [ 40 | 3696, 41 | 1, 42 | -0.115, 43 | ], 44 | ], 45 | ], 46 | }, 47 | { 48 | time: 2, 49 | payload: [ 50 | 2032, 51 | [ 52 | 3696, 53 | 0, 54 | -1, 55 | ], 56 | ], 57 | }, 58 | { 59 | time: 2, 60 | payload: [ 61 | 2032, 62 | [ 63 | 3688.7, 64 | 1, 65 | 2.11, 66 | ], 67 | ], 68 | }, 69 | ], 70 | '{"event":"unsubscribe","chanId":1}': [ 71 | { 72 | time: 1, 73 | // not handle this case yet 74 | payload: [] as any, 75 | }, 76 | ], 77 | }); 78 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/ticker/bitfinex-ticker.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkTicker } from '../../exchange-test.functions'; 4 | import { BitfinexTicker } from './bitfinex-ticker'; 5 | import { BitfinexWebsocket } from '../websocket'; 6 | import { MOCK_SOCKET } from './test-helpers'; 7 | 8 | const bitfinexWebsocket = new BitfinexWebsocket(); 9 | const bitfinexTicker = new BitfinexTicker('', bitfinexWebsocket); 10 | 11 | const pair = 'btc_usd'; 12 | 13 | describe('bitfinexTicker', () => { 14 | jest.setTimeout(30000); 15 | 16 | it(`should fetch ticker ${pair}`, async () => { 17 | const ticker = await bitfinexTicker.fetchTicker(pair); 18 | checkTicker(ticker); 19 | }); 20 | 21 | it(`should get ticker realtime ${pair}`, (done) => { 22 | bitfinexTicker 23 | .ticker$(pair) 24 | .pipe(take(1)) 25 | .subscribe( 26 | (ticker) => { 27 | checkTicker(ticker); 28 | }, 29 | (e) => console.log('Error'), 30 | async () => { 31 | await bitfinexTicker.stopTicker(pair); 32 | bitfinexWebsocket.destroy(); 33 | done(); 34 | }, 35 | ); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/ticker/bitfinex-ticker.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { Ticker } from '../../exchange-types'; 7 | import { getSymbol } from '../bitfinex-common'; 8 | import { BitfinexWebsocket } from '../websocket'; 9 | 10 | import { adaptBitfinexTicker, getTickerApiUrl } from './internal/functions'; 11 | import { BitfinexRawTicker } from './internal/types'; 12 | 13 | export class BitfinexTicker { 14 | /** 15 | * @param corsProxy 16 | * @param bitfinexWebsocket 17 | */ 18 | constructor(private readonly corsProxy: string = '', private readonly bitfinexWebsocket: BitfinexWebsocket) {} 19 | 20 | async fetchTicker(pair: string): Promise { 21 | // https://api.bitfinex.com/v2/ticker/tBTCUSD 22 | const originUrl = getTickerApiUrl(pair); 23 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 24 | 25 | const rawTicker: BitfinexRawTicker = await fetch(url).then(res => res.json()); 26 | 27 | return adaptBitfinexTicker(rawTicker, pair); 28 | } 29 | 30 | ticker$(pair: string): Observable { 31 | // { "event": "subscribe", "channel": "ticker", "symbol": "tEOSETH" } 32 | const subscribeRequest = { 33 | channel: 'ticker', 34 | symbol: getSymbol(pair), 35 | }; 36 | 37 | const data$ = this.bitfinexWebsocket.subscribeChannel(subscribeRequest); 38 | 39 | return data$.pipe(map((rawTicker) => adaptBitfinexTicker(rawTicker, pair))); 40 | } 41 | 42 | async stopTicker(pair: string): Promise { 43 | const unsubscribeRequest = { 44 | channel: 'ticker', 45 | symbol: getSymbol(pair), 46 | }; 47 | 48 | await this.bitfinexWebsocket.unsubscribeChannel(unsubscribeRequest); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/ticker/index.ts: -------------------------------------------------------------------------------- 1 | export { BitfinexTicker } from './bitfinex-ticker'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/ticker/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from '../../../exchange-types'; 2 | import { getSymbol, apiEndpoint } from '../../bitfinex-common'; 3 | import { BitfinexRawTicker } from './types'; 4 | 5 | export function getTickerApiUrl(pair: string): string { 6 | // https://api.bitfinex.com/v2/ticker/tBTCUSD 7 | const symbol = getSymbol(pair); 8 | const url = `${apiEndpoint}/ticker/${symbol}`; 9 | 10 | return url; 11 | } 12 | 13 | export function adaptBitfinexTicker(bitfinexTicker: BitfinexRawTicker, pair: string): Ticker { 14 | return { 15 | pair, 16 | ask: bitfinexTicker[2], 17 | bid: bitfinexTicker[0], 18 | low: bitfinexTicker[9], 19 | high: bitfinexTicker[8], 20 | last: bitfinexTicker[6], 21 | vol: bitfinexTicker[7], 22 | change24: bitfinexTicker[4], 23 | change24Perc: bitfinexTicker[5], 24 | timestamp: new Date().getTime(), 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/ticker/internal/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Ticker type 3 | */ 4 | 5 | export type BitfinexRawTicker = [ 6 | number, // bid 8219.2, 7 | number, // bid size 51.73033331, 8 | number, // ask 8220, 9 | number, // ask size 65.62603128, 10 | number, // daily change -28.6, 11 | number, // daily change percent -0.0035, 12 | number, // last price 8219.8, 13 | number, // volume 32325.84881438, 14 | number, // high 8278.7, 15 | number // low 8073.6 16 | ]; 17 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/ticker/test-helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mock-websocket'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/ticker/test-helpers/mock-websocket.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketRxJsMock } from '../../../../common'; 2 | import { WebsocketResponseI, WebsocketDataI } from '../../websocket'; 3 | 4 | export const MOCK_SOCKET = new WebsocketRxJsMock({ 5 | '{"channel":"ticker","symbol":"tBTCUSD","event":"subscribe"}': [ 6 | { 7 | time: 1, 8 | payload: { 9 | channel: 'ticker', 10 | event: 'subscribed', 11 | chanId: 1, 12 | pair: 'BTCUSD', 13 | symbol: 'tBTCUSD', 14 | }, 15 | }, 16 | { 17 | time: 2, 18 | payload: [1, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], 19 | }, 20 | { 21 | time: 2.5, 22 | payload: [1, [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]], 23 | }, 24 | ], 25 | '{"event":"unsubscribe","chanId":1}': [ 26 | { 27 | time: 1, 28 | // not handle this case yet 29 | payload: [] as any, 30 | }, 31 | ], 32 | }); 33 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/trade/bitfinex-trade.spec.ts: -------------------------------------------------------------------------------- 1 | import { bufferCount, take } from 'rxjs/operators'; 2 | 3 | import { checkTrades } from '../../exchange-test.functions'; 4 | import { BitfinexTrade } from './bitfinex-trade'; 5 | import { BitfinexWebsocket } from '../websocket'; 6 | 7 | const bitfinexWebsocket = new BitfinexWebsocket(); 8 | const bitfinexTrade = new BitfinexTrade('', bitfinexWebsocket); 9 | 10 | describe('bitfinexTrade', () => { 11 | jest.setTimeout(30000); 12 | 13 | const markets = ['btc_usd']; 14 | 15 | markets.forEach((market) => { 16 | it(`should fetch rest api trades ${market}`, async () => { 17 | const trades = await bitfinexTrade.fetchTrades(market, undefined, undefined, 10, 1); 18 | checkTrades(trades); 19 | }); 20 | }); 21 | 22 | markets.forEach((market) => { 23 | it(`should get realtime trades ${market}`, (done) => { 24 | bitfinexTrade 25 | .trade$(market) 26 | .pipe( 27 | bufferCount(2), 28 | take(1), 29 | ) 30 | .subscribe( 31 | (trades) => { 32 | checkTrades(trades); 33 | }, 34 | (e) => console.log('Error'), 35 | async () => { 36 | await bitfinexTrade.stopTrade(market); 37 | bitfinexWebsocket.destroy(); 38 | done(); 39 | }, 40 | ); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/trade/bitfinex-trade.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable } from 'rxjs'; 4 | import { map, filter } from 'rxjs/operators'; 5 | 6 | import { Trade } from '../../exchange-types'; 7 | import { getSymbol } from '../bitfinex-common'; 8 | import { BitfinexWebsocket, WebsocketRequestBaseI } from '../websocket'; 9 | 10 | import { getTradesUrl, adaptBitfinexTrade } from './internal/functions'; 11 | import { BitfinexRawTrade } from './internal/types'; 12 | 13 | export class BitfinexTrade { 14 | /** 15 | * 16 | * @param corsProxy 17 | * @param bitfinexWebsocket 18 | */ 19 | constructor(private readonly corsProxy: string = '', private readonly bitfinexWebsocket: BitfinexWebsocket) {} 20 | 21 | // fetch trades 22 | async fetchTrades(pair: string, start?: number, end?: number, limit?: number, sort?: number): Promise { 23 | const originUrl = getTradesUrl(pair, start, end, limit, sort); 24 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 25 | 26 | const rawTrades: BitfinexRawTrade[] = await fetch(url).then(res => res.json()); 27 | 28 | return rawTrades.map(adaptBitfinexTrade); 29 | } 30 | 31 | /** 32 | * latest trade only 33 | * 34 | * @param pair 35 | */ 36 | trade$(pair: string): Observable { 37 | const subcribeRequest = getTradeRequest(pair); 38 | 39 | return this.bitfinexWebsocket.subscribeChannel(subcribeRequest).pipe( 40 | filter((tradeArrayOrTrade) => typeof tradeArrayOrTrade[0] === 'number'), 41 | map((trade) => adaptBitfinexTrade(trade)), 42 | ); 43 | } 44 | 45 | /** 46 | * trade array at first 47 | * 48 | * @param pair 49 | */ 50 | tradeWithInitialHistory$(pair: string): Observable { 51 | const subcribeRequest = getTradeRequest(pair); 52 | 53 | return this.bitfinexWebsocket.subscribeChannel(subcribeRequest).pipe( 54 | map((tradeArrayOrTrade) => { 55 | // array trade 56 | if (tradeArrayOrTrade[0] && typeof tradeArrayOrTrade[0] === 'object') { 57 | const initialTrades = tradeArrayOrTrade; 58 | 59 | return initialTrades.map(adaptBitfinexTrade); 60 | } 61 | 62 | // single trade 63 | return adaptBitfinexTrade(tradeArrayOrTrade); 64 | }), 65 | ); 66 | } 67 | 68 | async stopTrade(pair: string): Promise { 69 | const unsubscribeRequest = getTradeRequest(pair); 70 | await this.bitfinexWebsocket.unsubscribeChannel(unsubscribeRequest); 71 | } 72 | } 73 | 74 | function getTradeRequest(pair: string): WebsocketRequestBaseI { 75 | return { 76 | channel: 'trades', 77 | symbol: getSymbol(pair), 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/trade/index.ts: -------------------------------------------------------------------------------- 1 | export { BitfinexTrade } from './bitfinex-trade'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/trade/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { getSymbol, apiEndpoint } from '../../bitfinex-common'; 2 | import { BitfinexRawTrade } from './types'; 3 | import { Trade } from '../../../exchange-types'; 4 | 5 | export function getTradesUrl(pair: string, start?: number, end?: number, limit?: number, sort: number = -1): string { 6 | // `${url}/trades/tBTCUSD/hist`, 7 | const symbol = getSymbol(pair); 8 | let url = `${apiEndpoint}/trades/${symbol}/hist?sort=${sort}`; 9 | 10 | if (start) { 11 | url += `&start=${start}`; 12 | } 13 | if (end) { 14 | url += `&end=${end}`; 15 | } 16 | if (limit) { 17 | url += `&limit=${limit}`; 18 | } 19 | 20 | return url; 21 | } 22 | 23 | export function adaptBitfinexTrade(bitfinexTrade: BitfinexRawTrade): Trade { 24 | return { 25 | id: bitfinexTrade[0], 26 | timestamp: bitfinexTrade[1], 27 | amount: Math.abs(bitfinexTrade[2]), 28 | price: bitfinexTrade[3], 29 | side: bitfinexTrade[2] > 0 ? 'buy' : 'sell', 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/trade/internal/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Trade 3 | */ 4 | 5 | export type BitfinexRawTrade = [ 6 | // ID, 7 | number, 8 | // MTS: millisecond time stamp 9 | number, 10 | // AMOUNT: How much was bought (positive) or sold (negative). 11 | number, 12 | // PRICE 13 | number 14 | ]; 15 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/websocket/bitfinex-websocket.spec.ts: -------------------------------------------------------------------------------- 1 | import { BitfinexWebsocket } from './bitfinex-websocket'; 2 | import { MOCK_SOCKET } from './test-helpers'; 3 | import { BitfinexRawTickerI } from './bitfinex-websocket.type'; 4 | import { take } from 'rxjs/operators'; 5 | 6 | const bitfinexWebsocket = new BitfinexWebsocket(); 7 | (bitfinexWebsocket as any).ws = MOCK_SOCKET; 8 | 9 | describe('bitfinexWebsocket', () => { 10 | it('should subscribe', async () => { 11 | const data$ = bitfinexWebsocket 12 | .subscribeChannel({ channel: 'ticker', symbol: 'tBTCUSD' }); 13 | 14 | const raw = await data$.pipe(take(1)).toPromise(); 15 | await bitfinexWebsocket.unsubscribeChannel({ channel: 'ticker', symbol: 'tBTCUSD' }); 16 | 17 | expect(raw).toEqual([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); 18 | }); 19 | 20 | it('should unsubscribe', async (done) => { 21 | const data$ = bitfinexWebsocket 22 | .subscribeChannel({ channel: 'ticker', symbol: 'tBTCUSD' }); 23 | 24 | data$.subscribe( 25 | () => {}, 26 | (e) => {}, 27 | () => { 28 | done(); 29 | }, 30 | ); 31 | 32 | await bitfinexWebsocket.unsubscribeChannel({ channel: 'ticker', symbol: 'tBTCUSD' }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/websocket/bitfinex-websocket.type.ts: -------------------------------------------------------------------------------- 1 | export interface WebsocketRequestBaseI { 2 | channel: string; 3 | 4 | // ex: tBTCUSD (ticker, orderbook) 5 | symbol?: string; 6 | 7 | // (orderbook) 8 | // level of price aggregation (P0, P1, P2, P3) 9 | // default: P0 10 | prec?: string; 11 | 12 | // (orderbook) 13 | // Frequency of updates(F0, F1). 14 | // F0 = realtime / F1=2sec. 15 | freq?: string; 16 | 17 | // (orderbook) 18 | len?: string; 19 | 20 | // (candles) 21 | key?: string; 22 | } 23 | 24 | export interface WebsocketSubscribeI extends WebsocketRequestBaseI { 25 | event: 'subscribe'; 26 | } 27 | 28 | export interface WebsocketUnSubscribeI { 29 | event: 'unsubscribe'; 30 | chanId: number; 31 | } 32 | 33 | export interface WebsocketResponseI extends WebsocketRequestBaseI { 34 | event: 'subscribed' | 'unsubscribed'; 35 | chanId: number; 36 | // ex: BTCUSD 37 | pair?: string; 38 | } 39 | 40 | export type WebsocketDataI = [number, any] | [number, string, any]; 41 | 42 | export type WebsocketMessageI = WebsocketResponseI | WebsocketDataI; 43 | 44 | /** 45 | * Ticker type 46 | */ 47 | 48 | export type BitfinexRawTickerI = [ 49 | number, // bid 8219.2, 50 | number, // bid size 51.73033331, 51 | number, // ask 8220, 52 | number, // ask size 65.62603128, 53 | number, // daily change -28.6, 54 | number, // daily change percent -0.0035, 55 | number, // last price 8219.8, 56 | number, // volume 32325.84881438, 57 | number, // high 8278.7, 58 | number // low 8073.6 59 | ]; 60 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/websocket/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bitfinex-websocket'; 2 | export * from './bitfinex-websocket.type'; 3 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/websocket/test-helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mock-websocket'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitfinex/websocket/test-helpers/mock-websocket.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketRxJsMock } from '../../../../common'; 2 | import { WebsocketResponseI, WebsocketDataI } from '../bitfinex-websocket.type'; 3 | 4 | export const MOCK_SOCKET = new WebsocketRxJsMock({ 5 | '{"channel":"ticker","symbol":"tBTCUSD","event":"subscribe"}': [ 6 | { 7 | time: 10, 8 | payload: { 9 | channel: 'ticker', 10 | event: 'subscribed', 11 | chanId: 1, 12 | pair: 'BTCUSD', 13 | symbol: 'tBTCUSD', 14 | }, 15 | }, 16 | { 17 | time: 20, 18 | payload: [1, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], 19 | }, 20 | ], 21 | '{"event":"unsubscribe","chanId":1}': [ 22 | { 23 | time: 30, 24 | // not handle this case yet 25 | payload: { 26 | channel: 'ticker', 27 | event: 'unsubscribed', 28 | chanId: 1, 29 | }, 30 | }, 31 | ], 32 | }); 33 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/README.md: -------------------------------------------------------------------------------- 1 | # Bitmex specific API -------------------------------------------------------------------------------- /src/exchanges/bitmex/bitmex-common.ts: -------------------------------------------------------------------------------- 1 | export const apiEndPoint = 'https://www.bitmex.com/api'; 2 | export const wsEndpoint = 'wss://www.bitmex.com/realtime'; 3 | 4 | // get bitmex symbol xbt_usd => XBTUSD 5 | export function getSymbol(pair: string): string { 6 | return pair.replace('_', '').toUpperCase(); 7 | } 8 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/candlestick/bitmex-candlestick.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkCandleStick } from '../../exchange-test.functions'; 2 | import { BitmexCandleStick } from './bitmex-candlestick'; 3 | 4 | const bitmexCandlestick = new BitmexCandleStick(); 5 | 6 | describe('bitmexCandlestick', () => { 7 | jest.setTimeout(30000); 8 | 9 | it('should fetch xbt_usd 5min candles in time range', async () => { 10 | const candles = await bitmexCandlestick.fetchCandleStickRange('xbt_usd', 5, 1529509826239 - 60000 * 60 * 24, 1529509826239); 11 | checkCandleStick(candles[0]); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/candlestick/bitmex-candlestick.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { CandleStick } from '../../exchange-types'; 4 | import { getCandleStickUrl, adaptBitmexCandlestick, BitmexRestCandlestick } from './internal'; 5 | 6 | export class BitmexCandleStick { 7 | /** 8 | * 9 | * @param corsProxy 10 | * @param bitfinexWebsocket 11 | */ 12 | constructor(private readonly corsProxy: string = '') {} 13 | 14 | /** 15 | * 16 | * @param pair 17 | * @param minutesFoot 18 | * @param start 19 | * @param end 20 | */ 21 | async fetchCandleStickRange(pair: string, minutesFoot: number, start: number, end: number): Promise { 22 | // https://www.bitmex.com/api/udf/history?symbol=EOSZ18&resolution=60&from=1544974016&to=1545578876 23 | // resolution = 1, 5, 60, D 24 | const originUrl = getCandleStickUrl(pair, minutesFoot, start, end); 25 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 26 | 27 | const candlestick: BitmexRestCandlestick = await fetch(url).then(res => res.json()); 28 | 29 | return adaptBitmexCandlestick(candlestick); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/candlestick/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bitmex-candlestick'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/candlestick/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { CandleStick } from '../../../exchange-types'; 2 | import { getSymbol, apiEndPoint } from '../../bitmex-common'; 3 | import { BitmexRestCandlestick } from './types'; 4 | 5 | // https://www.bitmex.com/api/udf/history?symbol=EOSZ18&resolution=60&from=1544974016&to=1545578876 6 | // resolution = 1, 5, 60, D 7 | export function getCandleStickUrl(pair: string, minutesFoot: number, start: number, end: number): string { 8 | const symbol = getSymbol(pair); 9 | const resolution = minutesFoot >= 1440 ? 'D' : minutesFoot; 10 | const from = start / 1000; 11 | const to = end / 1000; 12 | 13 | return `${apiEndPoint}/udf/history?symbol=${symbol}&resolution=${resolution}&from=${from}&to=${to}`; 14 | } 15 | 16 | export function adaptBitmexCandlestick(candlestickRestData: BitmexRestCandlestick): CandleStick[] { 17 | return candlestickRestData.t.map((time, i) => ({ 18 | open: candlestickRestData.o[i], 19 | close: candlestickRestData.c[i], 20 | high: candlestickRestData.h[i], 21 | low: candlestickRestData.l[i], 22 | volume: candlestickRestData.v[i], 23 | timestamp: time * 1000, 24 | })); 25 | } 26 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/candlestick/internal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './functions'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/candlestick/internal/types.ts: -------------------------------------------------------------------------------- 1 | export interface BitmexRestCandlestick { 2 | s: 'ok'; 3 | o: number[]; 4 | h: number[]; 5 | l: number[]; 6 | c: number[]; 7 | v: number[]; 8 | t: number[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/index.ts: -------------------------------------------------------------------------------- 1 | export { BitmexApi } from './bitmex-api'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/orderbook/bitmex-orderbook.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkOrderbook } from '../../exchange-test.functions'; 4 | import { BitmexOrderbook } from './bitmex-orderbook'; 5 | import { BitmexWebsocket } from '../websocket'; 6 | 7 | const bitmexWebsocket = new BitmexWebsocket(); 8 | const bitmexOrderbook = new BitmexOrderbook('', bitmexWebsocket); 9 | 10 | describe('bitmexOrderbook', () => { 11 | jest.setTimeout(30000); 12 | 13 | const markets = ['xbt_usd']; 14 | 15 | markets.forEach((market) => { 16 | it(`should get orderbook realtime ${market}`, (done) => { 17 | bitmexOrderbook 18 | .orderbook$(market) 19 | .pipe(take(4)) 20 | .subscribe( 21 | (orderbook) => { 22 | checkOrderbook(orderbook); 23 | }, 24 | (e) => console.log('Error'), 25 | () => { 26 | bitmexOrderbook.stopOrderbook(market); 27 | bitmexWebsocket.destroy(); 28 | done(); 29 | }, 30 | ); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/orderbook/bitmex-orderbook.ts: -------------------------------------------------------------------------------- 1 | import { Observable, concat } from 'rxjs'; 2 | import { Orderbook } from '../../exchange-types'; 3 | import { getSymbol } from '../bitmex-common'; 4 | import { BitmexWebsocket } from '../websocket'; 5 | import { filter, take, scan, map } from 'rxjs/operators'; 6 | import { bitmexUpdateOrderbook, adaptBitmexOrderbook, BitmexOrderbookWebsocketData } from './internal'; 7 | 8 | export class BitmexOrderbook { 9 | private readonly pairOderbookStreamMap = new Map>(); 10 | 11 | /** 12 | * @param corsProxy 13 | * @param bitfinexWebsocket 14 | */ 15 | constructor(private readonly corsProxy: string = '', private readonly bitmexWebsocket: BitmexWebsocket) {} 16 | 17 | async fetchOrderbook(pair: string): Promise { 18 | return Promise.resolve(undefined); 19 | } 20 | 21 | orderbook$(pair: string): Observable { 22 | let stream = this.pairOderbookStreamMap.get(pair); 23 | if (!stream) { 24 | stream = this.startOrderbook$(pair); 25 | this.pairOderbookStreamMap.set(pair, stream); 26 | } 27 | 28 | return stream; 29 | } 30 | 31 | stopOrderbook(pair: string): void { 32 | const channel = getOrderbookChannel(pair); 33 | this.bitmexWebsocket.unsubscribe(channel); 34 | this.pairOderbookStreamMap.delete(pair); 35 | } 36 | 37 | private startOrderbook$(pair: string): Observable { 38 | const channel = getOrderbookChannel(pair); 39 | 40 | const data$ = this.bitmexWebsocket.subscribe(channel); 41 | 42 | /* 43 | * Make sure 'partial' come first and then 'insert' 'update' 'delete' 44 | */ 45 | const snapshot$ = data$.pipe( 46 | filter((orderbookData) => orderbookData.action === 'partial'), 47 | take(1), 48 | ); 49 | 50 | const update$ = data$.pipe( 51 | filter( 52 | (orderbookData) => orderbookData.action === 'update' || orderbookData.action === 'insert' || orderbookData.action === 'delete', 53 | ), 54 | ); 55 | 56 | return concat(snapshot$, update$).pipe( 57 | scan(bitmexUpdateOrderbook), 58 | map(adaptBitmexOrderbook), 59 | ); 60 | } 61 | } 62 | 63 | function getOrderbookChannel(pair: string): string { 64 | const symbol = getSymbol(pair); 65 | 66 | return `orderBookL2_25:${symbol}`; 67 | } 68 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/orderbook/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bitmex-orderbook'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/orderbook/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Orderbook } from '../../../exchange-types'; 2 | import { WebsocketData } from '../../websocket'; 3 | import { BitmexOrderbookWebsocketData } from './types'; 4 | 5 | export function adaptBitmexOrderbook(orderbookWs: WebsocketData): Orderbook { 6 | const orderbook: Orderbook = { 7 | bids: [], 8 | asks: [], 9 | }; 10 | 11 | for (const item of orderbookWs.data) { 12 | const orderbookItem: [string, string] = [`${item.price}`, `${item.size}`]; 13 | if (item.side === 'Sell') { 14 | orderbook.asks.push(orderbookItem); 15 | } else { 16 | orderbook.bids.push(orderbookItem); 17 | } 18 | } 19 | 20 | orderbook.asks.reverse(); 21 | 22 | return orderbook; 23 | } 24 | 25 | export function bitmexUpdateOrderbook( 26 | origin: WebsocketData, 27 | update: WebsocketData, 28 | ): WebsocketData { 29 | const originData = origin.data || []; 30 | const updateData = update.data || []; 31 | 32 | origin.data = originData; 33 | 34 | let currentPosition = 0; 35 | for (const updateItem of updateData) { 36 | const position = findPosition(originData, updateItem, currentPosition); 37 | if (originData[position] && originData[position].id === updateItem.id) { 38 | // found => update size or delete 39 | if (update.action === 'update') { 40 | originData[position].size = updateItem.size; 41 | currentPosition = position + 1; 42 | } else if (update.action === 'delete') { 43 | originData.splice(position, 1); 44 | currentPosition = position; 45 | } 46 | } else if (update.action === 'insert') { 47 | // not found, insert to position 48 | originData.splice(position, 0, updateItem); 49 | currentPosition = position + 1; 50 | } 51 | } 52 | 53 | return origin; 54 | } 55 | 56 | // [{"symbol":"XBTUSD","id":8799599650,"side":"Sell","size":229425,"price":4003.5},{"symbol":"XBTUSD","id":8799600900,"side":"Buy","size":72647,"price":3991}]} 57 | 58 | /** 59 | * find the first position in origin that id larger or equal than update's id 60 | * 61 | * @param origin 62 | * @param update 63 | * @param fromId 64 | */ 65 | function findPosition(origin: BitmexOrderbookWebsocketData[], update: BitmexOrderbookWebsocketData, fromId: number = 0): number { 66 | let i = fromId; 67 | for (; i < origin.length; i++) { 68 | if (origin[i].id >= update.id) { 69 | break; 70 | } 71 | } 72 | 73 | return i; 74 | } 75 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/orderbook/internal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './functions'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/orderbook/internal/types.ts: -------------------------------------------------------------------------------- 1 | // { "symbol": "XBTUSD", "id": 8799601600, "side": "Sell", "size": 539077, "price": 3984 } 2 | export interface BitmexOrderbookWebsocketData { 3 | symbol: string; 4 | id: number; 5 | side: 'Sell' | 'Buy'; 6 | size: number; 7 | price: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/ticker/bitmex-ticker.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkTicker } from '../../exchange-test.functions'; 2 | import { BitmexTicker } from './bitmex-ticker'; 3 | 4 | const bitmexTicker = new BitmexTicker(); 5 | 6 | describe('bitmexTicker', () => { 7 | const markets = ['xbt_usd']; 8 | markets.forEach((market) => { 9 | it(`should fetch ticker ${market}`, () => {}); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/ticker/bitmex-ticker.ts: -------------------------------------------------------------------------------- 1 | import { Observable, EMPTY } from 'rxjs'; 2 | 3 | import { Ticker } from '../../exchange-types'; 4 | 5 | export class BitmexTicker { 6 | fetchTicker$(pair: string): Observable { 7 | return EMPTY; 8 | } 9 | 10 | ticker$(pair: string): Observable { 11 | // receive rawTicker and adapt to Ticker here 12 | return EMPTY; 13 | } 14 | 15 | stopTicker(pair: string): void { 16 | // implement 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/ticker/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bitmex-ticker'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/ticker/internal/functions.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/bitmex/ticker/internal/functions.ts -------------------------------------------------------------------------------- /src/exchanges/bitmex/ticker/internal/types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/bitmex/ticker/internal/types.ts -------------------------------------------------------------------------------- /src/exchanges/bitmex/trade/bitmex-trade.spec.ts: -------------------------------------------------------------------------------- 1 | import { bufferCount, take } from 'rxjs/operators'; 2 | 3 | import { checkTrades } from '../../exchange-test.functions'; 4 | import { BitmexTrade } from './bitmex-trade'; 5 | import { BitmexWebsocket } from '../websocket'; 6 | 7 | const bitmexWebsocket = new BitmexWebsocket(); 8 | const bitmexTrade = new BitmexTrade('', bitmexWebsocket); 9 | 10 | describe('bitfinexTrade', () => { 11 | jest.setTimeout(30000); 12 | 13 | const markets = ['xbt_usd']; 14 | 15 | markets.forEach((market) => { 16 | it(`should get realtime trades ${market}`, (done) => { 17 | bitmexTrade 18 | .trade$(market) 19 | .pipe( 20 | bufferCount(5), 21 | take(1), 22 | ) 23 | .subscribe( 24 | (trades) => { 25 | checkTrades(trades); 26 | }, 27 | (e) => console.log('Error'), 28 | () => { 29 | bitmexTrade.stopTrade(market); 30 | bitmexWebsocket.destroy(); 31 | done(); 32 | }, 33 | ); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/trade/bitmex-trade.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { Trade } from '../../exchange-types'; 3 | import { getSymbol } from '../bitmex-common'; 4 | import { BitmexWebsocket } from '../websocket'; 5 | import { BitmexTradeWebsocketData, adaptBitmexTrade } from './internal'; 6 | import { map } from 'rxjs/operators'; 7 | 8 | export class BitmexTrade { 9 | /** 10 | * 11 | * @param corsProxy 12 | * @param bitfinexWebsocket 13 | */ 14 | constructor(private readonly corsProxy: string = '', private readonly bitmexWebsocket: BitmexWebsocket) {} 15 | 16 | // fetch trades 17 | async fetchTrades(pair: string, start?: number, end?: number, limit?: number, sort?: number): Promise { 18 | return Promise.resolve(undefined); 19 | } 20 | 21 | /** 22 | * latest trade 23 | * 24 | * @param pair 25 | */ 26 | trade$(pair: string): Observable { 27 | const channel = getTradeChannel(pair); 28 | 29 | return this.bitmexWebsocket.subscribe(channel).pipe(map((wsData) => adaptBitmexTrade(wsData.data[0]))); 30 | } 31 | 32 | stopTrade(pair: string): void { 33 | const channel = getTradeChannel(pair); 34 | this.bitmexWebsocket.unsubscribe(channel); 35 | } 36 | } 37 | 38 | function getTradeChannel(pair: string): string { 39 | const symbol = getSymbol(pair); 40 | 41 | return `trade:${symbol}`; 42 | } 43 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/trade/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bitmex-trade'; 2 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/trade/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Trade } from '../../../exchange-types'; 2 | import { BitmexTradeWebsocketData } from './types'; 3 | 4 | export function adaptBitmexTrade(bitmexTrade: BitmexTradeWebsocketData): Trade { 5 | return { 6 | id: 1, 7 | side: bitmexTrade.side === 'Buy' ? 'buy' : 'sell', 8 | price: bitmexTrade.price, 9 | amount: bitmexTrade.size, 10 | timestamp: new Date(bitmexTrade.timestamp).getTime(), 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/trade/internal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './functions'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/trade/internal/types.ts: -------------------------------------------------------------------------------- 1 | // { 2 | // "table": "trade", 3 | // "action": "partial", 4 | // "keys": [], 5 | // "types": { 6 | // "timestamp": "timestamp", 7 | // "symbol": "symbol", 8 | // "side": "symbol", 9 | // "size": "long", 10 | // "price": "float", 11 | // "tickDirection": "symbol", 12 | // "trdMatchID": "guid", 13 | // "grossValue": "long", 14 | // "homeNotional": "float", 15 | // "foreignNotional": "float", 16 | // }, 17 | // "foreignKeys": { 18 | // "symbol": "instrument", 19 | // "side": "side", 20 | // }, 21 | // "attributes": { 22 | // "timestamp": "sorted", 23 | // "symbol": "grouped", 24 | // }, 25 | // "filter": { "symbol": "XBTUSD" }, 26 | // "data": [ 27 | // { 28 | // "timestamp": "2018-12-23T14:23:57.689Z", 29 | // "symbol": "XBTUSD", 30 | // "side": "Buy", 31 | // "size": 1000, 32 | // "price": 3975.5, 33 | // "tickDirection": "PlusTick", 34 | // "trdMatchID": "1c1cce1b-3e9d-0a83-181f-c96eb0725cca", 35 | // "grossValue": 25154000, 36 | // "homeNotional": 0.25154, 37 | // "foreignNotional": 1000, 38 | // }, 39 | // ], 40 | // } 41 | 42 | export interface BitmexTradeWebsocketData { 43 | timestamp: string; 44 | symbol: string; 45 | side: 'Buy' | 'Sell'; 46 | size: number; 47 | price: number; 48 | tickDirection: string; 49 | trdMatchID: string; 50 | grossValue: number; 51 | homeNotional: number; 52 | foreignNotional: number; 53 | } 54 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/websocket/bitmex-websocket.ts: -------------------------------------------------------------------------------- 1 | import { ReplaySubject, Observable } from 'rxjs'; 2 | import { ExchangeWebsocket } from '../../exchange-websocket.abstract'; 3 | import { wsEndpoint } from '../bitmex-common'; 4 | import { WebsocketRequest, WebsocketResponse, WebsocketData } from './types'; 5 | 6 | export class BitmexWebsocket extends ExchangeWebsocket { 7 | private readonly keyStreamMap = new Map>(); 8 | 9 | constructor(endPoint?: string) { 10 | super(endPoint || wsEndpoint); 11 | } 12 | 13 | /** 14 | * handle message 15 | * 16 | * @param message 17 | */ 18 | handleMessage(message: WebsocketResponse | WebsocketData): void { 19 | // console.log('handleMessage ==>', message); 20 | const wsData = message as WebsocketData; 21 | if (wsData.table) { 22 | const symbol = wsData.data && wsData.data.length ? wsData.data[0].symbol : ''; 23 | const key = `${wsData.table}:${symbol}`; 24 | const stream = this.keyStreamMap.get(key); 25 | if (stream) { 26 | stream.next(wsData); 27 | } 28 | } 29 | } 30 | 31 | // {"op": "subscribe", "args": "orderBookL2_25:XBTUSD"} 32 | subscribe(args: string): Observable> { 33 | let stream = this.keyStreamMap.get(args); 34 | 35 | if (!stream) { 36 | stream = new ReplaySubject>(1); 37 | this.keyStreamMap.set(args, stream); 38 | } 39 | 40 | this.send({ op: 'subscribe', args }); 41 | 42 | return stream.asObservable(); 43 | } 44 | 45 | /** 46 | * Unsubscribe channel 47 | * 48 | * @param arg 49 | */ 50 | unsubscribe(arg: string): void { 51 | const stream = this.keyStreamMap.get(arg); 52 | if (stream) { 53 | stream.complete(); 54 | this.keyStreamMap.delete(arg); 55 | this.send({ op: 'unsubscribe', args: arg }); 56 | } 57 | 58 | // TODO handle when unsubscribe complete 59 | } 60 | 61 | onDestroy(): void { 62 | // TODO complete all streams 63 | // clear stream map and key map 64 | this.keyStreamMap.clear(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/websocket/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bitmex-websocket'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/exchanges/bitmex/websocket/types.ts: -------------------------------------------------------------------------------- 1 | // { "op": "subscribe", "args": ["orderBookL2_25:XBTUSD"] } 2 | export interface WebsocketRequest { 3 | op: 'subscribe' | 'unsubscribe'; 4 | args: string | string[]; 5 | } 6 | 7 | // { "success": true, "subscribe": "orderBookL2_25:XBTUSD", "request": { "op": "subscribe", "args": ["orderBookL2_25:XBTUSD"] } } 8 | export interface WebsocketResponse { 9 | success: boolean; 10 | subscribe: string; 11 | request?: WebsocketRequest; 12 | } 13 | 14 | export interface WebsocketData { 15 | // Table name / Subscription topic. 16 | // Could be "trade", "order", "instrument", etc. 17 | table: string; 18 | 19 | // The type of the message. Types: 20 | // 'partial'; This is a table image, replace your data entirely. 21 | // 'update': Update a single row. 22 | // 'insert': Insert a new row. 23 | // 'delete': Delete a row. 24 | action: 'partial' | 'update' | 'insert' | 'delete'; 25 | 26 | // An array of table rows is emitted here. They are identical in structure to data returned from the REST API. 27 | data: T[]; 28 | 29 | // 30 | // The below fields define the table and are only sent on a `partial` 31 | // 32 | 33 | // Attribute names that are guaranteed to be unique per object. 34 | // If more than one is provided, the key is composite. 35 | // Use these key names to uniquely identify rows. Key columns are guaranteed 36 | // to be present on all data received. 37 | keys?: string[]; 38 | 39 | // This lists key relationships with other tables. 40 | // For example, `quote`'s foreign key is {symbol: 'instrument'} 41 | foreignKeys?: { [key: string]: string }; 42 | 43 | // This lists the shape of the table. The possible types: 44 | // "symbol" - In most languages this is equal to "string" 45 | // "guid" 46 | // "timestamp" 47 | // "timespan" 48 | // "float" 49 | // "long" 50 | // "integer" 51 | // "boolean" 52 | types?: { [key: string]: string }; 53 | 54 | // When multiple subscriptions are active to the same table, use the `filter` to correlate which datagram 55 | // belongs to which subscription, as the `table` property will not contain the subscription's symbol. 56 | filter?: { account?: number; symbol?: string }; 57 | 58 | // These are internal fields that indicate how responses are sorted and grouped. 59 | attributes?: { [key: string]: string }; 60 | } 61 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/README.md: -------------------------------------------------------------------------------- 1 | # Coinbase specific API -------------------------------------------------------------------------------- /src/exchanges/coinbase/candlestick/coinbase-candlestick.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkCandleStick } from '../../exchange-test.functions'; 2 | import { CoinbaseCandleStick } from './coinbase-candlestick'; 3 | 4 | const coinbaseCandlestick = new CoinbaseCandleStick(); 5 | 6 | describe('coinbaseCandlestick', () => { 7 | /** 8 | * Rest api candlestick 9 | */ 10 | it('should fetch btc_usd 5min candles in provided time range', async () => { 11 | const candles = await coinbaseCandlestick.fetchCandleStickRange('btc_usd', 5); 12 | checkCandleStick(candles[0]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/candlestick/coinbase-candlestick.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { CandleStick } from '../../exchange-types'; 4 | import { getCandleStickUrl, adaptCoinbaseCandleStick } from './internal/functions'; 5 | import { CoinbaseRawCandleStick } from './internal/types'; 6 | 7 | export class CoinbaseCandleStick { 8 | private readonly corsProxy: string; 9 | 10 | /** 11 | * 12 | * @param corsProxy 13 | */ 14 | constructor(corsProxy: string = '') { 15 | this.corsProxy = corsProxy; 16 | } 17 | 18 | async fetchCandleStickRange(pair: string, minutesFoot: number, start?: number, end?: number): Promise { 19 | const originUrl = getCandleStickUrl(pair, minutesFoot, start, end); 20 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 21 | 22 | const rawCandlesticks: CoinbaseRawCandleStick[] = await fetch(url).then(res => res.json()); 23 | 24 | return rawCandlesticks.map(adaptCoinbaseCandleStick).sort((c1, c2) => c1.timestamp - c2.timestamp); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/candlestick/index.ts: -------------------------------------------------------------------------------- 1 | export { CoinbaseCandleStick } from './coinbase-candlestick'; 2 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/candlestick/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { CandleStick } from '../../../exchange-types'; 2 | import { getProductId, apiEndpoint } from '../../coinbase-common'; 3 | import { CoinbaseRawCandleStick } from './types'; 4 | 5 | // /products//candles 6 | export function getCandleStickUrl(pair: string, minutesFoot: number, start?: number, end?: number): string { 7 | const granularity = minutesFoot * 60; 8 | let url = `${apiEndpoint}/products/${getProductId(pair)}/candles?granularity=${granularity}`; 9 | 10 | if (start) { 11 | url += `&start=${new Date(start).toISOString()}`; 12 | } 13 | if (end) { 14 | url += `&end=${new Date(end).toISOString()}`; 15 | } 16 | 17 | return url; 18 | } 19 | 20 | export function adaptCoinbaseCandleStick(rawCandle: CoinbaseRawCandleStick): CandleStick { 21 | return { 22 | low: rawCandle[1], 23 | high: rawCandle[2], 24 | open: rawCandle[3], 25 | close: rawCandle[4], 26 | volume: rawCandle[5], 27 | timestamp: rawCandle[0] * 1000, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/candlestick/internal/types.ts: -------------------------------------------------------------------------------- 1 | export type CoinbaseRawCandleStick = [ 2 | // time 3 | number, 4 | // low 5 | number, 6 | // high 7 | number, 8 | // open 9 | number, 10 | // close 11 | number, 12 | // volume 13 | number 14 | ]; 15 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/coinbase-common.ts: -------------------------------------------------------------------------------- 1 | export const apiEndpoint = 'https://api.pro.coinbase.com'; 2 | export const websocketEndpoint = 'wss://ws-feed.pro.coinbase.com'; 3 | 4 | // btc_usd => BTC-USD 5 | export function getProductId(pair: string): string { 6 | return pair.replace('_', '-').toUpperCase(); 7 | } 8 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/coinbase-common.types.ts: -------------------------------------------------------------------------------- 1 | // { "type": "subscribe", "product_ids": ["BTC-USD"], "channels": ["ticker"] } 2 | export interface WebsocketRequest { 3 | type: 'subscribe' | 'unsubscribe'; 4 | product_ids: string[]; 5 | channels: string[]; 6 | } 7 | 8 | // { "type": "subscriptions", "channels": [{ "name": "ticker", "product_ids": ["BTC-USD"] }] } 9 | // { 10 | // "type": "ticker", 11 | // "sequence": 5994036198, 12 | // "product_id": "BTC-USD", 13 | // "price": "7652.01000000", 14 | // "open_24h": "7580.00000000", 15 | // "volume_24h": "7490.10788184", 16 | // "low_24h": "7351.01000000", 17 | // "high_24h": "7697.09000000", 18 | // "volume_30d": "268954.72320294", 19 | // "best_bid": "7652", 20 | // "best_ask": "7652.01" 21 | // } 22 | export interface WebsocketMessageResponse { 23 | // ticker, heartbeat, l2update, snapshot 24 | type: string; 25 | // 'BTC-USD', 26 | product_id: string; 27 | // '2017-10-06T17:17:16.118000Z', 28 | time?: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/index.ts: -------------------------------------------------------------------------------- 1 | export { CoinbaseApi } from './coinbase-api'; 2 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/orderbook/coinbase-orderbook.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkOrderbook } from '../../exchange-test.functions'; 4 | import { CoinbaseOrderbook } from './coinbase-orderbook'; 5 | 6 | const coinbaseOrderbook = new CoinbaseOrderbook(); 7 | 8 | describe('coinbaseOrderbook', () => { 9 | jest.setTimeout(10000); 10 | 11 | const markets = ['btc_usd', 'eth_usd']; 12 | 13 | /** 14 | * Rest api orderbook 15 | */ 16 | markets.forEach((market) => { 17 | it(`should fetch orderbook api ${market}`, async () => { 18 | const orderbook = await coinbaseOrderbook.fetchOrderbook(market); 19 | checkOrderbook(orderbook); 20 | }); 21 | }); 22 | 23 | /** 24 | * Realtime orderbook 25 | */ 26 | markets.forEach((market) => { 27 | it(`should listen orderbook realtime ${market}`, (done) => { 28 | coinbaseOrderbook 29 | .orderbook$(market) 30 | .pipe(take(3)) 31 | .subscribe( 32 | (orderbook) => { 33 | checkOrderbook(orderbook); 34 | }, 35 | (e) => console.log('Error'), 36 | () => { 37 | coinbaseOrderbook.stopOrderbook(market); 38 | done(); 39 | }, 40 | ); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/orderbook/index.ts: -------------------------------------------------------------------------------- 1 | export { CoinbaseOrderbook } from './coinbase-orderbook'; 2 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/orderbook/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Orderbook } from '../../../exchange-types'; 2 | import { getProductId, apiEndpoint } from '../../coinbase-common'; 3 | import { CoinbaseWsOrderbookSnapshot, CoinbaseWsOrderbookUpdate, CoinbaseRestOrderbook } from './types'; 4 | 5 | // /products/BTC-USD/book?level=2 6 | export function getOrderbookUrl(pair: string): string { 7 | return `${apiEndpoint}/products/${getProductId(pair)}/book?level=2`; 8 | } 9 | 10 | export function adaptCoinbaseRestOrderbook(restOrderbook: CoinbaseRestOrderbook): Orderbook { 11 | return { 12 | bids: restOrderbook.bids.map((restBid) => <[string, string]>restBid.slice(0, 2)), 13 | asks: restOrderbook.asks.map((restAsk) => <[string, string]>restAsk.slice(0, 2)), 14 | }; 15 | } 16 | 17 | export function adaptCoinbaseWsOrderbookSnapshot(snapshot: CoinbaseWsOrderbookSnapshot): Orderbook { 18 | return { 19 | bids: snapshot.bids.slice(0, 50), 20 | asks: snapshot.asks.slice(0, 50), 21 | }; 22 | } 23 | 24 | export function adaptCoinbaseWsOrderbookUpdate(update: CoinbaseWsOrderbookUpdate): Orderbook { 25 | const orderbook: Orderbook = { bids: [], asks: [] }; 26 | update.changes.forEach((change) => { 27 | const orderbookItem: [string, string] = [change[1], change[2]]; 28 | if (change[0] === 'buy') { 29 | orderbook.bids.push(orderbookItem); 30 | } else if (change[0] === 'sell') { 31 | orderbook.asks.push(orderbookItem); 32 | } 33 | }); 34 | 35 | return orderbook; 36 | } 37 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/orderbook/internal/types.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketMessageResponse } from '../../coinbase-common.types'; 2 | 3 | // { 4 | // "sequence": "3", 5 | // "bids": [ 6 | // [price, size, num - orders], 7 | // ["295.96", "4.39088265", 2], 8 | // ... 9 | // ], 10 | // "asks": [ 11 | // [price, size, num - orders], 12 | // ["295.97", "25.23542881", 12], 13 | // ... 14 | // ] 15 | // } 16 | export interface CoinbaseRestOrderbook { 17 | sequence: string; 18 | // [price, size, num - orders] 19 | bids: [string, string, number][]; 20 | asks: [string, string, number][]; 21 | } 22 | 23 | // { 24 | // "type": "snapshot", 25 | // "product_id": "BTC-EUR", 26 | // "bids": [["6500.11", "0.45054140"]], 27 | // "asks": [["6500.15", "0.57753524"]] 28 | // } 29 | export interface CoinbaseWsOrderbookSnapshot extends WebsocketMessageResponse { 30 | type: 'snapshot'; 31 | bids: [string, string][]; 32 | asks: [string, string][]; 33 | } 34 | 35 | // { 36 | // "type": "l2update", 37 | // "product_id": "BTC-EUR", 38 | // "changes": [ 39 | // ["buy", "6500.09", "0.84702376"], 40 | // ["sell", "6507.00", "1.88933140"], 41 | // ["sell", "6505.54", "1.12386524"], 42 | // ["sell", "6504.38", "0"] 43 | // ] 44 | // } 45 | export interface CoinbaseWsOrderbookUpdate extends WebsocketMessageResponse { 46 | type: 'l2update'; 47 | changes: ['buy' | 'sell', string, string][]; 48 | } 49 | 50 | // export interface CoinbaseRawRestTicker { 51 | // trade_id: number; 52 | // price: string; 53 | // size: string; 54 | // bid: string; 55 | // ask: string; 56 | // volume: string; 57 | // time: string; 58 | // } 59 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/ticker/coinbase-ticker.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { checkTicker } from '../../exchange-test.functions'; 4 | import { CoinbaseTicker } from './coinbase-ticker'; 5 | 6 | const coinbaseTicker = new CoinbaseTicker(); 7 | 8 | describe('coinbaseTicker', () => { 9 | jest.setTimeout(20000); 10 | 11 | const markets = ['btc_usd', 'eth_usd']; 12 | 13 | /** 14 | * Rest api ticker 15 | */ 16 | markets.forEach((market) => { 17 | it(`should fetch ticker api ${market}`, async () => { 18 | const ticker = await coinbaseTicker.fetchTicker(market); 19 | checkTicker(ticker); 20 | }); 21 | }); 22 | 23 | /** 24 | * Realtime ticker 25 | */ 26 | markets.forEach((market) => { 27 | it(`should get ticker realtime ${market}`, (done) => { 28 | coinbaseTicker 29 | .ticker$(market) 30 | .pipe(take(2)) 31 | .subscribe( 32 | (ticker) => { 33 | checkTicker(ticker); 34 | }, 35 | (e) => console.log('Error'), 36 | () => { 37 | coinbaseTicker.stopTicker(market); 38 | done(); 39 | }, 40 | ); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/ticker/coinbase-ticker.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { fetchRxjs } from '../../../common'; 7 | import { Ticker } from '../../exchange-types'; 8 | import { WebsocketRequest } from '../coinbase-common.types'; 9 | import { getProductId } from '../coinbase-common'; 10 | import { CoinbaseWebsocket } from '../coinbase-websocket'; 11 | 12 | import { adaptCoinbaseRawWsTicker, adaptCoinbaseRawRestTicker, getTickerUrl } from './internal/functions'; 13 | import { CoinbaseRawWsTicker, CoinbaseRawRestTicker } from './internal/types'; 14 | 15 | export class CoinbaseTicker { 16 | private readonly corsProxy: string; 17 | private readonly coinbaseWebsocket: CoinbaseWebsocket; 18 | 19 | constructor(corsProxy: string = '', coinbaseWebsocket?: CoinbaseWebsocket) { 20 | this.corsProxy = corsProxy; 21 | this.coinbaseWebsocket = coinbaseWebsocket || new CoinbaseWebsocket(); 22 | } 23 | 24 | async fetchTicker(pair: string): Promise { 25 | // receive rawTicker and adapt to Ticker here 26 | const originUrl = getTickerUrl(pair); 27 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 28 | 29 | const rawTicker: CoinbaseRawRestTicker = await fetch(url).then(res => res.json()); 30 | 31 | return adaptCoinbaseRawRestTicker(rawTicker, pair) 32 | } 33 | 34 | ticker$(pair: string): Observable { 35 | // receive rawTicker and adapt to Ticker here 36 | const request: WebsocketRequest = { 37 | type: 'subscribe', 38 | channels: ['ticker'], 39 | product_ids: [getProductId(pair)], 40 | }; 41 | 42 | return this.coinbaseWebsocket 43 | .subscribe(request) 44 | .pipe(map((rawTicker) => adaptCoinbaseRawWsTicker(rawTicker, pair))); 45 | } 46 | 47 | stopTicker(pair: string): void { 48 | const request: WebsocketRequest = { 49 | type: 'unsubscribe', 50 | channels: ['ticker'], 51 | product_ids: [getProductId(pair)], 52 | }; 53 | 54 | this.coinbaseWebsocket.unsubscribe(request); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/ticker/index.ts: -------------------------------------------------------------------------------- 1 | export { CoinbaseTicker } from './coinbase-ticker'; 2 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/ticker/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from '../../../exchange-types'; 2 | import { getProductId, apiEndpoint } from '../../coinbase-common'; 3 | import { CoinbaseRawWsTicker, CoinbaseRawRestTicker } from './types'; 4 | 5 | // /products//ticker 6 | export function getTickerUrl(pair: string): string { 7 | return `${apiEndpoint}/products/${getProductId(pair)}/ticker`; 8 | } 9 | 10 | export function adaptCoinbaseRawWsTicker(coinbaseRawTicker: CoinbaseRawWsTicker, pair: string): Ticker { 11 | return { 12 | pair, 13 | ask: +coinbaseRawTicker.best_ask, 14 | bid: +coinbaseRawTicker.best_bid, 15 | low: +coinbaseRawTicker.low_24h, 16 | high: +coinbaseRawTicker.high_24h, 17 | last: +coinbaseRawTicker.price, 18 | vol: +coinbaseRawTicker.volume_24h, 19 | timestamp: new Date(coinbaseRawTicker.time || '').getTime(), 20 | }; 21 | } 22 | 23 | export function adaptCoinbaseRawRestTicker(coinbaseRawTicker: CoinbaseRawRestTicker, pair: string): Ticker { 24 | return { 25 | pair, 26 | ask: +coinbaseRawTicker.ask, 27 | bid: +coinbaseRawTicker.bid, 28 | low: 0, 29 | high: 0, 30 | last: +coinbaseRawTicker.price, 31 | vol: +coinbaseRawTicker.volume, 32 | timestamp: new Date(coinbaseRawTicker.time).getTime(), 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/ticker/internal/types.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketMessageResponse } from '../../coinbase-common.types'; 2 | 3 | export interface CoinbaseRawWsTicker extends WebsocketMessageResponse { 4 | sequence: number; // 4163689926, 5 | price: string; // '4387.95000000', 6 | open_24h: string; // '4305.09000000', 7 | volume_24h: string; // '6333.47909087', 8 | low_24h: string; // '4387.95000000', 9 | high_24h: string; // '4407.00000000', 10 | volume_30d: string; // '393681.12140634', 11 | best_bid: string; // '4387.94', 12 | best_ask: string; // '4387.95', 13 | side: string; // 'buy', 14 | trade_id: number; // 21659949, 15 | last_size: string; // '0.00001363' 16 | } 17 | 18 | export interface CoinbaseRawRestTicker { 19 | trade_id: number; 20 | price: string; 21 | size: string; 22 | bid: string; 23 | ask: string; 24 | volume: string; 25 | time: string; 26 | } 27 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/trade/coinbase-trade.spec.ts: -------------------------------------------------------------------------------- 1 | import { bufferCount, take } from 'rxjs/operators'; 2 | 3 | import { checkTrades } from '../../exchange-test.functions'; 4 | import { CoinbaseTrade } from './coinbase-trade'; 5 | 6 | const coinbaseTrade = new CoinbaseTrade(); 7 | 8 | describe('coinbaseTrade', () => { 9 | jest.setTimeout(20000); 10 | 11 | const markets = ['btc_usd']; 12 | 13 | /** 14 | * Rest api trades 15 | */ 16 | markets.forEach((market) => { 17 | it(`should fetch rest api trades ${market}`, async () => { 18 | const trades = await coinbaseTrade.fetchTrades(market); 19 | checkTrades(trades, false); 20 | }); 21 | }); 22 | 23 | /** 24 | * Realtime trade 25 | */ 26 | markets.forEach((market) => { 27 | it(`should get realtime trades ${market}`, (done) => { 28 | coinbaseTrade 29 | .trade$(market) 30 | .pipe( 31 | bufferCount(5), 32 | take(1), 33 | ) 34 | .subscribe( 35 | (trades) => { 36 | checkTrades(trades); 37 | }, 38 | (e) => console.log('Error'), 39 | () => { 40 | coinbaseTrade.stopTrade(market); 41 | done(); 42 | }, 43 | ); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/trade/coinbase-trade.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { fetchRxjs } from '../../../common'; 7 | import { Trade } from '../../exchange-types'; 8 | import { WebsocketRequest } from '../coinbase-common.types'; 9 | import { getProductId } from '../coinbase-common'; 10 | import { CoinbaseWebsocket } from '../coinbase-websocket'; 11 | 12 | import { adaptCoinbaseRawTrade, getTradesUrl } from './internal/functions'; 13 | import { CoinbaseRawWsTrade, CoinbaseRawRestTrade } from './internal/types'; 14 | 15 | export class CoinbaseTrade { 16 | private readonly corsProxy: string; 17 | private readonly coinbaseWebsocket: CoinbaseWebsocket; 18 | 19 | /** 20 | * 21 | * @param corsProxy 22 | * @param coinbaseWebsocket 23 | */ 24 | constructor(corsProxy: string = '', coinbaseWebsocket?: CoinbaseWebsocket) { 25 | this.corsProxy = corsProxy; 26 | this.coinbaseWebsocket = coinbaseWebsocket || new CoinbaseWebsocket(); 27 | } 28 | 29 | // fetch trades 30 | async fetchTrades(pair: string): Promise { 31 | const originUrl = getTradesUrl(pair); 32 | const url = this.corsProxy ? this.corsProxy + originUrl : originUrl; 33 | 34 | const rawTrades: CoinbaseRawRestTrade[] = await fetch(url).then(res => res.json()); 35 | 36 | return rawTrades.map(adaptCoinbaseRawTrade); 37 | } 38 | 39 | trade$(pair: string): Observable { 40 | const request: WebsocketRequest = { 41 | type: 'subscribe', 42 | channels: ['matches'], 43 | product_ids: [getProductId(pair)], 44 | }; 45 | 46 | return this.coinbaseWebsocket.subscribe(request).pipe(map(adaptCoinbaseRawTrade)); 47 | } 48 | 49 | stopTrade(pair: string): void { 50 | const request: WebsocketRequest = { 51 | type: 'unsubscribe', 52 | channels: ['matches'], 53 | product_ids: [getProductId(pair)], 54 | }; 55 | 56 | this.coinbaseWebsocket.unsubscribe(request); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/trade/index.ts: -------------------------------------------------------------------------------- 1 | export { CoinbaseTrade } from './coinbase-trade'; 2 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/trade/internal/functions.ts: -------------------------------------------------------------------------------- 1 | import { Trade } from '../../../exchange-types'; 2 | import { getProductId, apiEndpoint } from '../../coinbase-common'; 3 | import { CoinbaseRawWsTrade, CoinbaseRawRestTrade } from './types'; 4 | 5 | // /products//trades 6 | export function getTradesUrl(pair: string): string { 7 | return `${apiEndpoint}/products/${getProductId(pair)}/trades`; 8 | } 9 | 10 | export function adaptCoinbaseRawTrade(coinbaseRawTrade: CoinbaseRawWsTrade | CoinbaseRawRestTrade): Trade { 11 | return { 12 | id: coinbaseRawTrade.trade_id, 13 | side: coinbaseRawTrade.side, 14 | price: +coinbaseRawTrade.price, 15 | amount: +coinbaseRawTrade.size, 16 | timestamp: new Date(coinbaseRawTrade.time || '').getTime(), 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/exchanges/coinbase/trade/internal/types.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketMessageResponse } from '../../coinbase-common.types'; 2 | 3 | // { 4 | // "type": "match", 5 | // "trade_id": 10, 6 | // "sequence": 50, 7 | // "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", 8 | // "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", 9 | // "time": "2014-11-07T08:19:27.028459Z", 10 | // "product_id": "BTC-USD", 11 | // "size": "5.23512", 12 | // "price": "400.23", 13 | // "side": "sell" 14 | // } 15 | export interface CoinbaseRawWsTrade extends WebsocketMessageResponse { 16 | type: 'match'; 17 | trade_id: number; 18 | sequence: number; 19 | maker_order_id: string; 20 | taker_order_id: string; 21 | size: string; 22 | price: string; 23 | // side of maker sell: up, buy: down 24 | side: 'buy' | 'sell'; 25 | } 26 | 27 | // "time": "2014-11-07T01:08:43.642366Z", 28 | // "trade_id": 73, 29 | // "price": "100.00000000", 30 | // "size": "0.01000000", 31 | // "side": "sell" 32 | export interface CoinbaseRawRestTrade { 33 | time: string; 34 | trade_id: number; 35 | size: string; 36 | price: string; 37 | // side of maker sell: up, buy: down 38 | side: 'buy' | 'sell'; 39 | } 40 | -------------------------------------------------------------------------------- /src/exchanges/coincheck/candlestick/coincheck-candlestick.ts: -------------------------------------------------------------------------------- 1 | export class CoincheckCandleStick {} 2 | -------------------------------------------------------------------------------- /src/exchanges/coincheck/coincheck-api.ts: -------------------------------------------------------------------------------- 1 | import { Observable, EMPTY } from 'rxjs'; 2 | 3 | import { ExchangeApi } from '../exchange-api.abstract'; 4 | import { ExchangeInfo, SupportFeatures, Ticker, Orderbook, Trade, CandleStick } from '../exchange-types'; 5 | import { CoincheckTicker } from './ticker/coincheck-ticker'; 6 | 7 | export class CoincheckApi extends ExchangeApi { 8 | private readonly coincheckTicker: CoincheckTicker; 9 | 10 | get exchangeInfo(): ExchangeInfo { 11 | return { 12 | name: 'coincheck', 13 | logoUrl: 'https://coincheck-logo.png', 14 | homepage: 'https://www.coincheck.com/', 15 | country: 'Coincheck country', 16 | }; 17 | } 18 | 19 | get markets(): string[] { 20 | return ['btc_jpy']; 21 | } 22 | 23 | get representativeMarkets(): string[] { 24 | return ['btc_jpy']; 25 | } 26 | 27 | get supportFeatures(): SupportFeatures { 28 | return { 29 | ticker: false, 30 | orderbook: false, 31 | chart: false, 32 | }; 33 | } 34 | 35 | constructor() { 36 | super(); 37 | this.coincheckTicker = new CoincheckTicker(); 38 | } 39 | 40 | // api request for ticker 41 | async fetchTicker(pair: string): Promise { 42 | return this.coincheckTicker.fetchTicker(pair); 43 | } 44 | 45 | // realtime ticker 46 | ticker$(pair: string): Observable { 47 | return this.coincheckTicker.ticker$(pair); 48 | } 49 | 50 | // stop realtime ticker 51 | stopTicker(pair: string): void { 52 | this.coincheckTicker.stopTicker(pair); 53 | } 54 | 55 | // api request for depth 56 | async fetchOrderbook(pair: string): Promise { 57 | return Promise.resolve(undefined as any); 58 | } 59 | 60 | // realtime depth 61 | orderbook$(pair: string): Observable { 62 | return EMPTY; 63 | } 64 | 65 | // stop realtime orderbook 66 | stopOrderbook(pair: string): void { 67 | // implement 68 | } 69 | 70 | async fetchTrades(pair: string): Promise { 71 | return Promise.resolve(undefined as any); 72 | } 73 | 74 | trade$(pair: string): Observable { 75 | return EMPTY; 76 | } 77 | 78 | // stop realtime trade 79 | stopTrade(pair: string): void { 80 | // implement 81 | } 82 | 83 | // request candlestick by time range and resolution 84 | async fetchCandleStickRange(pair: string, minutesFoot: number, start: number, end: number): Promise { 85 | return Promise.resolve(undefined as any); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/exchanges/coincheck/coincheck-functions.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from '../exchange-types'; 2 | import { CoincheckRawTicker } from './coincheck-types'; 3 | 4 | export const publicUrl = 'https://coincheck.com'; 5 | 6 | export function adaptCoincheckRawTicker(coincheckRawTicker: CoincheckRawTicker, pair: string): Ticker { 7 | return { 8 | pair, 9 | ask: coincheckRawTicker.ask, 10 | bid: coincheckRawTicker.bid, 11 | low: coincheckRawTicker.low, 12 | high: coincheckRawTicker.high, 13 | last: coincheckRawTicker.last, 14 | vol: coincheckRawTicker.volume, 15 | timestamp: coincheckRawTicker.timestamp, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/exchanges/coincheck/coincheck-types.ts: -------------------------------------------------------------------------------- 1 | export interface CoincheckRawTicker { 2 | last: number; 3 | bid: number; 4 | ask: number; 5 | high: number; 6 | low: number; 7 | volume: number; 8 | timestamp: number; 9 | } 10 | -------------------------------------------------------------------------------- /src/exchanges/coincheck/index.ts: -------------------------------------------------------------------------------- 1 | export { CoincheckApi } from './coincheck-api'; 2 | -------------------------------------------------------------------------------- /src/exchanges/coincheck/orderbook/coincheck-orderbook.ts: -------------------------------------------------------------------------------- 1 | export class CoincheckOrderbook {} 2 | -------------------------------------------------------------------------------- /src/exchanges/coincheck/ticker/coincheck-ticker.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkTicker } from '../../exchange-test.functions'; 2 | import { CoincheckTicker } from './coincheck-ticker'; 3 | 4 | const coincheckTicker = new CoincheckTicker(); 5 | 6 | describe('coincheckTicker', () => { 7 | const markets = ['btc_jpy']; 8 | markets.forEach((market) => { 9 | it(`should fetch ticker ${market}`, async () => { 10 | const ticker = await coincheckTicker.fetchTicker(market); 11 | console.log(ticker.pair, ticker.last); 12 | checkTicker(ticker); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/exchanges/coincheck/ticker/coincheck-ticker.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | import { Observable, EMPTY } from 'rxjs'; 4 | 5 | import { Ticker } from '../../exchange-types'; 6 | import { CoincheckRawTicker } from '../coincheck-types'; 7 | import { adaptCoincheckRawTicker, publicUrl } from '../coincheck-functions'; 8 | 9 | export class CoincheckTicker { 10 | async fetchTicker(pair: string): Promise { 11 | const url = `${publicUrl}/api/ticker`; 12 | 13 | const rawTicker: CoincheckRawTicker = await fetch(url).then(res => res.json()); 14 | 15 | return adaptCoincheckRawTicker(rawTicker, pair); 16 | } 17 | 18 | ticker$(pair: string): Observable { 19 | // receive rawTicker and adapt to Ticker here 20 | return EMPTY; 21 | } 22 | 23 | stopTicker(pair: string): void { 24 | // implement 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/exchanges/exchange-api.abstract.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { scan } from 'rxjs/operators'; 3 | 4 | import { ExchangeInfo, SupportFeatures, Ticker, Orderbook, Trade, CandleStick, ExchangeOptions } from './exchange-types'; 5 | import { updateLastCandleWithNewTrade } from './helper.functions'; 6 | import { defaultOptions } from './exchange-default.options'; 7 | 8 | export abstract class ExchangeApi { 9 | protected options: ExchangeOptions; 10 | abstract get exchangeInfo(): ExchangeInfo; 11 | abstract get markets(): string[]; 12 | abstract get representativeMarkets(): string[]; 13 | abstract get supportFeatures(): SupportFeatures; 14 | 15 | constructor(options?: ExchangeOptions) { 16 | this.options = { ...defaultOptions, ...options }; 17 | } 18 | 19 | // request ticker 20 | abstract fetchTicker(pair: string): Promise; 21 | // realtime ticker 22 | abstract ticker$(pair: string): Observable; 23 | // stop realtime ticker 24 | abstract stopTicker(pair: string): void; 25 | 26 | // request orderbook 27 | abstract fetchOrderbook(pair: string): Promise; 28 | // realtime orderbook 29 | abstract orderbook$(pair: string): Observable; 30 | // stop realtime orderbook 31 | abstract stopOrderbook(pair: string): void; 32 | 33 | // request trades 34 | abstract fetchTrades(pair: string): Promise; 35 | // realtime trades 36 | abstract trade$(pair: string): Observable; 37 | // stop realtime trades 38 | abstract stopTrade(pair: string): void; 39 | 40 | // request candlestick (used in tradingview or other chart) 41 | abstract fetchCandleStickRange(pair: string, minutesFoot: number, start: number, end: number): Promise; 42 | 43 | // realtime last candlestick using initial last candle and realtime trade (used for tradingview datafeed) 44 | lastCandle$(pair: string, initialLastCandle: CandleStick, minutesFoot: number): Observable { 45 | return this.trade$(pair).pipe( 46 | scan((candle: CandleStick, trade: Trade) => updateLastCandleWithNewTrade(candle, trade, minutesFoot), initialLastCandle), 47 | ); 48 | } 49 | 50 | stopLastCandle(pair: string): void { 51 | // stop listening to realtime trades 52 | this.stopTrade(pair); 53 | } 54 | 55 | /** 56 | * Private api 57 | */ 58 | 59 | // abstract spotOrder(pair: string, side: 'buy' | 'sell', price: string, amount: string): Observable; 60 | // abstract cancelOrder(orderId: string): Observable; 61 | // abstract activeOrder(pair?: string): Observable; 62 | // abstract orderHistory(pair?: string): Observable; 63 | // abstract tradeHistory(pair?: string): Observable; 64 | } 65 | -------------------------------------------------------------------------------- /src/exchanges/exchange-api.test.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { ExchangeApi } from './exchange-api.abstract'; 4 | import { checkTicker } from './exchange-test.functions'; 5 | 6 | export class ExchangeApiTest { 7 | private readonly exchange: ExchangeApi; 8 | 9 | constructor(exchange: ExchangeApi) { 10 | this.exchange = exchange; 11 | } 12 | 13 | run(): void { 14 | testExchange(this.exchange); 15 | } 16 | } 17 | 18 | function testExchange(exchange: ExchangeApi): void { 19 | const markets = exchange.representativeMarkets; 20 | const supportFeatures = exchange.supportFeatures; 21 | 22 | describe(`${exchange.exchangeInfo.name} exchange`, () => { 23 | // it test for ticker 24 | if (supportFeatures.ticker) { 25 | markets.forEach((market) => { 26 | it(`should get ticker ${market} realtime`, (done) => { 27 | exchange 28 | .ticker$(market) 29 | .pipe(take(1)) 30 | .subscribe(checkTicker, (e) => console.log('Error', e), done); 31 | }); 32 | }); 33 | } 34 | 35 | // it test for orderbook 36 | if (supportFeatures.orderbook) { 37 | it('should get orderbook realtime for all pairs', () => { 38 | /* test orderbook */ 39 | }); 40 | } 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /src/exchanges/exchange-default.options.ts: -------------------------------------------------------------------------------- 1 | import { ExchangeOptions } from './exchange-types'; 2 | 3 | export const defaultOptions: ExchangeOptions = { 4 | apiKey: '', 5 | apiSecret: '', 6 | corsProxy: '', 7 | }; 8 | -------------------------------------------------------------------------------- /src/exchanges/exchange-types.ts: -------------------------------------------------------------------------------- 1 | export interface ExchangeOptions { 2 | apiKey?: string; 3 | apiSecret?: string; 4 | corsProxy?: string; 5 | } 6 | 7 | /* 8 | * Exchange basic info 9 | */ 10 | export interface ExchangeInfo { 11 | name: string; 12 | logoUrl: string; 13 | homepage: string; 14 | country: string; 15 | } 16 | 17 | /* 18 | * Ticker 19 | */ 20 | export interface Ticker { 21 | pair: string; 22 | ask: number; 23 | bid: number; 24 | low: number; 25 | high: number; 26 | last: number; 27 | vol: number; 28 | timestamp: number; 29 | change24?: number; 30 | change24Perc?: number; 31 | isRequest?: boolean; 32 | open?: number; 33 | prevClose?: number; 34 | } 35 | 36 | /* 37 | * Orderbook 38 | */ 39 | export interface Orderbook { 40 | // [price, amount] 41 | asks: [string, string][]; // asc order 42 | bids: [string, string][]; // des order 43 | lastUpdateId?: number; 44 | timestamp?: number; 45 | } 46 | 47 | export interface Trade { 48 | id: number; 49 | side: 'buy' | 'sell'; 50 | price: number; 51 | amount: number; 52 | timestamp: number; 53 | } 54 | 55 | /* 56 | * CandleStick 57 | */ 58 | export interface CandleStick { 59 | open: number; 60 | high: number; 61 | low: number; 62 | close: number; 63 | volume: number; 64 | timestamp: number; 65 | type?: number; // number in minute ex. 1 (1 minute), 60 (1hour) 66 | } 67 | 68 | export interface SupportFeatures { 69 | ticker: boolean; 70 | orderbook: boolean; 71 | chart: boolean; 72 | } 73 | -------------------------------------------------------------------------------- /src/exchanges/exchange-websocket.abstract.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketRxJs } from '../common/websocket-rxjs'; 2 | 3 | export type SocketFactory = (endPoint: string) => WebSocketRxJs; 4 | 5 | /** 6 | * Abstract class for websocket api 7 | * 8 | * @param T: ws send request type 9 | * @param U: raw ws response type 10 | */ 11 | export abstract class ExchangeWebsocket { 12 | private ws: WebSocketRxJs | null = null; 13 | constructor(private readonly endPoint: string, private readonly createSocket: SocketFactory = socketFactory) {} 14 | 15 | abstract handleMessage(response: U): void; 16 | abstract onDestroy(): void; 17 | 18 | send(request: T): void { 19 | if (!this.ws) { 20 | this.initWebsocket(); 21 | } 22 | 23 | if (this.ws) { 24 | this.ws.send(JSON.stringify(request)); 25 | } 26 | } 27 | 28 | destroy(): void { 29 | if (this.ws) { 30 | this.ws.close(); 31 | this.ws = null; 32 | } 33 | 34 | this.onDestroy(); 35 | } 36 | 37 | private initWebsocket(): void { 38 | if (this.ws) { 39 | throw new Error('websocket is already initialized'); 40 | } 41 | 42 | this.ws = this.createSocket(this.endPoint); 43 | this.ws.message$.subscribe((response) => { 44 | this.handleMessage(response); 45 | }); 46 | } 47 | } 48 | 49 | function socketFactory(endPoint: string): WebSocketRxJs { 50 | return new WebSocketRxJs(endPoint); 51 | } 52 | -------------------------------------------------------------------------------- /src/exchanges/helper.functions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains common pure helpful functions 3 | */ 4 | 5 | import { CandleStick, Trade } from './exchange-types'; 6 | 7 | /** 8 | * @param candle 9 | * @param trade 10 | * @param minutesFoot 11 | */ 12 | export function updateLastCandleWithNewTrade(candle: CandleStick, trade: Trade, minutesFoot: number): CandleStick { 13 | if (candle) { 14 | const candleEndTimestamp = candle.timestamp + minutesFoot * 60000; 15 | // ignore the trade if its timestamp is old 16 | if (trade.timestamp < candle.timestamp) { 17 | return candle; 18 | } 19 | 20 | // update the candle with new trade 21 | if (trade.timestamp <= candleEndTimestamp) { 22 | return { 23 | open: candle.open, 24 | high: Math.max(candle.high, trade.price), 25 | low: Math.min(candle.low, trade.price), 26 | close: trade.price, 27 | volume: candle.volume + trade.amount, 28 | timestamp: candle.timestamp, 29 | }; 30 | } 31 | } 32 | 33 | // create new candle with only 1 trade 34 | return { 35 | open: trade.price, 36 | high: trade.price, 37 | low: trade.price, 38 | close: trade.price, 39 | volume: trade.amount, 40 | timestamp: convertToCandleTimestamp(trade.timestamp, minutesFoot), 41 | }; 42 | } 43 | 44 | /** 45 | * @param timestamp 46 | * @param minutesFoot 47 | */ 48 | export function convertToCandleTimestamp(timestamp: number, minutesFoot: number): number { 49 | return timestamp - (timestamp % (minutesFoot * 60000)); 50 | } 51 | -------------------------------------------------------------------------------- /src/exchanges/index.ts: -------------------------------------------------------------------------------- 1 | export { ExchangeApi } from './exchange-api.abstract'; 2 | export { BinanceApi } from './binance'; 3 | export { BitbankApi } from './bitbank'; 4 | export { BitfinexApi } from './bitfinex'; 5 | export { CoinbaseApi } from './coinbase'; 6 | export { CoincheckApi } from './coincheck'; 7 | export * from './exchange-types'; 8 | export * from './exchange-websocket.abstract'; 9 | -------------------------------------------------------------------------------- /src/exchanges/sample/candlestick/index.ts: -------------------------------------------------------------------------------- 1 | export { SampleCandleStick } from './sample-candlestick'; 2 | -------------------------------------------------------------------------------- /src/exchanges/sample/candlestick/internal/functions.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/sample/candlestick/internal/functions.ts -------------------------------------------------------------------------------- /src/exchanges/sample/candlestick/internal/types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/sample/candlestick/internal/types.ts -------------------------------------------------------------------------------- /src/exchanges/sample/candlestick/sample-candlestick.ts: -------------------------------------------------------------------------------- 1 | export class SampleCandleStick {} 2 | -------------------------------------------------------------------------------- /src/exchanges/sample/index.ts: -------------------------------------------------------------------------------- 1 | export { SampleApi } from './sample-api'; 2 | -------------------------------------------------------------------------------- /src/exchanges/sample/orderbook/index.ts: -------------------------------------------------------------------------------- 1 | export { SampleOrderbook } from './sample-orderbook'; 2 | -------------------------------------------------------------------------------- /src/exchanges/sample/orderbook/internal/functions.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/sample/orderbook/internal/functions.ts -------------------------------------------------------------------------------- /src/exchanges/sample/orderbook/internal/types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/sample/orderbook/internal/types.ts -------------------------------------------------------------------------------- /src/exchanges/sample/orderbook/sample-orderbook.ts: -------------------------------------------------------------------------------- 1 | export class SampleOrderbook {} 2 | -------------------------------------------------------------------------------- /src/exchanges/sample/sample-api.ts: -------------------------------------------------------------------------------- 1 | import { Observable, EMPTY } from 'rxjs'; 2 | 3 | import { ExchangeApi } from '../exchange-api.abstract'; 4 | import { ExchangeInfo, SupportFeatures, Ticker, Orderbook, Trade, CandleStick } from '../exchange-types'; 5 | import { SampleTicker } from './ticker/sample-ticker'; 6 | 7 | export class SampleApi extends ExchangeApi { 8 | private readonly sampleTicker: SampleTicker; 9 | 10 | get exchangeInfo(): ExchangeInfo { 11 | return { 12 | name: 'sample', 13 | logoUrl: 'https://sample-logo.png', 14 | homepage: 'https://www.sample.com/', 15 | country: 'Exchange country', 16 | }; 17 | } 18 | 19 | get markets(): string[] { 20 | return ['btc_jpy']; 21 | } 22 | 23 | get representativeMarkets(): string[] { 24 | return ['btc_jpy']; 25 | } 26 | 27 | get supportFeatures(): SupportFeatures { 28 | return { 29 | ticker: false, 30 | orderbook: false, 31 | chart: false, 32 | }; 33 | } 34 | 35 | constructor() { 36 | super(); 37 | this.sampleTicker = new SampleTicker(); 38 | } 39 | 40 | // api request for ticker 41 | async fetchTicker(pair: string): Promise { 42 | return this.sampleTicker.fetchTicker(pair); 43 | } 44 | 45 | // realtime ticker 46 | ticker$(pair: string): Observable { 47 | return this.sampleTicker.ticker$(pair); 48 | } 49 | 50 | // stop realtime ticker 51 | stopTicker(pair: string): void { 52 | this.sampleTicker.stopTicker(pair); 53 | } 54 | 55 | // api request for depth 56 | async fetchOrderbook(pair: string): Promise { 57 | return Promise.resolve(undefined as any); 58 | } 59 | 60 | // realtime depth 61 | orderbook$(pair: string): Observable { 62 | return EMPTY; 63 | } 64 | 65 | // stop realtime orderbook 66 | stopOrderbook(pair: string): void { 67 | // implement 68 | } 69 | 70 | async fetchTrades(pair: string): Promise { 71 | return Promise.resolve(undefined as any); 72 | } 73 | 74 | trade$(pair: string): Observable { 75 | return EMPTY; 76 | } 77 | 78 | // stop realtime trade 79 | stopTrade(pair: string): void { 80 | // implement 81 | } 82 | 83 | // request candlestick by time range and resolution 84 | async fetchCandleStickRange(pair: string, minutesFoot: number, start: number, end: number): Promise { 85 | return Promise.resolve(undefined as any); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/exchanges/sample/sample-common.ts: -------------------------------------------------------------------------------- 1 | export const apiEndPoint = 'https://api.sample.com'; 2 | export const wsEndpoint = 'wss://www.sample.com/realtime'; 3 | 4 | // get sample pair 5 | export function samplePair(pair: string): string { 6 | return pair.replace('_', '').toUpperCase(); 7 | } 8 | -------------------------------------------------------------------------------- /src/exchanges/sample/ticker/index.ts: -------------------------------------------------------------------------------- 1 | export { SampleTicker } from './sample-ticker'; 2 | -------------------------------------------------------------------------------- /src/exchanges/sample/ticker/internal/functions.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/sample/ticker/internal/functions.ts -------------------------------------------------------------------------------- /src/exchanges/sample/ticker/internal/types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/sample/ticker/internal/types.ts -------------------------------------------------------------------------------- /src/exchanges/sample/ticker/sample-ticker.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkTicker } from '../../exchange-test.functions'; 2 | import { SampleTicker } from './sample-ticker'; 3 | 4 | const sampleTicker = new SampleTicker(); 5 | 6 | describe('sampleTicker', () => { 7 | const markets = ['btc_jpy']; 8 | markets.forEach((market) => { 9 | it(`should fetch ticker ${market}`, async () => { 10 | const ticker = await sampleTicker.fetchTicker(market); 11 | checkTicker(ticker); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/exchanges/sample/ticker/sample-ticker.ts: -------------------------------------------------------------------------------- 1 | import { Observable, EMPTY } from 'rxjs'; 2 | 3 | import { Ticker } from '../../exchange-types'; 4 | 5 | export class SampleTicker { 6 | async fetchTicker(pair: string): Promise { 7 | return Promise.resolve(undefined as any); 8 | } 9 | 10 | ticker$(pair: string): Observable { 11 | // receive rawTicker and adapt to Ticker here 12 | return EMPTY; 13 | } 14 | 15 | stopTicker(pair: string): void { 16 | // implement 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/exchanges/sample/trade/index.ts: -------------------------------------------------------------------------------- 1 | export { SampleTrade } from './sample-trade'; 2 | -------------------------------------------------------------------------------- /src/exchanges/sample/trade/internal/functions.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/sample/trade/internal/functions.ts -------------------------------------------------------------------------------- /src/exchanges/sample/trade/internal/types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dang1412/ccex-api/fb2c2e81a781667b120599ff90d003001f3a67bf/src/exchanges/sample/trade/internal/types.ts -------------------------------------------------------------------------------- /src/exchanges/sample/trade/sample-trade.ts: -------------------------------------------------------------------------------- 1 | export class SampleTrade {} 2 | -------------------------------------------------------------------------------- /src/global.type.ts: -------------------------------------------------------------------------------- 1 | declare const WorkerGlobalScope: any; 2 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export { updateOrderbook } from './update-orderbook/update-orderbook'; 2 | -------------------------------------------------------------------------------- /src/helpers/update-orderbook/upate-orderbook.spec.ts: -------------------------------------------------------------------------------- 1 | import { mergeBids, mergeAsks } from './update-orderbook'; 2 | 3 | interface BidTestCase { 4 | bids: [string, string][]; 5 | updateBids: [string, string][]; 6 | result: [string, string][]; 7 | } 8 | 9 | interface AskTestCase { 10 | asks: [string, string][]; 11 | updateAsks: [string, string][]; 12 | result: [string, string][]; 13 | } 14 | 15 | // bids test cases 16 | const bidTestCases: BidTestCase[] = [ 17 | { 18 | bids: [['1000', '1'], ['990', '1'], ['980', '1'], ['970', '1'], ['960', '1'], ['950', '1']], 19 | updateBids: [['995', '1'], ['990', '0']], 20 | result: [['1000', '1'], ['995', '1'], ['980', '1'], ['970', '1'], ['960', '1'], ['950', '1']], 21 | }, 22 | { 23 | bids: [['1000', '1'], ['990', '1'], ['980', '1'], ['970', '1'], ['960', '1'], ['950', '1']], 24 | updateBids: [['1100', '10'], ['990', '0'], ['960', '0.111'], ['950', '0'], ['940', '12']], 25 | result: [['1100', '10'], ['1000', '1'], ['980', '1'], ['970', '1'], ['960', '0.111'], ['940', '12']], 26 | }, 27 | { 28 | bids: [['1000', '1'], ['990', '1'], ['980', '1'], ['970', '1'], ['960', '1'], ['950', '1']], 29 | updateBids: [], 30 | result: [['1000', '1'], ['990', '1'], ['980', '1'], ['970', '1'], ['960', '1'], ['950', '1']], 31 | }, 32 | { 33 | bids: [['99999', '0'], ['99998', '1.11']], 34 | updateBids: [['1100', '10'], ['990', '0'], ['960', '0.111'], ['950', '0'], ['940', '12']], 35 | result: [['99998', '1.11'], ['1100', '10'], ['960', '0.111'], ['940', '12']], 36 | }, 37 | ]; 38 | 39 | // ask test cases 40 | const askTestCases: AskTestCase[] = [ 41 | { 42 | asks: [['1000', '1'], ['1100', '1'], ['1200', '1'], ['1300', '0']], 43 | updateAsks: [['900', '1'], ['1100', '0']], 44 | result: [['900', '1'], ['1000', '1'], ['1200', '1']], 45 | }, 46 | ]; 47 | 48 | describe('Test update-orderbook.ts helper functions', () => { 49 | bidTestCases.forEach((test, index) => { 50 | it(`Test mergeBids case # ${index}`, () => { 51 | const actualResult = mergeBids(test.bids, test.updateBids); 52 | console.log('bids actualResult', actualResult); 53 | expect(actualResult).toStrictEqual(test.result); // , '#' + index + ': actual result is different from expect result'); 54 | }); 55 | }); 56 | 57 | askTestCases.forEach((test, index) => { 58 | it(`Test mergeAsks case # ${index}`, () => { 59 | const actualResult = mergeAsks(test.asks, test.updateAsks); 60 | console.log('asks actualResult', actualResult); 61 | expect(actualResult).toStrictEqual(test.result); // , '#' + index + ': actual result is different from expect result'); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exchanges'; 2 | -------------------------------------------------------------------------------- /src/lib/isomorphic-ws/browser.js: -------------------------------------------------------------------------------- 1 | // https://github.com/maxogden/websocket-stream/blob/48dc3ddf943e5ada668c31ccd94e9186f02fafbd/ws-fallback.js 2 | 3 | var ws = null 4 | 5 | if (typeof WebSocket !== 'undefined') { 6 | ws = WebSocket 7 | } else if (typeof MozWebSocket !== 'undefined') { 8 | ws = MozWebSocket 9 | } else if (typeof global !== 'undefined') { 10 | ws = global.WebSocket || global.MozWebSocket 11 | } else if (typeof window !== 'undefined') { 12 | ws = window.WebSocket || window.MozWebSocket 13 | } else if (typeof self !== 'undefined') { 14 | ws = self.WebSocket || self.MozWebSocket 15 | } 16 | 17 | module.exports = ws 18 | -------------------------------------------------------------------------------- /src/lib/isomorphic-ws/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for isomorphic-ws 2 | // Run `npm install @types/ws` before using this. 3 | // Fix for https://github.com/heineiuo/isomorphic-ws/issues/8 4 | // If there is still something wrong, welcome issue. 5 | 6 | import * as WebSocket from 'ws'; 7 | 8 | export = WebSocket; 9 | -------------------------------------------------------------------------------- /src/lib/isomorphic-ws/node.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require('ws'); -------------------------------------------------------------------------------- /src/lib/isomorphic-ws/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node.js", 3 | "browser": "browser.js" 4 | } 5 | -------------------------------------------------------------------------------- /test/binance/user-account.ts: -------------------------------------------------------------------------------- 1 | import { apiKeys } from '../../src/exchanges/binance/api-key-test'; 2 | import { BinanceUserStream } from '../../src/exchanges/binance/user-stream/binance-user-stream'; 3 | 4 | const binanceUserStream = new BinanceUserStream(apiKeys.key); 5 | 6 | binanceUserStream.userDataAccount$().subscribe(accountInfo => { 7 | console.log(accountInfo); 8 | }); 9 | -------------------------------------------------------------------------------- /tsconfig.backup.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "outDir": "./dist", 5 | "rootDir": "src", 6 | "moduleResolution": "node", 7 | "module": "commonjs", 8 | "declaration": true, 9 | "inlineSourceMap": true, 10 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ , 11 | "strict": false /* Enable all strict type-checking options. */ , 12 | /* Strict Type-Checking Options */ 13 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 14 | // "strictNullChecks": true /* Enable strict null checks. */, 15 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 16 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 17 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 18 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 19 | /* Additional Checks */ 20 | "noUnusedLocals": true /* Report errors on unused locals. */ , 21 | "noUnusedParameters": false /* Report errors on unused parameters. */ , 22 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */ , 23 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ , 24 | /* Debugging Options */ 25 | "traceResolution": false /* Report module resolution log messages. */ , 26 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */ , 27 | "listFiles": false /* Print names of files part of the compilation. */ , 28 | "pretty": true /* Stylize errors and messages using color and context. */ , 29 | /* Experimental Options */ 30 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 31 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 32 | "lib": [ 33 | "es2017", 34 | "dom" 35 | ], 36 | "types": [ 37 | "node" 38 | ], 39 | "typeRoots": [ 40 | "node_modules/@types", 41 | ] 42 | }, 43 | "include": [ 44 | "src/**/*.ts" 45 | ], 46 | "exclude": [ 47 | "node_modules/**" 48 | ], 49 | "compileOnSave": false 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "strict": true, 8 | "types": [ 9 | "node", 10 | "jest" 11 | ], 12 | "typeRoots": [ 13 | "./node_modules/@types" 14 | ], 15 | "outDir": "./dist", 16 | "declaration": true 17 | }, 18 | "include": [ 19 | "src/**/*.ts" 20 | ], 21 | "exclude": [ 22 | "node_modules", 23 | "samples" 24 | ] 25 | } -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json' {} 2 | --------------------------------------------------------------------------------