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.
- 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
data/questrade_api.gemspec
CHANGED
@@ -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.
|
22
|
-
|
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,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
|