ExBroker.php 9.4 KB
<?php

namespace trader\exchange\binance;

require_once __DIR__ . '/../../struct/ApiInfo.php';
require_once __DIR__ . '/Api.php';
require_once __DIR__ . '/ApiSpot.php';
require_once __DIR__ . '/../../../jytools/Websocket.php';
require_once __DIR__ . '/../../../jytools/func.php';

use trader\struct\ApiInfo;
use trader\exchange\binance\Api as BnApi;
use trader\exchange\binance\ApiSpot as BnSpotApi;
use jytools\Websocket;
use Swoole\Coroutine;

use function jytools\getMicrotime;
use function jytools\output;

class ExBroker
{
    private $host = 'wss://fstream.binance.com';
    private $path = '/stream';
    private ?ApiInfo $apiInfo;
    private BnApi $api;
    private BnSpotApi $apiSpot;
    private ?Websocket $wsAcc;
    private ?Websocket $wsKline;
    private ?Websocket $wsDepth;
    private array $depthData = [
        'lid' => 0, //lastUpdateId
        'bids' => [],
        'asks' => [],
        'uts' => 0, //updateTime
    ];
    private array $depthCache = [];
    private bool $gettingDepth = false;

    public function __construct(?ApiInfo $apiInfo)
    {
        $this->apiInfo = $apiInfo;
        $this->api = new BnApi($apiInfo);
        $this->apiSpot = new BnSpotApi($apiInfo);
    }

    public function setWsHost($host)
    {
        $this->host = $host;
    }

    public function setRestHost($host)
    {
        $this->api->setHost($host);
    }

    public function accListen(callable $onData)
    {
        $listeneKey = $this->getListenKey();
        $this->wsAcc = new Websocket($this->host . $this->path);
        $this->wsAcc->connect(
            $onOpen = function () use ($listeneKey) {
                $subData = json_encode(['method' => 'SUBSCRIBE', 'params' => [$listeneKey], 'id' => 1]);
                $this->wsAcc->push($subData);
            },
            $onMessage = function ($data) use ($onData) {
                $data = json_decode($data, true);
                if (!$data || !isset($data['data'])) {
                    return;
                }
                $onData($data['data']);
            },
            $onClose = null,
        );
    }

    public function klineListen($symbol, $interval, callable $onData)
    {
        $this->wsKline = new Websocket($this->host . $this->path);
        $this->wsKline->connect(
            $onOpen = function () use ($symbol, $interval) {
                $subData = json_encode(['method' => 'SUBSCRIBE', 'params' => [strtolower($symbol) . '@kline_' . $interval], 'id' => 1]);
                $this->wsKline->push($subData);
            },
            $onMessage = function ($data) use ($onData) {
                $data = json_decode($data, true);
                if (!$data || !isset($data['data'])) {
                    return;
                }
                $onData($data['data']);
            },
            $onClose = null,
        );
    }

    private function getListenKey()
    {
        $res = $this->api->getListenKey([]);
        return $res['listenKey'];
    }
    //获取所有品种资金费率
    public function getAllPremium()
    {
        $res = $this->api->getPremiumIndex([]);
    }
    //设置双向持仓模式
    public function setLongShortMode($isLongShortMode)
    {
        if ($isLongShortMode) {
            $res = $this->api->setPositionMode(['dualSidePosition' => 'true']);
        } else {
            $res = $this->api->setPositionMode(['dualSidePosition' => 'false']);
        }
        return $res;
    }
    //获取所有账户杠杆
    public function getAllLevers()
    {
        $res = $this->api->getPositionRiskV2([]);
        $retArr = [];
        foreach ($res as $v) {
            $retArr[$v['symbol']] = $v['leverage'];
        }
        return $retArr;
    }
    public function stopListen()
    {
        if (isset($this->wsAcc)) {
            $this->wsAcc->close();
        }
        if (isset($this->wsKline)) {
            $this->wsKline->close();
        }
    }
    public function getSymbolInfos()
    {
        $res = $this->api->getExchangeInfo([]);
        if (!isset($res['symbols'])) {
            output('okx获取所有交易对信息失败');
            return [];
        }
        return $res['symbols'];
    }
    public function placeOrder(array $params)
    {
        return $this->api->placeOrder($params);
    }
    public function setLever($symbol, $lever)
    {
        return $this->api->setLever(['symbol' => $symbol, 'leverage' => $lever]);
    }
    public function getAllPos()
    {
        $newPositions = [];
        $res = $this->api->accountV3([]);
        if ($res && isset($res['positions'])) {
            $positions = $res['positions'];
            foreach ($positions as $key => $value) {
                if ($value['positionAmt'] != 0) {
                    $newPositions[] = $value;
                }
            }
        } else {
            output($res);
        }
        return $newPositions;
    }
    //获取资金费率 
    public function getPremiumAll($symbol = null)
    {
        $params = [];
        if ($symbol) {
            $params['symbol'] = $symbol;
        }
        $res = $this->api->getPremiumIndex($params);
        return $res;
    }
    //查询交易配置
    public function getSymbolConfig($symbol)
    {
        $res = $this->api->symbolConfig(['symbol' => $symbol]);
        return $res;
    }
    //获取k线
    public function getKlines($symbol, $interval, $limit = 200, $start = '', $end = '')
    {
        $params = [
            'symbol' => $symbol,
            'interval' => $interval,
            'limit' => $limit,
        ];
        if ($start) {
            $params['startTime'] = $start;
        }
        if ($end) {
            $params['endTime'] = $end;
        }
        $res = $this->api->klines($params);
        return $res;
    }
    public function subDepth($symbol, callable $onData)
    {
        var_dump($this->host . $this->path);
        $this->wsDepth = new Websocket($this->host . $this->path);
        $this->wsDepth->connect(
            $onOpen = function () use ($symbol) {
                $symbol = strtolower($symbol);
                $subData = json_encode(['method' => 'SUBSCRIBE', 'params' => [$symbol . '@depth@100ms'], 'id' => 1]);
                $this->wsDepth->push($subData);
            },
            $onMessage = function ($data) use ($onData) {
                $data = json_decode($data, true);
                if (!$data || !isset($data['data'])) {
                    return;
                }
                $startTs = getMicrotime();
                $this->updateDepthData($data['data']);
                $endTs = getMicrotime();
                $cost = $endTs - $startTs;
                output($data['data']['s'] . ' depth update cost(ms):' . $cost);
                $onData($this->depthData);
            },
            $onClose = null,
        );
    }
    private function updateDepthData($data)
    {
        if ($this->depthData['lid'] == 0 && !$this->gettingDepth) {
            Coroutine::create(function () use ($data) {
                $this->gettingDepth = true;
                $data = $this->api->depth(['symbol' => $data['s'], 'limit' => 1000]);
                if ($data) {
                    $this->depthData['lid'] = $data['lastUpdateId'];
                    $this->depthData['bids'] = $this->formatDepthData($data['bids'], 'bids');
                    $this->depthData['asks'] = $this->formatDepthData($data['asks'], 'asks');
                    $this->depthData['uts'] = $data['T'];
                }
                $this->gettingDepth = false;
            });
        }

        if ($this->depthData['lid'] == 0) {
            $this->depthCache[] = $data;
            while (count($this->depthCache) > 20) {
                array_shift($this->depthCache);
            }
        } else {
            if (count($this->depthCache) != 0) {
                foreach ($this->depthCache as $v) {
                    if ($v['pu'] == $this->depthData['lid'] || $v['T'] > $this->depthData['uts']) {
                        $this->addDepthData($v);
                    }
                }
                $this->depthCache = [];
            }
            $this->addDepthData($data);
        }
    }
    private function formatDepthData($data, $type)
    {
        $newData = [];
        foreach ($data as $item) {
            $price = ($item[0]);
            $qty = ($item[1]);
            $newData[$price] = $qty;
        }
        if ($type == 'asks') {
            //从小到大排序
            ksort($newData);
        } else {
            //从大到小排序
            krsort($newData);
        }
        return $newData;
    }
    private function addDepthData($data)
    {
        $data['a'] = $this->formatDepthData($data['a'], 'asks');
        $data['b'] = $this->formatDepthData($data['b'], 'bids');
        $this->depthData['asks'] = array_merge($this->depthData['asks'], $data['a']);
        $this->depthData['bids'] = array_merge($this->depthData['bids'], $data['b']);
        //保留值不为0的数据
        $this->depthData['asks'] = array_filter($this->depthData['asks'], function ($v) {
            return floatval($v) != 0;
        });
        $this->depthData['bids'] = array_filter($this->depthData['bids'], function ($v) {
            return floatval($v) != 0;
        });
        //从小到大排序
        ksort($this->depthData['asks']);
        //从大到小排序
        krsort($this->depthData['bids']);
        $this->depthData['lid'] = $data['u'];
        $this->depthData['uts'] = $data['T'];
    }
    public function getSpotCfg()
    {
        $res = $this->apiSpot->account([]);
        var_dump($res);
    }
}