├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── lib ├── accounts.js ├── authorization.js ├── etrade.js ├── market.js └── order.js ├── package.json ├── test.js ├── testProd.js └── tests ├── accounts.js ├── authorization.js ├── market.js └── order.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | .project 17 | test-keys* 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Patrick McMichael 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # E*TRADE API 2 | 3 | A Node.js module for the [E*TRADE web services API](https://us.etrade.com/ctnt/dev-portal/getContent?contentUri=V0_Documentation-GettingStarted). 4 | 5 | ## Usage 6 | 7 | All you need to get started with this module is your E*TRADE-supplied consumer key and consumer secret 8 | 9 | ```javascript 10 | var etrade = require('etrade'); 11 | 12 | var configuration = 13 | { 14 | useSandbox : true|false, // true if not provided 15 | key : 'your_key', 16 | secret : 'your_secret', 17 | verboseLogging: true|false // true if not provided 18 | } 19 | 20 | var et = new etrade(configuration); 21 | ``` 22 | 23 | You now have an etrade client. Before you can do anything useful, you will need to authenticate yourself with the E*TRADE system, and a user will have to authorize your service to access his/her account data. 24 | 25 | ### Authorization 26 | 27 | Authorization is a painful four step process. In general it looks like this: 28 | 29 | 1. Your user logs into your site and you ask E*TRADE to generate a "request token" 30 | 2. You redirect your user to E*TRADE's authorization site, where they log in and authorize your application. 31 | 3. They are provided with a "verification code" (for example: H92GX) that they must copy and paste back into your site. 32 | 4. You provide the verification code back to E*TRADE, and get an "access token" which permits all broader activity. 33 | 34 | It looks like this in code: 35 | ```javascript 36 | et.getRequestToken( 37 | function(authorizationUrl) { 38 | // Your service requires users, who will need to visit 39 | // the following URL and, after logging in and 40 | // authorizing your service to access their account 41 | // data, paste the E*TRADE provided verification 42 | // code back into your application. 43 | console.log("Please have your client visit " + 44 | authorizationUrl + 45 | " to authorize your service"); }, 46 | function(error) { 47 | console.log("Error encountered while attempting " + 48 | "to retrieve a request token: " + 49 | error); } 50 | ); 51 | 52 | ``` 53 | 54 | The user should come back to you with their verification code, through some "out-of-band" process (you can, apparently work with E*TRADE to have them redirect your user back to your site if your service is public facing). 55 | 56 | ```javascript 57 | et.getAccessToken(verificationCode, 58 | function() { 59 | // Your app can start using other E*TRADE API now 60 | }, 61 | function(error) { 62 | console.log("Error encountered while attempting " + 63 | "to exchange request token for access token: " + 64 | error); 65 | } 66 | ); 67 | ``` 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*global module,require*/ 2 | module.exports = require('./lib/etrade.js'); 3 | -------------------------------------------------------------------------------- /lib/accounts.js: -------------------------------------------------------------------------------- 1 | 2 | exports.listAccounts = function(successCallback,errorCallback) 3 | { 4 | // 5 | // From the etrade dev portal at 6 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AccountsAPI-ListAccounts 7 | // 8 | var actionDescriptor = { 9 | method : "GET", 10 | module : "accounts", 11 | action : "accountlist", 12 | useJSON: true, 13 | }; 14 | 15 | this._run(actionDescriptor,{},successCallback,errorCallback); 16 | }; 17 | 18 | exports.getAccountBalance = function(accountId,successCallback,errorCallback) 19 | { 20 | // 21 | // From the etrade dev portal at 22 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AccountsAPI-GetAccountBalance 23 | // 24 | // accountId path required Numeric account ID 25 | var actionDescriptor = { 26 | method : "GET", 27 | module : "accounts", 28 | action : "accountbalance/" + String(accountId), 29 | useJSON: true, 30 | }; 31 | 32 | this._run(actionDescriptor,{},successCallback,errorCallback); 33 | }; 34 | 35 | exports.getAccountPositions = function(params,successCallback,errorCallback) 36 | { 37 | // 38 | // From the etrade dev portal at 39 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AccountsAPI-GetAccountPositions 40 | // 41 | // accountId path required Numeric account ID 42 | // count integer optional The number of positions to return in the response. If not specified, defaults to 25. Used for paging as described in the Notes below. 43 | // marker string optional Specifies the desired starting point of the set of items to return. Used for paging as described in the Notes below. 44 | // typeCode string optional The type of security. Possible values are: EQ (equity), OPTN (option), INDX (index), MF (mutual fund), FI (fixed income). If set to OPTN, must specify callPut, strikePrice, expYear, expMonth, and expDay. 45 | // symbol string conditional The market symbol. Required if typeCode is OPTN. 46 | // callPut enum conditional Specifies which type of option(s) to return. Possible values are: CALL or PUT. Required if typeCode is OPTN. 47 | // strikePrice double conditional Specifies the strikePrice of the desired option. Required if typeCode is OPTN. 48 | // expYear string conditional The year the option will expire, as specified in the option contract. Required if typeCode is OPTN. 49 | // expMonth integer conditional The year the option will expire, as specified in the option contract. Required if typeCode is OPTN. 50 | // expDay integer conditional The year the option will expire, as specified in the option contract. Required if typeCode is OPTN. 51 | 52 | 53 | if (!this.accountPositionDescriptors) 54 | { 55 | var isTypeCodeOption = function(p) { p.hasOwnProperty("typeCode") && p.typeCode == "OPT"; }; 56 | this.accountPositionDescriptors = [ 57 | { 58 | name : "accountId", 59 | required : true, 60 | validator : this._validateAsString.bind(this), 61 | }, 62 | { 63 | name : "count", 64 | required : false, 65 | validator : this._validateAsInt.bind(this), 66 | }, 67 | { 68 | name : "marker", 69 | required : false, 70 | validator : this._validateAsString.bind(this), 71 | }, 72 | { 73 | name : "typeCode", 74 | required : false, 75 | validator : function(val) { return this._validateAsOneOf(val,["EQ","OPTN","INDX","MF","FI"]); }.bind(this), 76 | }, 77 | { 78 | name : "symbol", 79 | required : isTypeCodeOption, 80 | validator : this._validateAsString.bind(this), 81 | }, 82 | { 83 | name : "callPut", 84 | required : isTypeCodeOption, 85 | validator : function(val) { return this._validateAsOneOf(val,["CALL","PUT"]); } 86 | }, 87 | { 88 | name : "strikePrice", 89 | required : isTypeCodeOption, 90 | validator : this._validateAsFloat.bind(this), 91 | }, 92 | { 93 | name : "expYear", 94 | required : isTypeCodeOption, 95 | validator : this._validateAsString.bind(this), 96 | }, 97 | { 98 | name : "expMonth", 99 | required : isTypeCodeOption, 100 | validator : this._validateAsInt.bind(this), 101 | }, 102 | { 103 | name : "expDay", 104 | required : isTypeCodeOption, 105 | validator : this._validateAsInt.bind(this) 106 | } 107 | ]; 108 | } 109 | 110 | var validationResult = this._validateParams(this.accountPositionDescriptors,params); 111 | if (validationResult.length) 112 | return errorCallback(validationResult); // Validation failed 113 | 114 | var actionDescriptor = { 115 | method : "GET", 116 | module : "accounts", 117 | action : "accountpositions/" + params.accountId, 118 | useJSON: true, 119 | }; 120 | 121 | delete params.accountId; 122 | 123 | this._run(actionDescriptor,params,successCallback,errorCallback); 124 | }; 125 | 126 | exports.getTransactionHistory = function(params,successCallback,errorCallback) 127 | { 128 | // 129 | // From the etrade dev portal at 130 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AccountsAPI-GetTransactionHistory 131 | // 132 | // accountId path required Numeric account ID. This is the only required parameter. 133 | // group path optional Major groupings of the transaction types defined in the Transaction types table below. Possible values are: DEPOSITS, WITHDRAWALS, TRADES. 134 | // assetType path Only allowed if group is TRADES. Possible values are: EQ (equities), OPTN (options), MMF (money market funds), MF (mutual funds), BOND (bonds). To retrieve all types, use ALL or omit this parameter. 135 | // transactionType path Transaction type(s) to include, e.g., check, deposit, fee, dividend, etc. A list of types is provided in the Transaction types table below. If a group is specified, this can be a comma-separated list of valid types for the group. Otherwise, only one type is accepted. 136 | // tickerSymbol path Only allowed if group is TRADES. A single market symbol, e.g., GOOG. 137 | // startDate string The earliest date to include in the date range, formatted as MMDDYYYY. History is available for two years. 138 | // endDate string The latest date to include in the date range, formatted as MMDDYYYY. 139 | // count integer Number of transactions to return in the response. If not specified, defaults to 50. Used for paging as described in the Notes below. 140 | // marker integer Specifies the desired starting point of the set of items to return. Used for paging as described in the Notes below. 141 | 142 | 143 | if (!this.transactionHistoryDescriptors) 144 | { 145 | var isGroupTrades = function(p) { p.hasOwnProperty("group") && params.group; }; 146 | 147 | this.transactionHistoryDescriptors = this._buildParamsDescriptor([ 148 | "accountId", true, this._validateAsString.bind(this), 149 | "group", false, function(val) { return this._validateAsOneOf(val,["DEPOSITS","WITHDRAWALS","TRADES"]); }.bind(this), 150 | "assetType", isGroupTrades, function(val) { var res = this._validateAsOneOf(val,["EQ","OPTN","MMF","MF","BOND"]); 151 | res.valid = res.valid && isGroupTrades(); return res; }.bind(this), 152 | "transactionType", false, this._validateAsString.bind(this), // A better validator is probably not in scope right now 153 | "tickerSymbol", isGroupTrades, function(val) { return { valid:isGroupTrades(), value:String(val), }; }.bind(this), 154 | "startDate", false, this._validateAsMMDDYYYYDate.bind(this), 155 | "endDate", false, this._validateAsMMDDYYYYDate.bind(this), 156 | "count", false, this._validateAsInt.bind(this), 157 | "marker", false, this._validateAsInt.bind(this), 158 | ]); 159 | } 160 | 161 | var validationResult = this._validateParams(this.transactionHistoryDescriptors,params); 162 | if (validationResult.length) 163 | return errorCallback(validationResult); // Validation failed 164 | 165 | var actionDescriptor = { 166 | method : "GET", 167 | module : "accounts", 168 | action : params.accountId + "/transactions", 169 | useJSON: true, 170 | }; 171 | 172 | delete params.accountId; 173 | 174 | 175 | // https://etws.etrade.com/accounts/rest/{accountId}/transactions/{Group}/{AssetType}/{TransactionType}/{TickerSymbol}{.json} 176 | if (params.hasOwnProperty("group")) 177 | { 178 | actionDescriptor.action += "/" + params.group; 179 | delete params.group; 180 | 181 | if (params.hasOwnProperty("assetType")) 182 | { 183 | actionDescriptor.action += "/" + params.assetType; 184 | delete params.assetType; 185 | } 186 | 187 | if (params.hasOwnProperty("transactionType")) 188 | { 189 | actionDescriptor.action += "/" + params.transactionType; 190 | delete params.transactionType; 191 | } 192 | 193 | if (params.hasOwnProperty("tickerSymbol")) 194 | { 195 | actionDescriptor.action += "/" + params.tickerSymbol; 196 | delete params.tickerSymbol; 197 | } 198 | } 199 | 200 | this._run(actionDescriptor,params,successCallback,errorCallback); 201 | }; 202 | 203 | exports.getTransactionDetails = function(params,successCallback,errorCallback) 204 | { 205 | // 206 | // From the etrade dev portal at 207 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AccountsAPI-GetTransactionDetails 208 | // 209 | // accountId path required Numeric account ID 210 | // transactionId path required Numeric transaction ID, usually acquired by using the Get Transaction History API. 211 | if (!this.transactionDetailsDescriptors) 212 | { 213 | this.transactionDetailsDescriptors = this._buildParamsDescriptor([ 214 | "accountId", true, this._validateAsString.bind(this), 215 | "transactionId", true, this._validateAsString.bind(this), 216 | ]); 217 | } 218 | 219 | var validationResult = this._validateParams(this.transactionDetailsDescriptors,params); 220 | if (validationResult.length) 221 | return errorCallback(validationResult); // Validation failed 222 | 223 | var actionDescriptor = { 224 | method : "GET", 225 | module : "accounts", 226 | action : params.accountId + "/transactions/" + params.transactionId, 227 | useJSON: true, 228 | }; 229 | 230 | this._run(actionDescriptor,{},successCallback,errorCallback); 231 | }; 232 | 233 | exports.listAlerts = function(successCallback,errorCallback) 234 | { 235 | // 236 | // From the etrade dev portal at 237 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AccountsAPI-ListAlerts 238 | // 239 | var actionDescriptor = { 240 | method : "GET", 241 | module : "accounts", 242 | action : "alerts", 243 | useJSON: true, 244 | }; 245 | 246 | this._run(actionDescriptor,{},successCallback,errorCallback); 247 | }; 248 | 249 | exports.getAlert = function(alertId,successCallback,errorCallback) 250 | { 251 | // 252 | // From the etrade dev portal at 253 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AccountsAPI-ReadAlert 254 | // 255 | // alertId path required Numeric alert ID 256 | var actionDescriptor = { 257 | method : "GET", 258 | module : "accounts", 259 | action : "alerts/" + String(alertId), 260 | useJSON: true, 261 | }; 262 | 263 | this._run(actionDescriptor,{},successCallback,errorCallback); 264 | }; 265 | 266 | exports.deleteAlert = function(alertId,successCallback,errorCallback) 267 | { 268 | // 269 | // From the etrade dev portal at 270 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AccountsAPI-DeleteAlerts 271 | // 272 | // alertId path required Numeric alert ID 273 | var actionDescriptor = { 274 | method : "DELETE", 275 | module : "accounts", 276 | action : "alerts/" + String(alertId), 277 | useJSON: true, 278 | }; 279 | 280 | this._run(actionDescriptor,{},successCallback,errorCallback); 281 | }; 282 | -------------------------------------------------------------------------------- /lib/authorization.js: -------------------------------------------------------------------------------- 1 | 2 | exports.getRequestToken = function(successCallback,errorCallback) 3 | { 4 | // One of successCallback or errorCallback is invoked 5 | // successCallback is invoked with the redirection address 6 | // 7 | // errorCallback is invoked with an error message indicating the 8 | // failure, if any 9 | if (arguments.length != 2) 10 | errorCallback("Invalid invocation of etrade::getRequestToken(): Two arguments are required"); 11 | else if (typeof(successCallback) != "function" || 12 | typeof(errorCallback) != "function") 13 | errorCallback("Invalid invocation of etrade::getRequestToken(): One or more arguments are not functions"); 14 | 15 | // 16 | // From the etrade dev portal at 17 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AuthorizationAPI-GetRequestToken 18 | // 19 | // oauth_consumer_key string The value used by the consumer to identify itself to the service provider. 20 | // oauth_timestamp integer The date and time of the request, in epoch time. Must be accurate within five minutes. 21 | // oauth_nonce string A nonce, as described in the authorization guide - roughly, an arbitrary or random value that cannot be used again with the same timestamp. 22 | // oauth_signature_method string The signature method used by the consumer to sign the request. The only supported value is "HMAC-SHA1". 23 | // oauth_signature string Signature generated with the shared secret and token secret using the specified oauth_signature_method, as described in OAuth documentation. 24 | // oauth_callback string Callback information, as described elsewhere. Must always be set to "oob", whether using a callback or not. 25 | // 26 | 27 | var method = "GET"; 28 | var ts = new Date(); 29 | var module = "oauth"; 30 | var action = "request_token"; 31 | var useJSON = false; // For some reason, JSON is not supported here? 32 | 33 | var requestOptions = this._getRequestOptions(method,ts,module,action,useJSON); 34 | 35 | // Add this call's query parameters 36 | requestOptions.qs.oauth_callback = "oob"; 37 | 38 | // Sign the request 39 | var oauth_signature = this.oauth_sign.hmacsign(requestOptions.method,requestOptions.url, 40 | requestOptions.qs,this.configuration.secret); 41 | // Add the signature to the request 42 | requestOptions.qs.oauth_signature = oauth_signature; 43 | 44 | // Make the request 45 | //console.log("Request: " + requestOptions.url); 46 | this.request(requestOptions,function(error,message,body) 47 | { 48 | if (error) 49 | { 50 | console.error("Error received in etrade::getRequestToken(): " + error); 51 | errorCallback(error); 52 | } 53 | else 54 | { 55 | var response = this._parseBody(message.headers["content-type"],body); 56 | 57 | this.configuration.oauth.request_token = response.oauth_token; 58 | this.configuration.oauth.request_token_secret = response.oauth_token_secret; 59 | 60 | // https://us.etrade.com/e/t/etws/authorize?key={oauth_consumer_key}&token={oauth_token} 61 | var url = "https://us.etrade.com/e/t/etws/authorize?" + 62 | this.querystring.stringify({key:this.configuration.key, token:response.oauth_token}); 63 | 64 | successCallback(url); 65 | } 66 | }.bind(this)); 67 | }; 68 | 69 | exports.getAccessToken = function(verificationCode,successCallback,errorCallback) 70 | { 71 | // One of successCallback or errorCallback is invoked 72 | // successCallback is invoked with the consumer's access token, followed 73 | // by the token secret, successCallback(token,secret) 74 | // 75 | // errorCallback is invoked with an error message indicating the failure 76 | if (arguments.length != 3) 77 | throw "Invalid invocation of etrade::getAccessToken(): Three arguments are required"; 78 | else if (typeof(successCallback) != "function" || 79 | typeof(errorCallback) != "function") 80 | throw "Invalid invocation of etrade::getAccessToken(): One or more callbacks are not function objects"; 81 | else if (verificationCode.length == 0) 82 | errorCallback("Verification Code cannot be empty"); 83 | 84 | // 85 | // From the etrade dev portal at 86 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AuthorizationAPI-GetAccessToken 87 | // oauth_consumer_key string Required The value used by the consumer to identify itself to the service provider. 88 | // oauth_timestamp integer Required The date and time of the request, in epoch time. Must be accurate to within five minutes. 89 | // oauth_nonce string Required A nonce, as described in the authorization guide - roughly, an arbitrary or random value that cannot be used again with the same timestamp. 90 | // oauth_signature_method string Required The signature method used by the consumer to sign the request. The only supported value is "HMAC-SHA1". 91 | // oauth_signature string Required Signature generated with the shared secret and token secret using the specified oauth_signature_method, as described in OAuth documentation. 92 | // oauth_token string Required The consumer’s request token to be exchanged for an access token. 93 | // oauth_verifier string Required The code received by the user to authenticate with the third-party application. 94 | // 95 | 96 | var method = "GET"; 97 | var ts = new Date(); 98 | var module = "oauth"; 99 | var action = "access_token"; 100 | var useJSON = false; // For some reason, JSON is not supported here? 101 | 102 | var requestOptions = this._getRequestOptions(method,ts,module,action,useJSON); 103 | 104 | // Add this call's query parameters 105 | requestOptions.qs.oauth_token = this.configuration.oauth.request_token; 106 | requestOptions.qs.oauth_verifier = verificationCode; 107 | 108 | // Sign the request 109 | var oauth_signature = this.oauth_sign.hmacsign(requestOptions.method,requestOptions.url, 110 | requestOptions.qs, 111 | this.configuration.secret, 112 | this.configuration.oauth.request_token_secret); 113 | 114 | // Add the signature to the request 115 | requestOptions.qs.oauth_signature = oauth_signature; 116 | 117 | // Make the request 118 | //console.log("Request: " + requestOptions.url); 119 | this.request(requestOptions,function(error,message,body) 120 | { 121 | if (error) 122 | { 123 | errorCallback(error); 124 | } 125 | else 126 | { 127 | var response = this._parseBody(message.headers["content-type"],body); 128 | 129 | this.configuration.oauth.access_token = response.oauth_token; 130 | this.configuration.oauth.access_token_secret = response.oauth_token_secret; 131 | this.authorized = true; 132 | successCallback(response.oauth_token,response.oauth_token_secret); 133 | } 134 | }.bind(this)); 135 | }; 136 | 137 | exports.renewAccessToken = function(successCallback,errorCallback) 138 | { 139 | // One of successCallback or errorCallback is invoked 140 | // successCallback is invoked with no arguments on success 141 | // 142 | // errorCallback is invoked with an error message indicating the 143 | // failure, if any 144 | if (arguments.length != 2) 145 | errorCallback("Invalid invocation of etrade::renewAccessToken(): Two arguments are required"); 146 | else if (typeof(successCallback) != "function" || 147 | typeof(errorCallback) != "function") 148 | errorCallback("Invalid invocation of etrade::renewAccessToken(): One or more arguments are not functions"); 149 | 150 | // 151 | // From the etrade dev portal at 152 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AuthorizationAPI-RenewAccessToken 153 | // oauth_consumer_key string Required The value used by the consumer to identify itself to the service provider. 154 | // oauth_timestamp integer Required The date and time of the request, in epoch time. Must be accurate within five minutes. 155 | // oauth_nonce string Required A nonce, as described in the authorization guide - roughly, an arbitrary or random value that cannot be used again with the same timestamp. 156 | // oauth_signature_method string Required The signature method used by the consumer to sign the request. The only supported value is "HMAC-SHA1". 157 | // oauth_signature string Required Signature generated with the shared secret and token secret using the specified oauth_signature_method, as described in OAuth documentation. 158 | // oauth_token string Required The consumer's request [Access???] token to be exchanged for an access token. 159 | 160 | var method = "GET"; 161 | var ts = new Date(); 162 | var module = "oauth"; 163 | var action = "renew_access_token"; 164 | var useJSON = false; // For some reason, JSON is not supported here? 165 | 166 | var requestOptions = this._getRequestOptions(method,ts,module,action,useJSON); 167 | 168 | // Add this call's query parameters 169 | requestOptions.qs.oauth_token = this.configuration.oauth.access_token; 170 | 171 | // Sign the request 172 | var oauth_signature = this.oauth_sign.hmacsign(requestOptions.method,requestOptions.url, 173 | requestOptions.qs, 174 | this.configuration.secret, 175 | this.configuration.oauth.access_token_secret); 176 | 177 | // Add the signature to the request 178 | requestOptions.qs.oauth_signature = oauth_signature; 179 | 180 | // Make the request 181 | //console.log("Request: " + requestOptions.url); 182 | this.request(requestOptions,function(error,message,body) 183 | { 184 | if (error) 185 | { 186 | errorCallback(error); 187 | } 188 | else 189 | { 190 | //var response = this._parseBody(message.headers["content-type"],body); 191 | successCallback(); 192 | } 193 | }.bind(this)); 194 | }; 195 | 196 | exports.revokeAccessToken = function(successCallback,errorCallback) 197 | { 198 | // One of successCallback or errorCallback is invoked 199 | // successCallback is invoked with no arguments on success 200 | // 201 | // errorCallback is invoked with an error message indicating the 202 | // failure, if any 203 | if (arguments.length != 2) 204 | errorCallback("Invalid invocation of etrade::revokeAccessToken(): Two arguments are required"); 205 | else if (typeof(successCallback) != "function" || 206 | typeof(errorCallback) != "function") 207 | errorCallback("Invalid invocation of etrade::revokeAccessToken(): One or more arguments are not functions"); 208 | 209 | // 210 | // From the etrade dev portal at 211 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AuthorizationAPI-RevokeAccessToken 212 | // oauth_consumer_key string Required The value used by the consumer to identify itself to the service provider. 213 | // oauth_timestamp integer Required The date and time of the request, in epoch time. Must be accurate within five minutes. 214 | // oauth_nonce string Required A nonce, as described in the authorization guide - roughly, an arbitrary or random value that cannot be used again with the same timestamp. 215 | // oauth_signature_method string Required The signature method used by the consumer to sign the request. The only supported value is "HMAC-SHA1". 216 | // oauth_signature string Required Signature generated with the shared secret and token secret using the specified oauth_signature_method, as described in OAuth documentation. 217 | // oauth_token string Required The consumer’s access token to be revoked. 218 | 219 | var method = "GET"; 220 | var ts = new Date(); 221 | var module = "oauth"; 222 | var action = "revoke_access_token"; 223 | var useJSON = false; // For some reason, JSON is not supported here? 224 | 225 | var requestOptions = this._getRequestOptions(method,ts,module,action,useJSON); 226 | 227 | // Add this call's query parameters 228 | requestOptions.qs.oauth_token = this.configuration.oauth.access_token; 229 | 230 | // Sign the request 231 | var oauth_signature = this.oauth_sign.hmacsign(requestOptions.method,requestOptions.url, 232 | requestOptions.qs, 233 | this.configuration.secret, 234 | this.configuration.oauth.access_token_secret); 235 | 236 | // Add the signature to the request 237 | requestOptions.qs.oauth_signature = oauth_signature; 238 | 239 | // Make the request 240 | //console.log("Request: " + requestOptions.url); 241 | this.request(requestOptions,function(error,message,body) 242 | { 243 | if (error) 244 | { 245 | errorCallback(error); 246 | } 247 | else 248 | { 249 | //var response = this._parseBody(message.headers["content-type"],body); 250 | this.authorized = false; 251 | successCallback(); 252 | } 253 | }.bind(this)); 254 | }; 255 | -------------------------------------------------------------------------------- /lib/etrade.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var oauth_sign = require('oauth-sign'); 3 | var querystring = require('querystring'); 4 | var request = require('request'); 5 | 6 | module.exports = exports = function(options) 7 | { 8 | // Options 9 | // { 10 | // "useSandbox" : true | false, // default is true 11 | // "key" : key, 12 | // "secret" : secret 13 | // } 14 | 15 | if (arguments.length != 1 || typeof options != "object") 16 | throw Error("The etrade module requires an options block object parameter"); 17 | if (!options.hasOwnProperty("key")) 18 | throw Error("The etrade module requires specification of an API key"); 19 | if (!options.hasOwnProperty("secret")) 20 | throw Error("The etrade module requires specification of an API secret"); 21 | if (!options.hasOwnProperty("useSandbox")) 22 | options.useSandbox = true; 23 | if (!options.hasOwnProperty("verboseLogging")) 24 | options.verboseLogging = true; 25 | 26 | var configurations = 27 | { 28 | "base" : { 29 | "oauth" : { 30 | "host" : "etws.etrade.com", 31 | "token" : "", 32 | "secret" : "", 33 | "code" : "", 34 | "request_token" : "", 35 | "access_token" : "", 36 | "access_token_secret" : "" 37 | }, 38 | "authorize" : { 39 | "host" : "us.etrade.com", 40 | "path" : "/e/t/etws/authorize", 41 | "login": "/home", 42 | }, 43 | "pushURL" : "https://etwspushsb.etrade.com/apistream/cometd/oauth/", 44 | "getHostname" : function(module) { 45 | return module == "oauth" ? this.oauth.host : this.host; 46 | } 47 | }, 48 | "production" : { 49 | "host" : "etws.etrade.com", 50 | "buildPath" : function(module,action) { 51 | return "/" + module + (module == "oauth" ? "/" : "/rest/") + action; 52 | } 53 | }, 54 | "sandbox" : { 55 | "host" : "etwssandbox.etrade.com", 56 | "buildPath" : function(module,action) { 57 | return "/" + module + (module == "oauth" ? "/" : "/sandbox/rest/") + action; 58 | } 59 | }, 60 | }; 61 | 62 | this.configuration = configurations.base; 63 | 64 | if (options.useSandbox) 65 | { 66 | for (var attrname in configurations.sandbox) 67 | { 68 | this.configuration[attrname] = configurations.sandbox[attrname]; 69 | } 70 | } 71 | else 72 | { 73 | for (var attrname in configurations.production) 74 | { 75 | this.configuration[attrname] = configurations.production[attrname]; 76 | } 77 | } 78 | 79 | this.configuration.key = options.key; 80 | this.configuration.secret = options.secret; 81 | this.authorized = false; 82 | 83 | this.crypto = crypto; 84 | this.oauth_sign = oauth_sign; 85 | this.querystring = querystring; 86 | this.request = request; 87 | }; 88 | 89 | // Import module functions (these make up the public API of the E*TRADE client) 90 | 91 | //Etrade modules 92 | var modules = [require('./authorization.js'), 93 | require('./accounts.js'), 94 | require('./market.js'), 95 | require('./order.js')]; // List of E*TRADE modules 96 | for (var moduleIndex = 0; moduleIndex < modules.length; ++moduleIndex) 97 | for (var funcName in modules[moduleIndex]) 98 | exports.prototype[funcName] = modules[moduleIndex][funcName]; 99 | 100 | exports.prototype._getRequestOptions = function(method, timeStamp, module, action, useJSON) 101 | { 102 | return { 103 | url : "https://" + this.configuration.getHostname(module) + 104 | this.configuration.buildPath(module,action) + 105 | (useJSON ? ".json" : ""), 106 | method : method, 107 | qs : { 108 | oauth_consumer_key : this.configuration.key, 109 | oauth_nonce : this._generateNonceFor(timeStamp), 110 | oauth_signature_method : "HMAC-SHA1", 111 | oauth_timestamp : Math.floor(timeStamp.getTime()/1000), 112 | oauth_version : "1.0" // Yes, needs to be a string (otherwise gets truncated) 113 | }, 114 | headers : {}, 115 | }; 116 | }; 117 | 118 | exports.prototype._generateNonceFor = function(timeStamp) 119 | { 120 | var msSinceEpoch = timeStamp.getTime(); 121 | 122 | var secondsSinceEpoch = Math.floor(msSinceEpoch / 1000.0); 123 | var msSinceSecond = (msSinceEpoch - (secondsSinceEpoch*1000)) / 1000.0; 124 | 125 | var maxRand = 2147483647.0; // This constant comes from PHP, IIRC 126 | var rand = Math.round(Math.random() * maxRand); 127 | 128 | var microtimeString = "" + msSinceSecond + "00000 " + secondsSinceEpoch; 129 | var nonce = microtimeString + rand; 130 | 131 | var md5Hash = crypto.createHash('md5'); 132 | md5Hash.update(nonce); 133 | return md5Hash.digest('hex'); 134 | }; 135 | 136 | exports.prototype._parseBody = function(contentType,body) 137 | { 138 | var contentTypes = { 139 | "application/x-www-form-urlencoded" : function(body) 140 | { 141 | return querystring.parse(body); 142 | }, 143 | "application/json" : function(body) 144 | { 145 | return JSON.parse(body); 146 | }, 147 | "text/html" : function(body) 148 | { 149 | return body; // Ugh. Remove this content type after debugging... 150 | } 151 | }; 152 | contentType = contentType.split(";")[0]; 153 | 154 | if (typeof(contentTypes[contentType]) == 'function') 155 | { 156 | return contentTypes[contentType](body); 157 | } 158 | else 159 | { 160 | throw "Unrecognized content type: " + contentType + "\nbody:" + body; 161 | } 162 | }; 163 | 164 | exports.prototype._buildParamsDescriptor = function(paramsDescriptorArray) 165 | { 166 | var paramsDescriptor = []; 167 | for (var index = 0; index < paramsDescriptorArray.length;) 168 | { 169 | var name = paramsDescriptorArray[index]; 170 | var required = paramsDescriptorArray[index+1]; 171 | var validator = paramsDescriptorArray[index+2]; 172 | 173 | if (typeof(name) != "string") throw "Invalid 'name' in ParamsDescriptor!"; 174 | else if (typeof(required) != "boolean" && typeof(required) != "function") 175 | throw "Invalid 'required' in ParamsDescriptor!"; 176 | else if (typeof(validator) != "function") 177 | throw "Invalid 'validator' in ParamsDescriptor!"; 178 | 179 | index += 3; 180 | 181 | paramsDescriptor.push({ name:name, required:required, validator:validator }); 182 | } 183 | 184 | return paramsDescriptor; 185 | }; 186 | 187 | exports.prototype._validateAsString = function(val) 188 | { 189 | return { valid:true, value:String(val)}; 190 | }; 191 | 192 | exports.prototype._validateAsBool = function(val) 193 | { 194 | return { valid:(typeof(val) == "Boolean"), value:Boolean(val) }; 195 | }; 196 | 197 | exports.prototype._validateAsInt = function(val) 198 | { 199 | var newVal = parseInt(val); 200 | return { valid:!isNaN(newVal), value:newVal}; 201 | }; 202 | 203 | exports.prototype._validateAsFloat = function(val) 204 | { 205 | var newVal = parseFloat(val); 206 | return { valid:!isNaN(newVal), value:val }; 207 | }; 208 | 209 | exports.prototype._validateAsMMDDYYYYDate = function(val) 210 | { 211 | var regex = /^[01][0-9][0123][0-9][0-9]{4}$/; // Not a true date validation, but gets close 212 | return { valid:regex.test(val), value:val }; 213 | }; 214 | 215 | exports.prototype._validateAsOneOf = function(val,permittedValues) 216 | { 217 | if (arguments.length < 2) // Simplifies usage of this validator 218 | return function(v) { return this._validateAsOneOf(v,val); }.bind(this); 219 | 220 | var valid = false; 221 | for (var index = 0; index < permittedValues.length && !valid; ++index) 222 | valid = (val == permittedValues[index]); 223 | return { valid:valid, value:val }; 224 | }; 225 | 226 | exports.prototype._validateAsComplex = function(paramsDescriptor) 227 | { 228 | return function(params) 229 | { 230 | // Is this right? Why does this depart from the { valid:bool, value:val } paradigm everywhere else? 231 | var validationResult = this._validateParams(paramsDescriptor,params); 232 | if (validationResult.length) return "Complex Validation Failure: " + validationResult; 233 | else return ""; 234 | }.bind(this); 235 | }; 236 | 237 | exports.prototype._validateParams = function(paramsDescriptor,params) 238 | { 239 | for (var paramDescriptor in paramsDescriptor) 240 | { 241 | var name = paramDescriptor.name; 242 | var isParamRequired = typeof paramDescriptor == "function" ? 243 | paramDescriptor.required(params) : 244 | paramDescriptor.required; 245 | var isParamPresent = name in params; 246 | if (isParamRequired && !isParamPresent) 247 | { 248 | return "Request parameter '" + name + "' not provided by user"; 249 | } 250 | else if (isParamPresent) 251 | { 252 | var validationResult = paramDescriptor.validator(params[name]); 253 | if (validationResult.valid) 254 | { 255 | params[name] = validationResult.value; 256 | } 257 | else 258 | { 259 | return "Request parameter '" + name + "' failed client-side validation check"; 260 | } 261 | } 262 | } 263 | 264 | return ""; 265 | }; 266 | 267 | exports.prototype._getAuthorizationHeaderFor = function(requestOptions) 268 | { 269 | // Sign the request 270 | var oauth_signature = this.oauth_sign.hmacsign(requestOptions.method,requestOptions.url, 271 | requestOptions.qs, 272 | this.configuration.secret, 273 | this.configuration.oauth.access_token_secret); 274 | // From: http://tools.ietf.org/html/rfc5849 275 | // Authorization: OAuth realm="Example", 276 | // oauth_consumer_key="9djdj82h48djs9d2", 277 | // oauth_token="kkk9d7dh3k39sjv7", 278 | // oauth_signature_method="HMAC-SHA1", 279 | // oauth_timestamp="137131201", 280 | // oauth_nonce="7d8f3e4a", 281 | // oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D" 282 | // 283 | // Sample from: http://oauth.googlecode.com/svn/code/javascript/example/signature.html 284 | // OAuth realm="",oauth_version="1.0",oauth_consumer_key="abcd",oauth_token="ijkl",oauth_timestamp="1396481619",oauth_nonce="2HL9hcG4R2r",oauth_signature_method="HMAC-SHA1",oauth_signature="uIDIOWJapuMFBXluGCGHcjePIcM%3D" 285 | return "OAuth realm=\"\"," + 286 | "oauth_version=\"" + encodeURIComponent(requestOptions.qs.oauth_version) + "\"," + 287 | "oauth_consumer_key=\"" + encodeURIComponent(requestOptions.qs.oauth_consumer_key) +"\"," + 288 | "oauth_token=\"" + encodeURIComponent(requestOptions.qs.oauth_token) + "\"," + 289 | "oauth_timestamp=\"" + encodeURIComponent(requestOptions.qs.oauth_timestamp) + "\"," + 290 | "oauth_nonce=\"" + encodeURIComponent(requestOptions.qs.oauth_nonce) + "\"," + 291 | "oauth_signature_method=\"" + encodeURIComponent(requestOptions.qs.oauth_signature_method) + "\"," + 292 | "oauth_signature=\"" + encodeURIComponent(oauth_signature) + "\""; 293 | }; 294 | 295 | exports.prototype._run = function(actionDescriptor,params,successCallback,errorCallback) 296 | { 297 | if (typeof(successCallback) != "function" || typeof(errorCallback) != "function") 298 | throw "One or more callback is not a function!"; 299 | 300 | if (!this.authorized) 301 | return errorCallback("Please authorize first!"); 302 | 303 | // Generate the options for the request module 304 | var requestOptions = this._getRequestOptions(actionDescriptor.method, 305 | new Date(), 306 | actionDescriptor.module, 307 | actionDescriptor.action, 308 | actionDescriptor.useJSON); 309 | 310 | // Add our token 311 | requestOptions.qs.oauth_token = this.configuration.oauth.access_token; 312 | 313 | if (actionDescriptor.method == "GET") 314 | { 315 | // Add this call's query parameters 316 | for (var paramName in params) 317 | requestOptions.qs[paramName] = params[paramName]; 318 | 319 | requestOptions.headers["Authorization"] = this._getAuthorizationHeaderFor(requestOptions); 320 | 321 | // Override query string with just the query params 322 | requestOptions.qs = params; 323 | } 324 | else if (actionDescriptor.method == "POST" || actionDescriptor.method == "DELETE") 325 | { 326 | // Specify the content type for POST requests 327 | if (actionDescriptor.method == "POST") 328 | requestOptions.headers["Content-Type"] = actionDescriptor.useJSON ? 329 | "application/json" : 330 | "application/x-www-form-urlencoded"; 331 | 332 | requestOptions.headers["Authorization"] = this._getAuthorizationHeaderFor(requestOptions); 333 | 334 | //console.log("Authorization: " + requestOptions.headers["Authorization"]); 335 | 336 | requestOptions.qs = {}; // Clear query string (we don't use it in POST requests) 337 | 338 | if (actionDescriptor.method == "POST") 339 | requestOptions.body = actionDescriptor.useJSON ? 340 | JSON.stringify(params) : 341 | querystring.stringify(params); 342 | } 343 | 344 | // Make the request 345 | var qs = querystring.stringify(requestOptions.qs); 346 | 347 | if(options.verboseLogging) { 348 | console.log("Request: [" + requestOptions.method + "]: " + requestOptions.url + (qs.length ? "?" + qs : "")); 349 | } 350 | 351 | this.request(requestOptions,function(error,message,body) 352 | { 353 | if (error) 354 | { 355 | if(options.verboseLogging) { 356 | console.error("Error received in etrade::_run(): " + error); 357 | } 358 | 359 | errorCallback(error); 360 | } 361 | else if (message.statusCode != 200) 362 | { 363 | if(options.verboseLogging) { 364 | console.error("E*TRADE returned status code: " + message.statusCode); 365 | } 366 | 367 | var msg = { body:body, httpVersion:message.httpVersion, 368 | headers:message.headers, statusCode:message.statusCode }; 369 | 370 | errorCallback("E*TRADE responded with a non-OK HTTP status code: " + JSON.stringify(msg)); 371 | } 372 | else 373 | { 374 | var response = this._parseBody(message.headers["content-type"],body); 375 | successCallback(response); 376 | } 377 | }.bind(this)); 378 | }; 379 | -------------------------------------------------------------------------------- /lib/market.js: -------------------------------------------------------------------------------- 1 | 2 | exports.getOptionChains = function(params,successCallback,errorCallback) 3 | { 4 | // 5 | // From the etrade dev portal at 6 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-MarketAPI-GetOptionChains 7 | // 8 | // chainType enum required The type of option chain. Possible values are: CALL, PUT, or CALLPUT (i.e., both calls and puts). 9 | // expirationDay integer required The day the option will expire 10 | // expirationMonth integer required The month the option will expire 11 | // expirationYear integer required The year the option will expire 12 | // underlier string required The market symbol for the underlying security 13 | // skipAdjusted boolean optional Specifies whether to show (TRUE) or not show (FALSE) adjusted options, i.e., options that have undergone a change resulting in a modification of the option contract. Default value is TRUE. 14 | 15 | if (!this.getOptionChainsDescriptors) 16 | { 17 | this.getOptionChainsDescriptors = this._buildParamsDescriptor([ 18 | "chainType", true, this._validateAsOneOf(["CALL","PUT","CALLPUT"]), 19 | "expirationDay",true,this._validateAsInt.bind(this), 20 | "expirationMonth",true,this._validateAsInt.bind(this), 21 | "expirationYear",true,this._validateAsInt.bind(this), 22 | "underlier",true,this._validateAsString.bind(this), 23 | "skipAdjusted",false,this._validateAsBool.bind(this) 24 | ]); 25 | } 26 | 27 | var validationResult = this._validateParams(this.getOptionChainsDescriptors,params); 28 | if (validationResult.length) 29 | return errorCallback(validationResult); // Validation failed 30 | 31 | var actionDescriptor = { 32 | method : "GET", 33 | module : "market", 34 | action : "optionchains", 35 | useJSON: true, 36 | }; 37 | 38 | this._run(actionDescriptor,params,successCallback,errorCallback); 39 | }; 40 | 41 | exports.getOptionExpireDates = function(underlier,successCallback,errorCallback) 42 | { 43 | // 44 | // From the etrade dev portal at 45 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-MarketAPI-GetOptionExpireDates 46 | // 47 | // underlier string required The symbol for the underlying instrument for this option 48 | 49 | var validationResult = this._validateAsString(underlier); 50 | if (validationResult.length) 51 | return errorCallback(validationResult); // Validation failed 52 | 53 | var actionDescriptor = { 54 | method : "GET", 55 | module : "market", 56 | action : "optionexpiredate", 57 | useJSON: true, 58 | }; 59 | 60 | this._run(actionDescriptor,{underlier:underlier},successCallback,errorCallback); 61 | }; 62 | 63 | exports.lookupProduct = function(params,successCallback,errorCallback) 64 | { 65 | // 66 | // From the etrade dev portal at 67 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-MarketAPI-LookUpProduct 68 | // 69 | // 70 | // company string required Full or partial name of the company. Note that the system extensively abbreviates common words such as "company", "industries", and "systems", and generally skips punctuation (periods, commas, apostrophes, etc.) . 71 | // type enum required The type of security. Possible values are: EQ (equity) or MF (mutual fund). 72 | 73 | if (!this.lookupProductDescriptor) 74 | { 75 | this.lookupProductDescriptor = this._buildParamsDescriptor([ 76 | "company",true,this._validateAsString.bind(this), 77 | "type", true, this._validateAsOneOf(["EQ","MF"]) 78 | ]); 79 | } 80 | 81 | var validationResult = this._validateParams(this.lookupProductDescriptor,params); 82 | if (validationResult.length) 83 | return errorCallback(validationResult); // Validation failed 84 | 85 | var actionDescriptor = { 86 | method : "GET", 87 | module : "market", 88 | action : "productlookup", 89 | useJSON: true, 90 | }; 91 | 92 | this._run(actionDescriptor,params,successCallback,errorCallback); 93 | }; 94 | 95 | exports.getQuote = function(params,successCallback,errorCallback) 96 | { 97 | // 98 | // From the etrade dev portal at 99 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-MarketAPI-GetQuotes 100 | // 101 | // 102 | // symbol path required One or more (comma-separated) symbols for equities or options, up to a maximum of 25. Symbols for equities are simple, e.g., GOOG. Symbols for options are more complex, consisting of six elements separated by colons, in this format: underlier:year:month:day:optionType:strikePrice. 103 | // detailFlag enum optional Optional parameter specifying which details to return in the response. The field set for each possible value is listed in separate tables below. The possible values are: 104 | // • FUNDAMENTAL - Instrument fundamentals and latest price 105 | // • INTRADAY - Performance for the current or most recent trading day 106 | // • OPTIONS - Information on a given option offering 107 | // • WEEK52 - 52-week high and low (highest high and lowest low) 108 | // • ALL (default) - All of the above information and more 109 | 110 | if (!this.getQuoteDescriptor) 111 | { 112 | var symbolValidator = function(val) 113 | { 114 | var valid = true; 115 | if (typeof(val) == "string") 116 | { 117 | if (val.indexOf(",") != -1) 118 | { 119 | val = val.split(",").filter(Boolean); 120 | } 121 | else 122 | { 123 | valid = val.length > 0; 124 | val = [ val ]; 125 | } 126 | } 127 | else if (val instanceof Array) 128 | { 129 | val = val.filter(Boolean); 130 | } 131 | else 132 | { 133 | valid = false; 134 | } 135 | 136 | if (valid) 137 | { 138 | // At this point val should be an array of non-zero-length strings 139 | valid = val.length > 0 && val.length < 26; // validity is at least one and less than 25 symbols 140 | val = val.join(","); // re-stringify things 141 | } 142 | 143 | return { valid:valid, value:val }; 144 | }; 145 | 146 | this.getQuoteDescriptor = this._buildParamsDescriptor([ 147 | "symbol",true,symbolValidator, 148 | "detailFlag", false, this._validateAsOneOf(["FUNDAMENTAL","INTRADAY","OPTIONS","WEEK52","ALL"]) 149 | ]); 150 | } 151 | 152 | var validationResult = this._validateParams(this.getQuoteDescriptor,params); 153 | if (validationResult.length) 154 | return errorCallback(validationResult); // Validation failed 155 | 156 | var actionDescriptor = { 157 | method : "GET", 158 | module : "market", 159 | action : "quote/" + params.symbol, 160 | useJSON: true, 161 | }; 162 | 163 | delete params.symbol; 164 | 165 | this._run(actionDescriptor,params,successCallback,errorCallback); 166 | }; 167 | 168 | -------------------------------------------------------------------------------- /lib/order.js: -------------------------------------------------------------------------------- 1 | 2 | exports.listOrders = function(accountId,successCallback,errorCallback) 3 | { 4 | // 5 | // From the etrade dev portal at 6 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-OrderAPI-ListOrders 7 | // 8 | 9 | var validationResult = this._validateAsString(accountId); 10 | if (!validationResult.valid) 11 | return errorCallback("The accountId parameter is invalid"); 12 | 13 | var actionDescriptor = { 14 | method : "GET", 15 | module : "order", 16 | action : "orderlist/" + validationResult.value, 17 | useJSON: true, 18 | }; 19 | 20 | this._run(actionDescriptor,{},successCallback,errorCallback); 21 | }; 22 | 23 | exports.previewEquityOrder = function(params,successCallback,errorCallback) 24 | { 25 | // 26 | // From the etrade dev portal at 27 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-OrderAPI-PreviewEquityOrder 28 | // 29 | // accountId integer required Numeric account ID 30 | // symbol string required The market symbol for the security being bought or sold 31 | // orderAction enum required The action that the broker is requested to perform. Possible values are: 32 | // • BUY 33 | // • SELL 34 | // • BUY_TO_COVER 35 | // • SELL_SHORT 36 | // clientOrderId string required A reference number generated by the developer. Used to ensure that a duplicate order is not being submitted. It can be any value of 20 alphanumeric characters or less, but must be unique within this account. It does not appear in any API responses. 37 | // priceType enum required The type of pricing. Possible values are: 38 | // • MARKET 39 | // • LIMIT 40 | // • STOP 41 | // • STOP_LIMIT 42 | // • MARKET_ON_CLOSE 43 | // If STOP, requires a stopPrice. If LIMIT, requires a limitPrice. If STOP_LIMIT equity order, requires both. 44 | // limitPrice double conditional The highest price at which to buy or the lowest price at which to sell if specified in a limit order. Required if priceType is LIMIT or STOP_LIMIT. 45 | // stopPrice double conditional The price at which to buy or sell if specified in a stop order. Required if priceType is STOP or STOP_LIMIT. 46 | // allOrNone boolean optional If TRUE, the transactions specified in the order must be executed all at once, or not at all. Default is FALSE. 47 | // quantity integer required The number of shares to buy or sell 48 | // reserveOrder boolean optional If set to TRUE, publicly displays only a limited number of shares (the reserve quantity), instead of the entire order, to avoid influencing other traders. Default is FALSE. If TRUE, must also specify the reserveQuantity. 49 | // reserveQuantity integer conditional The number of shares to be publicly displayed if this is a reserve order. Required if reserveOrder is TRUE. 50 | // marketSession enum required Session in which the equity order will be placed. Possible values are: REGULAR, EXTENDED. 51 | // orderTerm enum required Specifies the term for which the order is in effect. Possible values are: 52 | // • GOOD_UNTIL_CANCEL 53 | // • GOOD_FOR_DAY 54 | // • IMMEDIATE_OR_CANCEL (only for limit orders) 55 | // • FILL_OR_KILL (only for limit orders) 56 | // routingDestination enum optional The exchange where the order should be executed. Users may want to specify this if they believe they can get a better order fill at a specific exchange, rather than relying on the automatic order routing system. Possible values are: 57 | // • AUTO (default) 58 | // • ARCA 59 | // • NSDQ 60 | // • NYSE 61 | 62 | 63 | if (!this.previewEquityOrderDescriptors) 64 | { 65 | var priceTypeIsStop = function(p) { return p.priceType == "STOP"; }; 66 | var priceTypeIsStopLimit = function(p) { return p.priceType == "STOP_LIMIT"; }; 67 | var priceTypeIsLimit = function(p) { return p.priceType == "LIMIT"; }; 68 | 69 | this.previewEquityOrderDescriptors = this._buildParamsDescriptor([ 70 | "accountId", true, this._validateAsInt.bind(this), 71 | "symbol",true, this._validateAsString.bind(this), 72 | "orderAction",true,this._validateAsOneOf(["BUY","SELL","BUY_TO_COVER","SELL_SHORT"]), 73 | "clientOrderId",true,this._validateAsString.bind(this), 74 | "priceType",true,this._validateAsOneOf(["MARKET","LIMIT","STOP","STOP_LIMIT","MARKET_ON_CLOSE"]), 75 | "limitPrice",function(p) { return priceTypeIsLimit(p) || priceTypeIsStopLimit(p); },this._validateAsFloat.bind(this), 76 | "stopPrice",function(p) { return priceTypeIsStop(p) || priceTypeIsStopLimit(p); }, this._validateAsFloat.bind(this), 77 | "allOrNone",false,this._validateAsBool.bind(this), 78 | "quantity",true,this._validateAsInt.bind(this), 79 | "reserveOrder",false,this._validateAsBool.bind(this), 80 | "reserveQuantity",function(p) { return p.reserveOrder; },this._validateAsInt.bind(this), 81 | "marketSession",true,this._validateAsOneOf(["REGULAR","EXTENDED"]), 82 | "orderTerm",true,this._validateAsOneOf(["GOOD_UNTIL_CANCEL","GOOD_FOR_DAY","IMMEDIATE_OR_CANCEL","FILL_OR_KILL"]), 83 | "routingDestination",false,this._validateAsOneOf(["AUTO","ARCA","NSDQ","NYSE"]), 84 | ]); 85 | } 86 | 87 | var validationResult = this._validateParams(this.previewEquityOrderDescriptors,params); 88 | if (validationResult.length) 89 | return errorCallback(validationResult); // Validation failed 90 | 91 | var actionDescriptor = { 92 | method : "POST", 93 | module : "order", 94 | action : "previewequityorder", 95 | useJSON: true, 96 | }; 97 | 98 | var wrappedParams = { PreviewEquityOrder : { "-xmlns": "http://order.etws.etrade.com", EquityOrderRequest:params }}; 99 | 100 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 101 | }; 102 | 103 | exports.placeEquityOrder = function(params,successCallback,errorCallback) 104 | { 105 | // 106 | // From the etrade dev portal at 107 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-OrderAPI-PlaceEquityOrder 108 | // 109 | // accountId integer required Numeric account ID 110 | // symbol string required The market symbol for the security being bought or sold 111 | // orderAction enum required The action that the broker is requested to perform. Possible values are: 112 | // • BUY 113 | // • SELL 114 | // • BUY_TO_COVER 115 | // • SELL_SHORT 116 | // previewId long conditional If the order was not previewed, this parameter should not be specified. If the order was previewed, this parameter must specify the numeric preview ID from the preview, and other parameters of this request must match the parameters of the preview. 117 | // clientOrderId string required A reference number generated by the developer. Used to ensure that a duplicate order is not being submitted. It can be any value of 20 alphanumeric characters or less, but must be unique within this account. It does not appear in any API responses. 118 | // priceType enum required The type of pricing specified in the equity order. Possible values are: 119 | // • MARKET 120 | // • LIMIT 121 | // • STOP 122 | // • STOP_LIMIT 123 | // • MARKET_ON_CLOSE 124 | // If STOP, requires a stopPrice. If LIMIT, requires a limitPrice. If STOP_LIMIT, requires both. 125 | // For more information on these values, refer to the E*TRADE online help on conditional orders. 126 | // limitPrice double conditional The highest price at which to buy or the lowest price at which to sell. Required if priceType is LIMIT or STOP_LIMIT. 127 | // stopPrice double conditional The price at which to buy or sell if specified in a stop order. Required if priceType is STOP or STOP_LIMIT. 128 | // allOrNone boolean optional If TRUE, the transactions specified in the order must be executed all at once, or not at all. Default is FALSE. 129 | // quantity integer required The number of shares to buy or sell 130 | // reserveOrder boolean optional If set to TRUE, publicly displays only a limited number of shares (the reserve quantity), instead of the entire order, to avoid influencing other traders. Default is FALSE. If TRUE, must also specify the reserveQuantity. 131 | // reserveQuantity integer conditional The number of shares to be publicly displayed if this is a reserve order. Required if reserveOrder is TRUE. 132 | // marketSession enum required Session in which the equity order will be placed. Possible values are: REGULAR, EXTENDED. 133 | // orderTerm enum required Specifies the term for which the order is in effect. Possible values are: 134 | // • GOOD_UNTIL_CANCEL 135 | // • GOOD_FOR_DAY 136 | // • IMMEDIATE_OR_CANCEL (only for limit orders) 137 | // • FILL_OR_KILL (only for limit orders) 138 | // routingDestination enum optional The exchange where the order should be executed. Users may want to specify this if they believe they can get a better order fill at a specific exchange, rather than relying on the automatic order routing system. Possible values are: 139 | // • AUTO (default) 140 | // • ARCA 141 | // • NSDQ 142 | // • NYSE 143 | 144 | 145 | if (!this.placeEquityOrderDescriptors) 146 | { 147 | var priceTypeIsStop = function(p) { return p.priceType == "STOP"; }; 148 | var priceTypeIsStopLimit = function(p) { return p.priceType == "STOP_LIMIT"; }; 149 | var priceTypeIsLimit = function(p) { return p.priceType == "LIMIT"; }; 150 | 151 | this.placeEquityOrderDescriptors = this._buildParamsDescriptor([ 152 | "accountId", true, this._validateAsInt.bind(this), 153 | "symbol",true, this._validateAsString.bind(this), 154 | "orderAction",true,this._validateAsOneOf(["BUY","SELL","BUY_TO_COVER","SELL_SHORT"]), 155 | "previewId",false,this._validateAsInt.bind(this), 156 | "clientOrderId",true,this._validateAsString.bind(this), 157 | "priceType",true,this._validateAsOneOf(["MARKET","LIMIT","STOP","STOP_LIMIT","MARKET_ON_CLOSE"]), 158 | "limitPrice",function(p) { return priceTypeIsLimit(p) || priceTypeIsStopLimit(p); },this._validateAsFloat.bind(this), 159 | "stopPrice",function(p) { return priceTypeIsStop(p) || priceTypeIsStopLimit(p); }, this._validateAsFloat.bind(this), 160 | "allOrNone",false,this._validateAsBool.bind(this), 161 | "quantity",true,this._validateAsInt.bind(this), 162 | "reserveOrder",false,this._validateAsBool.bind(this), 163 | "reserveQuantity",function(p) { return p.reserveOrder; },this._validateAsInt.bind(this), 164 | "marketSession",true,this._validateAsOneOf(["REGULAR","EXTENDED"]), 165 | "orderTerm",true,this._validateAsOneOf(["GOOD_UNTIL_CANCEL","GOOD_FOR_DAY","IMMEDIATE_OR_CANCEL","FILL_OR_KILL"]), 166 | "routingDestination",false,this._validateAsOneOf(["AUTO","ARCA","NSDQ","NYSE"]), 167 | ]); 168 | } 169 | 170 | var validationResult = this._validateParams(this.placeEquityOrderDescriptors,params); 171 | if (validationResult.length) 172 | return errorCallback(validationResult); // Validation failed 173 | 174 | var actionDescriptor = { 175 | method : "POST", 176 | module : "order", 177 | action : "placeequityorder", 178 | useJSON: true, 179 | }; 180 | 181 | var wrappedParams = { PlaceEquityOrder : { "-xmlns": "http://order.etws.etrade.com", EquityOrderRequest:params }}; 182 | 183 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 184 | }; 185 | 186 | exports.previewEquityOrderChange = function(params,successCallback,errorCallback) 187 | { 188 | // 189 | // From the etrade dev portal at 190 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-OrderAPI-PreviewEquityOrderChange 191 | // 192 | // accountId integer required Numeric account ID 193 | // orderNum integer required Order number, taken from a previous response, that identifies the order to be changed 194 | // priceType enum required The type of pricing. Possible values are: 195 | // • MARKET 196 | // • LIMIT 197 | // • STOP 198 | // • STOP_LIMIT 199 | // • MARKET_ON_CLOSE 200 | // If STOP, requires a stopPrice. If LIMIT, requires a limitPrice. If STOP_LIMIT, requires both. 201 | // limitPrice double conditional The highest price at which to buy or the lowest price at which to sell. Required if priceType is LIMIT. 202 | // stopPrice double conditional The price at which to buy or sell if specified in a stop order. Required if priceType is STOP. 203 | // allOrNone boolean optional If TRUE, the transactions specified in the order must be executed all at once, or not at all. Default is FALSE. 204 | // quantity integer required The number of shares to buy or sell 205 | // reserveOrder boolean optional If TRUE, publicly displays only a limited number of shares (the reserve quantity), instead of the entire order, to avoid influencing other traders. Default is FALSE. If TRUE, must also specify the reserveQuantity. 206 | // reserveQuantity integer conditional The number of shares displayed for a reserve order. Required if reserveOrder is TRUE. 207 | // orderTerm enum required Specifies the term for which the order is in effect. Possible values are: 208 | // • GOOD_UNTIL_CANCEL 209 | // • GOOD_FOR_DAY 210 | // • IMMEDIATE_OR_CANCEL (only for limit orders) 211 | // • FILL_OR_KILL (only for limit orders) 212 | // clientOrderId string optional A reference number generated by the developer. Used to ensure that a duplicate order is not being submitted. It can be any value of 20 alphanumeric characters or less, but must be unique within this account. It does not appear in any API responses. 213 | 214 | if (!this.previewEquityOrderChangeDescriptors) 215 | { 216 | var priceTypeIsStop = function(p) { return p.priceType == "STOP"; }; 217 | var priceTypeIsStopLimit = function(p) { return p.priceType == "STOP_LIMIT"; }; 218 | var priceTypeIsLimit = function(p) { return p.priceType == "LIMIT"; }; 219 | 220 | this.previewEquityOrderChangeDescriptors = this._buildParamsDescriptor([ 221 | "accountId", true, this._validateAsInt.bind(this), 222 | "orderNum",true, this._validateAsInt.bind(this), 223 | "priceType",true,this._validateAsOneOf(["MARKET","LIMIT","STOP","STOP_LIMIT","MARKET_ON_CLOSE"]), 224 | "limitPrice",function(p) { return priceTypeIsLimit(p) || priceTypeIsStopLimit(p); },this._validateAsFloat.bind(this), 225 | // Note that the E*TRADE documentation here does not specify STOP_LIMIT, but all the other 226 | // services that use this parameter do include STOP_LIMIT, so I am viewing this as 227 | // documentation error on this service (and its place counterpart) 228 | "stopPrice",function(p) { return priceTypeIsStop(p) || priceTypeIsStopLimit(p); }, this._validateAsFloat.bind(this), 229 | "allOrNone",false,this._validateAsBool.bind(this), 230 | "quantity",true,this._validateAsInt.bind(this), // The example request does not have this parameter, yet the documentation has it as required? 231 | "reserveOrder",false,this._validateAsBool.bind(this), 232 | "reserveQuantity",function(p) { return p.reserveOrder; },this._validateAsInt.bind(this), 233 | "orderTerm",true,this._validateAsOneOf(["GOOD_UNTIL_CANCEL","GOOD_FOR_DAY","IMMEDIATE_OR_CANCEL","FILL_OR_KILL"]), 234 | "clientOrderId",false,this._validateAsString.bind(this), 235 | ]); 236 | } 237 | 238 | var validationResult = this._validateParams(this.previewEquityOrderChangeDescriptors,params); 239 | if (validationResult.length) 240 | return errorCallback(validationResult); // Validation failed 241 | 242 | var actionDescriptor = { 243 | method : "POST", 244 | module : "order", 245 | action : "previewchangeequityorder", 246 | useJSON: true, 247 | }; 248 | 249 | var wrappedParams = { previewChangeEquityOrder : { "-xmlns": "http://order.etws.etrade.com", changeEquityOrderRequest:params }}; 250 | 251 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 252 | }; 253 | 254 | exports.placeEquityOrderChange = function(params,successCallback,errorCallback) 255 | { 256 | // 257 | // From the etrade dev portal at 258 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-OrderAPI-PlaceEquityOrderChange 259 | // 260 | // accountId integer required Numeric account ID 261 | // orderNum integer required Order number, taken from a previous response, that identifies the order to be changed 262 | // clientOrderId string optional A reference number generated by the developer. Used to ensure that a duplicate order is not being submitted. It can be any value of 20 alphanumeric characters or less, but must be unique within this account. It does not appear in any API responses. 263 | // previewId long optional If the change was not previewed, this parameter should not be specified. If the change was previewed, this parameter must specify the numeric preview ID from the preview, and other parameters of this request must match the parameters of the preview. 264 | // priceType enum required The type of pricing. Possible values are: 265 | // • MARKET 266 | // • LIMIT 267 | // • STOP 268 | // • STOP_LIMIT 269 | // • MARKET_ON_CLOSE 270 | // If STOP, requires a stopPrice. If LIMIT, requires a limitPrice. If STOP_LIMIT, requires both. 271 | // limitPrice double conditional The highest price at which to buy or the lowest price at which to sell. Required if priceType is LIMIT. 272 | // stopPrice double conditional The price at which to buy or sell if specified in a stop order. Required if priceType is STOP. 273 | // allOrNone boolean optional If TRUE, the transactions specified in the order must be executed all at once, or not at all. Default is FALSE. 274 | // quantity integer required The number of shares to buy or sell 275 | // reserveOrder boolean optional If TRUE, publicly displays only a limited number of shares (the reserve quantity), instead of the entire order, to avoid influencing other traders. Default is FALSE. If TRUE, must also specify the reserveQuantity. 276 | // reserveQuantity integer conditional The number of shares displayed for a reserve order. Required if reserveOrder is TRUE. 277 | // orderTerm enum required Specifies the term for which the order is in effect. Possible values are: 278 | // • GOOD_UNTIL_CANCEL 279 | // • GOOD_FOR_DAY 280 | // • IMMEDIATE_OR_CANCEL (only for limit orders) 281 | // • FILL_OR_KILL (only for limit orders) 282 | 283 | if (!this.placeEquityOrderChangeDescriptors) 284 | { 285 | var priceTypeIsStop = function(p) { return p.priceType == "STOP"; }; 286 | var priceTypeIsStopLimit = function(p) { return p.priceType == "STOP_LIMIT"; }; 287 | var priceTypeIsLimit = function(p) { return p.priceType == "LIMIT"; }; 288 | 289 | this.placeEquityOrderChangeDescriptors = this._buildParamsDescriptor([ 290 | "accountId", true, this._validateAsInt.bind(this), 291 | "orderNum",true, this._validateAsInt.bind(this), 292 | "clientOrderId",false,this._validateAsString.bind(this), 293 | "previewId",false,this._validateAsInt.bind(this), 294 | "priceType",true,this._validateAsOneOf(["MARKET","LIMIT","STOP","STOP_LIMIT","MARKET_ON_CLOSE"]), 295 | "limitPrice",function(p) { return priceTypeIsLimit(p) || priceTypeIsStopLimit(p); },this._validateAsFloat.bind(this), 296 | // Note that the E*TRADE documentation here does not specify STOP_LIMIT, but all the other 297 | // services that use this parameter do include STOP_LIMIT, so I am viewing this as 298 | // documentation error on this service (and its preview counterpart) 299 | "stopPrice",function(p) { return priceTypeIsStop(p) || priceTypeIsStopLimit(p); }, this._validateAsFloat.bind(this), 300 | "allOrNone",false,this._validateAsBool.bind(this), 301 | "quantity",true,this._validateAsInt.bind(this), // The example request does not have this parameter, yet the documentation has it as required? 302 | "reserveOrder",false,this._validateAsBool.bind(this), 303 | "reserveQuantity",function(p) { return p.reserveOrder; },this._validateAsInt.bind(this), 304 | "orderTerm",true,this._validateAsOneOf(["GOOD_UNTIL_CANCEL","GOOD_FOR_DAY","IMMEDIATE_OR_CANCEL","FILL_OR_KILL"]), 305 | ]); 306 | } 307 | 308 | var validationResult = this._validateParams(this.placeEquityOrderChangeDescriptors,params); 309 | if (validationResult.length) 310 | return errorCallback(validationResult); // Validation failed 311 | 312 | var actionDescriptor = { 313 | method : "POST", 314 | module : "order", 315 | action : "placechangeequityorder", 316 | useJSON: true, 317 | }; 318 | 319 | var wrappedParams = { placeChangeEquityOrder : { "-xmlns": "http://order.etws.etrade.com", changeEquityOrderRequest:params }}; 320 | 321 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 322 | 323 | }; 324 | 325 | // Okay, all this copy-paste is getting really old. Let's try something different for option orders. 326 | exports._buildOptionOrderDescriptor = function() 327 | { 328 | // 329 | // From the etrade dev portal at 330 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-OrderAPI-PreviewOptionOrder 331 | // 332 | // accountId integer required Numeric account ID 333 | // symbolInfo complex required Container for symbol information 334 | // -> symbol string required The market symbol for the underlier 335 | // -> callOrPut enum required Option type - specifies either CALL or PUT 336 | // -> strikePrice double required The strike price for this option 337 | // -> expirationYear integer required The 4-digit year the option will expire 338 | // -> expirationMonth integer required The month (1-12) the option will expire 339 | // -> expirationDay integer required The day (1-31) the option will expire 340 | // orderAction enum required The action that the broker is requested to perform. Possible values are: 341 | // • BUY_OPEN 342 | // • SELL_OPEN 343 | // • BUY_CLOSE 344 | // • SELL_CLOSE 345 | // priceType enum required The type of pricing specified in the equity order. Possible values are: 346 | // • MARKET 347 | // • LIMIT 348 | // • STOP 349 | // • STOP_LIMIT 350 | // If STOP, requires a stopPrice. If LIMIT, requires a limitPrice. If STOP_LIMIT, requires a stopLimitPrice. 351 | // limitPrice double conditional The highest price at which to buy or the lowest price at which to sell. Required if priceType is LIMIT. 352 | // stopPrice double conditional The price at which to buy or sell if specified in a stop order. Required if priceType is STOP. 353 | // stopLimitPrice double conditional The designated price for a stop-limit order. Required if priceType is STOP_LIMIT. 354 | // allOrNone boolean optional If TRUE, the transactions specified in the order must be executed all at once, or not at all. Default is FALSE. 355 | // quantity integer required The number of shares to buy or sell 356 | // reserveOrder boolean optional If TRUE, publicly displays only a limited number of shares (the reserve quantity), instead of the entire order, to avoid influencing other traders. Default is FALSE. If TRUE, must also specify the reserveQuantity. 357 | // reserveQuantity integer conditional The number of shares displayed for a reserve order. Required if reserveOrder is TRUE. 358 | // orderTerm enum required Specifies the term for which the order is in effect. Possible values are: 359 | // • GOOD_UNTIL_CANCEL 360 | // • GOOD_FOR_DAY 361 | // • IMMEDIATE_OR_CANCEL (only for limit orders) 362 | // • FILL_OR_KILL (only for limit orders) 363 | // routingDestination enum optional The exchange where the order should be executed. Users may want to specify this if they believe they can get a better order fill at a specific exchange, rather than relying on the automatic order routing system. Possible values are: 364 | // • AUTO (default) 365 | // • ARCA 366 | // • NSDQ 367 | // • NYSE 368 | // clientOrderId string optional A reference number generated by the developer. Used to ensure that a duplicate order is not being submitted. It can be any value of 20 alphanumeric characters or less, but must be unique within this account. It does not appear in any API responses. 369 | 370 | return this._buildParamsDescriptor([ 371 | "accountId", true, this._validateAsInt.bind(this), 372 | "symbolInfo", true, this._validateAsComplex( 373 | this._buildParamsDescriptor([ 374 | "symbol",true,this._validateAsString.bind(this), 375 | "callOrPut",true,this._validateAsOneOf(["CALL","PUT"]), 376 | "expirationYear",true,this._validateAsInt.bind(this), 377 | "expirationMonth",true,this._validateAsInt.bind(this), 378 | "expirationDay",true,this._validateAsInt.bind(this)])), 379 | "orderAction",true,this._validateAsOneOf(["BUY_OPEN","SELL_OPEN","BUY_CLOSE","SELL_CLOSE"]), 380 | "priceType",true,this._validateAsOneOf(["MARKET","LIMIT","STOP","STOP_LIMIT"]), 381 | "limitPrice",function(p) { return p.priceType == "LIMIT"; },this._validateAsFloat.bind(this), 382 | "stopPrice",function(p) { return p.priceType == "STOP"; },this._validateAsFloat.bind(this), 383 | "stopLimitPrice",function(p) { return p.priceType == "STOP_LIMIT"; },this._validateAsFloat.bind(this), 384 | "allOrNone",false,this._validateAsBool.bind(this), 385 | "quantity",true,this._validateAsInt.bind(this), 386 | "reserveOrder",false,this._validateAsBool.bind(this), 387 | "reserveQuantity",function(p) { return p.reserveOrder; },this._validateAsInt.bind(this), 388 | "orderTerm",true,this._validateAsOneOf(["GOOD_UNTIL_CANCEL","GOOD_FOR_DAY","IMMEDIATE_OR_CANCEL","FILL_OR_KILL"]), 389 | "routingDestination",false,this._validateAsOneOf(["AUTO","ARCA","NSDQ","NYSE"]), 390 | "clientOrderId",false,this._validateAsString.bind(this), 391 | ]); 392 | }; 393 | 394 | exports.previewOptionOrder = function(params,successCallback,errorCallback) 395 | { 396 | if (!this.previewOptionOrderDescriptor) 397 | { 398 | this.previewOptionOrderDescriptor = this._buildOptionOrderDescriptor(); 399 | } 400 | 401 | var validationResult = this._validateParams(this.previewOptionOrderDescriptor,params); 402 | if (validationResult.length) 403 | return errorCallback(validationResult); // Validation failed 404 | 405 | var actionDescriptor = { 406 | method : "POST", 407 | module : "order", 408 | action : "previewoptionorder", 409 | useJSON: true, 410 | }; 411 | 412 | var wrappedParams = { PreviewOptionOrder : { "-xmlns": "http://order.etws.etrade.com", OptionOrderRequest:params }}; 413 | 414 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 415 | }; 416 | 417 | exports.placeOptionOrder = function(params,successCallback,errorCallback) 418 | { 419 | if (!this.placeOptionOrderDescriptor) 420 | { 421 | this.placeOptionOrderDescriptor = this._buildOptionOrderDescriptor(); 422 | this.placeOptionOrderDescriptor.push({ 423 | name:"previewId", 424 | required:false, 425 | validator:this._validateAsInt.bind(this) 426 | }); 427 | } 428 | 429 | var validationResult = this._validateParams(this.placeOptionOrderDescriptor,params); 430 | if (validationResult.length) 431 | return errorCallback(validationResult); // Validation failed 432 | 433 | var actionDescriptor = { 434 | method : "POST", 435 | module : "order", 436 | action : "placeoptionorder", 437 | useJSON: true, 438 | }; 439 | 440 | var wrappedParams = { PlaceOptionOrder : { "-xmlns": "http://order.etws.etrade.com", OptionOrderRequest:params }}; 441 | 442 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 443 | }; 444 | 445 | exports._buildOptionOrderChangeDescriptor = function() 446 | { 447 | 448 | // 449 | // From the etrade dev portal at 450 | // https://us.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-OrderAPI-PreviewOptionOrderChange 451 | 452 | return this._buildParamsDescriptor([ 453 | "accountId", true, this._validateAsInt.bind(this), 454 | "orderNum",true, this._validateAsInt.bind(this), 455 | "clientOrderId",false,this._validateAsString.bind(this), 456 | "previewId",false,this._validateAsInt.bind(this), 457 | "priceType",true,this._validateAsOneOf(["MARKET","LIMIT","STOP","STOP_LIMIT","MARKET_ON_CLOSE"]), 458 | "limitPrice",function(p) { return p.priceType == "LIMIT"; },this._validateAsFloat.bind(this), 459 | "stopPrice",function(p) { return p.priceType == "STOP"; }, this._validateAsFloat.bind(this), 460 | "stopLimitPrice",function(p) { return p.priceType == "STOP_LIMIT"; }, this._validateAsFloat.bind(this), 461 | "allOrNone",false,this._validateAsBool.bind(this), 462 | "quantity",true,this._validateAsInt.bind(this), // The example request does not have this parameter, yet the documentation has it as required? 463 | "reserveOrder",false,this._validateAsBool.bind(this), 464 | "reserveQuantity",function(p) { return p.reserveOrder; },this._validateAsInt.bind(this), 465 | "orderTerm",true,this._validateAsOneOf(["GOOD_UNTIL_CANCEL","GOOD_FOR_DAY","IMMEDIATE_OR_CANCEL","FILL_OR_KILL"]), 466 | ]); 467 | }; 468 | 469 | exports.previewOptionOrderChange = function(params,successCallback,errorCallback) 470 | { 471 | if (!this.previewOptionOrderChangeDescriptor) 472 | { 473 | this.previewOptionOrderChangeDescriptor = this._buildOptionOrderChangeDescriptor(); 474 | } 475 | 476 | var validationResult = this._validateParams(this.previewOptionOrderChangeDescriptor,params); 477 | if (validationResult.length) 478 | return errorCallback(validationResult); // Validation failed 479 | 480 | var actionDescriptor = { 481 | method : "POST", 482 | module : "order", 483 | action : "previewchangeoptionorder", 484 | useJSON: true, 485 | }; 486 | 487 | var wrappedParams = { PreviewChangeOptionOrder : { "-xmlns": "http://order.etws.etrade.com", ChangeOptionOrderRequest:params }}; 488 | 489 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 490 | }; 491 | 492 | exports.placeOptionOrderChange = function(params,successCallback,errorCallback) 493 | { 494 | if (!this.placeOptionOrderChangeDescriptor) 495 | { 496 | this.placeOptionOrderDescriptor = this._buildOptionOrderDescriptor(); 497 | this.placeOptionOrderChangeDescriptor.push({ 498 | name:"previewId", 499 | required:false, 500 | validator:this._validateAsInt.bind(this) 501 | }); 502 | } 503 | 504 | var validationResult = this._validateParams(this.placeOptionOrderChangeDescriptor,params); 505 | if (validationResult.length) 506 | return errorCallback(validationResult); // Validation failed 507 | 508 | var actionDescriptor = { 509 | method : "POST", 510 | module : "order", 511 | action : "placechangeoptionorder", 512 | useJSON: true, 513 | }; 514 | 515 | var wrappedParams = { PlaceChangeOptionOrder : { "-xmlns": "http://order.etws.etrade.com", ChangeOptionOrderRequest:params }}; 516 | 517 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 518 | }; 519 | 520 | exports.cancelOrder = function(params,successCallback,errorCallback) 521 | { 522 | if (!this.cancelOrderDescriptor) 523 | { 524 | this.cancelOrderDescriptor = this._buildParamsDescriptor([ 525 | "accountId",true,this._validateAsInt.bind(this), 526 | "orderNum",true,this._validateAsInt.bind(this) 527 | ]); 528 | } 529 | 530 | var validationResult = this._validateParams(this.cancelOrderDescriptor,params); 531 | if (validationResult.length) 532 | return errorCallback(validationResult); 533 | 534 | var actionDescriptor = { 535 | method : "POST", 536 | module : "order", 537 | action : "cancelorder", 538 | useJSON : true, 539 | }; 540 | 541 | var wrappedParams = { cancelOrder : { "-xmlns": "http://order.etws.etrade.com", cancelOrderRequest:params }}; 542 | 543 | this._run(actionDescriptor,wrappedParams,successCallback,errorCallback); 544 | }; 545 | 546 | 547 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-etrade-api", 3 | "version": "0.9.2", 4 | "description": "Use E*TRADE's REST API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/theidealist/node-etrade-api.git" 12 | }, 13 | "keywords": [ 14 | "etrade", 15 | "api", 16 | "rest" 17 | ], 18 | "author": "Patrick McMichael ", 19 | "license": "BSD", 20 | "dependencies": { 21 | "oauth-sign": "0.4.x", 22 | "request" : "2.34.x" 23 | }, 24 | "devDependencies": { 25 | "colors" : "*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 2 | // Other nodejs modules 3 | var readline = require('readline'); 4 | 5 | // Our principal nodejs module 6 | var etrade = require('./index'); 7 | 8 | // The keys to the castle 9 | var options = require('./test-keys.js'); // Uncommited file. It has valid E*TRADE keys that are not shareable 10 | options.useSandbox = true; // Please don't run this against the live servers 11 | 12 | // A basic test harness for running tests with a little bit of shared state 13 | var TestHarness = function() 14 | { 15 | this.et = new etrade(options); 16 | this.rl = rl = readline.createInterface({ input: process.stdin, output: process.stdout }); 17 | this.tests = []; 18 | this.shared = {}; // For shared context (account list, etc.) 19 | }; 20 | 21 | TestHarness.prototype.getErrorCallbackFor = function(errorLabel) 22 | { 23 | if (arguments.length < 1) errorLabel = "Unknown"; 24 | 25 | return function(error) 26 | { 27 | console.error(errorLabel + ": " + error); 28 | process.exit(-1); 29 | }; 30 | }; 31 | 32 | TestHarness.prototype.next = function() 33 | { 34 | return this.tests.shift().bind(this); 35 | }; 36 | 37 | // Instantiate our test harness type 38 | var testHarness = new TestHarness(); 39 | 40 | var testModules = [ require('./tests/authorization.js'), 41 | require('./tests/accounts.js'), 42 | require('./tests/order.js'), 43 | require('./tests/market.js')]; 44 | 45 | for (var index = 0; index < testModules.length; ++index) 46 | testModules[index].registerTests(testHarness); 47 | 48 | // Close up shop 49 | testHarness.tests.push(function() { 50 | this.et.revokeAccessToken(this.next(),this.getErrorCallbackFor("RevokeAccessToken")); }); 51 | testHarness.tests.push(function() { 52 | console.log("Tests Completed"); process.exit(0); }); 53 | 54 | // Kick it! 55 | testHarness.next()(); 56 | -------------------------------------------------------------------------------- /testProd.js: -------------------------------------------------------------------------------- 1 | 2 | // Other nodejs modules 3 | var readline = require('readline'); 4 | 5 | // Our principal nodejs module 6 | var etrade = require('./index'); 7 | 8 | // The keys to the castle 9 | var options = require('./test-keys.js'); // Uncommited file. It has valid E*TRADE keys that are not shareable 10 | //options.useSandbox = false; // Please don't run this against the live servers 11 | 12 | // A basic test harness for running tests with a little bit of shared state 13 | var TestHarness = function() 14 | { 15 | this.et = new etrade(options); 16 | this.rl = rl = readline.createInterface({ input: process.stdin, output: process.stdout }); 17 | this.tests = []; 18 | this.shared = {}; // For shared context (account list, etc.) 19 | }; 20 | 21 | TestHarness.prototype.getErrorCallbackFor = function(errorLabel) 22 | { 23 | if (arguments.length < 1) errorLabel = "Unknown"; 24 | 25 | return function(error) 26 | { 27 | console.error(errorLabel + ": " + error); 28 | process.exit(-1); 29 | }; 30 | }; 31 | 32 | TestHarness.prototype.next = function() 33 | { 34 | return this.tests.shift().bind(this); 35 | }; 36 | 37 | // Instantiate our test harness type 38 | var testHarness = new TestHarness(); 39 | 40 | var testModules = [ require('./tests/authorization.js'), 41 | require('./tests/accounts.js'), 42 | //require('./tests/order.js'), 43 | require('./tests/market.js')]; 44 | 45 | for (var index = 0; index < testModules.length; ++index) 46 | testModules[index].registerTests(testHarness); 47 | 48 | // Close up shop 49 | testHarness.tests.push(function() { 50 | this.et.revokeAccessToken(this.next(),this.getErrorCallbackFor("RevokeAccessToken")); }); 51 | testHarness.tests.push(function() { 52 | console.log("Tests Completed"); process.exit(0); }); 53 | 54 | // Kick it! 55 | testHarness.next()(); 56 | -------------------------------------------------------------------------------- /tests/accounts.js: -------------------------------------------------------------------------------- 1 | // Tests for the accounts module 2 | 3 | var runTests = false; 4 | var displayResults = false; 5 | 6 | var t = []; // An array of test functions 7 | var m = []; // An array of mandatory test functions (usually later tests depend on these) 8 | var accounts = []; // A list of accounts 9 | 10 | exports.registerTests = function(th) 11 | { 12 | th.tests = th.tests.concat(runTests ? t : m); 13 | }; 14 | 15 | // list accounts 16 | t.push(function() { this.et.listAccounts(this.next(),this.getErrorCallbackFor("ListAccounts")); }); 17 | if (displayResults) t.push(function(accountList) { console.log("AccountList: " + JSON.stringify(accountList,null,2)); this.next()(accountList); }); 18 | t.push(function(accountList) { this.shared.accounts = accounts = accountList["json.accountListResponse"].response; this.next()(); }); 19 | 20 | // account balance 21 | t.push(function(account) { this.et.getAccountBalance(accounts[0].accountId,this.next(),this.getErrorCallbackFor("GetAccountBalance")); }); 22 | if (displayResults) t.push(function(balance) { console.log("AccountBalance: " + JSON.stringify(balance,null,2)); this.next()(balance);}); 23 | 24 | // account positions 25 | t.push(function() { this.et.getAccountPositions({accountId:accounts[0].accountId },this.next(),this.getErrorCallbackFor("GetAccountPositions")); }); 26 | if (displayResults) t.push(function(positions) { console.log("Positions[" + accounts[0].accountId + "]: " + JSON.stringify(positions,null,2)); this.next()(); }); 27 | t.push(function() { this.et.getAccountPositions({accountId:accounts[1].accountId },this.next(),this.getErrorCallbackFor("GetAccountPositions")); }); 28 | if (displayResults) t.push(function(positions) { console.log("Positions[" + accounts[1].accountId + "]: " + JSON.stringify(positions,null,2)); this.next()(); }); 29 | 30 | // transaction history 31 | t.push(function() { this.et.getTransactionHistory({accountId:accounts[0].accountId },this.next(),this.getErrorCallbackFor("GetTransactionHistory")); }); 32 | if (displayResults) t.push(function(history) { console.log("TransactionHistory: " + JSON.stringify(history,null,2)); this.next()(history); }); 33 | t.push(function(history) { transactions = history["json.transactions"].transactionList; this.next()(); }); 34 | 35 | // transaction details 36 | t.push(function() { this.et.getTransactionDetails({accountId:accounts[0].accountId, transactionId:transactions[0].transactionId}, 37 | this.next(),this.getErrorCallbackFor("GetTransactionDetails")); }); 38 | if (displayResults) t.push(function(transaction) { console.log("Transaction: " + JSON.stringify(transaction,null,2)); this.next()(); }); 39 | 40 | //list alerts 41 | t.push(function() { this.et.listAlerts(this.next(),this.getErrorCallbackFor("ListAlerts")); }); 42 | if (displayResults) t.push(function(alertList) { console.log("AlertList: " + JSON.stringify(alertList,null,2)); this.next()(alertList); }); 43 | t.push(function(alertList) { alerts = alertList["json.getAlertsResponse"].response; this.next()(); }); 44 | 45 | // get alert 46 | t.push(function() { this.et.getAlert(alerts[0].alertId,this.next(),this.getErrorCallbackFor("GetAlert")); }); 47 | if (displayResults) t.push(function(alert) { console.log("Alert: " + JSON.stringify(alert,null,2)); this.next()(); }); 48 | 49 | // delete alert 50 | t.push(function() { this.et.deleteAlert(alerts[0].alertId,this.next(),this.getErrorCallbackFor("DeleteAlert")); }); 51 | if (displayResults) t.push(function(deleteResponse) { console.log("DeleteResult: " + JSON.stringify(deleteResponse,null,2)); this.next()(); }); 52 | 53 | m.push(t[0],t[1]); 54 | -------------------------------------------------------------------------------- /tests/authorization.js: -------------------------------------------------------------------------------- 1 | // Tests for the authorization module 2 | 3 | var runTests = true; 4 | var displayResults = false; 5 | 6 | var t = []; // An array of test functions 7 | var m = []; // An array of mandatory test functions (usually later tests depend on these) 8 | 9 | exports.registerTests = function(th) 10 | { 11 | th.tests = th.tests.concat(runTests ? t : m); 12 | }; 13 | 14 | t.push(function() { 15 | this.et.getRequestToken(this.next(),this.getErrorCallbackFor("GetRequestToken")); }); 16 | t.push(function(url) { 17 | console.log("Authorize at: " + url); this.next()(); }); 18 | t.push(function() { rl.question("Verification Code? ",this.next()); }); 19 | t.push(function(verifier) { 20 | this.et.getAccessToken(verifier,this.next(),this.getErrorCallbackFor("GetAccessToken")); }); 21 | if (displayResults) t.push(function() { console.log("Got an access token"); this.next()(); }); 22 | t.push(function() { this.et.renewAccessToken(this.next(),this.getErrorCallbackFor("RenewAccessToken")); }); 23 | if (displayResults) t.push(function() { console.log("Renewed access token"); this.next()(); }); 24 | 25 | m.push(t[0],t[1],t[2],t[3]); 26 | 27 | -------------------------------------------------------------------------------- /tests/market.js: -------------------------------------------------------------------------------- 1 | // Tests for the order module 2 | 3 | var runTests = true; 4 | var displayResults = true; 5 | 6 | var t = []; // An array of test functions 7 | var m = []; // An array of mandatory test functions (usually later tests depend on these) 8 | var options = []; // A list of orders for an account 9 | 10 | exports.registerTests = function(th) 11 | { 12 | th.tests = th.tests.concat(runTests ? t : m); 13 | }; 14 | 15 | previewOptionChainParams = { 16 | chainType : "CALL", 17 | expirationDay : 29, 18 | expirationMonth : 01, 19 | expirationYear : 2016, 20 | underlier : "PCLN", 21 | skipAdjusted : true 22 | }; 23 | 24 | previewExpiryDates = { 25 | underlier : "GOOG" 26 | }; 27 | 28 | lookupProductParams = { 29 | company : "VSAT", 30 | type : "EQ" 31 | }; 32 | 33 | getQuoteEquityParams = { 34 | symbol : "MSFT", 35 | detailFlag : "OPTIONS" 36 | }; 37 | 38 | getQuoteOptionParams = { 39 | symbol : "AAPL:2015:8:21:CALL:130", 40 | detailFlag : "OPTIONS" 41 | }; 42 | 43 | // Test getOptionChains function 44 | t.push(function() { this.et.getOptionChains(previewOptionChainParams,this.next(),this.getErrorCallbackFor("OptionChain")); }); 45 | if (displayResults) t.push(function(optionChains) { console.log("Option Chains: " + JSON.stringify(optionChains,null,2)); this.next()(optionChains); }); 46 | t.push(function(optionChains) { this.shared.options = options = optionChains.optionDetails; this.next()(); }); 47 | 48 | // Test getOptionExpireDates function 49 | t.push(function() { this.et.getOptionExpireDates(previewExpiryDates["underlier"],this.next(),this.getErrorCallbackFor("OptionExpiry")); }); 50 | if (displayResults) t.push(function(optionExpiry) { console.log("Option Expiry Dates: " + JSON.stringify(optionExpiry,null,2)); this.next()(optionExpiry); }); 51 | t.push(function(optionExpiry) { this.shared.options = options = optionExpiry.optionDetails; this.next()(); }); 52 | 53 | // Test LookupProduct function 54 | t.push(function() { this.et.lookupProduct(lookupProductParams,this.next(),this.getErrorCallbackFor("LookupProduct")); }); 55 | if (displayResults) t.push(function(lookupProd) { console.log("Product Lookup: " + JSON.stringify(lookupProd,null,2)); this.next()(lookupProd); }); 56 | t.push(function(lookupProd) { this.shared.options = options = lookupProd.optionDetails; this.next()(); }); 57 | 58 | // Test getQuote function for single stock 59 | t.push(function() { this.et.getQuote(getQuoteEquityParams,this.next(),this.getErrorCallbackFor("GetQuote")); }); 60 | if (displayResults) t.push(function(quote) { console.log("Get Quote for Stock: " + JSON.stringify(quote,null,2)); this.next()(quote); }); 61 | t.push(function(quote) { this.shared.options = options = quote.optionDetails; this.next()(); }); 62 | 63 | // Test getQuote function for option 64 | t.push(function() { this.et.getQuote(getQuoteOptionParams,this.next(),this.getErrorCallbackFor("GetQuote")); }); 65 | if (displayResults) t.push(function(quote) { console.log("Get Quote for Option: " + JSON.stringify(quote,null,2)); this.next()(quote); }); 66 | t.push(function(quote) { this.shared.options = options = quote.optionDetails; this.next()(); }); 67 | 68 | 69 | 70 | // All done 71 | -------------------------------------------------------------------------------- /tests/order.js: -------------------------------------------------------------------------------- 1 | // Tests for the order module 2 | 3 | var runTests = true; 4 | var displayResults = true; 5 | 6 | var t = []; // An array of test functions 7 | var m = []; // An array of mandatory test functions (usually later tests depend on these) 8 | var accounts = []; // A list of accounts 9 | var account0Id; // The first account id 10 | var orders = []; // A list of orders for an account 11 | 12 | exports.registerTests = function(th) 13 | { 14 | th.tests = th.tests.concat(runTests ? t : m); 15 | }; 16 | 17 | previewEquityOrderParams = { 18 | accountId : "", 19 | symbol : "GOOG", 20 | orderAction : "BUY", 21 | clientOrderId : "AnOrderId", 22 | priceType : "MARKET", 23 | quantity : 20, 24 | marketSession : "REGULAR", 25 | orderTerm : "GOOD_FOR_DAY" 26 | }; 27 | 28 | equityOrderParams = { 29 | accountId : "", 30 | symbol : "GOOG", 31 | orderAction : "BUY", 32 | clientOrderId : "AnOrderId", 33 | priceType : "MARKET", 34 | quantity : 20, 35 | marketSession : "REGULAR", 36 | orderTerm : "GOOD_FOR_DAY" 37 | }; 38 | 39 | t.push(function() { accounts = this.shared.accounts; account0Id = accounts[0].accountId; this.next()(); }); 40 | t.push(function() { this.et.listOrders(account0Id,this.next(),this.getErrorCallbackFor("ListOrders")); }); 41 | if (displayResults) t.push(function(orderList) { console.log("OrderList: " + JSON.stringify(orderList,null,2)); this.next()(orderList); }); 42 | t.push(function(orderList) { this.shared.orders = orders = orderList.GetOrderListResponse.orderDetails; this.next()(); }); 43 | t.push(function() { previewEquityOrderParams.accountId = equityOrderParams.accountId = account0Id; this.next()(); }); 44 | t.push(function() { this.et.previewEquityOrder(previewEquityOrderParams,this.next(),this.getErrorCallbackFor("PreviewEquityOrder")); }); 45 | if (displayResults) t.push(function(orderPreview) { console.log("OrderPreview: " + JSON.stringify(orderPreview,null,2)); this.next()(orderPreview); }); 46 | t.push(function(orderPreview) { equityOrderParams.previewId = orderPreview.previewId; this.next()(); }); 47 | t.push(function() { this.et.placeEquityOrder(equityOrderParams,this.next(),this.getErrorCallbackFor("PlaceEquityOrder")); }); 48 | if (displayResults) t.push(function(order) { console.log("Order: " + JSON.stringify(order,null,2)); this.next()(order); }); 49 | t.push(function(order) { this.next()(); }); 50 | 51 | // Option Orders 52 | previewOptionOrderParams = { 53 | "accountId": "", 54 | "quantity": "1", 55 | "symbolInfo": { 56 | "symbol": "IBM", 57 | "callOrPut": "CALL", 58 | "strikePrice": "115", 59 | "expirationYear": "2014", 60 | "expirationMonth": "5", 61 | "expirationDay": "17" 62 | }, 63 | "orderAction": "BUY_OPEN", 64 | "priceType": "MARKET", 65 | "orderTerm": "GOOD_FOR_DAY", 66 | "clientOrderId" : "DifferentOrderId" 67 | }; 68 | 69 | placeOptionOrderParams = { 70 | "accountId": "", 71 | "clientOrderId": "259", 72 | "limitPrice": "10", 73 | "quantity": "1", 74 | "symbolInfo": { 75 | "symbol": "IBM", 76 | "callOrPut": "CALL", 77 | "strikePrice": "115", 78 | "expirationYear": "2015", 79 | "expirationMonth": "5", 80 | "expirationDay": "17" 81 | }, 82 | "orderAction": "BUY_OPEN", 83 | "priceType": "LIMIT", 84 | "orderTerm": "GOOD_FOR_DAY" 85 | }; 86 | 87 | t.push(function() { previewOptionOrderParams.accountId = account0Id; this.next()(); }); 88 | t.push(function() { placeOptionOrderParams.accountId = account0Id; this.next()(); }); 89 | t.push(function() { this.et.previewOptionOrder(previewOptionOrderParams,this.next(),this.getErrorCallbackFor("PreviewOptionOrder")); }); 90 | if (displayResults) t.push(function(res) { console.log("PreviewOptionOrderResponse: " + JSON.stringify(res,null,2)); this.next()(res); }); 91 | t.push(function(res) { previewOptionOrderParams.previewId = res.PreviewOptionOrderResponse.OptionOrderResponse.previewId; this.next()(); }); 92 | t.push(function() { this.et.placeOptionOrder(previewOptionOrderParams,this.next(),this.getErrorCallbackFor("PlaceOptionOrder-1")); }); 93 | if (displayResults) t.push(function(res) { console.log("PlaceOptionOrder-1Response: " + JSON.stringify(res,null,2)); this.next()(res); }); 94 | t.push(function(res) { this.next()(); }); 95 | 96 | // Note sure why these cause E*TRADE server to return with an unhelpful HTTP status 500 "The server encountered an internal 97 | // error () that prevented it from fulfilling this request." message. Ugh. 98 | //t.push(function() { this.et.placeOptionOrder(placeOptionOrderParams,this.next(),this.getErrorCallbackFor("PlaceOptionOrder-2")); }); 99 | //if (displayResults) t.push(function(res) { console.log("PlaceOptionOrder-2Response: " + JSON.stringify(res)); this.next()(res); }); 100 | //t.push(function(res) { this.next()(); }); 101 | 102 | t.push(function() { this.et.cancelOrder({ accountId : account0Id, orderNum:262 },this.next(),this.getErrorCallbackFor("CancelOrder")); }); 103 | if (displayResults) t.push(function(res) { console.log("CancelOrderResponse: " + JSON.stringify(res,null,2)); this.next()(res); }); 104 | t.push(function(res) { this.next()(); }); 105 | 106 | // All done 107 | --------------------------------------------------------------------------------