tdameritrade-api-ruby 1.3.0.20210215

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +29 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +84 -0
  8. data/Rakefile +8 -0
  9. data/lib/tdameritrade-api-ruby.rb +1 -0
  10. data/lib/tdameritrade.rb +9 -0
  11. data/lib/tdameritrade/authentication.rb +65 -0
  12. data/lib/tdameritrade/client.rb +56 -0
  13. data/lib/tdameritrade/error.rb +20 -0
  14. data/lib/tdameritrade/operations/base_operation.rb +36 -0
  15. data/lib/tdameritrade/operations/create_watchlist.rb +30 -0
  16. data/lib/tdameritrade/operations/get_instrument_fundamentals.rb +22 -0
  17. data/lib/tdameritrade/operations/get_price_history.rb +55 -0
  18. data/lib/tdameritrade/operations/get_quotes.rb +21 -0
  19. data/lib/tdameritrade/operations/get_watchlists.rb +12 -0
  20. data/lib/tdameritrade/operations/replace_watchlist.rb +33 -0
  21. data/lib/tdameritrade/operations/support/build_watchlist_items.rb +21 -0
  22. data/lib/tdameritrade/operations/update_watchlist.rb +33 -0
  23. data/lib/tdameritrade/util.rb +50 -0
  24. data/lib/tdameritrade/version.rb +3 -0
  25. data/spec/spec_helper.rb +110 -0
  26. data/spec/support/authenticated_client.rb +12 -0
  27. data/spec/support/spec/mocks/mock_get_instrument_fundamentals.rb +17 -0
  28. data/spec/support/spec/mocks/mock_get_price_history.rb +17 -0
  29. data/spec/support/spec/mocks/mock_get_quotes.rb +17 -0
  30. data/spec/support/spec/mocks/mock_watchlists.rb +43 -0
  31. data/spec/support/spec/mocks/tdameritrade_api_mock_base.rb +21 -0
  32. data/spec/support/webmock_off.rb +7 -0
  33. data/spec/tdameritrade/operations/create_watchlist_spec.rb +42 -0
  34. data/spec/tdameritrade/operations/error_spec.rb +75 -0
  35. data/spec/tdameritrade/operations/get_instrument_fundamentals_spec.rb +182 -0
  36. data/spec/tdameritrade/operations/get_price_history_spec.rb +160 -0
  37. data/spec/tdameritrade/operations/get_quotes_spec.rb +351 -0
  38. data/spec/tdameritrade/operations/get_watchlists_spec.rb +80 -0
  39. data/spec/tdameritrade/operations/replace_watchlist_spec.rb +45 -0
  40. data/spec/tdameritrade/operations/update_watchlist_spec.rb +45 -0
  41. data/tdameritrade-api-ruby.gemspec +33 -0
  42. metadata +253 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8d134792147ae3f4c334dae26243c3f8a13bd48841f38521b03ca4296ae8749c
4
+ data.tar.gz: bff7d8e1f07e85b27d3d91674bb61a4d367cd10e7aa8854b2eb9dd4dc0364ba2
5
+ SHA512:
6
+ metadata.gz: 3c860ad9f0ed410f23617e2cf7e5d0b8a8056f1e39aede00e2471696df97723bf15f069bd397920fbadeff11260d1ccc539301daf0798f30e5e1f83fdfc20647
7
+ data.tar.gz: c49cee69e2a13981cca1bc2dbd626cafee90be3de4a7bbcc88789d6b0cc8e3846281f74a7ed218d725b5ea0edee4e82be35456875d3d5bb911ebfe04e58f86d7
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea
7
+ .DS_STORE
8
+ spec/test_data/sample_stream_rspec_test.binary
9
+ Gemfile.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ spec/test_data/sample_stream_archives
19
+ test/tmp
20
+ test/version_tmp
21
+ tmp
22
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ Version 1.3.0.20210215
2
+ - Added tracking of token expiration dates to instance vars
3
+ - API request results are now returned as a Hashie::Mash to make 'quotes' or :symbol reference of values indifferent
4
+ - Corrected incorrect date submission in create watchlist operation
5
+
6
+ Verion 1.2.0.20190915
7
+ - (Breaking change) Make get_price_history return datetime stamp as Ruby Time vs milliseconds since epoch
8
+
9
+ Verion 1.1.1.20190915
10
+ - Fix incorrect formatting when using startdate and enddate in get_price_history
11
+
12
+ Version 1.1.0 8/28/19
13
+ - Enhance error messages for rate limit and invalid token
14
+
15
+ Version 1.0 8/27/19
16
+ - Change structure internally to use dependency injection of operations
17
+ - Price history feature
18
+ - Real time quotes feature
19
+ - Added RSpec test coverage
20
+
21
+ Version 0.2.alpha
22
+ - Basic Watchlist retrieval and modification
23
+
24
+ Version 0.1.alpha - 11/29/2018
25
+ - Initial, very basic release
26
+ - Authentication: refresh access token by refresh_token
27
+ - Instruments: get fundamentals
28
+
29
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tdameritrade_api.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 wkotzan
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # TD Ameritrade API gem for Ruby
2
+
3
+ This is a gem for connecting to the OAuth/JSON-based TD Ameritrade Developers API released in 2018. Go to
4
+ https://developer.tdameritrade.com/ for the official documentation and to create your OAuth application.
5
+
6
+ For a gem that allows you to connect to the older version of the TDAmeritrade API, go to
7
+ https://github.com/wakproductions/tdameritrade_api
8
+
9
+ ## Installation
10
+
11
+ In your Gemfile
12
+
13
+ `gem 'tdameritrade-api-ruby', git: 'https://github.com/wakproductions/tdameritrade-api-ruby.git'`
14
+
15
+ ## Authenticating
16
+
17
+ Currently this gem is designed for private app authorization. Read this guide for a general overview of signing in your
18
+ private app: https://developer.tdameritrade.com/content/simple-auth-local-apps
19
+
20
+ The TD Ameritrade API now uses OAuth for authentication. For an introduction to the OAuth flow, [I recommend reading
21
+ this tutorial](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2).
22
+
23
+ I plan on writing more detailed instructions in the file /doc/authentication.md
24
+
25
+ ## Basic Usage
26
+
27
+ ```
28
+ client = TDAmeritrade::Client.new(
29
+ client_id: 'MYCLIENTID@AMER.OAUTHAP',
30
+ redirect_uri: 'http://my-redirect-url',
31
+ refresh_token: 'b6w31RJvP/Cz3MVghpx8S5dzeYVcHygEQHKWYQuI98NGpsMb1j...'
32
+ )
33
+
34
+ client.get_instrument_fundamentals('TWTR')
35
+ #=> {"TWTR"=>
36
+ {"fundamental"=>
37
+ {"symbol"=>"TWTR",
38
+ "high52"=>47.79,
39
+ "low52"=>20.12,
40
+ "dividendAmount"=>0.0,
41
+ "dividendYield"=>0.0,
42
+ "peRatio"=>17.93788,
43
+ "pegRatio"=>0.039026,
44
+ "pbRatio"=>3.85707,
45
+ ...
46
+ ```
47
+
48
+ # Current State of Functionality
49
+
50
+ The official API is documented [here](https://developer.tdameritrade.com/apis). This gem currently implements the
51
+ following functionality. If you would like to expand its functionality, then please submit a pull request.
52
+
53
+ - [ ] Accounts and Trading
54
+ - [x] Authentication
55
+ - [x] Instruments
56
+ - [ ] Market Hours
57
+ - [ ] Movers
58
+ - [ ] Option Chains
59
+ - [x] Price History
60
+ - [ ] Real-time Quotes
61
+ - [ ] Transaction History
62
+ - [ ] User Info and Preferences
63
+ - [x] Watchlist
64
+ - [x] Get Watchlists
65
+ - [x] Replace Watchlist
66
+ - [x] Delete Watchlist
67
+ - [x] Update Watchlist
68
+
69
+ ## Contributions
70
+
71
+ If you would like to make a contribution, please submit a pull request to the original branch. Feel free to email me Winston Kotzan
72
+ at wak@wakproductions.com with any feature requests, bug reports, or feedback.
73
+
74
+ #### Wish List
75
+
76
+ * Test Coverage in RSpec
77
+
78
+ ## Support
79
+
80
+ Please open an issue on Github if you have any problems or questions.
81
+
82
+ ## Release Notes
83
+
84
+ See CHANGELOG.md
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'tdameritrade-api-ruby'
3
+
4
+ # This is just a placeholder
5
+ # task :taskname do
6
+ # # perform logic
7
+ # end
8
+
@@ -0,0 +1 @@
1
+ require 'tdameritrade'
@@ -0,0 +1,9 @@
1
+ require 'httparty'
2
+
3
+ require 'tdameritrade/authentication'
4
+ require 'tdameritrade/client'
5
+ require 'tdameritrade/error'
6
+ require 'tdameritrade/version'
7
+
8
+ module TDAmeritrade
9
+ end
@@ -0,0 +1,65 @@
1
+ require 'tdameritrade/util'
2
+ require 'hashie'
3
+ require 'httparty'
4
+
5
+ module TDAmeritrade
6
+ module Authentication
7
+ include Util
8
+
9
+ attr_reader :client_id, :redirect_uri, :authorization_code, :access_token, :refresh_token,
10
+ :access_token_expires_at, :refresh_token_expires_at
11
+
12
+ # This is the OAuth code retrieved from your browser window, first step in OAuth needed to retrieve the tokens
13
+ def get_access_tokens(authorization_grant_code)
14
+ # headers = { 'Content-Type': 'application/x-www-form-urlencoded' } # turns out didn't need this
15
+ params = {
16
+ 'grant_type': 'authorization_code',
17
+ 'access_type': 'offline',
18
+ 'code': authorization_grant_code,
19
+ 'client_id': client_id,
20
+ 'redirect_uri': redirect_uri
21
+ }
22
+ response = HTTParty.post(
23
+ 'https://api.tdameritrade.com/v1/oauth2/token',
24
+ body: params
25
+ )
26
+
27
+ unless response_success?(response)
28
+ raise TDAmeritrade::Error::TDAmeritradeError.new(
29
+ "Unable to retrieve access tokens from API - #{response.code} - #{response.body}"
30
+ )
31
+ end
32
+
33
+ update_tokens(parse_json_response(response))
34
+ end
35
+
36
+ def get_new_access_token
37
+ params = {
38
+ grant_type: 'refresh_token',
39
+ refresh_token: refresh_token,
40
+ access_type: 'offline',
41
+ client_id: client_id,
42
+ redirect_uri: redirect_uri
43
+ }
44
+
45
+ response = HTTParty.post(
46
+ 'https://api.tdameritrade.com/v1/oauth2/token',
47
+ body: params
48
+ )
49
+
50
+ update_tokens(parse_json_response(response))
51
+ end
52
+ alias :refresh_access_token :get_new_access_token
53
+
54
+ def update_tokens(args={})
55
+ gem_error(args[:error]) if args.has_key?(:error)
56
+
57
+ @access_token = args[:access_token]
58
+ @refresh_token = args[:refresh_token]
59
+ @access_token_expires_at = Time.now + (args[:expires_in] || 0)
60
+ @refresh_token_expires_at = Time.now + (args[:refresh_token_expires_in] || 0)
61
+
62
+ args
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,56 @@
1
+ require 'tdameritrade/authentication'
2
+ require 'tdameritrade/client'
3
+ require 'tdameritrade/error'
4
+ require 'tdameritrade/version'
5
+ require 'tdameritrade/operations/create_watchlist'
6
+ require 'tdameritrade/operations/get_instrument_fundamentals'
7
+ require 'tdameritrade/operations/get_price_history'
8
+ require 'tdameritrade/operations/get_quotes'
9
+ require 'tdameritrade/operations/get_watchlists'
10
+ require 'tdameritrade/operations/replace_watchlist'
11
+ require 'tdameritrade/operations/update_watchlist'
12
+
13
+ module TDAmeritrade
14
+ class Client
15
+ include TDAmeritrade::Authentication
16
+ include TDAmeritrade::Error
17
+
18
+ def initialize(**args)
19
+ @access_token = args[:access_token]
20
+ @refresh_token = args[:refresh_token]
21
+ @access_token_expires_at = args[:access_token_expires_at]
22
+ @refresh_token_expires_at = args[:refresh_token_expires_at]
23
+ @client_id = args[:client_id] || Error.gem_error('client_id is required!')
24
+ @redirect_uri = args[:redirect_uri] || Error.gem_error('redirect_uri is required!')
25
+ end
26
+
27
+ def get_instrument_fundamentals(symbol)
28
+ Operations::GetInstrumentFundamentals.new(self).call(symbol)
29
+ end
30
+
31
+ def get_price_history(symbol, **options)
32
+ Operations::GetPriceHistory.new(self).call(symbol, options)
33
+ end
34
+
35
+ def get_quotes(symbols)
36
+ Operations::GetQuotes.new(self).call(symbols: symbols)
37
+ end
38
+
39
+ def create_watchlist(account_id, watchlist_name, symbols)
40
+ Operations::CreateWatchlist.new(self).call(account_id, watchlist_name, symbols)
41
+ end
42
+
43
+ def get_watchlists(account_id)
44
+ Operations::GetWatchlists.new(self).call(account_id: account_id)
45
+ end
46
+
47
+ def replace_watchlist(account_id, watchlist_id, watchlist_name, symbols_to_add=[])
48
+ Operations::ReplaceWatchlist.new(self).call(account_id, watchlist_id, watchlist_name, symbols_to_add)
49
+ end
50
+
51
+ def update_watchlist(account_id, watchlist_id, watchlist_name, symbols_to_add=[])
52
+ Operations::UpdateWatchlist.new(self).call(account_id, watchlist_id, watchlist_name, symbols_to_add)
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,20 @@
1
+ module TDAmeritrade
2
+ module Error
3
+ module_function
4
+
5
+ class TDAmeritradeError < StandardError
6
+ end
7
+
8
+ class RateLimitError < StandardError
9
+ end
10
+
11
+ class NotAuthorizedError < StandardError
12
+ end
13
+
14
+ def gem_error(message)
15
+ error = TDAmeritradeError.new(message)
16
+ error.set_backtrace(caller)
17
+ raise error
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ require 'tdameritrade/error'
2
+ require 'tdameritrade/util'
3
+
4
+ module TDAmeritrade
5
+ module Operations
6
+ class BaseOperation
7
+ include Util
8
+
9
+ HTTP_DEBUG_OUTPUT=ENV['DEBUG_OUTPUT'] # to make live testing easier
10
+
11
+ attr_reader :client
12
+
13
+ def initialize(client)
14
+ @client = client # inject dependency of client credentials
15
+ end
16
+
17
+ private
18
+
19
+ def debug_output?
20
+ HTTP_DEBUG_OUTPUT.to_s == 'true'
21
+ end
22
+
23
+ def perform_api_get_request(url: , query: nil)
24
+ options = { headers: { 'Authorization': "Bearer #{client.access_token}" } }
25
+ options.merge!(query: query) if query
26
+ options.merge!(debug_output: $stdout) if debug_output?
27
+
28
+ HTTParty.get(
29
+ url,
30
+ options
31
+ )
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ require 'tdameritrade/operations/base_operation'
2
+ require 'tdameritrade/operations/support/build_watchlist_items'
3
+
4
+ module TDAmeritrade; module Operations
5
+ class CreateWatchlist < BaseOperation
6
+ include Support::BuildWatchlistItems
7
+
8
+ def call(account_id, watchlist_name, symbols)
9
+ body = {
10
+ "name": watchlist_name,
11
+ "watchlistItems": build_watchlist_items(symbols)
12
+ }.to_json
13
+ uri = URI("https://api.tdameritrade.com/v1/accounts/#{account_id}/watchlists")
14
+ request = Net::HTTP::Post.new(
15
+ uri.path,
16
+ 'authorization' => "Bearer #{client.access_token}",
17
+ 'content-type' => 'application/json'
18
+ )
19
+ request.body = body
20
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
21
+
22
+ if response_success?(response)
23
+ true
24
+ else
25
+ parse_api_response(response)
26
+ end
27
+ end
28
+
29
+ end
30
+ end; end
@@ -0,0 +1,22 @@
1
+ require 'tdameritrade/operations/base_operation'
2
+
3
+ module TDAmeritrade; module Operations
4
+ class GetInstrumentFundamentals < BaseOperation
5
+
6
+ def call(symbol)
7
+ params = {
8
+ apikey: client.client_id,
9
+ symbol: symbol,
10
+ projection: 'fundamental'
11
+ }
12
+
13
+ response = perform_api_get_request(
14
+ url: 'https://api.tdameritrade.com/v1/instruments',
15
+ query: params,
16
+ )
17
+
18
+ parse_api_response(response)
19
+ end
20
+
21
+ end
22
+ end; end