ExBroker.php 11.3 KB
<?php

namespace trader\exchange\okx;

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

use trader\struct\ApiInfo;
use trader\exchange\okx\Api as OkxApi;
use jytools\Websocket;
use function jytools\output;


class ExBroker
{
    private $host = 'wss://ws.okx.com:8443';
    private $pathPrivate = '/ws/v5/private';
    private $pathPublic = '/ws/v5/public';
    private $pathBusiness = '/ws/v5/business';
    private ?ApiInfo $apiInfo;
    private OkxApi $api;
    private ?Websocket $wsAcc;
    private ?Websocket $wsKline;
    private $timerAccPing = 0;
    private $timerKlinePing = 0;

    public function __construct(?ApiInfo $apiInfo)
    {
        $this->apiInfo = $apiInfo;
        $this->api = new OkxApi($apiInfo);
    }
    public function setRestHost($host)
    {
        $this->api->setHost($host);
    }
    public function setWsHost($host)
    {
        $this->host = $host;
    }

    public function accListen(callable $onWsData)
    {
        if (isset($this->wsAcc)) {
            $this->wsAcc->close();
        }
        $this->wsAcc = new Websocket($this->host . $this->pathPrivate);
        $this->wsAcc->connect(
            $onOpen = function () {
                $this->wsLogin();
                //开始定时ping
                $this->wsAccPing();
            },
            $onMessage = function ($data) use ($onWsData) {
                $this->onWsDataPre($data, $onWsData);
            },
            $onClose = function () {
                // 关闭链接
                swoole_timer_clear($this->timerAccPing);
            }
        );
    }
    public function klineListen($symbol, $period, callable $onData)
    {
        if (isset($this->wsKline)) {
            $this->wsKline->close();
        }
        $this->wsKline = new Websocket($this->host . $this->pathBusiness);
        $this->wsKline->connect(
            $onOpen = function () use ($symbol, $period) {
                $subData = [];
                $subData['op'] = 'subscribe';
                $subData['args'] = [
                    [
                        'channel' => 'candle' . $period,
                        'instId' => $symbol,
                    ],
                ];
                output("订阅", $subData);
                $this->wsKline->push(json_encode($subData));
                //开始定时ping
                $this->wsKlinePing();
            },
            $onMessage = function ($data) use ($onData) {
                $data = json_decode($data, true);
                if (!isset($data['data'])) {
                    return;
                }
                $data = $data['data'][0];
                $onData($data);
            },
            $onClose = function () {
                // 关闭链接 关闭定时ping
                swoole_timer_clear($this->timerKlinePing);
            }
        );
    }
    private function wsKlinePing()
    {
        $this->timerKlinePing = swoole_timer_tick(1000 * 20, function () {
            $this->wsKline->push("ping");
        });
    }
    private function wsAccPing()
    {
        $this->timerAccPing = swoole_timer_tick(1000 * 20, function () {
            $this->wsAcc->push("ping");
        });
    }
    private function crateWsSign($timeStamp)
    {
        $method = 'GET';
        $path = '/users/self/verify';
        $str = $timeStamp . $method . $path;
        $sign = hash_hmac('sha256', $str, $this->apiInfo->secret, true);
        $sign = base64_encode($sign);
        return $sign;
    }
    private function  wsLogin()
    {
        $ts = time();
        $subData = [];
        $subData['op'] = 'login';
        $subData['args'] = [
            [
                'apiKey' => $this->apiInfo->key,
                'passphrase' => $this->apiInfo->passphrase,
                'timestamp' => $ts,
                'sign' => $this->crateWsSign($ts),
            ]
        ];
        $this->wsAcc->push(json_encode($subData));
    }
    // ws 消息预处理
    private function onWsDataPre($data, callable $onWsData)
    {
        if ($data == "ping" || $data == "pong") {
            return;
        }
        $data = json_decode($data, true);
        if (isset($data['event'])) {
            if ($data['event'] == 'login' && $data['code'] == '0') {
                output('ws登录成功');
                $this->wsSubscribe();
                return;
            }
            if ($data['event'] == 'subscribe' || $data['event'] == 'unsubscribe' || $data['event'] == 'channel-conn-count') {
                // output("ws 过滤数据", $data);
                return;
            }
            output("ok ws 未处理数据", $data);
        }
        call_user_func($onWsData, $data);
    }
    // 订阅
    private function wsSubscribe()
    {
        $subData = [];
        $subData['op'] = 'subscribe';
        $subData['args'] = [
            [
                'channel' => 'orders',
                'instType' => 'SWAP',
            ],
            [
                'channel' => 'positions',
                'instType' => 'SWAP',
            ],
            [
                'channel' => 'account',
                'ccy' => 'USDT',
            ]
        ];
        $this->wsAcc->push(json_encode($subData));
    }
    public function placeOrder($param)
    {
        return $this->api->placeOrder($param);
    }
    public function getSymbolInfos()
    {
        $res = $this->api->instruments(["instType" => "SWAP"]);
        if ($res['code'] != '0') {
            output('okx获取所有交易对信息失败');
            return [];
        }
        return $res['data'];
    }
    public function stopListen()
    {
        if (isset($this->wsAcc)) {
            $this->wsAcc->close();
        }
        if (isset($this->wsKline)) {
            $this->wsKline->close();
        }
    }
    public function closeAllPos()
    {
        //获取所有仓位
        $param = [
            'instType' => 'SWAP'
        ];
        $res = $this->api->getPositions($param);
        if ($res['code'] == 0) {
            $positions = $res['data'];
            foreach ($positions as $key => $value) {
                if ($value['pos'] != 0) {
                    $this->clsoePos($value['instId'], $value['posSide'], abs($value['pos']), $value['mgnMode']);
                }
            }
        }
    }
    // 平仓
    private function clsoePos($instId, $posSide, $size, $tdMode)
    {
        $param = [
            'instId' => $instId,
            'tdMode' => $tdMode,
            'side' => strtoupper($posSide) == 'LONG' ? 'sell' : 'buy',
            'posSide' => $posSide,
            'ordType' => 'market',
            'sz' => $size,
        ];
        $res = $this->api->placeOrder($param);
        if ($res['code'] == 0) {
            return true;
        } else {
            return false;
        }
    }
    // 设置是否允许合约
    public function setAllowFutures($isAllowFutures)
    {
        $param = [
            'acctLv' => $isAllowFutures ? '2' : '1',
        ];
        $res = $this->api->setAccountLevel($param);
        output("设置合约模式", $res);
    }
    // 设置为双向持仓
    public function setLongShortMode($isLongShortMode)
    {
        $param = [
            'posMode' => $isLongShortMode ? 'long_short_mode' : 'net_mode',
        ];
        $res = $this->api->setPositionMode($param);
        output("设置持仓模式", $res);
    }
    //获取所有品种杠杆信息
    public function getAllLevers()
    {
        //获取所有USDT SWAP 交易对
        $res = $this->api->instruments(["instType" => "SWAP"]);
        $Symbols = [];
        foreach ($res["data"] as $key => $value) {
            if ($value["settleCcy"] == "USDT") {
                $Symbols[] = $value["instId"];
            }
        }
        //获取所有品种的杠杆信息
        return $this->getLevers($Symbols);
    }
    //获取杠杆
    private function getLevers($symbols)
    {
        $levers = [];
        $param = [
            'mgnMode' => 'cross'
        ];
        $count = 0;
        $instId = '';
        foreach ($symbols as $key => $value) {
            $count++;
            if ($instId == '') {
                $instId = $value;
            } else {
                $instId = $instId . ',' . $value;
            }
            if ($count % 20 == 0 || $count == count($symbols)) {
                $param['instId'] = $instId;
                $instId = '';
                $res = $this->api->leverageInfo($param, $this->apiInfo);
                if ($res['code'] == 0) {
                    foreach ($res["data"] as $key => $value) {
                        $levers[$value["instId"]] = $value["lever"];
                    }
                } else {
                    var_dump($res);
                    break;
                }
            }
        }
        return $levers;
    }
    //设置杠杆
    public function setLever($instId, $lever)
    {
        $param = [
            'instId' => $instId,
            'lever' => $lever,
            'mgnMode' => 'cross',
        ];
        $res = $this->api->setLeverage($param);
        if ($res && $res['code'] == 0) {
            output($instId, '设置杠杆为:', $lever);
            return true;
        } else {
            $msg = $res['msg'];
            if ($res["data"] && $res["data"]["msg"]) {
                $msg = $res["data"]["msg"];
            }
            output($instId, '设置杠杆错误', $msg);
            return false;
        }
    }
    //查询获取所有仓位
    public function getAllPos()
    {
        $newPositions = [];
        $param = [
            'instType' => 'SWAP'
        ];
        $res = $this->api->getPositions($param);
        if ($res['code'] == 0) {
            $positions = $res['data'];
            foreach ($positions as $key => $value) {
                if ($value['pos'] != 0) {
                    $newPositions[] = $value;
                }
            }
        } else {
            output($res);
        }
        return $newPositions;
    }
    //查询获取某个品种方向具体仓位
    public function getPos($symbol, $posSide)
    {
        $param = [
            'instType' => 'SWAP',
            'instId' => $symbol,
        ];
        $res = $this->api->getPositions($param);
        if ($res['code'] == 0) {
            $positions = $res['data'];
            foreach ($positions as $key => $value) {
                if ($value['posSide'] == $posSide) {
                    return $value['pos'];
                }
            }
            return 0;
        } else {
            output($res);
        }
        return -1;
    }
    //获取k线
    public function getKlines($symbol, $period, $limit = "", $startTs = "", $endTs = "")
    {
        $param = [
            'instId' => $symbol,
            'bar' => $period,
        ];
        if ($limit) {
            $param['limit'] = $limit;
        }
        if ($startTs) {
            $param['after'] = $startTs;
        }
        if ($endTs) {
            $param['before'] = $endTs;
        }
        return $this->api->klines($param);
    }
    //获取资金费率
    public function getPremium($symbol)
    {
        $param = [
            'instId' => $symbol,
        ];
        $res = $this->api->fundingRate($param);
        return $res;
    }
    public function getAccountConfig()
    {
        return $this->api->getAccountConfig();
    }
}