questrade_api 0.0.1 → 0.0.2
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 +4 -4
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/.yardopts +9 -0
- data/Gemfile +15 -0
- data/Guardfile +6 -0
- data/{LICENSE.txt → LICENSE} +0 -0
- data/README.md +31 -45
- data/Rakefile +4 -0
- data/lib/questrade_api/authorization.rb +94 -0
- data/lib/questrade_api/client.rb +87 -0
- data/lib/questrade_api/modules/util.rb +22 -0
- data/lib/questrade_api/rest/account.rb +101 -0
- data/lib/questrade_api/rest/activity.rb +58 -0
- data/lib/questrade_api/rest/balance.rb +81 -0
- data/lib/questrade_api/rest/base.rb +88 -0
- data/lib/questrade_api/rest/execution.rb +59 -0
- data/lib/questrade_api/rest/market.rb +42 -0
- data/lib/questrade_api/rest/order.rb +58 -0
- data/lib/questrade_api/rest/position.rb +48 -0
- data/lib/questrade_api/rest/time.rb +26 -0
- data/lib/questrade_api/version.rb +1 -1
- data/questrade_api.gemspec +2 -3
- data/spec/fixtures/json/accounts.json +21 -0
- data/spec/fixtures/json/activities.json +36 -0
- data/spec/fixtures/json/balances.json +55 -0
- data/spec/fixtures/json/executions.json +46 -0
- data/spec/fixtures/json/markets.json +34 -0
- data/spec/fixtures/json/orders.json +49 -0
- data/spec/fixtures/json/positions.json +17 -0
- data/spec/fixtures/json/time.json +3 -0
- data/spec/questrade_api/authorization_spec.rb +74 -0
- data/spec/questrade_api/client_spec.rb +38 -0
- data/spec/questrade_api/rest/account_spec.rb +88 -0
- data/spec/questrade_api/rest/activity_spec.rb +70 -0
- data/spec/questrade_api/rest/balance_spec.rb +34 -0
- data/spec/questrade_api/rest/execution_spec.rb +80 -0
- data/spec/questrade_api/rest/market_spec.rb +63 -0
- data/spec/questrade_api/rest/order_spec.rb +95 -0
- data/spec/questrade_api/rest/position_spec.rb +47 -0
- data/spec/questrade_api/rest/time_spec.rb +34 -0
- data/spec/spec_helper.rb +107 -0
- data/spec/support/json_fixtures.rb +21 -0
- metadata +61 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49d2e66e9476e189911591ed7fac0b63134b2fce
|
4
|
+
data.tar.gz: c941e8f74073691030eaed6bd8066c1af859b88b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7895d0c76f3515bad23d095557f010f0d1d8528a660557335a7b05e2c3aeec99baa586f4f6685ee59f93c8dc2987fa56248ab29bc2c9246fc8066ae29302dd61
|
7
|
+
data.tar.gz: edf1116607c99e92f40e7618809eb5ee7d4995e2d5c651001ce3649f017892bcf04de3baf59ad467d358befcb27cdd1fd338e62d008dd8723557dd86123bc922
|
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,19 @@
|
|
1
1
|
source 'http://rubygems.org'
|
2
2
|
|
3
|
+
group :docs do
|
4
|
+
gem 'yard'
|
5
|
+
gem "redcarpet"
|
6
|
+
gem 'github-markup'
|
7
|
+
end
|
8
|
+
|
9
|
+
group :test do
|
10
|
+
gem 'guard'
|
11
|
+
gem 'guard-rspec'
|
12
|
+
gem 'bundler', '~> 1.14'
|
13
|
+
end
|
14
|
+
|
15
|
+
gem 'rake'
|
16
|
+
|
3
17
|
# Specify your gem's dependencies in questrade_api.gemspec
|
4
18
|
gemspec
|
19
|
+
|
data/Guardfile
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
2
|
+
watch(%r{^spec/.+_spec\.rb$})
|
3
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
4
|
+
watch(%r{^lib/questrade_api/rest/(.+)\.rb$}) { |m| "spec/questrade_api/rest/#{m[1]}_spec.rb" }
|
5
|
+
watch('spec/spec_helper.rb') { "spec" }
|
6
|
+
end
|
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,37 +1,32 @@
|
|
1
|
+
[](https://travis-ci.org/brunomeira/questrade_api)
|
2
|
+
[](https://codeclimate.com/github/brunomeira/questrade_api)
|
3
|
+
|
1
4
|
# QuestradeApi
|
2
5
|
|
3
6
|
A Ruby interface to use the [Questrade API](http://www.questrade.com/api).
|
4
7
|
|
5
8
|
## Quick Start
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
2. Add the following line to your Gemfile
|
10
|
+
- Create a Questrade Demo Account on: <http://www.questrade.com/api/free-practice-account>
|
11
|
+
- Add the following line to your Gemfile
|
10
12
|
|
11
13
|
```
|
12
14
|
gem 'questrade_api'
|
13
|
-
|
14
15
|
```
|
15
|
-
|
16
|
-
3. Run Bundle to install gem
|
16
|
+
- Run Bundle to install gem
|
17
17
|
|
18
18
|
```
|
19
19
|
$ bundle
|
20
|
-
|
21
20
|
```
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
5. Copy the snippet of code below to your application, and replace the 'XXXX' token with the token generated on step 4.
|
21
|
+
- Follow the tutorial on <http://www.questrade.com/api/documentation/getting-started> to generate a refresh token
|
22
|
+
- Copy the snippet of code below to your application, and replace the 'XXXX' token with the token generated in the previous step.
|
26
23
|
|
27
24
|
```ruby
|
28
25
|
# By default this API calls the practice(demo) endpoint.
|
29
26
|
# Check our documentation to learn how to call the live endpoint.
|
30
27
|
client = QuestradeApi::Client.new(refresh_token: 'XXXX')
|
31
|
-
|
32
28
|
```
|
33
|
-
|
34
|
-
6. That's all you need to access the API. A few examples of what you can do with it:
|
29
|
+
- That's all you need to access the API. A few examples of what you can do with it:
|
35
30
|
|
36
31
|
```ruby
|
37
32
|
# Get Questrade's current server time
|
@@ -53,57 +48,48 @@ client.activities('account_id', startTime: DateTime.yesterday.to_s, endTime: Dat
|
|
53
48
|
# authorization can be any object that responds to url and access_token
|
54
49
|
authorization = QuestradeApi::Authorization.new(access_token: 'access_token', api_server: 'url')
|
55
50
|
accounts = QuestradeApi::REST::Account.all(accounts)
|
56
|
-
|
57
51
|
```
|
58
|
-
|
59
|
-
For more advanced options, check out our [documentation](http://www.google.com).
|
52
|
+
For more advanced options, check out our [documentation](http://www.rubydoc.info/gems/questrade_api).
|
60
53
|
|
61
54
|
## Current Status
|
62
55
|
|
63
56
|
This Project is under development and some endpoints are still not accessible through the gem.
|
64
57
|
Check the tables below for more details.
|
65
58
|
|
66
|
-
<dl>
|
67
|
-
<dt>LEGEND</dt>
|
68
|
-
|
69
|
-
<dd>:green_heart: = Available</dd>
|
70
|
-
<dd>:heart: = Not Available</dd>
|
71
|
-
</dl>
|
72
|
-
|
73
59
|
### Account Calls
|
74
60
|
|
75
61
|
| Endpoint | Development | Documentation |
|
76
62
|
| --- | --- | --- |
|
77
|
-
| /time |
|
78
|
-
| /accounts |
|
79
|
-
| /accounts/:id/positions |
|
80
|
-
| /accounts/:id/balances |
|
81
|
-
| /accounts/:id/executions |
|
82
|
-
| /accounts/:id/orders |
|
83
|
-
| /accounts/:id/activities |
|
63
|
+
| /time |DONE | DONE |
|
64
|
+
| /accounts |DONE | DONE |
|
65
|
+
| /accounts/:id/positions |DONE | DONE |
|
66
|
+
| /accounts/:id/balances |DONE | DONE |
|
67
|
+
| /accounts/:id/executions |DONE | DONE |
|
68
|
+
| /accounts/:id/orders |DONE | DONE |
|
69
|
+
| /accounts/:id/activities |DONE | DONE |
|
84
70
|
|
85
71
|
### Market Calls
|
86
72
|
|
87
73
|
| Endpoint | Development | Documentation |
|
88
74
|
| --- | --- | --- |
|
89
|
-
| /symbols/:id |
|
90
|
-
| /symbols/:search |
|
91
|
-
| /symbols/:id/options |
|
92
|
-
| /markets |
|
93
|
-
| /markets/quotes/:id |
|
94
|
-
| /markets/quotes/options |
|
95
|
-
| /markets/quotes/strategies |
|
96
|
-
| /markets/candles/:id |
|
75
|
+
| /symbols/:id | | |
|
76
|
+
| /symbols/:search | | |
|
77
|
+
| /symbols/:id/options | | |
|
78
|
+
| /markets |DONE| |
|
79
|
+
| /markets/quotes/:id | | |
|
80
|
+
| /markets/quotes/options | | |
|
81
|
+
| /markets/quotes/strategies | | |
|
82
|
+
| /markets/candles/:id | | |
|
97
83
|
|
98
84
|
### Order Calls
|
99
85
|
|
100
86
|
| Endpoint | Development | Documentation |
|
101
87
|
| --- | --- | --- |
|
102
|
-
| POST accounts/:id/orders |
|
103
|
-
| POST accounts/:id/orders/impact |
|
104
|
-
| DELETE accounts/:id/orders |
|
105
|
-
| POST accounts/:id/orders/brackets |
|
106
|
-
| POST accounts/:id/orders/strategy |
|
88
|
+
| POST accounts/:id/orders | | |
|
89
|
+
| POST accounts/:id/orders/impact | | |
|
90
|
+
| DELETE accounts/:id/orders | | |
|
91
|
+
| POST accounts/:id/orders/brackets | | |
|
92
|
+
| POST accounts/:id/orders/strategy | | |
|
107
93
|
|
108
94
|
## Contributing
|
109
95
|
|
@@ -123,4 +109,4 @@ We are not responsible for any damages, capital losses, or any claim caused by t
|
|
123
109
|
|
124
110
|
USE IT AT YOUR OWN RISK.
|
125
111
|
|
126
|
-
[LICENSE]: LICENSE
|
112
|
+
[LICENSE]: https://github.com/brunomeira/questrade_api/blob/master/LICENSE
|
data/Rakefile
CHANGED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'questrade_api/modules/util'
|
5
|
+
|
6
|
+
module QuestradeApi
|
7
|
+
# @author Bruno Meira <goesmeira@gmail.com>
|
8
|
+
class Authorization
|
9
|
+
include QuestradeApi::Util
|
10
|
+
|
11
|
+
MODE = %i(live practice).freeze
|
12
|
+
attr_reader :mode, :data, :connection
|
13
|
+
|
14
|
+
# Initialize an instance of QuestradeApi::Client.
|
15
|
+
#
|
16
|
+
# @note Only access_token, api_server, and refresh_token are needed for this gem.
|
17
|
+
#
|
18
|
+
# @param params [Hash] for Questrade authorization.
|
19
|
+
# @option params [String] :access_token Access token used to access API.
|
20
|
+
# @option params [String] :api_server Endpoint used to access API. Example: 'https://apiXX.iq.questrade.com/', where X is a number.
|
21
|
+
# @option params [Integer] :expires_in How much time the access token is valid.
|
22
|
+
# @option params [String] :refresh_token Token used to get a new access token.
|
23
|
+
# @option params [String] :token_type Token type.
|
24
|
+
#
|
25
|
+
# @param mode [Symbol] accessed on Questrade, `:live` or `:practice`
|
26
|
+
def initialize(params, mode = :practice)
|
27
|
+
raise 'Mode must be :live or :practice' unless MODE.include?(mode)
|
28
|
+
|
29
|
+
@mode = mode
|
30
|
+
@connection = build_connection
|
31
|
+
|
32
|
+
build_data(params)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Uses refresh_token to fetch a new valid access token.
|
36
|
+
#
|
37
|
+
# @note #data will be populated accordingly, if call is successful.
|
38
|
+
#
|
39
|
+
# @return The result of the call.
|
40
|
+
def refresh_token
|
41
|
+
response = connection.get do |req|
|
42
|
+
req.params[:grant_type] = 'refresh_token'
|
43
|
+
req.params[:refresh_token] = data.refresh_token
|
44
|
+
end
|
45
|
+
|
46
|
+
if response.status == 200
|
47
|
+
raw_body = JSON.parse(response.body)
|
48
|
+
build_data(raw_body)
|
49
|
+
end
|
50
|
+
|
51
|
+
response
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the authorized access token.
|
55
|
+
def access_token
|
56
|
+
data.access_token
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the server associated with the authorized access token.
|
60
|
+
def url
|
61
|
+
data.api_server
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks if selected mode is live.
|
65
|
+
#
|
66
|
+
# @return [Boolean]
|
67
|
+
def live?
|
68
|
+
mode == :live
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def login_url
|
74
|
+
if live?
|
75
|
+
'https://login.questrade.com/oauth2/token'
|
76
|
+
else
|
77
|
+
'https://practicelogin.questrade.com/oauth2/token'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_data(data)
|
82
|
+
hash = hash_to_snakecase(data)
|
83
|
+
@data = OpenStruct.new(hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_connection
|
87
|
+
Faraday.new(login_url) do |faraday|
|
88
|
+
faraday.response :logger
|
89
|
+
faraday.adapter Faraday.default_adapter
|
90
|
+
faraday.headers['Content-Type'] = 'application/json'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'questrade_api/authorization'
|
2
|
+
require 'questrade_api/rest/time'
|
3
|
+
require 'questrade_api/rest/account'
|
4
|
+
require 'questrade_api/rest/balance'
|
5
|
+
require 'questrade_api/rest/position'
|
6
|
+
require 'questrade_api/rest/execution'
|
7
|
+
require 'questrade_api/rest/activity'
|
8
|
+
require 'questrade_api/rest/order'
|
9
|
+
|
10
|
+
require 'questrade_api/rest/market'
|
11
|
+
|
12
|
+
module QuestradeApi
|
13
|
+
# @author Bruno Meira <goesmeira@gmail.com>
|
14
|
+
class Client
|
15
|
+
attr_reader :authorization
|
16
|
+
|
17
|
+
# @see QuestradeApi::Client#initialize for more details
|
18
|
+
def initialize(params = {}, mode = :practice)
|
19
|
+
@authorization = QuestradeApi::Authorization.new(params, mode)
|
20
|
+
refresh_token if refresh_token?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Fetches a new access_token. (see QuestradeApi::Authorization#refresh_token)
|
24
|
+
def refresh_token
|
25
|
+
@authorization.refresh_token
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fetch current server time.
|
29
|
+
#
|
30
|
+
# @return [DateTime] if no issues to call /time endpoint occurs.
|
31
|
+
# @return [nil] if current server time cannot be fetched.
|
32
|
+
def time
|
33
|
+
time = QuestradeApi::REST::Time.new(@authorization)
|
34
|
+
time.get
|
35
|
+
|
36
|
+
time.data && time.data.time
|
37
|
+
end
|
38
|
+
|
39
|
+
# Fetch all accounts associated with user.
|
40
|
+
#
|
41
|
+
# @return [Array<QuestradeApi::REST::Account>]
|
42
|
+
def accounts
|
43
|
+
QuestradeApi::REST::Account.all(@authorization).accounts
|
44
|
+
end
|
45
|
+
|
46
|
+
# Fetch all positions associated with account.
|
47
|
+
#
|
48
|
+
# @param account_id [String] to which positions will be fetched.
|
49
|
+
#
|
50
|
+
# @return [Array<QuestradeApi::REST::Position>]
|
51
|
+
def positions(account_id)
|
52
|
+
QuestradeApi::REST::Position.all(@authorization, account_id).positions
|
53
|
+
end
|
54
|
+
|
55
|
+
# Fetch all balances associated with account.
|
56
|
+
#
|
57
|
+
# @param account_id [String] to which balances will be fetched.
|
58
|
+
#
|
59
|
+
# @return [OpenStruct(per_currency_balances)]
|
60
|
+
def balances(account_id)
|
61
|
+
QuestradeApi::REST::Balance.all(@authorization, account_id)
|
62
|
+
end
|
63
|
+
|
64
|
+
def executions(account_id, params = {})
|
65
|
+
QuestradeApi::REST::Execution.all(@authorization, account_id, params)
|
66
|
+
end
|
67
|
+
|
68
|
+
def activities(account_id, params = {})
|
69
|
+
QuestradeApi::REST::Activity.all(@authorization, account_id, params)
|
70
|
+
end
|
71
|
+
|
72
|
+
def orders(account_id, params = {})
|
73
|
+
QuestradeApi::REST::Activity.all(@authorization, account_id, params)
|
74
|
+
end
|
75
|
+
|
76
|
+
def markets
|
77
|
+
QuestradeApi::REST::Market.all(@authorization)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def refresh_token?
|
83
|
+
data = @authorization.data
|
84
|
+
data.refresh_token && !data.api_server && !data.access_token
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module QuestradeApi
|
2
|
+
module Util
|
3
|
+
private
|
4
|
+
|
5
|
+
def hash_to_snakecase(hash)
|
6
|
+
values = hash.map do |k, v|
|
7
|
+
[underscore(k.to_s).to_sym, v]
|
8
|
+
end
|
9
|
+
|
10
|
+
Hash[values]
|
11
|
+
end
|
12
|
+
|
13
|
+
def underscore(camel_cased_word)
|
14
|
+
camel_cased_word
|
15
|
+
.gsub(/::/, '/')
|
16
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
17
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
18
|
+
.tr('-', '_')
|
19
|
+
.downcase
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'questrade_api/modules/util'
|
2
|
+
|
3
|
+
require 'questrade_api/rest/base'
|
4
|
+
require 'questrade_api/rest/activity'
|
5
|
+
require 'questrade_api/rest/position'
|
6
|
+
require 'questrade_api/rest/balance'
|
7
|
+
require 'questrade_api/rest/execution'
|
8
|
+
|
9
|
+
module QuestradeApi
|
10
|
+
module REST
|
11
|
+
# @author Bruno Meira <goesmeira@gmail.com>
|
12
|
+
class Account < QuestradeApi::REST::Base
|
13
|
+
attr_accessor :id, :user_id
|
14
|
+
|
15
|
+
def initialize(authorization, params)
|
16
|
+
super(authorization)
|
17
|
+
|
18
|
+
@id = params[:id]
|
19
|
+
@user_id = params[:user_id]
|
20
|
+
|
21
|
+
@raw_body = params[:data]
|
22
|
+
build_data(params[:data]) if @raw_body
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Fetch activities associated with account.
|
27
|
+
#
|
28
|
+
# @param see QuestradeApi::REST::Activity.all
|
29
|
+
#
|
30
|
+
# @return [OpenStruct(executions: [QuestradeApi::REST::Activity)]
|
31
|
+
def activities(params)
|
32
|
+
QuestradeApi::REST::Activity.all(authorization, id, params)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Fetch executions associated with account.
|
36
|
+
#
|
37
|
+
# @param see QuestradeApi::REST::Execution.all
|
38
|
+
#
|
39
|
+
# @return [OpenStruct(executions: [QuestradeApi::REST::Execution)]
|
40
|
+
def executions(params)
|
41
|
+
QuestradeApi::REST::Execution.all(authorization, id, params)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Fetch balances associated with account.
|
45
|
+
#
|
46
|
+
# @return [OpenStruct(per_currency_balances)]
|
47
|
+
def balances
|
48
|
+
QuestradeApi::REST::Balance.all(authorization, id)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fetch positions associated with account.
|
52
|
+
#
|
53
|
+
# @return [OpenStruct(positions: [QuestradeApi::REST::Position])]
|
54
|
+
def positions
|
55
|
+
QuestradeApi::REST::Position.all(authorization, id)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Fetch accounts for specific authorized user.
|
59
|
+
#
|
60
|
+
# @param authorization is any object that
|
61
|
+
# responds to #access_token and #url
|
62
|
+
#
|
63
|
+
# @return [OpenStruct(accounts: [QuestradeApi::REST::Account])] if call to server is successful
|
64
|
+
# @return [Faraday::Response] if call to server is not successful
|
65
|
+
def self.all(authorization)
|
66
|
+
response = super(access_token: authorization.access_token,
|
67
|
+
endpoint: endpoint,
|
68
|
+
url: authorization.url)
|
69
|
+
|
70
|
+
result = OpenStruct.new(accounts: [])
|
71
|
+
|
72
|
+
if response.status == 200
|
73
|
+
result.accounts = parse_accounts(authorization, response.body)
|
74
|
+
response = result
|
75
|
+
end
|
76
|
+
|
77
|
+
response
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.endpoint
|
81
|
+
"#{BASE_ENDPOINT}/accounts"
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.parse_accounts(authorization, body)
|
85
|
+
raw = JSON.parse(body)
|
86
|
+
clients = []
|
87
|
+
|
88
|
+
raw['accounts'].each do |client|
|
89
|
+
clients << new(authorization,
|
90
|
+
id: client['number'],
|
91
|
+
user_id: raw['userId'],
|
92
|
+
data: client)
|
93
|
+
end
|
94
|
+
|
95
|
+
clients
|
96
|
+
end
|
97
|
+
|
98
|
+
private_class_method :parse_accounts
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|