honeymaker 0.1.0 → 0.3.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 +4 -4
- data/README.md +4 -0
- data/lib/honeymaker/client.rb +96 -0
- data/lib/honeymaker/clients/binance.rb +288 -0
- data/lib/honeymaker/clients/binance_us.rb +9 -0
- data/lib/honeymaker/clients/bingx.rb +136 -0
- data/lib/honeymaker/clients/bitget.rb +157 -0
- data/lib/honeymaker/clients/bitmart.rb +146 -0
- data/lib/honeymaker/clients/bitrue.rb +130 -0
- data/lib/honeymaker/clients/bitvavo.rb +160 -0
- data/lib/honeymaker/clients/bybit.rb +166 -0
- data/lib/honeymaker/clients/coinbase.rb +219 -0
- data/lib/honeymaker/clients/gemini.rb +99 -0
- data/lib/honeymaker/clients/hyperliquid.rb +65 -0
- data/lib/honeymaker/clients/kraken.rb +162 -0
- data/lib/honeymaker/clients/kucoin.rb +180 -0
- data/lib/honeymaker/clients/mexc.rb +166 -0
- data/lib/honeymaker/exchanges/binance.rb +11 -17
- data/lib/honeymaker/exchanges/bitget.rb +2 -2
- data/lib/honeymaker/exchanges/bitrue.rb +6 -16
- data/lib/honeymaker/exchanges/bitvavo.rb +2 -2
- data/lib/honeymaker/exchanges/bybit.rb +2 -2
- data/lib/honeymaker/exchanges/coinbase.rb +5 -9
- data/lib/honeymaker/exchanges/gemini.rb +4 -6
- data/lib/honeymaker/exchanges/kraken.rb +5 -9
- data/lib/honeymaker/exchanges/kucoin.rb +2 -2
- data/lib/honeymaker/exchanges/mexc.rb +11 -17
- data/lib/honeymaker/utils.rb +9 -0
- data/lib/honeymaker/version.rb +1 -1
- data/lib/honeymaker.rb +38 -0
- data/test/fixtures/bingx_symbols.json +26 -0
- data/test/fixtures/bitget_symbols.json +28 -0
- data/test/fixtures/bitmart_symbols.json +26 -0
- data/test/fixtures/bitrue_exchange_info.json +34 -0
- data/test/fixtures/bitvavo_markets.json +16 -0
- data/test/fixtures/bybit_instruments.json +23 -0
- data/test/fixtures/coinbase_products.json +24 -0
- data/test/fixtures/gemini_symbol_detail.json +9 -0
- data/test/fixtures/gemini_symbols.json +1 -0
- data/test/fixtures/hyperliquid_spot_meta.json +12 -0
- data/test/fixtures/kucoin_symbols.json +17 -0
- data/test/fixtures/mexc_exchange_info.json +40 -0
- data/test/honeymaker/client_test.rb +53 -0
- data/test/honeymaker/clients/binance_client_test.rb +101 -0
- data/test/honeymaker/clients/binance_us_client_test.rb +25 -0
- data/test/honeymaker/clients/bingx_client_test.rb +82 -0
- data/test/honeymaker/clients/bitget_client_test.rb +109 -0
- data/test/honeymaker/clients/bitmart_client_test.rb +96 -0
- data/test/honeymaker/clients/bitrue_client_test.rb +69 -0
- data/test/honeymaker/clients/bitvavo_client_test.rb +105 -0
- data/test/honeymaker/clients/bybit_client_test.rb +102 -0
- data/test/honeymaker/clients/coinbase_client_test.rb +132 -0
- data/test/honeymaker/clients/gemini_client_test.rb +83 -0
- data/test/honeymaker/clients/honeymaker_client_registry_test.rb +44 -0
- data/test/honeymaker/clients/hyperliquid_client_test.rb +65 -0
- data/test/honeymaker/clients/kraken_client_test.rb +84 -0
- data/test/honeymaker/clients/kucoin_client_test.rb +106 -0
- data/test/honeymaker/clients/mexc_client_test.rb +93 -0
- data/test/honeymaker/clients/validation_test.rb +182 -0
- data/test/honeymaker/exchanges/binance_us_test.rb +40 -0
- data/test/honeymaker/exchanges/bingx_test.rb +53 -0
- data/test/honeymaker/exchanges/bitget_test.rb +52 -0
- data/test/honeymaker/exchanges/bitmart_test.rb +52 -0
- data/test/honeymaker/exchanges/bitrue_test.rb +53 -0
- data/test/honeymaker/exchanges/bitvavo_test.rb +52 -0
- data/test/honeymaker/exchanges/bybit_test.rb +43 -0
- data/test/honeymaker/exchanges/coinbase_test.rb +52 -0
- data/test/honeymaker/exchanges/gemini_test.rb +48 -0
- data/test/honeymaker/exchanges/hyperliquid_test.rb +52 -0
- data/test/honeymaker/exchanges/kucoin_test.rb +43 -0
- data/test/honeymaker/exchanges/mexc_test.rb +64 -0
- data/test/honeymaker/utils_test.rb +38 -0
- data/test/test_helper.rb +1 -0
- metadata +74 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ff95afc5916845501bc9cbb7c625979f9f06662608d0e47bfbf9ec4bb7be686b
|
|
4
|
+
data.tar.gz: 978d54c3f108c0a037c627250bd277d6c1e5159d940c10a1579e54d0f96523d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aec9b76d37ca978445002ba6243c73c857b6a3e9bfc4b7fd43f8c73091a7606503df1076da8af93856a4a6a74fb9e72c32af20237b74b3e339339cc5b168d3d3
|
|
7
|
+
data.tar.gz: 630b79116ad0d74be37993f412aa4b81f371cdc58fae756fd5c08fe7496a887c458aee0b9e6439575d89268a6f8f0c8b78bc7734d35ac8debe0c16185d7280a1
|
data/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Honeymaker
|
|
2
2
|
|
|
3
|
+
[](https://rubygems.org/gems/honeymaker)
|
|
4
|
+
[](https://github.com/deltabadger/honeymaker/actions)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
Ruby clients for cryptocurrency exchange APIs. Originally extracted from [Deltabadger](https://github.com/deltabadger/deltabadger).
|
|
4
8
|
|
|
5
9
|
## Supported Exchanges
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "base64"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
|
|
7
|
+
module Honeymaker
|
|
8
|
+
class Client
|
|
9
|
+
OPTIONS = {
|
|
10
|
+
request: {
|
|
11
|
+
open_timeout: 5,
|
|
12
|
+
read_timeout: 30,
|
|
13
|
+
write_timeout: 10
|
|
14
|
+
}
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :api_key, :api_secret
|
|
18
|
+
|
|
19
|
+
def initialize(api_key: nil, api_secret: nil, proxy: nil, logger: nil)
|
|
20
|
+
@api_key = api_key
|
|
21
|
+
@api_secret = api_secret
|
|
22
|
+
@proxy = proxy
|
|
23
|
+
@logger = logger
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def validate(type = :trading)
|
|
27
|
+
return Result::Failure.new("No credentials provided") unless authenticated?
|
|
28
|
+
|
|
29
|
+
case type
|
|
30
|
+
when :trading then validate_trading_credentials
|
|
31
|
+
when :read then validate_read_credentials
|
|
32
|
+
else raise Error, "Unknown validation type: #{type}. Use :trading or :read"
|
|
33
|
+
end
|
|
34
|
+
rescue Error
|
|
35
|
+
raise
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
Result::Failure.new(e.message)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def validate_trading_credentials
|
|
43
|
+
raise NotImplementedError, "#{self.class} must implement #validate_trading_credentials"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_read_credentials
|
|
47
|
+
raise NotImplementedError, "#{self.class} must implement #validate_read_credentials"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def with_rescue
|
|
51
|
+
Result::Success.new(yield)
|
|
52
|
+
rescue Faraday::Error => e
|
|
53
|
+
body = e.respond_to?(:response_body) ? e.response_body : nil
|
|
54
|
+
error_message = (body && !body.empty?) ? body : e.message.to_s
|
|
55
|
+
error_message = "Unknown API error" if error_message.nil? || error_message.empty?
|
|
56
|
+
status = e.respond_to?(:response_status) ? e.response_status : nil
|
|
57
|
+
Result::Failure.new(error_message, data: { status: status })
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
msg = e.message
|
|
60
|
+
Result::Failure.new((msg && !msg.empty?) ? msg : "Unknown error")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def connection
|
|
64
|
+
@connection ||= build_client_connection(self.class::URL)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_client_connection(url, content_type_match: nil)
|
|
68
|
+
Faraday.new(url: url, **OPTIONS) do |config|
|
|
69
|
+
config.proxy = @proxy if @proxy
|
|
70
|
+
config.request :json
|
|
71
|
+
if content_type_match
|
|
72
|
+
config.response :json, content_type: content_type_match
|
|
73
|
+
else
|
|
74
|
+
config.response :json
|
|
75
|
+
end
|
|
76
|
+
config.response :raise_error
|
|
77
|
+
config.response :logger, @logger, headers: false, bodies: false, log_level: :debug if @logger
|
|
78
|
+
config.adapter :net_http_persistent do |http|
|
|
79
|
+
http.idle_timeout = 100
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def authenticated?
|
|
85
|
+
@api_key && !@api_key.empty? && @api_secret && !@api_secret.empty?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def timestamp_ms
|
|
89
|
+
(Time.now.utc.to_f * 1_000).to_i
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def hmac_sha256(secret, data)
|
|
93
|
+
OpenSSL::HMAC.hexdigest("sha256", secret, data)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Honeymaker
|
|
4
|
+
module Clients
|
|
5
|
+
class Binance < Client
|
|
6
|
+
URL = "https://api.binance.com"
|
|
7
|
+
|
|
8
|
+
def exchange_information(symbol: nil, symbols: nil, permissions: nil, show_permission_sets: nil, symbol_status: nil)
|
|
9
|
+
with_rescue do
|
|
10
|
+
response = connection.get do |req|
|
|
11
|
+
req.url "/api/v3/exchangeInfo"
|
|
12
|
+
req.headers = headers
|
|
13
|
+
req.params = {
|
|
14
|
+
symbol: symbol,
|
|
15
|
+
symbols: symbols&.to_json,
|
|
16
|
+
permissions: permissions&.to_json,
|
|
17
|
+
showPermissionSets: show_permission_sets,
|
|
18
|
+
symbolStatus: symbol_status
|
|
19
|
+
}.compact
|
|
20
|
+
end
|
|
21
|
+
response.body
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def symbol_price_ticker(symbol: nil, symbols: nil)
|
|
26
|
+
with_rescue do
|
|
27
|
+
response = connection.get do |req|
|
|
28
|
+
req.url "/api/v3/ticker/price"
|
|
29
|
+
req.headers = headers
|
|
30
|
+
req.params = { symbol: symbol, symbols: symbols&.to_json }.compact
|
|
31
|
+
end
|
|
32
|
+
response.body
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def symbol_order_book_ticker(symbol: nil, symbols: nil)
|
|
37
|
+
with_rescue do
|
|
38
|
+
response = connection.get do |req|
|
|
39
|
+
req.url "/api/v3/ticker/bookTicker"
|
|
40
|
+
req.headers = headers
|
|
41
|
+
req.params = { symbol: symbol, symbols: symbols&.to_json }.compact
|
|
42
|
+
end
|
|
43
|
+
response.body
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def candlestick_data(symbol:, interval:, start_time: nil, end_time: nil, time_zone: 0, limit: 500)
|
|
48
|
+
with_rescue do
|
|
49
|
+
response = connection.get do |req|
|
|
50
|
+
req.url "/api/v3/klines"
|
|
51
|
+
req.headers = headers
|
|
52
|
+
req.params = {
|
|
53
|
+
symbol: symbol, interval: interval,
|
|
54
|
+
startTime: start_time, endTime: end_time,
|
|
55
|
+
timeZone: time_zone, limit: limit
|
|
56
|
+
}.compact
|
|
57
|
+
end
|
|
58
|
+
response.body
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def account_information(omit_zero_balances: false, recv_window: 5000)
|
|
63
|
+
with_rescue do
|
|
64
|
+
response = connection.get do |req|
|
|
65
|
+
req.url "/api/v3/account"
|
|
66
|
+
req.headers = headers
|
|
67
|
+
req.params = {
|
|
68
|
+
omitZeroBalances: omit_zero_balances,
|
|
69
|
+
recvWindow: recv_window,
|
|
70
|
+
timestamp: timestamp_ms
|
|
71
|
+
}.compact
|
|
72
|
+
req.params[:signature] = sign_params(req.params)
|
|
73
|
+
end
|
|
74
|
+
response.body
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def account_trade_list(symbol:, order_id: nil, start_time: nil, end_time: nil, from_id: nil, limit: 500, recv_window: 5000)
|
|
79
|
+
with_rescue do
|
|
80
|
+
response = connection.get do |req|
|
|
81
|
+
req.url "/api/v3/myTrades"
|
|
82
|
+
req.headers = headers
|
|
83
|
+
req.params = {
|
|
84
|
+
symbol: symbol, orderId: order_id,
|
|
85
|
+
startTime: start_time, endTime: end_time,
|
|
86
|
+
fromId: from_id, limit: limit,
|
|
87
|
+
recvWindow: recv_window, timestamp: timestamp_ms
|
|
88
|
+
}.compact
|
|
89
|
+
req.params[:signature] = sign_params(req.params)
|
|
90
|
+
end
|
|
91
|
+
response.body
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def query_order(symbol:, order_id: nil, orig_client_order_id: nil, recv_window: 5000)
|
|
96
|
+
with_rescue do
|
|
97
|
+
response = connection.get do |req|
|
|
98
|
+
req.url "/api/v3/order"
|
|
99
|
+
req.headers = headers
|
|
100
|
+
req.params = {
|
|
101
|
+
symbol: symbol, orderId: order_id,
|
|
102
|
+
origClientOrderId: orig_client_order_id,
|
|
103
|
+
recvWindow: recv_window, timestamp: timestamp_ms
|
|
104
|
+
}.compact
|
|
105
|
+
req.params[:signature] = sign_params(req.params)
|
|
106
|
+
end
|
|
107
|
+
response.body
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def all_orders(symbol:, order_id: nil, start_time: nil, end_time: nil, limit: 500, recv_window: 5000)
|
|
112
|
+
with_rescue do
|
|
113
|
+
response = connection.get do |req|
|
|
114
|
+
req.url "/api/v3/allOrders"
|
|
115
|
+
req.headers = headers
|
|
116
|
+
req.params = {
|
|
117
|
+
symbol: symbol, orderId: order_id,
|
|
118
|
+
startTime: start_time, endTime: end_time,
|
|
119
|
+
limit: limit, recvWindow: recv_window, timestamp: timestamp_ms
|
|
120
|
+
}.compact
|
|
121
|
+
req.params[:signature] = sign_params(req.params)
|
|
122
|
+
end
|
|
123
|
+
response.body
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def new_order(symbol:, side:, type:, time_in_force: nil, quantity: nil, quote_order_qty: nil,
|
|
128
|
+
price: nil, new_client_order_id: nil, strategy_id: nil, strategy_type: nil,
|
|
129
|
+
stop_price: nil, trailing_delta: nil, iceberg_qty: nil, new_order_resp_type: nil,
|
|
130
|
+
self_trade_prevention_mode: nil, recv_window: 5000)
|
|
131
|
+
with_rescue do
|
|
132
|
+
response = connection.post do |req|
|
|
133
|
+
req.url "/api/v3/order"
|
|
134
|
+
req.headers = headers
|
|
135
|
+
req.params = {
|
|
136
|
+
symbol: symbol, side: side, type: type,
|
|
137
|
+
timeInForce: time_in_force, quantity: quantity,
|
|
138
|
+
quoteOrderQty: quote_order_qty, price: price,
|
|
139
|
+
newClientOrderId: new_client_order_id,
|
|
140
|
+
strategyId: strategy_id, strategyType: strategy_type,
|
|
141
|
+
stopPrice: stop_price, trailingDelta: trailing_delta,
|
|
142
|
+
icebergQty: iceberg_qty, newOrderRespType: new_order_resp_type,
|
|
143
|
+
selfTradePreventionMode: self_trade_prevention_mode,
|
|
144
|
+
recvWindow: recv_window, timestamp: timestamp_ms
|
|
145
|
+
}.compact
|
|
146
|
+
req.params[:signature] = sign_params(req.params)
|
|
147
|
+
end
|
|
148
|
+
response.body
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def cancel_order(symbol:, order_id: nil, orig_client_order_id: nil, new_client_order_id: nil,
|
|
153
|
+
cancel_restrictions: nil, recv_window: 5000)
|
|
154
|
+
with_rescue do
|
|
155
|
+
response = connection.delete do |req|
|
|
156
|
+
req.url "/api/v3/order"
|
|
157
|
+
req.headers = headers
|
|
158
|
+
req.params = {
|
|
159
|
+
symbol: symbol, orderId: order_id,
|
|
160
|
+
origClientOrderId: orig_client_order_id,
|
|
161
|
+
newClientOrderId: new_client_order_id,
|
|
162
|
+
cancelRestrictions: cancel_restrictions,
|
|
163
|
+
recvWindow: recv_window, timestamp: timestamp_ms
|
|
164
|
+
}.compact
|
|
165
|
+
req.params[:signature] = sign_params(req.params)
|
|
166
|
+
end
|
|
167
|
+
response.body
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def deposit_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: 1000, recv_window: 5000)
|
|
172
|
+
with_rescue do
|
|
173
|
+
response = connection.get do |req|
|
|
174
|
+
req.url "/sapi/v1/capital/deposit/hisrec"
|
|
175
|
+
req.headers = headers
|
|
176
|
+
req.params = {
|
|
177
|
+
coin: coin, status: status,
|
|
178
|
+
startTime: start_time, endTime: end_time,
|
|
179
|
+
offset: offset, limit: limit,
|
|
180
|
+
recvWindow: recv_window, timestamp: timestamp_ms
|
|
181
|
+
}.compact
|
|
182
|
+
req.params[:signature] = sign_params(req.params)
|
|
183
|
+
end
|
|
184
|
+
response.body
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def withdraw_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: 1000, recv_window: 5000)
|
|
189
|
+
with_rescue do
|
|
190
|
+
response = connection.get do |req|
|
|
191
|
+
req.url "/sapi/v1/capital/withdraw/history"
|
|
192
|
+
req.headers = headers
|
|
193
|
+
req.params = {
|
|
194
|
+
coin: coin, status: status,
|
|
195
|
+
startTime: start_time, endTime: end_time,
|
|
196
|
+
offset: offset, limit: limit,
|
|
197
|
+
recvWindow: recv_window, timestamp: timestamp_ms
|
|
198
|
+
}.compact
|
|
199
|
+
req.params[:signature] = sign_params(req.params)
|
|
200
|
+
end
|
|
201
|
+
response.body
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def get_all_coins_information(recv_window: 5000)
|
|
206
|
+
with_rescue do
|
|
207
|
+
response = connection.get do |req|
|
|
208
|
+
req.url "/sapi/v1/capital/config/getall"
|
|
209
|
+
req.headers = headers
|
|
210
|
+
req.params = { recvWindow: recv_window, timestamp: timestamp_ms }
|
|
211
|
+
req.params[:signature] = sign_params(req.params)
|
|
212
|
+
end
|
|
213
|
+
response.body
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def api_description(recv_window: 5000)
|
|
218
|
+
with_rescue do
|
|
219
|
+
response = connection.get do |req|
|
|
220
|
+
req.url "/sapi/v1/account/apiRestrictions"
|
|
221
|
+
req.headers = headers
|
|
222
|
+
req.params = { recvWindow: recv_window, timestamp: timestamp_ms }
|
|
223
|
+
req.params[:signature] = sign_params(req.params)
|
|
224
|
+
end
|
|
225
|
+
response.body
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def get_withdraw_addresses(recv_window: 5000)
|
|
230
|
+
with_rescue do
|
|
231
|
+
response = connection.get do |req|
|
|
232
|
+
req.url "/sapi/v1/capital/withdraw/address/list"
|
|
233
|
+
req.headers = headers
|
|
234
|
+
req.params = { recvWindow: recv_window, timestamp: timestamp_ms }
|
|
235
|
+
req.params[:signature] = sign_params(req.params)
|
|
236
|
+
end
|
|
237
|
+
response.body
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def withdraw(coin:, address:, amount:, network: nil, address_tag: nil, recv_window: 5000)
|
|
242
|
+
with_rescue do
|
|
243
|
+
response = connection.post do |req|
|
|
244
|
+
req.url "/sapi/v1/capital/withdraw/apply"
|
|
245
|
+
req.headers = headers
|
|
246
|
+
req.params = {
|
|
247
|
+
coin: coin, address: address, amount: amount,
|
|
248
|
+
network: network, addressTag: address_tag,
|
|
249
|
+
recvWindow: recv_window, timestamp: timestamp_ms
|
|
250
|
+
}.compact
|
|
251
|
+
req.params[:signature] = sign_params(req.params)
|
|
252
|
+
end
|
|
253
|
+
response.body
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
private
|
|
258
|
+
|
|
259
|
+
def validate_trading_credentials
|
|
260
|
+
# Try cancelling a non-existent order — error -2011 (ORDER_DOES_NOT_EXIST) means key is valid with trade permission
|
|
261
|
+
result = cancel_order(symbol: "ETHBTC", order_id: "9999999999")
|
|
262
|
+
return Result::Failure.new("Invalid trading credentials") if result.failure?
|
|
263
|
+
|
|
264
|
+
code = result.data.is_a?(Hash) ? result.data["code"] : nil
|
|
265
|
+
code == -2011 ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def validate_read_credentials
|
|
269
|
+
result = api_description
|
|
270
|
+
Result.new(data: result.success?, errors: result.success? ? [] : ["Invalid read credentials"])
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def headers
|
|
274
|
+
if authenticated?
|
|
275
|
+
{ "X-MBX-APIKEY": @api_key, Accept: "application/json", "Content-Type": "application/json" }
|
|
276
|
+
else
|
|
277
|
+
{ Accept: "application/json", "Content-Type": "application/json" }
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def sign_params(params)
|
|
282
|
+
return unless @api_secret
|
|
283
|
+
query = Faraday::Utils.build_query(params)
|
|
284
|
+
hmac_sha256(@api_secret, query)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Honeymaker
|
|
4
|
+
module Clients
|
|
5
|
+
class BingX < Client
|
|
6
|
+
URL = "https://open-api.bingx.com"
|
|
7
|
+
|
|
8
|
+
def get_symbols
|
|
9
|
+
get_public("/openApi/spot/v1/common/symbols")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get_ticker(symbol: nil)
|
|
13
|
+
get_public("/openApi/spot/v2/market/ticker", { symbol: symbol })
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get_depth(symbol:, limit: nil)
|
|
17
|
+
get_public("/openApi/spot/v1/market/depth", { symbol: symbol, limit: limit })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_klines(symbol:, interval:, start_time: nil, end_time: nil, limit: nil)
|
|
21
|
+
get_public("/openApi/spot/v1/market/kline", {
|
|
22
|
+
symbol: symbol, interval: interval,
|
|
23
|
+
startTime: start_time, endTime: end_time, limit: limit
|
|
24
|
+
})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get_balances
|
|
28
|
+
get_signed("/openApi/spot/v1/account/balance")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def place_order(symbol:, side:, type:, quantity: nil, quote_order_qty: nil, price: nil,
|
|
32
|
+
time_in_force: nil, client_order_id: nil)
|
|
33
|
+
post_signed("/openApi/spot/v1/trade/order", {
|
|
34
|
+
symbol: symbol, side: side, type: type,
|
|
35
|
+
quantity: quantity, quoteOrderQty: quote_order_qty,
|
|
36
|
+
price: price, timeInForce: time_in_force,
|
|
37
|
+
newClientOrderId: client_order_id
|
|
38
|
+
})
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_order(symbol:, order_id: nil, client_order_id: nil)
|
|
42
|
+
get_signed("/openApi/spot/v1/trade/query", {
|
|
43
|
+
symbol: symbol, orderId: order_id, clientOrderID: client_order_id
|
|
44
|
+
})
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cancel_order(symbol:, order_id: nil, client_order_id: nil)
|
|
48
|
+
post_signed("/openApi/spot/v1/trade/cancel", {
|
|
49
|
+
symbol: symbol, orderId: order_id, clientOrderID: client_order_id
|
|
50
|
+
})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def get_trade_fills(symbol: nil, order_id: nil, start_time: nil, end_time: nil, from_id: nil, limit: nil)
|
|
54
|
+
get_signed("/openApi/spot/v1/trade/fills", {
|
|
55
|
+
symbol: symbol, orderId: order_id,
|
|
56
|
+
startTime: start_time, endTime: end_time, fromId: from_id, limit: limit
|
|
57
|
+
})
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def deposit_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: nil)
|
|
61
|
+
get_signed("/openApi/wallets/v1/capital/deposit/hisrec", {
|
|
62
|
+
coin: coin, status: status,
|
|
63
|
+
startTime: start_time, endTime: end_time, offset: offset, limit: limit
|
|
64
|
+
})
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def withdraw_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: nil, id: nil)
|
|
68
|
+
get_signed("/openApi/wallets/v1/capital/withdraw/history", {
|
|
69
|
+
coin: coin, status: status,
|
|
70
|
+
startTime: start_time, endTime: end_time, offset: offset, limit: limit, id: id
|
|
71
|
+
})
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def withdraw(coin:, address:, amount:, network: nil, wallet_type: nil, tag: nil)
|
|
75
|
+
post_signed("/openApi/wallets/v1/capital/withdraw/apply", {
|
|
76
|
+
coin: coin, address: address, amount: amount,
|
|
77
|
+
network: network, walletType: wallet_type, tag: tag
|
|
78
|
+
})
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def validate_trading_credentials
|
|
84
|
+
result = get_balances
|
|
85
|
+
return Result::Failure.new("Invalid trading credentials") if result.failure?
|
|
86
|
+
result.data["code"]&.to_i&.zero? ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate_read_credentials
|
|
90
|
+
validate_trading_credentials
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def get_public(path, params = {})
|
|
94
|
+
with_rescue do
|
|
95
|
+
response = connection.get do |req|
|
|
96
|
+
req.url path
|
|
97
|
+
req.params = params.compact
|
|
98
|
+
end
|
|
99
|
+
response.body
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def get_signed(path, params = {})
|
|
104
|
+
with_rescue do
|
|
105
|
+
params = params.compact.merge(timestamp: timestamp_ms)
|
|
106
|
+
params[:signature] = hmac_sha256(@api_secret, Faraday::Utils.build_query(params))
|
|
107
|
+
|
|
108
|
+
response = connection.get do |req|
|
|
109
|
+
req.url path
|
|
110
|
+
req.headers = { "X-BX-APIKEY": @api_key }
|
|
111
|
+
req.params = params
|
|
112
|
+
end
|
|
113
|
+
response.body
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def post_signed(path, params = {})
|
|
118
|
+
with_rescue do
|
|
119
|
+
params = params.compact.merge(timestamp: timestamp_ms)
|
|
120
|
+
params[:signature] = hmac_sha256(@api_secret, Faraday::Utils.build_query(params))
|
|
121
|
+
|
|
122
|
+
response = connection.post do |req|
|
|
123
|
+
req.url path
|
|
124
|
+
req.headers = { "X-BX-APIKEY": @api_key, "Content-Type": "application/json" }
|
|
125
|
+
req.params = params
|
|
126
|
+
end
|
|
127
|
+
response.body
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def connection
|
|
132
|
+
@connection ||= build_client_connection(URL, content_type_match: //)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|