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
@@ -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