Search by

mishasaz / bybit-php

mishasaz

A clean, dependency-free PHP client for the Bybit v5 REST API (HMAC signing, typed endpoints, pluggable transport).

Package info

github.com/mishasaz/bybit-php

pkg:composer/mishasaz/bybit-php

Statistics

Installs: 7

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.3.0 2026-08-14 13:28 UTC

This package is auto-updated.

Last update: 2026-09-14 13:38:26 UTC


README

A clean, dependency-free PHP client for the Bybit v5 REST API.

  • HMAC-SHA256 request signing done for you
  • Typed methods for the common linear (USDT-perpetual) endpoints
  • Zero required dependencies - ships with a PHP-streams transport; swap in Guzzle / any PSR-18 client via one interface
  • Returns Bybit's raw response envelope (retCode / retMsg / result) - nothing hidden, one branch to check
  • Immutable, injectable config - no globals, no environment reads, fully testable

Requires PHP 8.1+.

Install

composer require mishasaz/bybit-php

Quick start

use Mishasaz\Bybit\BybitClient;
use Mishasaz\Bybit\Config;

$client = new BybitClient(new Config(
    apiKey:    'YOUR_KEY',
    apiSecret: 'YOUR_SECRET',
    testnet:   false,
));

$ticker = $client->getTicker('BTCUSDT');
echo $ticker['result']['list'][0]['lastPrice'];

$order = $client->placeOrder([
    'category'  => 'linear',
    'symbol'    => 'BTCUSDT',
    'side'      => 'Buy',
    'orderType' => 'Limit',
    'qty'       => '0.001',
    'price'     => '50000',
]);
if (($order['retCode'] ?? -1) === 0) {
    echo 'placed: ' . $order['result']['orderId'];
} else {
    echo 'error: ' . $order['retMsg'];
}

What's covered

Account: getBalance, getPositions, getClosedPnl, getFeeRate, setLeverage, setTradingStop($symbol, $takeProfit = null, $stopLoss = null, $tpOrderType = null, $tpLimitPrice = null) - pass 'Limit' with a limit price to have the take-profit rest as a limit order instead of firing at market:

// Market (Bybit's default): closes at whatever is there when the price is touched.
$client->setTradingStop('BTCUSDT', takeProfit: '52000');

// Limit: rests at the price and fills as maker.
$client->setTradingStop('BTCUSDT', takeProfit: '52000', tpOrderType: 'Limit', tpLimitPrice: '52000');

The difference is the fee side: a market exit pays taker plus whatever the book gives you, a resting limit pays maker. It matters most when the target sits close to the entry, where that spread is a large share of the trade.

Orders: placeOrder, placeReduceOrder, getOpenOrders (auto-paginated), getOrderHistory, cancelOrder, cancelAllOrders, closePosition($symbol, $cancelOrders = true) - pass false to close the position without sweeping the symbol's resting orders (useful when the caller cancels its own order ids selectively). Market data: getTicker, getKline, getRecentTrades, getInstrumentInfo.

History: getExecutions($params) returns raw fills - execPrice, execQty, execFee, feeRate, isMaker, markPrice - which is what you need to measure what execution actually cost rather than what it was modelled to cost. It returns ONE page per call on purpose: Bybit caps the window at seven days and the page at 100 records, and returns nextPageCursor in the result, so paginating inside the client would hide both limits from you.

$res = $client->getExecutions(['startTime' => $from, 'endTime' => $to]);
$cursor = $res['result']['nextPageCursor'] ?? '';

Anything not wrapped is one line away via the transport:

$res = $client->transport()->get('/v5/market/tickers', ['category' => 'spot', 'symbol' => 'BTCUSDT']);

Response format

Every method returns the decoded Bybit envelope as an array:

['retCode' => 0, 'retMsg' => 'OK', 'result' => [ ... ]]

retCode === 0 means success. A transport-level failure (timeout, DNS, invalid JSON) is reported the same way with retCode = -1, so you always check one place:

if (($res['retCode'] ?? -1) !== 0) {
    // handle $res['retMsg']
}

Custom transport

The client talks to Bybit through a single Transport interface. The bundled StreamTransport uses PHP streams and needs nothing extra. To use your own HTTP stack (pooling, proxies, retries, PSR-18), implement the interface and inject it:

use Mishasaz\Bybit\{BybitClient, Config, Transport};

final class GuzzleTransport implements Transport
{
    public function get(string $endpoint, array $params = []): array { /* ... */ }
    public function post(string $endpoint, array $params = []): array { /* ... */ }
    public function isConfigured(): bool { /* ... */ }
}

$client = new BybitClient($config, new GuzzleTransport(/* ... */));

Testnet

$client = new BybitClient(new Config($key, $secret, testnet: true));

Or point at any base URL / tune the request window and timeout:

new Config($key, $secret, timeoutSeconds: 15, recvWindowMs: 8000, baseUrl: 'https://api.bybit.com');

Disclaimer

Not affiliated with Bybit. Trading is risky; use at your own risk. This library mirrors the API faithfully and does not add strategy or risk-management logic.

Donate

If this library was useful and saved you a bit of precious time, a tip is welcome.

USDT (BEP-20): 0x7e5db543734E5BD59E0df1B27820A1EAF2BE438B

License

MIT. Do whatever you want with it.