├── .dockerignore
├── mocha.opts
├── .npmignore
├── demos
├── basic
│ ├── index.html
│ └── index.js
├── oauth
│ ├── index.html
│ └── index.js
├── advanced
│ ├── index.html
│ └── demo.js
└── node
│ ├── package.json
│ ├── node-demo.js
│ └── webpack.config.js
├── Dockerfile
├── .gitignore
├── src
├── calls
│ ├── index.js
│ ├── trade.js
│ ├── payments.js
│ ├── admin.js
│ ├── read.js
│ └── unauthenticated.js
├── index.js
├── ServerError.js
├── __tests__
│ ├── LiveApi-test.js
│ ├── ServerError-test.js
│ ├── trade-test.js
│ ├── LiveEvents-test.js
│ ├── useRx-test.js
│ ├── resubscribe-test.js
│ ├── oauth-test.js
│ ├── admin-test.js
│ ├── custom-test.js
│ ├── unauthenticated-test.js
│ ├── read-test.js
│ └── stateful-test.js
├── OAuth.js
├── LiveEvents.js
├── ApiState.js
├── custom.js
└── LiveApi.js
├── .eslintignore
├── .istanbul.yml
├── default.conf
├── .flowconfig
├── wallaby.conf.js
├── .eslintrc
├── webpack.config.js
├── gulpfile.js
├── LICENSE
├── README.md
├── package.json
├── docs
└── networkcalls.md
├── .circleci
└── config.yml
└── flow-typed
├── binary-live-api.js.flow
└── binary-api.js.flow
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | Dockerfile
3 |
--------------------------------------------------------------------------------
/mocha.opts:
--------------------------------------------------------------------------------
1 | --timeout 10000 ./src/**/__tests__/*.js
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | src
4 | test
5 | demos
6 | examples
7 | coverage
8 | node_modules
9 |
--------------------------------------------------------------------------------
/demos/basic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/demos/oauth/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:alpine
2 | COPY ./lib /usr/share/nginx/html
3 | COPY ./default.conf /etc/nginx/conf.d/default.conf
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | *.log
4 | .DS_Store
5 | lib
6 | coverage
7 | demos/node/dist
8 | demos/advanced/dist
9 | .publish
--------------------------------------------------------------------------------
/src/calls/index.js:
--------------------------------------------------------------------------------
1 | export * from './unauthenticated';
2 | export * from './read';
3 | export * from './trade';
4 | export * from './payments';
5 | export * from './admin';
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | coverage
3 | lib
4 | demos
5 | node_modules
6 | **/*/all.js
7 | webpack.*.js
8 | server.js
9 | karma.*.js
10 | test/integration
11 | ./src/_constants/texts.js
12 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as LiveEvents } from './LiveEvents';
2 | export { default as LiveApi } from './LiveApi';
3 | export { helpers } from './custom';
4 | export * as OAuth from './OAuth';
5 |
--------------------------------------------------------------------------------
/.istanbul.yml:
--------------------------------------------------------------------------------
1 | instrumentation:
2 | root: src
3 | include-all-sources: true
4 | verbose: true
5 | excludes:
6 | - index.js
7 | - __tests__/*.js
8 | reporting:
9 | dir: "coverage"
10 |
--------------------------------------------------------------------------------
/demos/advanced/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/demos/oauth/index.js:
--------------------------------------------------------------------------------
1 | console.log(window['binary-live-api']);
2 | const { oauthUrl, parseOAuthResponse } = window['binary-live-api'].OAuth;
3 |
4 | const url = oauthUrl('id-ud5PPOTeBcEnkam7ArXIc4AO9e9gw');
5 |
6 | window.location = url;
7 |
8 | // const accounts = parseOAuthResponse(returnUrl);
9 |
--------------------------------------------------------------------------------
/demos/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-demo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "node-demo.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "author": "Boris Yankov (https://github.com/borisyankov)",
11 | "license": "ISC",
12 | "dependencies": {
13 | "binary-live-api": "*",
14 | "ws": "^7.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/demos/node/node-demo.js:
--------------------------------------------------------------------------------
1 | var ws = require('ws');
2 | var LiveApi = require('binary-live-api').LiveApi;
3 |
4 | var api = new LiveApi({ websocket: ws });
5 |
6 | function pingWithEventHandlers() {
7 | api.events.on('ping', function(response) {
8 | console.log(response);
9 | });
10 | api.ping();
11 | }
12 |
13 | function pingWithPromises() {
14 | api.ping().then(function(response) {
15 | console.log(response);
16 | });
17 | }
18 |
19 | pingWithEventHandlers();
20 |
--------------------------------------------------------------------------------
/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 |
5 | add_header Cache-Control "public, max-age=7200, s-maxage=600, must-revalidate";
6 | charset UTF-8;
7 |
8 | error_page 404 /404.html;
9 |
10 | location @custom_error_503 {
11 | return 503;
12 | }
13 |
14 | location ~ /\.git {
15 | return 404;
16 | }
17 |
18 | location / {
19 | root /usr/share/nginx/html;
20 | index index.html index.htm;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | .*/wallaby.conf.js
3 | .*/node_modules
4 | .*/coverage
5 | .*/__tests__
6 | .*/lib
7 | .*/demos
8 | .*/webpack.config.js
9 |
10 | [include]
11 |
12 | [libs]
13 | ./node_modules/binary-utils/flow-typed/binary.js.flow
14 | ./node_modules/binary-utils/flow-typed/binary-utils.js.flow
15 | ./flow-typed/binary-api.js.flow
16 | ./flow-typed/binary-live-api.js.flow
17 |
18 | [options]
19 | esproposal.class_static_fields=enable
20 | esproposal.export_star_as=enable
21 | esproposal.class_instance_fields=enable
22 |
--------------------------------------------------------------------------------
/wallaby.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = (wallaby) => ({
2 | files: [{
3 | pattern: 'src/**/*.js*',
4 | load: false,
5 | }, {
6 | pattern: 'src/**/__tests__/*.js',
7 | ignore: true,
8 | }],
9 | tests: [
10 | 'src/**/__tests__/*.js',
11 | ],
12 | env: {
13 | type: 'node',
14 | },
15 | testFramework: 'jest',
16 | compilers: {
17 | '**/*.js': wallaby.compilers.babel({
18 | presets: [
19 | 'latests',
20 | ],
21 | }),
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "binary",
4 | "env": {
5 | "browser": true,
6 | "es6": true,
7 | "jest": true
8 | },
9 | "rules": {
10 | "camelcase": 0,
11 | "no-undef": 0,
12 | "react/jsx-indent": [0, "tab"],
13 | "react/prefer-stateless-function": 0,
14 | "react/jsx-indent-props": [0, "tab"],
15 | "react/jsx-filename-extension": 0,
16 | "import/named": 0,
17 | "import/default": 0,
18 | "import/namespace": 0,
19 | "import/prefer-default-export": 0,
20 | "import/no-extraneous-dependencies": 0,
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/ServerError.js:
--------------------------------------------------------------------------------
1 | export default class ServerError extends Error {
2 | stack: any;
3 | error: ApiErrorResponse;
4 | name: string;
5 | message: string;
6 |
7 | constructor(errorObj: ApiErrorResponse) {
8 | super(errorObj);
9 |
10 | this.stack = new Error().stack;
11 | this.error = errorObj;
12 | this.name = errorObj.error.code;
13 |
14 | const { error: { message }, echo_req } = errorObj;
15 |
16 | const echoStr = JSON.stringify(echo_req, null, 2);
17 | this.message = `[ServerError] ${message}\n${echoStr}`;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/calls/trade.js:
--------------------------------------------------------------------------------
1 | export const buyContract = (contractId: number, price: number) => ({
2 | buy: contractId,
3 | price,
4 | });
5 |
6 | export const buyContractParams = (params: Object, price: number) => ({
7 | buy : 1,
8 | price,
9 | parameters: params,
10 | });
11 |
12 | export const sellContract = (contractId: number, price: number) => ({
13 | sell: contractId,
14 | price,
15 | });
16 |
17 | export const sellExpiredContracts = () => ({
18 | sell_expired: 1,
19 | });
20 |
21 | export const topUpVirtualAccount = () => ({
22 | topup_virtual: 1,
23 | });
24 |
--------------------------------------------------------------------------------
/src/calls/payments.js:
--------------------------------------------------------------------------------
1 | export const getCashierLockStatus = () => ({
2 | cashier_password: 1,
3 | });
4 |
5 | export const setCashierLock = (options: Object) => ({
6 | cashier_password: 1,
7 | ...options,
8 | });
9 |
10 | export const withdrawToPaymentAgent = (options: Object) => ({
11 | paymentagent_withdraw: 1,
12 | ...options,
13 | });
14 |
15 | export const paymentAgentTransfer = (options: Object) => ({
16 | paymentagent_transfer: 1,
17 | ...options,
18 | });
19 |
20 | export const transferBetweenAccounts = (options: Object) => ({
21 | transfer_between_accounts: 1,
22 | ...options,
23 | });
24 |
--------------------------------------------------------------------------------
/demos/basic/index.js:
--------------------------------------------------------------------------------
1 | var LiveApi = window['binary-live-api'].LiveApi;
2 | var api = new LiveApi();
3 |
4 | function pingWithEventHandlers() {
5 | api.events.on('ping', function(response) {
6 | console.log(response);
7 | });
8 | api.ping();
9 | }
10 |
11 | function pingWithPromises() {
12 | api.ping().then(function(response) {
13 | console.log(response);
14 | });
15 | }
16 |
17 | function foreverPing() {
18 | setInterval(() => api.ping().then(response => console.log(response)), 1000);
19 | }
20 |
21 | api.subscribeToTick('R_100');
22 | api.unsubscribeFromTick('R_100');
23 |
24 | api.resubscribe();
25 |
--------------------------------------------------------------------------------
/src/__tests__/LiveApi-test.js:
--------------------------------------------------------------------------------
1 | import websocket from 'ws';
2 | import LiveApi from '../LiveApi';
3 |
4 | describe('LiveApi', () => {
5 | let liveApi;
6 |
7 | beforeAll(() => {
8 | liveApi = new LiveApi({ websocket, appId: 1089 });
9 | });
10 |
11 | it('can be created', () => {
12 | expect(liveApi).toBeDefined();
13 | });
14 |
15 | it('can be connected to', () => {
16 | expect(() => liveApi.connect()).not.toThrow();
17 | });
18 |
19 | it('can change language', () => {
20 | expect(() => liveApi.changeLanguage()).not.toThrow();
21 | });
22 |
23 | it('using api calls returns a Promise', () => {
24 | const response = liveApi.ping();
25 | expect(typeof response.then).toBe('function');
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/OAuth.js:
--------------------------------------------------------------------------------
1 | export const oauthUrl = (appId: number): string => `https://www.binary.com/oauth2/authorize?app_id=${appId}`;
2 |
3 | export const oauthUrlWithLanguage = (appId: number, langCode: string): string =>
4 | `https://www.binary.com/oauth2/authorize?app_id=${appId}&l=${langCode}`;
5 |
6 | type Account = {
7 | account: string,
8 | token: string,
9 | };
10 |
11 | export const parseOAuthResponse = (responseUrl: string): Account[] => {
12 | const matcher = /acct\d=(\w+)&token\d=([\w-]+)/g;
13 | const urlParts = responseUrl.split('/redirect?');
14 | if (urlParts.length !== 2) throw new Error('Not a valid url');
15 |
16 | const params = urlParts[1].split(matcher);
17 |
18 | const accounts = [];
19 |
20 | for (let i = 1; i < params.length; i += 3) {
21 | accounts.push({
22 | account: params[i],
23 | token : params[i + 1],
24 | });
25 | }
26 |
27 | return accounts;
28 | };
29 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | devtool: 'source-map',
6 | entry: ['./src/index.js'],
7 | plugins: [
8 | new webpack.DefinePlugin({
9 | 'process.env': {
10 | NODE_ENV: JSON.stringify('production'),
11 | },
12 | }),
13 | ],
14 | module: {
15 | loaders: [
16 | {
17 | test: /\.js$/,
18 | loader: 'babel-loader',
19 | include: path.join(__dirname, 'src'),
20 | },
21 | {
22 | test: /\.js$/,
23 | loader: 'eslint-loader',
24 | include: path.join(__dirname, 'src'),
25 | },
26 | ],
27 | },
28 | output: {
29 | library: 'binary-live-api',
30 | libraryTarget: 'umd',
31 | path: path.resolve(__dirname, 'lib'),
32 | filename: 'binary-live-api.js',
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/demos/node/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | devtool: 'eval',
6 | entry: [
7 | './demo'
8 | ],
9 | output: {
10 | path: path.join(__dirname, 'dist'),
11 | filename: 'bundle.js',
12 | publicPath: '/static/'
13 | },
14 | plugins: [
15 | new webpack.HotModuleReplacementPlugin(),
16 | new webpack.NoErrorsPlugin()
17 | ],
18 | resolve: {
19 | alias: {
20 | 'library-boilerplate': path.join(__dirname, '..', '..', 'src')
21 | },
22 | extensions: ['', '.js']
23 | },
24 | module: {
25 | loaders: [{
26 | test: /\.js$/,
27 | loaders: ['babel'],
28 | exclude: /node_modules/,
29 | include: __dirname
30 | }, {
31 | test: /\.js$/,
32 | loaders: ['babel'],
33 | include: path.join(__dirname, '..', '..', 'src')
34 | }]
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/src/__tests__/ServerError-test.js:
--------------------------------------------------------------------------------
1 | import ServerError from '../ServerError';
2 |
3 | describe('ServerError', () => {
4 | it('can be thrown', () => {
5 | expect(() => {
6 | throw new ServerError({ error: {} });
7 | }).toThrow(/ServerError/);
8 | });
9 |
10 | it('contains all elements of the error response', () => {
11 | const errorResponse = {
12 | echo_req: {
13 | some_key: 'some_value',
14 | },
15 | error: {
16 | message: 'Unrecognised request.',
17 | code : 'UnrecognisedRequest',
18 | },
19 | msg_type: 'error',
20 | };
21 |
22 | const error = new ServerError(errorResponse);
23 | const str = error.toString();
24 |
25 | expect(str).toContain('ServerError');
26 | expect(str).toContain('some_value');
27 | expect(str).toContain('Unrecognised request.');
28 | expect(error.name).toEqual('UnrecognisedRequest');
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var gh = require('gulp-gh-pages');
3 | var webpack = require('webpack');
4 | var gutil = require('gulp-util');
5 | var pkg = require('./package.json');
6 | /**
7 | * Push build to gh-pages
8 | */
9 |
10 | gulp.task('build', function(callback) {
11 | webpack(require('./webpack.config.js'), function(err, stats) {
12 | if(err) throw new gutil.PluginError("webpack", err);
13 | gutil.log("[webpack]", stats.toString());
14 |
15 | callback();
16 | });
17 | });
18 |
19 | gulp.task('versioning', ['build'], function () {
20 | var v = pkg.version;
21 | return gulp.src(['lib/*.*'])
22 | .pipe(gulp.dest('lib/' + v));
23 | });
24 |
25 | gulp.task('deploy', ['versioning'], function () {
26 | return gulp.src(["./lib/**/*", "./CNAME"])
27 | .pipe(gh({ force: true }));
28 | });
29 |
30 | gulp.task('deploy-prod', ['versioning'], function () {
31 | return gulp.src(["./lib/**/*", "./CNAME"])
32 | .pipe(gh({ force: true, origin: 'upstream' }));
33 | });
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present Binary Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/LiveEvents.js:
--------------------------------------------------------------------------------
1 | type LivEventHandler = (msgData: Object) => void;
2 |
3 | export default class LiveEvents {
4 | messageHandlers: Object;
5 |
6 | constructor() {
7 | this.messageHandlers = {};
8 | }
9 |
10 | emitSingle(msgType: string, msgData: Object) {
11 | const handlers = this.messageHandlers[msgType] || [];
12 | handlers.forEach(handler => {
13 | handler(msgData);
14 | });
15 | }
16 |
17 | emitWildcard(msgData: Object) {
18 | const handlers = this.messageHandlers['*'] || [];
19 | handlers.forEach(handler => {
20 | handler(msgData);
21 | });
22 | }
23 |
24 | emit(msgType: string, msgData: Object) {
25 | this.emitSingle(msgType, msgData);
26 | this.emitWildcard(msgData);
27 | }
28 |
29 | on(msgType: string, callback: LivEventHandler) {
30 | if (!this.messageHandlers[msgType]) {
31 | this.messageHandlers[msgType] = [callback];
32 | } else {
33 | this.messageHandlers[msgType].push(callback);
34 | }
35 | }
36 |
37 | ignoreAll(msgType: string) {
38 | delete this.messageHandlers[msgType];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/__tests__/trade-test.js:
--------------------------------------------------------------------------------
1 | import websocket from 'ws';
2 | import LiveApi from '../LiveApi';
3 |
4 | describe('trade', () => {
5 | let liveApi;
6 |
7 | beforeEach(() => {
8 | liveApi = new LiveApi({ websocket, appId: 1089 });
9 | });
10 |
11 | it('can buy contract', () => {
12 | expect(() => liveApi.buyContract('someid', 100)).not.toThrow();
13 | });
14 |
15 | it('can buy contract with parameters', () => {
16 | const parameters = {
17 | amount : 100,
18 | basis : 'payout', // or 'stake'
19 | contract_type: 'PUT', // or 'CALL'
20 | currency : 'USD',
21 | duration : 5,
22 | duration_unit: 't',
23 | symbol : 'frxEURUSD',
24 | };
25 | expect(() => liveApi.buyContract(parameters, 100)).not.toThrow();
26 | });
27 |
28 | it('can sell contract', () => {
29 | expect(() => liveApi.sellContract('someid', 100)).not.toThrow();
30 | });
31 |
32 | it('can sell expired contracts', () => {
33 | expect(() => liveApi.sellExpiredContracts()).not.toThrow();
34 | });
35 |
36 | it('can topup virtual account', () => {
37 | expect(() => liveApi.topUpVirtualAccount()).not.toThrow();
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/src/__tests__/LiveEvents-test.js:
--------------------------------------------------------------------------------
1 | import LiveEvents from '../LiveEvents';
2 |
3 | describe('LiveEvents', () => {
4 | let liveEvents;
5 |
6 | beforeEach(() => {
7 | liveEvents = new LiveEvents();
8 | });
9 |
10 | it('can create object', () => {
11 | expect(liveEvents).toBeTruthy();
12 | });
13 |
14 | it('can subscribe to events', () => {
15 | expect(() => liveEvents.on('message', () => {})).not.toThrow();
16 | });
17 |
18 | it('can emit events', () => {
19 | expect(() => liveEvents.emit('message', {})).not.toThrow();
20 | });
21 |
22 | it('can receive emitted events', done => {
23 | liveEvents.on('message', done);
24 | liveEvents.emit('message');
25 | });
26 |
27 | it('wildcard event handler should catch any event', done => {
28 | liveEvents.on('*', done);
29 | liveEvents.emit('message');
30 | });
31 |
32 | it('can have multiple handlers per message', done => {
33 | let handleCount = 0;
34 | const handler = () => {
35 | handleCount++;
36 | if (handleCount === 3) done();
37 | };
38 | liveEvents.on('message', handler);
39 | liveEvents.on('message', handler);
40 | liveEvents.on('message', handler);
41 | liveEvents.emit('message');
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/src/__tests__/useRx-test.js:
--------------------------------------------------------------------------------
1 | // import { Observable } from 'rx-lite';
2 | import 'babel-polyfill';
3 | import websocket from 'ws';
4 | import LiveApi from '../LiveApi';
5 |
6 | describe('use rx', () => {
7 | const apiWithRX = new LiveApi({ websocket, useRx: true, appId: 1089 });
8 |
9 | it('should return observable for any call', callback => {
10 | const obs = apiWithRX.ping();
11 |
12 | obs.subscribe(
13 | next => {
14 | expect(next.msg_type).toEqual('ping');
15 | },
16 | err => console.log(err), // eslint-disable-line no-console
17 | callback
18 | );
19 | obs.connect();
20 | });
21 |
22 | // simple example
23 | it('should make stream handling easier', callback => {
24 | const stream = apiWithRX.subscribeToTick('R_100');
25 |
26 | const avgPerTick = stream.scan((avg, json, idx) => {
27 | const currentVal = +json.tick.quote;
28 | const newAvg = (avg * idx + currentVal) / (idx + 1);
29 | return newAvg;
30 | }, 0);
31 |
32 | avgPerTick.take(2).subscribe(
33 | avg => {
34 | expect(typeof avg).toBe('number');
35 | },
36 | err => console.log(err), // eslint-disable-line no-console
37 | callback
38 | );
39 | stream.connect();
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/src/calls/admin.js:
--------------------------------------------------------------------------------
1 | export const deleteApiToken = (token: string) => ({
2 | api_token : 1,
3 | delete_token: token,
4 | });
5 |
6 | export const getApiTokens = () => ({
7 | api_token: 1,
8 | });
9 |
10 | export const createApiToken = (token: string, scopes: string[]) => ({
11 | api_token : 1,
12 | new_token : token,
13 | new_token_scopes: scopes,
14 | });
15 |
16 | export const changePassword = (oldPassword: string, newPassword: string) => ({
17 | change_password: 1,
18 | old_password : oldPassword,
19 | new_password : newPassword,
20 | });
21 |
22 | export const registerApplication = (options: Object) => ({
23 | app_register: 1,
24 | ...options,
25 | });
26 |
27 | export const getAllAppList = () => ({
28 | app_list: 1,
29 | });
30 |
31 | export const getAppslistById = (appid: number) => ({
32 | app_get: appid,
33 | });
34 |
35 | export const deleteApplication = (appid: number) => ({
36 | app_delete: appid,
37 | });
38 |
39 | export const createRealAccountMaltaInvest = (options: Object) => ({
40 | new_account_maltainvest: 1,
41 | ...options,
42 | });
43 |
44 | export const createRealAccount = (options: Object) => ({
45 | new_account_real: 1,
46 | ...options,
47 | });
48 |
49 | export const setAccountCurrency = (currency: string) => ({
50 | set_account_currency: currency,
51 | });
52 |
53 | export const setSelfExclusion = (options: Object) => ({
54 | set_self_exclusion: 1,
55 | ...options,
56 | });
57 |
58 | export const setAccountSettings = (options: Object) => ({
59 | set_settings: 1,
60 | ...options,
61 | });
62 |
63 | export const setTnCApproval = () => ({
64 | tnc_approval: 1,
65 | });
66 |
--------------------------------------------------------------------------------
/demos/advanced/demo.js:
--------------------------------------------------------------------------------
1 | var LiveApi = window['binary-live-api'].LiveApi;
2 |
3 | var api = new LiveApi();
4 |
5 | const token = 'qdJ86Avvrsh0Le4';
6 | api.authorize(token).then(
7 | () => console.log('Authorized!'),
8 | () => console.log('Not Authorized')
9 | );
10 |
11 | function tickHistoryDemo() {
12 | api.events.on('history', function(response) {
13 | console.log(response);
14 | });
15 | api.getTickHistory({symbol: 'frxUSDJPY', end: 'latest', count: 10});
16 | }
17 |
18 | function tickHistoryPromiseDemo() {
19 | api.getTickHistory('frxUSDJPY', {end: 'latest', count: 10}).then(function(response) {
20 | console.log(response);
21 | });
22 | }
23 |
24 | function forgetDemo() {
25 | api.unsubscribeFromAllTicks();
26 | }
27 |
28 | function tickStreamDemo() {
29 | api.events.on('tick', function(response) {
30 | console.log(response);
31 | });
32 | api.subscribeToTick('frxUSDJPY');
33 | }
34 |
35 | function pingDemo() {
36 | api.events.on('ping', function(response) {
37 | console.log(response);
38 | });
39 | api.ping();
40 | }
41 |
42 | function pingPromiseDemo() {
43 | api.ping().then(response => {
44 | console.log(response)
45 | });
46 | }
47 |
48 | function openPositionsDemo() {
49 | api.events.on('portfolio', function(response) {
50 | console.log(response);
51 | });
52 | api.getPortfolio();
53 | }
54 |
55 | function tradingTimesDemo() {
56 | api.events.on('trading_times', function(response) {
57 | console.log(response);
58 | });
59 | api.getTradingTimes();
60 | }
61 |
62 | api.events.on('*', function(response) {
63 | console.log('all', response);
64 | });
65 |
66 | console.log(api.events.messageHandlers);
67 |
68 | // tickHistoryPromiseDemo();
69 | pingPromiseDemo();
70 |
--------------------------------------------------------------------------------
/src/calls/read.js:
--------------------------------------------------------------------------------
1 | export const getAccountLimits = () => ({
2 | get_limits: 1,
3 | });
4 |
5 | export const getAccountSettings = () => ({
6 | get_settings: 1,
7 | });
8 |
9 | export const getAccountStatus = () => ({
10 | get_account_status: 1,
11 | });
12 |
13 | export const getSelfExclusion = () => ({
14 | get_self_exclusion: 1,
15 | });
16 |
17 | export const logOut = () => ({
18 | logout: 1,
19 | });
20 |
21 | export const getStatement = (options: Object) => ({
22 | statement: 1,
23 | ...options,
24 | });
25 |
26 | export const getPortfolio = () => ({
27 | portfolio: 1,
28 | });
29 |
30 | export const getProfitTable = (options: Object) => ({
31 | profit_table: 1,
32 | ...options,
33 | });
34 |
35 | export const getRealityCheckSummary = () => ({
36 | reality_check: 1,
37 | });
38 |
39 | export const subscribeToBalance = () => ({
40 | balance : 1,
41 | subscribe: 1,
42 | });
43 |
44 | export const unsubscribeFromBalance = () => ({
45 | forget_all: 'balance',
46 | });
47 |
48 | export const subscribeToOpenContract = (contractId: number) => ({
49 | proposal_open_contract: 1,
50 | subscribe : 1,
51 | contract_id : contractId,
52 | });
53 |
54 | export const getContractInfo = (contractId: number) => ({
55 | proposal_open_contract: 1,
56 | contract_id : contractId,
57 | });
58 |
59 | export const subscribeToAllOpenContracts = () => ({
60 | proposal_open_contract: 1,
61 | subscribe : 1,
62 | });
63 |
64 | export const unsubscribeFromAllOpenContracts = () => ({
65 | forget_all: 'proposal_open_contract',
66 | });
67 |
68 | export const subscribeToTransactions = () => ({
69 | transaction: 1,
70 | subscribe : 1,
71 | });
72 |
73 | export const unsubscribeFromTransactions = () => ({
74 | forget_all: 'transaction',
75 | });
76 |
--------------------------------------------------------------------------------
/src/__tests__/resubscribe-test.js:
--------------------------------------------------------------------------------
1 | import websocket from 'ws';
2 | import LiveApi from '../LiveApi';
3 |
4 | function sleep(ms = 0) {
5 | return new Promise(r => setTimeout(r, ms));
6 | }
7 |
8 | describe('resubscribe', () => {
9 | it('should reconnect when disconnected', async () => {
10 | const api = new LiveApi({ websocket, appId: 1089 });
11 |
12 | await api.ping();
13 |
14 | try {
15 | api.socket.close();
16 | } catch (e) {
17 | // ignore error
18 | }
19 |
20 | await sleep(2000);
21 |
22 | const response = await api.ping();
23 | expect(response.ping).toBeTruthy();
24 | });
25 |
26 | it.skip('should resubscribe all subscription after reconnect', async () => {
27 | const spy = jest.fn();
28 | const api = new LiveApi({ websocket, appId: 1089 });
29 |
30 | await api.ping();
31 |
32 | api.events.on('tick', spy);
33 |
34 | const ticks = ['R_100'];
35 | api.subscribeToTicks(ticks);
36 |
37 | try {
38 | api.socket.close();
39 | } catch (e) {
40 | // ignore error
41 | }
42 |
43 | await sleep(2000);
44 |
45 | expect(api.apiState.getState().ticks.has('R_100')).toEqual(true);
46 | expect(spy).toHaveBeenCalled();
47 | });
48 |
49 | // check if empty state, and no resubsription when new
50 | // check for specific resubsriptions
51 |
52 | it('should reject promise with DisconnectError when socket disconnected before response received', async () => {
53 | const api = new LiveApi({ websocket, appId: 1089 });
54 |
55 | await sleep(2000);
56 |
57 | const promise = api.ping();
58 |
59 | try {
60 | api.socket.close();
61 | } catch (e) {
62 | // ignore error
63 | }
64 |
65 | return promise.catch(err => expect(err.name).toEqual('DisconnectError'));
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/src/__tests__/oauth-test.js:
--------------------------------------------------------------------------------
1 | import { oauthUrl, oauthUrlWithLanguage, parseOAuthResponse } from '../OAuth';
2 |
3 | describe('OAuth', () => {
4 | const appId = 'id-ud5PPOTeBcEnkam7ArXIc4AO9e9gw';
5 |
6 | it('should be able to get the OAuth url', () => {
7 | const url = oauthUrl(appId);
8 | expect(url).toContain('binary.com');
9 | });
10 |
11 | it('should be able to get the OAuth url with language', () => {
12 | const url = oauthUrlWithLanguage(appId, 'RU');
13 | expect(url).toContain('binary.com');
14 | expect(url).toContain('RU');
15 | });
16 |
17 | it('should be able to parse the simplest response url', () => {
18 | const response = '/redirect?acct1=vr123&token1=a1-456';
19 | const parsed = parseOAuthResponse(response);
20 | const expected = [{ account: 'vr123', token: 'a1-456' }];
21 | expect(expected).toEqual(parsed);
22 | });
23 |
24 | it('should throw an exception if the url is not valid', () => {
25 | expect(() => parseOAuthResponse('not valid')).toThrow();
26 | });
27 |
28 | it('should parse multipe account url', () => {
29 | const response = '/redirect?acct1=vr123&token1=a1-456&acct2=cc123&token2=a1-bob&acct3=ml123&token3=a1-hello';
30 | const parsed = parseOAuthResponse(response);
31 | const expected = [
32 | { account: 'vr123', token: 'a1-456' },
33 | { account: 'cc123', token: 'a1-bob' },
34 | { account: 'ml123', token: 'a1-hello' },
35 | ];
36 | expect(expected).toEqual(parsed);
37 | });
38 |
39 | it('should parse actual url response', () => {
40 | const response =
41 | 'https://test.example.com/redirect?' +
42 | 'acct1=CR300810&token1=a1-isZfteMh8GOxnpPUIi1rlUqepWXKW&' +
43 | 'acct2=VRTC547953&token2=a1-LVaOlP2v56wDwE7Fv8VftJNDdIt2G';
44 | const parsed = parseOAuthResponse(response);
45 | const expected = [
46 | { account: 'CR300810', token: 'a1-isZfteMh8GOxnpPUIi1rlUqepWXKW' },
47 | {
48 | account: 'VRTC547953',
49 | token : 'a1-LVaOlP2v56wDwE7Fv8VftJNDdIt2G',
50 | },
51 | ];
52 | expect(expected).toEqual(parsed);
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/src/__tests__/admin-test.js:
--------------------------------------------------------------------------------
1 | import websocket from 'ws';
2 | import LiveApi from '../LiveApi';
3 |
4 | describe('admin', () => {
5 | let liveApi;
6 |
7 | beforeAll(() => {
8 | liveApi = new LiveApi({ websocket, appId: 1089 });
9 | });
10 |
11 | it('should be able to call getApiToken without an error', async () => {
12 | expect(() => liveApi.getApiTokens()).not.toThrow();
13 | });
14 |
15 | it('should be able to call deleteApiToken function without an error', () => {
16 | expect(() => liveApi.deleteApiToken('token')).not.toThrow();
17 | });
18 |
19 | it('should be able to call createApiToken without an issue', () => {
20 | expect(() => liveApi.createApiToken('TokenName')).not.toThrow();
21 | });
22 |
23 | it('should be able to call changePassword', () => {
24 | expect(() => liveApi.changePassword('oldpassword', 'newpassword')).not.toThrow();
25 | });
26 |
27 | it('should be able to call the function registerApplication with no error', () => {
28 | expect(() => liveApi.registerApplication({ name: 'AppName', link: 'Applink' })).not.toThrow();
29 | });
30 |
31 | it('should be able to call getAllAppList function without throwing error ', () => {
32 | expect(() => liveApi.getAllAppList()).not.toThrow();
33 | });
34 |
35 | it('should be able to call getAppslistById function without throwing error', () => {
36 | expect(() => liveApi.getAppslistById(0)).not.toThrow();
37 | });
38 |
39 | it('should be able to call the deleteApplication function without throwing error', () => {
40 | expect(() => liveApi.deleteApplication(0)).not.toThrow();
41 | });
42 |
43 | it('it should be able to call the function createRealAccountMaltaInvest without error', () => {
44 | expect(() =>
45 | liveApi.createRealAccountMaltaInvest({
46 | name : 'name',
47 | username: 'username',
48 | })
49 | ).not.toThrow();
50 | });
51 |
52 | it.skip('should be able to call createRealAccount function with no error', async () =>
53 | expect(
54 | await liveApi.createRealAccount({
55 | name : 'name',
56 | username: 'username',
57 | })
58 | ).not.toThrow()
59 | );
60 |
61 | it('should be able to call the function setAccountCurrency with no error', () => {
62 | expect(() => liveApi.setAccountCurrency('EUR')).not.toThrow();
63 | });
64 |
65 | it('should be able to call the function setSelfExclusion without error', () => {
66 | expect(() => liveApi.setSelfExclusion({ balance: 300, limit: 30 })).not.toThrow();
67 | });
68 |
69 | it('should be able to call setAccountSettings without throwing error', () => {
70 | expect(() =>
71 | liveApi.setAccountSettings({
72 | option1: 'option1',
73 | option2: 'option2',
74 | })
75 | ).not.toThrow();
76 | });
77 |
78 | it('should be able to call setTnCApproval function without thrwoing error', () => {
79 | expect(() => liveApi.setTnCApproval()).not.toThrow();
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # binary-live-api
2 |
3 | This repository is **deprecated**, please use https://github.com/binary-com/deriv-api/ instead.
4 |
5 | [](https://travis-ci.org/binary-com/binary-live-api)
6 |
7 | [](https://coveralls.io/github/binary-com/binary-live-api?branch=master)
8 |
9 | This library is a high-level abstraction over the [Binary.com Websockets API](https://developers.binary.com)
10 |
11 | ##
12 |
13 | ## Features
14 |
15 | 1. Promise based, all network calls return a promise that is resolved when response is received, request response mapping is handled out of the box
16 | 2. Automatic reconnect when disconnection, including resubscribe to subscription made before disconnection
17 |
18 | ## Usage in the Browser
19 |
20 | ```
21 | var api = new LiveApi();
22 | api.authorize('yourtoken');
23 | api.getPortfolio();
24 | api.events.on('portfolio', function(data) {
25 | // do stuff with portfolio data
26 | });
27 | ```
28 |
29 | ## Usage From Node
30 |
31 | Install a WebSockets library like 'ws'
32 |
33 | ```
34 | npm init
35 | npm install ws --save
36 | npm install binary-live-api --save
37 | ```
38 |
39 | Alternatively, you can add the library to your project with the following link: [https://liveapi.binary.com/binary-live-api.js](https://liveapi.binary.com/binary-live-api.js) - or to fix to a specific version, put the version number in the URL as follows: [https://liveapi.binary.com/27.0.0/binary-live-api.js](https://liveapi.binary.com/27.0.0/binary-live-api.js)
40 |
41 | Require the library and then pass it to LiveApi's constructor.
42 |
43 | ```
44 | var ws = require('ws');
45 | var LiveApi = require('binary-live-api').LiveApi;
46 |
47 | var api = new LiveApi({ websocket: ws });
48 | api.authorize('yourtoken');
49 | api.getPortfolio();
50 | api.events.on('portfolio', function(data) {
51 | // do stuff with portfolio data
52 | });
53 | ```
54 |
55 | For all available calls, please check [here](docs/networkcalls.md)
56 |
57 | ## Experimental feature (Not for production)
58 | support [RxJs](https://github.com/Reactive-Extensions/RxJS)
59 |
60 | User can opt to use observables API instead of Promise API by passing `useRx = true` in constructor, like below
61 |
62 | ```
63 | var api = new LiveApi({ useRx: true });
64 | api.ping() // return Observable, instead of Promise
65 | ```
66 |
67 | No more global events ~!! as Stream is now modelled as observables, you can pass it around, instead of listening to global event.
68 | This will allow better composition of streams, right now it only include rx.lite, thus not all observables operator are supported,
69 | all supported operators can be check [here](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/libraries/lite/rx.lite.md)
70 |
71 | Example
72 |
73 | ```
74 | var api = new LiveApi({ useRx: true });
75 | var r100TickStream = api.subscribeToTicks('R_100');
76 |
77 | // silly example, but to illustrate you can now operate on them independently
78 | var epochs = r100TickStream.map(function(json){return json.tick.epoch});
79 | var quotes = r100TickStream.map(function(json){return json.tick.quote});
80 |
81 | ```
82 |
83 | ## To deploy as library on gh pages
84 | run `gulp deploy` to deploy library to origin/gh-pages
85 |
86 | run `gulp deploy-prod` to deploy library to upstream/gh-pages
87 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "binary-live-api",
3 | "version": "29.0.2",
4 | "description": "Library to consume Binary.com WebSocket API",
5 | "main": "lib/binary-live-api.js",
6 | "devDependencies": {
7 | "babel-cli": "^6.26.0",
8 | "babel-core": "^6.24.0",
9 | "babel-eslint": "10.0.3",
10 | "babel-loader": "^6.4.1",
11 | "babel-plugin-transform-class-properties": "^6.23.0",
12 | "babel-plugin-transform-export-extensions": "^6.22.0",
13 | "babel-plugin-transform-flow-strip-types": "^6.22.0",
14 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
15 | "babel-polyfill": "^6.23.0",
16 | "babel-preset-env": "^1.4.0",
17 | "coveralls": "^3.0.5",
18 | "eslint": "~4.18.2",
19 | "eslint-config-airbnb": "^14.1.0",
20 | "eslint-config-binary": "^1.0.2",
21 | "eslint-config-prettier": "^1.7.0",
22 | "eslint-loader": "^1.6.3",
23 | "eslint-plugin-import": "^2.2.0",
24 | "eslint-plugin-jsx-a11y": "^4.0.0",
25 | "eslint-plugin-react": "^6.10.3",
26 | "husky": "^0.13.3",
27 | "jest-cli": "^19.0.2",
28 | "lint-staged": "^3.4.0",
29 | "prettier-eslint-cli": "^3.3.0",
30 | "rimraf": "^2.6.1",
31 | "webpack": "^2.3.0",
32 | "ws": "^7.0.0"
33 | },
34 | "scripts": {
35 | "clean": "rimraf lib dist",
36 | "test": "jest --forceExit",
37 | "test:coverage": "jest --forceExit --coverage",
38 | "test:coveralls": "npm run test:coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage",
39 | "start": "webpack --watch",
40 | "test:eslint": "eslint src/*.js src/calls/*.js",
41 | "test:flow": "flow check --all --show-all-errors",
42 | "test:full": "npm run test:eslint && npm run test:coveralls",
43 | "build": "webpack",
44 | "lint": "eslint src",
45 | "prepublish": "webpack",
46 | "precommit": "lint-staged"
47 | },
48 | "repository": {
49 | "type": "git",
50 | "url": "git+https://github.com/binary-com/binary-live-api.git"
51 | },
52 | "author": "Boris @ Binary.com",
53 | "license": "MIT",
54 | "bugs": {
55 | "url": "https://github.com/binary-com/binary-live-api/issues"
56 | },
57 | "homepage": "https://github.com/binary-com/binary-live-api#readme",
58 | "babel": {
59 | "babelrc": false,
60 | "presets": [
61 | [
62 | "env",
63 | {
64 | "targets": {
65 | "browsers": [
66 | "last 2 versions",
67 | "ios_saf >= 8",
68 | "not IE <= 10",
69 | "chrome >= 49",
70 | "firefox >= 49",
71 | "> 1%"
72 | ]
73 | },
74 | "loose": true
75 | }
76 | ]
77 | ],
78 | "plugins": [
79 | "transform-export-extensions",
80 | "transform-object-rest-spread",
81 | "transform-class-properties",
82 | "transform-flow-strip-types"
83 | ]
84 | },
85 | "lint-staged": {
86 | "*.js": [
87 | "prettier-eslint --write",
88 | "git add"
89 | ]
90 | },
91 | "dependencies": {
92 | "binary-utils": "^4.21.0",
93 | "gulp": "^4.0.2",
94 | "gulp-gh-pages": "^0.5.4",
95 | "rx-lite": "^4.0.8"
96 | },
97 | "resolutions": {
98 | "braces": "3.0.2",
99 | "acorn": "5.7.4",
100 | "lodash.template": "4.5.0",
101 | "minimist": "0.2.1"
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/ApiState.js:
--------------------------------------------------------------------------------
1 | const getInitialState = () => ({
2 | token : undefined,
3 | balance : false,
4 | contracts : new Set(),
5 | allContract : false,
6 | transactions : false,
7 | ticks : new Set(),
8 | ticksHistory : new Map(),
9 | candlesHistory : new Map(),
10 | proposals : new Set(),
11 | streamIdMapping: new Map(),
12 | });
13 |
14 | export default class ApiState {
15 | constructor() {
16 | this.state = getInitialState();
17 | }
18 |
19 | resetState = () => {
20 | this.state = getInitialState();
21 | };
22 |
23 | getState = () => this.state;
24 |
25 | authorize = (token: string) => {
26 | this.state.token = token;
27 | };
28 |
29 | subscribeToBalance = () => {
30 | this.state.balance = true;
31 | };
32 |
33 | unsubscribeFromBalance = () => {
34 | this.state.balance = false;
35 | };
36 |
37 | subscribeToOpenContract = (contractId: string, streamId: string) => {
38 | if (streamId) {
39 | this.state.contracts.add(contractId);
40 | this.state.streamIdMapping.set(streamId, contractId);
41 | }
42 | };
43 |
44 | unsubscribeFromAllOpenContracts = () => {
45 | this.state.contracts.clear();
46 | this.state.allContract = false;
47 | };
48 |
49 | subscribeToAllOpenContracts = () => {
50 | this.state.allContract = true;
51 | };
52 |
53 | subscribeToTransactions = () => {
54 | this.state.transactions = true;
55 | };
56 |
57 | unsubscribeFromTransactions = () => {
58 | this.state.transactions = false;
59 | };
60 |
61 | subscribeToTick = (symbol: string) => {
62 | this.state.ticks.add(symbol);
63 | };
64 |
65 | subscribeToTicks = (symbols: string[]) => {
66 | symbols.forEach(this.subscribeToTick);
67 | };
68 |
69 | unsubscribeFromAllTicks = () => {
70 | this.state.ticks.clear();
71 | this.state.ticksHistory.clear();
72 | };
73 |
74 | unsubscribeFromAllCandles = () => {
75 | this.state.candlesHistory.clear();
76 | };
77 |
78 | getTickHistory = (symbol: string, params: Object) => {
79 | if (params && params.subscribe === 1) {
80 | if (params.style === 'candles') {
81 | this.state.candlesHistory.set(symbol, params);
82 | } else {
83 | this.state.ticksHistory.set(symbol, params);
84 | }
85 | }
86 | };
87 |
88 | subscribeToPriceForContractProposal = (options: Object, streamId: string) => {
89 | if (streamId) {
90 | this.state.proposals.add(options);
91 | this.state.streamIdMapping.set(streamId, options);
92 | }
93 | };
94 |
95 | unsubscribeFromAllProposals = () => {
96 | this.state.proposals.clear();
97 | };
98 |
99 | // special care needed to forget subscription, as backends rely on
100 | // and id instead of more natural keys like symbol and payload
101 | unsubscribeByID = id => {
102 | this.state.streamIdMapping.forEach((payload, streamId) => {
103 | if (streamId === id) {
104 | this.state.contracts.delete(payload);
105 | this.state.proposals.delete(payload);
106 | }
107 | });
108 | this.state.streamIdMapping.delete(id);
109 | };
110 | }
111 |
--------------------------------------------------------------------------------
/docs/networkcalls.md:
--------------------------------------------------------------------------------
1 | ## Network calls api
2 |
3 | This library provide JS friendly wrapper for all network calls
4 |
5 | Details are documented at https://developers.binary.com/api/
6 |
7 | ### ADMIN
8 |
9 | * `deleteApiToken(token: string)`
10 |
11 | * `getApiTokens()`
12 |
13 | * `createApiToken(token: string, scopes: string[])`
14 |
15 | * `changePassword(oldPassword: string, newPassword: string)`
16 |
17 | * `registerApplication(options: Object)`
18 |
19 | * `getAllAppList()`
20 |
21 | * `getAppslistById(appid: number)`
22 |
23 | * `deleteApplication(appid: number)`
24 |
25 | * `createRealAccountMaltaInvest(options: Object)`
26 |
27 | * `createRealAccount(options: Object)`
28 |
29 | * `setAccountCurrency(currency: string)`
30 |
31 | * `setSelfExclusion(options: Object)`
32 |
33 | * `setAccountSettings(options: Object)`
34 |
35 | * `setTnCApproval()`
36 |
37 | ----
38 |
39 | ### PAYMENT
40 |
41 | * `getCashierLockStatus()`
42 |
43 | * `setCashierLock(options: Object)`
44 |
45 | * `withdrawToPaymentAgent(options: Object)`
46 |
47 | * `paymentAgentTransfer(options: Object)`
48 |
49 | * `transferBetweenAccounts(options: Object)`
50 |
51 |
52 | -----
53 |
54 | ### READ
55 |
56 | * `getAccountLimits()`
57 |
58 | * `getAccountSettings()`
59 |
60 | * `getAccountStatus()`
61 |
62 | * `getSelfExclusion()`
63 |
64 | * `logOut()`
65 |
66 | * `getStatement(options: Object)`
67 |
68 | * `getPortfolio()`
69 |
70 | * `getProfitTable(options: Object)`
71 |
72 | * `getRealityCheckSummary()`
73 |
74 | * `unsubscribeFromBalance()`
75 |
76 | * `subscribeToOpenContract(contractId: number)`
77 |
78 | * `getContractInfo(contractId: number)`
79 |
80 | * `subscribeToAllOpenContracts()`
81 |
82 | * `unsubscribeFromAllOpenContracts()`
83 |
84 | * `subscribeToTransactions()`
85 |
86 | * `unsubscribeFromTransactions()`
87 |
88 |
89 | ----
90 |
91 | ### TRADE
92 |
93 | * `buyContract(contractId: number, price: number)`
94 |
95 | * `sellContract(contractId: number, price: number)`
96 |
97 | * `sellExpiredContracts()`
98 |
99 | * `topUpVirtualAccount()`
100 |
101 |
102 | -----
103 |
104 | ### UNAUTHENTICATED
105 |
106 | * `getActiveSymbolsBrief()`
107 |
108 | * `getActiveSymbolsFull()`
109 |
110 | * `getAssetIndex()`
111 |
112 | * `authorize(token: string)`
113 |
114 | * `getContractsForSymbol(symbol: string)`
115 |
116 | * `unsubscribeFromTick(symbol: string)`
117 |
118 | * `unsubscribeFromTicks(symbols: string[])`
119 |
120 | * `unsubscribeByID(id: number)`
121 |
122 | * `unsubscribeFromAllTicks()`
123 |
124 | * `unsubscribeFromAllCandles()`
125 |
126 | * `unsubscribeFromAllProposals()`
127 |
128 | * `unsubscribeFromAllPortfolios()`
129 |
130 | * `unsubscribeFromAllProposalsOpenContract()`
131 |
132 | * `getLandingCompany(landingCompany: string)`
133 |
134 | * `getLandingCompanyDetails(landingCompany: string)`
135 |
136 | * `createVirtualAccount(options: Object)`
137 |
138 | * `ping()`
139 |
140 | * `getPaymentAgentsForCountry(countryCode: string)`
141 |
142 | * `getPayoutCurrencies()`
143 |
144 | * `getPriceProposalForContract(options: Object)`
145 |
146 | * `subscribeToPriceForContractProposal(options: Object)`
147 |
148 | * `getResidences()`
149 |
150 | * `getStatesForCountry(countryCode: string)`
151 |
152 | * `subscribeToTick(symbol: string)`
153 |
154 | * `subscribeToTicks(symbols: string[])`
155 |
156 | * `getTickHistory(symbol: string, options: Object)`
157 |
158 | * `getCandles(symbol: string, options: Object)`
159 |
160 | * `getCandlesForLastNDays(symbol: string, ndays: number)`
161 |
162 | * `getServerTime()`
163 |
164 | * `getTradingTimes(date: Date)`
165 |
166 | * `verifyEmail(email: string, type: string)`
167 |
168 | * `getWebsiteStatus()`
169 |
170 |
--------------------------------------------------------------------------------
/src/calls/unauthenticated.js:
--------------------------------------------------------------------------------
1 | export const getActiveSymbolsBrief = () => ({
2 | active_symbols: 'brief',
3 | });
4 |
5 | export const getActiveSymbolsFull = () => ({
6 | active_symbols: 'full',
7 | });
8 |
9 | export const getAssetIndex = () => ({
10 | asset_index: 1,
11 | });
12 |
13 | export const authorize = (token: string) => ({
14 | authorize: token,
15 | });
16 |
17 | export const getContractsForSymbol = (symbol: string) => ({
18 | contracts_for: symbol,
19 | });
20 |
21 | export const unsubscribeFromTick = (symbol: string) => ({
22 | forget: symbol,
23 | });
24 |
25 | export const unsubscribeFromTicks = (symbols: string[]) => ({
26 | forget: symbols,
27 | });
28 |
29 | export const unsubscribeByID = (id: number) => ({
30 | forget: id,
31 | });
32 |
33 | export const unsubscribeFromAllTicks = () => ({
34 | forget_all: 'ticks',
35 | });
36 |
37 | export const unsubscribeFromAllCandles = () => ({
38 | forget_all: 'candles',
39 | });
40 |
41 | export const unsubscribeFromAllProposals = () => ({
42 | forget_all: 'proposal',
43 | });
44 |
45 | export const unsubscribeFromAllPortfolios = () => ({
46 | forget_all: 'portfolio',
47 | });
48 |
49 | export const getLandingCompany = (landingCompany: string) => ({
50 | landing_company: landingCompany,
51 | });
52 |
53 | export const getLandingCompanyDetails = (landingCompany: string) => ({
54 | landing_company_details: landingCompany,
55 | });
56 |
57 | export const createVirtualAccount = (options: Object) => ({
58 | new_account_virtual: 1,
59 | ...options,
60 | });
61 |
62 | export const ping = () => ({
63 | ping: 1,
64 | });
65 |
66 | export const getPaymentAgentsForCountry = (countryCode: string) => ({
67 | paymentagent_list: countryCode,
68 | });
69 |
70 | export const getPayoutCurrencies = () => ({
71 | payout_currencies: 1,
72 | });
73 |
74 | export const getPriceProposalForContract = (options: Object) => ({
75 | proposal: 1,
76 | ...options,
77 | });
78 |
79 | export const subscribeToPriceForContractProposal = (options: Object) => ({
80 | proposal : 1,
81 | subscribe: 1,
82 | ...options,
83 | });
84 |
85 | export const getResidences = () => ({
86 | residence_list: 1,
87 | });
88 |
89 | export const getStatesForCountry = (countryCode: string) => ({
90 | states_list: countryCode,
91 | });
92 |
93 | export const subscribeToTick = (symbol: string) => ({
94 | ticks: symbol,
95 | });
96 |
97 | export const subscribeToTicks = (symbols: string[]) => ({
98 | ticks: symbols,
99 | });
100 |
101 | export const getTickHistory = (symbol: string, options: Object) => ({
102 | ticks_history: symbol,
103 | ...(options || { end: 'latest' }),
104 | });
105 |
106 | export const getCandles = (symbol: string, options: Object) => ({
107 | ticks_history: symbol,
108 | style : 'candles',
109 | ...(options || { end: 'latest' }),
110 | });
111 |
112 | export const getCandlesForLastNDays = (symbol: string, ndays: number) => ({
113 | ticks_history: symbol,
114 | style : 'candles',
115 | start : Math.floor(Date.now() / 1000) - (ndays - 1) * 60 * 60 * 24,
116 | end : 'latest',
117 | granularity : 60 * 60 * 24,
118 | count : 30,
119 | });
120 |
121 | export const getServerTime = () => ({
122 | time: 1,
123 | });
124 |
125 | export const getTradingTimes = (date: Date) => ({
126 | trading_times: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
127 | });
128 |
129 | export const verifyEmail = (email: string, type: string) => ({
130 | verify_email: email,
131 | type,
132 | });
133 |
134 | export const getWebsiteStatus = () => ({
135 | website_status: 1,
136 | });
137 |
--------------------------------------------------------------------------------
/src/__tests__/custom-test.js:
--------------------------------------------------------------------------------
1 | import websocket from 'ws';
2 | import LiveApi from '../LiveApi';
3 |
4 | describe('custom', () => {
5 | let liveApi;
6 | const token = 'qdJ86Avvrsh0Le4';
7 | beforeAll(() => {
8 | liveApi = new LiveApi({ websocket, appId: 1089 });
9 | });
10 |
11 | describe('getDataForContract', () => {
12 | it('should get more extra ticks for non-tick-contract', async () => {
13 | await liveApi.authorize(token);
14 | const nonTickContractID = '8686424368';
15 | const { ticks } = await liveApi.getDataForContract(() =>
16 | liveApi.getContractInfo(nonTickContractID).then(r => r.proposal_open_contract)
17 | );
18 | expect(ticks.length).toBe(165);
19 | });
20 |
21 | it('should get exact number of ticks for tick-contract', async () => {
22 | await liveApi.authorize(token);
23 | const tickContractID = '8818581808';
24 | const { ticks } = await liveApi.getDataForContract(() =>
25 | liveApi.getContractInfo(tickContractID).then(r => r.proposal_open_contract)
26 | );
27 | expect(ticks.length).toBe(11);
28 | });
29 |
30 | it('should return candles if user request candles', async () => {
31 | await liveApi.authorize(token);
32 | const nonTickContractID = '8686424368';
33 | const { candles } = await liveApi.getDataForContract(
34 | () => liveApi.getContractInfo(nonTickContractID).then(r => r.proposal_open_contract),
35 | undefined,
36 | 'candles'
37 | );
38 | expect(candles.length).toBe(6);
39 | expect(candles[0].open).toBeTruthy();
40 | expect(candles[0].close).toBeTruthy();
41 | expect(candles[0].epoch).toBeTruthy();
42 | expect(candles[0].high).toBeTruthy();
43 | expect(candles[0].low).toBeTruthy();
44 | });
45 |
46 | it('should return even if contract does not have end time', async () => {
47 | await liveApi.authorize(token);
48 | const nonTickContractID = '8686424368';
49 | const { candles } = await liveApi.getDataForContract(
50 | () =>
51 | liveApi.getContractInfo(nonTickContractID).then(r => {
52 | const cloned = Object.assign({}, r.proposal_open_contract);
53 | delete cloned.exit_tick_time;
54 | delete cloned.date_expiry;
55 | return cloned;
56 | }),
57 | undefined,
58 | 'candles'
59 | );
60 | expect(candles.length).toBeLessThan(700);
61 | expect(candles[0].open).toBeTruthy();
62 | expect(candles[0].close).toBeTruthy();
63 | expect(candles[0].epoch).toBeTruthy();
64 | expect(candles[0].high).toBeTruthy();
65 | expect(candles[0].low).toBeTruthy();
66 | });
67 |
68 | it('should return isSold == true if contract sold', async () => {
69 | await liveApi.authorize(token);
70 | const tickContractID = '8818581808';
71 | const { isSold } = await liveApi.getDataForContract(() =>
72 | liveApi.getContractInfo(tickContractID).then(r => r.proposal_open_contract)
73 | );
74 | expect(isSold).toBeTruthy();
75 | });
76 | });
77 |
78 | describe('getDataForSymbol', () => {
79 | it('should get data for specified market', async () => {
80 | await liveApi.authorize(token);
81 | const { ticks } = await liveApi.getDataForSymbol('R_100');
82 | expect(ticks.length).toBeLessThan(700);
83 | });
84 |
85 | it('should get data for specified market using given duration params', async () => {
86 | await liveApi.authorize(token);
87 | const { ticks } = await liveApi.getDataForSymbol('R_100');
88 | expect(ticks.length).toBeGreaterThan(29);
89 | });
90 |
91 | it('should get candles for specified market if requested candles', async () => {
92 | await liveApi.authorize(token);
93 | const { candles } = await liveApi.getDataForSymbol('R_100', 60 * 60, 'candles');
94 | expect(candles.length).toBeGreaterThan(59);
95 | });
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/src/__tests__/unauthenticated-test.js:
--------------------------------------------------------------------------------
1 | import websocket from 'ws';
2 | import LiveApi from '../LiveApi';
3 |
4 | describe('unauthenticated', () => {
5 | let liveApi;
6 |
7 | beforeAll(() => {
8 | liveApi = new LiveApi({ websocket, appId: 1089 });
9 | });
10 |
11 | it('can ping server', async () => expect((await liveApi.ping()).ping).toBeTruthy());
12 |
13 | it('can call authorize', () => expect(liveApi.authorize).toBeTruthy());
14 |
15 | it('can get available contracts for symbol', async () =>
16 | expect((await liveApi.getContractsForSymbol('R_100')).contracts_for).toBeTruthy());
17 |
18 | it('can get brief active symbols', async () =>
19 | expect((await liveApi.getActiveSymbolsBrief()).active_symbols).toBeTruthy());
20 |
21 | it('can get full active symbols', async () =>
22 | expect((await liveApi.getActiveSymbolsFull()).active_symbols).toBeTruthy());
23 |
24 | it('can get asset index', async () => expect((await liveApi.getAssetIndex()).asset_index).toBeTruthy());
25 |
26 | it('must provide a parameter to getTradingTimes', async () =>
27 | expect((await liveApi.getTradingTimes(new Date())).trading_times).toBeTruthy());
28 |
29 | it('can get trading times', async () =>
30 | expect((await liveApi.getTradingTimes(new Date())).trading_times).toBeTruthy());
31 |
32 | it('can get residences', async () => expect((await liveApi.getResidences()).residence_list).toBeTruthy());
33 |
34 | it('can get states for a country', async () =>
35 | expect((await liveApi.getStatesForCountry('de')).states_list).toBeTruthy());
36 |
37 | it('can subscribe to tick updates', async () => expect((await liveApi.subscribeToTick('R_100')).tick).toBeTruthy());
38 |
39 | it('can subscribe to multiple ticks updates', async () =>
40 | expect(() => liveApi.subscribeToTicks(['R_25', 'R_50', 'R_100'])).not.toThrow());
41 |
42 | it('can unsubscribe from all tick updates', async () =>
43 | expect(() => liveApi.unsubscribeFromAllTicks()).not.toThrow());
44 |
45 | it('can get tick history with no parameters', async () =>
46 | expect(() => liveApi.getTickHistory('R_100').history).not.toThrow());
47 |
48 | it('can get tick history with custom intervals', async () =>
49 | expect(
50 | async () =>
51 | (await liveApi.getTickHistory('R_100', {
52 | end : 'latest',
53 | count: 10,
54 | })).history
55 | ).not.toThrow());
56 |
57 | it('can get the landing company for a country', async () =>
58 | expect((await liveApi.getLandingCompany('de')).landing_company).toBeTruthy());
59 |
60 | it('can get details about a landing company', async () =>
61 | expect((await liveApi.getLandingCompanyDetails('costarica')).landing_company_details).toBeTruthy());
62 |
63 | it('can get payment agents for a country', async () =>
64 | expect((await liveApi.getPaymentAgentsForCountry('id')).paymentagent_list).toBeTruthy());
65 |
66 | it('can get payout currencies', async () =>
67 | expect((await liveApi.getPayoutCurrencies()).payout_currencies).toBeTruthy());
68 |
69 | it('can get price proposal for contract', async () =>
70 | expect(
71 | (await liveApi.getPriceProposalForContract({
72 | amount : 100,
73 | basis : 'payout',
74 | contract_type: 'CALL',
75 | currency : 'USD',
76 | duration : 60,
77 | duration_unit: 's',
78 | symbol : 'R_100',
79 | })).proposal
80 | ).toBeTruthy());
81 |
82 | it('can subscribe to price proposal updates for contract', async () =>
83 | expect(
84 | (await liveApi.subscribeToPriceForContractProposal({
85 | amount : 100,
86 | basis : 'payout',
87 | contract_type: 'CALL',
88 | currency : 'USD',
89 | duration : 60,
90 | duration_unit: 's',
91 | symbol : 'R_100',
92 | })).proposal
93 | ).toBeTruthy());
94 |
95 | it('can unsubscribe from all price proposal updates', async () =>
96 | expect(() => liveApi.unsubscribeFromAllProposals()).not.toThrow());
97 |
98 | it('can get candles for a symbol', async () => expect((await liveApi.getCandles('R_50')).candles).toBeTruthy());
99 |
100 | it('can get candles for last N days', async () =>
101 | expect((await liveApi.getCandlesForLastNDays('R_50', 40)).candles).toBeTruthy());
102 |
103 | it('can get server time', async () => expect((await liveApi.getServerTime()).time).toBeTruthy());
104 |
105 | it('can verify email', async () =>
106 | expect((await liveApi.verifyEmail('address@example.com', 'account_opening')).verify_email).toBeTruthy());
107 |
108 | it('can get website status', async () => expect((await liveApi.getWebsiteStatus()).website_status).toBeTruthy());
109 | });
110 |
--------------------------------------------------------------------------------
/src/__tests__/read-test.js:
--------------------------------------------------------------------------------
1 | import websocket from 'ws';
2 | import LiveApi from '../LiveApi';
3 |
4 | const token = 'if9yO8qFuejRCDk';
5 |
6 | describe('read', () => {
7 | let liveApi;
8 |
9 | beforeAll(() => {
10 | liveApi = new LiveApi({ websocket, appId: 1089 });
11 | });
12 |
13 | it('should be able to get account limit', async () => {
14 | await liveApi.authorize(token);
15 | const response = await liveApi.getAccountLimits();
16 | expect(response.get_limits).toBeTruthy();
17 | });
18 |
19 | it('should be able return account limits in a server response', async () => {
20 | await liveApi.authorize(token);
21 | const response = await liveApi.getAccountLimits();
22 | expect(response.get_limits.account_balance).toEqual('100000.00');
23 | });
24 |
25 | it('should be able to call the function getAccountSettings', () =>
26 | expect(() => liveApi.getAccountSettings()).not.toThrow());
27 |
28 | it('should be able to return account settings from a api response', async () => {
29 | await liveApi.authorize(token);
30 | const response = await liveApi.getAccountSettings();
31 | expect(response.get_settings.country).toEqual('Indonesia');
32 | });
33 |
34 | it('should be able to call getAccountStatus without an error', () => {
35 | expect(() => liveApi.getAccountStatus()).not.toThrow();
36 | });
37 |
38 | it('should be able to get account status in a response from a server', async () => {
39 | await liveApi.authorize(token);
40 | const response = await liveApi.getAccountStatus();
41 | expect(response.get_account_status.status).toBeTruthy();
42 | });
43 |
44 | it('should be able to call getSelfExclusion without an issue', () => {
45 | expect(() => liveApi.getSelfExclusion()).not.toThrow();
46 | });
47 |
48 | it('should be able to getSelfExclusion in a response from a server', async () => {
49 | await liveApi.authorize(token);
50 | const response = await liveApi.getSelfExclusion();
51 | expect(response.get_self_exclusion.max_balance).toEqual('100000');
52 | });
53 |
54 | it('should be able to call logout function', () => {
55 | expect(() => liveApi.logOut()).not.toThrow();
56 | });
57 |
58 | it('should be able to sign user out of his account', async () => {
59 | await liveApi.authorize(token);
60 | const response = await liveApi.logOut();
61 | expect(response.error).toBeFalsy();
62 | });
63 |
64 | it('it should be able to call getStatement function without an issue', () =>
65 | expect(() =>
66 | liveApi.getStatement({
67 | statement : 1,
68 | description: 1,
69 | limit : 100,
70 | })
71 | ).not.toThrow());
72 |
73 | it('it should be able to get portfolio', () => expect(() => liveApi.getPortfolio()).not.toThrow());
74 |
75 | it('should be able to get a statement if logged in', async () => {
76 | await liveApi.authorize(token);
77 | const response = await liveApi.getStatement();
78 | expect(response.statement).toBeTruthy();
79 | });
80 |
81 | it('should be able to call getProfitTable without an error', () => {
82 | expect(() => liveApi.getProfitTable()).not.toThrow();
83 | });
84 |
85 | it('can get profitTable from the server', async () => {
86 | await liveApi.authorize(token);
87 | const response = await liveApi.getProfitTable();
88 | expect(response.profit_table).toBeTruthy();
89 | });
90 |
91 | it('should be able to call getRealityCheckSummary without an error', () => {
92 | expect(() => liveApi.getRealityCheckSummary()).not.toThrow();
93 | });
94 |
95 | it('should be able to subscribe to balance updates', () => {
96 | expect(() => liveApi.subscribeToBalance()).not.toThrow();
97 | });
98 |
99 | it('should be able to unsubscribe from balance updates', () => {
100 | expect(() => liveApi.unsubscribeFromBalance()).not.toThrow();
101 | });
102 |
103 | it('should be able to subscribe to open contract updates', () => {
104 | expect(() => liveApi.subscribeToAllOpenContracts()).not.toThrow();
105 | });
106 |
107 | it('should be able to unsubscribe from open contract updates', () => {
108 | expect(() => liveApi.unsubscribeFromAllOpenContracts()).not.toThrow();
109 | });
110 |
111 | it('should be able to subscribe to transaction updates', () => {
112 | expect(() => liveApi.subscribeToTransactions()).not.toThrow();
113 | });
114 |
115 | it('should be able to unsubscribe from transaction updates', () => {
116 | expect(() => liveApi.unsubscribeFromTransactions()).not.toThrow();
117 | });
118 |
119 | it('should be able to call subscribeToOpenContract without an issue', () => {
120 | expect(() => liveApi.subscribeToOpenContract()).not.toThrow();
121 | });
122 |
123 | it('should subscribeToOpenContract and return a server response', async () => {
124 | await liveApi.authorize(token);
125 | const response = await liveApi.subscribeToOpenContract();
126 | expect(response.echo_req.subscribe).toBeTruthy();
127 | });
128 | });
129 |
--------------------------------------------------------------------------------
/src/__tests__/stateful-test.js:
--------------------------------------------------------------------------------
1 | import websocket from 'ws';
2 | import LiveApi from '../LiveApi';
3 |
4 | describe('stateful', () => {
5 | let liveApi;
6 |
7 | beforeAll(async () => {
8 | liveApi = new LiveApi({ websocket, appId: 1089 });
9 | await liveApi.ping();
10 | });
11 |
12 | beforeEach(() => {
13 | liveApi.apiState.resetState();
14 | });
15 |
16 | it('initial state is empty', () => {
17 | const state = liveApi.apiState.getState();
18 |
19 | expect(state.token).toBeFalsy();
20 | expect(state.balance).toBeFalsy();
21 | expect(state.allContract).toBeFalsy();
22 | expect(state.transactions).toBe(false);
23 | expect(state.ticks.size).toBeFalsy();
24 | expect(state.proposals.size).toBeFalsy();
25 | });
26 |
27 | it('after authorization token is retained', () => {
28 | liveApi.authorize('some token');
29 | const stateAfter = liveApi.apiState.getState();
30 | expect(stateAfter.token).toEqual('some token');
31 | });
32 |
33 | it('subscribing to balance updates is remembered', () => {
34 | liveApi.subscribeToBalance();
35 | const stateAfter = liveApi.apiState.getState();
36 | expect(stateAfter.balance).toBeTruthy();
37 | });
38 |
39 | it('subscribing to balance updates is remembered', () => {
40 | liveApi.subscribeToAllOpenContracts();
41 | const stateAfter = liveApi.apiState.getState();
42 | expect(stateAfter.allContract).toBeTruthy();
43 | });
44 |
45 | it('subscribing to transactions updates is remembered', () => {
46 | liveApi.subscribeToTransactions();
47 | const stateAfter = liveApi.apiState.getState();
48 | expect(stateAfter.transactions).toBeTruthy();
49 | });
50 |
51 | it('subscribing to a single tick updates is remembered', () => {
52 | liveApi.subscribeToTick('R_50');
53 | const stateAfter = liveApi.apiState.getState();
54 | expect(stateAfter.ticks.size).toEqual(1);
55 | });
56 |
57 | // unsubscribeFromTick is not really working, we should consider remove it
58 | it.skip('unsubsribing from a tick is remembered', () => {
59 | liveApi.subscribeToTick('R_50');
60 | liveApi.unsubscribeFromTick('R_50');
61 | const stateAfter = liveApi.apiState.getState();
62 | expect(stateAfter.ticks.size).toEqual(0);
63 | });
64 |
65 | it('subscribing to multiple tick updates is remembered', () => {
66 | liveApi.subscribeToTicks(['R_25', 'R_50', 'R_100']);
67 | const stateAfter = liveApi.apiState.getState();
68 | expect(stateAfter.ticks.has('R_25')).toBeTruthy();
69 | expect(stateAfter.ticks.has('R_50')).toBeTruthy();
70 | expect(stateAfter.ticks.has('R_100')).toBeTruthy();
71 | });
72 |
73 | // unsubscribeFromTicks is not really working, we should consider remove it
74 | it.skip('unsubscribing from multiple tick updates is remembered', () => {
75 | liveApi.subscribeToTicks(['R_25', 'R_50', 'R_100']);
76 | liveApi.unsubscribeFromTicks(['R_50', 'R_100']);
77 | const stateAfter = liveApi.apiState.getState();
78 | expect(stateAfter.ticks.has('R_25')).toBeTruthy();
79 | expect(stateAfter.ticks.has('R_50')).toBeFalsy();
80 | expect(stateAfter.ticks.has('R_100')).toBeFalsy();
81 | });
82 |
83 | it('subscribe ticks thru tickhistory should be remembered', () => {
84 | liveApi.getTickHistory('R_100', { subscribe: 1 });
85 | const stateAfter = liveApi.apiState.getState();
86 |
87 | expect(stateAfter.ticksHistory.has('R_100')).toBeTruthy();
88 | expect(stateAfter.candlesHistory.has('R_100')).toBeFalsy();
89 | });
90 |
91 | it('subscribe candles thru tickhistory should be remembered', () => {
92 | liveApi.getTickHistory('R_100', { subscribe: 1, style: 'candles' });
93 | const stateAfter = liveApi.apiState.getState();
94 |
95 | expect(stateAfter.ticksHistory.has('R_100')).toBeFalsy();
96 | expect(stateAfter.candlesHistory.has('R_100')).toBeTruthy();
97 | });
98 |
99 | // skipped as it only works for live contract, and there aint so many forever live contract for testing
100 | it.skip('subscribe to single contract is remembered only if contract id is valid', async () => {
101 | await liveApi.authorize('qdJ86Avvrsh0Le4');
102 | await liveApi.subscribeToOpenContract('9939813188');
103 | const stateAfter = liveApi.apiState.getState();
104 |
105 | expect(stateAfter.contracts.size).toEqual(1);
106 | });
107 |
108 | it('unsubscribeById should remove corresponding id', async () => {
109 | await liveApi
110 | .subscribeToPriceForContractProposal({
111 | amount : 100,
112 | basis : 'payout',
113 | contract_type: 'CALL',
114 | currency : 'USD',
115 | duration : 60,
116 | duration_unit: 's',
117 | symbol : 'R_100',
118 | })
119 | .then(r => {
120 | const id = r.proposal.id;
121 |
122 | const stateBefore = liveApi.apiState.getState();
123 | expect(stateBefore.proposals.size).toEqual(1);
124 | liveApi.unsubscribeByID(id);
125 |
126 | const stateAfter = liveApi.apiState.getState();
127 | expect(stateAfter.proposals.size).toEqual(0);
128 | });
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/src/custom.js:
--------------------------------------------------------------------------------
1 | import { nowAsEpoch, computeStartEndForContract } from 'binary-utils';
2 |
3 | const responseSizeLimit = 700;
4 |
5 | const granularities: number[] = [60, 120, 180, 300, 600, 900, 1800, 3600, 7200, 14400, 28800, 86400];
6 |
7 | const ohlcDataToTicks = (candles: Candle[]): Tick[] => candles.map(data => ({ quote: +data.open, epoch: +data.epoch }));
8 |
9 | export const autoAdjustGetData = (
10 | api: LiveApi,
11 | symbol: string,
12 | start: Epoch,
13 | end: Epoch,
14 | style: string = 'ticks',
15 | subscribe: boolean,
16 | extra = {}
17 | ) => {
18 | const secs = end - start;
19 | const ticksCount = secs / 2;
20 | if (ticksCount >= responseSizeLimit || style === 'candles') {
21 | const idealGranularity = secs / responseSizeLimit;
22 | let finalGranularity = 60;
23 | granularities.forEach((g, i) => {
24 | if (idealGranularity > g && idealGranularity <= granularities[i + 1]) {
25 | finalGranularity = granularities[i + 1];
26 | }
27 | });
28 | finalGranularity = Math.min(86400, finalGranularity);
29 |
30 | return api
31 | .getTickHistory(symbol, {
32 | start,
33 | end,
34 | adjust_start_time: 1,
35 | count : responseSizeLimit,
36 | style : 'candles',
37 | granularity : finalGranularity,
38 | subscribe : subscribe ? 1 : undefined,
39 | })
40 | .then(r => {
41 | if (style === 'ticks') {
42 | return {
43 | ...extra,
44 | ticks: ohlcDataToTicks(r.candles),
45 | symbol,
46 | };
47 | }
48 | return {
49 | ...extra,
50 | candles: r.candles,
51 | symbol,
52 | };
53 | });
54 | }
55 | return api
56 | .getTickHistory(symbol, {
57 | start,
58 | end,
59 | adjust_start_time: 1,
60 | count : responseSizeLimit,
61 | style : 'ticks',
62 | subscribe : subscribe ? 1 : undefined,
63 | })
64 | .then(r => {
65 | const ticks = r.history.times.map((t, idx) => {
66 | const quote = r.history.prices[idx];
67 | return { epoch: +t, quote: +quote };
68 | });
69 | return {
70 | ...extra,
71 | ticks,
72 | symbol,
73 | };
74 | });
75 | };
76 |
77 | export function getDataForSymbol(
78 | api: LiveApi,
79 | symbol: string,
80 | duration: Epoch = 600,
81 | style: string = 'ticks',
82 | subscribe: boolean
83 | ) {
84 | const end = nowAsEpoch();
85 | const start = end - duration;
86 | return autoAdjustGetData(api, symbol, start, end, style, subscribe);
87 | }
88 |
89 | /**
90 | * get data of contract
91 | * @param api - will be injected by library
92 | * @param getContract - function that accept nothing and return a Promise containing contract
93 | * @param durationCount - number of duration
94 | * @param durationType - type of duration, check http://api.highcharts.com/highstock#rangeSelector.buttons
95 | * @param style - one of ['ticks', 'candles'], this will affect the return data shape,
96 | * internally library might not always use this param when requesting, eg. when data is too large,
97 | * library will use `candles` instead of `ticks`, this is handle by library so user do not need to worry
98 | * @param granularity - default to 60, check https://developers.binary.com/api/#ticks_history
99 | * @returns {*|Promise.}
100 | */
101 | export function getDataForContract(
102 | api: LiveApi,
103 | getContract,
104 | duration?: Epoch,
105 | style: string = 'ticks',
106 | subscribe: boolean
107 | ) {
108 | const getAllData = () =>
109 | getContract().then(contract => {
110 | const symbol = contract.underlying;
111 | const { start, end } = computeStartEndForContract(contract);
112 | return autoAdjustGetData(api, symbol, start, end, style, subscribe, { isSold: !!contract.sell_time });
113 | });
114 |
115 | if (!duration) {
116 | return getAllData();
117 | }
118 |
119 | return getContract().then(contract => {
120 | const symbol = contract.underlying;
121 | const startTime = +contract.date_start;
122 |
123 | // handle Contract not started yet
124 | if (startTime > nowAsEpoch()) {
125 | return autoAdjustGetData(api, symbol, nowAsEpoch() - 600, nowAsEpoch(), style, subscribe, {
126 | isSold: !!contract.sell_time,
127 | });
128 | }
129 |
130 | const sellT = contract.sell_time;
131 | const end = sellT || nowAsEpoch();
132 |
133 | const buffer = (end - startTime) * 0.05;
134 |
135 | const start = Math.min(startTime - buffer, end - duration);
136 | return autoAdjustGetData(api, symbol, Math.round(start), Math.round(end), style, subscribe, {
137 | isSold: !!contract.sell_time,
138 | });
139 | });
140 | }
141 |
142 | export const helpers = {
143 | autoAdjustGetData,
144 | };
145 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | k8s: circleci/kubernetes@0.7.0
4 | slack: circleci/slack@3.4.2
5 | commands:
6 | npm_install:
7 | description: "Install npm modules"
8 | steps:
9 | - restore_cache:
10 | name: Restore npm cache
11 | keys:
12 | - npm-v1-{{ checksum "package.json" }}
13 | - npm-v1-
14 | - run:
15 | name: Install npm modules
16 | command: yarn
17 | - save_cache:
18 | name: Save NPM cache
19 | key: npm-v1-{{ checksum "package.json" }}
20 | paths:
21 | - "node_modules"
22 | build:
23 | description: "Build"
24 | steps:
25 | - run:
26 | name: "yarn build"
27 | command: yarn build
28 | versioning:
29 | description: "Add version to build"
30 | parameters:
31 | target_branch:
32 | type: string
33 | steps:
34 | - run:
35 | name: Tag build
36 | command: echo "<< parameters.target_branch >> $(date -u +'%Y-%m-%dT%H:%M:%SZ')" > lib/version
37 | docker_build_push:
38 | description: "Build and Push image to docker hub"
39 | steps:
40 | - setup_remote_docker
41 | - run:
42 | name: Building docker image
43 | command: |
44 | docker build -t ${DOCKHUB_ORGANISATION}/binary-static-liveapi:${CIRCLE_SHA1} -t ${DOCKHUB_ORGANISATION}/binary-static-liveapi:latest .
45 | - run:
46 | name: Pushing Image to docker hub
47 | command: |
48 | echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin
49 | docker push ${DOCKHUB_ORGANISATION}/binary-static-liveapi:${CIRCLE_SHA1}
50 | docker push ${DOCKHUB_ORGANISATION}/binary-static-liveapi:latest
51 | k8s_deploy:
52 | description: "Deploy to k8s cluster"
53 | parameters:
54 | k8s_namespace:
55 | type: string
56 | default: "liveapi-binary-com-production"
57 | k8s_service:
58 | type: string
59 | default: "binary-static-liveapi"
60 | steps:
61 | - k8s/install-kubectl
62 | - run:
63 | name: Deploying to k8s cluster for service binary-liveapi
64 | command: |
65 | export NAMESPACE=<< parameters.k8s_namespace >>
66 | git clone https://github.com/binary-com/devops-ci-scripts
67 | cd devops-ci-scripts/k8s-build_tools
68 | echo $CA_CRT | base64 --decode > ca.crt
69 | ./release.sh << parameters.k8s_service >> ${CIRCLE_SHA1}
70 | notify_slack:
71 | description: "Notify slack"
72 | steps:
73 | - slack/status:
74 | include_project_field: false
75 | failure_message: "Release failed for liveapi.binary.com with version *$(cat lib/version)*"
76 | success_message: "Release succeeded for liveapi.binary.com with version *$(cat lib/version)*"
77 | webhook: ${SLACK_WEBHOOK}
78 | publish_to_pages_staging:
79 | description: "Publish to cloudflare pages"
80 | steps:
81 | - run:
82 | name: "Publish to cloudflare pages (staging)"
83 | command: |
84 | cd lib
85 | npx wrangler pages publish . --project-name=binary-live-api-pages --branch=staging
86 | echo "New staging website - http://staging.cf-pages-binary-live-api.binary.com"
87 |
88 | publish_to_pages_production:
89 | description: "Publish to cloudflare pages"
90 | steps:
91 | - run:
92 | name: "Publish to cloudflare pages (production)"
93 | command: |
94 | cd lib
95 | npx wrangler pages publish . --project-name=binary-live-api-pages --branch=main
96 | echo "New website - http://cf-pages-binary-live-api.binary.com"
97 | jobs:
98 | build:
99 | docker:
100 | - image: circleci/node:8.10.0-stretch
101 | steps:
102 | - checkout
103 | - npm_install
104 | - build
105 | release_staging:
106 | docker:
107 | - image: circleci/node:8.10.0-stretch
108 | steps:
109 | - checkout
110 | - npm_install
111 | - build
112 | - versioning:
113 | target_branch: "staging"
114 | - persist_to_workspace:
115 | root: lib
116 | paths:
117 | - .
118 | release_production:
119 | docker:
120 | - image: circleci/node:8.10.0-stretch
121 | steps:
122 | - checkout
123 | - npm_install
124 | - build
125 | - versioning:
126 | target_branch: "production"
127 | - persist_to_workspace:
128 | root: lib
129 | paths:
130 | - .
131 | - docker_build_push
132 | - k8s_deploy
133 | - notify_slack
134 | publish_cloudflare_staging:
135 | docker:
136 | - image: cimg/node:18.4.0
137 | steps:
138 | - attach_workspace:
139 | at: lib
140 | - publish_to_pages_staging
141 | publish_cloudflare_production:
142 | docker:
143 | - image: cimg/node:18.4.0
144 | steps:
145 | - attach_workspace:
146 | at: lib
147 | - publish_to_pages_production
148 |
149 | workflows:
150 | build:
151 | jobs:
152 | - build:
153 | filters:
154 | branches:
155 | ignore: /^master$/
156 | release_staging:
157 | jobs:
158 | - release_staging:
159 | filters:
160 | branches:
161 | only: /^master$/
162 | context: binary-frontend-artifact-upload
163 | - publish_cloudflare_staging:
164 | requires:
165 | - release_staging
166 | filters:
167 | branches:
168 | only: /^master$/
169 | context: binary-frontend-artifact-upload
170 | release_production:
171 | jobs:
172 | - release_production:
173 | filters:
174 | branches:
175 | ignore: /.*/
176 | tags:
177 | only: /^production.*/
178 | context: binary-frontend-artifact-upload
179 | - publish_cloudflare_production:
180 | requires:
181 | - release_production
182 | filters:
183 | branches:
184 | ignore: /.*/
185 | tags:
186 | only: /^production.*/
187 | context: binary-frontend-artifact-upload
188 |
--------------------------------------------------------------------------------
/flow-typed/binary-live-api.js.flow:
--------------------------------------------------------------------------------
1 | type LivePromise = Promise