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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +1 -1
- data/cex.gemspec +1 -1
- data/lib/cex.rb +11 -4
- data/lib/cex/apide.rb +240 -0
- data/lib/cex/apifr.rb +139 -0
- data/lib/cex/apimt.rb +138 -0
- data/lib/cex/apius.rb +167 -0
- data/lib/cex/bigquery1.rb +145 -0
- data/lib/cex/bigquery2.rb +151 -0
- data/lib/cex/bitcoinde.rb +135 -0
- data/lib/cex/kraken.rb +36 -35
- data/lib/cex/paymium.rb +106 -0
- data/lib/cex/therock.rb +95 -0
- data/lib/cex/version.rb +1 -1
- metadata +14 -7
- data/lib/cex/bigquery.rb +0 -126
- data/lib/cex/client.rb +0 -154
data/lib/cex/apimt.rb
ADDED
@@ -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
|
data/lib/cex/apius.rb
ADDED
@@ -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
|