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.
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
@@ -1,3 +1,3 @@
1
1
  module QuestradeApi
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.0.2'.freeze
3
3
  end
@@ -18,9 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.14"
22
- spec.add_development_dependency "guard"
23
- spec.add_development_dependency "guard-rspec"
21
+ spec.required_ruby_version = '>= 2.0.0'
22
+
24
23
  spec.add_development_dependency 'rspec', '~> 3.5'
25
24
  spec.add_development_dependency 'webmock', '~> 2.3'
26
25
  spec.add_development_dependency 'rubocop', '~> 0.47'
@@ -0,0 +1,21 @@
1
+ {
2
+ "accounts": [
3
+ {
4
+ "type": "Margin",
5
+ "number": "26598145",
6
+ "status": "Active",
7
+ "isPrimary": true,
8
+ "isBilling": true,
9
+ "clientAccountType": "Individual"
10
+ },
11
+ {
12
+ "type": "Margin",
13
+ "number": "11122233",
14
+ "status": "Active",
15
+ "isPrimary": false,
16
+ "isBilling": true,
17
+ "clientAccountType": "Individual"
18
+ }
19
+ ],
20
+ "userId": "123456"
21
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "activities": [
3
+ {
4
+ "tradeDate": "2011-02-16T00:00:00.000000-05:00",
5
+ "transactionDate": "2011-02-16T00:00:00.000000-05:00",
6
+ "settlementDate": "2011-02-16T00:00:00.000000-05:00",
7
+ "action": "",
8
+ "symbol": "",
9
+ "symbolId": 0,
10
+ "description": "INT FR 02/04 THRU02/15@ 4 3/4%BAL 205,006 AVBAL 204,966 ",
11
+ "currency": "USD",
12
+ "quantity": 0,
13
+ "price": 0,
14
+ "grossAmount": 0,
15
+ "commission": 0,
16
+ "netAmount": -320.08,
17
+ "type": "Interest"
18
+ },
19
+ {
20
+ "tradeDate": "2011-01-16T00:00:00.000000-05:00",
21
+ "transactionDate": "2011-01-16T00:00:00.000000-05:00",
22
+ "settlementDate": "2011-01-16T00:00:00.000000-05:00",
23
+ "action": "",
24
+ "symbol": "",
25
+ "symbolId": 0,
26
+ "description": "INT FR 02/04 THRU02/15@ 4 3/4%BAL 205,006 AVBAL 204,966 ",
27
+ "currency": "USD",
28
+ "quantity": 0,
29
+ "price": 0,
30
+ "grossAmount": 0,
31
+ "commission": 0,
32
+ "netAmount": 120.08,
33
+ "type": "Interest"
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "perCurrencyBalances": [
3
+ {
4
+ "currency": "CAD",
5
+ "cash": 243971.7,
6
+ "marketValue": 6017,
7
+ "totalEquity": 249988.7,
8
+ "buyingPower": 496367.2,
9
+ "maintenanceExcess": 248183.6,
10
+ "isRealTime": false
11
+ },
12
+ {
13
+ "currency": "USD",
14
+ "cash": 198259.05,
15
+ "marketValue": 53745,
16
+ "totalEquity": 252004.05,
17
+ "buyingPower": 461013.3,
18
+ "maintenanceExcess": 230506.65,
19
+ "isRealTime": false
20
+ }
21
+ ],
22
+ "combinedBalances": [
23
+ {
24
+ "currency": "CAD",
25
+ "cash": 243971.7,
26
+ "marketValue": 6017,
27
+ "totalEquity": 249988.7,
28
+ "buyingPower": 496367.2,
29
+ "maintenanceExcess": 248183.6,
30
+ "isRealTime": true
31
+ }
32
+ ],
33
+ "sodPerCurrencyBalances": [
34
+ {
35
+ "currency": "USD",
36
+ "cash": 243971.7,
37
+ "marketValue": 6017,
38
+ "totalEquity": 249988.7,
39
+ "buyingPower": 496367.2,
40
+ "maintenanceExcess": 248183.6,
41
+ "isRealTime": false
42
+ }
43
+ ],
44
+ "sodCombinedBalances": [
45
+ {
46
+ "currency": "CAD",
47
+ "cash": 243971.8,
48
+ "marketValue": 6017,
49
+ "totalEquity": 249988.7,
50
+ "buyingPower": 496367.2,
51
+ "maintenanceExcess": 248183.6,
52
+ "isRealTime": false
53
+ }
54
+ ]
55
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "executions": [
3
+ {
4
+ "symbol": "AAPL",
5
+ "symbolId": 8049,
6
+ "quantity": 10,
7
+ "side": "Buy",
8
+ "price": 536.87,
9
+ "id": 53817310,
10
+ "orderId": 177106005,
11
+ "orderChainId": 177106005,
12
+ "exchangeExecId": "XS1771060050147",
13
+ "timestamp": "2014-03-31T13:38:29.000000-04:00",
14
+ "notes": "",
15
+ "venue": "LAMP",
16
+ "totalCost": 5368.7,
17
+ "orderPlacementCommission": 0,
18
+ "commission": 4.95,
19
+ "executionFee": 0,
20
+ "secFee": 0,
21
+ "canadianExecutionFee": 0,
22
+ "parentId": 0
23
+ },
24
+ {
25
+ "symbol": "GOOGL",
26
+ "symbolId": 8048,
27
+ "quantity": 11,
28
+ "side": "Buy",
29
+ "price": 531.87,
30
+ "id": 53817311,
31
+ "orderId": 177106015,
32
+ "orderChainId": 177106015,
33
+ "exchangeExecId": "XS1773060050147",
34
+ "timestamp": "2014-03-31T13:38:29.000000-04:00",
35
+ "notes": "",
36
+ "venue": "LAMP",
37
+ "totalCost": 5268.7,
38
+ "orderPlacementCommission": 0,
39
+ "commission": 2.95,
40
+ "executionFee": 0,
41
+ "secFee": 0,
42
+ "canadianExecutionFee": 0,
43
+ "parentId": 0
44
+ }
45
+ ]
46
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "markets": [
3
+ {
4
+ "name": "TSX",
5
+ "tradingVenues": [
6
+ "TSX",
7
+ "ALPH",
8
+ "CHIC",
9
+ "OMGA",
10
+ "PURE"
11
+ ],
12
+ "defaultTradingVenue": "AUTO",
13
+ "primaryOrderRoutes": [
14
+ "AUTO"
15
+ ],
16
+ "secondaryOrderRoutes": [
17
+ "TSX",
18
+ "AUTO"
19
+ ],
20
+ "level1Feeds": [
21
+ "ALPH",
22
+ "CHIC",
23
+ "OMGA",
24
+ "PURE",
25
+ "TSX"
26
+ ],
27
+ "extendedStartTime": "2014-10-06T07:00:00.000000-04:00",
28
+ "startTime": "2014-10-06T09:30:00.000000-04:00",
29
+ "endTime": "2014-10-06T16:00:00.000000-04:00",
30
+ "currency": "CAD",
31
+ "snapQuotesLimit": 99999
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "orders": [
3
+ {
4
+ "id": 173577870,
5
+ "symbol": "AAPL",
6
+ "symbolId": 8049,
7
+ "totalQuantity": 100,
8
+ "openQuantity": 100,
9
+ "filledQuantity": 0,
10
+ "canceledQuantity": 0,
11
+ "side": "Buy",
12
+ "type": "Limit",
13
+ "limitPrice": 500.95,
14
+ "stopPrice": null,
15
+ "isAllOrNone": false,
16
+ "isAnonymous": false,
17
+ "icebergQty": null,
18
+ "minQuantity": null,
19
+ "avgExecPrice": null,
20
+ "lastExecPrice": null,
21
+ "source": "TradingAPI",
22
+ "timeInForce": "Day",
23
+ "gtdDate": null,
24
+ "state": "Canceled",
25
+ "clientReasonStr": "",
26
+ "chainId": 173577870,
27
+ "creationTime": "2014-10-23T20:03:41.636000-04:00",
28
+ "updateTime": "2014-10-23T20:03:42.890000-04:00",
29
+ "notes": "",
30
+ "primaryRoute": "AUTO",
31
+ "secondaryRoute": "",
32
+ "orderRoute": "LAMP",
33
+ "venueHoldingOrder": "",
34
+ "comissionCharged": 0,
35
+ "exchangeOrderId": "XS173577870",
36
+ "isSignificantShareHolder": false,
37
+ "isInsider": false,
38
+ "isLimitOffsetInDollar": false,
39
+ "userId": 3000124,
40
+ "placementCommission": null,
41
+ "legs": [],
42
+ "strategyType": "SingleLeg",
43
+ "triggerStopPrice": null,
44
+ "orderGroupId": 0,
45
+ "orderClass": null,
46
+ "mainChainId": 0
47
+ }
48
+ ]
49
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "positions": [
3
+ {
4
+ "symbol": "THI.TO",
5
+ "symbolId": 38738 ,
6
+ "openQuantity": 100,
7
+ "currentMarketValue": 6017,
8
+ "currentPrice": 60.17,
9
+ "averageEntryPrice": 60.23,
10
+ "closedPnl": 0,
11
+ "openPnl": -6,
12
+ "totalCost": false,
13
+ "isRealTime": "Individual",
14
+ "isUnderReorg": false
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "time": "2017-01-24T12:14:42.730000-04:00"
3
+ }
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/authorization'
4
+
5
+ describe QuestradeApi::Authorization do
6
+ let(:access_token) { 'XXXX' }
7
+ let(:api_server) { 'http://test.com' }
8
+ let(:expires_in) { 123 }
9
+ let(:refresh_token) { 'AAAAA' }
10
+ let(:token_type) { 'refresh_token' }
11
+ let(:data) do
12
+ {
13
+ access_token: access_token,
14
+ api_server: api_server,
15
+ expires_in: expires_in,
16
+ refresh_token: refresh_token,
17
+ token_type: token_type
18
+ }
19
+ end
20
+
21
+ context '#initialize' do
22
+ it 'only accepts :live or :practice mode' do
23
+ expect { QuestradeApi::Authorization.new(data) }.to_not raise_error
24
+ expect { QuestradeApi::Authorization.new(data, :practice) }.to_not raise_error
25
+ expect { QuestradeApi::Authorization.new(data, :live) }.to_not raise_error
26
+
27
+ expect { QuestradeApi::Authorization.new(data, :other) }.to raise_error('Mode must be :live or :practice')
28
+ end
29
+
30
+ it 'populates attributes accordingly with practice mode as default' do
31
+ subject = QuestradeApi::Authorization.new(data)
32
+
33
+ expect(subject.mode).to eq(:practice)
34
+ expect(subject.connection).to be_a(Faraday::Connection)
35
+
36
+ expect(subject.data.access_token).to eq(access_token)
37
+ expect(subject.data.api_server).to eq(api_server)
38
+ expect(subject.data.expires_in).to eq(expires_in)
39
+ expect(subject.data.refresh_token).to eq(refresh_token)
40
+ expect(subject.data.token_type).to eq(token_type)
41
+ end
42
+ end
43
+
44
+ context '#refresh_token' do
45
+ end
46
+
47
+ context '#access_token' do
48
+ subject { QuestradeApi::Authorization.new(data) }
49
+
50
+ it 'returns value of #data.access_token' do
51
+ expect(subject.access_token).to eq(access_token)
52
+ end
53
+ end
54
+
55
+ context '#url' do
56
+ subject { QuestradeApi::Authorization.new(data) }
57
+
58
+ it 'returns value of #data.api_server' do
59
+ expect(subject.url).to eq(api_server)
60
+ end
61
+ end
62
+
63
+ context '#live?' do
64
+ it 'returns true if live' do
65
+ subject = QuestradeApi::Authorization.new(data, :live)
66
+ expect(subject.live?).to be_truthy
67
+ end
68
+
69
+ it 'returns false if practice' do
70
+ subject = QuestradeApi::Authorization.new(data)
71
+ expect(subject.live?).to be_falsy
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/client'
4
+
5
+ describe QuestradeApi::Client do
6
+ let(:refresh_token) { 'XXXXX' }
7
+ let(:api_server) { 'http://test.com' }
8
+ let(:access_token) { 'ABCDEF' }
9
+
10
+ context '#initialize' do
11
+ it 'refreshes token if refresh_token is present, and api_server and access_token are not' do
12
+ expect_any_instance_of(QuestradeApi::Client).to receive(:refresh_token).once
13
+ QuestradeApi::Client.new(refresh_token: refresh_token)
14
+ end
15
+
16
+ it 'does not refreshes token if refresh_token is present, and api_server and/or access_token are' do
17
+ expect_any_instance_of(QuestradeApi::Client).to receive(:refresh_token).never
18
+ QuestradeApi::Client.new(refresh_token: refresh_token,
19
+ api_server: api_server)
20
+
21
+ QuestradeApi::Client.new(refresh_token: refresh_token,
22
+ access_token: access_token)
23
+
24
+ QuestradeApi::Client.new(refresh_token: refresh_token,
25
+ api_server: api_server,
26
+ access_token: access_token)
27
+ end
28
+ end
29
+
30
+ context '#refresh_token' do
31
+ it 'calls authorization refresh token' do
32
+ client = QuestradeApi::Client.new(refresh_token: refresh_token, access_token: access_token)
33
+ expect(client.authorization).to receive(:refresh_token).once
34
+
35
+ client.refresh_token
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ require 'questrade_api/rest/account'
4
+
5
+ describe QuestradeApi::REST::Account 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
+ context 'instance methods' do
13
+ let(:id) { '123' }
14
+ subject { QuestradeApi::REST::Account.new(authorization, id: id) }
15
+
16
+ it 'returns a list of activities' do
17
+ expect(QuestradeApi::REST::Activity)
18
+ .to(receive(:all).with(authorization, id, {}))
19
+ .once
20
+
21
+ subject.activities({})
22
+ end
23
+
24
+ it 'returns a list of executions' do
25
+ expect(QuestradeApi::REST::Execution)
26
+ .to(receive(:all).with(authorization, id, {}))
27
+ .once
28
+
29
+ subject.executions({})
30
+ end
31
+
32
+ it 'returns a list of positions' do
33
+ expect(QuestradeApi::REST::Position)
34
+ .to(receive(:all).with(authorization, id))
35
+ .once
36
+
37
+ subject.positions
38
+ end
39
+
40
+ it 'returns a list of balances' do
41
+ expect(QuestradeApi::REST::Balance)
42
+ .to(receive(:all).with(authorization, id))
43
+ .once
44
+
45
+ subject.balances
46
+ end
47
+ end
48
+
49
+ context '.all' do
50
+ it "returns an object that contains a list of all user's accounts" do
51
+ full_url = url + QuestradeApi::REST::Account.endpoint
52
+ stub_request(:get, full_url).to_return(status: 200,
53
+ body: json_string('accounts.json'),
54
+ headers: {})
55
+
56
+ response = QuestradeApi::REST::Account.all(authorization)
57
+
58
+ expect(response.accounts.size).to be(2)
59
+
60
+ first_account = response.accounts.first
61
+ expect(first_account.id).to eq('26598145')
62
+ expect(first_account.user_id).to eq('123456')
63
+ expect(first_account.data.to_h).to eq(type:'Margin',
64
+ number: '26598145',
65
+ status: 'Active',
66
+ is_primary: true,
67
+ is_billing: true,
68
+ client_account_type: 'Individual')
69
+
70
+ second_account = response.accounts.last
71
+ expect(second_account.id).to eq('11122233')
72
+ expect(second_account.user_id).to eq('123456')
73
+ expect(second_account.data.to_h).to eq(type:'Margin',
74
+ number: '11122233',
75
+ status: 'Active',
76
+ is_primary: false,
77
+ is_billing: true,
78
+ client_account_type: 'Individual')
79
+ end
80
+ end
81
+
82
+ context '.endpoint' do
83
+ it 'calls right endpoint' do
84
+ url = '/v1/accounts'
85
+ expect(QuestradeApi::REST::Account.endpoint).to eq(url)
86
+ end
87
+ end
88
+ end