cex 0.1.5 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('openssl')
4
+ require('base64')
5
+ require('curb')
6
+ require('json')
7
+
8
+ # @author Hernani Rodrigues Vaz
9
+ module Cex
10
+ DC = %w[LTC NMC PPC DOGE XRP Linden USD CAD GBP ZEC BCH EURN NOKU FDZ GUSD SEED USDC].freeze
11
+
12
+ # classe para processar dados no therock
13
+ class Apimt
14
+ # @return [String] API key
15
+ attr_reader :aky
16
+ # @return [String] API secret
17
+ attr_reader :asc
18
+ # @return [String] API url base
19
+ attr_reader :urb
20
+
21
+ # @param [String] pky API key
22
+ # @param [String] psc API secret
23
+ # @param [Hash] ops parametrizacao base da API
24
+ # @return [Apius] API therock base
25
+ def initialize(
26
+ pky: ENV['THEROCK_API_KEY'],
27
+ psc: ENV['THEROCK_API_SECRET'],
28
+ ops: { www: 'https://api.therocktrading.com', ver: 1 }
29
+ )
30
+ @aky = pky
31
+ @asc = psc
32
+ @urb = "#{ops[:www]}/v#{ops[:ver]}"
33
+ end
34
+
35
+ # @example
36
+ # {
37
+ # balances: [
38
+ # { currency: 'BTC', balance: 0.0, trading_balance: 0.0 },
39
+ # { currency: 'ETH', balance: 0.0, trading_balance: 0.0 },
40
+ # { currency: 'EUR', balance: 0.0, trading_balance: 0.0 },
41
+ # { currency: 'DAI', balance: 0.0, trading_balance: 0.0 },
42
+ # ]
43
+ # }
44
+ # @return [Hash] saldos no therock
45
+ def account
46
+ api_get('balances')[:balances].delete_if { |e| DC.include?(e[:currency]) }
47
+ .sort { |a, b| a[:currency] <=> b[:currency] }
48
+ end
49
+
50
+ # @example
51
+ # {
52
+ # transactions: [
53
+ # {
54
+ # id: 305_445,
55
+ # date: '2014-03-06T10:59:13.000Z',
56
+ # type: 'withdraw',
57
+ # price: 97.47,
58
+ # currency: 'EUR',
59
+ # fund_id: nil,
60
+ # order_id: nil,
61
+ # trade_id: nil,
62
+ # note: 'BOV withdraw',
63
+ # transfer_detail: nil
64
+ # },
65
+ # {}
66
+ # ],
67
+ # meta: {
68
+ # total_count: nil,
69
+ # first: { page: 1, href: 'https://api.therocktrading.com/v1/transactions?page=1' },
70
+ # previous: nil,
71
+ # current: { page: 1, href: 'https://api.therocktrading.com/v1/transactions?page=1' },
72
+ # next: { page: 2, href: 'https://api.therocktrading.com/v1/transactions?page=2' },
73
+ # last: nil
74
+ # }
75
+ # }
76
+ # @return [Hash] ledger no therock
77
+ def ledger(pag = 1, ary = [])
78
+ r = api_get('transactions', page: pag)[:transactions]
79
+ r.empty? ? ary : ledger(pag + r.size, ary + r)
80
+ rescue StandardError
81
+ ary
82
+ end
83
+
84
+ private
85
+
86
+ # HTTP GET request for public therock API queries.
87
+ def api_get(uri, **ops)
88
+ resposta(Curl.get("#{urb}/#{uri}", ops) { |r| r.headers = hdrs(url(uri, ops), nonce, {}) })
89
+ end
90
+
91
+ # HTTP POST request for private therock API queries involving user credentials.
92
+ def api_post(uri, **ops)
93
+ resposta(Curl.post("#{urb}/#{uri}", ops) { |r| r.headers = hdrs(uri, nonce, ops) })
94
+ end
95
+
96
+ # @return [String] URL do pedido formatado com todos os parametros
97
+ def url(uri, ops)
98
+ ops.empty? ? uri : "#{uri}?#{URI.encode_www_form(ops)}"
99
+ end
100
+
101
+ # @return [Hash] headers necessarios para pedido HTTP
102
+ def hdrs(qry, non, ops)
103
+ {
104
+ content_type: 'application/json',
105
+ 'X-TRT-KEY': aky,
106
+ 'X-TRT-NONCE': non,
107
+ 'X-TRT-SIGN': auth(qry, non, URI.encode_www_form(ops))
108
+ }
109
+ end
110
+
111
+ # @return [String] assinarura codificada dos pedidos HTTP
112
+ def auth(qry, non, par)
113
+ raise(ArgumentError, 'API Key is not set') unless aky
114
+ raise(ArgumentError, 'API Secret is not set') unless asc
115
+
116
+ OpenSSL::HMAC.hexdigest('sha512', asc, [non, "#{urb}/#{qry}", par].join)
117
+ end
118
+
119
+ # @return [Integer] continually-increasing unsigned integer nonce from the current Unix Time
120
+ def nonce
121
+ Integer(Float(Time.now) * 1e6)
122
+ end
123
+
124
+ # @return [Hash] resposta do pedido HTTP
125
+ def resposta(http)
126
+ http.response_code == 200 ? JSON.parse(http.body, symbolize_names: true) : http.status
127
+ rescue JSON::ParserError,
128
+ EOFError,
129
+ Errno::ECONNRESET,
130
+ Errno::EINVAL,
131
+ Net::HTTPBadResponse,
132
+ Net::HTTPHeaderSyntaxError,
133
+ Net::ProtocolError,
134
+ Timeout::Error => e
135
+ "Erro da API therock #{e.inspect}"
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('openssl')
4
+ require('base64')
5
+ require('curb')
6
+ require('json')
7
+
8
+ # @author Hernani Rodrigues Vaz
9
+ module Cex
10
+ # classe para processar dados no kraken
11
+ class Apius
12
+ # @return [String] API key
13
+ attr_reader :aky
14
+ # @return [String] API secret
15
+ attr_reader :asc
16
+ # @return [String] API url base
17
+ attr_reader :urb
18
+ # @return [String] API private path
19
+ attr_reader :pth
20
+
21
+ # @param [String] pky API key
22
+ # @param [String] psc API secret
23
+ # @param [Hash] ops parametrizacao base da API
24
+ # @return [Apius] API kraken base
25
+ def initialize(
26
+ pky: ENV['KRAKEN_API_KEY'],
27
+ psc: ENV['KRAKEN_API_SECRET'],
28
+ ops: { www: 'https://api.kraken.com', ver: 0 }
29
+ )
30
+ @aky = pky
31
+ @asc = psc
32
+ @urb = "#{ops[:www]}/#{ops[:ver]}"
33
+ @pth = "/#{ops[:ver]}/private/"
34
+ end
35
+
36
+ # @example
37
+ # {
38
+ # error: [],
39
+ # result: {
40
+ # ZEUR: '0.0038',
41
+ # XXBT: '0.0000000000',
42
+ # XETH: '1.0000000000',
43
+ # XETC: '0.0000000000',
44
+ # EOS: '0.0000001700',
45
+ # BCH: '0.0000000000'
46
+ # }
47
+ # }
48
+ # @return [Hash] saldos no kraken
49
+ def account
50
+ api_post('Balance')[:result]
51
+ end
52
+
53
+ # @example
54
+ # {
55
+ # error: [],
56
+ # result: {
57
+ # trades: {
58
+ # "TVINF5-TIOUB-YFNGKE": {
59
+ # ordertxid: 'ORPSUW-YKP4F-UJZOC6',
60
+ # pair: 'XETHXXBT',
61
+ # time: 1_463_435_684.8387,
62
+ # type: 'buy',
63
+ # ordertype: 'market',
64
+ # price: '0.024989',
65
+ # cost: '1.193973',
66
+ # fee: '0.003104',
67
+ # vol: '47.77994129',
68
+ # margin: '0.000000',
69
+ # misc: ''
70
+ # },
71
+ # "OUTRO-TRADE-ID": {}
72
+ # },
73
+ # count: 157
74
+ # }
75
+ # }
76
+ # @param [Integer] ofs offset dos dados a obter
77
+ # @param [Hash] has acumulador dos dados a obter
78
+ # @return [Hash] dados trades no kraken
79
+ def trades(ofs = 0, has = {})
80
+ r = api_post('TradesHistory', ofs: ofs)[:result]
81
+ has.merge!(r[:trades])
82
+ ofs += 50
83
+ ofs < r[:count] ? trades(ofs, has) : has
84
+ rescue StandardError
85
+ has
86
+ end
87
+
88
+ # @example
89
+ # {
90
+ # error: [],
91
+ # result: {
92
+ # ledger: {
93
+ # "LXXURB-ITI7S-CXVERS": {
94
+ # refid: 'ACCHF3A-RIBBMO-VYBESY',
95
+ # time: 1_543_278_716.2775,
96
+ # type: 'withdrawal',
97
+ # subtype: '',
98
+ # aclass: 'currency',
99
+ # asset: 'ZEUR',
100
+ # amount: '-15369.6200',
101
+ # fee: '0.0900',
102
+ # balance: '0.0062'
103
+ # },
104
+ # "OUTRO-LEDGER-ID": {}
105
+ # },
106
+ # count: 376
107
+ # }
108
+ # }
109
+ # @param (see trades)
110
+ # @return [Hash] dados ledger no kraken
111
+ def ledger(ofs = 0, has = {})
112
+ r = api_post('Ledgers', ofs: ofs)[:result]
113
+ has.merge!(r[:ledger])
114
+ ofs += 50
115
+ ofs < r[:count] ? ledger(ofs, has) : has
116
+ rescue StandardError
117
+ has
118
+ end
119
+
120
+ private
121
+
122
+ # HTTP GET request for public kraken API queries.
123
+ def api_get(uri, **ops)
124
+ resposta(Curl.get("#{urb}/public/#{uri}", ops))
125
+ end
126
+
127
+ # HTTP POST request for private kraken API queries involving user credentials.
128
+ def api_post(uri, **ops)
129
+ # continually-increasing unsigned integer nonce from the current Unix Time
130
+ ops.merge!({ nonce: Integer(Float(Time.now) * 1e6) })
131
+
132
+ resposta(Curl.post("#{urb}/private/#{uri}", ops) { |r| r.headers = hdrs(uri, ops) })
133
+ end
134
+
135
+ # @return [Hash] headers necessarios para pedido HTTP
136
+ def hdrs(qry, ops)
137
+ {
138
+ 'api-key': aky,
139
+ 'api-sign': auth(qry, ops[:nonce], URI.encode_www_form(ops))
140
+ }
141
+ end
142
+
143
+ # @return [String] assinarura codificada dos pedidos HTTP
144
+ def auth(qry, non, par)
145
+ raise(ArgumentError, 'API Key is not set') unless aky
146
+ raise(ArgumentError, 'API Secret is not set') unless asc
147
+
148
+ Base64.strict_encode64(
149
+ OpenSSL::HMAC.digest('sha512', Base64.decode64(asc), [pth, qry, Digest::SHA256.digest("#{non}#{par}")].join)
150
+ )
151
+ end
152
+
153
+ # @return [Hash] resposta do pedido HTTP
154
+ def resposta(http)
155
+ http.response_code == 200 ? JSON.parse(http.body, symbolize_names: true) : http.status
156
+ rescue JSON::ParserError,
157
+ EOFError,
158
+ Errno::ECONNRESET,
159
+ Errno::EINVAL,
160
+ Net::HTTPBadResponse,
161
+ Net::HTTPHeaderSyntaxError,
162
+ Net::ProtocolError,
163
+ Timeout::Error => e
164
+ "Erro da API kraken #{e.inspect}"
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('google/cloud/bigquery')
4
+ require('bigdecimal/util')
5
+
6
+ # @author Hernani Rodrigues Vaz
7
+ module Cex
8
+ BD = 'hernanirvaz.coins'
9
+
10
+ # classe para processar bigquery
11
+ class Bigquery
12
+ # @return [Google::Cloud::Bigquery] API bigquery
13
+ attr_reader :api
14
+ # @return [Google::Cloud::Bigquery::QueryJob] job bigquery
15
+ attr_reader :job
16
+ # @return [Thor::CoreExt::HashWithIndifferentAccess] opcoes trabalho
17
+ attr_reader :ops
18
+ # @return (see sql)
19
+ attr_reader :sqr
20
+
21
+ # @param [Thor::CoreExt::HashWithIndifferentAccess] pop opcoes trabalho
22
+ # @option pop [Hash] :h ({}) configuracao ajuste reposicionamento temporal
23
+ # @option pop [Boolean] :v (false) mostra transacoes trades & ledger?
24
+ # @option pop [Boolean] :t (false) mostra transacoes todas ou somente novas?
25
+ # @return [Bigquery] API bigquery & kraken/bitcoinde/paymium/therock
26
+ def initialize(pop)
27
+ # usa env GOOGLE_APPLICATION_CREDENTIALS para obter credentials
28
+ # @see https://cloud.google.com/bigquery/docs/authentication/getting-started
29
+ @api = Google::Cloud::Bigquery.new
30
+ @ops = pop
31
+ end
32
+
33
+ # @return [Kraken] API kraken - obter saldos & transacoes trades e ledger
34
+ def apius
35
+ @apius ||= Kraken.new(
36
+ {
37
+ sl: sql("select sum(btc) xxbt,sum(eth) xeth,sum(eos) eos,sum(eur) zeur from #{BD}.ussl")[0],
38
+ nt: sql("select * from #{BD}.ustx order by time,txid"),
39
+ nl: sql("select * from #{BD}.uslx order by time,txid")
40
+ },
41
+ ops
42
+ )
43
+ end
44
+
45
+ # @return [Bitcoinde] API Bitcoinde - obter saldos & transacoes trades e ledger
46
+ def apide
47
+ @apide ||= Bitcoinde.new(
48
+ {
49
+ sl: sql("select sum(btc) btc from #{BD}.desl")[0],
50
+ nt: sql("select * from #{BD}.detx order by time,txid"),
51
+ nl: sql("select * from #{BD}.delx order by time,txid")
52
+ },
53
+ ops
54
+ )
55
+ end
56
+
57
+ # @return [Paymium] API Paymium - obter saldos & transacoes ledger
58
+ def apifr
59
+ @apifr ||= Paymium.new(
60
+ {
61
+ sl: sql("select sum(btc) btc,sum(eur) eur from #{BD}.frsl")[0],
62
+ nl: sql("select * from #{BD}.frlx order by time,txid")
63
+ },
64
+ ops
65
+ )
66
+ end
67
+
68
+ # @return [TheRock] API TheRock - obter saldos & transacoes ledger
69
+ def apimt
70
+ @apimt ||= TheRock.new(
71
+ {
72
+ sl: sql("select sum(btc) btc,sum(eur) eur from #{BD}.mtsl")[0],
73
+ nl: sql("select * from #{BD}.mtlx order by time,txid")
74
+ },
75
+ ops
76
+ )
77
+ end
78
+
79
+ # situacao completa entre kraken/bitcoinde/paymium/therock & bigquery
80
+ def mostra_tudo
81
+ apius.mostra_resumo
82
+ apide.mostra_resumo
83
+ apifr.mostra_resumo
84
+ apimt.mostra_resumo
85
+ end
86
+
87
+ # insere (caso existam) transacoes novas do kraken/bitcoinde/paymium/therock no bigquery
88
+ def processa_tudo
89
+ processa_us
90
+ processa_de
91
+ processa_fr
92
+ processa_mt
93
+ end
94
+
95
+ # insere transacoes kraken novas nas tabelas ust (trades), usl (ledger)
96
+ def processa_us
97
+ puts(format("%<n>2i TRADES KRAKEN INSERIDAS #{BD}.ust", n: apius.trades.empty? ? 0 : dml(ust_ins)))
98
+ puts(format("%<n>2i LEDGER KRAKEN INSERIDAS #{BD}.usl", n: apius.ledger.empty? ? 0 : dml(usl_ins)))
99
+ end
100
+
101
+ # insere transacoes bitcoinde novas nas tabelas det (trades), del (ledger)
102
+ def processa_de
103
+ puts(format("%<n>2i TRADES BITCOINDE INSERIDAS #{BD}.det", n: apide.trades.empty? ? 0 : dml(det_ins)))
104
+ puts(format("%<n>2i LEDGER BITCOINDE INSERIDAS #{BD}.del", n: apide.ledger.empty? ? 0 : dml(del_ins)))
105
+ end
106
+
107
+ # insere transacoes paymium novas na tabela fr (ledger)
108
+ def processa_fr
109
+ puts(format("%<n>2i LEDGER PAYMIUM INSERIDAS #{BD}.fr", n: apifr.ledger.empty? ? 0 : dml(frl_ins)))
110
+ end
111
+
112
+ # insere transacoes paymium novas na tabela mt (ledger)
113
+ def processa_mt
114
+ puts(format("%<n>2i LEDGER THEROCK INSERIDAS #{BD}.mt", n: apimt.ledger.empty? ? 0 : dml(mtl_ins)))
115
+ end
116
+
117
+ # cria job bigquery & verifica execucao
118
+ #
119
+ # @param cmd (see sql)
120
+ # @return [Boolean] job ok?
121
+ def job?(cmd)
122
+ @job = api.query_job(cmd)
123
+ @job.wait_until_done!
124
+ puts(@job.error['message']) if @job.failed?
125
+ @job.failed?
126
+ end
127
+
128
+ # cria Structured Query Language (SQL) job bigquery
129
+ #
130
+ # @param [String] cmd comando SQL a executar
131
+ # @param [String] res resultado quando SQL tem erro
132
+ # @return [Google::Cloud::Bigquery::Data] resultado do SQL
133
+ def sql(cmd, res = [])
134
+ @sqr = job?(cmd) ? res : job.data
135
+ end
136
+
137
+ # cria Data Manipulation Language (DML) job bigquery
138
+ #
139
+ # @param cmd (see sql)
140
+ # @return [Integer] numero linhas afetadas
141
+ def dml(cmd)
142
+ job?(cmd) ? 0 : job.num_dml_affected_rows
143
+ end
144
+ end
145
+ end