degiro_client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: da9cf0fe579f0f1c213a32b17c2901db5381d1d0b8b4098eea76a5df983f8cef
4
+ data.tar.gz: e35647671e6e281746b231a0d0c988b32daa46c22375451a72db36ec1b97d11d
5
+ SHA512:
6
+ metadata.gz: a69fc835f4f1cbb58cdb361a8edfca33eaada0c8db553f6b0a8fb9ff83550451ca5c877d2ee52361ce55bcc3f07eab19b4d8c3819e8848c04a3e97075d9a5c97
7
+ data.tar.gz: decada6030e3f01130769a877266998708b2dbf8452f6c996e7535ce16a72d9b21269242ccd8a6a45e13d3a461a452d88b511ad2516d8b671369cb0d153bbc79
@@ -0,0 +1,36 @@
1
+ require 'forwardable'
2
+
3
+ require_relative 'connection.rb'
4
+ require_relative 'create_order.rb'
5
+ require_relative 'find_product_by_id.rb'
6
+ require_relative 'find_products.rb'
7
+ require_relative 'get_cash_funds.rb'
8
+ require_relative 'get_orders.rb'
9
+ require_relative 'get_portfolio.rb'
10
+ require_relative 'get_transactions.rb'
11
+
12
+ module DeGiro
13
+ class Client
14
+ extend Forwardable
15
+
16
+ def_delegators :@create_order, :create_buy_order, :create_sell_order
17
+ def_delegators :@find_product_by_id, :find_product_by_id
18
+ def_delegators :@find_products, :find_products
19
+ def_delegators :@get_cash_funds, :get_cash_funds
20
+ def_delegators :@get_orders, :get_orders
21
+ def_delegators :@get_portfolio, :get_portfolio
22
+ def_delegators :@get_transactions, :get_transactions
23
+
24
+ def initialize(login:, password:)
25
+ connection = Connection.new(login, password)
26
+
27
+ @create_order = CreateOrder.new(connection)
28
+ @find_product_by_id = FindProductById.new(connection)
29
+ @find_products = FindProducts.new(connection)
30
+ @get_cash_funds = GetCashFunds.new(connection)
31
+ @get_orders = GetOrders.new(connection)
32
+ @get_portfolio = GetPortfolio.new(connection)
33
+ @get_transactions = GetTransactions.new(connection)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,43 @@
1
+ require 'json'
2
+ require 'faraday'
3
+ require 'faraday-cookie_jar'
4
+
5
+ require_relative 'urls_map.rb'
6
+ require_relative 'user_data.rb'
7
+ require_relative 'errors.rb'
8
+
9
+ module DeGiro
10
+ class Connection
11
+ extend Forwardable
12
+
13
+ def_delegators :@conn, :get, :post
14
+ attr_reader :urls_map, :user_data, :session_id
15
+
16
+ BASE_TRADER_URL = 'https://trader.degiro.nl'.freeze
17
+
18
+ def initialize(login, password)
19
+ @conn = Faraday.new(url: BASE_TRADER_URL) do |builder|
20
+ builder.use :cookie_jar
21
+ builder.use Faraday::Response::RaiseError
22
+ builder.adapter Faraday.default_adapter
23
+ end
24
+
25
+ response = @conn.post('/login/secure/login') do |req|
26
+ req.headers['Content-Type'] = 'application/json'
27
+ req.body = {
28
+ username: login,
29
+ password: password,
30
+ isPassCodeReset: false,
31
+ isRedirectToMobile: false,
32
+ queryParams: { reason: 'session_expired' }
33
+ }.to_json
34
+ end
35
+
36
+ @session_id = response.headers['set-cookie'][/JSESSIONID=(.*?);/m, 1]
37
+ raise MissingSessionIdError, 'Could not find valid session id' if @session_id == '' || @session_id.nil?
38
+
39
+ @urls_map = UrlsMap.new(JSON.parse(@conn.get('/login/secure/config').body))
40
+ @user_data = UserData.new(JSON.parse(@conn.get("#{@urls_map['pa_url']}/client?sessionId=#{@session_id}").body)["data"])
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,66 @@
1
+ require 'json'
2
+
3
+ module DeGiro
4
+ class CreateOrder
5
+ BUY_SELL = { buy: 0, sell: 1 }.freeze
6
+ ORDER_TYPES = { limited: 0, stop_limited: 1, market_order: 2, stop_loss: 3 }.freeze
7
+ TIME_TYPES = { day: 1, permanent: 3 }.freeze
8
+
9
+ def initialize(connection)
10
+ @connection = connection
11
+ end
12
+
13
+ def create_buy_order(product_id:, size:, price:)
14
+ order = order(BUY_SELL[:buy], product_id, size, price)
15
+ confirmation_id = JSON.parse(check_order(order).body)['confirmationId']
16
+ confirm_order(order, confirmation_id)
17
+ end
18
+
19
+ def create_sell_order(product_id:, size:, price:)
20
+ order = order(BUY_SELL[:sell], product_id, size, price)
21
+ confirmation_id = JSON.parse(check_order(order).body)['confirmationId']
22
+ confirm_order(order, confirmation_id)
23
+ end
24
+
25
+ private
26
+
27
+ def order(type, product_id, size, price)
28
+ {
29
+ buysell: type,
30
+ orderType: ORDER_TYPES[:limited],
31
+ productId: product_id,
32
+ size: size,
33
+ timeType: TIME_TYPES[:permanent],
34
+ price: price
35
+ }
36
+ end
37
+
38
+ def check_order(order)
39
+ @connection.post(check_order_url) do |req|
40
+ req.headers['Content-Type'] = 'application/json; charset=UTF-8'
41
+ req.body = order.to_json
42
+ end
43
+ end
44
+
45
+ def confirm_order(order, confirmation_id)
46
+ @connection.post(confirm_order_url(confirmation_id)) do |req|
47
+ req.headers['Content-Type'] = 'application/json; charset=UTF-8'
48
+ req.body = order.to_json
49
+ end
50
+ end
51
+
52
+ def check_order_url
53
+ "#{@connection.urls_map['trading_url']}/v5/checkOrder" \
54
+ ";jsessionid=#{@connection.session_id}" \
55
+ "?intAccount=#{@connection.user_data['int_account']}" \
56
+ "&sessionId=#{@connection.session_id}"
57
+ end
58
+
59
+ def confirm_order_url(confirmation_id)
60
+ "#{@connection.urls_map['trading_url']}/v5/order/#{confirmation_id}" \
61
+ ";jsessionid=#{@connection.session_id}" \
62
+ "?intAccount=#{@connection.user_data['int_account']}" \
63
+ "&sessionId=#{@connection.session_id}"
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,9 @@
1
+ module DeGiro
2
+ class MissingSessionIdError < StandardError; end
3
+
4
+ class MissingUrlError < StandardError; end
5
+ class IncorrectUrlError < StandardError; end
6
+
7
+ class MissingUserFieldError < StandardError; end
8
+ class IncorrectUserFieldError < StandardError; end
9
+ end
@@ -0,0 +1,37 @@
1
+ require 'json'
2
+
3
+ module DeGiro
4
+ class FindProductById
5
+ def initialize(connection)
6
+ @connection = connection
7
+ end
8
+
9
+ def find_product_by_id(id:)
10
+ parse_product(JSON.parse(find_by_id(id).body))
11
+ end
12
+
13
+ private
14
+
15
+ def find_by_id(product_id)
16
+ @connection.post(url) do |req|
17
+ req.headers['Content-Type'] = 'application/json; charset=UTF-8'
18
+ req.body = [product_id].to_json
19
+ end
20
+ end
21
+
22
+ def parse_product(response)
23
+ {
24
+ id: response['data'].values[0]['id'].to_s,
25
+ ticker: response['data'].values[0]['symbol'].to_s,
26
+ exchange_id: response['data'].values[0]['exchangeId'].to_s,
27
+ isin: response['data'].values[0]['isin'].to_s
28
+ }
29
+ end
30
+
31
+ def url
32
+ "#{@connection.urls_map['product_search_url']}/v5/products/info" \
33
+ "?intAccount=#{@connection.user_data['int_account']}" \
34
+ "&sessionId=#{@connection.session_id}"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+
3
+ module DeGiro
4
+ class FindProducts
5
+ def initialize(connection)
6
+ @connection = connection
7
+ end
8
+
9
+ def find_products(search_text:, limit: 7)
10
+ params = URI.encode_www_form(searchText: search_text, limit: limit)
11
+ parse_products(JSON.parse(@connection.get(url(params)).body))
12
+ end
13
+
14
+ private
15
+
16
+ def parse_products(response)
17
+ response['products'].map do |product|
18
+ {
19
+ id: product['id'].to_s,
20
+ ticker: product['symbol'],
21
+ exchange_id: product['exchangeId'],
22
+ isin: product['isin']
23
+ }
24
+ end
25
+ end
26
+
27
+ def url(params)
28
+ "#{@connection.urls_map['product_search_url']}/v5/products/lookup" \
29
+ "?intAccount=#{@connection.user_data['int_account']}" \
30
+ "&sessionId=#{@connection.session_id}" \
31
+ "&#{params}"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ require 'json'
2
+
3
+ module DeGiro
4
+ class GetCashFunds
5
+ def initialize(connection)
6
+ @connection = connection
7
+ end
8
+
9
+ def get_cash_funds
10
+ params = URI.encode_www_form(cashFunds: 0)
11
+ parse_cash_funds(JSON.parse(@connection.get(url(params)).body))
12
+ end
13
+
14
+ private
15
+
16
+ def parse_cash_funds(response)
17
+ funds = response['cashFunds']['value'].map do |cash|
18
+ {
19
+ currency: cash['value'].find { |field| field['name'] == 'currencyCode' }['value'],
20
+ amount: cash['value'].find { |field| field['name'] == 'value' }['value']
21
+ }
22
+ end
23
+ Hash[funds.map { |cash| [cash[:currency], cash[:amount]] }]
24
+ end
25
+
26
+ def url(params)
27
+ "#{@connection.urls_map['trading_url']}/v5/update/" \
28
+ "#{@connection.user_data['int_account']};jsessionid=#{@connection.session_id}" \
29
+ "?#{params}"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+
3
+ module DeGiro
4
+ class GetOrders
5
+ def initialize(connection)
6
+ @connection = connection
7
+ end
8
+
9
+ def get_orders
10
+ params = URI.encode_www_form(orders: 0, historicalOrders: 0, transactions: 0)
11
+ parse_orders(JSON.parse(@connection.get(url(params)).body))
12
+ end
13
+
14
+ private
15
+
16
+ def parse_orders(response)
17
+ response['orders']['value'].map do |order|
18
+ {
19
+ id: order['id'],
20
+ type: order['value'].find { |field| field['name'] == 'buysell' }['value'],
21
+ size: order['value'].find { |field| field['name'] == 'size' }['value'],
22
+ price: order['value'].find { |field| field['name'] == 'price' }['value'],
23
+ product_id: order['value'].find { |field| field['name'] == 'productId' }['value'].to_s,
24
+ product: order['value'].find { |field| field['name'] == 'product' }['value']
25
+ }
26
+ end
27
+ end
28
+
29
+ def url(params)
30
+ "#{@connection.urls_map['trading_url']}/v5/update/" \
31
+ "#{@connection.user_data['int_account']};jsessionid=#{@connection.session_id}" \
32
+ "?#{params}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ require 'json'
2
+
3
+ module DeGiro
4
+ class GetPortfolio
5
+ def initialize(connection)
6
+ @connection = connection
7
+ end
8
+
9
+ def get_portfolio
10
+ params = URI.encode_www_form(portfolio: 0)
11
+ parse_portfolio(JSON.parse(@connection.get(url(params)).body))
12
+ end
13
+
14
+ private
15
+
16
+ def parse_portfolio(response)
17
+ portfolio = response['portfolio']['value'].map do |order|
18
+ {
19
+ size: order['value'].find { |field| field['name'] == 'size' }['value'],
20
+ value: order['value'].find { |field| field['name'] == 'price' }['value'],
21
+ product_id: order['value'].find { |field| field['name'] == 'id' }['value'].to_s
22
+ }
23
+ end
24
+ portfolio.select { |entry| entry[:size] > 0 }
25
+ end
26
+
27
+ def url(params)
28
+ "#{@connection.urls_map['trading_url']}/v5/update/" \
29
+ "#{@connection.user_data['int_account']};jsessionid=#{@connection.session_id}" \
30
+ "?#{params}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ require 'date'
2
+ require 'json'
3
+
4
+ module DeGiro
5
+ class GetTransactions
6
+ def initialize(connection)
7
+ @connection = connection
8
+ end
9
+
10
+ def get_transactions(from: (Date.today - (365 * 5)).strftime('%d/%m/%Y'), to: Date.today.strftime('%d/%m/%Y'))
11
+ params = URI.encode_www_form(fromDate: from, toDate: to)
12
+ parse_transactions(JSON.parse(@connection.get(url(params)).body))
13
+ end
14
+
15
+ private
16
+
17
+ def parse_transactions(response)
18
+ response['data']
19
+ .sort_by { |transaction| transaction['date'] }
20
+ .reverse
21
+ .map do |transaction|
22
+ {
23
+ id: transaction['id'],
24
+ type: transaction['buysell'],
25
+ size: transaction['quantity'].abs,
26
+ price: transaction['price'],
27
+ product_id: transaction['productId'].to_s
28
+ }
29
+ end
30
+ end
31
+
32
+ def url(params)
33
+ "#{@connection.urls_map['reporting_url']}/v4/transactions" \
34
+ '?orderId=&product=&groupTransactionsByOrder=false' \
35
+ "&intAccount=#{@connection.user_data['int_account']}" \
36
+ "&sessionId=#{@connection.session_id}" \
37
+ "&#{params}"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'errors.rb'
2
+
3
+ module DeGiro
4
+ class UrlsMap
5
+ URL_NAMES = [
6
+ 'paUrl',
7
+ 'productSearchUrl',
8
+ 'productTypesUrl',
9
+ 'reportingUrl',
10
+ 'tradingUrl',
11
+ 'vwdQuotecastServiceUrl'
12
+ ].freeze
13
+
14
+ def initialize(data)
15
+ @map = URL_NAMES.each_with_object({}) do |url_name, acc|
16
+ raise MissingUrlError, "Could not find url '#{url_name}'" unless data.key?(url_name)
17
+ acc[url_name.gsub(/(.)([A-Z])/, '\1_\2').downcase] = data[url_name]
18
+ end
19
+ end
20
+
21
+ def [](url_name)
22
+ raise IncorrectUrlError, "Could not find url '#{url_name}'" unless @map.key?(url_name)
23
+ @map[url_name]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'errors.rb'
2
+
3
+ module DeGiro
4
+ class UserData
5
+ USER_FIELDS = [
6
+ 'id',
7
+ 'intAccount'
8
+ ].freeze
9
+
10
+ def initialize(data)
11
+ @map = USER_FIELDS.each_with_object({}) do |user_field, acc|
12
+ raise MissingUserFieldError, "Could not find user field '#{user_field}'" unless data.key?(user_field)
13
+ acc[user_field.gsub(/(.)([A-Z])/, '\1_\2').downcase] = data[user_field]
14
+ end
15
+ end
16
+
17
+ def [](user_field)
18
+ raise IncorrectUserFieldError, "Could not find user field '#{user_field}'" unless @map.key?(user_field)
19
+ @map[user_field]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module DeGiro
2
+ VERSION = '0.0.2'.freeze
3
+ end
data/lib/degiro.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'degiro/client.rb'
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: degiro_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Grzegorz Błaszczyk
8
+ - Tom Van Eyck
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-08-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.13.1
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.13.1
28
+ - !ruby/object:Gem::Dependency
29
+ name: faraday-cookie_jar
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 0.0.6
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 0.0.6
42
+ - !ruby/object:Gem::Dependency
43
+ name: pry
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 0.12.2
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 0.12.2
56
+ - !ruby/object:Gem::Dependency
57
+ name: rubocop
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.51.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.51.0
70
+ description: Ruby Client for the unofficial DeGiro API
71
+ email:
72
+ - grzegorz.blaszczyk@gmail.com
73
+ - tomvaneyck@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - lib/degiro.rb
79
+ - lib/degiro/client.rb
80
+ - lib/degiro/connection.rb
81
+ - lib/degiro/create_order.rb
82
+ - lib/degiro/errors.rb
83
+ - lib/degiro/find_product_by_id.rb
84
+ - lib/degiro/find_products.rb
85
+ - lib/degiro/get_cash_funds.rb
86
+ - lib/degiro/get_orders.rb
87
+ - lib/degiro/get_portfolio.rb
88
+ - lib/degiro/get_transactions.rb
89
+ - lib/degiro/urls_map.rb
90
+ - lib/degiro/user_data.rb
91
+ - lib/degiro/version.rb
92
+ homepage: https://github.com/grzegorzblaszczyk/degiro
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.0.4
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: Ruby Client for the unofficial DeGiro API
115
+ test_files: []