baconfy / exchanges
Requires
- php: ^8.4
- brick/math: ^0.18
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
Requires (Dev)
- guzzlehttp/guzzle: ^8.0
- illuminate/container: ^12.0
- illuminate/support: ^12.0
- pestphp/pest: ^5.0
Suggests
- illuminate/support: To use the Laravel bridge (ExchangesServiceProvider)
README
Framework-agnostic Composer package for connecting to cryptocurrency exchanges. A single Exchange contract lets consumers fetch market data across multiple exchanges (Bybit, Binance, OKX, ...), where each exchange is an interchangeable driver — consumers program against the contract, never against a specific exchange.
v0 scope: klines() only (public candlestick data, no authentication). Balance, positions and orders come in later versions.
Requirements
Installation
composer require baconfy/exchanges
The package requires only PSR interfaces — bring your own PSR-18/PSR-17 implementation:
composer require guzzlehttp/guzzle
Usage
use Baconfy\Exchanges\ExchangeManager; use Baconfy\Exchanges\Enums\MarketType; use Baconfy\Exchanges\Enums\Timeframe; use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; $manager = new ExchangeManager(new Client(), new HttpFactory()); $candles = $manager->make('bybit', MarketType::Perpetual)->klines('BTCUSDT', Timeframe::M5, 100); foreach ($candles as $candle) { // oldest to newest, every OHLCV field is an exact Brick\Math\BigDecimal echo "{$candle->openTime->format('c')}: {$candle->close}\n"; }
Supported timeframes
Timeframe::M1, M5, M15, M30, H1, H4, D1, W1 — each driver translates these to its own dialect (e.g. Bybit's interval=240 for H4).
Error handling
Every failure the package throws implements Baconfy\Exchanges\Exceptions\ExchangeException, so you can catch broadly or narrowly:
use Baconfy\Exchanges\Exceptions\ApiException; use Baconfy\Exchanges\Exceptions\ExchangeException; use Baconfy\Exchanges\Exceptions\TransportException; try { $manager->make('bybit', MarketType::Perpetual)->klines('BTCUSDT', Timeframe::M5); } catch (ApiException $e) { // the exchange responded with an error: $e->retCode / $e->retMsg } catch (TransportException $e) { // network failure, HTTP error status, or invalid JSON } catch (ExchangeException $e) { // any other package failure (unknown driver, wrong credentials type, ...) }
Registering another driver
$manager->register('binance', BinanceDriver::class); $manager->available(); // ['bybit', 'binance']
Every driver constructor follows the same shape — (ClientInterface $http, RequestFactoryInterface $requests, MarketType $market, ?Credentials $credentials = null) — so ExchangeManager::make() can build any of them generically.
Laravel
The package ships an optional ExchangesServiceProvider, auto-discovered via Composer. It registers ExchangeManager as a container singleton, wired to whatever PSR-18/PSR-17 implementations your app has bound — it does not bind Exchange::class itself, since each connection may use a different driver and credentials:
$manager = app(\Baconfy\Exchanges\ExchangeManager::class); $exchange = $manager->make($connection->driver, MarketType::Perpetual, $credentials);
illuminate/support is a suggest, never a hard dependency — the core works in a plain PHP script with no framework at all.
Testing
composer install vendor/bin/pest
No test hits the network: HTTP is mocked at the PSR-18 boundary, and Bybit fixtures under tests/Fixtures/ are real API responses, versioned deliberately.
Architecture
Design decisions, invariants and the full build rationale live in .claude/ARCHITECTURE.md — the source of truth for this package.