mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 17:28:08 +00:00
feat: enhance WebSocket documentation and improve LighterGateway position handling for better account management
This commit is contained in:
+252
-157
@@ -1,18 +1,16 @@
|
|||||||
WebSocket
|
# WebSocket
|
||||||
This page will help you get started with zkLighter WebSocket server.
|
URL: `wss://mainnet.zklighter.elliot.ai/stream`
|
||||||
|
|
||||||
Connection
|
|
||||||
URL: wss://mainnet.zklighter.elliot.ai/stream
|
|
||||||
|
|
||||||
You can directly connect to the WebSocket server using wscat:
|
You can directly connect to the WebSocket server using wscat:
|
||||||
|
|
||||||
|
```
|
||||||
wscat -c 'wss://mainnet.zklighter.elliot.ai/stream'
|
wscat -c 'wss://mainnet.zklighter.elliot.ai/stream'
|
||||||
Send Tx
|
```
|
||||||
|
|
||||||
|
|
||||||
You can send transactions using the websocket as follows:
|
You can send transactions using the websocket as follows:
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "jsonapi/sendtx",
|
"type": "jsonapi/sendtx",
|
||||||
"data": {
|
"data": {
|
||||||
@@ -20,32 +18,33 @@ JSON
|
|||||||
"tx_info": ...
|
"tx_info": ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
The tx_type options can be found in the SignerClient file, while tx_info can be generated using the sign methods in the SignerClient.
|
```
|
||||||
|
|
||||||
Example: ws_send_tx.py
|
|
||||||
|
|
||||||
Send Batch Tx
|
The _tx\_type_ options can be found in the [SignerClient](https://github.com/elliottech/lighter-python/blob/main/lighter/signer_client.py) file, while _tx\_info_ can be generated using the sign methods in the SignerClient.
|
||||||
|
|
||||||
|
Example: [ws\_send\_tx.py](https://github.com/elliottech/lighter-python/blob/main/examples/ws_send_tx.py)
|
||||||
|
|
||||||
You can send batch transactions to execute up to 50 transactions in a single message.
|
You can send batch transactions to execute up to 50 transactions in a single message.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "jsonapi/sendtxbatch",
|
"type": "jsonapi/sendtxbatch",
|
||||||
"data": {
|
"data": {
|
||||||
"tx_types": [INTEGER],
|
"tx_types": "[INTEGER]",
|
||||||
"tx_infos": [tx_info]
|
"tx_infos": "[tx_info]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
The tx_type options can be found in the SignerClient file, while tx_info can be generated using the sign methods in the SignerClient.
|
```
|
||||||
|
|
||||||
Example: ws_send_batch_tx.py
|
|
||||||
|
|
||||||
Types
|
The _tx\_type_ options can be found in the [SignerClient](https://github.com/elliottech/lighter-python/blob/main/lighter/signer_client.py) file, while _tx\_info_ can be generated using the sign methods in the SignerClient.
|
||||||
|
|
||||||
|
Example: [ws\_send\_batch\_tx.py](https://github.com/elliottech/lighter-python/blob/main/examples/ws_send_batch_tx.py)
|
||||||
|
|
||||||
We first need to define some types that appear often in the JSONs.
|
We first need to define some types that appear often in the JSONs.
|
||||||
|
|
||||||
Transaction JSON
|
```
|
||||||
JSON
|
|
||||||
|
|
||||||
Transaction = {
|
Transaction = {
|
||||||
"hash": STRING,
|
"hash": STRING,
|
||||||
"type": INTEGER,
|
"type": INTEGER,
|
||||||
@@ -63,10 +62,12 @@ Transaction = {
|
|||||||
"sequence_index": INTEGER,
|
"sequence_index": INTEGER,
|
||||||
"parent_hash": STRING
|
"parent_hash": STRING
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"hash": "0xabc123456789def",
|
"hash": "0xabc123456789def",
|
||||||
"type": 15,
|
"type": 15,
|
||||||
@@ -84,11 +85,12 @@ JSON
|
|||||||
"sequence_index": 5678,
|
"sequence_index": 5678,
|
||||||
"parent_hash": "0xparenthash123456"
|
"parent_hash": "0xparenthash123456"
|
||||||
}
|
}
|
||||||
Used in: Transaction, Executed Transaction, Account Tx.
|
```
|
||||||
|
|
||||||
Order JSON
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
Used in: [Transaction](https://apibetadocs.lighter.xyz/docs/websocket-reference#transaction), [Executed Transaction](https://apibetadocs.lighter.xyz/docs/websocket-reference#executed-transaction), [Account Tx](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-tx).
|
||||||
|
|
||||||
|
```
|
||||||
Order = {
|
Order = {
|
||||||
"order_index": INTEGER,
|
"order_index": INTEGER,
|
||||||
"client_order_index": INTEGER,
|
"client_order_index": INTEGER,
|
||||||
@@ -122,11 +124,12 @@ Order = {
|
|||||||
"block_height": INTEGER,
|
"block_height": INTEGER,
|
||||||
"timestamp": INTEGER,
|
"timestamp": INTEGER,
|
||||||
}
|
}
|
||||||
Used in: Account Market, Account All Orders, Account Orders.
|
```
|
||||||
|
|
||||||
Trade JSON
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
Used in: [Account Market](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-market), [Account All Orders](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-all-orders), [Account Orders](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-orders).
|
||||||
|
|
||||||
|
```
|
||||||
Trade = {
|
Trade = {
|
||||||
"trade_id": INTEGER,
|
"trade_id": INTEGER,
|
||||||
"tx_hash": STRING,
|
"tx_hash": STRING,
|
||||||
@@ -153,10 +156,12 @@ Trade = {
|
|||||||
"maker_initial_margin_fraction_before": INTEGER (omitted when zero),
|
"maker_initial_margin_fraction_before": INTEGER (omitted when zero),
|
||||||
"maker_position_sign_changed": BOOL (omitted when false),
|
"maker_position_sign_changed": BOOL (omitted when false),
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"trade_id": 401,
|
"trade_id": 401,
|
||||||
"tx_hash": "0xabc123456789",
|
"tx_hash": "0xabc123456789",
|
||||||
@@ -179,11 +184,12 @@ JSON
|
|||||||
"maker_entry_quote_before":"3075.396750",
|
"maker_entry_quote_before":"3075.396750",
|
||||||
"maker_initial_margin_fraction_before":400
|
"maker_initial_margin_fraction_before":400
|
||||||
}
|
}
|
||||||
Used in: Trade, Account All, Account Market, Account All Trades.
|
```
|
||||||
|
|
||||||
Position JSON
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
Used in: [Trade](https://apibetadocs.lighter.xyz/docs/websocket-reference#trade), [Account All](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-all), [Account Market](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-market), [Account All Trades](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-all-trades).
|
||||||
|
|
||||||
|
```
|
||||||
Position = {
|
Position = {
|
||||||
"market_id": INTEGER,
|
"market_id": INTEGER,
|
||||||
"symbol": STRING,
|
"symbol": STRING,
|
||||||
@@ -202,10 +208,12 @@ Position = {
|
|||||||
"margin_mode": INT,
|
"margin_mode": INT,
|
||||||
"allocated_margin": STRING,
|
"allocated_margin": STRING,
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"market_id": 101,
|
"market_id": 101,
|
||||||
"symbol": "BTC-USD",
|
"symbol": "BTC-USD",
|
||||||
@@ -224,49 +232,56 @@ JSON
|
|||||||
"margin_mode": 1,
|
"margin_mode": 1,
|
||||||
"allocated_margin": "46342",
|
"allocated_margin": "46342",
|
||||||
}
|
}
|
||||||
Used in: Account All, Account Market, Account All Positions.
|
```
|
||||||
|
|
||||||
PoolShares JSON
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
Used in: [Account All](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-all), [Account Market](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-market), [Account All Positions](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-all-positions).
|
||||||
|
|
||||||
|
```
|
||||||
PoolShares = {
|
PoolShares = {
|
||||||
"public_pool_index": INTEGER,
|
"public_pool_index": INTEGER,
|
||||||
"shares_amount": INTEGER,
|
"shares_amount": INTEGER,
|
||||||
"entry_usdc": STRING
|
"entry_usdc": STRING
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"public_pool_index": 1,
|
"public_pool_index": 1,
|
||||||
"shares_amount": 100,
|
"shares_amount": 100,
|
||||||
"entry_usdc": "1000.00"
|
"entry_usdc": "1000.00"
|
||||||
}
|
}
|
||||||
Used in: Account All, Account All Positions.
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Used in: [Account All](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-all), [Account All Positions](https://apibetadocs.lighter.xyz/docs/websocket-reference#account-all-positions).
|
||||||
|
|
||||||
Channels
|
|
||||||
Order Book
|
|
||||||
The order book channel sends the new ask and bid orders for the given market.
|
The order book channel sends the new ask and bid orders for the given market.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "order_book/{MARKET_INDEX}"
|
"channel": "order_book/{MARKET_INDEX}"
|
||||||
}
|
}
|
||||||
Example Subscription
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Subscription**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "order_book/0"
|
"channel": "order_book/0"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "order_book:{MARKET_INDEX}",
|
"channel": "order_book:{MARKET_INDEX}",
|
||||||
"offset": INTEGER,
|
"offset": INTEGER,
|
||||||
@@ -288,10 +303,12 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/order_book"
|
"type": "update/order_book"
|
||||||
}
|
}
|
||||||
Example Response
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Response**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "order_book:0",
|
"channel": "order_book:0",
|
||||||
"offset": 41692864,
|
"offset": 41692864,
|
||||||
@@ -313,35 +330,42 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/order_book"
|
"type": "update/order_book"
|
||||||
}
|
}
|
||||||
Market Stats
|
```
|
||||||
|
|
||||||
|
|
||||||
The market stats channel sends the market stat data for the given market.
|
The market stats channel sends the market stat data for the given market.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "market_stats/{MARKET_INDEX}"
|
"channel": "market_stats/{MARKET_INDEX}"
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "market_stats/all"
|
"channel": "market_stats/all"
|
||||||
}
|
}
|
||||||
Example Subscription
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Subscription**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "market_stats/0"
|
"channel": "market_stats/0"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "market_stats:{MARKET_INDEX}",
|
"channel": "market_stats:{MARKET_INDEX}",
|
||||||
"market_stats": {
|
"market_stats": {
|
||||||
@@ -361,10 +385,12 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/market_stats"
|
"type": "update/market_stats"
|
||||||
}
|
}
|
||||||
Example Response
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Response**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "market_stats:0",
|
"channel": "market_stats:0",
|
||||||
"market_stats": {
|
"market_stats": {
|
||||||
@@ -384,37 +410,44 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/market_stats"
|
"type": "update/market_stats"
|
||||||
}
|
}
|
||||||
Trade
|
```
|
||||||
|
|
||||||
|
|
||||||
The trade channel sends the new trade data for the given market.
|
The trade channel sends the new trade data for the given market.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "trade/{MARKET_INDEX}"
|
"channel": "trade/{MARKET_INDEX}"
|
||||||
}
|
}
|
||||||
Example Subscription
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Subscription**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "trade/0"
|
"channel": "trade/0"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "trade:{MARKET_INDEX}",
|
"channel": "trade:{MARKET_INDEX}",
|
||||||
"trades": [Trade]
|
"trades": [Trade]
|
||||||
],
|
],
|
||||||
"type": "update/trade"
|
"type": "update/trade"
|
||||||
}
|
}
|
||||||
Example Response
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Response**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "trade:0",
|
"channel": "trade:0",
|
||||||
"trades": [
|
"trades": [
|
||||||
@@ -437,27 +470,32 @@ JSON
|
|||||||
],
|
],
|
||||||
"type": "update/trade"
|
"type": "update/trade"
|
||||||
}
|
}
|
||||||
Account All
|
```
|
||||||
|
|
||||||
|
|
||||||
The account all channel sends specific account market data for all markets.
|
The account all channel sends specific account market data for all markets.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_all/{ACCOUNT_ID}"
|
"channel": "account_all/{ACCOUNT_ID}"
|
||||||
}
|
}
|
||||||
Example Subscription
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Subscription**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_all/1"
|
"channel": "account_all/1"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"account": INTEGER,
|
"account": INTEGER,
|
||||||
"channel": "account_all:{ACCOUNT_ID}",
|
"channel": "account_all:{ACCOUNT_ID}",
|
||||||
@@ -491,10 +529,12 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/account_all"
|
"type": "update/account_all"
|
||||||
}
|
}
|
||||||
Example Response
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Response**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"account": 10,
|
"account": 10,
|
||||||
"channel": "account_all:10",
|
"channel": "account_all:10",
|
||||||
@@ -574,29 +614,34 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/account"
|
"type": "update/account"
|
||||||
}
|
}
|
||||||
Account Market
|
```
|
||||||
|
|
||||||
|
|
||||||
The account market channel sends specific account market data for a market.
|
The account market channel sends specific account market data for a market.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_market/{MARKET_ID}/{ACCOUNT_ID}",
|
"channel": "account_market/{MARKET_ID}/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Example Subscription
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Subscription**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_market/0/40",
|
"channel": "account_market/0/40",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"account": INTEGER,
|
"account": INTEGER,
|
||||||
"channel": "account_all/{MARKET_ID}/{ACCOUNT_ID}",
|
"channel": "account_all/{MARKET_ID}/{ACCOUNT_ID}",
|
||||||
@@ -614,27 +659,32 @@ JSON
|
|||||||
"trades": [Trade],
|
"trades": [Trade],
|
||||||
"type": "update/account_market"
|
"type": "update/account_market"
|
||||||
}
|
}
|
||||||
Account Stats
|
```
|
||||||
|
|
||||||
|
|
||||||
The account stats channel sends account stats data for the specific account.
|
The account stats channel sends account stats data for the specific account.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "user_stats/{ACCOUNT_ID}"
|
"channel": "user_stats/{ACCOUNT_ID}"
|
||||||
}
|
}
|
||||||
Example Subscription
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Subscription**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "user_stats/0"
|
"channel": "user_stats/0"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "user_stats:{ACCOUNT_ID}",
|
"channel": "user_stats:{ACCOUNT_ID}",
|
||||||
"stats": {
|
"stats": {
|
||||||
@@ -664,10 +714,12 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/user_stats"
|
"type": "update/user_stats"
|
||||||
}
|
}
|
||||||
Example Response
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example Response**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "user_stats:10",
|
"channel": "user_stats:10",
|
||||||
"stats": {
|
"stats": {
|
||||||
@@ -696,57 +748,65 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/user_stats"
|
"type": "update/user_stats"
|
||||||
}
|
}
|
||||||
Transaction
|
```
|
||||||
|
|
||||||
|
|
||||||
The transaction channel sends all new transactions.
|
The transaction channel sends all new transactions.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "transaction"
|
"channel": "transaction"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "transaction",
|
"channel": "transaction",
|
||||||
"txs": [Transaction],
|
"txs": [Transaction],
|
||||||
"type": "update/transaction"
|
"type": "update/transaction"
|
||||||
}
|
}
|
||||||
Executed Transaction
|
```
|
||||||
The structure is the same as with Transaction channel. But this channel sends only executed transactions.
|
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
The structure is the same as with [Transaction](#transaction) channel. But this channel sends only executed transactions.
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "executed_transaction"
|
"channel": "executed_transaction"
|
||||||
}
|
}
|
||||||
Account Tx
|
```
|
||||||
The structure is the same as with Transaction channel. But this channel sends only transactions related to a specific account.
|
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
The structure is the same as with [Transaction](#transaction) channel. But this channel sends only transactions related to a specific account.
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_tx/{ACCOUNT_ID}",
|
"channel": "account_tx/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Account All Orders
|
```
|
||||||
|
|
||||||
|
|
||||||
The account all orders channel sends data about all the orders of an account.
|
The account all orders channel sends data about all the orders of an account.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_all_orders/{ACCOUNT_ID}",
|
"channel": "account_all_orders/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "account_all_orders:{ACCOUNT_ID}",
|
"channel": "account_all_orders:{ACCOUNT_ID}",
|
||||||
"orders": {
|
"orders": {
|
||||||
@@ -754,38 +814,44 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/account_all_orders"
|
"type": "update/account_all_orders"
|
||||||
}
|
}
|
||||||
Height
|
```
|
||||||
|
|
||||||
|
|
||||||
Blockchain height updates
|
Blockchain height updates
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "height",
|
"channel": "height",
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "height",
|
"channel": "height",
|
||||||
"height": INTEGER,
|
"height": INTEGER,
|
||||||
"type": "update/height"
|
"type": "update/height"
|
||||||
}
|
}
|
||||||
Pool data
|
```
|
||||||
|
|
||||||
|
|
||||||
Provides data about pool activities: trades, orders, positions, shares and funding histories.
|
Provides data about pool activities: trades, orders, positions, shares and funding histories.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "pool_data/{ACCOUNT_ID}",
|
"channel": "pool_data/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "pool_data:{ACCOUNT_ID}",
|
"channel": "pool_data:{ACCOUNT_ID}",
|
||||||
"account": INTEGER,
|
"account": INTEGER,
|
||||||
@@ -804,20 +870,23 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "subscribed/pool_data"
|
"type": "subscribed/pool_data"
|
||||||
}
|
}
|
||||||
Pool info
|
```
|
||||||
|
|
||||||
|
|
||||||
Provides information about pools.
|
Provides information about pools.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "pool_info/{ACCOUNT_ID}",
|
"channel": "pool_info/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "pool_info:{ACCOUNT_ID}",
|
"channel": "pool_info:{ACCOUNT_ID}",
|
||||||
"pool_info": {
|
"pool_info": {
|
||||||
@@ -842,20 +911,23 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "subscribed/pool_info"
|
"type": "subscribed/pool_info"
|
||||||
}
|
}
|
||||||
Notification
|
```
|
||||||
|
|
||||||
|
|
||||||
Provides notifications received by an account. Notifications can be of three kinds: liquidation, deleverage, or announcement. Each kind has a different content structure.
|
Provides notifications received by an account. Notifications can be of three kinds: liquidation, deleverage, or announcement. Each kind has a different content structure.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "notification/{ACCOUNT_ID}",
|
"channel": "notification/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "notification:{ACCOUNT_ID}",
|
"channel": "notification:{ACCOUNT_ID}",
|
||||||
"notifs": [
|
"notifs": [
|
||||||
@@ -872,10 +944,12 @@ JSON
|
|||||||
],
|
],
|
||||||
"type": "subscribed/notification"
|
"type": "subscribed/notification"
|
||||||
}
|
}
|
||||||
Liquidation Notification Content
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Liquidation Notification Content**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"id": STRING,
|
"id": STRING,
|
||||||
"is_ask": BOOL,
|
"is_ask": BOOL,
|
||||||
@@ -886,10 +960,12 @@ JSON
|
|||||||
"timestamp": INTEGER,
|
"timestamp": INTEGER,
|
||||||
"avg_price": STRING
|
"avg_price": STRING
|
||||||
}
|
}
|
||||||
Deleverage Notification Content
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Deleverage Notification Content**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"id": STRING,
|
"id": STRING,
|
||||||
"usdc_amount": STRING,
|
"usdc_amount": STRING,
|
||||||
@@ -898,19 +974,23 @@ JSON
|
|||||||
"settlement_price": STRING,
|
"settlement_price": STRING,
|
||||||
"timestamp": INTEGER
|
"timestamp": INTEGER
|
||||||
}
|
}
|
||||||
Announcement Notification Content
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Announcement Notification Content**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"title": STRING,
|
"title": STRING,
|
||||||
"content": STRING,
|
"content": STRING,
|
||||||
"created_at": INTEGER
|
"created_at": INTEGER
|
||||||
}
|
}
|
||||||
Example response
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Example response**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "notification:12345",
|
"channel": "notification:12345",
|
||||||
"notifs": [
|
"notifs": [
|
||||||
@@ -953,20 +1033,23 @@ JSON
|
|||||||
],
|
],
|
||||||
"type": "update/notification"
|
"type": "update/notification"
|
||||||
}
|
}
|
||||||
Account Orders
|
```
|
||||||
|
|
||||||
|
|
||||||
The account all orders channel sends data about the orders of an account on a certain market.
|
The account all orders channel sends data about the orders of an account on a certain market.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_orders/{MARKET_INDEX}/{ACCOUNT_ID}",
|
"channel": "account_orders/{MARKET_INDEX}/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"account": {ACCOUNT_INDEX},
|
"account": {ACCOUNT_INDEX},
|
||||||
"channel": "account_orders:{MARKET_INDEX}",
|
"channel": "account_orders:{MARKET_INDEX}",
|
||||||
@@ -976,20 +1059,23 @@ JSON
|
|||||||
},
|
},
|
||||||
"type": "update/account_orders"
|
"type": "update/account_orders"
|
||||||
}
|
}
|
||||||
Account All Trades
|
```
|
||||||
|
|
||||||
|
|
||||||
The account all trades channel sends data about all the trades of an account.
|
The account all trades channel sends data about all the trades of an account.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_all_trades/{ACCOUNT_ID}",
|
"channel": "account_all_trades/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "account_all_trades:{ACCOUNT_ID}",
|
"channel": "account_all_trades:{ACCOUNT_ID}",
|
||||||
"trades": {
|
"trades": {
|
||||||
@@ -1001,20 +1087,23 @@ JSON
|
|||||||
"daily_volume": FLOAT,
|
"daily_volume": FLOAT,
|
||||||
"type": "update/account_all_trades"
|
"type": "update/account_all_trades"
|
||||||
}
|
}
|
||||||
Account All Positions
|
```
|
||||||
|
|
||||||
|
|
||||||
The account all orders channel sends data about all the order of an account.
|
The account all orders channel sends data about all the order of an account.
|
||||||
|
|
||||||
JSON
|
```
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "subscribe",
|
"type": "subscribe",
|
||||||
"channel": "account_all_positions/{ACCOUNT_ID}",
|
"channel": "account_all_positions/{ACCOUNT_ID}",
|
||||||
"auth": "{AUTH_TOKEN}"
|
"auth": "{AUTH_TOKEN}"
|
||||||
}
|
}
|
||||||
Response Structure
|
```
|
||||||
|
|
||||||
JSON
|
|
||||||
|
|
||||||
|
**Response Structure**
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"channel": "account_all_positions:{ACCOUNT_ID}",
|
"channel": "account_all_positions:{ACCOUNT_ID}",
|
||||||
"positions": {
|
"positions": {
|
||||||
@@ -1023,3 +1112,9 @@ JSON
|
|||||||
"shares": [PoolShares],
|
"shares": [PoolShares],
|
||||||
"type": "update/account_all_positions"
|
"type": "update/account_all_positions"
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Updated 30 days ago
|
||||||
|
|
||||||
|
* * *
|
||||||
@@ -118,6 +118,7 @@ const DEFAULT_TICKER_POLL_MS = 3000;
|
|||||||
const DEFAULT_KLINE_POLL_MS = 15000;
|
const DEFAULT_KLINE_POLL_MS = 15000;
|
||||||
const WS_HEARTBEAT_INTERVAL_MS = 5_000;
|
const WS_HEARTBEAT_INTERVAL_MS = 5_000;
|
||||||
const WS_STALE_TIMEOUT_MS = 20_000;
|
const WS_STALE_TIMEOUT_MS = 20_000;
|
||||||
|
const POSITION_EPSILON = 1e-12;
|
||||||
|
|
||||||
const RESOLUTION_MS: Record<string, number> = {
|
const RESOLUTION_MS: Record<string, number> = {
|
||||||
"1m": 60_000,
|
"1m": 60_000,
|
||||||
@@ -128,6 +129,8 @@ const RESOLUTION_MS: Record<string, number> = {
|
|||||||
"1d": 86_400_000,
|
"1d": 86_400_000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TERMINAL_ORDER_STATUSES = new Set(["filled", "canceled", "cancelled", "expired"]);
|
||||||
|
|
||||||
export interface LighterGatewayOptions {
|
export interface LighterGatewayOptions {
|
||||||
symbol: string; // display symbol used by strategy logging
|
symbol: string; // display symbol used by strategy logging
|
||||||
marketSymbol?: string; // actual Lighter order book symbol (e.g., BTC)
|
marketSymbol?: string; // actual Lighter order book symbol (e.g., BTC)
|
||||||
@@ -418,6 +421,14 @@ export class LighterGateway {
|
|||||||
}
|
}
|
||||||
if (details) {
|
if (details) {
|
||||||
this.accountDetails = details;
|
this.accountDetails = details;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(details, "positions")) {
|
||||||
|
const initialPositions = this.normalizePositions(details.positions);
|
||||||
|
if (initialPositions.length) {
|
||||||
|
this.replacePositions(initialPositions);
|
||||||
|
} else if (this.isEmptyPositionsPayload(details.positions)) {
|
||||||
|
this.positions = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
this.emitAccount();
|
this.emitAccount();
|
||||||
} else {
|
} else {
|
||||||
// Fallback: emit an empty account snapshot so strategies can proceed
|
// Fallback: emit an empty account snapshot so strategies can proceed
|
||||||
@@ -657,77 +668,225 @@ export class LighterGateway {
|
|||||||
|
|
||||||
private handleAccountAll(message: any): void {
|
private handleAccountAll(message: any): void {
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
// account_all may be partial; merge provided markets into existing positions
|
|
||||||
if (Object.prototype.hasOwnProperty.call(message, "positions")) {
|
if (Object.prototype.hasOwnProperty.call(message, "positions")) {
|
||||||
const positionsObject = message.positions ?? {};
|
const positionsObject = message.positions ?? {};
|
||||||
const incoming: LighterPosition[] = (Array.isArray(positionsObject)
|
const incoming = this.normalizePositions(positionsObject);
|
||||||
? (positionsObject as LighterPosition[])
|
if (!incoming.length && this.isEmptyPositionsPayload(positionsObject)) {
|
||||||
: (Object.values(positionsObject) as LighterPosition[])) as LighterPosition[];
|
this.positions = [];
|
||||||
|
} else if (incoming.length) {
|
||||||
const byMarket = new Map<number, LighterPosition>();
|
this.mergePositions(incoming);
|
||||||
for (const p of this.positions ?? []) {
|
|
||||||
const mid = Number(p.market_id);
|
|
||||||
if (Number.isFinite(mid)) byMarket.set(mid, p);
|
|
||||||
}
|
}
|
||||||
for (const p of incoming) {
|
|
||||||
const mid = Number(p.market_id);
|
|
||||||
if (!Number.isFinite(mid)) continue;
|
|
||||||
const sign = Number(p.sign ?? 0);
|
|
||||||
const size = Number(p.position ?? 0);
|
|
||||||
if (sign === 0 || Math.abs(size) < 1e-12) {
|
|
||||||
byMarket.delete(mid);
|
|
||||||
} else {
|
|
||||||
byMarket.set(mid, p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.positions = Array.from(byMarket.values());
|
|
||||||
}
|
}
|
||||||
this.emitAccount();
|
this.emitAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleAccountMarket(message: any): void {
|
private handleAccountMarket(message: any): void {
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
const type = typeof message.type === "string" ? message.type : "";
|
||||||
const position: LighterPosition | undefined = message.position as LighterPosition | undefined;
|
const position: LighterPosition | undefined = message.position as LighterPosition | undefined;
|
||||||
if (!position || !Number.isFinite(Number(position.market_id))) return;
|
const channelMarketId = this.extractMarketIdFromChannel(message.channel);
|
||||||
const marketId = Number(position.market_id);
|
if (position && Number.isFinite(Number(position.market_id))) {
|
||||||
const sign = Number(position.sign ?? 0);
|
this.mergePositions([position]);
|
||||||
const size = Number(position.position ?? 0);
|
} else if (type === "subscribed/account_market" && channelMarketId != null) {
|
||||||
const shouldRemove = sign === 0 || Math.abs(size) < 1e-12;
|
this.removePositionsForMarkets([channelMarketId]);
|
||||||
if (shouldRemove) {
|
|
||||||
this.positions = (this.positions ?? []).filter((p) => Number(p.market_id) !== marketId);
|
|
||||||
} else {
|
|
||||||
let updated = false;
|
|
||||||
this.positions = (this.positions ?? []).map((p) => {
|
|
||||||
if (Number(p.market_id) === marketId) {
|
|
||||||
updated = true;
|
|
||||||
return position;
|
|
||||||
}
|
}
|
||||||
return p;
|
if (Array.isArray(message.orders) && message.orders.length) {
|
||||||
});
|
const marketId = Number(position?.market_id ?? channelMarketId ?? this.marketId ?? NaN);
|
||||||
if (!updated) this.positions.push(position);
|
this.applyOrderList(message.orders, Number.isFinite(marketId) ? Number(marketId) : null, type === "subscribed/account_market");
|
||||||
|
} else if (type === "subscribed/account_market" && channelMarketId != null) {
|
||||||
|
this.clearOrdersForMarket(channelMarketId);
|
||||||
|
this.emitOrders();
|
||||||
}
|
}
|
||||||
this.emitAccount();
|
this.emitAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleAccountOrders(message: any): void {
|
private handleAccountOrders(message: any): void {
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
const snapshot = message.type === "subscribed/account_all_orders";
|
||||||
const ordersObject = message.orders ?? {};
|
const ordersObject = message.orders ?? {};
|
||||||
const buckets = Object.values(ordersObject) as unknown[];
|
this.applyOrderBuckets(ordersObject, snapshot);
|
||||||
const allOrders: LighterOrder[] = buckets.flatMap((entry) => Array.isArray(entry) ? (entry as LighterOrder[]) : []);
|
}
|
||||||
const terminalStatuses = new Set(["filled", "canceled", "cancelled", "expired"]);
|
|
||||||
for (const order of allOrders) {
|
private normalizePositions(source: unknown): LighterPosition[] {
|
||||||
const key = String(order.order_index ?? order.order_id ?? order.client_order_index ?? "");
|
if (!source) return [];
|
||||||
const status = (order.status ?? "").toLowerCase();
|
if (Array.isArray(source)) {
|
||||||
if (!key) continue;
|
return source.filter((entry): entry is LighterPosition => this.isPosition(entry));
|
||||||
if (terminalStatuses.has(status)) {
|
}
|
||||||
this.orderMap.delete(key);
|
if (isPlainObject(source)) {
|
||||||
|
return Object.values(source).filter((entry): entry is LighterPosition => this.isPosition(entry));
|
||||||
|
}
|
||||||
|
if (this.isPosition(source)) return [source];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private isPosition(value: unknown): value is LighterPosition {
|
||||||
|
return typeof value === "object" && value != null && Number.isFinite(Number((value as LighterPosition).market_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mergePositions(updates: LighterPosition[]): void {
|
||||||
|
if (!updates.length) return;
|
||||||
|
const byMarket = new Map<number, LighterPosition>();
|
||||||
|
for (const existing of this.positions ?? []) {
|
||||||
|
const mid = Number(existing.market_id);
|
||||||
|
if (Number.isFinite(mid)) {
|
||||||
|
byMarket.set(mid, existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const update of updates) {
|
||||||
|
const marketId = Number(update.market_id);
|
||||||
|
if (!Number.isFinite(marketId)) continue;
|
||||||
|
if (this.shouldRemovePosition(update)) {
|
||||||
|
byMarket.delete(marketId);
|
||||||
} else {
|
} else {
|
||||||
this.orderMap.set(key, order);
|
byMarket.set(marketId, update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.positions = Array.from(byMarket.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private replacePositions(positions: LighterPosition[]): void {
|
||||||
|
if (!positions.length) {
|
||||||
|
this.positions = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filtered = this.filterPositions(positions);
|
||||||
|
this.positions = filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterPositions(positions: LighterPosition[]): LighterPosition[] {
|
||||||
|
const byMarket = new Map<number, LighterPosition>();
|
||||||
|
for (const entry of positions) {
|
||||||
|
const marketId = Number(entry.market_id);
|
||||||
|
if (!Number.isFinite(marketId)) continue;
|
||||||
|
if (this.shouldRemovePosition(entry)) {
|
||||||
|
byMarket.delete(marketId);
|
||||||
|
} else {
|
||||||
|
byMarket.set(marketId, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(byMarket.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldRemovePosition(position: LighterPosition): boolean {
|
||||||
|
const size = Number(position.position ?? 0);
|
||||||
|
return !Number.isFinite(size) || Math.abs(size) < POSITION_EPSILON;
|
||||||
|
}
|
||||||
|
|
||||||
|
private removePositionsForMarkets(markets: number[]): void {
|
||||||
|
if (!markets.length) return;
|
||||||
|
const targets = new Set(markets.filter((value) => Number.isFinite(value)).map((value) => Number(value)));
|
||||||
|
if (!targets.size) return;
|
||||||
|
this.positions = (this.positions ?? []).filter((position) => !targets.has(Number(position.market_id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyOrderBuckets(rawOrders: unknown, snapshot: boolean): void {
|
||||||
|
const ordersObject = isPlainObject(rawOrders) ? (rawOrders as Record<string, unknown>) : {};
|
||||||
|
const marketKeys = Object.keys(ordersObject);
|
||||||
|
if (snapshot && marketKeys.length === 0) {
|
||||||
|
this.orderMap.clear();
|
||||||
|
this.orders = [];
|
||||||
|
this.emitOrders();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (snapshot) {
|
||||||
|
this.orderMap.clear();
|
||||||
|
}
|
||||||
|
for (const [market, bucket] of Object.entries(ordersObject)) {
|
||||||
|
const marketId = Number(market);
|
||||||
|
const normalized = this.normalizeOrders(bucket);
|
||||||
|
if (Number.isFinite(marketId)) {
|
||||||
|
this.clearOrdersForMarket(marketId);
|
||||||
|
}
|
||||||
|
if (!normalized.length) continue;
|
||||||
|
for (const order of normalized) {
|
||||||
|
this.applyOrderUpdate(order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.orders = Array.from(this.orderMap.values());
|
this.orders = Array.from(this.orderMap.values());
|
||||||
const mapped = toOrders(this.displaySymbol, this.orders);
|
this.emitOrders();
|
||||||
this.ordersEvent.emit(mapped);
|
}
|
||||||
|
|
||||||
|
private normalizeOrders(source: unknown): LighterOrder[] {
|
||||||
|
if (!source) return [];
|
||||||
|
if (Array.isArray(source)) {
|
||||||
|
return (source as unknown[]).filter((entry): entry is LighterOrder => this.isOrder(entry));
|
||||||
|
}
|
||||||
|
if (isPlainObject(source) && this.isOrder(source)) {
|
||||||
|
return [source];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private isOrder(value: unknown): value is LighterOrder {
|
||||||
|
return typeof value === "object" && value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyOrderList(rawOrders: unknown, marketId: number | null, snapshot: boolean): void {
|
||||||
|
const orders = this.normalizeOrders(rawOrders);
|
||||||
|
if (marketId != null) {
|
||||||
|
this.clearOrdersForMarket(marketId);
|
||||||
|
} else if (snapshot) {
|
||||||
|
this.orderMap.clear();
|
||||||
|
}
|
||||||
|
for (const order of orders) {
|
||||||
|
this.applyOrderUpdate(order);
|
||||||
|
}
|
||||||
|
this.orders = Array.from(this.orderMap.values());
|
||||||
|
this.emitOrders();
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyOrderUpdate(order: LighterOrder): void {
|
||||||
|
const key = String(order.order_index ?? order.order_id ?? order.client_order_index ?? "");
|
||||||
|
if (!key) return;
|
||||||
|
const status = (order.status ?? "").toLowerCase();
|
||||||
|
if (TERMINAL_ORDER_STATUSES.has(status)) {
|
||||||
|
this.orderMap.delete(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.client_order_index != null || order.order_index != null) {
|
||||||
|
for (const [existingKey, existingOrder] of Array.from(this.orderMap.entries())) {
|
||||||
|
if (existingKey === key) continue;
|
||||||
|
const sameOrderIndex =
|
||||||
|
order.order_index != null &&
|
||||||
|
existingOrder.order_index != null &&
|
||||||
|
Number(existingOrder.order_index) === Number(order.order_index);
|
||||||
|
const sameClientIndex =
|
||||||
|
order.client_order_index != null &&
|
||||||
|
existingOrder.client_order_index != null &&
|
||||||
|
Number(existingOrder.client_order_index) === Number(order.client_order_index);
|
||||||
|
if (sameOrderIndex || sameClientIndex) {
|
||||||
|
this.orderMap.delete(existingKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.orderMap.set(key, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearOrdersForMarket(marketId: number): void {
|
||||||
|
const normalized = Number(marketId);
|
||||||
|
if (!Number.isFinite(normalized)) return;
|
||||||
|
for (const [key, existing] of Array.from(this.orderMap.entries())) {
|
||||||
|
if (Number(existing.market_index) === normalized) {
|
||||||
|
this.orderMap.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractMarketIdFromChannel(channel: unknown): number | null {
|
||||||
|
if (typeof channel !== "string") return null;
|
||||||
|
const match = channel.match(/account_market:(\d+)/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
const value = Number(match[1]);
|
||||||
|
return Number.isFinite(value) ? value : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isEmptyPositionsPayload(value: unknown): boolean {
|
||||||
|
if (value == null) return true;
|
||||||
|
if (Array.isArray(value)) return value.length === 0;
|
||||||
|
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitDepth(): void {
|
private emitDepth(): void {
|
||||||
@@ -1050,3 +1209,7 @@ function decimalsToStep(decimals: number): number {
|
|||||||
const step = Number(`1e-${decimals}`);
|
const step = Number(`1e-${decimals}`);
|
||||||
return Number.isFinite(step) ? step : Math.pow(10, -decimals);
|
return Number.isFinite(step) ? step : Math.pow(10, -decimals);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value != null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user