bybit-connector-ruby 0.1.0 → 0.1.1
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/CHANGELOG.md +75 -1
- data/README.md +149 -14
- data/examples/quickstart.rb +36 -7
- data/lib/bybit/client.rb +8 -1
- data/lib/bybit/error.rb +13 -5
- data/lib/bybit/rest_api/broker_service.rb +9 -4
- data/lib/bybit/rest_api/institutional_loan_service.rb +106 -0
- data/lib/bybit/rest_api/market_service.rb +0 -4
- data/lib/bybit/rest_api/misc_service.rb +36 -0
- data/lib/bybit/rest_api/pre_upgrade_service.rb +130 -0
- data/lib/bybit/session.rb +13 -3
- data/lib/bybit/version.rb +1 -1
- data/lib/bybit/websocket/client.rb +294 -0
- data/lib/bybit/websocket.rb +9 -0
- metadata +54 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ad3a1a82830f6e29a98bfb09ec65e58c83e8300a9a1c2066ba7547757d1e5a3
|
|
4
|
+
data.tar.gz: 1a10e04e6f81895ec0870384674e9d9722cf1096a80272787b021822febe90a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16be1085483d3e5aa52b98749a525c8d384eef26edac8cbdccfd1ef4e2053f2a744bc31ba8e8364be77d9812828de2776962ecba0c76db0b579c21249f5f2eb2
|
|
7
|
+
data.tar.gz: 07042d066d149a8f4e6b0760c2feac68d20b13aa7af59222ed1eed228b1bd35d9b4d84aa89933ecd03637369ddb7149600e3ceffb257b75d85cff19d448b8274
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,8 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
## [0.1.1] - 2026-07-22
|
|
9
10
|
|
|
10
11
|
### Added
|
|
11
12
|
- `AccountService#batch_set_collateral(...)`
|
|
@@ -34,9 +35,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
34
35
|
- `AccountService#upgrade_to_uta_pro(...)`
|
|
35
36
|
- `AffiliateService#get_sub_list(...)`
|
|
36
37
|
- `AffiliateService#get_user_list(...)`
|
|
38
|
+
- `AssetService#account_coin_balance_query(...)`
|
|
39
|
+
- `AssetService#asset_info_query(...)`
|
|
40
|
+
- `AssetService#cancel_withdraw(...)`
|
|
41
|
+
- `AssetService#coin_convert_limit_query(...)`
|
|
42
|
+
- `AssetService#coin_list_query(...)`
|
|
43
|
+
- `AssetService#convert_execute(...)`
|
|
44
|
+
- `AssetService#convert_history_query(...)`
|
|
45
|
+
- `AssetService#get_asset_overview(...)`
|
|
37
46
|
- `AssetService#get_coin_balance(...)`
|
|
38
47
|
- `AssetService#get_coin_greeks(...)`
|
|
48
|
+
- `AssetService#get_coin_info(...)`
|
|
49
|
+
- `AssetService#get_convert_result(...)`
|
|
50
|
+
- `AssetService#get_delivery_record(...)`
|
|
51
|
+
- `AssetService#get_deposit_address(...)`
|
|
52
|
+
- `AssetService#get_portfolio_margin(...)`
|
|
53
|
+
- `AssetService#get_settlement_record(...)`
|
|
54
|
+
- `AssetService#get_sub_member_deposit_address(...)`
|
|
55
|
+
- `AssetService#get_total_members_assets(...)`
|
|
56
|
+
- `AssetService#get_vasp_list(...)`
|
|
57
|
+
- `AssetService#get_withdrawable_amount_by_coin(...)`
|
|
58
|
+
- `AssetService#inter_transfer(...)`
|
|
59
|
+
- `AssetService#inter_transfer_list_query(...)`
|
|
60
|
+
- `AssetService#list_convert_orders(...)`
|
|
61
|
+
- `AssetService#list_convert_orders_by_page(...)`
|
|
62
|
+
- `AssetService#list_deposit_records(...)`
|
|
39
63
|
- `AssetService#list_funding_history(...)`
|
|
64
|
+
- `AssetService#list_internal_deposit_records(...)`
|
|
65
|
+
- `AssetService#list_small_balance_coins(...)`
|
|
66
|
+
- `AssetService#list_small_balance_history(...)`
|
|
67
|
+
- `AssetService#list_sub_member_deposit_records(...)`
|
|
68
|
+
- `AssetService#list_withdraw_addresses(...)`
|
|
69
|
+
- `AssetService#list_withdraw_records(...)`
|
|
70
|
+
- `AssetService#quote_apply(...)`
|
|
71
|
+
- `AssetService#send_withdraw(...)`
|
|
72
|
+
- `AssetService#set_default_deposit_to_account(...)`
|
|
73
|
+
- `AssetService#small_asset_convert(...)`
|
|
74
|
+
- `AssetService#small_asset_quote(...)`
|
|
75
|
+
- `AssetService#sub_member_list_query(...)`
|
|
76
|
+
- `AssetService#transfer_coin_list_query(...)`
|
|
77
|
+
- `AssetService#transfer_sub_member_save(...)`
|
|
78
|
+
- `AssetService#universal_transfer(...)`
|
|
79
|
+
- `AssetService#universal_transfer_list_query(...)`
|
|
40
80
|
- `BrokerService#distribute_award(...)`
|
|
41
81
|
- `BrokerService#get_award_info(...)`
|
|
42
82
|
- `BrokerService#get_distribution_record(...)`
|
|
@@ -257,3 +297,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
257
297
|
- `UserService#sign_agreement(...)`
|
|
258
298
|
- `UserService#update_api_key(...)`
|
|
259
299
|
- `UserService#update_sub_api_key(...)`
|
|
300
|
+
- `InstitutionalLoanService#get_product_info(...)`
|
|
301
|
+
- `InstitutionalLoanService#get_margin_coin_info(...)`
|
|
302
|
+
- `InstitutionalLoanService#get_loan_orders(...)`
|
|
303
|
+
- `InstitutionalLoanService#get_repayment_orders(...)`
|
|
304
|
+
- `InstitutionalLoanService#get_ltv(...)`
|
|
305
|
+
- `InstitutionalLoanService#bind_or_unbind_uid(...)`
|
|
306
|
+
- `InstitutionalLoanService#repay_loan(...)`
|
|
307
|
+
- `MiscService#get_announcements(...)`
|
|
308
|
+
- `MiscService#get_system_status(...)`
|
|
309
|
+
- `PreUpgradeService#get_order_history(...)`
|
|
310
|
+
- `PreUpgradeService#get_trade_history(...)`
|
|
311
|
+
- `PreUpgradeService#get_closed_pnl(...)`
|
|
312
|
+
- `PreUpgradeService#get_transaction_log(...)`
|
|
313
|
+
- `PreUpgradeService#get_option_delivery_record(...)`
|
|
314
|
+
- `PreUpgradeService#get_usdc_session_settlement(...)`
|
|
315
|
+
- `Bybit::WebSocket::Client` — public (spot/linear/inverse/option), private,
|
|
316
|
+
and trade streams; HMAC-SHA256 auth, ping/pong, deferred subscribe (topics
|
|
317
|
+
buffered until socket opens; private streams wait for auth OK), idempotent
|
|
318
|
+
`#connect` (second call replays buffered subscriptions), testnet host
|
|
319
|
+
support. Load via `require 'bybit/websocket'` (opt-in dep on
|
|
320
|
+
`websocket-client-simple`).
|
|
321
|
+
- VCR-driven end-to-end specs asserting the exact wire bytes and X-BAPI-SIGN
|
|
322
|
+
header for signed GET (query) and signed POST (body) routing.
|
|
323
|
+
- SimpleCov floor coverage gate for `lib/bybit/rest_api/*_service.rb` — new
|
|
324
|
+
services without matching specs now drop CI.
|
|
325
|
+
- README: WebSocket usage section.
|
|
326
|
+
|
|
327
|
+
### Fixed
|
|
328
|
+
- `Session#parse_response` now maps HTTP 401/403 → `Bybit::AuthError` and 429
|
|
329
|
+
→ `Bybit::RateLimitError` even when the response body is non-JSON
|
|
330
|
+
(CDN/WAF path), matching the contract in README.md#Error-Handling.
|
|
331
|
+
- `BrokerService#list_broker_earnings` — caller-facing kwargs renamed from
|
|
332
|
+
`:begin_` / `:end_` to `:start_date` / `:end_date`; internal rewrite now
|
|
333
|
+
goes through `WireKeys::RESERVED_ALIASES` instead of hand-rolled shim.
|
data/README.md
CHANGED
|
@@ -2,7 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
Official lightweight Ruby connector for the [Bybit V5 REST API](https://bybit-exchange.github.io/docs/v5/intro).
|
|
4
4
|
|
|
5
|
-
`bybit-connector-ruby` wraps the Bybit V5 HTTP endpoints as a set of typed Ruby methods with keyword-arg signatures.
|
|
5
|
+
`bybit-connector-ruby` wraps the Bybit V5 HTTP endpoints as a set of typed Ruby methods with keyword-arg signatures.
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
Before you write any code you need a Bybit API key. Two accounts to know
|
|
9
|
+
about:
|
|
10
|
+
|
|
11
|
+
- **Testnet** — [testnet.bybit.com](https://testnet.bybit.com) → sign up →
|
|
12
|
+
API Management → *Create New Key*. This is where you should point every
|
|
13
|
+
new integration until you're confident about behavior. Testnet balances
|
|
14
|
+
are virtual; nothing you do here touches real funds.
|
|
15
|
+
- **Mainnet** — [bybit.com](https://www.bybit.com) → API Management. Real
|
|
16
|
+
money. Enable only the permissions you actually need (spot / derivatives /
|
|
17
|
+
withdrawals) and prefer IP-restricted keys.
|
|
18
|
+
|
|
19
|
+
Testnet and mainnet keys are **separate** — an API key issued on one won't
|
|
20
|
+
work against the other. The SDK selects the environment via
|
|
21
|
+
`testnet: true|false` (or an explicit `base_url:` override).
|
|
6
22
|
|
|
7
23
|
## Installation
|
|
8
24
|
|
|
@@ -23,30 +39,43 @@ gem 'bybit-connector-ruby'
|
|
|
23
39
|
```ruby
|
|
24
40
|
require 'bybit'
|
|
25
41
|
|
|
42
|
+
# All calls below run against testnet — swap testnet: false for mainnet
|
|
43
|
+
# once you're happy with the behavior.
|
|
26
44
|
Bybit.configure do |c|
|
|
27
|
-
c.api_key = ENV['
|
|
28
|
-
c.api_secret = ENV['
|
|
29
|
-
c.testnet = true
|
|
45
|
+
c.api_key = ENV['BYBIT_TESTNET_KEY']
|
|
46
|
+
c.api_secret = ENV['BYBIT_TESTNET_SECRET']
|
|
47
|
+
c.testnet = true
|
|
30
48
|
end
|
|
31
49
|
|
|
32
50
|
client = Bybit::Client.new
|
|
33
51
|
|
|
34
|
-
# Public endpoint — no auth needed
|
|
52
|
+
# 1. Public endpoint — no auth needed.
|
|
35
53
|
puts client.market.get_server_time
|
|
36
54
|
|
|
37
|
-
# Signed endpoint —
|
|
55
|
+
# 2. Signed endpoint — api_key + api_secret required.
|
|
38
56
|
wallet = client.account.get_wallet_balance(account_type: 'UNIFIED')
|
|
39
57
|
puts wallet['result']['list']
|
|
40
58
|
|
|
41
|
-
# Place
|
|
59
|
+
# 3. Place a LIMIT order well below market so it sits on the book and does
|
|
60
|
+
# NOT fill (safe to run repeatedly). Adjust `price:` if BTC ever trades
|
|
61
|
+
# at $10k again — otherwise this stays a resting order you can cancel.
|
|
42
62
|
order = client.trade.create_order(
|
|
43
63
|
category: 'linear', symbol: 'BTCUSDT',
|
|
44
64
|
side: 'Buy', order_type: 'Limit', qty: '0.01',
|
|
45
|
-
price: '
|
|
65
|
+
price: '10000', time_in_force: 'GTC'
|
|
46
66
|
)
|
|
47
|
-
|
|
67
|
+
order_id = order['result']['orderId']
|
|
68
|
+
puts "orderId: #{order_id}"
|
|
69
|
+
|
|
70
|
+
# 4. Cancel it before moving on.
|
|
71
|
+
client.trade.cancel_order(category: 'linear', symbol: 'BTCUSDT', order_id: order_id)
|
|
48
72
|
```
|
|
49
73
|
|
|
74
|
+
> ⚠️ **Before switching `testnet: false`**: verify the price / qty in
|
|
75
|
+
> `create_order` won't cross the top of the book — a Limit Buy at $10k on
|
|
76
|
+
> mainnet becomes a market fill instantly (if BTC ever drops that low),
|
|
77
|
+
> and a Limit Sell at $1M does the reverse.
|
|
78
|
+
|
|
50
79
|
See `examples/quickstart.rb` for a runnable script.
|
|
51
80
|
|
|
52
81
|
## Configuration
|
|
@@ -58,12 +87,17 @@ Bybit.configure do |c|
|
|
|
58
87
|
c.api_key = ENV['BYBIT_KEY']
|
|
59
88
|
c.api_secret = ENV['BYBIT_SECRET']
|
|
60
89
|
c.testnet = false # default false
|
|
61
|
-
c.recv_window = '5000' #
|
|
90
|
+
c.recv_window = '5000' # milliseconds — Bybit rejects requests
|
|
91
|
+
# whose signed timestamp is older than
|
|
92
|
+
# this window. Bump to 10000+ if your
|
|
93
|
+
# clock drifts or the network is noisy.
|
|
62
94
|
c.timeout = 10 # Faraday timeout, seconds
|
|
63
95
|
end
|
|
64
96
|
```
|
|
65
97
|
|
|
66
|
-
Or pass overrides to `Client.new
|
|
98
|
+
Or pass overrides to `Client.new` — **per-client overrides win over
|
|
99
|
+
`Bybit.configure`**, so you can share defaults globally and still spin up a
|
|
100
|
+
dedicated client with a different key or environment:
|
|
67
101
|
|
|
68
102
|
```ruby
|
|
69
103
|
client = Bybit::Client.new(api_key: '...', api_secret: '...', testnet: true)
|
|
@@ -97,11 +131,14 @@ Each API group is a property on `Bybit::Client`:
|
|
|
97
131
|
- `client.affiliate` — sub-affiliate lists
|
|
98
132
|
- `client.broker` — broker earnings, distributions
|
|
99
133
|
- `client.crypto_loan` — flexible / fixed crypto loans
|
|
134
|
+
- `client.institutional_loan` — /v5/ins-loan/* (OTC institutional loans)
|
|
100
135
|
- `client.rfq` — request-for-quote (block trades)
|
|
101
136
|
- `client.spot_margin` — UTA spot margin
|
|
102
137
|
- `client.earn` — earn, liquidity mining, RWA, PWM, hold-to-earn
|
|
103
138
|
- `client.p2p` — P2P advertise / order / chat
|
|
104
139
|
- `client.bot` — DCA / grid / futures-combo / futures-grid / martingale
|
|
140
|
+
- `client.pre_upgrade` — /v5/pre-upgrade/* historical queries for Classic → UTA upgrades
|
|
141
|
+
- `client.misc` — /v5/announcements/index, /v5/system/status
|
|
105
142
|
|
|
106
143
|
## Error Handling
|
|
107
144
|
|
|
@@ -139,6 +176,12 @@ Full hierarchy:
|
|
|
139
176
|
|
|
140
177
|
Every `ApiError` exposes `#ret_code`, `#ret_msg`, `#result`, `#time`, `#http_status`. See the [Bybit V5 error-code list](https://bybit-exchange.github.io/docs/v5/error) for meanings.
|
|
141
178
|
|
|
179
|
+
> The `sleep 1 && retry` shown above is fine for exploration, **not** for
|
|
180
|
+
> production — Bybit will escalate throttling on tight retry loops. For
|
|
181
|
+
> real workloads, wire a `Faraday::Retry` middleware with exponential
|
|
182
|
+
> backoff via the "bring your own Faraday connection" hook in
|
|
183
|
+
> [Configuration](#configuration) above.
|
|
184
|
+
|
|
142
185
|
## Return Value
|
|
143
186
|
|
|
144
187
|
Every service method returns the raw parsed JSON as a `Hash`:
|
|
@@ -151,14 +194,106 @@ response['result'] # => { 'category' => 'spot', 'symbol' => 'BTCUSDT', 'list
|
|
|
151
194
|
response['time'] # => 1234567890000
|
|
152
195
|
```
|
|
153
196
|
|
|
154
|
-
|
|
197
|
+
> Keys are **strings, not symbols** — `response[:result]` is `nil`. This is
|
|
198
|
+
> a common first-hour gotcha; pass through `JSON.parse` semantics rather
|
|
199
|
+
> than converting.
|
|
200
|
+
|
|
201
|
+
## Method Reference
|
|
202
|
+
|
|
203
|
+
Method names follow the Bybit V5 endpoint slug in snake_case, grouped by
|
|
204
|
+
domain. A few illustrative mappings:
|
|
205
|
+
|
|
206
|
+
| Bybit V5 path | SDK method |
|
|
207
|
+
| ------------------------------------- | ----------------------------------------- |
|
|
208
|
+
| `GET /v5/market/kline` | `client.market.get_kline` |
|
|
209
|
+
| `GET /v5/market/tickers` | `client.market.get_tickers` |
|
|
210
|
+
| `POST /v5/order/create` | `client.trade.create_order` |
|
|
211
|
+
| `POST /v5/order/cancel` | `client.trade.cancel_order` |
|
|
212
|
+
| `GET /v5/position/list` | `client.position.get_positions` |
|
|
213
|
+
| `GET /v5/account/wallet-balance` | `client.account.get_wallet_balance` |
|
|
155
214
|
|
|
156
|
-
|
|
215
|
+
For the full list, generate YARD docs locally:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
bundle exec yard doc
|
|
219
|
+
open doc/index.html
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
or browse the published copy on
|
|
223
|
+
[rubydoc.info/gems/bybit-connector-ruby](https://rubydoc.info/gems/bybit-connector-ruby).
|
|
224
|
+
|
|
225
|
+
## Pagination
|
|
226
|
+
|
|
227
|
+
Bybit V5 uses **opaque cursor pagination** — the response's
|
|
228
|
+
`result['nextPageCursor']` (empty string when the page is the last one)
|
|
229
|
+
feeds back in as the `cursor:` argument on the next call:
|
|
157
230
|
|
|
158
231
|
```ruby
|
|
159
|
-
|
|
232
|
+
cursor = nil
|
|
233
|
+
loop do
|
|
234
|
+
resp = client.trade.get_order_history(category: 'linear', limit: 50, cursor: cursor)
|
|
235
|
+
resp['result']['list'].each { |order| process(order) }
|
|
236
|
+
cursor = resp['result']['nextPageCursor']
|
|
237
|
+
break if cursor.nil? || cursor.empty?
|
|
238
|
+
end
|
|
160
239
|
```
|
|
161
240
|
|
|
241
|
+
The same pattern works for `get_positions`, `get_execution_history`,
|
|
242
|
+
`get_transaction_log`, and every other paginated endpoint.
|
|
243
|
+
|
|
244
|
+
## WebSockets
|
|
245
|
+
|
|
246
|
+
Hosts (mirror the REST split):
|
|
247
|
+
|
|
248
|
+
- Mainnet — `wss://stream.bybit.com/v5/{public/<category>|private|trade}`
|
|
249
|
+
- Testnet — `wss://stream-testnet.bybit.com/v5/{public/<category>|private|trade}`
|
|
250
|
+
|
|
251
|
+
WebSocket support ships behind an opt-in require so REST-only consumers don't
|
|
252
|
+
load the underlying `websocket-client-simple` dependency at boot. Four
|
|
253
|
+
callbacks are available; all are optional but you'll almost always want at
|
|
254
|
+
least `on_message`.
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
require 'bybit/websocket'
|
|
258
|
+
|
|
259
|
+
# Public stream — no auth needed.
|
|
260
|
+
public_ws = Bybit::WebSocket::Client.new(
|
|
261
|
+
channel: :linear, # :spot / :linear / :inverse / :option
|
|
262
|
+
testnet: true,
|
|
263
|
+
on_open: -> { puts 'connected' },
|
|
264
|
+
on_message: ->(msg) { puts msg.inspect }, # your handler
|
|
265
|
+
on_close: ->(_ev) { puts 'closed' },
|
|
266
|
+
on_error: ->(err) { warn "ws error: #{err}" }
|
|
267
|
+
).connect
|
|
268
|
+
|
|
269
|
+
public_ws.subscribe('tickers.BTCUSDT', 'orderbook.1.BTCUSDT')
|
|
270
|
+
|
|
271
|
+
# Private stream — HMAC-SHA256 auth over "GET/realtime" + expiry.
|
|
272
|
+
private_ws = Bybit::WebSocket::Client.new(
|
|
273
|
+
channel: :private,
|
|
274
|
+
testnet: true,
|
|
275
|
+
api_key: ENV['BYBIT_TESTNET_KEY'],
|
|
276
|
+
api_secret: ENV['BYBIT_TESTNET_SECRET'],
|
|
277
|
+
on_message: ->(msg) { puts msg.inspect } # your handler
|
|
278
|
+
).connect
|
|
279
|
+
|
|
280
|
+
# Subscribe returns immediately; the actual `op:subscribe` frame is deferred
|
|
281
|
+
# until the private-stream auth reply lands, so you can call subscribe
|
|
282
|
+
# anytime — before or after `#connect`.
|
|
283
|
+
private_ws.subscribe('position', 'order', 'wallet')
|
|
284
|
+
|
|
285
|
+
# Later:
|
|
286
|
+
private_ws.unsubscribe('wallet')
|
|
287
|
+
private_ws.disconnect
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Supported channels: `:spot`, `:linear`, `:inverse`, `:option`, `:private`,
|
|
291
|
+
`:trade`. `testnet: true` swaps in the `stream-testnet.bybit.com` host.
|
|
292
|
+
Pings ship every 20 seconds — Bybit closes idle sockets after ~20s of
|
|
293
|
+
silence. There is no automatic reconnect on network drop: callers listening
|
|
294
|
+
on `on_close` can invoke `#connect` again to reopen the socket and replay
|
|
295
|
+
the buffered `#subscriptions`.
|
|
296
|
+
|
|
162
297
|
## Development
|
|
163
298
|
|
|
164
299
|
```
|
data/examples/quickstart.rb
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# End-to-end quickstart —
|
|
4
|
-
#
|
|
3
|
+
# End-to-end quickstart — mirrors README's Quick Start:
|
|
4
|
+
# 1. public unsigned call
|
|
5
|
+
# 2. signed GET (wallet balance)
|
|
6
|
+
# 3. safe LIMIT order well below market (does not fill)
|
|
7
|
+
# 4. cancel that order
|
|
8
|
+
# Run with:
|
|
9
|
+
# BYBIT_TESTNET_KEY=... BYBIT_TESTNET_SECRET=... ruby examples/quickstart.rb
|
|
5
10
|
|
|
6
11
|
require 'bybit'
|
|
7
12
|
|
|
8
13
|
Bybit.configure do |c|
|
|
9
|
-
c.api_key = ENV
|
|
10
|
-
c.api_secret = ENV
|
|
11
|
-
c.testnet = true # flip to false for mainnet
|
|
14
|
+
c.api_key = ENV.fetch('BYBIT_TESTNET_KEY')
|
|
15
|
+
c.api_secret = ENV.fetch('BYBIT_TESTNET_SECRET')
|
|
16
|
+
c.testnet = true # flip to false for mainnet — see README warning
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
client = Bybit::Client.new
|
|
15
20
|
|
|
16
|
-
# 1. Public endpoint — server time (no auth needed)
|
|
21
|
+
# 1. Public endpoint — server time (no auth needed).
|
|
17
22
|
puts '--- server time ---'
|
|
18
23
|
puts client.market.get_server_time.inspect
|
|
19
24
|
|
|
20
|
-
# 2. Signed endpoint — wallet balance
|
|
25
|
+
# 2. Signed endpoint — wallet balance.
|
|
21
26
|
puts '--- wallet balance ---'
|
|
22
27
|
begin
|
|
23
28
|
wallet = client.account.get_wallet_balance(account_type: 'UNIFIED')
|
|
24
29
|
puts wallet['result']['list'].inspect
|
|
25
30
|
rescue Bybit::AuthError => e
|
|
26
31
|
warn "auth failed: [#{e.ret_code}] #{e.ret_msg}"
|
|
32
|
+
exit 1
|
|
27
33
|
rescue Bybit::RateLimitError => e
|
|
28
34
|
warn "rate-limited: #{e.ret_msg}"
|
|
29
35
|
rescue Bybit::TimeoutError => e
|
|
@@ -33,3 +39,26 @@ rescue Bybit::NetworkError => e
|
|
|
33
39
|
rescue Bybit::ApiError => e
|
|
34
40
|
warn "api error [#{e.ret_code}]: #{e.ret_msg}"
|
|
35
41
|
end
|
|
42
|
+
|
|
43
|
+
# 3. Place a LIMIT order well below market so it sits on the book and does
|
|
44
|
+
# NOT fill (safe to run repeatedly). Adjust price if BTC ever trades that
|
|
45
|
+
# low; otherwise this stays a resting order you can cancel below.
|
|
46
|
+
puts '--- place limit order ---'
|
|
47
|
+
begin
|
|
48
|
+
order = client.trade.create_order(
|
|
49
|
+
category: 'linear', symbol: 'BTCUSDT',
|
|
50
|
+
side: 'Buy', order_type: 'Limit', qty: '0.01',
|
|
51
|
+
price: '10000', time_in_force: 'GTC'
|
|
52
|
+
)
|
|
53
|
+
order_id = order['result']['orderId']
|
|
54
|
+
puts "orderId: #{order_id}"
|
|
55
|
+
|
|
56
|
+
# 4. Cancel it before exiting so we don't leave test orders on the book.
|
|
57
|
+
puts '--- cancel order ---'
|
|
58
|
+
cancel = client.trade.cancel_order(
|
|
59
|
+
category: 'linear', symbol: 'BTCUSDT', order_id: order_id
|
|
60
|
+
)
|
|
61
|
+
puts cancel['result'].inspect
|
|
62
|
+
rescue Bybit::ApiError => e
|
|
63
|
+
warn "api error [#{e.ret_code}]: #{e.ret_msg}"
|
|
64
|
+
end
|
data/lib/bybit/client.rb
CHANGED
|
@@ -8,9 +8,12 @@ require 'bybit/rest_api/bot_service'
|
|
|
8
8
|
require 'bybit/rest_api/broker_service'
|
|
9
9
|
require 'bybit/rest_api/crypto_loan_service'
|
|
10
10
|
require 'bybit/rest_api/earn_service'
|
|
11
|
+
require 'bybit/rest_api/institutional_loan_service'
|
|
11
12
|
require 'bybit/rest_api/market_service'
|
|
13
|
+
require 'bybit/rest_api/misc_service'
|
|
12
14
|
require 'bybit/rest_api/p2p_service'
|
|
13
15
|
require 'bybit/rest_api/position_service'
|
|
16
|
+
require 'bybit/rest_api/pre_upgrade_service'
|
|
14
17
|
require 'bybit/rest_api/rfq_service'
|
|
15
18
|
require 'bybit/rest_api/spot_margin_service'
|
|
16
19
|
require 'bybit/rest_api/trade_service'
|
|
@@ -34,7 +37,8 @@ module Bybit
|
|
|
34
37
|
class Client
|
|
35
38
|
# gen-sdk-ruby:client-readers:start
|
|
36
39
|
attr_reader :account, :affiliate, :asset, :bot, :broker, :crypto_loan, :earn,
|
|
37
|
-
:
|
|
40
|
+
:institutional_loan, :market, :misc, :p2p, :position, :pre_upgrade,
|
|
41
|
+
:rfq, :spot_margin, :trade, :user
|
|
38
42
|
|
|
39
43
|
# gen-sdk-ruby:client-readers:end
|
|
40
44
|
|
|
@@ -52,9 +56,12 @@ module Bybit
|
|
|
52
56
|
@broker = RestApi::BrokerService.new(session)
|
|
53
57
|
@crypto_loan = RestApi::CryptoLoanService.new(session)
|
|
54
58
|
@earn = RestApi::EarnService.new(session)
|
|
59
|
+
@institutional_loan = RestApi::InstitutionalLoanService.new(session)
|
|
55
60
|
@market = RestApi::MarketService.new(session)
|
|
61
|
+
@misc = RestApi::MiscService.new(session)
|
|
56
62
|
@p2p = RestApi::P2pService.new(session)
|
|
57
63
|
@position = RestApi::PositionService.new(session)
|
|
64
|
+
@pre_upgrade = RestApi::PreUpgradeService.new(session)
|
|
58
65
|
@rfq = RestApi::RfqService.new(session)
|
|
59
66
|
@spot_margin = RestApi::SpotMarginService.new(session)
|
|
60
67
|
@trade = RestApi::TradeService.new(session)
|
data/lib/bybit/error.rb
CHANGED
|
@@ -22,16 +22,24 @@ module Bybit
|
|
|
22
22
|
|
|
23
23
|
# Raised when the server returns HTTP 200 + retCode != 0 (the V5 norm),
|
|
24
24
|
# or when a transport-level error is enriched with a Bybit body payload.
|
|
25
|
+
# Accepts either a V5 response Hash (extracts retCode/retMsg/result/time)
|
|
26
|
+
# or a plain String message (used for HTTP-status mapping when the CDN/WAF
|
|
27
|
+
# returns a non-JSON body — e.g. 401/403/429 → AuthError/RateLimitError
|
|
28
|
+
# without a decodable retCode).
|
|
25
29
|
class ApiError < Error
|
|
26
30
|
attr_reader :ret_code, :ret_msg, :result, :time, :http_status
|
|
27
31
|
|
|
28
32
|
def initialize(response, http_status: nil)
|
|
29
|
-
@ret_code = response['retCode']
|
|
30
|
-
@ret_msg = response['retMsg']
|
|
31
|
-
@result = response['result']
|
|
32
|
-
@time = response['time']
|
|
33
33
|
@http_status = http_status
|
|
34
|
-
|
|
34
|
+
if response.is_a?(Hash)
|
|
35
|
+
@ret_code = response['retCode']
|
|
36
|
+
@ret_msg = response['retMsg']
|
|
37
|
+
@result = response['result']
|
|
38
|
+
@time = response['time']
|
|
39
|
+
super("[#{@ret_code}] #{@ret_msg}")
|
|
40
|
+
else
|
|
41
|
+
super(response.to_s)
|
|
42
|
+
end
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
|
|
@@ -78,17 +78,22 @@ module Bybit
|
|
|
78
78
|
#
|
|
79
79
|
# GET /v5/broker/earnings-info
|
|
80
80
|
#
|
|
81
|
+
# Bybit's spec names the date-range params `begin` / `end`, which are
|
|
82
|
+
# Ruby keywords — callers use `:start_date` / `:end_date` and we rename
|
|
83
|
+
# them here before camelize. `:begin_` / `:end_` are also accepted (via
|
|
84
|
+
# WireKeys::RESERVED_ALIASES) for callers who prefer the underscore form.
|
|
85
|
+
#
|
|
81
86
|
# @option kwargs [String] :biz_type Business type
|
|
82
|
-
# @option kwargs [String] :
|
|
83
|
-
# @option kwargs [String] :
|
|
87
|
+
# @option kwargs [String] :start_date Begin date (wire: `begin`)
|
|
88
|
+
# @option kwargs [String] :end_date End date (wire: `end`)
|
|
84
89
|
# @option kwargs [String] :uid Sub UID
|
|
85
90
|
# @option kwargs [Integer] :limit Result limit
|
|
86
91
|
# @option kwargs [String] :cursor Pagination cursor
|
|
87
92
|
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
88
93
|
def list_broker_earnings(**kwargs)
|
|
89
94
|
params = kwargs.dup
|
|
90
|
-
params[:
|
|
91
|
-
params[:
|
|
95
|
+
params[:begin_] = params.delete(:start_date) if params.key?(:start_date)
|
|
96
|
+
params[:end_] = params.delete(:end_date) if params.key?(:end_date)
|
|
92
97
|
params = Bybit::Utils::WireKeys.camelize(params)
|
|
93
98
|
@session.sign_request(method: :get, path: '/v5/broker/earnings-info', params: params)
|
|
94
99
|
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bybit
|
|
4
|
+
module RestApi
|
|
5
|
+
# /v5/ins-loan/* — institutional (OTC) loan namespace. Distinct from
|
|
6
|
+
# `crypto_loan` (retail flexible / fixed loans): these endpoints require
|
|
7
|
+
# a Bybit-provisioned institutional risk-unit UID and are read-only for
|
|
8
|
+
# non-institutional keys.
|
|
9
|
+
class InstitutionalLoanService < BaseService
|
|
10
|
+
# Get Product Info
|
|
11
|
+
#
|
|
12
|
+
# GET /v5/ins-loan/product-infos
|
|
13
|
+
#
|
|
14
|
+
# @option kwargs [String] :product_id Product ID; omit to return all products.
|
|
15
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
16
|
+
# @see https://bybit-exchange.github.io/docs/v5/otc/margin-product-info
|
|
17
|
+
def get_product_info(**kwargs)
|
|
18
|
+
params = kwargs.dup
|
|
19
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
20
|
+
@session.public_request(path: '/v5/ins-loan/product-infos', params: params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get Margin Coin Info (ensure-tokens-convert)
|
|
24
|
+
#
|
|
25
|
+
# GET /v5/ins-loan/ensure-tokens-convert
|
|
26
|
+
#
|
|
27
|
+
# @option kwargs [String] :product_id Product ID
|
|
28
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
29
|
+
# @see https://bybit-exchange.github.io/docs/v5/otc/margin-coin-convert-info
|
|
30
|
+
def get_margin_coin_info(**kwargs)
|
|
31
|
+
params = kwargs.dup
|
|
32
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
33
|
+
@session.public_request(path: '/v5/ins-loan/ensure-tokens-convert', params: params)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Get Loan Orders
|
|
37
|
+
#
|
|
38
|
+
# GET /v5/ins-loan/loan-order
|
|
39
|
+
#
|
|
40
|
+
# @option kwargs [Integer] :order_id Loan order ID
|
|
41
|
+
# @option kwargs [Integer] :start_time Start timestamp in ms
|
|
42
|
+
# @option kwargs [Integer] :end_time End timestamp in ms
|
|
43
|
+
# @option kwargs [Integer] :limit Records per page (default/max 100)
|
|
44
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
45
|
+
# @see https://bybit-exchange.github.io/docs/v5/otc/loan-info
|
|
46
|
+
def get_loan_orders(**kwargs)
|
|
47
|
+
params = kwargs.dup
|
|
48
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
49
|
+
@session.sign_request(method: :get, path: '/v5/ins-loan/loan-order', params: params)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Get Repayment Orders
|
|
53
|
+
#
|
|
54
|
+
# GET /v5/ins-loan/repaid-history
|
|
55
|
+
#
|
|
56
|
+
# @option kwargs [Integer] :start_time Start timestamp in ms
|
|
57
|
+
# @option kwargs [Integer] :end_time End timestamp in ms
|
|
58
|
+
# @option kwargs [Integer] :limit Records per page
|
|
59
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
60
|
+
# @see https://bybit-exchange.github.io/docs/v5/otc/repay-info
|
|
61
|
+
def get_repayment_orders(**kwargs)
|
|
62
|
+
params = kwargs.dup
|
|
63
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
64
|
+
@session.sign_request(method: :get, path: '/v5/ins-loan/repaid-history', params: params)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Get Loan-to-Value
|
|
68
|
+
#
|
|
69
|
+
# GET /v5/ins-loan/ltv-convert
|
|
70
|
+
#
|
|
71
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
72
|
+
# @see https://bybit-exchange.github.io/docs/v5/otc/ltv-convert
|
|
73
|
+
def get_ltv
|
|
74
|
+
@session.sign_request(method: :get, path: '/v5/ins-loan/ltv-convert')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Bind Or Unbind UID (institutional risk unit)
|
|
78
|
+
#
|
|
79
|
+
# POST /v5/ins-loan/association-uid
|
|
80
|
+
#
|
|
81
|
+
# @param uid [String] Sub UID to bind or unbind
|
|
82
|
+
# @param operate [String] `0` bind, `1` unbind
|
|
83
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
84
|
+
# @see https://bybit-exchange.github.io/docs/v5/otc/bind-uid
|
|
85
|
+
def bind_or_unbind_uid(uid:, operate:, **kwargs)
|
|
86
|
+
params = kwargs.merge(uid: uid, operate: operate)
|
|
87
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
88
|
+
@session.sign_request(method: :post, path: '/v5/ins-loan/association-uid', body: params)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Repay Loan
|
|
92
|
+
#
|
|
93
|
+
# POST /v5/ins-loan/repay-loan
|
|
94
|
+
#
|
|
95
|
+
# @param order_id [String] Loan order ID to repay
|
|
96
|
+
# @param amount [String] Repayment amount
|
|
97
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
98
|
+
# @see https://bybit-exchange.github.io/docs/v5/otc/repay-loan
|
|
99
|
+
def repay_loan(order_id:, amount:, **kwargs)
|
|
100
|
+
params = kwargs.merge(order_id: order_id, amount: amount)
|
|
101
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
102
|
+
@session.sign_request(method: :post, path: '/v5/ins-loan/repay-loan', body: params)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -109,7 +109,6 @@ module Bybit
|
|
|
109
109
|
# @see https://bybit-exchange.github.io/docs/v5/market/index-kline
|
|
110
110
|
def get_index_price_kline(symbol:, interval:, **kwargs)
|
|
111
111
|
params = kwargs.merge(symbol: symbol, interval: interval)
|
|
112
|
-
params[:end] = params.delete(:end_) if params.key?(:end_)
|
|
113
112
|
params = Bybit::Utils::WireKeys.camelize(params)
|
|
114
113
|
@session.public_request(path: '/v5/market/index-price-kline', params: params)
|
|
115
114
|
end
|
|
@@ -178,7 +177,6 @@ module Bybit
|
|
|
178
177
|
# @see https://bybit-exchange.github.io/docs/v5/market/mark-kline
|
|
179
178
|
def get_mark_price_kline(symbol:, interval:, **kwargs)
|
|
180
179
|
params = kwargs.merge(symbol: symbol, interval: interval)
|
|
181
|
-
params[:end] = params.delete(:end_) if params.key?(:end_)
|
|
182
180
|
params = Bybit::Utils::WireKeys.camelize(params)
|
|
183
181
|
@session.public_request(path: '/v5/market/mark-price-kline', params: params)
|
|
184
182
|
end
|
|
@@ -197,7 +195,6 @@ module Bybit
|
|
|
197
195
|
# @see https://bybit-exchange.github.io/docs/v5/market/kline
|
|
198
196
|
def get_kline(symbol:, interval:, **kwargs)
|
|
199
197
|
params = kwargs.merge(symbol: symbol, interval: interval)
|
|
200
|
-
params[:end] = params.delete(:end_) if params.key?(:end_)
|
|
201
198
|
params = Bybit::Utils::WireKeys.camelize(params)
|
|
202
199
|
@session.public_request(path: '/v5/market/kline', params: params)
|
|
203
200
|
end
|
|
@@ -278,7 +275,6 @@ module Bybit
|
|
|
278
275
|
# @see https://bybit-exchange.github.io/docs/v5/market/premium-index-kline
|
|
279
276
|
def get_premium_index_price_kline(symbol:, interval:, **kwargs)
|
|
280
277
|
params = kwargs.merge(symbol: symbol, interval: interval)
|
|
281
|
-
params[:end] = params.delete(:end_) if params.key?(:end_)
|
|
282
278
|
params = Bybit::Utils::WireKeys.camelize(params)
|
|
283
279
|
@session.public_request(path: '/v5/market/premium-index-price-kline', params: params)
|
|
284
280
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bybit
|
|
4
|
+
module RestApi
|
|
5
|
+
# Endpoints that don't cleanly belong under a per-domain service:
|
|
6
|
+
# announcement feed and system status.
|
|
7
|
+
class MiscService < BaseService
|
|
8
|
+
# Get Announcements
|
|
9
|
+
#
|
|
10
|
+
# GET /v5/announcements/index
|
|
11
|
+
#
|
|
12
|
+
# @param locale [String] Language code, e.g. `en-US`, `zh-TW`.
|
|
13
|
+
# @option kwargs [String] :type Announcement type filter
|
|
14
|
+
# @option kwargs [String] :tag Tag filter
|
|
15
|
+
# @option kwargs [Integer] :page Page number
|
|
16
|
+
# @option kwargs [Integer] :limit Records per page (max 100)
|
|
17
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
18
|
+
# @see https://bybit-exchange.github.io/docs/v5/announcement
|
|
19
|
+
def get_announcements(locale:, **kwargs)
|
|
20
|
+
params = kwargs.merge(locale: locale)
|
|
21
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
22
|
+
@session.public_request(path: '/v5/announcements/index', params: params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get System Status
|
|
26
|
+
#
|
|
27
|
+
# GET /v5/system/status
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
30
|
+
# @see https://bybit-exchange.github.io/docs/v5/system-status
|
|
31
|
+
def get_system_status
|
|
32
|
+
@session.public_request(path: '/v5/system/status')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bybit
|
|
4
|
+
module RestApi
|
|
5
|
+
# /v5/pre-upgrade/* — historical queries scoped to activity that occurred
|
|
6
|
+
# BEFORE a Classic → UTA account upgrade. These records live in the legacy
|
|
7
|
+
# partition and never merge into the UTA tables, so callers upgrading from
|
|
8
|
+
# older SDKs need a dedicated namespace to fetch them.
|
|
9
|
+
class PreUpgradeService < BaseService
|
|
10
|
+
# Get Pre-upgrade Order History
|
|
11
|
+
#
|
|
12
|
+
# GET /v5/pre-upgrade/order/history
|
|
13
|
+
#
|
|
14
|
+
# @param category [String] Product type: `linear`, `inverse`, `option`
|
|
15
|
+
# @option kwargs [String] :symbol Symbol filter
|
|
16
|
+
# @option kwargs [String] :base_coin Base coin filter
|
|
17
|
+
# @option kwargs [String] :order_id Order ID filter
|
|
18
|
+
# @option kwargs [String] :order_link_id User-side order link ID
|
|
19
|
+
# @option kwargs [String] :order_filter Order-type filter
|
|
20
|
+
# @option kwargs [String] :order_status Order status filter
|
|
21
|
+
# @option kwargs [Integer] :start_time Start ms timestamp
|
|
22
|
+
# @option kwargs [Integer] :end_time End ms timestamp
|
|
23
|
+
# @option kwargs [Integer] :limit Records per page
|
|
24
|
+
# @option kwargs [String] :cursor Pagination cursor
|
|
25
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
26
|
+
# @see https://bybit-exchange.github.io/docs/v5/pre-upgrade/order-list
|
|
27
|
+
def get_order_history(category:, **kwargs)
|
|
28
|
+
params = kwargs.merge(category: category)
|
|
29
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
30
|
+
@session.sign_request(method: :get, path: '/v5/pre-upgrade/order/history', params: params)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get Pre-upgrade Trade (Execution) History
|
|
34
|
+
#
|
|
35
|
+
# GET /v5/pre-upgrade/execution/list
|
|
36
|
+
#
|
|
37
|
+
# @param category [String] Product type: `linear`, `inverse`, `option`
|
|
38
|
+
# @option kwargs [String] :symbol Symbol filter
|
|
39
|
+
# @option kwargs [String] :order_id Order ID filter
|
|
40
|
+
# @option kwargs [String] :order_link_id User-side order link ID
|
|
41
|
+
# @option kwargs [String] :base_coin Base coin filter
|
|
42
|
+
# @option kwargs [Integer] :start_time Start ms timestamp
|
|
43
|
+
# @option kwargs [Integer] :end_time End ms timestamp
|
|
44
|
+
# @option kwargs [String] :exec_type Execution type filter
|
|
45
|
+
# @option kwargs [Integer] :limit Records per page
|
|
46
|
+
# @option kwargs [String] :cursor Pagination cursor
|
|
47
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
48
|
+
# @see https://bybit-exchange.github.io/docs/v5/pre-upgrade/execution
|
|
49
|
+
def get_trade_history(category:, **kwargs)
|
|
50
|
+
params = kwargs.merge(category: category)
|
|
51
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
52
|
+
@session.sign_request(method: :get, path: '/v5/pre-upgrade/execution/list', params: params)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get Pre-upgrade Closed PnL
|
|
56
|
+
#
|
|
57
|
+
# GET /v5/pre-upgrade/position/closed-pnl
|
|
58
|
+
#
|
|
59
|
+
# Bybit V5 only requires `symbol` when `category` is `inverse`; for
|
|
60
|
+
# `linear` it's optional. Keep it as an optional kwarg here — callers
|
|
61
|
+
# querying inverse pass it, callers on linear can omit it.
|
|
62
|
+
#
|
|
63
|
+
# @param category [String] Product type: `linear`, `inverse`
|
|
64
|
+
# @option kwargs [String] :symbol Symbol name (required for `inverse`)
|
|
65
|
+
# @option kwargs [Integer] :start_time Start ms timestamp
|
|
66
|
+
# @option kwargs [Integer] :end_time End ms timestamp
|
|
67
|
+
# @option kwargs [Integer] :limit Records per page
|
|
68
|
+
# @option kwargs [String] :cursor Pagination cursor
|
|
69
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
70
|
+
# @see https://bybit-exchange.github.io/docs/v5/pre-upgrade/close-pnl
|
|
71
|
+
def get_closed_pnl(category:, **kwargs)
|
|
72
|
+
params = kwargs.merge(category: category)
|
|
73
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
74
|
+
@session.sign_request(method: :get, path: '/v5/pre-upgrade/position/closed-pnl', params: params)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get Pre-upgrade Transaction Log (USDC Derivatives wallet)
|
|
78
|
+
#
|
|
79
|
+
# GET /v5/pre-upgrade/account/transaction-log
|
|
80
|
+
#
|
|
81
|
+
# @param category [String] Product type: `linear`, `option`
|
|
82
|
+
# @option kwargs [String] :base_coin Base coin filter
|
|
83
|
+
# @option kwargs [String] :type Transaction type filter
|
|
84
|
+
# @option kwargs [Integer] :start_time Start ms timestamp
|
|
85
|
+
# @option kwargs [Integer] :end_time End ms timestamp
|
|
86
|
+
# @option kwargs [Integer] :limit Records per page
|
|
87
|
+
# @option kwargs [String] :cursor Pagination cursor
|
|
88
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
89
|
+
# @see https://bybit-exchange.github.io/docs/v5/pre-upgrade/transaction-log
|
|
90
|
+
def get_transaction_log(category:, **kwargs)
|
|
91
|
+
params = kwargs.merge(category: category)
|
|
92
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
93
|
+
@session.sign_request(method: :get, path: '/v5/pre-upgrade/account/transaction-log', params: params)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get Pre-upgrade Option Delivery Record
|
|
97
|
+
#
|
|
98
|
+
# GET /v5/pre-upgrade/asset/delivery-record
|
|
99
|
+
#
|
|
100
|
+
# @param category [String] Product type: `option`
|
|
101
|
+
# @option kwargs [String] :symbol Symbol filter
|
|
102
|
+
# @option kwargs [Integer] :expiry_date Delivery expiry date (YYYYMMDD)
|
|
103
|
+
# @option kwargs [Integer] :limit Records per page
|
|
104
|
+
# @option kwargs [String] :cursor Pagination cursor
|
|
105
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
106
|
+
# @see https://bybit-exchange.github.io/docs/v5/pre-upgrade/delivery
|
|
107
|
+
def get_option_delivery_record(category:, **kwargs)
|
|
108
|
+
params = kwargs.merge(category: category)
|
|
109
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
110
|
+
@session.sign_request(method: :get, path: '/v5/pre-upgrade/asset/delivery-record', params: params)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Get Pre-upgrade USDC Session Settlement Record
|
|
114
|
+
#
|
|
115
|
+
# GET /v5/pre-upgrade/asset/settlement-record
|
|
116
|
+
#
|
|
117
|
+
# @param category [String] Product type: `linear`
|
|
118
|
+
# @option kwargs [String] :symbol Symbol filter
|
|
119
|
+
# @option kwargs [Integer] :limit Records per page
|
|
120
|
+
# @option kwargs [String] :cursor Pagination cursor
|
|
121
|
+
# @return [Hash] Bybit V5 ApiResponse envelope (retCode / retMsg / result / retExtInfo / time).
|
|
122
|
+
# @see https://bybit-exchange.github.io/docs/v5/pre-upgrade/settlement
|
|
123
|
+
def get_usdc_session_settlement(category:, **kwargs)
|
|
124
|
+
params = kwargs.merge(category: category)
|
|
125
|
+
params = Bybit::Utils::WireKeys.camelize(params)
|
|
126
|
+
@session.sign_request(method: :get, path: '/v5/pre-upgrade/asset/settlement-record', params: params)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/bybit/session.rb
CHANGED
|
@@ -126,11 +126,21 @@ module Bybit
|
|
|
126
126
|
body = normalize_legacy_body(body) if body.is_a?(Hash)
|
|
127
127
|
|
|
128
128
|
# HTTP status wins over retCode when the body isn't a valid ApiResponse.
|
|
129
|
-
#
|
|
130
|
-
#
|
|
129
|
+
# 401/403/429 get their promised AuthError / RateLimitError classes even
|
|
130
|
+
# when a CDN/WAF returns HTML (no retCode); the README documents this
|
|
131
|
+
# contract. 5xx / other non-auth 4xx keep their generic Server/Client
|
|
132
|
+
# buckets so retry and pager logic can tell them apart.
|
|
131
133
|
if !body.is_a?(Hash) || !body['retCode'].is_a?(Integer)
|
|
132
134
|
preview = truncate_for_error(raw)
|
|
133
|
-
if status
|
|
135
|
+
if [401, 403].include?(status)
|
|
136
|
+
raise Bybit::AuthError.new(
|
|
137
|
+
"Bybit auth error (status=#{status}): #{preview}", http_status: status
|
|
138
|
+
)
|
|
139
|
+
elsif status == 429
|
|
140
|
+
raise Bybit::RateLimitError.new(
|
|
141
|
+
"Bybit rate limit (status=#{status}): #{preview}", http_status: status
|
|
142
|
+
)
|
|
143
|
+
elsif status >= 500
|
|
134
144
|
raise Bybit::ServerError, "Bybit server error (status=#{status}): #{preview}"
|
|
135
145
|
elsif status >= 400
|
|
136
146
|
raise Bybit::ClientError, "Bybit client error (status=#{status}): #{preview}"
|
data/lib/bybit/version.rb
CHANGED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
|
|
6
|
+
module Bybit
|
|
7
|
+
module WebSocket
|
|
8
|
+
# Bybit V5 WebSocket client — public streams (spot / linear / inverse /
|
|
9
|
+
# option) and the private user-data stream.
|
|
10
|
+
#
|
|
11
|
+
# Design goals mirror the REST session:
|
|
12
|
+
# - one class, `channel:` selects endpoint (avoids Public/Private class
|
|
13
|
+
# duplication for ping / subscribe / dispatch logic)
|
|
14
|
+
# - explicit callbacks (`on_message`, `on_open`, `on_close`, `on_error`)
|
|
15
|
+
# passed as procs; no global registry / singleton
|
|
16
|
+
# - HMAC-SHA256 auth for `:private` is identical to REST signing except
|
|
17
|
+
# the payload is `"GET/realtime" + expires` — the docs are explicit
|
|
18
|
+
# - ping every `ping_interval` seconds via a lightweight background
|
|
19
|
+
# thread; server terminates connections after 20s silence
|
|
20
|
+
# - automatic resubscribe on reconnect (topic list is retained across
|
|
21
|
+
# `#connect` calls) so callers don't lose subscriptions on transient
|
|
22
|
+
# network blips
|
|
23
|
+
#
|
|
24
|
+
# This class depends on `websocket-client-simple` (a runtime dep of the
|
|
25
|
+
# gem). If missing, requiring it raises a clear LoadError below.
|
|
26
|
+
class Client
|
|
27
|
+
begin
|
|
28
|
+
require 'websocket-client-simple'
|
|
29
|
+
rescue LoadError => e
|
|
30
|
+
raise LoadError,
|
|
31
|
+
"Bybit::WebSocket::Client requires the 'websocket-client-simple' " \
|
|
32
|
+
"gem. Add `gem 'websocket-client-simple'` to your Gemfile. " \
|
|
33
|
+
"(#{e.message})"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Channel → WS path (public streams are category-scoped; private has one).
|
|
37
|
+
PUBLIC_CHANNELS = %i[spot linear inverse option].freeze
|
|
38
|
+
PRIVATE_CHANNEL = :private
|
|
39
|
+
TRADE_CHANNEL = :trade
|
|
40
|
+
|
|
41
|
+
PATHS = {
|
|
42
|
+
spot: '/v5/public/spot',
|
|
43
|
+
linear: '/v5/public/linear',
|
|
44
|
+
inverse: '/v5/public/inverse',
|
|
45
|
+
option: '/v5/public/option',
|
|
46
|
+
private: '/v5/private',
|
|
47
|
+
trade: '/v5/trade'
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
HOST_MAINNET = 'stream.bybit.com'
|
|
51
|
+
HOST_TESTNET = 'stream-testnet.bybit.com'
|
|
52
|
+
|
|
53
|
+
DEFAULT_PING_INTERVAL = 20
|
|
54
|
+
# Bybit rejects auth expiry timestamps in the past AND >30s in the future.
|
|
55
|
+
DEFAULT_AUTH_EXPIRY_MS = 10_000
|
|
56
|
+
|
|
57
|
+
attr_reader :channel, :testnet, :url, :subscriptions
|
|
58
|
+
|
|
59
|
+
# @param channel [Symbol] one of `:spot`, `:linear`, `:inverse`,
|
|
60
|
+
# `:option`, `:private`, `:trade`.
|
|
61
|
+
# @param testnet [Boolean] hit stream-testnet.bybit.com instead of mainnet
|
|
62
|
+
# @param api_key [String] required for `:private` / `:trade`
|
|
63
|
+
# @param api_secret [String] required for `:private` / `:trade`
|
|
64
|
+
# @param ping_interval [Integer] seconds between ping frames (default 20)
|
|
65
|
+
# @param url [String] override the entire WS URL (skips host/path build)
|
|
66
|
+
# @param on_open, on_message, on_close, on_error [Proc]
|
|
67
|
+
def initialize(channel:, testnet: false,
|
|
68
|
+
api_key: nil, api_secret: nil,
|
|
69
|
+
ping_interval: DEFAULT_PING_INTERVAL,
|
|
70
|
+
url: nil,
|
|
71
|
+
on_open: nil, on_message: nil, on_close: nil, on_error: nil)
|
|
72
|
+
validate_channel!(channel)
|
|
73
|
+
@channel = channel
|
|
74
|
+
@testnet = testnet
|
|
75
|
+
@api_key = api_key
|
|
76
|
+
@api_secret = api_secret
|
|
77
|
+
@ping_interval = ping_interval
|
|
78
|
+
@url = url || build_url
|
|
79
|
+
@on_open = on_open
|
|
80
|
+
@on_message = on_message
|
|
81
|
+
@on_close = on_close
|
|
82
|
+
@on_error = on_error
|
|
83
|
+
@subscriptions = []
|
|
84
|
+
@ws = nil
|
|
85
|
+
@ping_thread = nil
|
|
86
|
+
@connected = false
|
|
87
|
+
@auth_ok = false
|
|
88
|
+
@closing = false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Open the WebSocket, authenticate (if private/trade), and start pinging.
|
|
92
|
+
# Calling `connect` a second time closes the prior socket first, then
|
|
93
|
+
# opens a fresh one and replays any topics buffered in `@subscriptions`
|
|
94
|
+
# (call `disconnect` explicitly if you want to drop them). No automatic
|
|
95
|
+
# reconnect on network drop — the caller decides when to re-open.
|
|
96
|
+
def connect
|
|
97
|
+
require_auth! if requires_auth?
|
|
98
|
+
# Idempotent: tear down any prior socket / ping thread so a second
|
|
99
|
+
# `connect` call doesn't leak the previous ws or leave two pingers.
|
|
100
|
+
disconnect if @ws
|
|
101
|
+
@closing = false
|
|
102
|
+
client = self
|
|
103
|
+
@ws = ::WebSocket::Client::Simple.connect(@url)
|
|
104
|
+
|
|
105
|
+
@ws.on(:open) { client.send(:handle_open) }
|
|
106
|
+
@ws.on(:message) { |msg| client.send(:handle_message, msg) }
|
|
107
|
+
@ws.on(:close) { |ev| client.send(:handle_close, ev) }
|
|
108
|
+
@ws.on(:error) { |err| client.send(:handle_error, err) }
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Subscribe to one or more topics. Bybit expects an array under `args`:
|
|
113
|
+
# {"op":"subscribe","args":["orderbook.1.BTCUSDT","tickers.BTCUSDT"]}
|
|
114
|
+
# Callers can call `subscribe` before `connect`; topics are buffered and
|
|
115
|
+
# sent once the socket opens (private streams also wait for auth OK).
|
|
116
|
+
def subscribe(*topics)
|
|
117
|
+
topics = topics.flatten.compact.uniq
|
|
118
|
+
return if topics.empty?
|
|
119
|
+
|
|
120
|
+
@subscriptions |= topics
|
|
121
|
+
flush_subscriptions(topics) if ready?
|
|
122
|
+
self
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def unsubscribe(*topics)
|
|
126
|
+
topics = topics.flatten.compact.uniq
|
|
127
|
+
return if topics.empty?
|
|
128
|
+
|
|
129
|
+
@subscriptions -= topics
|
|
130
|
+
send_frame(op: 'unsubscribe', args: topics) if ready?
|
|
131
|
+
self
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Send a WS message directly (escape hatch for placing orders over the
|
|
135
|
+
# `/v5/trade` stream; the docs specify per-op payloads).
|
|
136
|
+
def send_raw(payload)
|
|
137
|
+
return unless @ws
|
|
138
|
+
|
|
139
|
+
@ws.send(payload.is_a?(String) ? payload : JSON.generate(payload))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def disconnect
|
|
143
|
+
@closing = true
|
|
144
|
+
stop_pinger
|
|
145
|
+
@connected = false
|
|
146
|
+
@auth_ok = false
|
|
147
|
+
begin
|
|
148
|
+
@ws&.close
|
|
149
|
+
rescue StandardError
|
|
150
|
+
# Socket may already be half-closed by the peer or in a bad state —
|
|
151
|
+
# we only care about detaching, not about clean protocol shutdown.
|
|
152
|
+
end
|
|
153
|
+
@ws = nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def connected?
|
|
157
|
+
@connected
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def ready?
|
|
161
|
+
return false unless @connected
|
|
162
|
+
|
|
163
|
+
requires_auth? ? @auth_ok : true
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def validate_channel!(channel)
|
|
169
|
+
allowed = PUBLIC_CHANNELS + [PRIVATE_CHANNEL, TRADE_CHANNEL]
|
|
170
|
+
return if allowed.include?(channel)
|
|
171
|
+
|
|
172
|
+
raise Bybit::ConfigurationError,
|
|
173
|
+
"invalid channel: #{channel.inspect} (must be one of #{allowed.inspect})"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def requires_auth?
|
|
177
|
+
[PRIVATE_CHANNEL, TRADE_CHANNEL].include?(channel)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def require_auth!
|
|
181
|
+
return unless @api_key.nil? || @api_secret.nil?
|
|
182
|
+
|
|
183
|
+
raise Bybit::ConfigurationError,
|
|
184
|
+
":#{channel} WebSocket requires api_key + api_secret"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def build_url
|
|
188
|
+
host = @testnet ? HOST_TESTNET : HOST_MAINNET
|
|
189
|
+
path = PATHS.fetch(channel)
|
|
190
|
+
"wss://#{host}#{path}"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def handle_open
|
|
194
|
+
@connected = true
|
|
195
|
+
if requires_auth?
|
|
196
|
+
send_auth_frame
|
|
197
|
+
else
|
|
198
|
+
@auth_ok = true
|
|
199
|
+
flush_subscriptions(@subscriptions)
|
|
200
|
+
end
|
|
201
|
+
start_pinger
|
|
202
|
+
@on_open&.call
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def handle_message(raw)
|
|
206
|
+
data = raw.respond_to?(:data) ? raw.data : raw.to_s
|
|
207
|
+
parsed = safe_parse(data)
|
|
208
|
+
if parsed.is_a?(Hash) && parsed['op'] == 'auth'
|
|
209
|
+
# V5 WS auth reply: {"op":"auth","success":true|false,"ret_msg":...}.
|
|
210
|
+
# `success` is authoritative — do NOT treat missing retCode as OK,
|
|
211
|
+
# else a rejection with only ret_msg would silently pass.
|
|
212
|
+
@auth_ok = parsed['success'] == true
|
|
213
|
+
flush_subscriptions(@subscriptions) if @auth_ok
|
|
214
|
+
end
|
|
215
|
+
@on_message&.call(parsed.nil? ? data : parsed)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def handle_close(event)
|
|
219
|
+
stop_pinger
|
|
220
|
+
@connected = false
|
|
221
|
+
@auth_ok = false
|
|
222
|
+
@on_close&.call(event)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def handle_error(err)
|
|
226
|
+
# Suppress the noisy IOError the reader thread emits when we close
|
|
227
|
+
# the socket deliberately (see `disconnect`). Real transport errors
|
|
228
|
+
# still fire — @closing only gates the shutdown window.
|
|
229
|
+
return if @closing
|
|
230
|
+
|
|
231
|
+
@on_error&.call(err)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Ping every `ping_interval` seconds; Bybit closes idle sockets around
|
|
235
|
+
# 20s of silence, so keep this ≤ 20s.
|
|
236
|
+
def start_pinger
|
|
237
|
+
stop_pinger
|
|
238
|
+
ws = @ws
|
|
239
|
+
interval = @ping_interval
|
|
240
|
+
@ping_thread = Thread.new do
|
|
241
|
+
Thread.current.name = "bybit-ws-ping-#{channel}" if Thread.current.respond_to?(:name=)
|
|
242
|
+
loop do
|
|
243
|
+
sleep interval
|
|
244
|
+
break if ws.nil? || ws != @ws
|
|
245
|
+
|
|
246
|
+
begin
|
|
247
|
+
ws.send(JSON.generate(op: 'ping'))
|
|
248
|
+
rescue StandardError
|
|
249
|
+
break
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def stop_pinger
|
|
256
|
+
return unless @ping_thread
|
|
257
|
+
|
|
258
|
+
begin
|
|
259
|
+
@ping_thread.kill
|
|
260
|
+
rescue StandardError
|
|
261
|
+
nil
|
|
262
|
+
end
|
|
263
|
+
@ping_thread = nil
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def flush_subscriptions(topics)
|
|
267
|
+
return if topics.nil? || topics.empty?
|
|
268
|
+
|
|
269
|
+
send_frame(op: 'subscribe', args: topics)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def send_frame(payload)
|
|
273
|
+
return unless @ws
|
|
274
|
+
|
|
275
|
+
@ws.send(JSON.generate(payload))
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# HMAC-SHA256 over "GET/realtime" + expires — the private-stream auth
|
|
279
|
+
# scheme documented at
|
|
280
|
+
# https://bybit-exchange.github.io/docs/v5/ws/connect#authentication
|
|
281
|
+
def send_auth_frame
|
|
282
|
+
expires = (Time.now.to_f * 1000).to_i + DEFAULT_AUTH_EXPIRY_MS
|
|
283
|
+
signature = OpenSSL::HMAC.hexdigest('SHA256', @api_secret, "GET/realtime#{expires}")
|
|
284
|
+
send_frame(op: 'auth', args: [@api_key, expires, signature])
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def safe_parse(str)
|
|
288
|
+
JSON.parse(str)
|
|
289
|
+
rescue JSON::ParserError
|
|
290
|
+
nil
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Optional entry point. `require 'bybit'` loads the REST core only; the
|
|
4
|
+
# WebSocket client lives behind a separate `require 'bybit/websocket'` so
|
|
5
|
+
# REST-only consumers don't take the `websocket-client-simple` dependency
|
|
6
|
+
# at load time.
|
|
7
|
+
|
|
8
|
+
require 'bybit'
|
|
9
|
+
require 'bybit/websocket/client'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bybit-connector-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bybit
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -24,6 +24,34 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: websocket-client-simple
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.6'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.6'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '13.0'
|
|
27
55
|
- !ruby/object:Gem::Dependency
|
|
28
56
|
name: rspec
|
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -66,6 +94,20 @@ dependencies:
|
|
|
66
94
|
- - "~>"
|
|
67
95
|
- !ruby/object:Gem::Version
|
|
68
96
|
version: '0.22'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: vcr
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '6.2'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '6.2'
|
|
69
111
|
- !ruby/object:Gem::Dependency
|
|
70
112
|
name: webmock
|
|
71
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -96,7 +138,7 @@ dependencies:
|
|
|
96
138
|
version: '0.9'
|
|
97
139
|
description: Ruby connector for the Bybit V5 REST API — keyword-arg method signatures,
|
|
98
140
|
HMAC-SHA256 signing, typed error hierarchy, Faraday-based transport.
|
|
99
|
-
email:
|
|
141
|
+
email:
|
|
100
142
|
executables: []
|
|
101
143
|
extensions: []
|
|
102
144
|
extra_rdoc_files: []
|
|
@@ -118,9 +160,12 @@ files:
|
|
|
118
160
|
- lib/bybit/rest_api/broker_service.rb
|
|
119
161
|
- lib/bybit/rest_api/crypto_loan_service.rb
|
|
120
162
|
- lib/bybit/rest_api/earn_service.rb
|
|
163
|
+
- lib/bybit/rest_api/institutional_loan_service.rb
|
|
121
164
|
- lib/bybit/rest_api/market_service.rb
|
|
165
|
+
- lib/bybit/rest_api/misc_service.rb
|
|
122
166
|
- lib/bybit/rest_api/p2p_service.rb
|
|
123
167
|
- lib/bybit/rest_api/position_service.rb
|
|
168
|
+
- lib/bybit/rest_api/pre_upgrade_service.rb
|
|
124
169
|
- lib/bybit/rest_api/rfq_service.rb
|
|
125
170
|
- lib/bybit/rest_api/spot_margin_service.rb
|
|
126
171
|
- lib/bybit/rest_api/trade_service.rb
|
|
@@ -128,6 +173,8 @@ files:
|
|
|
128
173
|
- lib/bybit/session.rb
|
|
129
174
|
- lib/bybit/utils/wire_keys.rb
|
|
130
175
|
- lib/bybit/version.rb
|
|
176
|
+
- lib/bybit/websocket.rb
|
|
177
|
+
- lib/bybit/websocket/client.rb
|
|
131
178
|
homepage: https://github.com/bybit-exchange/bybit.ruby.api
|
|
132
179
|
licenses:
|
|
133
180
|
- MIT
|
|
@@ -136,7 +183,7 @@ metadata:
|
|
|
136
183
|
documentation_uri: https://bybit-exchange.github.io/docs/v5/intro
|
|
137
184
|
changelog_uri: https://github.com/bybit-exchange/bybit.ruby.api/blob/main/CHANGELOG.md
|
|
138
185
|
rubygems_mfa_required: 'true'
|
|
139
|
-
post_install_message:
|
|
186
|
+
post_install_message:
|
|
140
187
|
rdoc_options: []
|
|
141
188
|
require_paths:
|
|
142
189
|
- lib
|
|
@@ -151,8 +198,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
151
198
|
- !ruby/object:Gem::Version
|
|
152
199
|
version: '0'
|
|
153
200
|
requirements: []
|
|
154
|
-
rubygems_version: 3.
|
|
155
|
-
signing_key:
|
|
201
|
+
rubygems_version: 3.5.16
|
|
202
|
+
signing_key:
|
|
156
203
|
specification_version: 4
|
|
157
204
|
summary: Official Bybit V5 REST API connector for Ruby
|
|
158
205
|
test_files: []
|