tdameritrade-api-ruby 1.3.0.20210215

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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