kaesen 0.1.0
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 +7 -0
- data/.env.sample +15 -0
- data/.gitignore +44 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kaesen.gemspec +34 -0
- data/lib/kaesen.rb +17 -0
- data/lib/kaesen/bitflyer.rb +390 -0
- data/lib/kaesen/bitflyerfx.rb +26 -0
- data/lib/kaesen/btcbox.rb +294 -0
- data/lib/kaesen/client.rb +65 -0
- data/lib/kaesen/coincheck.rb +371 -0
- data/lib/kaesen/kraken.rb +156 -0
- data/lib/kaesen/lakebtc.rb +154 -0
- data/lib/kaesen/market.rb +188 -0
- data/lib/kaesen/monetago.rb +110 -0
- data/lib/kaesen/quoine.rb +155 -0
- data/lib/kaesen/version.rb +3 -0
- data/lib/kaesen/zaif.rb +382 -0
- metadata +129 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'market.rb'
|
2
|
+
require 'net/http'
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
require 'date'
|
6
|
+
require 'bigdecimal'
|
7
|
+
|
8
|
+
module Kaesen
|
9
|
+
# BitFlyer FX Wrapper Class
|
10
|
+
# https://coincheck.jp/documents/exchange/api?locale=ja
|
11
|
+
## API制限
|
12
|
+
## . Private API は 1 分間に約 200 回を上限とします。
|
13
|
+
## . IP アドレスごとに 1 分間に約 500 回を上限とします。
|
14
|
+
|
15
|
+
class Bitflyerfx < Bitflyer
|
16
|
+
def initialize
|
17
|
+
super()
|
18
|
+
@name = "BitFlyerFX"
|
19
|
+
@product_code = "FX_BTC_JPY"
|
20
|
+
end
|
21
|
+
|
22
|
+
def balance
|
23
|
+
raise NotImplemented.new() # getcollateral
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,294 @@
|
|
1
|
+
require_relative 'market.rb'
|
2
|
+
require 'net/http'
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
require 'bigdecimal'
|
6
|
+
|
7
|
+
module Kaesen
|
8
|
+
# BtcBox Wrapper Class
|
9
|
+
# https://www.btcbox.co.jp/help/api.html
|
10
|
+
|
11
|
+
class Btcbox < Market
|
12
|
+
@@nonce = 0
|
13
|
+
|
14
|
+
def initialize()
|
15
|
+
super()
|
16
|
+
@name = "BtcBox"
|
17
|
+
@api_key = ENV["BTCBOX_KEY"]
|
18
|
+
@api_secret = ENV["BTCBOX_SECRET"]
|
19
|
+
@url_public = "https://www.btcbox.co.jp/api/v1"
|
20
|
+
@url_private = @url_public
|
21
|
+
end
|
22
|
+
|
23
|
+
#############################################################
|
24
|
+
# API for public information
|
25
|
+
#############################################################
|
26
|
+
|
27
|
+
# Get ticker information.
|
28
|
+
# @return [hash] ticker
|
29
|
+
# ask: [BigDecimal] 最良売気配値
|
30
|
+
# bid: [BigDecimal] 最良買気配値
|
31
|
+
# last: [BigDecimal] 最近値(?用語要チェック), last price
|
32
|
+
# high: [BigDecimal] 高値
|
33
|
+
# low: [BigDecimal] 安値
|
34
|
+
# volume: [BigDecimal] 取引量
|
35
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
36
|
+
def ticker
|
37
|
+
h = get_ssl(@url_public + "/ticker")
|
38
|
+
{
|
39
|
+
"ask" => BigDecimal.new(h["sell"].to_s),
|
40
|
+
"bid" => BigDecimal.new(h["buy"].to_s),
|
41
|
+
"last" => BigDecimal.new(h["last"].to_s),
|
42
|
+
"high" => BigDecimal.new(h["high"].to_s),
|
43
|
+
"low" => BigDecimal.new(h["low"].to_s),
|
44
|
+
"volume" => BigDecimal.new(h["vol"].to_s),
|
45
|
+
"ltimestamp" => Time.now.to_i,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get order book.
|
50
|
+
# @abstract
|
51
|
+
# @return [hash] array of market depth
|
52
|
+
# asks: [Array] 売りオーダー
|
53
|
+
# price : [BigDecimal]
|
54
|
+
# size : [BigDecimal]
|
55
|
+
# bids: [Array] 買いオーダー
|
56
|
+
# price : [BigDecimal]
|
57
|
+
# size : [BigDecimal]
|
58
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
59
|
+
def depth
|
60
|
+
h = get_ssl(@url_public + "/depth")
|
61
|
+
{
|
62
|
+
"asks" => h["asks"].map{|a,b| [BigDecimal.new(a.to_s), BigDecimal.new(b.to_s)]}, # to_s でないと誤差が生じる
|
63
|
+
"bids" => h["bids"].map{|a,b| [BigDecimal.new(a.to_s), BigDecimal.new(b.to_s)]}, # to_s でないと誤差が生じる
|
64
|
+
"ltimestamp" => Time.now.to_i,
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
#############################################################
|
69
|
+
# API for private user data and trading
|
70
|
+
#############################################################
|
71
|
+
|
72
|
+
# Get account balance.
|
73
|
+
# @abstract
|
74
|
+
# @return [hash] account_balance_hash
|
75
|
+
# jpy: [hash]
|
76
|
+
# amount: [BigDecimal] 総日本円
|
77
|
+
# available: [BigDecimal] 取引可能な日本円
|
78
|
+
# btc [hash]
|
79
|
+
# amount: [BigDecimal] 総BTC
|
80
|
+
# available: [BigDecimal] 取引可能なBTC
|
81
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
82
|
+
def balance
|
83
|
+
have_key?
|
84
|
+
h = post_ssl_with_sign(@url_private + "/balance/")
|
85
|
+
{
|
86
|
+
"jpy" => {
|
87
|
+
"amount" => BigDecimal.new(h["jpy_balance"].to_s) + BigDecimal.new(h["jpy_lock"].to_s),
|
88
|
+
"available" => BigDecimal.new(h["jpy_balance"].to_s),
|
89
|
+
},
|
90
|
+
"btc" => {
|
91
|
+
"amount" => BigDecimal.new(h["btc_balance"].to_s) + BigDecimal.new(h["btc_lock"].to_s),
|
92
|
+
"available" => BigDecimal.new(h["btc_balance"].to_s),
|
93
|
+
},
|
94
|
+
"ltimestamp" => Time.now.to_i,
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Get open orders.
|
99
|
+
# @abstract
|
100
|
+
# @return [Array] open_orders_array
|
101
|
+
# @return [hash] history_order_hash
|
102
|
+
# success: [bool]
|
103
|
+
# id: [String] order id in the market
|
104
|
+
# rate: [BigDecimal]
|
105
|
+
# amount: [BigDecimal]
|
106
|
+
# order_type: [String] "sell" or "buy"
|
107
|
+
# ltimestamp: [int] Local Timestamp
|
108
|
+
def opens
|
109
|
+
have_key?
|
110
|
+
address = @url_private + "/trade_list/"
|
111
|
+
params = {
|
112
|
+
"type" => "open",
|
113
|
+
}
|
114
|
+
h = post_ssl_with_sign(address, params)
|
115
|
+
h.map{|x|
|
116
|
+
{
|
117
|
+
"success" => "true",
|
118
|
+
"id" => x["id"],
|
119
|
+
"rate" => BigDecimal.new(x["price"].to_s),
|
120
|
+
"amount" => BigDecimal.new(x["amount_outstanding"].to_s),
|
121
|
+
"order_type" => x["type"],
|
122
|
+
}
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
# Bought the amount of Bitcoin at the rate.
|
127
|
+
# 指数注文 買い.
|
128
|
+
# Abstract Method.
|
129
|
+
# @param [BigDecimal] rate
|
130
|
+
# @param [BigDecimal] amount # minimal trade amount is 0.01 BTC
|
131
|
+
# @return [hash] history_order_hash
|
132
|
+
# success: [String] "true" or "false"
|
133
|
+
# id: [String] order id at the market
|
134
|
+
# rate: [BigDecimal]
|
135
|
+
# amount: [BigDecimal] minimal amount is 0.01 BTC
|
136
|
+
# order_type: [String] "sell" or "buy"
|
137
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
138
|
+
def buy(rate, amount=BigDecimal.new(0))
|
139
|
+
have_key?
|
140
|
+
address = @url_private + "/trade_add/"
|
141
|
+
params = {
|
142
|
+
"amount" => amount.to_f.round(4),
|
143
|
+
"price" => rate.to_i,
|
144
|
+
"type" => "buy",
|
145
|
+
}
|
146
|
+
h = post_ssl_with_sign(address, params)
|
147
|
+
{
|
148
|
+
"success" => h["result"].to_s,
|
149
|
+
"id" => h["id"].to_s,
|
150
|
+
"rate" => BigDecimal.new(rate.to_s),
|
151
|
+
"amount" => BigDecimal.new(amount.to_s),
|
152
|
+
"order_type" => "sell",
|
153
|
+
"ltimestamp" => Time.now.to_i,
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
# Sell the amount of Bitcoin at the rate.
|
158
|
+
# 指数注文 売り.
|
159
|
+
# Abstract Method.
|
160
|
+
# @param [BigDecimal] rate
|
161
|
+
# @param [BigDecimal] amount # minimal trade amount is 0.01 BTC
|
162
|
+
# @return [hash] history_order_hash
|
163
|
+
# success: [String] "true" or "false"
|
164
|
+
# id: [String] order id at the market
|
165
|
+
# rate: [BigDecimal]
|
166
|
+
# amount: [BigDecimal] minimal amount is 0.01 BTC
|
167
|
+
# order_type: [String] "sell" or "buy"
|
168
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
169
|
+
def sell(rate, amount=BigDecimal.new(0))
|
170
|
+
have_key?
|
171
|
+
address = @url_private + "/trade_add/"
|
172
|
+
params = {
|
173
|
+
"amount" => amount.to_f.round(4),
|
174
|
+
"price" => rate.to_i,
|
175
|
+
"type" => "sell",
|
176
|
+
}
|
177
|
+
h = post_ssl_with_sign(address, params)
|
178
|
+
{
|
179
|
+
"success" => h["result"].to_s,
|
180
|
+
"id" => h["id"].to_s,
|
181
|
+
"rate" => BigDecimal.new(rate.to_s),
|
182
|
+
"amount" => BigDecimal.new(amount.to_s),
|
183
|
+
"order_type" => "sell",
|
184
|
+
"ltimestamp" => Time.now.to_i,
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def initialize_https(uri)
|
191
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
192
|
+
https.use_ssl = true
|
193
|
+
https.open_timeout = 5
|
194
|
+
https.read_timeout = 15
|
195
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
196
|
+
https.verify_depth = 5
|
197
|
+
https
|
198
|
+
end
|
199
|
+
|
200
|
+
# Connect to address via https, and return json reponse.
|
201
|
+
def get_ssl(address)
|
202
|
+
uri = URI.parse(address)
|
203
|
+
|
204
|
+
begin
|
205
|
+
https = initialize_https(uri)
|
206
|
+
https.start {|w|
|
207
|
+
response = w.get(uri.request_uri)
|
208
|
+
case response
|
209
|
+
when Net::HTTPSuccess
|
210
|
+
json = JSON.parse(response.body)
|
211
|
+
raise JSONException, response.body if json == nil
|
212
|
+
return json
|
213
|
+
else
|
214
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}."
|
215
|
+
end
|
216
|
+
}
|
217
|
+
rescue
|
218
|
+
raise
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_nonce
|
223
|
+
pre_nonce = @@nonce
|
224
|
+
next_nonce = (1000*Time.now.to_f).to_i
|
225
|
+
|
226
|
+
if next_nonce <= pre_nonce
|
227
|
+
@@nonce = pre_nonce + 1
|
228
|
+
else
|
229
|
+
@@nonce = next_nonce
|
230
|
+
end
|
231
|
+
|
232
|
+
return @@nonce
|
233
|
+
end
|
234
|
+
|
235
|
+
def get_sign(params)
|
236
|
+
secret = Digest::MD5.hexdigest(@api_secret)
|
237
|
+
text = URI.encode_www_form(params)
|
238
|
+
|
239
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, text)
|
240
|
+
end
|
241
|
+
|
242
|
+
def get_ssl_with_sign(address, params={})
|
243
|
+
uri = URI.parse(address)
|
244
|
+
params["key"] = @api_key
|
245
|
+
params["nonce"] = get_nonce
|
246
|
+
params["signature"] = get_sign(params)
|
247
|
+
|
248
|
+
begin
|
249
|
+
req = Net::HTTP::Get.new(uri)
|
250
|
+
req.set_form(params)
|
251
|
+
|
252
|
+
https = initialize_https(uri)
|
253
|
+
https.start {|w|
|
254
|
+
response = w.request(req)
|
255
|
+
case response
|
256
|
+
when Net::HTTPSuccess
|
257
|
+
json = JSON.parse(response.body)
|
258
|
+
return json
|
259
|
+
else
|
260
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}: " + response.value
|
261
|
+
end
|
262
|
+
}
|
263
|
+
rescue
|
264
|
+
raise
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def post_ssl_with_sign(address, params={})
|
269
|
+
uri = URI.parse(address)
|
270
|
+
params["key"] = @api_key
|
271
|
+
params["nonce"] = get_nonce
|
272
|
+
params["signature"] = get_sign(params)
|
273
|
+
|
274
|
+
begin
|
275
|
+
req = Net::HTTP::Post.new(uri)
|
276
|
+
req.set_form(params)
|
277
|
+
|
278
|
+
https = initialize_https(uri)
|
279
|
+
https.start {|w|
|
280
|
+
response = w.request(req)
|
281
|
+
case response
|
282
|
+
when Net::HTTPSuccess
|
283
|
+
json = JSON.parse(response.body)
|
284
|
+
return json
|
285
|
+
else
|
286
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}: " + response.value
|
287
|
+
end
|
288
|
+
}
|
289
|
+
rescue
|
290
|
+
raise
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Kaesen
|
2
|
+
class Client
|
3
|
+
attr_reader :markets
|
4
|
+
attr_reader :ticker
|
5
|
+
attr_reader :balance
|
6
|
+
attr_reader :depth
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@markets = [] # [Array]:
|
10
|
+
# [Market] instance of markets
|
11
|
+
@tickers = {} # [Hash]
|
12
|
+
# [String]: market name
|
13
|
+
# [Hash]: hash of ticker
|
14
|
+
@depths = {} # [Hash]:
|
15
|
+
# [String]: market name
|
16
|
+
# [Hash]: hash of depth
|
17
|
+
@balances = {} # [Hash]:
|
18
|
+
# [String]: market name
|
19
|
+
# [Hash]: hash of depth
|
20
|
+
end
|
21
|
+
|
22
|
+
# register the instance of market
|
23
|
+
# @parm [Market]
|
24
|
+
def push(market)
|
25
|
+
@markets.push(market)
|
26
|
+
end
|
27
|
+
|
28
|
+
# get the instance of market with key
|
29
|
+
# @parms [String] market name
|
30
|
+
# @return [Market] or nil
|
31
|
+
def get(market_name)
|
32
|
+
@markets.each{|m|
|
33
|
+
return m if m.name == market_name
|
34
|
+
}
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# Update market information.
|
39
|
+
# @return [hash] hash of ticker
|
40
|
+
def update_tickers()
|
41
|
+
@markets.each{|m|
|
42
|
+
@tickers[m.name] = m.ticker
|
43
|
+
}
|
44
|
+
@tickers
|
45
|
+
end
|
46
|
+
|
47
|
+
# Update market information.
|
48
|
+
# @return [hash] hash of depth
|
49
|
+
def update_depths()
|
50
|
+
@markets.each{|m|
|
51
|
+
@depths[m.name] = m.depth
|
52
|
+
}
|
53
|
+
@depths
|
54
|
+
end
|
55
|
+
|
56
|
+
# Update asset information.
|
57
|
+
# @return [hash] hash of balance
|
58
|
+
def update_balances()
|
59
|
+
@markets.each{|m|
|
60
|
+
@balances[m.name] = m.balance
|
61
|
+
}
|
62
|
+
@balances
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,371 @@
|
|
1
|
+
require_relative 'market.rb'
|
2
|
+
require 'net/http'
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
require 'bigdecimal'
|
6
|
+
|
7
|
+
module Kaesen
|
8
|
+
# Coincheck Wrapper Class
|
9
|
+
# https://coincheck.jp/documents/exchange/api?locale=ja
|
10
|
+
|
11
|
+
class Coincheck < Market
|
12
|
+
@@nonce = 0
|
13
|
+
|
14
|
+
def initialize()
|
15
|
+
super()
|
16
|
+
@name = "Coincheck"
|
17
|
+
@api_key = ENV["COINCHECK_KEY"]
|
18
|
+
@api_secret = ENV["COINCHECK_SECRET"]
|
19
|
+
@url_public = "https://coincheck.jp"
|
20
|
+
@url_private = @url_public
|
21
|
+
end
|
22
|
+
|
23
|
+
#############################################################
|
24
|
+
# API for public information
|
25
|
+
#############################################################
|
26
|
+
|
27
|
+
# Get ticker information.
|
28
|
+
# @return [hash] ticker
|
29
|
+
# ask: [BigDecimal] 最良売気配値
|
30
|
+
# bid: [BigDecimal] 最良買気配値
|
31
|
+
# last: [BigDecimal] 最近値(?用語要チェック), last price
|
32
|
+
# high: [BigDecimal] 高値
|
33
|
+
# low: [BigDecimal] 安値
|
34
|
+
# volume: [BigDecimal] 取引量
|
35
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
36
|
+
# timestamp: [int] タイムスタンプ
|
37
|
+
def ticker
|
38
|
+
h = get_ssl(@url_public + "/api/ticker")
|
39
|
+
@ticker =
|
40
|
+
{
|
41
|
+
"ask" => BigDecimal.new(h["ask"].to_s),
|
42
|
+
"bid" => BigDecimal.new(h["bid"].to_s),
|
43
|
+
"last" => BigDecimal.new(h["last"].to_s),
|
44
|
+
"high" => BigDecimal.new(h["high"].to_s),
|
45
|
+
"low" => BigDecimal.new(h["low"].to_s),
|
46
|
+
"volume" => BigDecimal.new(h["volume"].to_s),
|
47
|
+
"ltimestamp" => Time.now.to_i,
|
48
|
+
"timestamp" => h["timestamp"].to_i,
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get order book.
|
53
|
+
# @abstract
|
54
|
+
# @return [hash] array of market depth
|
55
|
+
# asks: [Array] 売りオーダー
|
56
|
+
# price : [BigDecimal]
|
57
|
+
# size : [BigDecimal]
|
58
|
+
# bids: [Array] 買いオーダー
|
59
|
+
# price : [BigDecimal]
|
60
|
+
# size : [BigDecimal]
|
61
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
62
|
+
def depth
|
63
|
+
h = get_ssl(@url_public + "/api/order_books")
|
64
|
+
{
|
65
|
+
"asks" => h["asks"].map{|a,b| [BigDecimal.new(a.to_s), BigDecimal.new(b.to_s)]},
|
66
|
+
"bids" => h["bids"].map{|a,b| [BigDecimal.new(a.to_s), BigDecimal.new(b.to_s)]},
|
67
|
+
"ltimestamp" => Time.now.to_i,
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
#############################################################
|
72
|
+
# API for private user data and trading
|
73
|
+
#############################################################
|
74
|
+
|
75
|
+
# Get account balance.
|
76
|
+
# @abstract
|
77
|
+
# @return [hash] account_balance_hash
|
78
|
+
# jpy: [hash]
|
79
|
+
# amount: [BigDecimal] 総日本円
|
80
|
+
# available: [BigDecimal] 取引可能な日本円
|
81
|
+
# btc [hash]
|
82
|
+
# amount: [BigDecimal] 総BTC
|
83
|
+
# available: [BigDecimal] 取引可能なBTC
|
84
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
85
|
+
def balance
|
86
|
+
have_key?
|
87
|
+
address = @url_private + "/api/accounts/balance"
|
88
|
+
h = get_ssl_with_sign(address)
|
89
|
+
{
|
90
|
+
"jpy" => {
|
91
|
+
"amount" => BigDecimal.new(h["jpy"].to_s) + BigDecimal.new(h["jpy_reserved"].to_s),
|
92
|
+
"available" => BigDecimal.new(h["jpy"].to_s),
|
93
|
+
},
|
94
|
+
"btc" => {
|
95
|
+
"amount" => BigDecimal.new(h["btc"].to_s) + BigDecimal.new(h["btc_reserved"].to_s),
|
96
|
+
"available" => BigDecimal.new(h["btc"].to_s),
|
97
|
+
},
|
98
|
+
"ltimestamp" => Time.now.to_i,
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# Get open orders.
|
103
|
+
# @abstract
|
104
|
+
# @return [Array] open_orders_array
|
105
|
+
# @return [hash] history_order_hash
|
106
|
+
# success: [bool]
|
107
|
+
# id: [String] order id in the market
|
108
|
+
# rate: [BigDecimal]
|
109
|
+
# amount: [BigDecimal]
|
110
|
+
# order_type: [String] "sell" or "buy"
|
111
|
+
# ltimestamp: [int] Local Timestamp
|
112
|
+
def opens
|
113
|
+
have_key?
|
114
|
+
address = @url_private + "/api/exchange/orders/opens"
|
115
|
+
h = get_ssl_with_sign(address)
|
116
|
+
h["orders"].map{|x|
|
117
|
+
{
|
118
|
+
"success" => "true",
|
119
|
+
"id" => x["id"].to_s,
|
120
|
+
"rate" => BigDecimal.new(x["rate"].to_s),
|
121
|
+
"amount" => BigDecimal.new(x["pending_amount"].to_s),
|
122
|
+
"order_type" => x["order_type"],
|
123
|
+
}
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# Buy the amount of Bitcoin at the rate.
|
128
|
+
# 指数注文 買い.
|
129
|
+
# @abstract
|
130
|
+
# @param [BigDecimal] rate
|
131
|
+
# @param [BigDecimal] amount # # minimal trade amount is 0.005 BTC
|
132
|
+
# @return [hash] history_order_hash
|
133
|
+
# success: [String] "true" or "false"
|
134
|
+
# id: [String] order id at the market
|
135
|
+
# rate: [BigDecimal]
|
136
|
+
# amount: [BigDecimal]
|
137
|
+
# order_type: [String] "sell" or "buy"
|
138
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
139
|
+
# timestamp: [int] タイムスタンプ
|
140
|
+
def buy(rate, amount=BigDecimal.new(0))
|
141
|
+
have_key?
|
142
|
+
address = @url_private + "/api/exchange/orders"
|
143
|
+
body = {
|
144
|
+
"rate" => rate.to_i,
|
145
|
+
"amount" => amount.to_f.round(4),
|
146
|
+
"order_type" => "buy",
|
147
|
+
"pair" => "btc_jpy",
|
148
|
+
}
|
149
|
+
h = post_ssl_with_sign(address,body)
|
150
|
+
{
|
151
|
+
"success" => h["success"].to_s,
|
152
|
+
"id" => h["id"].to_s,
|
153
|
+
"rate" => BigDecimal.new(h["rate"].to_s),
|
154
|
+
"amount" => BigDecimal.new(h["size"].to_s),
|
155
|
+
"order_type" => h["order_type"],
|
156
|
+
"ltimestamp" => Time.now.to_i,
|
157
|
+
"timestamp" => DateTime.parse(h["created_at"]).to_time.to_i,
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
# Buy the amount of Bitcoin from the market.
|
162
|
+
# 成行注文 買い.
|
163
|
+
# @abstract
|
164
|
+
# @param [BigDecimal] amount
|
165
|
+
# @return [hash] history_order_hash
|
166
|
+
# success: [bool]
|
167
|
+
# id: [String] order id in the market
|
168
|
+
# rate: [BigDecimal]
|
169
|
+
# market_buy_amount: [BigDecimal] amount of JPY
|
170
|
+
# order_type: [String] "sell" or "buy"
|
171
|
+
# ltimestamp: [int] Local Timestamp
|
172
|
+
def market_buy(market_buy_amount=BigDecimal.new("0.0"))
|
173
|
+
have_key?
|
174
|
+
address = @url_private + "/api/exchange/orders"
|
175
|
+
body = {
|
176
|
+
"market_buy_amount" => market_buy_amount.to_f.round(4),
|
177
|
+
"order_type" => "market_buy",
|
178
|
+
"pair" => "btc_jpy",
|
179
|
+
}
|
180
|
+
h = post_ssl_with_sign(address,body)
|
181
|
+
{
|
182
|
+
"success" => h["success"].to_s,
|
183
|
+
"id" => h["id"].to_s,
|
184
|
+
"rate" => BigDecimal.new(h["rate"].to_s),
|
185
|
+
"amount" => BigDecimal.new(h["size"].to_s),
|
186
|
+
"order_type" => h["order_type"],
|
187
|
+
"ltimestamp" => Time.now.to_i,
|
188
|
+
"timestamp" => DateTime.parse(h["created_at"]).to_time.to_i,
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
# Sell the amount of Bitcoin at the rate.
|
193
|
+
# 指数注文 売り.
|
194
|
+
# @abstract
|
195
|
+
# @param [BigDecimal] rate
|
196
|
+
# @param [BigDecimal] amount # minimal trade amount is 0.005 BTC
|
197
|
+
# @return [hash] history_order_hash
|
198
|
+
# success: [String] "true" or "false"
|
199
|
+
# id: [String] order id at the market
|
200
|
+
# rate: [BigDecimal]
|
201
|
+
# amount: [BigDecimal]
|
202
|
+
# order_type: [String] "sell" or "buy"
|
203
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
204
|
+
# timestamp: [int] タイムスタンプ
|
205
|
+
def sell(rate, amount=BigDecimal.new(0))
|
206
|
+
have_key?
|
207
|
+
address = @url_private + "/api/exchange/orders"
|
208
|
+
body = {
|
209
|
+
"rate" => rate.to_i,
|
210
|
+
"amount" => amount.to_f.round(4),
|
211
|
+
"order_type" => "sell",
|
212
|
+
"pair" => "btc_jpy",
|
213
|
+
}
|
214
|
+
h = post_ssl_with_sign(address,body)
|
215
|
+
{
|
216
|
+
"success" => h["success"].to_s,
|
217
|
+
"id" => h["id"].to_s,
|
218
|
+
"rate" => BigDecimal.new(h["rate"].to_s),
|
219
|
+
"amount" => BigDecimal.new(h["size"].to_s),
|
220
|
+
"order_type" => h["order_type"],
|
221
|
+
"ltimestamp" => Time.now.to_i,
|
222
|
+
"timestamp" => DateTime.parse(h["created_at"]).to_time.to_i,
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
# Sell the amount of Bitcoin to the market.
|
227
|
+
# 成行注文 売り.
|
228
|
+
# @abstract
|
229
|
+
# @param [BigDecimal] amount
|
230
|
+
# @return [hash] history_order_hash
|
231
|
+
# success: [bool]
|
232
|
+
# id: [String] order id in the market
|
233
|
+
# rate: [BigDecimal]
|
234
|
+
# amount: [BigDecimal]
|
235
|
+
# order_type: [String] "sell" or "buy"
|
236
|
+
# ltimestamp: [int] Local Timestamp
|
237
|
+
def market_sell(amount=BigDecimal.new("0.0"))
|
238
|
+
have_key?
|
239
|
+
address = @url_private + "/api/exchange/orders"
|
240
|
+
body = {
|
241
|
+
"amount" => amount.to_f.round(4),
|
242
|
+
"order_type" => "market_sell",
|
243
|
+
"pair" => "btc_jpy",
|
244
|
+
}
|
245
|
+
h = post_ssl_with_sign(address,body)
|
246
|
+
{
|
247
|
+
"success" => h["success"].to_s,
|
248
|
+
"id" => h["id"].to_s,
|
249
|
+
"rate" => BigDecimal.new(h["rate"].to_s),
|
250
|
+
"amount" => BigDecimal.new(h["size"].to_s),
|
251
|
+
"order_type" => h["order_type"],
|
252
|
+
"ltimestamp" => Time.now.to_i,
|
253
|
+
"timestamp" => DateTime.parse(h["created_at"]).to_time.to_i,
|
254
|
+
}
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def initialize_https(uri)
|
260
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
261
|
+
https.use_ssl = true
|
262
|
+
https.open_timeout = 5
|
263
|
+
https.read_timeout = 15
|
264
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
265
|
+
https.verify_depth = 5
|
266
|
+
https
|
267
|
+
end
|
268
|
+
|
269
|
+
def get_nonce
|
270
|
+
pre_nonce = @@nonce
|
271
|
+
next_nonce = (1000*Time.now.to_f).to_i
|
272
|
+
|
273
|
+
if next_nonce <= pre_nonce
|
274
|
+
@@nonce = pre_nonce + 1
|
275
|
+
else
|
276
|
+
@@nonce = next_nonce
|
277
|
+
end
|
278
|
+
|
279
|
+
return @@nonce
|
280
|
+
end
|
281
|
+
|
282
|
+
def get_sign(address, nonce, body)
|
283
|
+
secret = @api_secret
|
284
|
+
text = nonce.to_s + address.to_s + body
|
285
|
+
|
286
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, text)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Connect to address via https, and return json response.
|
290
|
+
def get_ssl(address)
|
291
|
+
uri = URI.parse(address)
|
292
|
+
begin
|
293
|
+
https = initialize_https(uri)
|
294
|
+
https.start {|w|
|
295
|
+
response = w.get(uri.request_uri)
|
296
|
+
case response
|
297
|
+
when Net::HTTPSuccess
|
298
|
+
json = JSON.parse(response.body)
|
299
|
+
raise JSONException, response.body if json == nil
|
300
|
+
return json
|
301
|
+
else
|
302
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}."
|
303
|
+
end
|
304
|
+
}
|
305
|
+
rescue
|
306
|
+
raise
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def get_headers(address, nonce, body="")
|
311
|
+
{
|
312
|
+
"ACCESS-KEY" => @api_key,
|
313
|
+
"ACCESS-NONCE" => nonce.to_s,
|
314
|
+
"ACCESS-SIGNATURE" => get_sign(address, nonce, body.to_json),
|
315
|
+
"Content-Type" => "application/json",
|
316
|
+
}
|
317
|
+
end
|
318
|
+
|
319
|
+
# Connect to address via https, and return json response.
|
320
|
+
def get_ssl_with_sign(address,body="")
|
321
|
+
uri = URI.parse(address)
|
322
|
+
nonce = get_nonce
|
323
|
+
headers = get_headers(address, nonce, body)
|
324
|
+
|
325
|
+
begin
|
326
|
+
req = Net::HTTP::Get.new(uri, headers)
|
327
|
+
req.body = body.to_json
|
328
|
+
|
329
|
+
https = initialize_https(uri)
|
330
|
+
https.start {|w|
|
331
|
+
response = w.request(req)
|
332
|
+
case response
|
333
|
+
when Net::HTTPSuccess
|
334
|
+
json = JSON.parse(response.body)
|
335
|
+
raise JSONException, response.body if json == nil
|
336
|
+
return json
|
337
|
+
else
|
338
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}: " + response.value
|
339
|
+
end
|
340
|
+
}
|
341
|
+
rescue
|
342
|
+
raise
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def post_ssl_with_sign(address, body="")
|
347
|
+
uri = URI.parse(address)
|
348
|
+
nonce = get_nonce
|
349
|
+
headers = get_headers(address, nonce, body)
|
350
|
+
|
351
|
+
begin
|
352
|
+
req = Net::HTTP::Post.new(uri, headers)
|
353
|
+
req.body = body.to_json
|
354
|
+
|
355
|
+
https = initialize_https(uri)
|
356
|
+
https.start {|w|
|
357
|
+
response = w.request(req)
|
358
|
+
case response
|
359
|
+
when Net::HTTPSuccess
|
360
|
+
json = JSON.parse(response.body)
|
361
|
+
return json
|
362
|
+
else
|
363
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}: " + response.value
|
364
|
+
end
|
365
|
+
}
|
366
|
+
rescue
|
367
|
+
raise
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|