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
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/rest/activity'
4
+
5
+ describe QuestradeApi::REST::Activity do
6
+ include JSONFixtures
7
+
8
+ let(:access_token) { 'XXXX' }
9
+ let(:account_id) { '123456' }
10
+ let(:url) { 'http://test.com'}
11
+ let(:authorization) { OpenStruct.new(access_token: access_token, url: url) }
12
+
13
+ context '.all' do
14
+ it "returns an object that contains a list of all user's activities for the specific period" do
15
+ start_time = DateTime.now.to_s
16
+ end_time = DateTime.now.to_s
17
+ params = "startTime=#{start_time}&endTime=#{end_time}"
18
+ full_url =
19
+ url + QuestradeApi::REST::Activity.endpoint(account_id) + "?#{params}"
20
+ stub_request(:get, full_url).to_return(status: 200, body: json_string('activities.json'))
21
+
22
+ response = QuestradeApi::REST::Activity.all(authorization, account_id,
23
+ { startTime: start_time,
24
+ endTime: end_time })
25
+
26
+ expect(response.activities.size).to be(2)
27
+
28
+ first_activity = response.activities.first
29
+ expect(first_activity.account_id).to eq(account_id)
30
+ expect(first_activity.data.to_h).to eq(trade_date: '2011-02-16T00:00:00.000000-05:00',
31
+ transaction_date: '2011-02-16T00:00:00.000000-05:00',
32
+ settlement_date: '2011-02-16T00:00:00.000000-05:00',
33
+ action: '',
34
+ symbol: '',
35
+ symbol_id: 0,
36
+ description: 'INT FR 02/04 THRU02/15@ 4 3/4%BAL 205,006 AVBAL 204,966 ',
37
+ currency: 'USD',
38
+ quantity: 0,
39
+ price: 0,
40
+ gross_amount: 0,
41
+ commission: 0,
42
+ net_amount: -320.08,
43
+ type: 'Interest')
44
+
45
+ second_activity = response.activities.last
46
+ expect(second_activity.account_id).to eq(account_id)
47
+ expect(second_activity.data.to_h).to eq(trade_date: '2011-01-16T00:00:00.000000-05:00',
48
+ transaction_date: '2011-01-16T00:00:00.000000-05:00',
49
+ settlement_date: '2011-01-16T00:00:00.000000-05:00',
50
+ action: '',
51
+ symbol: '',
52
+ symbol_id: 0,
53
+ description: 'INT FR 02/04 THRU02/15@ 4 3/4%BAL 205,006 AVBAL 204,966 ',
54
+ currency: 'USD',
55
+ quantity: 0,
56
+ price: 0,
57
+ gross_amount: 0,
58
+ commission: 0,
59
+ net_amount: 120.08,
60
+ type: 'Interest')
61
+ end
62
+ end
63
+
64
+ context '.endpoint' do
65
+ it 'calls right endpoint' do
66
+ url = "/v1/accounts/#{account_id}/activities"
67
+ expect(QuestradeApi::REST::Activity.endpoint(account_id)).to eq(url)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/rest/balance'
4
+
5
+ describe QuestradeApi::REST::Balance do
6
+ include JSONFixtures
7
+
8
+ let(:access_token) { 'XXXX' }
9
+ let(:account_id) { '123456' }
10
+ let(:url) { 'http://test.com'}
11
+ let(:authorization) { OpenStruct.new(access_token: access_token, url: url) }
12
+
13
+ context '.all' do
14
+ it "returns an object that contains a list of all user's balances" do
15
+ full_url =
16
+ url + QuestradeApi::REST::Balance.endpoint(account_id)
17
+ stub_request(:get, full_url).to_return(status: 200, body: json_string('balances.json'))
18
+
19
+ response = QuestradeApi::REST::Balance.all(authorization, account_id)
20
+
21
+ expect(response.per_currency_balances.size).to be(2)
22
+ expect(response.combined_balances.size).to be(1)
23
+ expect(response.sod_per_currency_balances.size).to be(1)
24
+ expect(response.sod_combined_balances.size).to be(1)
25
+ end
26
+ end
27
+
28
+ context '.endpoint' do
29
+ it 'calls right endpoint' do
30
+ url = "/v1/accounts/#{account_id}/balances"
31
+ expect(QuestradeApi::REST::Balance.endpoint(account_id)).to eq(url)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/rest/execution'
4
+
5
+ describe QuestradeApi::REST::Execution do
6
+ include JSONFixtures
7
+
8
+ let(:access_token) { 'XXXX' }
9
+ let(:account_id) { '123456' }
10
+ let(:url) { 'http://test.com'}
11
+ let(:authorization) { OpenStruct.new(access_token: access_token, url: url) }
12
+
13
+ context '.all' do
14
+ it "returns an object that contains a list of all user's executions for the specific period" do
15
+ start_time = DateTime.now.to_s
16
+ end_time = DateTime.now.to_s
17
+ params = "startTime=#{start_time}&endTime=#{end_time}"
18
+ full_url =
19
+ url + QuestradeApi::REST::Execution.endpoint(account_id) + "?#{params}"
20
+ stub_request(:get, full_url).to_return(status: 200, body: json_string('executions.json'))
21
+
22
+ response = QuestradeApi::REST::Execution.all(authorization, account_id,
23
+ startTime: start_time,
24
+ endTime: end_time)
25
+
26
+ expect(response.executions.size).to be(2)
27
+
28
+ first_execution = response.executions.first
29
+ expect(first_execution.account_id).to eq(account_id)
30
+ expect(first_execution.data.to_h).to eq(symbol: 'AAPL',
31
+ symbol_id: 8049,
32
+ quantity: 10,
33
+ side: 'Buy',
34
+ price: 536.87,
35
+ id: 53817310,
36
+ order_id: 177106005,
37
+ order_chain_id: 177106005,
38
+ exchange_exec_id: 'XS1771060050147',
39
+ timestamp: '2014-03-31T13:38:29.000000-04:00',
40
+ notes: '',
41
+ venue: 'LAMP',
42
+ total_cost: 5368.7,
43
+ order_placement_commission: 0,
44
+ commission: 4.95,
45
+ execution_fee: 0,
46
+ sec_fee: 0,
47
+ canadian_execution_fee: 0,
48
+ parent_id: 0)
49
+
50
+ second_execution = response.executions.last
51
+ expect(second_execution.account_id).to eq(account_id)
52
+ expect(second_execution.data.to_h).to eq(symbol: 'GOOGL',
53
+ symbol_id: 8048,
54
+ quantity: 11,
55
+ side: 'Buy',
56
+ price: 531.87,
57
+ id: 53817311,
58
+ order_id: 177106015,
59
+ order_chain_id: 177106015,
60
+ exchange_exec_id: 'XS1773060050147',
61
+ timestamp: '2014-03-31T13:38:29.000000-04:00',
62
+ notes: '',
63
+ venue: 'LAMP',
64
+ total_cost: 5268.7,
65
+ order_placement_commission: 0,
66
+ commission: 2.95,
67
+ execution_fee: 0,
68
+ sec_fee: 0,
69
+ canadian_execution_fee: 0,
70
+ parent_id: 0)
71
+ end
72
+ end
73
+
74
+ context '.endpoint' do
75
+ it 'calls right endpoint' do
76
+ url = "/v1/accounts/#{account_id}/executions"
77
+ expect(QuestradeApi::REST::Execution.endpoint(account_id)).to eq(url)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/rest/market'
4
+
5
+ describe QuestradeApi::REST::Market do
6
+ include JSONFixtures
7
+
8
+ let(:access_token) { 'XXXX' }
9
+ let(:account_id) { '123456' }
10
+ let(:url) { 'http://test.com'}
11
+ let(:authorization) { OpenStruct.new(access_token: access_token, url: url) }
12
+
13
+ context '.all' do
14
+ it "returns an object that contains a list of all supported" do
15
+ full_url = url + QuestradeApi::REST::Market.endpoint
16
+
17
+ stub_request(:get, full_url).to_return(status: 200, body: json_string('markets.json'))
18
+
19
+ response = QuestradeApi::REST::Market.all(authorization)
20
+
21
+ expect(response.markets.size).to be(1)
22
+
23
+ first_market = response.markets.first
24
+ expect(first_market.data.to_h).to eq(
25
+ name: "TSX",
26
+ trading_venues: [
27
+ "TSX",
28
+ "ALPH",
29
+ "CHIC",
30
+ "OMGA",
31
+ "PURE"
32
+ ],
33
+ default_trading_venue: "AUTO",
34
+ primary_order_routes: [
35
+ "AUTO"
36
+ ],
37
+ secondary_order_routes: [
38
+ "TSX",
39
+ "AUTO"
40
+ ],
41
+ level1_feeds: [
42
+ "ALPH",
43
+ "CHIC",
44
+ "OMGA",
45
+ "PURE",
46
+ "TSX"
47
+ ],
48
+ extended_start_time: "2014-10-06T07:00:00.000000-04:00",
49
+ start_time: "2014-10-06T09:30:00.000000-04:00",
50
+ end_time: "2014-10-06T16:00:00.000000-04:00",
51
+ currency: "CAD",
52
+ snap_quotes_limit: 99999
53
+ )
54
+ end
55
+ end
56
+
57
+ context '.endpoint' do
58
+ it 'calls right endpoint' do
59
+ url = "/v1/markets"
60
+ expect(QuestradeApi::REST::Market.endpoint).to eq(url)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/rest/order'
4
+
5
+ describe QuestradeApi::REST::Order do
6
+ include JSONFixtures
7
+
8
+ let(:access_token) { 'XXXX' }
9
+ let(:account_id) { '123456' }
10
+ let(:url) { 'http://test.com'}
11
+ let(:authorization) { OpenStruct.new(access_token: access_token, url: url) }
12
+
13
+ context '.all' do
14
+ it "returns an object that contains a list of all user's orders" do
15
+ time = DateTime.now.to_s
16
+ params = "startTime=#{time}&endTime=#{time}&stateFilter=All"
17
+ full_url =
18
+ url + QuestradeApi::REST::Order.endpoint(account_id) + "?#{params}"
19
+
20
+ stub_request(:get, full_url).to_return(status: 200, body: json_string('orders.json'))
21
+
22
+ response = QuestradeApi::REST::Order.all(authorization, account_id,
23
+ startTime: time, endTime: time, stateFilter: 'All')
24
+
25
+ expect(response.orders.size).to be(1)
26
+
27
+ first_order = response.orders.first
28
+ expect(first_order.account_id).to eq(account_id)
29
+ expect(first_order.data.to_h).to eq(
30
+ id: 173577870,
31
+ symbol: 'AAPL',
32
+ symbol_id: 8049,
33
+ total_quantity: 100,
34
+ open_quantity: 100,
35
+ filled_quantity: 0,
36
+ canceled_quantity: 0,
37
+ side: "Buy",
38
+ type: "Limit",
39
+ limit_price: 500.95,
40
+ stop_price: nil,
41
+ is_all_or_none: false,
42
+ is_anonymous: false,
43
+ iceberg_qty: nil,
44
+ min_quantity: nil,
45
+ avg_exec_price: nil,
46
+ last_exec_price: nil,
47
+ source: "TradingAPI",
48
+ time_in_force: "Day",
49
+ gtd_date: nil,
50
+ state: "Canceled",
51
+ client_reason_str: "",
52
+ chain_id: 173577870,
53
+ creation_time: "2014-10-23T20:03:41.636000-04:00",
54
+ update_time: "2014-10-23T20:03:42.890000-04:00",
55
+ notes: "",
56
+ primary_route: "AUTO",
57
+ secondary_route: "",
58
+ order_route: "LAMP",
59
+ venue_holding_order: "",
60
+ comission_charged: 0,
61
+ exchange_order_id: "XS173577870",
62
+ is_significant_share_holder: false,
63
+ is_insider: false,
64
+ is_limit_offset_in_dollar: false,
65
+ user_id: 3000124,
66
+ placement_commission: nil,
67
+ legs: [],
68
+ strategy_type: "SingleLeg",
69
+ trigger_stop_price: nil,
70
+ order_group_id: 0,
71
+ order_class: nil,
72
+ main_chain_id: 0
73
+ )
74
+ end
75
+ end
76
+
77
+ context '#endpoint' do
78
+ let(:id) { '1' }
79
+ subject {
80
+ QuestradeApi::REST::Order.new(authorization, id: '1', account_id: account_id)
81
+ }
82
+
83
+ it 'calls right endpoint' do
84
+ url = "/v1/accounts/#{account_id}/orders/#{id}"
85
+ expect(subject.endpoint).to eq(url)
86
+ end
87
+ end
88
+
89
+ context '.endpoint' do
90
+ it 'calls right endpoint' do
91
+ url = "/v1/accounts/#{account_id}/orders"
92
+ expect(QuestradeApi::REST::Order.endpoint(account_id)).to eq(url)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/rest/position'
4
+
5
+ describe QuestradeApi::REST::Position do
6
+ include JSONFixtures
7
+
8
+ let(:access_token) { 'XXXX' }
9
+ let(:account_id) { '123456' }
10
+ let(:url) { 'http://test.com'}
11
+ let(:authorization) { OpenStruct.new(access_token: access_token, url: url) }
12
+
13
+ context '.all' do
14
+ it "returns an object that contains a list of all user's positions" do
15
+ full_url = url + QuestradeApi::REST::Position.endpoint(account_id)
16
+
17
+ stub_request(:get, full_url).to_return(status: 200, body: json_string('positions.json'))
18
+
19
+ response = QuestradeApi::REST::Position.all(authorization, account_id)
20
+
21
+ expect(response.positions.size).to be(1)
22
+
23
+ first_position = response.positions.first
24
+ expect(first_position.account_id).to eq(account_id)
25
+ expect(first_position.data.to_h).to eq(
26
+ symbol: "THI.TO",
27
+ symbol_id: 38738 ,
28
+ open_quantity: 100,
29
+ current_market_value: 6017,
30
+ current_price: 60.17,
31
+ average_entry_price: 60.23,
32
+ closed_pnl: 0,
33
+ open_pnl: -6,
34
+ total_cost: false,
35
+ is_real_time: "Individual",
36
+ is_under_reorg: false
37
+ )
38
+ end
39
+ end
40
+
41
+ context '.endpoint' do
42
+ it 'calls right endpoint' do
43
+ url = "/v1/accounts/#{account_id}/positions"
44
+ expect(QuestradeApi::REST::Position.endpoint(account_id)).to eq(url)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/rest/time'
4
+
5
+ describe QuestradeApi::REST::Time do
6
+ include JSONFixtures
7
+
8
+ let(:access_token) { 'XXXX' }
9
+ let(:url) { 'http://test.com'}
10
+ let(:authorization) { OpenStruct.new(access_token: access_token, url: url) }
11
+
12
+ subject { QuestradeApi::REST::Time.new(authorization) }
13
+
14
+ context '#get' do
15
+ let(:url) { 'http://test.com/v1/time' }
16
+
17
+ it 'calls the proper endpoint with proper data' do
18
+ expect(subject.data).to be_nil
19
+
20
+ stub_request(:get, url).to_return(status: 200,
21
+ body: json_string('time.json'))
22
+ subject.get
23
+
24
+ expect(subject.data).to_not be_nil
25
+ expect(subject.data.time).to eq('2017-01-24T12:14:42.730000-04:00')
26
+ end
27
+ end
28
+
29
+ context '.endpoint' do
30
+ it 'returns right endpoint' do
31
+ expect(QuestradeApi::REST::Time.endpoint).to eq('/v1/time')
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,107 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+
20
+ require 'webmock/rspec'
21
+ require 'support/json_fixtures'
22
+
23
+ RSpec.configure do |config|
24
+ # rspec-expectations config goes here. You can use an alternate
25
+ # assertion/expectation library such as wrong or the stdlib/minitest
26
+ # assertions if you prefer.
27
+ config.expect_with :rspec do |expectations|
28
+ # This option will default to `true` in RSpec 4. It makes the `description`
29
+ # and `failure_message` of custom matchers include text for helper methods
30
+ # defined using `chain`, e.g.:
31
+ # be_bigger_than(2).and_smaller_than(4).description
32
+ # # => "be bigger than 2 and smaller than 4"
33
+ # ...rather than:
34
+ # # => "be bigger than 2"
35
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
36
+ end
37
+
38
+ # rspec-mocks config goes here. You can use an alternate test double
39
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
40
+ config.mock_with :rspec do |mocks|
41
+ # Prevents you from mocking or stubbing a method that does not exist on
42
+ # a real object. This is generally recommended, and will default to
43
+ # `true` in RSpec 4.
44
+ mocks.verify_partial_doubles = true
45
+ end
46
+
47
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
48
+ # have no way to turn it off -- the option exists only for backwards
49
+ # compatibility in RSpec 3). It causes shared context metadata to be
50
+ # inherited by the metadata hash of host groups and examples, rather than
51
+ # triggering implicit auto-inclusion in groups with matching metadata.
52
+ config.shared_context_metadata_behavior = :apply_to_host_groups
53
+
54
+ # The settings below are suggested to provide a good initial experience
55
+ # with RSpec, but feel free to customize to your heart's content.
56
+ =begin
57
+ # This allows you to limit a spec run to individual examples or groups
58
+ # you care about by tagging them with `:focus` metadata. When nothing
59
+ # is tagged with `:focus`, all examples get run. RSpec also provides
60
+ # aliases for `it`, `describe`, and `context` that include `:focus`
61
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
62
+ config.filter_run_when_matching :focus
63
+
64
+ # Allows RSpec to persist some state between runs in order to support
65
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
66
+ # you configure your source control system to ignore this file.
67
+ config.example_status_persistence_file_path = "spec/examples.txt"
68
+
69
+ # Limits the available syntax to the non-monkey patched syntax that is
70
+ # recommended. For more details, see:
71
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
72
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
73
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
74
+ config.disable_monkey_patching!
75
+
76
+ # This setting enables warnings. It's recommended, but in some cases may
77
+ # be too noisy due to issues in dependencies.
78
+ config.warnings = true
79
+
80
+ # Many RSpec users commonly either run the entire suite or an individual
81
+ # file, and it's useful to allow more verbose output when running an
82
+ # individual spec file.
83
+ if config.files_to_run.one?
84
+ # Use the documentation formatter for detailed output,
85
+ # unless a formatter has already been configured
86
+ # (e.g. via a command-line flag).
87
+ config.default_formatter = 'doc'
88
+ end
89
+
90
+ # Print the 10 slowest examples and example groups at the
91
+ # end of the spec run, to help surface which specs are running
92
+ # particularly slow.
93
+ config.profile_examples = 10
94
+
95
+ # Run specs in random order to surface order dependencies. If you find an
96
+ # order dependency and want to debug it, you can fix the order by providing
97
+ # the seed, which is printed after each run.
98
+ # --seed 1234
99
+ config.order = :random
100
+
101
+ # Seed global randomization in this process using the `--seed` CLI option.
102
+ # Setting this allows you to use `--seed` to deterministically reproduce
103
+ # test failures related to randomization by passing the same `--seed` value
104
+ # as the one that triggered the failure.
105
+ Kernel.srand config.seed
106
+ =end
107
+ end