Class: Bybit::WebSocket::Client
- Inherits:
-
Object
- Object
- Bybit::WebSocket::Client
- Defined in:
- lib/bybit/websocket/client.rb
Overview
Bybit V5 WebSocket client — public streams (spot / linear / inverse / option) and the private user-data stream.
Design goals mirror the REST session:
- one class,
channel:selects endpoint (avoids Public/Private class duplication for ping / subscribe / dispatch logic) - explicit callbacks (
on_message,on_open,on_close,on_error) passed as procs; no global registry / singleton - HMAC-SHA256 auth for
:privateis identical to REST signing except the payload is"GET/realtime" + expires— the docs are explicit - ping every
ping_intervalseconds via a lightweight background thread; server terminates connections after 20s silence - automatic resubscribe on reconnect (topic list is retained across
#connectcalls) so callers don't lose subscriptions on transient network blips
This class depends on websocket-client-simple (a runtime dep of the
gem). If missing, requiring it raises a clear LoadError below.
Constant Summary collapse
- PUBLIC_CHANNELS =
Channel → WS path (public streams are category-scoped; private has one).
%i[spot linear inverse option].freeze
- PRIVATE_CHANNEL =
:private- TRADE_CHANNEL =
:trade- PATHS =
{ spot: '/v5/public/spot', linear: '/v5/public/linear', inverse: '/v5/public/inverse', option: '/v5/public/option', private: '/v5/private', trade: '/v5/trade' }.freeze
- HOST_MAINNET =
'stream.bybit.com'- HOST_TESTNET =
'stream-testnet.bybit.com'- DEFAULT_PING_INTERVAL =
20- DEFAULT_AUTH_EXPIRY_MS =
Bybit rejects auth expiry timestamps in the past AND >30s in the future.
10_000
Instance Attribute Summary collapse
-
#channel ⇒ Object
readonly
Returns the value of attribute channel.
-
#subscriptions ⇒ Object
readonly
Returns the value of attribute subscriptions.
-
#testnet ⇒ Object
readonly
Returns the value of attribute testnet.
-
#url ⇒ Object
readonly
Returns the value of attribute url.
Instance Method Summary collapse
-
#connect ⇒ Object
Open the WebSocket, authenticate (if private/trade), and start pinging.
- #connected? ⇒ Boolean
- #disconnect ⇒ Object
-
#initialize(channel:, testnet: false, api_key: nil, api_secret: nil, ping_interval: DEFAULT_PING_INTERVAL, url: nil, on_open: nil, on_message: nil, on_close: nil, on_error: nil) ⇒ Client
constructor
A new instance of Client.
- #ready? ⇒ Boolean
-
#send_raw(payload) ⇒ Object
Send a WS message directly (escape hatch for placing orders over the
/v5/tradestream; the docs specify per-op payloads). -
#subscribe(*topics) ⇒ Object
Subscribe to one or more topics.
- #unsubscribe(*topics) ⇒ Object
Constructor Details
#initialize(channel:, testnet: false, api_key: nil, api_secret: nil, ping_interval: DEFAULT_PING_INTERVAL, url: nil, on_open: nil, on_message: nil, on_close: nil, on_error: nil) ⇒ Client
Returns a new instance of Client.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/bybit/websocket/client.rb', line 67 def initialize(channel:, testnet: false, api_key: nil, api_secret: nil, ping_interval: DEFAULT_PING_INTERVAL, url: nil, on_open: nil, on_message: nil, on_close: nil, on_error: nil) validate_channel!(channel) @channel = channel @testnet = testnet @api_key = api_key @api_secret = api_secret @ping_interval = ping_interval @url = url || build_url @on_open = on_open @on_message = @on_close = on_close @on_error = on_error @subscriptions = [] @ws = nil @ping_thread = nil @connected = false @auth_ok = false @closing = false end |
Instance Attribute Details
#channel ⇒ Object (readonly)
Returns the value of attribute channel.
57 58 59 |
# File 'lib/bybit/websocket/client.rb', line 57 def channel @channel end |
#subscriptions ⇒ Object (readonly)
Returns the value of attribute subscriptions.
57 58 59 |
# File 'lib/bybit/websocket/client.rb', line 57 def subscriptions @subscriptions end |
#testnet ⇒ Object (readonly)
Returns the value of attribute testnet.
57 58 59 |
# File 'lib/bybit/websocket/client.rb', line 57 def testnet @testnet end |
#url ⇒ Object (readonly)
Returns the value of attribute url.
57 58 59 |
# File 'lib/bybit/websocket/client.rb', line 57 def url @url end |
Instance Method Details
#connect ⇒ Object
Open the WebSocket, authenticate (if private/trade), and start pinging.
Calling connect a second time closes the prior socket first, then
opens a fresh one and replays any topics buffered in @subscriptions
(call disconnect explicitly if you want to drop them). No automatic
reconnect on network drop — the caller decides when to re-open.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/bybit/websocket/client.rb', line 96 def connect require_auth! if requires_auth? # Idempotent: tear down any prior socket / ping thread so a second # `connect` call doesn't leak the previous ws or leave two pingers. disconnect if @ws @closing = false client = self @ws = ::WebSocket::Client::Simple.connect(@url) @ws.on(:open) { client.send(:handle_open) } @ws.on(:message) { |msg| client.send(:handle_message, msg) } @ws.on(:close) { |ev| client.send(:handle_close, ev) } @ws.on(:error) { |err| client.send(:handle_error, err) } self end |
#connected? ⇒ Boolean
156 157 158 |
# File 'lib/bybit/websocket/client.rb', line 156 def connected? @connected end |
#disconnect ⇒ Object
142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/bybit/websocket/client.rb', line 142 def disconnect @closing = true stop_pinger @connected = false @auth_ok = false begin @ws&.close rescue StandardError # Socket may already be half-closed by the peer or in a bad state — # we only care about detaching, not about clean protocol shutdown. end @ws = nil end |
#ready? ⇒ Boolean
160 161 162 163 164 |
# File 'lib/bybit/websocket/client.rb', line 160 def ready? return false unless @connected requires_auth? ? @auth_ok : true end |
#send_raw(payload) ⇒ Object
Send a WS message directly (escape hatch for placing orders over the
/v5/trade stream; the docs specify per-op payloads).
136 137 138 139 140 |
# File 'lib/bybit/websocket/client.rb', line 136 def send_raw(payload) return unless @ws @ws.send(payload.is_a?(String) ? payload : JSON.generate(payload)) end |
#subscribe(*topics) ⇒ Object
Subscribe to one or more topics. Bybit expects an array under args:
{"op":"subscribe","args":["orderbook.1.BTCUSDT","tickers.BTCUSDT"]}
Callers can call subscribe before connect; topics are buffered and
sent once the socket opens (private streams also wait for auth OK).
116 117 118 119 120 121 122 123 |
# File 'lib/bybit/websocket/client.rb', line 116 def subscribe(*topics) topics = topics.flatten.compact.uniq return if topics.empty? @subscriptions |= topics flush_subscriptions(topics) if ready? self end |
#unsubscribe(*topics) ⇒ Object
125 126 127 128 129 130 131 132 |
# File 'lib/bybit/websocket/client.rb', line 125 def unsubscribe(*topics) topics = topics.flatten.compact.uniq return if topics.empty? @subscriptions -= topics send_frame(op: 'unsubscribe', args: topics) if ready? self end |