questrade_api 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/.yardopts +9 -0
  6. data/Gemfile +15 -0
  7. data/Guardfile +6 -0
  8. data/{LICENSE.txt → LICENSE} +0 -0
  9. data/README.md +31 -45
  10. data/Rakefile +4 -0
  11. data/lib/questrade_api/authorization.rb +94 -0
  12. data/lib/questrade_api/client.rb +87 -0
  13. data/lib/questrade_api/modules/util.rb +22 -0
  14. data/lib/questrade_api/rest/account.rb +101 -0
  15. data/lib/questrade_api/rest/activity.rb +58 -0
  16. data/lib/questrade_api/rest/balance.rb +81 -0
  17. data/lib/questrade_api/rest/base.rb +88 -0
  18. data/lib/questrade_api/rest/execution.rb +59 -0
  19. data/lib/questrade_api/rest/market.rb +42 -0
  20. data/lib/questrade_api/rest/order.rb +58 -0
  21. data/lib/questrade_api/rest/position.rb +48 -0
  22. data/lib/questrade_api/rest/time.rb +26 -0
  23. data/lib/questrade_api/version.rb +1 -1
  24. data/questrade_api.gemspec +2 -3
  25. data/spec/fixtures/json/accounts.json +21 -0
  26. data/spec/fixtures/json/activities.json +36 -0
  27. data/spec/fixtures/json/balances.json +55 -0
  28. data/spec/fixtures/json/executions.json +46 -0
  29. data/spec/fixtures/json/markets.json +34 -0
  30. data/spec/fixtures/json/orders.json +49 -0
  31. data/spec/fixtures/json/positions.json +17 -0
  32. data/spec/fixtures/json/time.json +3 -0
  33. data/spec/questrade_api/authorization_spec.rb +74 -0
  34. data/spec/questrade_api/client_spec.rb +38 -0
  35. data/spec/questrade_api/rest/account_spec.rb +88 -0
  36. data/spec/questrade_api/rest/activity_spec.rb +70 -0
  37. data/spec/questrade_api/rest/balance_spec.rb +34 -0
  38. data/spec/questrade_api/rest/execution_spec.rb +80 -0
  39. data/spec/questrade_api/rest/market_spec.rb +63 -0
  40. data/spec/questrade_api/rest/order_spec.rb +95 -0
  41. data/spec/questrade_api/rest/position_spec.rb +47 -0
  42. data/spec/questrade_api/rest/time_spec.rb +34 -0
  43. data/spec/spec_helper.rb +107 -0
  44. data/spec/support/json_fixtures.rb +21 -0
  45. metadata +61 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ded11a2d448dfa40a7da186b112118a97cfbe9d
4
- data.tar.gz: d44c839445d9f7b17435447011269540fa70e9af
3
+ metadata.gz: 49d2e66e9476e189911591ed7fac0b63134b2fce
4
+ data.tar.gz: c941e8f74073691030eaed6bd8066c1af859b88b
5
5
  SHA512:
6
- metadata.gz: 16429ac6177b3a1629e9b60b1e0509e1a93d5e8623ca58d91ddae8edb131b5b93e1d759dc18583a43389d8f0aef92920cdb5765a00e95cf4867b6b5fd4cef01e
7
- data.tar.gz: a7875e088aa7cd1ac4dfe0eb1c5c8e19e6393960687073b0ab1d6b4bb0925ccbca6003be9b2322ae07a400739f354a22d2dfd150f2fccdc3a9e2af3e46f45a8a
6
+ metadata.gz: 7895d0c76f3515bad23d095557f010f0d1d8528a660557335a7b05e2c3aeec99baa586f4f6685ee59f93c8dc2987fa56248ab29bc2c9246fc8066ae29302dd61
7
+ data.tar.gz: edf1116607c99e92f40e7618809eb5ee7d4995e2d5c651001ce3649f017892bcf04de3baf59ad467d358befcb27cdd1fd338e62d008dd8723557dd86123bc922
data/.gitignore CHANGED
@@ -20,3 +20,7 @@ tmp
20
20
  *.o
21
21
  *.a
22
22
  mkmf.log
23
+ *.sw*
24
+ .DS_Store
25
+ .byebug_history
26
+ .vimrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
@@ -0,0 +1,9 @@
1
+ --title "QuestradeApi Documentation"
2
+ --markup-provider redcarpet
3
+ --markup markdown
4
+ --readme README.md
5
+ --protected
6
+ --exclude lib/questrade_api/modules/util.rb
7
+ -
8
+ 'lib/questrade_api/**/*.rb'
9
+ LICENSE
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
+
@@ -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
File without changes
data/README.md CHANGED
@@ -1,37 +1,32 @@
1
+ [![Build Status](https://travis-ci.org/brunomeira/questrade_api.svg?branch=master)](https://travis-ci.org/brunomeira/questrade_api)
2
+ [![Code Climate](https://codeclimate.com/github/brunomeira/questrade_api/badges/gpa.svg)](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
- 1. Create a Questrade Demo Account on: <http://www.questrade.com/api/free-practice-account>
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
- 4. Follow the tutorial on <http://www.questrade.com/api/documentation/getting-started> to generate a refresh token
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 | :green_heart: | :green_heart: |
78
- | /accounts | :green_heart: | :green_heart: |
79
- | /accounts/:id/positions | :green_heart: | :heart: |
80
- | /accounts/:id/balances | :green_heart: | :heart: |
81
- | /accounts/:id/executions | :green_heart: | :heart: |
82
- | /accounts/:id/orders | :green_heart: | :heart: |
83
- | /accounts/:id/activities | :green_heart: | :heart: |
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 | :heart: | :heart: |
90
- | /symbols/:search | :heart: | :heart: |
91
- | /symbols/:id/options | :heart: | :heart: |
92
- | /markets | :green_heart: | :heart: |
93
- | /markets/quotes/:id | :heart: | :heart: |
94
- | /markets/quotes/options | :heart: | :heart: |
95
- | /markets/quotes/strategies | :heart: | :heart: |
96
- | /markets/candles/:id | :heart: | :heart: |
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 | :heart: | :heart: |
103
- | POST accounts/:id/orders/impact | :heart: | :heart: |
104
- | DELETE accounts/:id/orders | :heart: | :heart: |
105
- | POST accounts/:id/orders/brackets | :heart: | :heart: |
106
- | POST accounts/:id/orders/strategy | :heart: | :heart: |
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.txt
112
+ [LICENSE]: https://github.com/brunomeira/questrade_api/blob/master/LICENSE
data/Rakefile CHANGED
@@ -3,3 +3,7 @@ require "bundler/gem_tasks"
3
3
  task :console do
4
4
  exec "irb -r questrade_api -I ./lib"
5
5
  end
6
+
7
+ require 'rspec/core/rake_task'
8
+ task :default => :spec
9
+ RSpec::Core::RakeTask.new
@@ -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