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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +29 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +84 -0
- data/Rakefile +8 -0
- data/lib/tdameritrade-api-ruby.rb +1 -0
- data/lib/tdameritrade.rb +9 -0
- data/lib/tdameritrade/authentication.rb +65 -0
- data/lib/tdameritrade/client.rb +56 -0
- data/lib/tdameritrade/error.rb +20 -0
- data/lib/tdameritrade/operations/base_operation.rb +36 -0
- data/lib/tdameritrade/operations/create_watchlist.rb +30 -0
- data/lib/tdameritrade/operations/get_instrument_fundamentals.rb +22 -0
- data/lib/tdameritrade/operations/get_price_history.rb +55 -0
- data/lib/tdameritrade/operations/get_quotes.rb +21 -0
- data/lib/tdameritrade/operations/get_watchlists.rb +12 -0
- data/lib/tdameritrade/operations/replace_watchlist.rb +33 -0
- data/lib/tdameritrade/operations/support/build_watchlist_items.rb +21 -0
- data/lib/tdameritrade/operations/update_watchlist.rb +33 -0
- data/lib/tdameritrade/util.rb +50 -0
- data/lib/tdameritrade/version.rb +3 -0
- data/spec/spec_helper.rb +110 -0
- data/spec/support/authenticated_client.rb +12 -0
- data/spec/support/spec/mocks/mock_get_instrument_fundamentals.rb +17 -0
- data/spec/support/spec/mocks/mock_get_price_history.rb +17 -0
- data/spec/support/spec/mocks/mock_get_quotes.rb +17 -0
- data/spec/support/spec/mocks/mock_watchlists.rb +43 -0
- data/spec/support/spec/mocks/tdameritrade_api_mock_base.rb +21 -0
- data/spec/support/webmock_off.rb +7 -0
- data/spec/tdameritrade/operations/create_watchlist_spec.rb +42 -0
- data/spec/tdameritrade/operations/error_spec.rb +75 -0
- data/spec/tdameritrade/operations/get_instrument_fundamentals_spec.rb +182 -0
- data/spec/tdameritrade/operations/get_price_history_spec.rb +160 -0
- data/spec/tdameritrade/operations/get_quotes_spec.rb +351 -0
- data/spec/tdameritrade/operations/get_watchlists_spec.rb +80 -0
- data/spec/tdameritrade/operations/replace_watchlist_spec.rb +45 -0
- data/spec/tdameritrade/operations/update_watchlist_spec.rb +45 -0
- data/tdameritrade-api-ruby.gemspec +33 -0
- 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
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
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 @@
|
|
1
|
+
require 'tdameritrade'
|
data/lib/tdameritrade.rb
ADDED
@@ -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
|