Files
laravel-bitunix-api/src/LaravelBitunixApi.php
T
2026-05-19 16:28:49 +02:00

427 lines
12 KiB
PHP

<?php
namespace Msr\LaravelBitunixApi;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Msr\LaravelBitunixApi\Contracts\RateLimiterContract as RateLimiter;
use Msr\LaravelBitunixApi\Requests\ChangeLeverageRequestContract;
use Msr\LaravelBitunixApi\Requests\ChangeMarginModeRequestContract;
use Msr\LaravelBitunixApi\Requests\FlashClosePositionRequestContract;
use Msr\LaravelBitunixApi\Requests\FutureKLineRequestContract;
use Msr\LaravelBitunixApi\Requests\GetHistoryPositionsRequestContract;
use Msr\LaravelBitunixApi\Requests\GetHistoryTradesRequestContract;
use Msr\LaravelBitunixApi\Requests\GetPendingPositionsRequestContract;
use Msr\LaravelBitunixApi\Requests\GetSingleAccountRequestContract;
use Msr\LaravelBitunixApi\Requests\GetTradingPairsRequestContract;
use Msr\LaravelBitunixApi\Requests\Header;
use Msr\LaravelBitunixApi\Requests\PlaceOrderRequestContract;
use Msr\LaravelBitunixApi\Requests\PlacePositionTpSlOrderRequestContract;
use Msr\LaravelBitunixApi\Requests\PlaceTpSlOrderRequestContract;
use Msr\LaravelBitunixApi\Support\BitunixAccountConfig;
use Psr\Http\Message\ResponseInterface;
class LaravelBitunixApi implements ChangeLeverageRequestContract, ChangeMarginModeRequestContract, FlashClosePositionRequestContract, FutureKLineRequestContract, GetHistoryPositionsRequestContract, GetHistoryTradesRequestContract, GetPendingPositionsRequestContract, GetSingleAccountRequestContract, GetTradingPairsRequestContract, PlaceOrderRequestContract, PlacePositionTpSlOrderRequestContract, PlaceTpSlOrderRequestContract
{
private Client $publicFutureClient;
protected BitunixAccountConfig $config;
public function __construct(protected RateLimiter $rateLimiter)
{
$this->config = BitunixAccountConfig::fromConfig();
$this->publicFutureClient = new Client([
'base_uri' => ($this->config->baseUrl ?? config('bitunix-api.future_base_uri')).'/api/v1/futures/market/',
]);
}
public function forAccount(BitunixAccountConfig $config): self
{
$this->config = $config;
return $this;
}
protected function getPrivateFutureClient(array $queryParams = [], array $body = []): Client
{
$bodyString = count($body) ? json_encode($body) : '';
$headers = Header::generateHeaders($queryParams, $bodyString, $this->config);
return new Client([
'base_uri' => ($this->config->baseUrl ?? config('bitunix-api.future_base_uri')).'/api/v1/futures/',
'headers' => $headers,
]);
}
public function getFutureKline(
string $symbol,
string $interval,
int $limit = 100,
?int $startTime = null,
?int $endTime = null,
string $type = 'LAST_PRICE'
): ResponseInterface {
$response = $this->publicFutureClient->get('kline', [
'query' => [
'symbol' => $symbol,
'interval' => $interval,
'limit' => $limit,
'startTime' => $startTime,
'endTime' => $endTime,
'type' => $type,
],
]);
return $response;
}
public function getTradingPairs(?string $symbols = null): ResponseInterface
{
$queryParams = [];
if ($symbols !== null) {
$queryParams['symbols'] = $symbols;
}
$response = $this->publicFutureClient->get('trading_pairs', [
'query' => $queryParams,
]);
return $response;
}
public function changeLeverage(string $symbol, string $marginCoin, int $leverage): ResponseInterface
{
$body = [
'symbol' => $symbol,
'marginCoin' => $marginCoin,
'leverage' => $leverage,
];
$response = $this->getPrivateFutureClient([], $body)->post('account/change_leverage', [
'json' => $body,
]);
return $response;
}
public function changeMarginMode(string $symbol, string $marginCoin, string $marginMode): ResponseInterface
{
$body = [
'symbol' => $symbol,
'marginCoin' => $marginCoin,
'marginMode' => $marginMode,
];
$response = $this->getPrivateFutureClient([], $body)->post('account/change_margin_mode', [
'json' => $body,
]);
return $response;
}
public function placeOrder(
string $symbol,
string $qty,
string $side,
string $tradeSide,
string $orderType,
?string $price = null,
?string $positionId = null,
?string $effect = null,
?string $clientId = null,
?bool $reduceOnly = null,
?string $tpPrice = null,
?string $tpStopType = null,
?string $tpOrderType = null,
?string $tpOrderPrice = null,
?string $slPrice = null,
?string $slStopType = null,
?string $slOrderType = null,
?string $slOrderPrice = null
): ResponseInterface {
$body = [
'symbol' => $symbol,
'qty' => $qty,
'side' => $side,
'tradeSide' => $tradeSide,
'orderType' => $orderType,
];
// Add optional parameters if provided
if ($price !== null) {
$body['price'] = $price;
}
if ($positionId !== null) {
$body['positionId'] = $positionId;
}
if ($effect !== null) {
$body['effect'] = $effect;
}
if ($clientId !== null) {
$body['clientId'] = $clientId;
}
if ($reduceOnly !== null) {
$body['reduceOnly'] = $reduceOnly;
}
if ($tpPrice !== null) {
$body['tpPrice'] = $tpPrice;
}
if ($tpStopType !== null) {
$body['tpStopType'] = $tpStopType;
}
if ($tpOrderType !== null) {
$body['tpOrderType'] = $tpOrderType;
}
if ($tpOrderPrice !== null) {
$body['tpOrderPrice'] = $tpOrderPrice;
}
if ($slPrice !== null) {
$body['slPrice'] = $slPrice;
}
if ($slStopType !== null) {
$body['slStopType'] = $slStopType;
}
if ($slOrderType !== null) {
$body['slOrderType'] = $slOrderType;
}
if ($slOrderPrice !== null) {
$body['slOrderPrice'] = $slOrderPrice;
}
$response = $this->getPrivateFutureClient([], $body)->post('trade/place_order', [
'json' => $body,
]);
return $response;
}
public function flashClosePosition(string $positionId): ResponseInterface
{
$body = [
'positionId' => $positionId,
];
$response = $this->getPrivateFutureClient([], $body)->post('trade/flash_close_position', [
'json' => $body,
]);
return $response;
}
public function getHistoryTrades(
?string $symbol = null,
?string $orderId = null,
?string $positionId = null,
?int $startTime = null,
?int $endTime = null,
int $skip = 0,
int $limit = 100
): ResponseInterface {
$this->rateLimiter->throttle(
'futures::get_history_trades',
$this->resolveRateLimitIdentity(),
maxRequests: 8,
perSeconds: 1
);
$queryParams = [
'skip' => $skip,
'limit' => $limit,
];
if ($symbol !== null) {
$queryParams['symbol'] = $symbol;
}
if ($orderId !== null) {
$queryParams['orderId'] = $orderId;
}
if ($positionId !== null) {
$queryParams['positionId'] = $positionId;
}
if ($startTime !== null) {
$queryParams['startTime'] = $startTime;
}
if ($endTime !== null) {
$queryParams['endTime'] = $endTime;
}
$response = $this->getPrivateFutureClient($queryParams, [])->get('trade/get_history_trades', [
'query' => $queryParams,
]);
return $response;
}
public function getPendingPositions(?string $symbol = null, ?string $positionId = null): ResponseInterface
{
$queryParams = [];
if ($symbol != null) {
$queryParams['symbol'] = $symbol;
}
if ($positionId != null) {
$queryParams['positionId'] = $positionId;
}
$response = $this->getPrivateFutureClient($queryParams, [])->get('position/get_pending_positions', [
'query' => $queryParams,
]);
return $response;
}
/**
* @throws GuzzleException
*/
public function getHistoryPositions(
?string $symbol = null,
?int $startTime = null,
?int $endTime = null,
int $skip = 0,
int $limit = 100
): ResponseInterface {
$this->rateLimiter->throttle(
'futures::get_history_positions',
$this->resolveRateLimitIdentity(),
maxRequests: 8,
perSeconds: 1
);
$queryParams = [
'skip' => $skip,
'limit' => $limit,
];
if ($symbol !== null) {
$queryParams['symbol'] = $symbol;
}
if ($startTime !== null) {
$queryParams['startTime'] = $startTime;
}
if ($endTime !== null) {
$queryParams['endTime'] = $endTime;
}
return $this->getPrivateFutureClient($queryParams, [])->get('position/get_history_positions', [
'query' => $queryParams,
]);
}
public function getSingleAccount(string $marginCoin): ResponseInterface
{
$queryParams = [
'marginCoin' => $marginCoin,
];
$response = $this->getPrivateFutureClient($queryParams, [])->get('account', [
'query' => $queryParams,
]);
return $response;
}
public function placeTpSlOrder(
string $symbol,
string $positionId,
?string $tpPrice = null,
?string $tpStopType = null,
?string $slPrice = null,
?string $slStopType = null,
?string $tpOrderType = null,
?string $tpOrderPrice = null,
?string $slOrderType = null,
?string $slOrderPrice = null,
?string $tpQty = null,
?string $slQty = null
): ResponseInterface {
$body = [
'symbol' => $symbol,
'positionId' => $positionId,
];
// Add optional parameters if provided
if ($tpPrice !== null) {
$body['tpPrice'] = $tpPrice;
}
if ($tpStopType !== null) {
$body['tpStopType'] = $tpStopType;
}
if ($slPrice !== null) {
$body['slPrice'] = $slPrice;
}
if ($slStopType !== null) {
$body['slStopType'] = $slStopType;
}
if ($tpOrderType !== null) {
$body['tpOrderType'] = $tpOrderType;
}
if ($tpOrderPrice !== null) {
$body['tpOrderPrice'] = $tpOrderPrice;
}
if ($slOrderType !== null) {
$body['slOrderType'] = $slOrderType;
}
if ($slOrderPrice !== null) {
$body['slOrderPrice'] = $slOrderPrice;
}
if ($tpQty !== null) {
$body['tpQty'] = $tpQty;
}
if ($slQty !== null) {
$body['slQty'] = $slQty;
}
$response = $this->getPrivateFutureClient([], $body)->post('tpsl/place_order', [
'json' => $body,
]);
return $response;
}
public function placePositionTpSlOrder(
string $symbol,
string $positionId,
?string $tpPrice = null,
?string $tpStopType = null,
?string $slPrice = null,
?string $slStopType = null
): ResponseInterface {
$body = [
'symbol' => $symbol,
'positionId' => $positionId,
];
// Add optional parameters if provided
if ($tpPrice !== null) {
$body['tpPrice'] = $tpPrice;
}
if ($tpStopType !== null) {
$body['tpStopType'] = $tpStopType;
}
if ($slPrice !== null) {
$body['slPrice'] = $slPrice;
}
if ($slStopType !== null) {
$body['slStopType'] = $slStopType;
}
return $this->getPrivateFutureClient([], $body)->post('tpsl/position/place_order', [
'json' => $body,
]);
}
protected function resolveRateLimitIdentity(): string
{
return (string) ($this->config->accountKey ?? $this->config->apiKey);
}
}