作者 karlet

feat:增加trader

1 logs 1 logs
  2 +vendor
  3 +composer.lock
1 { 1 {
2 - "name": "jiaoyin/common", 2 + "name": "jiaoyin/base",
3 "description": "indigo common php lib by jy", 3 "description": "indigo common php lib by jy",
4 "require": { 4 "require": {
5 "ext-json": "*" 5 "ext-json": "*"
6 }, 6 },
7 "autoload": { 7 "autoload": {
8 "psr-4": { 8 "psr-4": {
9 - "Jiaoyin\\": "./src/" 9 + "Jiaoyin\\": "./src/",
  10 + "Trader\\": "./trader/"
10 }, 11 },
11 - "files": [ "src/functions.php" ]  
12 - } 12 + "files": [ "src/functions.php", "trader/common/func.php" ]
  13 + },
  14 + "require-dev": {
  15 + "swoole/ide-helper": "^5.0"
  16 + }
13 } 17 }
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 3
4 class Auth 4 class Auth
5 { 5 {
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 3
4 /* 4 /*
5 binance合约接口 5 binance合约接口
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 3
4 /* 4 /*
5 binance现货接口 5 binance现货接口
1 <?php 1 <?php
2 2
3 -namespace Jiaoyin; 3 +namespace jiaoyin;
4 4
5 class Curl 5 class Curl
6 { 6 {
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 3
4 class Feishu{ 4 class Feishu{
5 const user_liquid = 'gd1d6b34'; 5 const user_liquid = 'gd1d6b34';
1 <?php 1 <?php
2 2
3 -namespace Jiaoyin; 3 +namespace jiaoyin;
4 4
5 class KlineAssist 5 class KlineAssist
6 { 6 {
1 <?php 1 <?php
2 2
3 -namespace Jiaoyin; 3 +namespace jiaoyin;
4 require_once __DIR__ . '/functions.php'; 4 require_once __DIR__ . '/functions.php';
5 class Logger 5 class Logger
6 { 6 {
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 3
4 class MongoCli 4 class MongoCli
5 { 5 {
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 3
4 use MongoDB\Client; 4 use MongoDB\Client;
5 use Swoole\Coroutine; 5 use Swoole\Coroutine;
1 <?php 1 <?php
2 2
3 -namespace Jiaoyin; 3 +namespace jiaoyin;
4 4
5 use Swoole\Database\MysqliConfig; 5 use Swoole\Database\MysqliConfig;
6 use Swoole\Database\MysqliPool; 6 use Swoole\Database\MysqliPool;
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 3
4 /* 4 /*
5 所有接口 5 所有接口
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 3
4 use Swoole\Database\RedisConfig; 4 use Swoole\Database\RedisConfig;
5 use Swoole\Database\RedisPool; 5 use Swoole\Database\RedisPool;
1 <?php 1 <?php
2 2
3 -namespace Jiaoyin; 3 +namespace jiaoyin;
4 4
5 class SimpleRequest 5 class SimpleRequest
6 { 6 {
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 use Swoole\Http\Request; 3 use Swoole\Http\Request;
4 use Swoole\Http\Response; 4 use Swoole\Http\Response;
5 use Swoole\Http\Server; 5 use Swoole\Http\Server;
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 use Swoole\Http\Request; 3 use Swoole\Http\Request;
4 use Swoole\Http\Response; 4 use Swoole\Http\Response;
5 use Swoole\Coroutine\Http\Server; 5 use Swoole\Coroutine\Http\Server;
1 <?php 1 <?php
2 2
3 -namespace Jiaoyin; 3 +namespace jiaoyin;
4 4
5 use Swoole\Coroutine; 5 use Swoole\Coroutine;
6 use Swoole\Coroutine\Http\Client; 6 use Swoole\Coroutine\Http\Client;
@@ -26,7 +26,7 @@ class Websocket @@ -26,7 +26,7 @@ class Websocket
26 private int $timerPing = 0; 26 private int $timerPing = 0;
27 private string $desc = 'websocket未命名'; 27 private string $desc = 'websocket未命名';
28 private int $pingCount = 0; 28 private int $pingCount = 0;
29 - private int $pingDelay = 10000;//ping tick时间 10s 29 + private int $pingDelay = 10000; //ping tick时间 10s
30 30
31 // 例如:wss://fstream.binance.com/stream、ws://175.178.36.217:9528/spot/stream 31 // 例如:wss://fstream.binance.com/stream、ws://175.178.36.217:9528/spot/stream
32 public function __construct($url, $desc = null) 32 public function __construct($url, $desc = null)
@@ -64,9 +64,13 @@ class Websocket @@ -64,9 +64,13 @@ class Websocket
64 $this->client?->close(); 64 $this->client?->close();
65 } 65 }
66 66
67 - public function connect(callable $onOpen = null, callable $onMessage = null,  
68 - callable $onClose = null, $onPing = null, $onPong = null): void  
69 - { 67 + public function connect(
  68 + callable $onOpen = null,
  69 + callable $onMessage = null,
  70 + callable $onClose = null,
  71 + $onPing = null,
  72 + $onPong = null
  73 + ): void {
70 $this->close(); 74 $this->close();
71 $this->pingCount = 0; 75 $this->pingCount = 0;
72 $this->onOpen = $onOpen; 76 $this->onOpen = $onOpen;
@@ -80,8 +84,8 @@ class Websocket @@ -80,8 +84,8 @@ class Websocket
80 $ret = $client->upgrade($this->path); 84 $ret = $client->upgrade($this->path);
81 if ($ret) { 85 if ($ret) {
82 output($this->desc, "连接成功"); 86 output($this->desc, "连接成功");
83 - $this->client=&$client;  
84 - swoole_timer_after(50, function () use ($onOpen,$client) { 87 + $this->client = &$client;
  88 + swoole_timer_after(50, function () use ($onOpen, $client) {
85 if ($onOpen) { 89 if ($onOpen) {
86 call_user_func($onOpen, $client); 90 call_user_func($onOpen, $client);
87 } 91 }
@@ -90,7 +94,7 @@ class Websocket @@ -90,7 +94,7 @@ class Websocket
90 while ($client) { 94 while ($client) {
91 $frame = $client->recv($this->recvTimeout); 95 $frame = $client->recv($this->recvTimeout);
92 if (!$frame && $client->errCode != 60) { 96 if (!$frame && $client->errCode != 60) {
93 - output($this->desc,'错误码:' . $client->errCode.",错误数据:", $frame); 97 + output($this->desc, '错误码:' . $client->errCode . ",错误数据:", $frame);
94 break; 98 break;
95 } 99 }
96 $this->lastRecvTime = time(); 100 $this->lastRecvTime = time();
@@ -185,4 +189,4 @@ class Websocket @@ -185,4 +189,4 @@ class Websocket
185 } 189 }
186 }); 190 });
187 } 191 }
188 -}  
  192 +}
1 <?php 1 <?php
2 -namespace Jiaoyin; 2 +namespace jiaoyin;
3 //格式化输出 3 //格式化输出
4 use Swoole\Process; 4 use Swoole\Process;
5 use Swoole\Event; 5 use Swoole\Event;
  1 +<?php
  2 +
  3 +namespace trader;
  4 +
  5 +require_once __DIR__ . '/autoload.php';
  6 +require_once __DIR__ . '/../vendor/autoload.php';
  7 +
  8 +use trader\struct\ApiInfo;
  9 +use trader\okx\ExBroker as OkxBroker;
  10 +use trader\binance\ExBroker as BinanceBroker;
  11 +use trader\struct\Kline;
  12 +use trader\struct\Order;
  13 +use trader\struct\WsData;
  14 +use trader\struct\WsDataTrade;
  15 +use trader\struct\WsDataPos;
  16 +use trader\struct\SymbolInfo;
  17 +use trader\struct\WsDataOrder;
  18 +use trader\struct\Pos;
  19 +use \Exception;
  20 +use function Jiaoyin\timeFormat;
  21 +use trader\okx\Api as OkApi;
  22 +
  23 +class CmBroker
  24 +{
  25 + private $plat;
  26 + private ?OkxBroker $exBroker;
  27 + /** @var SymbolInfo[] $symbolInfos */
  28 + public $symbolInfos = [];
  29 + /** @var Pos[] $positions */
  30 + private $positions = [];
  31 + private $name = '';
  32 +
  33 +
  34 + public function __construct($plat, ApiInfo $apiInfo)
  35 + {
  36 + $this->plat = $plat;
  37 + $exBroker = null;
  38 + if ($plat == 'okx') {
  39 + $exBroker = new OkxBroker($apiInfo);
  40 + }
  41 + if ($plat == 'binance') {
  42 + $exBroker = new BinanceBroker($apiInfo);
  43 + }
  44 + $this->exBroker = $exBroker;
  45 + }
  46 + public function setName($name)
  47 + {
  48 + $this->name = $name;
  49 + }
  50 + public function init()
  51 + {
  52 + $this->initSymbolInfos();
  53 + }
  54 + public function accListen(callable $onData)
  55 + {
  56 + $this->exBroker->accListen(function ($data) use ($onData) {
  57 + // output("ws 有效数据", $data);
  58 + if ($this->plat == 'binance') {
  59 + $this->msg("binance 无处理ws数据实现");
  60 + }
  61 + if ($this->plat == 'okx') {
  62 + if (isset($data['arg']) && $data['arg']['channel'] == 'orders') {
  63 + foreach ($data['data'] as $key => $value) {
  64 + $wsDataTrade = WsDataTrade::TransferOkxOrder($value, $this->symbolInfos, function ($symbol) {
  65 + return $this->getSymbolSt($symbol);
  66 + });
  67 + if ($wsDataTrade != null) {
  68 + $wsData = new WsData($this->plat, 'trade', $trade = $wsDataTrade);
  69 + $onData($wsData);
  70 + }
  71 + $wsDataOrd = WsDataOrder::TransferOkxOrder($value, $this->symbolInfos, function ($symbol) {
  72 + return $this->getSymbolSt($symbol);
  73 + });
  74 + if ($wsDataOrd != null) {
  75 + $wsData = new WsData($this->plat, 'order', $trade = null, $pos = null, $order = $wsDataOrd);
  76 + $onData($wsData);
  77 + }
  78 + }
  79 + return;
  80 + }
  81 + if (isset($data['arg']) && $data['arg']['channel'] == 'positions') {
  82 + $positions = [];
  83 + foreach ($data['data'] as $key => $value) {
  84 + $wsDataPos = WsDataPos::TransferOkxPos($value, $this->symbolInfos, function ($symbol) {
  85 + return $this->getSymbolSt($symbol);
  86 + });
  87 + if ($wsDataPos) {
  88 + $pos = Pos::transferWsDataPos($wsDataPos);
  89 + $positions[$wsDataPos->symbol . "_" . $wsDataPos->posSide] = $pos;
  90 + $wsData = new WsData($this->plat, 'pos', $trade = null, $pos = $wsDataPos);
  91 + $onData($wsData);
  92 + }
  93 + }
  94 + $this->positions = $positions;
  95 + return;
  96 + }
  97 + $this->msg("okx 无处理ws数据实现", $data);
  98 + }
  99 + });
  100 + }
  101 + public function klineListen($symbol, $peroid, $onData)
  102 + {
  103 + $symbol = $this->getSymbolOri($symbol, $this->plat);
  104 + $this->exBroker->klineListen($symbol, $peroid, function ($data) use ($onData) {
  105 + $kline = Kline::transferOkx($data);
  106 + $onData($kline);
  107 + });
  108 + }
  109 + //转换为标准交易对
  110 + public function getSymbolSt($symbol)
  111 + {
  112 + $symbol = str_replace('-USDT-SWAP', 'USDT', $symbol);
  113 + if (preg_match('/^[A-Z0-9]+USDT$/', $symbol)) {
  114 + return $symbol;
  115 + } else {
  116 + throw new Exception('转换标准交易对错误' + $symbol + ' to ', $symbol);
  117 + }
  118 + }
  119 +
  120 + //转换为原始交易对
  121 + public function getSymbolOri($symbol, $platTarget)
  122 + {
  123 + $symbolSt = $this->getSymbolSt($symbol);
  124 + if ($platTarget == 'binance') {
  125 + return $symbolSt;
  126 + }
  127 + if ($platTarget == 'okx') {
  128 + return str_replace('USDT', '-USDT-SWAP', $symbolSt);
  129 + }
  130 + throw new Exception('平台错误' + $platTarget);
  131 + }
  132 +
  133 + public function placeOrder(Order $order)
  134 + {
  135 + if ($this->plat == 'okx') {
  136 + $orderOri = $order->toOkxOrder($this->symbolInfos, function ($symbol) {
  137 + return $this->getSymbolOri($symbol, $this->plat);
  138 + });
  139 + $this->msg("下单", $orderOri);
  140 + if ($orderOri['sz'] == 0) {
  141 + $this->msg("下单数量为0,不下单", $orderOri);
  142 + return ["code" => 1, "msg" => "下单数量为0,不下单"];
  143 + }
  144 + $res = $this->exBroker->placeOrder($orderOri);
  145 + $this->msg("下单结果", $res);
  146 + return $res;
  147 + }
  148 + }
  149 +
  150 + public function msg()
  151 + {
  152 + $args = func_get_args();
  153 + $outStr = '[' . timeFormat('ms') . ']:' . $this->name . ": ";
  154 + foreach ($args as $key => $value) {
  155 + $value = is_array($value) ? json_encode($value) : $value;
  156 + if (is_bool($value)) {
  157 + $value = $value ? 'bool:true' : 'bool:false';
  158 + }
  159 + $outStr .= count($args) - $key > 1 ? $value . ' ' : $value;
  160 + }
  161 + echo $outStr . PHP_EOL;
  162 + }
  163 + public function getPosQty($symbol, $posSide)
  164 + {
  165 + $key = $symbol . "_" . $posSide;
  166 + if (!isset($this->positions[$key])) {
  167 + return 0;
  168 + }
  169 + /** @var Pos $pos */
  170 + $pos = $this->positions[$key];
  171 + return abs($pos->qty);
  172 + }
  173 + private function initSymbolInfos()
  174 + {
  175 + if ($this->plat == 'okx') {
  176 + //获取所有USDT SWAP 交易对
  177 + $res = $this->exBroker->getSymbolInfos();
  178 + $infos = [];
  179 + foreach ($res as $key => $value) {
  180 + if ($value["settleCcy"] == "USDT") {
  181 + $info = SymbolInfo::transferOkx($value, function ($symbol) {
  182 + return $this->getSymbolSt($symbol);
  183 + });
  184 + $infos[$info->symbol] = $info;
  185 + }
  186 + }
  187 + $this->symbolInfos = $infos;
  188 + }
  189 + //定时10m刷新
  190 + swoole_timer_after(1000 * 60 * 10, function () {
  191 + $this->initSymbolInfos();
  192 + });
  193 + }
  194 + public function stopListen()
  195 + {
  196 + $this->exBroker->stopListen();
  197 + }
  198 +}
  1 +<?php
  2 +require_once __DIR__ . '/CmBroker.php';
  3 +require_once __DIR__ . '/common/tool.php';
  4 +require_once __DIR__ . '/struct/ApiInfo.php';
  5 +require_once __DIR__ . '/struct/SymbolInfo.php';
  6 +require_once __DIR__ . '/struct/Order.php';
  7 +require_once __DIR__ . '/struct/WsData.php';
  8 +require_once __DIR__ . '/struct/WsDataTrade.php';
  9 +require_once __DIR__ . '/struct/WsDataPos.php';
  10 +require_once __DIR__ . '/struct/WsDataOrder.php';
  11 +require_once __DIR__ . '/struct/Pos.php';
  12 +require_once __DIR__ . '/struct/Kline.php';
  13 +require_once __DIR__ . '/exchange/okx/ExBroker.php';
  1 +<?php
  2 +
  3 +namespace trader;
  4 +
  5 +
  6 +function scToNum($scStr)
  7 +{
  8 + $check_str = '';
  9 + if (strpos($scStr, 'e') !== false) {
  10 + $check_str = 'e';
  11 + }
  12 + if (strpos($scStr, 'E') !== false) {
  13 + $check_str = 'E';
  14 + }
  15 + if (empty($check_str)) {
  16 + return $scStr; //非科学计数直接返回。
  17 + }
  18 + $num_length = 0;
  19 + $split_array = explode($check_str, $scStr);
  20 + $num_length += strlen($split_array[0]);
  21 + $num_length += (int)str_replace(['-', '+'], '', $split_array[1]);
  22 + $float_number = (float)$scStr;
  23 + $decimal_string = number_format($float_number, $num_length, '.', '');
  24 + $num = rtrim(rtrim($decimal_string, '0'), '.'); //去除小数后多余的0
  25 + return $num;
  26 +}
  27 +function tsToISO($timestamp)
  28 +{
  29 + $datetime = new \DateTime();
  30 + $datetime->setTimestamp(floor($timestamp / 1000));
  31 + $datetime->setTimezone(new \DateTimeZone('UTC'));
  32 + $milliseconds = sprintf('.%03d', $timestamp % 1000);
  33 + return $datetime->format('Y-m-d\TH:i:s') . $milliseconds . 'Z';
  34 +}
  1 +<?php
  2 +
  3 +namespace trader\binance;
  4 +
  5 +require_once __DIR__ . '/../../autoload.php';
  6 +require_once __DIR__ . '/../../../vendor/autoload.php';
  7 +
  8 +use trader\struct\ApiInfo;
  9 +
  10 +class Api
  11 +{
  12 + private $host = 'https://fapi.binance.com';
  13 + private ApiInfo $apiInfo;
  14 + public function __construct(ApiInfo $apiInfo)
  15 + {
  16 + $this->apiInfo = $apiInfo;
  17 + }
  18 +
  19 + public function getPremiumIndex()
  20 + {
  21 + $url = "/fapi/v1/premiumIndex";
  22 + $method = "GET";
  23 + }
  24 +}
  1 +<?php
  2 +
  3 +namespace trader\binance;
  4 +
  5 +require_once __DIR__ . '/../../../vendor/autoload.php';
  6 +require_once __DIR__ . '/Api.php';
  7 +
  8 +use trader\struct\ApiInfo;
  9 +use trader\binance\Api as BnApi;
  10 +use Jiaoyin\Websocket;
  11 +
  12 +class ExBroker
  13 +{
  14 + // static private $host = 'wss://ws.okx.com:8443';
  15 + static private $host = 'ws://okws.keetu.com';
  16 + static private $pathPrivate = '/ws/v5/private';
  17 + static private $pathPublic = '/ws/v5/public';
  18 + static private $pathBusiness = '/ws/v5/business';
  19 + private ApiInfo $apiInfo;
  20 + private BnApi $api;
  21 + private ?Websocket $wsAcc;
  22 + private ?Websocket $wsKline;
  23 +
  24 + public function __construct(ApiInfo $apiInfo)
  25 + {
  26 + $this->apiInfo = $apiInfo;
  27 + $this->api = new BnApi($apiInfo);
  28 + }
  29 +
  30 + //获取所有品种资金费率
  31 + public function getAllPremium() {
  32 + $res = $this->api->getPremiumIndex();
  33 + }
  34 +}
  1 +<?php
  2 +
  3 +namespace trader\okx;
  4 +
  5 +require_once __DIR__ . '/../../autoload.php';
  6 +require_once __DIR__ . '/../../../vendor/autoload.php';
  7 +
  8 +use trader\struct\ApiInfo;
  9 +use Jiaoyin\Curl;
  10 +use function Jiaoyin\getMicrotime;
  11 +use function trader\tsToISO;
  12 +
  13 +class Api
  14 +{
  15 + private ?ApiInfo $apiInfo = null;
  16 + // private string $host = "https://www.okx.com";
  17 + private string $host = "http://okapi.keetu.com";
  18 +
  19 + public function __construct($apiInfo = null, $host = "")
  20 + {
  21 + if ($apiInfo != null) {
  22 + $this->apiInfo = $apiInfo;
  23 + }
  24 + if ($host != "") {
  25 + $this->host = $host;
  26 + }
  27 + }
  28 +
  29 + //-----------public interface ------------
  30 + //所有交易产品基础信息
  31 + public function instruments($param)
  32 + {
  33 + $path = '/api/v5/public/instruments';
  34 + $method = 'GET';
  35 + return $this->request($path, $method, $param);
  36 + }
  37 +
  38 + public function positionTiers($param)
  39 + {
  40 + $path = '/api/v5/public/position-tiers';
  41 + $method = 'GET';
  42 + return $this->request($path, $method, $param);
  43 + }
  44 +
  45 + //获取资金费率
  46 + public function fundingRate($param)
  47 + {
  48 + $path = '/api/v5/public/funding-rate';
  49 + $method = 'GET';
  50 + return $this->request($path, $method, $param);
  51 + }
  52 +
  53 +
  54 + //-----------private interface ------------
  55 + // 查询杠杆
  56 + public function leverageInfo($param)
  57 + {
  58 + $path = '/api/v5/account/leverage-info';
  59 + $method = 'GET';
  60 + return $this->request($path, $method, $param, $this->apiInfo);
  61 + }
  62 +
  63 + //设置杠杆
  64 + public function setLeverage($param)
  65 + {
  66 + $path = '/api/v5/account/set-leverage';
  67 + $method = 'POST';
  68 + return $this->request($path, $method, $param, $this->apiInfo);
  69 + }
  70 +
  71 + //下单
  72 + public function placeOrder($param)
  73 + {
  74 + $path = '/api/v5/trade/order';
  75 + $method = 'POST';
  76 + return $this->request($path, $method, $param, $this->apiInfo);
  77 + }
  78 +
  79 + //-------------------------------------
  80 +
  81 + private function createSign($timestamp, $method, $requestPath, $secretKey, $body = '')
  82 + {
  83 + $sign = base64_encode(hash_hmac('sha256', $timestamp . $method . $requestPath . $body, $secretKey, true));
  84 + return $sign;
  85 + }
  86 +
  87 + private function request($path, $method, $param)
  88 + {
  89 + $header = [];
  90 + $body = '';
  91 + $fullPath = $path;
  92 + if ($method == 'GET' && count($param) > 0) {
  93 + $fullPath = $path . '?' . http_build_query($param);
  94 + } else {
  95 + $header[] = 'Content-Type: application/json';
  96 + $body = json_encode($param);
  97 + }
  98 + if ($this->apiInfo != null) {
  99 + $timestamp = tsToISO(getMicrotime());
  100 + $header[] = 'OK-ACCESS-KEY: ' . $this->apiInfo->key;
  101 + $header[] = 'OK-ACCESS-PASSPHRASE: ' . $this->apiInfo->passphrase;
  102 + $header[] = 'OK-ACCESS-TIMESTAMP: ' . $timestamp;
  103 + $sign = $this->createSign($timestamp, $method, $fullPath, $this->apiInfo->secret, $body);
  104 + $header[] = 'OK-ACCESS-SIGN: ' . $sign;
  105 + }
  106 +
  107 + $url = $this->host . $path;
  108 + $result = "";
  109 + if ($method == 'POST') {
  110 + $result = Curl::httpPost($url, $param, $header);
  111 + } else if ($method == 'GET') {
  112 + $result = Curl::httpGet($url, $param, $header);
  113 + } else {
  114 + $result = Curl::httpGet($url, $param, $header);
  115 + }
  116 + return json_decode($result, true);
  117 + }
  118 +}
  1 +<?php
  2 +
  3 +namespace trader\okx;
  4 +
  5 +require_once __DIR__ . '/../../../vendor/autoload.php';
  6 +require_once __DIR__ . '/Api.php';
  7 +
  8 +use trader\struct\ApiInfo;
  9 +use trader\okx\Api as OkxApi;
  10 +use Jiaoyin\Websocket;
  11 +use function Jiaoyin\output;
  12 +
  13 +
  14 +class ExBroker
  15 +{
  16 + // static private $host = 'wss://ws.okx.com:8443';
  17 + static private $host = 'ws://okws.keetu.com';
  18 + static private $pathPrivate = '/ws/v5/private';
  19 + static private $pathPublic = '/ws/v5/public';
  20 + static private $pathBusiness = '/ws/v5/business';
  21 + private ApiInfo $apiInfo;
  22 + private OkxApi $api;
  23 + private ?Websocket $wsAcc;
  24 + private ?Websocket $wsKline;
  25 +
  26 + public function __construct($apiInfo)
  27 + {
  28 + $this->apiInfo = $apiInfo;
  29 + $this->api = new OkxApi($apiInfo);
  30 + }
  31 +
  32 + public function accListen(callable $onWsData)
  33 + {
  34 + if (isset($this->wsAcc)) {
  35 + $this->wsAcc->close();
  36 + }
  37 + $this->wsAcc = new Websocket(self::$host . self::$pathPrivate);
  38 + $this->wsAcc->connect(
  39 + $onOpen = function () {
  40 + $this->wsLogin();
  41 + },
  42 + $onMessage = function ($data) use ($onWsData) {
  43 + $this->onWsDataPre($data, $onWsData);
  44 + },
  45 + $onClose = function () {
  46 + // 关闭链接
  47 + }
  48 + );
  49 + }
  50 + public function klineListen($symbol, $period, callable $onData)
  51 + {
  52 + if (isset($this->wsKline)) {
  53 + $this->wsKline->close();
  54 + }
  55 + $this->wsKline = new Websocket(self::$host . self::$pathBusiness);
  56 + $this->wsKline->connect(
  57 + $onOpen = function () use ($symbol, $period) {
  58 + $subData = [];
  59 + $subData['op'] = 'subscribe';
  60 + $subData['args'] = [
  61 + [
  62 + 'channel' => $this->getKlineChannels()[$period],
  63 + 'instId' => $symbol,
  64 + ],
  65 + ];
  66 + output("订阅", $subData);
  67 + $this->wsKline->push(json_encode($subData));
  68 + },
  69 + $onMessage = function ($data) use ($onData) {
  70 + $data = json_decode($data, true);
  71 + if (!isset($data['data'])) {
  72 + return;
  73 + }
  74 + $data = $data['data'][0];
  75 + $onData($data);
  76 + },
  77 + $onClose = function () {
  78 + // 关闭链接
  79 + }
  80 + );
  81 + }
  82 + private function crateWsSign($timeStamp)
  83 + {
  84 + $method = 'GET';
  85 + $path = '/users/self/verify';
  86 + $str = $timeStamp . $method . $path;
  87 + $sign = hash_hmac('sha256', $str, $this->apiInfo->secret, true);
  88 + $sign = base64_encode($sign);
  89 + return $sign;
  90 + }
  91 + private function wsLogin()
  92 + {
  93 + $ts = time();
  94 + $subData = [];
  95 + $subData['op'] = 'login';
  96 + $subData['args'] = [
  97 + [
  98 + 'apiKey' => $this->apiInfo->key,
  99 + 'passphrase' => $this->apiInfo->passphrase,
  100 + 'timestamp' => $ts,
  101 + 'sign' => $this->crateWsSign($ts),
  102 + ]
  103 + ];
  104 + $this->wsAcc->push(json_encode($subData));
  105 + }
  106 + // ws 消息预处理
  107 + private function onWsDataPre($data, callable $onWsData)
  108 + {
  109 + $data = json_decode($data, true);
  110 + if (isset($data['event'])) {
  111 + if ($data['event'] == 'login' && $data['code'] == '0') {
  112 + output('ws登录成功');
  113 + $this->wsSubscribe();
  114 + return;
  115 + }
  116 + if ($data['event'] == 'subscribe' || $data['event'] == 'unsubscribe' || $data['event'] == 'channel-conn-count') {
  117 + // output("ws 过滤数据", $data);
  118 + return;
  119 + }
  120 + output("ok ws 未处理数据", $data);
  121 + }
  122 + call_user_func($onWsData, $data);
  123 + }
  124 + // 订阅
  125 + private function wsSubscribe()
  126 + {
  127 + $subData = [];
  128 + $subData['op'] = 'subscribe';
  129 + $subData['args'] = [
  130 + [
  131 + 'channel' => 'orders',
  132 + 'instType' => 'SWAP',
  133 + ],
  134 + [
  135 + 'channel' => 'positions',
  136 + 'instType' => 'SWAP',
  137 + ],
  138 + ];
  139 + $this->wsAcc->push(json_encode($subData));
  140 + }
  141 + public function placeOrder($param)
  142 + {
  143 + return $this->api->placeOrder($param);
  144 + }
  145 + public function getKlineChannels()
  146 + {
  147 + return [
  148 + '1s' => 'candle1s',
  149 + '1m' => 'candle1m',
  150 + '5m' => 'candle5m',
  151 + '15m' => 'candle15m',
  152 + '30m' => 'candle30m',
  153 + '1h' => 'candle1H',
  154 + '2h' => 'candle2H',
  155 + '4h' => 'candle4H',
  156 + '6h' => 'candle6H',
  157 + '12h' => 'candle12H',
  158 + '1d' => 'candle1D',
  159 + ];
  160 + }
  161 + public function getSymbolInfos()
  162 + {
  163 + $res = $this->api->instruments(["instType" => "SWAP"]);
  164 + if ($res['code'] != '0') {
  165 + return [];
  166 + }
  167 + return $res['data'];
  168 + }
  169 + public function stopListen()
  170 + {
  171 + $this->wsAcc->close();
  172 + $this->wsKline->close();
  173 + }
  174 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +class ApiInfo
  6 +{
  7 + public string $key = '';
  8 + public string $secret = '';
  9 + public string $passphrase = '';
  10 +
  11 + public function __construct($key, $secret, $passphrase)
  12 + {
  13 + $this->key = $key;
  14 + $this->secret = $secret;
  15 + $this->passphrase = $passphrase;
  16 + }
  17 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +class Kline
  6 +{
  7 + public $time;
  8 + public $open;
  9 + public $high;
  10 + public $low;
  11 + public $close;
  12 + public $vol;
  13 + public $volQuote;
  14 +
  15 + public function __construct($time, $open, $high, $low, $close, $vol, $volQuote)
  16 + {
  17 + $this->time = $time;
  18 + $this->open = $open;
  19 + $this->high = $high;
  20 + $this->low = $low;
  21 + $this->close = $close;
  22 + $this->vol = $vol;
  23 + $this->volQuote = $volQuote;
  24 + }
  25 + public function toArray()
  26 + {
  27 + return [
  28 + 'time' => $this->time,
  29 + 'open' => $this->open,
  30 + 'high' => $this->high,
  31 + 'low' => $this->low,
  32 + 'close' => $this->close,
  33 + 'vol' => $this->vol,
  34 + 'volQuote' => $this->volQuote,
  35 + ];
  36 + }
  37 + public static function transferOkx($data)
  38 + {
  39 + $time = $data[0];
  40 + $open = $data[1];
  41 + $high = $data[2];
  42 + $low = $data[3];
  43 + $close = $data[4];
  44 + $vol = $data[6];
  45 + $volQuote = $data[7];
  46 + return new Kline($time, $open, $high, $low, $close, $vol, $volQuote);
  47 + }
  48 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +class Order
  6 +{
  7 + public string $symbol; //大写
  8 + public string $ordType; // 订单类型 LIMIT 限价 IOC 不成则撤 FOK 全部成交或立即取消 MARKET 市价 ONLYMAKER 仅做maker单
  9 + public string $posSide; //大写
  10 + public string $side; //大写
  11 + public float $price; //成交价格
  12 + public float $qty; //币种真实数量
  13 + private string $cliOrdId = ''; // 客户端订单号
  14 +
  15 +
  16 + public function __construct($symbol, $ordType, $posSide, $side, $price, $qty)
  17 + {
  18 + $this->symbol = $symbol;
  19 + $this->ordType = $ordType;
  20 + $this->posSide = $posSide;
  21 + $this->side = $side;
  22 + $this->price = $price;
  23 + $this->qty = $qty;
  24 + }
  25 + public function setCliOrdId($cliOrdId)
  26 + {
  27 + $this->cliOrdId = $cliOrdId;
  28 + }
  29 + public function getCliOrdId()
  30 + {
  31 + return $this->cliOrdId;
  32 + }
  33 +
  34 + public function toArray()
  35 + {
  36 + $arr = [
  37 + 'symbol' => $this->symbol,
  38 + 'ordType' => $this->ordType,
  39 + 'posSide' => $this->posSide,
  40 + 'side' => $this->side,
  41 + 'price' => $this->price,
  42 + 'qty' => $this->qty,
  43 + ];
  44 + if ($this->cliOrdId != "") {
  45 + $arr['cliOrdId'] = $this->cliOrdId;
  46 + }
  47 + return $arr;
  48 + }
  49 +
  50 + public function toOkxOrder(array $symbolInfos, callable $toSymbolOri)
  51 + {
  52 + $instId = call_user_func($toSymbolOri, $this->symbol);
  53 + /** @var SymbolInfo $symbolInfo */
  54 + $symbolInfo = $symbolInfos[$this->symbol];
  55 + $order = [
  56 + 'instId' => $instId,
  57 + 'tdMode' => 'cross',
  58 + 'posSide' => strtolower($this->posSide),
  59 + 'side' => strtolower($this->side),
  60 + 'ordType' => $this->ordType,
  61 + ];
  62 + if ($this->ordType == 'LIMIT' || $this->ordType == 'IOC') {
  63 + $order['px'] = round($this->price, $symbolInfo->pricePrec);
  64 + }
  65 + $lot = $this->qty / $symbolInfo->ctVal;
  66 + $order['sz'] = round($lot, $symbolInfo->lotPrec);
  67 + if ($this->ordType == 'ONLYMAKER') {
  68 + $order['ordType'] = 'POST_ONLY';
  69 + }
  70 + $order['ordType'] = strtolower($order['ordType']);
  71 + if ($this->cliOrdId != "") {
  72 + $order['clOrdId'] = $this->cliOrdId;
  73 + }
  74 + return $order;
  75 + }
  76 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +require_once __DIR__ . '/WsDataPos.php';
  6 +
  7 +use trader\struct\WsDataPos;
  8 +
  9 +class Pos
  10 +{
  11 + public $symbol;
  12 + public $posSide;
  13 + public $qty;
  14 + public $lot;
  15 + public $avgPrice;
  16 + public $pnl;
  17 + public $lever;
  18 + public $margin;
  19 +
  20 + public function __construct($symbol, $posSide, $qty, $lot, $avgPrice, $pnl, $lever, $margin)
  21 + {
  22 + $this->symbol = $symbol;
  23 + $this->posSide = $posSide;
  24 + $this->qty = $qty;
  25 + $this->lot = $lot;
  26 + $this->avgPrice = $avgPrice;
  27 + $this->pnl = $pnl;
  28 + $this->lever = $lever;
  29 + $this->margin = $margin;
  30 + }
  31 +
  32 + public function toArray()
  33 + {
  34 + return [
  35 + 'symbol' => $this->symbol,
  36 + 'posSide' => $this->posSide,
  37 + 'qty' => $this->qty,
  38 + 'lot' => $this->lot,
  39 + 'avgPrice' => $this->avgPrice,
  40 + 'pnl' => $this->pnl,
  41 + 'lever' => $this->lever,
  42 + 'margin' => $this->margin,
  43 + ];
  44 + }
  45 +
  46 + public static function transferOkxPos($pos, $symbolInfos, callable $toSymbolSt)
  47 + {
  48 + $symbol = call_user_func($toSymbolSt, $pos['instId']);
  49 + /** @var SymbolInfo $symbolInfo */
  50 + $symbolInfo = $symbolInfos[$symbol];
  51 + $posSide = strtoupper($pos['posSide']);
  52 + $lot = abs($pos['pos']);
  53 + $qty = round($lot * $symbolInfo->ctVal, $symbolInfo->qtyPrec);
  54 + $avgPrice = $pos['avgPx'];
  55 + $pnl = $pos['upl'];
  56 + $lever = $pos['lever'];
  57 + $margin = $pos['imr'];
  58 + return new Pos($symbol, $posSide, $qty, $lot, $avgPrice, $pnl, $lever, $margin);
  59 + }
  60 +
  61 + public static function transferWsDataPos(WsDataPos $wsDataPos)
  62 + {
  63 + return new Pos($wsDataPos->symbol, $wsDataPos->posSide, $wsDataPos->qty, $wsDataPos->lot, $wsDataPos->avgPrice, $wsDataPos->pnl, 0, 0);
  64 + }
  65 +}
  1 +<?php
  2 +
  3 +namespace trader\PreOrd;
  4 +
  5 +class Premium
  6 +{
  7 + private $symbol;
  8 + private $rate;
  9 + private $settleTs;
  10 + private $settleTime;
  11 +
  12 + public function __construct($symbol, $rate, $settleTs, $settleTime)
  13 + {
  14 + $this->symbol = $symbol;
  15 + $this->rate = $rate;
  16 + $this->settleTs = $settleTs;
  17 + $this->settleTime = $settleTime;
  18 + }
  19 +
  20 + public function toArray()
  21 + {
  22 + return [
  23 + 'symbol' => $this->symbol,
  24 + 'rate' => $this->rate,
  25 + 'settleTs' => $this->settleTs,
  26 + 'settleTime' => $this->settleTime,
  27 + ];
  28 + }
  29 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +require_once __DIR__ . '/../../vendor/autoload.php';
  6 +
  7 +use function Jiaoyin\getPrecision;
  8 +
  9 +class SymbolInfo
  10 +{
  11 + public string $symbolOri; // 原始交易对
  12 + public string $symbol; // 标准交易对
  13 + public string $ctVal; // 合约面值
  14 + public string $pricePrec;
  15 + public string $qtyPrec;
  16 + public string $lotPrec;
  17 + public string $minLot; // 最小下单张数
  18 + public string $minQty; // 最小下单数量
  19 +
  20 + public function __construct($symbolOri, $symbol, $ctVal, $pricePrec, $qtyPrec, $lotPrec, $minLot, $minQty)
  21 + {
  22 + $this->symbolOri = $symbolOri;
  23 + $this->symbol = $symbol;
  24 + $this->ctVal = $ctVal;
  25 + $this->pricePrec = $pricePrec;
  26 + $this->qtyPrec = $qtyPrec;
  27 + $this->lotPrec = $lotPrec;
  28 + $this->minLot = $minLot;
  29 + $this->minQty = $minQty;
  30 + }
  31 + public function toArray()
  32 + {
  33 + return [
  34 + 'symbolOri' => $this->symbolOri,
  35 + 'symbol' => $this->symbol,
  36 + 'ctVal' => $this->ctVal,
  37 + 'pricePrec' => $this->pricePrec,
  38 + 'qtyPrec' => $this->qtyPrec,
  39 + 'lotPrec' => $this->lotPrec,
  40 + 'minLot' => $this->minLot,
  41 + 'minQty' => $this->minQty,
  42 + ];
  43 + }
  44 + public static function transferOkx($data, callable $getSymbolSt): SymbolInfo
  45 + {
  46 + $symbolSt = $getSymbolSt($data["instId"]); //转换为标准交易对
  47 + $pricePrec = getPrecision($data['tickSz']);
  48 + $ctVal = $data['ctVal'];
  49 + $minLot = $data['minSz'];
  50 + $minQty = $minLot * $ctVal;
  51 + $qtyPrec = getPrecision($minQty);
  52 + $lotPrec = getPrecision($minLot);
  53 + $info = new SymbolInfo($data["instId"], $symbolSt, $ctVal, $pricePrec, $qtyPrec, $lotPrec, $minLot, $minQty);
  54 + return $info;
  55 + }
  56 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +require_once __DIR__ . '/WsDataTrade.php';
  6 +
  7 +use trader\struct\WsDataTrade;
  8 +use trader\struct\WsDataPos;
  9 +use trader\struct\WsDataOrder;
  10 +
  11 +// 综合ws数据
  12 +class WsData
  13 +{
  14 + public string $platform;
  15 + public string $dataType; // trade | order | account | pos
  16 + public ?WsDataTrade $trade = null;
  17 + public ?WsDataPos $pos = null;
  18 + public ?WsDataOrder $order = null;
  19 +
  20 +
  21 + public function __construct(string $platform, string $dataType, WsDataTrade $trade = null, WsDataPos $pos = null, WsDataOrder $order = null)
  22 + {
  23 + $this->platform = $platform;
  24 + $this->dataType = $dataType;
  25 + $this->trade = $trade;
  26 + $this->pos = $pos;
  27 + $this->order = $order;
  28 + }
  29 +
  30 + public function toArray()
  31 + {
  32 + return [
  33 + 'platform' => $this->platform,
  34 + 'dataType' => $this->dataType,
  35 + 'trade' => $this->trade?->toArray(),
  36 + 'pos' => $this->pos?->toArray(),
  37 + 'order' => $this->order?->toArray(),
  38 + ];
  39 + }
  40 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +require_once __DIR__ . '/../autoload.php';
  6 +
  7 +use trader\struct\SymbolInfo;
  8 +
  9 +class WsDataOrder
  10 +{
  11 + public string $platform;
  12 + public string $posSide; //大写
  13 + public string $symbol; //大写
  14 + public string $side; //大写
  15 + public float $price; //下单价格
  16 + public float $avgPx; //成交均价
  17 + public float $qty; //币种真实数量
  18 + public float $lot; //合约张数
  19 + public float $pnl; //盈亏
  20 + public int $ts; //下单时间戳 ms
  21 + public int $uts; //更新时间戳 ms
  22 + public string $ordType; //下单类型
  23 + public string $cliOrdId; //客户端订单号
  24 + public string $ordId; //订单号
  25 + public string $status; //订单状态
  26 +
  27 + public function __construct(string $platform, string $posSide, string $symbol, string $side, float $price, float $avgPx, float $qty, float $lot, float $pnl, int $ts, int $uts, string $ordType, string $cliOrdId, string $ordId, string $status)
  28 + {
  29 + $this->platform = $platform;
  30 + $this->posSide = $posSide;
  31 + $this->symbol = $symbol;
  32 + $this->side = $side;
  33 + $this->price = $price;
  34 + $this->avgPx = $avgPx;
  35 + $this->qty = $qty;
  36 + $this->lot = $lot;
  37 + $this->pnl = $pnl;
  38 + $this->ts = $ts;
  39 + $this->uts = $uts;
  40 + $this->ordType = $ordType;
  41 + $this->cliOrdId = $cliOrdId;
  42 + $this->ordId = $ordId;
  43 + $this->status = $status;
  44 + }
  45 +
  46 + public function toArray()
  47 + {
  48 + return [
  49 + 'platform' => $this->platform,
  50 + 'posSide' => $this->posSide,
  51 + 'symbol' => $this->symbol,
  52 + 'side' => $this->side,
  53 + 'price' => $this->price,
  54 + 'avgPx' => $this->avgPx,
  55 + 'qty' => $this->qty,
  56 + 'lot' => $this->lot,
  57 + 'pnl' => $this->pnl,
  58 + 'ts' => $this->ts,
  59 + 'uts' => $this->uts,
  60 + 'ordType' => $this->ordType,
  61 + 'cliOrdId' => $this->cliOrdId,
  62 + 'ordId' => $this->ordId,
  63 + 'status' => $this->status,
  64 + ];
  65 + }
  66 +
  67 + public static function TransferOkxOrder(array $data, array $symbolInfos, callable $toSymbolSt): WsDataOrder|null
  68 + {
  69 + $symbol = call_user_func($toSymbolSt, $data['instId']);
  70 + /** @var SymbolInfo $symbolInfo */
  71 + $symbolInfo = $symbolInfos[$symbol] ?? null;
  72 + if ($symbolInfo == null) {
  73 + return null;
  74 + }
  75 + $platform = 'okx';
  76 + $posSide = strtoupper($data['posSide']);
  77 + $side = strtoupper($data['side']);
  78 + $price = (float)$data['px'];
  79 + $avgPx = (float)$data['avgPx'];
  80 + $lot = (float)$data['sz'];
  81 + $qty = round($lot * $symbolInfo->ctVal, $symbolInfo->qtyPrec);
  82 + $pnl = (float)$data['pnl'];
  83 + $ts = (int)$data['cTime'];
  84 + $uts = (int)$data['uTime'];
  85 + $ordType = strtoupper($data['ordType']);
  86 + $cliOrdId = $data['clOrdId'];
  87 + $ordId = $data['ordId'];
  88 + $status = self::getStatus($data['state']);
  89 + if ($data['cancelSource'] != '') {
  90 + $status = 'CANCELED';
  91 + }
  92 + return new WsDataOrder($platform, $posSide, $symbol, $side, $price, $avgPx, $qty, $lot, $pnl, $ts, $uts, $ordType, $cliOrdId, $ordId, $status);
  93 + }
  94 + private static function getStatus(string $state)
  95 + {
  96 + if ($state == 'live') {
  97 + return 'LIVE';
  98 + }
  99 + if ($state == 'partially_filled') {
  100 + return 'PARTIALLY_FILLED';
  101 + }
  102 + if ($state == 'filled') {
  103 + return 'FILLED';
  104 + }
  105 + if ($state == 'canceled') {
  106 + return 'CANCELED';
  107 + }
  108 + return 'UNKNOWN';
  109 + }
  110 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +require_once __DIR__ . '/../autoload.php';
  6 +
  7 +use trader\struct\SymbolInfo;
  8 +
  9 +class WsDataPos
  10 +{
  11 + public string $symbol;
  12 + public string $posSide; //大写
  13 + public float $qty; //币种真实数量
  14 + public float $lot; //合约张数
  15 + public float $avgPrice; //平均持仓价
  16 + public bool $isSnapshot; //是否快照;
  17 + public float $pnl; //盈亏
  18 +
  19 + public function __construct($symbol, $posSide, $qty, $lot, $avgPrice, $isSnapshot, $pnl)
  20 + {
  21 + $this->symbol = $symbol;
  22 + $this->posSide = $posSide;
  23 + $this->qty = $qty;
  24 + $this->lot = $lot;
  25 + $this->avgPrice = $avgPrice;
  26 + $this->isSnapshot = $isSnapshot;
  27 + $this->pnl = $pnl;
  28 + }
  29 +
  30 + public function toArray()
  31 + {
  32 + return [
  33 + 'symbol' => $this->symbol,
  34 + 'posSide' => $this->posSide,
  35 + 'qty' => $this->qty,
  36 + 'lot' => $this->lot,
  37 + 'avgPrice' => $this->avgPrice,
  38 + 'isSnapshot' => $this->isSnapshot,
  39 + 'pnl' => $this->pnl,
  40 + ];
  41 + }
  42 +
  43 + public static function TransferOkxPos(array $data, array $symbolInfos, callable $toSymbolSt): WsDataPos|null
  44 + {
  45 + $symbol = call_user_func($toSymbolSt, $data['instId']);
  46 + /** @var SymbolInfo $symbolInfo */
  47 + $symbolInfo = $symbolInfos[$symbol] ?? null;
  48 + if ($symbolInfo === null) {
  49 + return null;
  50 + }
  51 + $posSide = strtoupper($data['posSide']);
  52 + $lot = (float)$data['pos'];
  53 + $qty = round($lot * $symbolInfo->ctVal, $symbolInfo->qtyPrec);
  54 + $avgPrice = (float)$data['avgPx'];
  55 + $pnl = (float)$data['upl'];
  56 + return new WsDataPos($symbol, $posSide, $qty, $lot, $avgPrice, false, $pnl);
  57 + }
  58 +}
  1 +<?php
  2 +
  3 +namespace trader\struct;
  4 +
  5 +require_once __DIR__ . '/../autoload.php';
  6 +
  7 +use trader\struct\SymbolInfo;
  8 +
  9 +class WsDataTrade
  10 +{
  11 + public string $platform;
  12 + public string $posSide; //大写
  13 + public string $symbol; //大写
  14 + public string $side; //大写
  15 + public float $price; //成交价格
  16 + public float $qty; //币种真实数量
  17 + public float $lot; //合约张数
  18 + public float $pnl; //盈亏
  19 + public float $quoteVol; //计价货币数量 成交额
  20 + public int $ts; //时间戳 ms
  21 + public string $tradeId;
  22 +
  23 + public function __construct($platform, $posSide, $symbol, $side, $price, $qty, $lot, $pnl, $quoteVol, $ts, $tradeId)
  24 + {
  25 + $this->platform = $platform;
  26 + $this->posSide = $posSide;
  27 + $this->symbol = $symbol;
  28 + $this->side = $side;
  29 + $this->price = $price;
  30 + $this->qty = $qty;
  31 + $this->lot = $lot;
  32 + $this->pnl = $pnl;
  33 + $this->quoteVol = $quoteVol;
  34 + $this->ts = $ts;
  35 + $this->tradeId = $tradeId;
  36 + }
  37 +
  38 + public function toArray()
  39 + {
  40 + return [
  41 + 'platform' => $this->platform,
  42 + 'posSide' => $this->posSide,
  43 + 'symbol' => $this->symbol,
  44 + 'side' => $this->side,
  45 + 'price' => $this->price,
  46 + 'qty' => $this->qty,
  47 + 'lot' => $this->lot,
  48 + 'pnl' => $this->pnl,
  49 + 'quoteVol' => $this->quoteVol,
  50 + 'ts' => $this->ts,
  51 + 'tradeId' => $this->tradeId,
  52 + ];
  53 + }
  54 +
  55 + public static function TransferOkxOrder(array $data, array $symbolInfos, callable $toSymbolSt): WsDataTrade|null
  56 + {
  57 + if ($data['fillSz'] != "" && $data['fillSz'] != "0") {
  58 + $symbol = call_user_func($toSymbolSt, $data['instId']);
  59 + /** @var SymbolInfo $symbolInfo */
  60 + $symbolInfo = $symbolInfos[$symbol] ?? null;
  61 + if ($symbolInfo == null) {
  62 + return null;
  63 + }
  64 + $platform = 'okx';
  65 + $posSide = strtoupper($data['posSide']);
  66 + $side = strtoupper($data['side']);
  67 + $price = (float)$data['fillPx'];
  68 + $lot = (float)$data['fillSz'];
  69 + $qty = round($lot * $symbolInfo->ctVal, $symbolInfo->qtyPrec);
  70 + $pnl = (float)$data['fillPnl'];
  71 + $quoteVol = $price * $qty;
  72 + $ts = $data['uTime'];
  73 + $tradeId = $data['tradeId'];
  74 + return new WsDataTrade($platform, $posSide, $symbol, $side, $price, $qty, $lot, $pnl, $quoteVol, $ts, $tradeId);
  75 + }
  76 + return null;
  77 + }
  78 +}