CmBroker.php 12.6 KB
<?php

namespace trader;

require_once __DIR__ . '/struct/ApiInfo.php';
require_once __DIR__ . '/exchange/okx/ExBroker.php';
require_once __DIR__ . '/exchange/binance/ExBroker.php';
require_once __DIR__ . '/struct/Kline.php';
require_once __DIR__ . '/struct/Order.php';
require_once __DIR__ . '/struct/WsData.php';
require_once __DIR__ . '/struct/WsDataTrade.php';
require_once __DIR__ . '/struct/WsDataPos.php';
require_once __DIR__ . '/struct/SymbolInfo.php';
require_once __DIR__ . '/struct/WsDataOrder.php';
require_once __DIR__ . '/struct/WsDataAccount.php';
require_once __DIR__ . '/struct/Pos.php';
require_once __DIR__ . '/../jytools/func.php';
require_once __DIR__ . '/../jytools/Websocket.php';


use trader\struct\ApiInfo;
use trader\exchange\okx\ExBroker as OkxBroker;
use trader\exchange\binance\ExBroker as BinanceBroker;
use trader\struct\Kline;
use trader\struct\Order;
use trader\struct\WsData;
use trader\struct\WsDataTrade;
use trader\struct\WsDataPos;
use trader\struct\SymbolInfo;
use trader\struct\WsDataOrder;
use trader\struct\WsDataAccount;
use trader\struct\Pos;
use \Exception;
use function jytools\timeFormat;

class CmBroker
{
    const PLAT_OKX = 'okx';
    const PLAT_BINANCE = 'binance';
    const PLAT_BYBIT = 'bybit';
    const PLAT_BITGET = 'bitget';
    /**
     * @var string 当前使用的交易平台
     * @see PLAT_OKX
     * @see PLAT_BINANCE
     * @see PLAT_BYBIT 
     * @see PLAT_BITGET
     */
    public $plat;
    private OkxBroker|BinanceBroker $exBroker;
    /** @var SymbolInfo[] $symbolInfos */
    public $symbolInfos = [];
    /** @var Pos[] $positions */
    public $positions = [];
    public $name = '';
    public $levers = [];


    public function __construct($plat, ApiInfo $apiInfo)
    {
        $this->plat = $plat;
        $exBroker = null;
        if ($plat == self::PLAT_OKX) {
            $exBroker = new OkxBroker($apiInfo);
        }
        if ($plat == self::PLAT_BINANCE) {
            $exBroker = new BinanceBroker($apiInfo);
        }
        $this->exBroker = $exBroker;
    }
    public function setWsHost($host)
    {
        $this->exBroker->setWsHost($host);
    }
    public function setRestHost($host)
    {
        $this->exBroker->setRestHost($host);
    }
    public function setName($name)
    {
        $this->name = $name;
    }
    public function init()
    {
        $this->initSymbolInfos();
    }
    public function accListen(callable $onData)
    {
        $this->exBroker->accListen(function ($data) use ($onData) {
            // output("ws 有效数据", $data);
            if ($this->plat == self::PLAT_BINANCE) {
                $this->binanceAccDataHandle($data, $onData);
            }
            if ($this->plat == self::PLAT_OKX) {
                $this->okxAccDataHandle($data, $onData);
            }
        });
    }
    //处理币安相关账户数据监听
    private function binanceAccDataHandle($data, $onData)
    {
        $this->msg("binance 推送", $data);
    }
    //处理欧意账户相关数据监听
    private function okxAccDataHandle($data, $onData)
    {
        if (isset($data['arg']) && $data['arg']['channel'] == 'orders') {
            foreach ($data['data'] as $key => $value) {
                $wsDataTrade = WsDataTrade::TransferOkxOrder($value, $this->symbolInfos, function ($symbol) {
                    return $this->getSymbolSt($symbol);
                });
                if ($wsDataTrade != null) {
                    $wsData = new WsData($this->plat, 'trade', $trade = $wsDataTrade);
                    $onData($wsData);
                }
                $wsDataOrd = WsDataOrder::TransferOkxOrder($value, $this->symbolInfos, function ($symbol) {
                    return $this->getSymbolSt($symbol);
                });
                if ($wsDataOrd != null) {
                    $wsData = new WsData($this->plat, 'order', $trade = null, $pos = null, $order = $wsDataOrd);
                    $onData($wsData);
                }
            }
            return;
        }
        if (isset($data['arg']) && $data['arg']['channel'] == 'positions') {
            $positions = [];
            $eventType = $data['eventType'];
            foreach ($data['data'] as $key => $value) {
                $wsDataPos = WsDataPos::TransferOkxPos($value, $this->symbolInfos, function ($symbol) {
                    return $this->getSymbolSt($symbol);
                });
                if ($wsDataPos) {
                    $pos = Pos::transferWsDataPos($wsDataPos);
                    $positions[$wsDataPos->symbol . "_" . $wsDataPos->posSide] = $pos;
                    $wsData = new WsData($this->plat, 'pos', $trade = null, $pos = $wsDataPos);
                    $onData($wsData);
                    if ($eventType == 'event_update') {
                        $this->positions[$wsDataPos->symbol . "_" . $wsDataPos->posSide] = $pos;
                    }
                }
            }
            if ($eventType == 'snapshot') {
                $this->positions = $positions;
            }
            return;
        }
        if (isset($data['arg']) && $data['arg']['channel'] == 'account') {
            $wsDataAccount = WsDataAccount::TransferOkxAccount($data['data']);
            $wsData = new WsData($this->plat, 'account', $trade = null, $pos = null, $order = null, $account = $wsDataAccount);
            $onData($wsData);
            return;
        }
        $this->msg("okx 无处理ws数据实现", $data);
    }
    public function klineListen($symbol, $peroid, $onData)
    {
        if ($this->plat == self::PLAT_BINANCE && $peroid == '1s') {
            $this->msg("币安不支持1sk线", $symbol, $peroid);
            return;
        }
        $symbol = $this->getSymbolOri($symbol, $this->plat);
        $this->exBroker->klineListen($symbol, $peroid, function ($data) use ($onData) {
            if ($this->plat == self::PLAT_BINANCE) {
                $kline = Kline::transferBinance($data);
            }
            if ($this->plat == self::PLAT_OKX) {
                $kline = Kline::transferOkx($data);
            }
            $onData($kline);
        });
    }
    //转换为标准交易对
    public function getSymbolSt($symbol)
    {
        $symbol = str_replace('-USDT-SWAP', 'USDT',  $symbol);
        if (preg_match('/^[A-Z0-9]+USDT$/', $symbol)) {
            return $symbol;
        } else {
            throw new Exception('转换标准交易对错误' + $symbol + ' to ', $symbol);
        }
    }

    //转换为原始交易对
    public function getSymbolOri($symbol, $platTarget)
    {
        $symbolSt = $this->getSymbolSt($symbol);
        if ($platTarget == self::PLAT_BINANCE) {
            return $symbolSt;
        }
        if ($platTarget == self::PLAT_OKX) {
            return str_replace('USDT', '-USDT-SWAP', $symbolSt);
        }
        throw new Exception('平台错误' + $platTarget);
    }

    public function placeOrder(Order $order)
    {
        if ($this->plat == self::PLAT_OKX) {
            $orderOri = $order->toOkxOrder($this->symbolInfos, function ($symbol) {
                return $this->getSymbolOri($symbol, $this->plat);
            });
            /** @var SymbolInfo $symbolInfo */
            $symbolInfo = $this->symbolInfos[$order->symbol];
            $this->msg("下单", $orderOri);
            if ($orderOri['sz'] == 0) {
                $msg = "当前下单数量为{$order->qty},最小下单数量为{$symbolInfo->minQty},不下单";
                $this->msg($msg, $orderOri);
                return ["code" => 2, "msg" => $msg];
            }
            $res = $this->exBroker->placeOrder($orderOri);
            $this->msg("下单结果", $res);
            return $res;
        }
    }

    public function msg()
    {
        $args = func_get_args();
        $outStr = '[' . timeFormat('ms') . ']:' . $this->name . ": ";
        foreach ($args as $key => $value) {
            $value = is_array($value) ? json_encode($value) : $value;
            if (is_bool($value)) {
                $value = $value ? 'bool:true' : 'bool:false';
            }
            $outStr .= count($args) - $key > 1 ? $value . '  ' : $value;
        }
        echo $outStr . PHP_EOL;
    }
    public function getPosQty($symbol, $posSide)
    {
        $key = $symbol . "_" . $posSide;
        if (!isset($this->positions[$key])) {
            return 0;
        }
        /** @var Pos $pos */
        $pos = $this->positions[$key];
        return abs($pos->qty);
    }
    private function initSymbolInfos()
    {
        if ($this->plat == self::PLAT_OKX) {
            //获取所有USDT SWAP 交易对
            $res = $this->exBroker->getSymbolInfos();
            $infos = [];
            foreach ($res as $key => $value) {
                if ($value["settleCcy"] == "USDT") {
                    $info = SymbolInfo::transferOkx($value, function ($symbol) {
                        return $this->getSymbolSt($symbol);
                    });
                    $infos[$info->symbol] = $info;
                }
            }
            $this->symbolInfos = $infos;
        }
        if ($this->plat == self::PLAT_BINANCE) {
            $res = $this->exBroker->getSymbolInfos();
            $infos = [];
            foreach ($res as $key => $value) {
                if ($value["quoteAsset"] == "USDT") {
                    $info = SymbolInfo::transferBinance($value, function ($symbol) {
                        return $this->getSymbolSt($symbol);
                    });
                    $infos[$info->symbol] = $info;
                }
            }
        }
        //定时10m刷新
        swoole_timer_after(1000 * 60 * 10, function () {
            $this->initSymbolInfos();
        });
    }
    public function stopListen()
    {
        $this->exBroker->stopListen();
    }
    public function closeAllPos()
    {
        $this->exBroker->closeAllPos();
    }
    //设置允许合约
    public function setAllowFutures($isAllowFutures)
    {
        $this->exBroker->setAllowFutures($isAllowFutures);
    }
    //设置双向持仓模式
    public function setLongShortMode($isLongShort)
    {
        $this->exBroker->setLongShortMode($isLongShort);
    }
    //获取全部品种的杠杆
    public function getLevers()
    {
        $levers = $this->exBroker->getAllLevers();
        if ($this->plat == self::PLAT_OKX || $this->plat == self::PLAT_BINANCE) {
            $resNew = [];
            foreach ($levers as $key => $value) {
                $symbol = $this->getSymbolSt($key); //转换为标准交易对
                $resNew[$symbol] = $value;
            }
            $levers = $resNew;
        }
        $this->levers = $levers; //缓存levers
        return $levers;
    }
    //设置杠杆
    public function setLever($symbol, $lever)
    {
        $symbol = $this->getSymbolOri($symbol, $this->plat);
        $res = $this->exBroker->setLever($symbol, $lever);
        if ($res) {
            $this->levers[$symbol] = $lever;
        }
    }
    //查询获取全部持仓
    public function getAllPos(): array
    {
        $positions = $this->exBroker->getAllPos();
        $newPositions = [];
        foreach ($positions as $key => $value) {
            $item = Pos::transferOkxPos($value, $this->symbolInfos, function ($symbol) {
                return $this->getSymbolSt($symbol);
            });
            $newPositions[$item->symbol . "_" . $item->posSide] = $item;
        }
        return $newPositions;
    }
    //获取某个品种的某个方向仓位
    public function getPos($symbol, $posSide)
    {
        $symbolOri = $this->getSymbolOri($symbol, $this->plat);
        if ($this->plat == self::PLAT_OKX) {
            $posSide = strtolower($posSide);
            $symbolInfo = $this->symbolInfos[$symbol];
            $lot = $this->exBroker->getPos($symbolOri, $posSide);
            $qty = round($lot * $symbolInfo->ctVal, $symbolInfo->qtyPrec);
            return $qty;
        }
        return -1;
    }
    /**
     * 获取k线数据
     * @param string $symbol 交易对
     * @param string $peroid 周期
     * @param int $limit 限制数量
     * @return Kline[] 返回K线数组
     */
    public function getKlines($symbol, $peroid, $limit = 100)
    {
        $symbolOri = $this->getSymbolOri($symbol, $this->plat);
        if ($this->plat == self::PLAT_OKX) {
            $res = $this->exBroker->getKlines($symbolOri, $peroid, $limit);
            if ($res['code'] != '0') {
                return [];
            }
            $newKlines = [];
            foreach ($res['data'] as $key => $value) {
                $kline = Kline::transferOkx($value);
                $newKlines[] = $kline;
            }
            //把 $newKlines 倒序
            $newKlines = array_reverse($newKlines);
            return $newKlines;
        }
        return [];
    }
}