├── .gitignore ├── README.md ├── tutorial ├── Access │ ├── EX-0-Access-Start │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ └── storage.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-1-Simple-Request │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── isMobile.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-2-Storing-A-Token │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── isMobile.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-3-Time-Penalty │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-4-Test-Request │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── handleAccountList.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-4a-Place-An-Order │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── placeOrder.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ └── README.md ├── WebSockets │ ├── EX-05-WebSockets-Start │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ ├── isMobile.js │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-06-Heartbeats │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-07-Making-Requests │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-07a-Making-Requests-Solution │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── renderETH.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-08-Realtime-Market-Data │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-08a-Realtime-Market-Data-Solution │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── renderQuote.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-09-Realtime-Market-Data-Pt2 │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── renderQuote.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-09a-Realtime-Market-Data-Pt2-Solution │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── renderDOM.js │ │ │ ├── renderPriceSize.js │ │ │ ├── renderQuote.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-10-Chart-Data │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-10a-Chart-Data-Solution │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ ├── counter.js │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-11-Tick-Charts │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ ├── counter.js │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-11a-Tick-Charts-Solution │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── renderDOM.js │ │ │ ├── renderPriceSize.js │ │ │ ├── renderQuote.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ ├── counter.js │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-12-Calculating-Open-PL │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── renderPosition.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ ├── isMobile.js │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ ├── EX-12a-Calculating-Open-PL-Solution │ │ ├── babel.config.json │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── TradovateSocket.js │ │ │ ├── app.js │ │ │ ├── connect.js │ │ │ ├── renderPosition.js │ │ │ ├── services.js │ │ │ ├── storage.js │ │ │ └── utils │ │ │ │ ├── isMobile.js │ │ │ │ └── waitForMs.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ └── README.md ├── tutorialsCredentials.js └── tutorialsURLs.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # other 107 | project-notes.txt 108 | 109 | # compiled output 110 | dist 111 | 112 | # this is my working folder, I'd rather not expose anything other than the tutorials section 113 | private -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # example-api-js 2 | 3 | This is a collection of example projects to get you started with the Tradovate API using JavaScript. 4 | 5 | ## Usage 6 | 7 | You can clone this repository and follow along with each project. To run one of the tutorial projects, 8 | first open a terminal and navigate to the specific tutorial folder you'd like to work from. Once you've navigated to your chosen tutorial 9 | project's root, install the dependencies: 10 | 11 | ``` 12 | > yarn install 13 | ``` 14 | 15 | Then follow along with the README guide located in the root of each project. When you're ready to run 16 | a project run the following command: 17 | 18 | ``` 19 | > yarn start 20 | ``` 21 | 22 | Then open a browser and navigate to `localhost:8080`, and hack away. 23 | 24 | > To make the more complicated examples more accessible, we've included 'Solution' folders with some of the projects. They can be opened and run just the same as any of the 25 | > other follow-along projects so that you can compare your work to the end results. 26 | -------------------------------------------------------------------------------- /tutorial/Access/EX-0-Access-Start/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-0-Access-Start/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /tutorial/Access/EX-0-Access-Start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "node-polyfill-webpack-plugin": "^1.1.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tutorial/Access/EX-0-Access-Start/src/app.js: -------------------------------------------------------------------------------- 1 | import { connect } from './connect' 2 | import { tvGet, tvPost } from './services' 3 | 4 | //uncomment the next line to test it 5 | //connect() 6 | -------------------------------------------------------------------------------- /tutorial/Access/EX-0-Access-Start/src/connect.js: -------------------------------------------------------------------------------- 1 | import { URLs } from '../../../tutorialsURLs' 2 | 3 | const { DEMO_URL } = URLs 4 | 5 | export const connect = () => { /*...*/ } -------------------------------------------------------------------------------- /tutorial/Access/EX-0-Access-Start/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | 6 | export const setAvailableAccounts = accounts => { 7 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 8 | } 9 | 10 | /** 11 | * Returns and array of available accounts or undefined. 12 | * @returns Account[] 13 | */ 14 | export const getAvailableAccounts = () => { 15 | return JSON.parse(sessionStorage.getItem(JSON.parse(AVAIL_ACCTS_KEY))) 16 | } 17 | 18 | /** 19 | * Use a predicate function to find an account. May be undefined. 20 | */ 21 | export const queryAvailableAccounts = predicate => { 22 | return getAvailableAccounts().find(predicate) 23 | } 24 | 25 | export const setDeviceId = (id) => { 26 | sessionStorage.setItem(DEVICE_ID_KEY, id) 27 | } 28 | 29 | export const getDeviceId = () => { 30 | return sessionStorage.getItem(DEVICE_ID_KEY) 31 | } 32 | 33 | export const setAccessToken = (token, expiration) => { 34 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 35 | sessionStorage.setItem(STORAGE_KEY, token) 36 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 37 | } 38 | 39 | export const getAccessToken = () => { 40 | const token = sessionStorage.getItem(STORAGE_KEY) 41 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 42 | if(!token) { 43 | console.warn('No access token retrieved. Please request an access token.') 44 | } 45 | return { token, expiration } 46 | } 47 | 48 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 -------------------------------------------------------------------------------- /tutorial/Access/EX-0-Access-Start/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/Access/EX-1-Simple-Request/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-1-Simple-Request/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /tutorial/Access/EX-1-Simple-Request/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/Access/EX-1-Simple-Request/src/app.js: -------------------------------------------------------------------------------- 1 | import { credentials } from '../../../tutorialsCredentials' 2 | import { connect } from './connect' 3 | 4 | connect(credentials) -------------------------------------------------------------------------------- /tutorial/Access/EX-1-Simple-Request/src/connect.js: -------------------------------------------------------------------------------- 1 | import { tvPost } from "./services" 2 | 3 | 4 | export const connect = async (data) => { 5 | 6 | const json = await tvPost('/auth/accesstokenrequest', data, false) 7 | 8 | console.log(json) 9 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-1-Simple-Request/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | 6 | export const setAvailableAccounts = accounts => { 7 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 8 | } 9 | 10 | /** 11 | * Returns and array of available accounts or undefined. 12 | * @returns Account[] 13 | */ 14 | export const getAvailableAccounts = () => { 15 | return JSON.parse(sessionStorage.getItem(JSON.parse(AVAIL_ACCTS_KEY))) 16 | } 17 | 18 | /** 19 | * Use a predicate function to find an account. May be undefined. 20 | */ 21 | export const queryAvailableAccounts = predicate => { 22 | return getAvailableAccounts().find(predicate) 23 | } 24 | 25 | export const setDeviceId = (id) => { 26 | sessionStorage.setItem(DEVICE_ID_KEY, id) 27 | } 28 | 29 | export const getDeviceId = () => { 30 | return sessionStorage.getItem(DEVICE_ID_KEY) 31 | } 32 | 33 | export const setAccessToken = (token, expiration) => { 34 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 35 | sessionStorage.setItem(STORAGE_KEY, token) 36 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 37 | } 38 | 39 | export const getAccessToken = () => { 40 | const token = sessionStorage.getItem(STORAGE_KEY) 41 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 42 | if(!token) { 43 | console.warn('No access token retrieved. Please request an access token.') 44 | } 45 | return { token, expiration } 46 | } 47 | 48 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 -------------------------------------------------------------------------------- /tutorial/Access/EX-1-Simple-Request/src/utils/isMobile.js: -------------------------------------------------------------------------------- 1 | export const isMobile = () => { 2 | let check = false; 3 | (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); 4 | return check 5 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-1-Simple-Request/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/README.md: -------------------------------------------------------------------------------- 1 | ## Storing a Token 2 | Now that we now how to get an access token, we really should store it locally. Fortunately, included with this project is a file called `storage.js`. This additional file contains functions that will let you set and retrieve items using `sessionStorage`. Using `sessionStorage` ensures that our temporary tokens will be cleared at the end of the session, and it also means that our apps will work even in browsers' Incognito mode. 3 | 4 | > **Quick Tip!** From this section forward, you can use the developers' console to play with Tradovate REST API requests directly. Simply type `tradovate.get` or `tradovate.post` in the browser. These are functions that match `tvGet` or `tvPost` respectively, from `services.js`. 5 | 6 | Let's see how our storage functions work. In `connect.js` we will make some changes: 7 | 8 | ```javascript 9 | //connect.js 10 | 11 | import { tvPost } from './services' 12 | import { getAccessToken, setAccessToken, tokenIsValid } from './storage' 13 | 14 | export const connect = async (data) => { 15 | const { token, expiration } = getAccessToken() 16 | if(token && tokenIsValid(expiration)) { 17 | console.log('Already have an access token. Using existing token.') 18 | return 19 | } 20 | const { accessToken, expirationTime } = await tvPost('/auth/accesstokenrequest', data, false) 21 | 22 | setAccessToken(accessToken, expirationTime) 23 | } 24 | ``` 25 | Our `connect` function now checks for a valid token. If it can't find it, then it will fire the regular access token request and store the response token and expiry. 26 | 27 | There is an exception though - some readers may have gotten a different response. If you got a response with properties like `p-ticket` and `p-time` instead of the standard auth response, then you've gotten a Time Penalty response. This happens when you try to log in too many times with incorrect credentials. Unfortunately, the login penalty includes a `p-captcha` field. This essentially means that the request cannot be completed by a third party application (so any app using your API key, like this one). You'll have to wait an hour to try again, so make sure to use the correct credentials when you ask for an access token. For most endpoints, however, the time penalty response is pretty reasonable and easy to deal with. Let's look at how we can code to be prepared for time penalty responses in the next section. 28 | 29 | ### [< Prev Section](https://github.com/tradovate/example-api-js/tree/main/tutorial/Access/EX-1-Simple-Request) [Next Section >](https://github.com/tradovate/example-api-js/tree/main/tutorial/Access/EX-3-Time-Penalty) 30 | -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/src/app.js: -------------------------------------------------------------------------------- 1 | import { connect } from './connect' 2 | import { isMobile } from './utils/isMobile' 3 | import "device-uuid" 4 | import { getDeviceId, setDeviceId } from './storage' 5 | 6 | import { credentials } from '../../../tutorialsCredentials' 7 | import { setAccessToken } from './storage' 8 | 9 | setAccessToken(null) 10 | 11 | let DEVICE_ID 12 | if(!isMobile()) { 13 | const device = getDeviceId() 14 | DEVICE_ID = device || new DeviceUUID().get() 15 | setDeviceId(DEVICE_ID) 16 | } else { 17 | DEVICE_ID = new DeviceUUID().get() 18 | } 19 | 20 | if(!isMobile()) { 21 | setDeviceId(DEVICE_ID) 22 | } 23 | 24 | connect(credentials) -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/src/connect.js: -------------------------------------------------------------------------------- 1 | import { tvPost } from './services' 2 | import { getAccessToken, setAccessToken, tokenIsValid } from './storage' 3 | 4 | export const connect = async (data) => { 5 | const { token, expiration } = getAccessToken() 6 | if(token && tokenIsValid(expiration)) { 7 | console.log('Already have an access token. Using existing token.') 8 | return 9 | } 10 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 11 | 12 | const { accessToken, expirationTime } = authResponse 13 | 14 | console.log(authResponse) 15 | 16 | setAccessToken(accessToken, expirationTime) 17 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/src/utils/isMobile.js: -------------------------------------------------------------------------------- 1 | export const isMobile = () => { 2 | let check = false; 3 | (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); 4 | return check 5 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-2-Storing-A-Token/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/Access/EX-3-Time-Penalty/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-3-Time-Penalty/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /tutorial/Access/EX-3-Time-Penalty/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/Access/EX-3-Time-Penalty/src/app.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken } from './storage' 2 | import { connect } from './connect' 3 | import { credentials } from '../../../tutorialsCredentials' 4 | 5 | setAccessToken(null) 6 | 7 | //Connect to the tradovate API by retrieving an access token 8 | connect(credentials) 9 | -------------------------------------------------------------------------------- /tutorial/Access/EX-3-Time-Penalty/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | setAvailableAccounts(accounts) 29 | return 30 | } 31 | 32 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 33 | 34 | if(authResponse['p-ticket']) { 35 | return await handleRetry(data, authResponse) 36 | } else { 37 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 38 | 39 | if(errorText) { 40 | console.error(errorText) 41 | return 42 | } 43 | 44 | setAccessToken(accessToken, expirationTime) 45 | 46 | const accounts = await tvGet('/account/list') 47 | 48 | console.log(accounts) 49 | 50 | setAvailableAccounts(accounts) 51 | 52 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 53 | } 54 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-3-Time-Penalty/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/Access/EX-3-Time-Penalty/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-3-Time-Penalty/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/src/app.js: -------------------------------------------------------------------------------- 1 | 2 | import { handleAccountList } from './handleAccountList' 3 | import { connect } from './connect' 4 | import { tvGet } from './services' 5 | import { credentials } from '../../../tutorialsCredentials' 6 | 7 | const main = async () => { 8 | await connect(credentials) 9 | 10 | // APPLICATION ------------------ 11 | 12 | const $accountListBtn = document.querySelector('#get-acct-btn') 13 | 14 | $accountListBtn.addEventListener('click', async () => { 15 | let accounts = await tvGet('/account/list') 16 | handleAccountList(accounts) 17 | }) 18 | 19 | } 20 | 21 | main() -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | setAvailableAccounts(accounts) 29 | return 30 | } 31 | 32 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 33 | 34 | if(authResponse['p-ticket']) { 35 | return await handleRetry(data, authResponse) 36 | } else { 37 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 38 | 39 | if(errorText) { 40 | console.error(errorText) 41 | return 42 | } 43 | 44 | setAccessToken(accessToken, expirationTime) 45 | 46 | const accounts = await tvGet('/account/list') 47 | 48 | console.log(accounts) 49 | 50 | setAvailableAccounts(accounts) 51 | 52 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 53 | } 54 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/src/handleAccountList.js: -------------------------------------------------------------------------------- 1 | 2 | const $outlet = document.querySelector('#outlet') 3 | 4 | export const handleAccountList = (data) => { 5 | 6 | data.forEach(item => { 7 | const { 8 | accountType, 9 | active, 10 | archived, 11 | autoLiqProfileId, 12 | clearingHouseId, 13 | id, 14 | legalStatus, 15 | marginAccountType, 16 | name, 17 | riskCategoryId, 18 | userId, 19 | } = item 20 | 21 | const templateHtml = ` 22 |

Name: ${name}

23 |

Account Type: ${accountType}

24 |
25 |
Active: ${active ? 'Yes' : 'Inactive'}
26 |
ID: ${id}
27 |
UserID: ${userId}
28 |
legalStatus: ${legalStatus}
29 |
marginAccountType: ${marginAccountType}
30 |
riskCategory: ${riskCategoryId}
31 |
autoLiqProfileId: ${autoLiqProfileId}
32 |
clearingHouseId: ${clearingHouseId}
33 |
archived: ${archived}
34 |
35 | ` 36 | 37 | const container = document.createElement('div') 38 | container.innerHTML = templateHtml 39 | 40 | if($outlet.firstElementChild) { 41 | $outlet.firstElementChild.replaceWith(container) 42 | } else { 43 | $outlet.appendChild(container) 44 | } 45 | }) 46 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-4-Test-Request/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/src/app.js: -------------------------------------------------------------------------------- 1 | import { credentials } from '../../../tutorialsCredentials' 2 | import { connect } from './connect' 3 | import { ORDER_ACTION, ORDER_TYPE, placeOrder } from './placeOrder' 4 | 5 | 6 | const main = async () => { 7 | //Connect to the tradovate API by retrieving an access token 8 | await connect(credentials) 9 | 10 | const $symbol = document.getElementById('symbol') 11 | const $input = document.getElementById('buy') 12 | 13 | $input.addEventListener('click', async () => { 14 | if(!$symbol.value) return 15 | const response = await placeOrder({ 16 | action: ORDER_ACTION.Buy, 17 | symbol: $symbol.value, 18 | orderQty: 1, 19 | orderType: ORDER_TYPE.Market, 20 | }) 21 | console.log(response) 22 | }) 23 | } 24 | 25 | //app entry point 26 | main() 27 | -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | setAvailableAccounts(accounts) 29 | return 30 | } 31 | 32 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 33 | 34 | if(authResponse['p-ticket']) { 35 | return await handleRetry(data, authResponse) 36 | } else { 37 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 38 | 39 | if(errorText) { 40 | console.error(errorText) 41 | return 42 | } 43 | 44 | setAccessToken(accessToken, expirationTime) 45 | 46 | const accounts = await tvGet('/account/list') 47 | 48 | console.log(accounts) 49 | 50 | setAvailableAccounts(accounts) 51 | 52 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 53 | } 54 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/src/placeOrder.js: -------------------------------------------------------------------------------- 1 | import { tvPost } from '../../EX-3-Time-Penalty/src/services' 2 | import { getAvailableAccounts, getAccessToken } from './storage' 3 | 4 | export const ORDER_TYPE = { 5 | Limit: 'Limit', 6 | MIT: 'MIT', 7 | Market: 'Market', 8 | QTS: 'QTS', 9 | Stop: 'Stop', 10 | StopLimit: 'StopLimit', 11 | TrailingStop: 'TralingStop', 12 | TrailingStopLimit: 'TrailingStopLimit' 13 | } 14 | 15 | export const ORDER_ACTION = { 16 | Buy: 'Buy', 17 | Sell: 'Sell' 18 | } 19 | 20 | export const placeOrder = async ({ 21 | action, 22 | symbol, 23 | orderQty, 24 | orderType, 25 | accountSpec, 26 | accountId, 27 | clOrdId, 28 | price, 29 | stopPrice, 30 | maxShow, 31 | pegDifference, 32 | timeInForce, 33 | expireTime, 34 | text, 35 | activationTime, 36 | customTag50, 37 | isAutomated 38 | }) => { 39 | 40 | const { id, name } = getAvailableAccounts()[0] 41 | const { token } = getAccessToken() 42 | 43 | const normalized_body = { 44 | action, symbol, orderQty, orderType, 45 | isAutomated: isAutomated || false, 46 | accountId: id, 47 | accountSpec: name 48 | } 49 | 50 | if(!token) { 51 | console.error('No access token found. Please acquire a token and try again.') 52 | return 53 | } 54 | 55 | const res = await tvPost('/order/placeOrder', normalized_body) 56 | 57 | return res 58 | } 59 | -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/Access/EX-4a-Place-An-Order/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/Access/README.md: -------------------------------------------------------------------------------- 1 | # Accessing the Tradovate API 2 | 3 | This section is focused on acquiring an access token for making further requests to the Tradovate API. The 4 | easiest way to get started is to go to the [EX-0](https://github.com/tradovate/example-api-js/tree/main/tutorial/Access/EX-0-Access-Start) 5 | tutorial and clone the project. -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/src/app.js: -------------------------------------------------------------------------------- 1 | import { credentials } from '../../../tutorialsCredentials' 2 | import { connect } from './connect' 3 | import { setAccessToken } from './storage' 4 | 5 | setAccessToken(null) 6 | 7 | const main = async () => { 8 | //Connect to the tradovate API by retrieving an access token 9 | connect(credentials) 10 | } 11 | 12 | //app entry point 13 | main() 14 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/src/utils/isMobile.js: -------------------------------------------------------------------------------- 1 | export const isMobile = () => { 2 | let check = false; 3 | (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); 4 | return check 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-05-WebSockets-Start/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-06-Heartbeats/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-06-Heartbeats/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-06-Heartbeats/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-06-Heartbeats/src/app.js: -------------------------------------------------------------------------------- 1 | import { connect } from './connect' 2 | import { getAccessToken } from './storage' 3 | import { credentials } from '../../../tutorialsCredentials' 4 | import { URLs } from '../../../tutorialsURLs' 5 | import { setAccessToken } from './storage' 6 | 7 | setAccessToken(null) 8 | 9 | const main = async () => { 10 | 11 | //Connect to the tradovate API by retrieving an access token 12 | const { accessToken } = await connect(credentials) 13 | 14 | const ws = new WebSocket(URLs.WS_DEMO_URL) 15 | let curTime = new Date() 16 | 17 | ws.onmessage = msg => { 18 | 19 | const now = new Date() 20 | 21 | if(now.getTime() - curTime.getTime() >= 2500) { 22 | ws.send('[]') 23 | console.log('sent response heartbeat') 24 | curTime = new Date() 25 | } 26 | 27 | const { type, data } = msg 28 | const kind = data.slice(0,1) // what kind of message is this? the first character lets us know 29 | 30 | if(type !== 'message') { 31 | console.log('non-message type received') 32 | console.log(msg) 33 | return 34 | } 35 | 36 | //message discriminator 37 | switch(kind) { 38 | case 'o': 39 | console.log('Opening Socket Connection...') 40 | const { token } = getAccessToken() 41 | ws.send(`authorize\n0\n\n${token}`) 42 | break 43 | case 'h': 44 | console.log('received server heartbeat...') 45 | break 46 | case 'a': 47 | const data = JSON.parse(msg.data.slice(1)) 48 | console.log(data) 49 | break 50 | case 'c': 51 | console.log('closing websocket') 52 | break 53 | default: 54 | console.error('Unexpected response token received:') 55 | console.error(msg) 56 | break; 57 | } 58 | } 59 | } 60 | 61 | //app entry point 62 | main() 63 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-06-Heartbeats/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-06-Heartbeats/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-06-Heartbeats/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-06-Heartbeats/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07-Making-Requests/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07-Making-Requests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07-Making-Requests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07-Making-Requests/src/app.js: -------------------------------------------------------------------------------- 1 | import { credentials } from '../../../tutorialsCredentials' 2 | import { URLs } from '../../../tutorialsURLs' 3 | import { connect } from './connect' 4 | import { setAccessToken } from './storage' 5 | import { TradovateSocket } from './TradovateSocket' 6 | 7 | setAccessToken(null) 8 | 9 | //Connect to the tradovate API by retrieving an access token 10 | const main = async () => { 11 | const { accessToken } = await connect(credentials) 12 | 13 | 14 | const ws = new TradovateSocket() 15 | await ws.connect(URLs.WS_DEMO_URL, accessToken) 16 | } 17 | 18 | main() -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07-Making-Requests/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07-Making-Requests/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07-Making-Requests/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07-Making-Requests/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07a-Making-Requests-Solution/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07a-Making-Requests-Solution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07a-Making-Requests-Solution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07a-Making-Requests-Solution/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07a-Making-Requests-Solution/src/renderETH.js: -------------------------------------------------------------------------------- 1 | export const renderETH = ({ 2 | allowProviderContractInfo, 3 | contractGroupId, 4 | currencyId, 5 | description, 6 | exchangeChannelId, 7 | exchangeId, 8 | id, 9 | isMicro, 10 | marketDataSource, 11 | name, 12 | priceFormat, 13 | priceFormatType, 14 | productType, 15 | status, 16 | tickSize, 17 | valuePerPoint, 18 | }) => { 19 | return ` 20 |
21 |

${name}

22 |

currency ID: ${currencyId == 1 ? '$' : currencyId}

23 |

info:

24 | 25 |
allowProviderContractInfo: ${allowProviderContractInfo}
26 |
contractGroupId: ${contractGroupId}
27 |
exchangeChannelId: ${exchangeChannelId}
28 |
exchangeId: ${exchangeId}
29 |
id: ${id}
30 |
isMicro: ${isMicro}
31 |
marketDataSource: ${marketDataSource}
32 |
priceFormat: ${priceFormat}
33 |
priceFormatType: ${priceFormatType}
34 |
productType: ${productType}
35 |
status: ${status}
36 |
tickSize: ${tickSize}
37 |
valuePerPoint: ${valuePerPoint}
38 |
39 |

Description

40 |

${description}

41 |
42 | ` 43 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07a-Making-Requests-Solution/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07a-Making-Requests-Solution/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-07a-Making-Requests-Solution/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08-Realtime-Market-Data/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08-Realtime-Market-Data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 |
51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08-Realtime-Market-Data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08-Realtime-Market-Data/src/app.js: -------------------------------------------------------------------------------- 1 | import { credentials } from '../../../tutorialsCredentials' 2 | import { connect } from './connect' 3 | import { setAccessToken } from './storage' 4 | import { TradovateSocket } from './TradovateSocket' 5 | 6 | setAccessToken(null) 7 | 8 | const main = async () => { 9 | //Connect to the tradovate API by retrieving an access token 10 | //await allows us to not call any further code until this is done, 11 | //ensuring that our dependent code will execute properly. This 12 | //is how we are strategizing our initialization. 13 | const { accessToken } = await connect(credentials) 14 | 15 | 16 | // APPLICATION ---------------------------------------------------- 17 | 18 | //HTML elements 19 | const $outlet = document.getElementById('outlet') 20 | const $reqBtn = document.getElementById('request-btn') 21 | const $unsubBtn = document.getElementById('unsubscribe-btn') 22 | const $connBtn = document.getElementById('connect-btn') 23 | const $discBtn = document.getElementById('disconnect-btn') 24 | const $statusInd = document.getElementById('status') 25 | const $symbol = document.getElementById('symbol') 26 | 27 | //The websocket helper tool 28 | const socket = new TradovateSocket() 29 | } 30 | 31 | main() -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08-Realtime-Market-Data/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08-Realtime-Market-Data/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08-Realtime-Market-Data/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08-Realtime-Market-Data/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 |
51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/src/app.js: -------------------------------------------------------------------------------- 1 | import { credentials } from '../../../tutorialsCredentials' 2 | import { URLs } from '../../../tutorialsURLs' 3 | import { TradovateSocket } from '../../EX-07-Making-Requests/src/TradovateSocket' 4 | import { connect } from './connect' 5 | import { renderQuote } from './renderQuote' 6 | import { setAccessToken } from './storage' 7 | 8 | setAccessToken(null) 9 | 10 | const main = async () => { 11 | 12 | const { accessToken } = await connect(credentials) 13 | 14 | //HTML elements 15 | const $outlet = document.getElementById('outlet') 16 | const $reqBtn = document.getElementById('request-btn') 17 | const $unsubBtn = document.getElementById('unsubscribe-btn') 18 | const $connBtn = document.getElementById('connect-btn') 19 | const $discBtn = document.getElementById('disconnect-btn') 20 | const $statusInd = document.getElementById('status') 21 | const $symbol = document.getElementById('symbol') 22 | 23 | //The websocket helper tool 24 | const socket = new TradovateSocket() 25 | let lastSymb 26 | let unsubscribe 27 | 28 | //give user some feedback about the state of their connection 29 | //by adding an event listener to 'message' that will change the color 30 | const onStateChange = _ => { 31 | $statusInd.style.backgroundColor = 32 | socket.ws.readyState == 0 ? 'gold' //pending 33 | : socket.ws.readyState == 1 ? 'green' //OK 34 | : socket.ws.readyState == 2 ? 'orange' //closing 35 | : socket.ws.readyState == 3 ? 'red' //closed 36 | : /*else*/ 'silver' //unknown/default 37 | } 38 | 39 | $connBtn.addEventListener('click', async () => { 40 | if(socket.ws && socket.ws.readyState === 1) return 41 | 42 | await socket.connect(URLs.MD_URL, accessToken) 43 | //add your feedback function to the socket's 44 | socket.ws.addEventListener('message', onStateChange) 45 | }) 46 | 47 | //disconnect socket on disconnect button click 48 | $discBtn.addEventListener('click', () => { 49 | if(socket.ws.readyState !== 1) return 50 | 51 | socket.ws.close() 52 | $statusInd.style.backgroundColor = 'red' 53 | $outlet.innerText = '' 54 | 55 | }) 56 | 57 | $unsubBtn.addEventListener('click', () => { 58 | unsubscribe() 59 | lastSymb = '' 60 | }) 61 | 62 | //clicking the request button will fire our request and initialize 63 | //a listener to await the response. 64 | $reqBtn.addEventListener('click', async () => { 65 | 66 | lastSymb = $symbol.value 67 | unsubscribe = await socket.subscribe({ 68 | url: 'md/subscribequote', 69 | body: { symbol: $symbol.value }, 70 | subscription: data => { 71 | const newElement = document.createElement('div') 72 | newElement.innerHTML = renderQuote($symbol.value, data.entries) 73 | $outlet.firstElementChild 74 | ? $outlet.firstElementChild.replaceWith(newElement) 75 | : $outlet.append(newElement) 76 | } 77 | }) 78 | }) 79 | } 80 | 81 | main() -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/src/renderQuote.js: -------------------------------------------------------------------------------- 1 | 2 | const renderPriceSize = ({price, size}) => ` 3 | ${price ? '
  • price: ' +price+ '
  • ' : ''} 4 | ${size ? '
  • size: ' +size+ '
  • ' : ''} 5 | ` 6 | 7 | export const renderQuote = (symbol, { 8 | Bid, 9 | HighPrice, 10 | LowPrice, 11 | Offer, 12 | OpenInterest, 13 | OpeningPrice, 14 | SettlementPrice, 15 | TotalTradeVolume, 16 | Trade, 17 | }) => ` 18 |
    19 |

    ${symbol}

    20 | 21 |
    22 |

    Bid

    23 |
      24 | ${renderPriceSize(Bid)} 25 |
    26 |
    27 |
    28 |

    HighPrice

    29 |
      30 | ${renderPriceSize(HighPrice)} 31 |
    32 |
    33 |
    34 |

    LowPrice

    35 |
      36 | ${renderPriceSize(LowPrice)} 37 |
    38 |
    39 |
    40 |

    Offer

    41 |
      42 | ${renderPriceSize(Offer)} 43 |
    44 |
    45 |
    46 |

    OpenInterest

    47 |
      48 | ${renderPriceSize(OpenInterest)} 49 |
    50 |
    51 |
    52 |

    OpeningPrice

    53 |
      54 | ${renderPriceSize(OpeningPrice)} 55 |
    56 |
    57 |
    58 |

    SettlementPrice

    59 |
      60 | ${renderPriceSize(SettlementPrice)} 61 |
    62 |
    63 |
    64 |

    TotalTradeVolume

    65 |
      66 | ${renderPriceSize(TotalTradeVolume)} 67 |
    68 |
    69 |
    70 |

    Trade

    71 |
      72 | ${renderPriceSize(Trade)} 73 |
    74 |
    75 |
    76 |
    77 | ` -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-08a-Realtime-Market-Data-Solution/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
    49 |
    50 |
    51 | 52 |
    53 | 54 | 55 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/src/app.js: -------------------------------------------------------------------------------- 1 | import { credentials } from '../../../tutorialsCredentials' 2 | import { URLs } from '../../../tutorialsURLs' 3 | import { TradovateSocket } from '../../EX-07-Making-Requests/src/TradovateSocket' 4 | import { connect } from './connect' 5 | import { renderQuote } from './renderQuote' 6 | import { setAccessToken } from './storage' 7 | 8 | setAccessToken(null) 9 | 10 | const main = async () => { 11 | 12 | await connect(credentials) 13 | 14 | //HTML elements 15 | const $outlet = document.getElementById('outlet') 16 | const $reqBtn = document.getElementById('request-btn') 17 | const $unsubBtn = document.getElementById('unsubscribe-btn') 18 | const $connBtn = document.getElementById('connect-btn') 19 | const $discBtn = document.getElementById('disconnect-btn') 20 | const $statusInd = document.getElementById('status') 21 | const $symbol = document.getElementById('symbol') 22 | 23 | //The websocket helper tool 24 | const socket = new TradovateSocket() 25 | let lastSymb 26 | let unsubscribe 27 | 28 | //give user some feedback about the state of their connection 29 | //by adding an event listener to 'message' that will change the color 30 | const onStateChange = _ => { 31 | $statusInd.style.backgroundColor = 32 | socket.ws.readyState == 0 ? 'gold' //pending 33 | : socket.ws.readyState == 1 ? 'green' //OK 34 | : socket.ws.readyState == 2 ? 'orange' //closing 35 | : socket.ws.readyState == 3 ? 'red' //closed 36 | : /*else*/ 'silver' //unknown/default 37 | } 38 | 39 | 40 | $connBtn.addEventListener('click', async () => { 41 | if(socket.ws.readyState === 1) return 42 | 43 | await socket.connect(URLs.MDS_URL) 44 | //add your feedback function to the socket's 45 | socket.ws.addEventListener('message', onStateChange) 46 | socket.ws.addEventListener('message', onStateChange) 47 | }) 48 | 49 | //disconnect socket on disconnect button click 50 | $discBtn.addEventListener('click', () => { 51 | if(socket.ws.readyState !== 1) return 52 | 53 | socket.disconnect() 54 | $statusInd.style.backgroundColor = 'red' 55 | $outlet.innerText = '' 56 | 57 | }) 58 | 59 | $unsubBtn.addEventListener('click', () => { 60 | if(unsubscribe) { 61 | unsubscribe() 62 | lastSymb = '' 63 | } 64 | }) 65 | 66 | //clicking the request button will fire our request and initialize 67 | //a listener to await the response. 68 | $reqBtn.addEventListener('click', async () => { 69 | 70 | lastSymb = $symbol.value 71 | socket.subscribe({ 72 | url: 'md/subscribequote', 73 | body: { symbol: $symbol.value }, 74 | subscription: data => { 75 | const newElement = document.createElement('div') 76 | newElement.innerHTML = renderQuote($symbol.value, data) 77 | $outlet.firstElementChild 78 | ? $outlet.firstElementChild.replaceWith(newElement) 79 | : $outlet.append(newElement) 80 | } 81 | }) 82 | }) 83 | } 84 | 85 | main() -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/src/renderQuote.js: -------------------------------------------------------------------------------- 1 | 2 | const renderPriceSize = ({price, size}) => ` 3 | ${price ? '
  • price: ' +price+ '
  • ' : ''} 4 | ${size ? '
  • size: ' +size+ '
  • ' : ''} 5 | ` 6 | 7 | export const renderQuote = (symbol, { 8 | Bid, 9 | HighPrice, 10 | LowPrice, 11 | Offer, 12 | OpenInterest, 13 | OpeningPrice, 14 | SettlementPrice, 15 | TotalTradeVolume, 16 | Trade, 17 | }) => ` 18 |
    19 |

    ${symbol}

    20 | 21 |
    22 |

    Bid

    23 |
      24 | ${renderPriceSize(Bid)} 25 |
    26 |
    27 |
    28 |

    HighPrice

    29 |
      30 | ${renderPriceSize(HighPrice)} 31 |
    32 |
    33 |
    34 |

    LowPrice

    35 |
      36 | ${renderPriceSize(LowPrice)} 37 |
    38 |
    39 |
    40 |

    Offer

    41 |
      42 | ${renderPriceSize(Offer)} 43 |
    44 |
    45 |
    46 |

    OpenInterest

    47 |
      48 | ${renderPriceSize(OpenInterest)} 49 |
    50 |
    51 |
    52 |

    OpeningPrice

    53 |
      54 | ${renderPriceSize(OpeningPrice)} 55 |
    56 |
    57 |
    58 |

    SettlementPrice

    59 |
      60 | ${renderPriceSize(SettlementPrice)} 61 |
    62 |
    63 |
    64 |

    TotalTradeVolume

    65 |
      66 | ${renderPriceSize(TotalTradeVolume)} 67 |
    68 |
    69 |
    70 |

    Trade

    71 |
      72 | ${renderPriceSize(Trade)} 73 |
    74 |
    75 |
    76 |
    77 | ` -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09-Realtime-Market-Data-Pt2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
    85 |
    86 |
    87 | 88 |
    89 | 90 | 91 | 92 | 93 | 94 |
    95 | 96 |
    97 | 98 | 99 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/src/renderDOM.js: -------------------------------------------------------------------------------- 1 | 2 | import { renderPriceSize } from './renderPriceSize' 3 | 4 | const renderBidOffer = bid => ` 5 |
    6 | 9 |
    10 | ` 11 | 12 | export const renderDOM = (symbol, { 13 | contractId, 14 | timestamp, 15 | bids, 16 | offers, 17 | }) => ` 18 |
    19 | 20 |

    ${symbol} - ${contractId}

    21 | 22 |
    23 |
    24 |
    25 |

    Bids

    26 | ${bids.map(renderBidOffer).join()} 27 |
    28 |
    29 |

    Offers

    30 | ${offers.map(renderBidOffer).join()} 31 |
    32 |
    33 |
    34 | ` -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/src/renderPriceSize.js: -------------------------------------------------------------------------------- 1 | export const renderPriceSize = ({price, size}) => ` 2 | ${price ? '
  • price: ' +price+ '
  • ' : ''} 3 | ${size ? '
  • size: ' +size+ '
  • ' : ''} 4 | ` -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/src/renderQuote.js: -------------------------------------------------------------------------------- 1 | 2 | import { renderPriceSize } from './renderPriceSize' 3 | 4 | export const renderQuote = (symbol, { 5 | Bid, 6 | HighPrice, 7 | LowPrice, 8 | Offer, 9 | OpenInterest, 10 | OpeningPrice, 11 | SettlementPrice, 12 | TotalTradeVolume, 13 | Trade, 14 | }) => ` 15 |
    16 |

    ${symbol}

    17 | 18 |
    19 |

    Bid

    20 |
      21 | ${renderPriceSize(Bid)} 22 |
    23 |
    24 |
    25 |

    HighPrice

    26 |
      27 | ${renderPriceSize(HighPrice)} 28 |
    29 |
    30 |
    31 |

    LowPrice

    32 |
      33 | ${renderPriceSize(LowPrice)} 34 |
    35 |
    36 |
    37 |

    Offer

    38 |
      39 | ${renderPriceSize(Offer)} 40 |
    41 |
    42 |
    43 |

    OpenInterest

    44 |
      45 | ${renderPriceSize(OpenInterest)} 46 |
    47 |
    48 |
    49 |

    OpeningPrice

    50 |
      51 | ${renderPriceSize(OpeningPrice)} 52 |
    53 |
    54 |
    55 |

    SettlementPrice

    56 |
      57 | ${renderPriceSize(SettlementPrice)} 58 |
    59 |
    60 |
    61 |

    TotalTradeVolume

    62 |
      63 | ${renderPriceSize(TotalTradeVolume)} 64 |
    65 |
    66 |
    67 |

    Trade

    68 |
      69 | ${renderPriceSize(Trade)} 70 |
    71 |
    72 |
    73 |
    74 | ` -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-09a-Realtime-Market-Data-Pt2-Solution/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10-Chart-Data/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10-Chart-Data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 12 | 13 | 14 | 15 | 16 |
    17 |
    18 |
    19 | 20 |
    21 | 22 | 23 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10-Chart-Data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10-Chart-Data/src/app.js: -------------------------------------------------------------------------------- 1 | import { connect } from './connect' 2 | import { credentials } from '../../../tutorialsCredentials' 3 | import { URLs } from '../../../tutorialsURLs' 4 | import { TradovateSocket } from '../../EX-07-Making-Requests/src/TradovateSocket' 5 | import { setAccessToken } from './storage' 6 | 7 | setAccessToken(null) 8 | 9 | const main = async () => { 10 | 11 | let unsubscribe //variable to hold our unsubscribe function 12 | 13 | const { accessToken } = await connect(credentials) 14 | 15 | //socket init 16 | const socket = new TradovateSocket() 17 | await socket.connect(URLs.MD_URL, accessToken) 18 | 19 | //HTML elements 20 | const $getChart = document.getElementById('get-chart-btn') 21 | const $statusInd = document.getElementById('status') 22 | 23 | const onStateChange = _ => { 24 | $statusInd.style.backgroundColor = 25 | socket.ws.readyState == 0 ? 'gold' //pending 26 | : socket.ws.readyState == 1 ? 'green' //OK 27 | : socket.ws.readyState == 2 ? 'orange' //closing 28 | : socket.ws.readyState == 3 ? 'red' //closed 29 | : /*else*/ 'silver' //unknown/default 30 | } 31 | socket.ws.addEventListener('message', onStateChange) 32 | 33 | } 34 | 35 | main() -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10-Chart-Data/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10-Chart-Data/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10-Chart-Data/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10-Chart-Data/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10a-Chart-Data-Solution/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10a-Chart-Data-Solution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 50 | 53 | 54 | 57 | 58 |
    59 |
    60 | 61 | 62 | 63 | 64 |

    Chart

    65 |
    66 |
    67 | 68 |
    69 | 70 | 71 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10a-Chart-Data-Solution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10a-Chart-Data-Solution/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10a-Chart-Data-Solution/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10a-Chart-Data-Solution/src/utils/counter.js: -------------------------------------------------------------------------------- 1 | export function Counter() { 2 | this.current = 0 3 | } 4 | 5 | Counter.prototype.increment = function() { 6 | return ++this.current 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10a-Chart-Data-Solution/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-10a-Chart-Data-Solution/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 50 | 53 | 54 | 57 | 58 |
    59 |
    60 | 61 | 62 | 63 | 64 |

    Chart

    65 |
    66 |
    67 | 68 |
    69 | 70 | 71 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/src/app.js: -------------------------------------------------------------------------------- 1 | import { connect } from './connect' 2 | import { credentials } from '../../../tutorialsCredentials' 3 | import { URLs } from '../../../tutorialsURLs' 4 | import { TradovateSocket } from '../../EX-07-Making-Requests/src/TradovateSocket' 5 | import { setAccessToken } from './storage' 6 | 7 | setAccessToken(null) 8 | 9 | const main = async () => { 10 | 11 | let all_bars = [] 12 | let unsubscribe 13 | 14 | const { accessToken } = await connect(credentials) 15 | 16 | //socket init 17 | const socket = new TradovateSocket() 18 | 19 | //HTML elements 20 | const $getChart = document.getElementById('get-chart-btn') 21 | const $statusInd = document.getElementById('status') 22 | const $symbol = document.getElementById('symbol') 23 | const $type = document.getElementById('type') 24 | const $nElements = document.getElementById('n-elements') 25 | const $elemSize = document.getElementById('elem-size') 26 | 27 | 28 | const onStateChange = _ => { 29 | $statusInd.style.backgroundColor = 30 | socket.ws.readyState == 0 ? 'gold' //pending 31 | : socket.ws.readyState == 1 ? 'green' //OK 32 | : socket.ws.readyState == 2 ? 'orange' //closing 33 | : socket.ws.readyState == 3 ? 'red' //closed 34 | : /*else*/ 'silver' //unknown/default 35 | } 36 | 37 | $getChart.addEventListener('click', async () => { 38 | all_bars = [] 39 | 40 | if(unsubscribe) unsubscribe() 41 | 42 | unsubscribe = await socket.subscribe({ 43 | url: 'md/getchart', 44 | body: { 45 | symbol: $symbol.value, 46 | chartDescription: { 47 | underlyingType: $type.value, 48 | elementSize: parseInt($elemSize.value), 49 | elementSizeUnit: 'UnderlyingUnits', 50 | withHistogram: false, 51 | }, 52 | timeRange: { 53 | asMuchAsElements: parseInt($nElements.value) 54 | } 55 | }, 56 | subscription: chart => { 57 | //we need to render our chart still! 58 | } 59 | }) 60 | }) 61 | 62 | await socket.connect(URLs.MD_URL, accessToken) 63 | 64 | socket.ws.addEventListener('message', onStateChange) 65 | } 66 | 67 | main() -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/src/utils/counter.js: -------------------------------------------------------------------------------- 1 | export function Counter() { 2 | this.current = 0 3 | } 4 | 5 | Counter.prototype.increment = function() { 6 | return ++this.current 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11-Tick-Charts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 10 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 50 | 53 | 54 | 57 | 58 |
    59 |
    60 | 61 | 62 | 63 | 64 |

    Chart

    65 |
    66 |
    67 | 68 |
    69 | 70 | 71 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/src/renderDOM.js: -------------------------------------------------------------------------------- 1 | 2 | import { renderPriceSize } from './renderPriceSize' 3 | 4 | const renderBidOffer = bid => ` 5 |
    6 | 9 |
    10 | ` 11 | 12 | export const renderDOM = ({ 13 | contractId, 14 | timestamp, 15 | bids, 16 | offers, 17 | }) => ` 18 |
    19 | 20 |

    ETHH1 - ${contractId}

    21 | 22 |
    23 |
    24 |
    25 |

    Bids

    26 | ${bids.map(renderBidOffer).join('')} 27 |
    28 |
    29 |

    Offers

    30 | ${offers.map(renderBidOffer).join('')} 31 |
    32 |
    33 |
    34 | ` -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/src/renderPriceSize.js: -------------------------------------------------------------------------------- 1 | export const renderPriceSize = ({price, size}) => ` 2 | ${price ? '
  • price: ' +price+ '
  • ' : ''} 3 | ${size ? '
  • size: ' +size+ '
  • ' : ''} 4 | ` -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/src/renderQuote.js: -------------------------------------------------------------------------------- 1 | 2 | import { renderPriceSize } from './renderPriceSize' 3 | 4 | export const renderQuote = ({ 5 | Bid, 6 | HighPrice, 7 | LowPrice, 8 | Offer, 9 | OpenInterest, 10 | OpeningPrice, 11 | SettlementPrice, 12 | TotalTradeVolume, 13 | Trade, 14 | }) => ` 15 |
    16 |

    BTCH1

    17 | 18 |
    19 |

    Bid

    20 |
      21 | ${renderPriceSize(Bid)} 22 |
    23 |
    24 |
    25 |

    HighPrice

    26 |
      27 | ${renderPriceSize(HighPrice)} 28 |
    29 |
    30 |
    31 |

    LowPrice

    32 |
      33 | ${renderPriceSize(LowPrice)} 34 |
    35 |
    36 |
    37 |

    Offer

    38 |
      39 | ${renderPriceSize(Offer)} 40 |
    41 |
    42 |
    43 |

    OpenInterest

    44 |
      45 | ${renderPriceSize(OpenInterest)} 46 |
    47 |
    48 |
    49 |

    OpeningPrice

    50 |
      51 | ${renderPriceSize(OpeningPrice)} 52 |
    53 |
    54 |
    55 |

    SettlementPrice

    56 |
      57 | ${renderPriceSize(SettlementPrice)} 58 |
    59 |
    60 |
    61 |

    TotalTradeVolume

    62 |
      63 | ${renderPriceSize(TotalTradeVolume)} 64 |
    65 |
    66 |
    67 |

    Trade

    68 |
      69 | ${renderPriceSize(Trade)} 70 |
    71 |
    72 |
    73 |
    74 | ` -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/src/utils/counter.js: -------------------------------------------------------------------------------- 1 | export function Counter() { 2 | this.current = 0 3 | } 4 | 5 | Counter.prototype.increment = function() { 6 | return ++this.current 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-11a-Tick-Charts-Solution/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |

    Open P/L

    30 |

    --

    31 |
    32 | 33 |

    Positions

    34 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/src/app.js: -------------------------------------------------------------------------------- 1 | import { connect } from './connect' 2 | import { isMobile } from './utils/isMobile' 3 | import { DeviceUUID } from "device-uuid" 4 | import { tvPost } from './services' 5 | import { getDeviceId, setAccessToken, setDeviceId } from './storage' 6 | import { TradovateSocket } from './TradovateSocket' 7 | import { credentials } from '../../../tutorialsCredentials' 8 | import { URLs } from '../../../tutorialsURLs' 9 | 10 | setAccessToken(null) 11 | 12 | //MOBILE DEVICE DETECTION 13 | let DEVICE_ID 14 | if(!isMobile()) { 15 | const device = getDeviceId() 16 | DEVICE_ID = device || new DeviceUUID().get() 17 | setDeviceId(DEVICE_ID) 18 | } else { 19 | DEVICE_ID = new DeviceUUID().get() 20 | } 21 | 22 | //get relevant UI elements 23 | const $buyBtn = document.getElementById('buy-btn'), 24 | $sellBtn = document.getElementById('sell-btn'), 25 | $posList = document.getElementById('position-list'), 26 | $symbol = document.getElementById('symbol'), 27 | $openPL = document.getElementById('open-pl'), 28 | $qty = document.getElementById('qty') 29 | 30 | 31 | //Setup events for active UI elements. 32 | const setupUI = (socket) => { 33 | //We will hook up UI events here 34 | } 35 | 36 | 37 | //APPLICATION ENTRY POINT 38 | const main = async () => { 39 | 40 | //for caching our open positions 41 | const pls = [] 42 | 43 | //Connect to the tradovate API by retrieving an access token 44 | await connect(credentials) 45 | 46 | //We will need a MarketDataSocket to get realtime price quotes to compare w/ our positions 47 | const socket = new TradovateSocket({debugLabel: 'Realtime API'}) 48 | await socket.connect(URLs.WS_DEMO_URL) 49 | 50 | const mdsocket = new TradovateSocket({debugLabel: 'Market Data API'}) 51 | await mdsocket.connect(URLs.MD_URL) 52 | 53 | //run the UI Setup 54 | setupUI(socket) 55 | 56 | //Calculate P&L! ...but how? 57 | 58 | } 59 | 60 | //START APP 61 | main() 62 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/src/renderPosition.js: -------------------------------------------------------------------------------- 1 | export const renderPos = (name, pl) => { 2 | 3 | return `
  • 4 |

    ${name} : ($${pl.toFixed(2)})

    5 |
  • ` 6 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/src/utils/isMobile.js: -------------------------------------------------------------------------------- 1 | export const isMobile = () => { 2 | let check = false; 3 | (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); 4 | return check 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12-Calculating-Open-PL/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tradovate API JS Example 8 | 9 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |

    Open P/L

    30 |

    --

    31 |
    32 | 33 |

    Positions

    34 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-api-js", 3 | "version": "1.0.0", 4 | "description": "Examples for using the Tradovate API with JavaScript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tradovate/example-api-js" 8 | }, 9 | "main": "index.html", 10 | "scripts": { 11 | "build": "webpack build", 12 | "start": "webpack serve", 13 | "export": "webpack --watch" 14 | }, 15 | "author": "Alexander Ross Lennert", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@babel/cli": "^7.13.14", 19 | "@babel/core": "^7.13.15", 20 | "@babel/node": "^7.13.13", 21 | "@babel/preset-env": "^7.13.15", 22 | "babel-loader": "^8.2.2", 23 | "del": "^2.2.0", 24 | "html-webpack-plugin": "^5.3.1", 25 | "path": "^0.12.7", 26 | "webpack": "^5.33.2", 27 | "webpack-cli": "^4.6.0", 28 | "webpack-dev-server": "^3.11.2" 29 | }, 30 | "dependencies": { 31 | "@babel/polyfill": "^7.12.1", 32 | "device-uuid": "^1.0.4", 33 | "node-polyfill-webpack-plugin": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/src/connect.js: -------------------------------------------------------------------------------- 1 | import { setAccessToken, getAccessToken, tokenIsValid, setAvailableAccounts, setUserData, getUserData } from './storage' 2 | import { tvGet, tvPost } from './services' 3 | import { waitForMs } from './utils/waitForMs' 4 | 5 | 6 | const handleRetry = async (data, json) => { 7 | const ticket = json['p-ticket'], 8 | time = json['p-time'], 9 | captcha = json['p-captcha'] 10 | 11 | if(captcha) { 12 | console.error('Captcha present, cannot retry auth request via third party application. Please try again in an hour.') 13 | return 14 | } 15 | 16 | console.log(`Time Penalty present. Retrying operation in ${time}s`) 17 | 18 | await waitForMs(time * 1000) 19 | await connect({ ...data, 'p-ticket': ticket }) 20 | } 21 | 22 | export const connect = async (data) => { 23 | let { token, expiration } = getAccessToken() 24 | 25 | if(token && tokenIsValid(expiration)) { 26 | console.log('Already connected. Using valid token.') 27 | const accounts = await tvGet('/account/list') 28 | const { token, expiration } = getAccessToken() 29 | const { userId, name } = getUserData() 30 | setAvailableAccounts(accounts) 31 | return { accessToken: token, expirationTime: expiration, userId, name } 32 | } 33 | 34 | const authResponse = await tvPost('/auth/accesstokenrequest', data, false) 35 | 36 | if(authResponse['p-ticket']) { 37 | return await handleRetry(data, authResponse) 38 | } else { 39 | const { errorText, accessToken, userId, userStatus, name, expirationTime } = authResponse 40 | 41 | if(errorText) { 42 | console.error(errorText) 43 | return 44 | } 45 | 46 | setAccessToken(accessToken, expirationTime) 47 | 48 | setUserData({ userId, name }) 49 | 50 | const accounts = await tvGet('/account/list') 51 | 52 | console.log(accounts) 53 | 54 | setAvailableAccounts(accounts) 55 | 56 | console.log(`Successfully stored access token ${accessToken} for user {name: ${name}, ID: ${userId}, status: ${userStatus}}.`) 57 | 58 | return authResponse 59 | } 60 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/src/renderPosition.js: -------------------------------------------------------------------------------- 1 | export const renderPos = (name, pl, netpos) => { 2 | 3 | return `
  • 4 | 5 |

    ${name}${netpos > 0 ? '+' + netpos : '' + netpos}

    :

    (${pl.toFixed(2)} | USD)

    6 |
    7 |
  • ` 8 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/src/storage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'tradovate-api-access-token' 2 | const EXPIRATION_KEY = 'tradovate-api-access-expiration' 3 | const DEVICE_ID_KEY = 'tradovate-device-id' 4 | const AVAIL_ACCTS_KEY = 'tradovate-api-available-accounts' 5 | const USER_DATA_KEY = 'tradovate-user-data' 6 | 7 | export const setDeviceId = (id) => { 8 | sessionStorage.setItem(DEVICE_ID_KEY, id) 9 | } 10 | 11 | export const getDeviceId = () => { 12 | return sessionStorage.getItem(DEVICE_ID_KEY) 13 | } 14 | 15 | export const setAvailableAccounts = accounts => { 16 | sessionStorage.setItem(AVAIL_ACCTS_KEY, JSON.stringify(accounts)) 17 | } 18 | 19 | /** 20 | * Returns and array of available accounts or undefined. 21 | * @returns Account[] 22 | */ 23 | export const getAvailableAccounts = () => { 24 | return JSON.parse(sessionStorage.getItem(AVAIL_ACCTS_KEY)) 25 | } 26 | 27 | /** 28 | * Use a predicate function to find an account. May be undefined. 29 | */ 30 | export const queryAvailableAccounts = predicate => { 31 | return JSON.parse(getAvailableAccounts()).find(predicate) 32 | } 33 | 34 | export const setAccessToken = (token, expiration) => { 35 | //if(!token || !expiration) throw new Error('attempted to set undefined token') 36 | sessionStorage.setItem(STORAGE_KEY, token) 37 | sessionStorage.setItem(EXPIRATION_KEY, expiration) 38 | } 39 | 40 | export const getAccessToken = () => { 41 | const token = sessionStorage.getItem(STORAGE_KEY) 42 | const expiration = sessionStorage.getItem(EXPIRATION_KEY) 43 | if(!token) { 44 | console.warn('No access token retrieved. Please request an access token.') 45 | } 46 | return { token, expiration } 47 | } 48 | 49 | export const tokenIsValid = expiration => new Date(expiration) - new Date() > 0 50 | 51 | export const setUserData = (data) => sessionStorage.setItem(USER_DATA_KEY, JSON.stringify(data)) 52 | export const getUserData = () => JSON.parse(sessionStorage.getItem(USER_DATA_KEY)) -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/src/utils/isMobile.js: -------------------------------------------------------------------------------- 1 | export const isMobile = () => { 2 | let check = false; 3 | (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); 4 | return check 5 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/src/utils/waitForMs.js: -------------------------------------------------------------------------------- 1 | export const waitForMs = t => { 2 | return new Promise((res) => { 3 | setTimeout(() => { 4 | res() 5 | }, t) 6 | }) 7 | } -------------------------------------------------------------------------------- /tutorial/WebSockets/EX-12a-Calculating-Open-PL-Solution/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | 7 | mode: "development", 8 | 9 | entry: ["@babel/polyfill", "./src/app.js"], 10 | 11 | output: { 12 | path: path.resolve(__dirname, "./dist"), 13 | filename: "bundle.js" 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.m?js$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ['@babel/preset-env'] 25 | } 26 | } 27 | } 28 | ] 29 | }, 30 | 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | filename: "index.html", 34 | template: 'index.html' 35 | }), 36 | new NodePolyfillPlugin() 37 | ], 38 | 39 | 40 | devtool: "source-map", 41 | devServer: { 42 | contentBase: path.join(__dirname, "dist"), 43 | compress: true, 44 | port: 8080 45 | } 46 | }; -------------------------------------------------------------------------------- /tutorial/WebSockets/README.md: -------------------------------------------------------------------------------- 1 | # Connecting the Tradovate WebSocket Client 2 | 3 | In this section, we will focus on connecting the websocket client. We will discuss all of its features, including how to 4 | establish a connection, how to maintain a connection using *heartbeat frames*, how to send requests to the server, and 5 | what to expect back in response. For readers who are new to using the Tradovate API, connecting to websockets requires 6 | acquiring an access token. You can follow the lessons from [part one](https://github.com/tradovate/example-api-js/tree/main/tutorial/Access/EX-0-Access-Start) to learn how to acquire a token. -------------------------------------------------------------------------------- /tutorial/tutorialsCredentials.js: -------------------------------------------------------------------------------- 1 | export const credentials = { 2 | name: "Your credentials here", 3 | password: "Your credentials here", 4 | appId: "Sample App", 5 | appVersion: "1.0", 6 | cid: 0, 7 | sec: "Your API secret here" 8 | } -------------------------------------------------------------------------------- /tutorial/tutorialsURLs.js: -------------------------------------------------------------------------------- 1 | export const URLs = { 2 | DEMO_URL: "https://demo.tradovateapi.com/v1", 3 | LIVE_URL: 'https://live.tradovateapi.com/v1', 4 | MD_URL: 'wss://md.tradovateapi.com/v1/websocket', 5 | WS_DEMO_URL: 'wss://demo.tradovateapi.com/v1/websocket', 6 | WS_LIVE_URL: 'wss://live.tradovateapi.com/v1/websocket' 7 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------