ExBroker.php 6.4 KB
<?php

namespace trader\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\okx\Api as OkxApi;
use jytools\Websocket;
use function jytools\output;


class ExBroker
{
    // static private $host = 'wss://ws.okx.com:8443';
    // static private $host = 'ws://okws.keetu.com';
    static private $host = 'ws://okws.a.indigo888.com';
    static private $pathPrivate = '/ws/v5/private';
    static private $pathPublic = '/ws/v5/public';
    static private $pathBusiness = '/ws/v5/business';
    private ApiInfo $apiInfo;
    private OkxApi $api;
    private ?Websocket $wsAcc;
    private ?Websocket $wsKline;

    public function __construct($apiInfo)
    {
        $this->apiInfo = $apiInfo;
        $this->api = new OkxApi($apiInfo);
    }

    public function accListen(callable $onWsData)
    {
        if (isset($this->wsAcc)) {
            $this->wsAcc->close();
        }
        $this->wsAcc = new Websocket(self::$host . self::$pathPrivate);
        $this->wsAcc->connect(
            $onOpen = function () {
                $this->wsLogin();
            },
            $onMessage = function ($data) use ($onWsData) {
                $this->onWsDataPre($data, $onWsData);
            },
            $onClose = function () {
                // 关闭链接
            }
        );
    }
    public function klineListen($symbol, $period, callable $onData)
    {
        if (isset($this->wsKline)) {
            $this->wsKline->close();
        }
        $this->wsKline = new Websocket(self::$host . self::$pathBusiness);
        $this->wsKline->connect(
            $onOpen = function () use ($symbol, $period) {
                $subData = [];
                $subData['op'] = 'subscribe';
                $subData['args'] = [
                    [
                        'channel' => $this->getKlineChannels()[$period],
                        'instId' => $symbol,
                    ],
                ];
                output("订阅", $subData);
                $this->wsKline->push(json_encode($subData));
            },
            $onMessage = function ($data) use ($onData) {
                $data = json_decode($data, true);
                if (!isset($data['data'])) {
                    return;
                }
                $data = $data['data'][0];
                $onData($data);
            },
            $onClose = function () {
                // 关闭链接
            }
        );
    }
    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)
    {
        $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 getKlineChannels()
    {
        return [
            '1s' => 'candle1s',
            '1m' => 'candle1m',
            '5m' => 'candle5m',
            '15m' => 'candle15m',
            '30m' => 'candle30m',
            '1h' => 'candle1H',
            '2h' => 'candle2H',
            '4h' => 'candle4H',
            '6h' => 'candle6H',
            '12h' => 'candle12H',
            '1d' => 'candle1D',
        ];
    }
    public function getSymbolInfos()
    {
        $res = $this->api->instruments(["instType" => "SWAP"]);
        if ($res['code'] != '0') {
            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;
        }
    }
}