|
...
|
...
|
@@ -11,6 +11,7 @@ use trader\struct\ApiInfo; |
|
|
|
use trader\exchange\bybit\Api as BbApi;
|
|
|
|
use jytools\Websocket;
|
|
|
|
use function jytools\output;
|
|
|
|
use function jytools\getMicrotime;
|
|
|
|
|
|
|
|
class ExBroker
|
|
|
|
{
|
|
...
|
...
|
@@ -21,6 +22,8 @@ class ExBroker |
|
|
|
private BbApi $api;
|
|
|
|
private ?Websocket $wsAcc;
|
|
|
|
private ?Websocket $wsKline;
|
|
|
|
private $timerAccPing = 0;
|
|
|
|
private $timerKlinePing = 0;
|
|
|
|
|
|
|
|
public function __construct(ApiInfo $apiInfo)
|
|
|
|
{
|
|
...
|
...
|
@@ -38,10 +41,31 @@ class ExBroker |
|
|
|
$this->api->setHost($host);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function accListen(callable $onData) {}
|
|
|
|
public function accListen(callable $onData)
|
|
|
|
{
|
|
|
|
if (isset($this->wsAcc)) {
|
|
|
|
$this->wsAcc->close();
|
|
|
|
}
|
|
|
|
$this->wsAcc = new Websocket($this->host . $this->pathPri);
|
|
|
|
$this->wsAcc->connect(
|
|
|
|
function () {
|
|
|
|
$this->wsLogin();
|
|
|
|
$this->wsAccPing();
|
|
|
|
},
|
|
|
|
function ($data) use ($onData) {
|
|
|
|
$this->onWsDataPre($data, $onData);
|
|
|
|
},
|
|
|
|
function () {
|
|
|
|
swoole_timer_clear($this->timerAccPing);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function klineListen($symbol, $interval, callable $onData)
|
|
|
|
{
|
|
|
|
if (isset($this->wsKline)) {
|
|
|
|
$this->wsKline->close();
|
|
|
|
}
|
|
|
|
$this->wsKline = new Websocket($this->host . $this->pathPub);
|
|
|
|
$this->wsKline->connect(
|
|
|
|
function () use ($symbol, $interval) {
|
|
...
|
...
|
@@ -51,15 +75,118 @@ class ExBroker |
|
|
|
"kline.{$interval}.{$symbol}"
|
|
|
|
]
|
|
|
|
]));
|
|
|
|
$this->wsKlinePing();
|
|
|
|
},
|
|
|
|
function ($data) use ($onData) {
|
|
|
|
$data = json_decode($data, true);
|
|
|
|
if (isset($data['op']) && $data['op'] == 'pong') {
|
|
|
|
return; //pong 消息不处理
|
|
|
|
}
|
|
|
|
if (isset($data['data'])) {
|
|
|
|
$onData($data);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function () {
|
|
|
|
swoole_timer_clear($this->timerKlinePing);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function wsKlinePing()
|
|
|
|
{
|
|
|
|
$this->timerKlinePing = swoole_timer_tick(1000 * 20, function () {
|
|
|
|
$pingMsg = [
|
|
|
|
'op' => 'ping',
|
|
|
|
'req_id' => (string)time(),
|
|
|
|
];
|
|
|
|
$this->wsKline->push(json_encode($pingMsg));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private function wsAccPing()
|
|
|
|
{
|
|
|
|
$this->timerAccPing = swoole_timer_tick(1000 * 20, function () {
|
|
|
|
$pingMsg = [
|
|
|
|
'op' => 'ping',
|
|
|
|
'req_id' => (string)time(),
|
|
|
|
];
|
|
|
|
$this->wsAcc->push(json_encode($pingMsg));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private function createWsSign($expires)
|
|
|
|
{
|
|
|
|
$param = "GET/realtime" . $expires;
|
|
|
|
return hash_hmac('sha256', $param, $this->apiInfo->secret);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function wsLogin()
|
|
|
|
{
|
|
|
|
$expires = getMicrotime() + 10 * 1000;
|
|
|
|
$this->wsAcc->push(json_encode([
|
|
|
|
'req_id' => (string)time(),
|
|
|
|
'op' => 'auth',
|
|
|
|
'args' => [
|
|
|
|
$this->apiInfo->key,
|
|
|
|
$expires,
|
|
|
|
$this->createWsSign($expires)
|
|
|
|
]
|
|
|
|
]));
|
|
|
|
}
|
|
|
|
|
|
|
|
private function onWsDataPre($data, callable $onWsData)
|
|
|
|
{
|
|
|
|
$data = json_decode($data, true);
|
|
|
|
if (isset($data['op'])) {
|
|
|
|
if ($data['op'] == 'pong') {
|
|
|
|
return; //pong 消息不处理
|
|
|
|
}
|
|
|
|
if ($data['op'] == 'auth' && $data['success']) {
|
|
|
|
output('ws登录成功');
|
|
|
|
$this->wsSubscribe();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ($data['op'] == 'subscribe') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
output('bybit ws 未处理数据', $data);
|
|
|
|
}
|
|
|
|
call_user_func($onWsData, $data);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function wsSubscribe()
|
|
|
|
{
|
|
|
|
$this->wsAcc->push(json_encode([
|
|
|
|
'req_id' => (string)time(),
|
|
|
|
'op' => 'subscribe',
|
|
|
|
'args' => [
|
|
|
|
// 'order',
|
|
|
|
// 'execution',
|
|
|
|
'position',
|
|
|
|
// 'wallet'
|
|
|
|
]
|
|
|
|
]));
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getKlineChannels()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
'1' => '1',
|
|
|
|
'3' => '3',
|
|
|
|
'5' => '5',
|
|
|
|
'15' => '15',
|
|
|
|
'30' => '30',
|
|
|
|
'60' => '60',
|
|
|
|
'120' => '120',
|
|
|
|
'240' => '240',
|
|
|
|
'360' => '360',
|
|
|
|
'720' => '720',
|
|
|
|
'D' => 'D',
|
|
|
|
'W' => 'W',
|
|
|
|
'M' => 'M'
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getSymbolInfos()
|
|
|
|
{
|
|
|
|
$res = $this->api->instruments(["category" => "linear"]);
|
|
...
|
...
|
@@ -69,4 +196,95 @@ class ExBroker |
|
|
|
}
|
|
|
|
return $res['result']['list'];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function stopListen()
|
|
|
|
{
|
|
|
|
if (isset($this->wsAcc)) {
|
|
|
|
$this->wsAcc->close();
|
|
|
|
}
|
|
|
|
if (isset($this->wsKline)) {
|
|
|
|
$this->wsKline->close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function placeOrder($param)
|
|
|
|
{
|
|
|
|
return $this->api->placeOrder($param);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getPositions($symbol = '')
|
|
|
|
{
|
|
|
|
$param = [
|
|
|
|
'category' => 'linear'
|
|
|
|
];
|
|
|
|
if ($symbol) {
|
|
|
|
$param['symbol'] = $symbol;
|
|
|
|
}
|
|
|
|
return $this->api->getPositions($param);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setLeverage($symbol, $leverage)
|
|
|
|
{
|
|
|
|
$param = [
|
|
|
|
'category' => 'linear',
|
|
|
|
'symbol' => $symbol,
|
|
|
|
'buyLeverage' => $leverage,
|
|
|
|
'sellLeverage' => $leverage
|
|
|
|
];
|
|
|
|
$res = $this->api->setLeverage($param);
|
|
|
|
if ($res['retCode'] == 0) {
|
|
|
|
output($symbol, '设置杠杆为:', $leverage);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
output($symbol, '设置杠杆错误', $res['retMsg']);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getKlines($symbol, $interval, $limit = 200, $start = '', $end = '')
|
|
|
|
{
|
|
|
|
$param = [
|
|
|
|
'category' => 'linear',
|
|
|
|
'symbol' => $symbol,
|
|
|
|
'interval' => $interval,
|
|
|
|
'limit' => $limit
|
|
|
|
];
|
|
|
|
if ($start) {
|
|
|
|
$param['start'] = $start;
|
|
|
|
}
|
|
|
|
if ($end) {
|
|
|
|
$param['end'] = $end;
|
|
|
|
}
|
|
|
|
return $this->api->klines($param);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function closeAllPos()
|
|
|
|
{
|
|
|
|
$res = $this->getPositions();
|
|
|
|
if ($res['retCode'] == 0 && !empty($res['result']['list'])) {
|
|
|
|
foreach ($res['result']['list'] as $pos) {
|
|
|
|
if ($pos['size'] != '0') {
|
|
|
|
$this->closePos($pos['symbol'], $pos['side'], $pos['size']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function closePos($symbol, $side, $size)
|
|
|
|
{
|
|
|
|
$param = [
|
|
|
|
'category' => 'linear',
|
|
|
|
'symbol' => $symbol,
|
|
|
|
'side' => $side == 'Buy' ? 'Sell' : 'Buy',
|
|
|
|
'orderType' => 'Market',
|
|
|
|
'qty' => $size
|
|
|
|
];
|
|
|
|
$res = $this->api->placeOrder($param);
|
|
|
|
if ($res['retCode'] == 0) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
output('平仓失败', $res);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} |
...
|
...
|
|