cex 0.1.5 → 0.1.7

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