npm package: @senal-group/senal-sdk
Description: 本文档覆盖 SDK 的所有功能模块，包含完整的代码示例与说明。
Latest version: 0.0.1-beta.8

# Prediction Market MM SDK — Demo

本文档覆盖 SDK 的所有功能模块，包含完整的代码示例与说明。

---

## 安装

```bash
npm install @senal-group/senal-sdk
```

---

## 初始化客户端

`ApiClient` 继承自 `BaseClient`，同时提供 REST API 和 WebSocket 能力。  
API 凭证（`apiKey` / `apiSecret` / `passphrase`）由平台统一分配，开始前请确认已获取。

```ts
import { ApiClient } from '@senal-group/senal-sdk';

const client = new ApiClient({
  baseUrl:    'https://api.example.com',
  wsUrl:      'wss://ws.example.com',
  apiKey:     'YOUR_API_KEY',
  apiSecret:  'YOUR_API_SECRET',
  passphrase: 'YOUR_PASSPHRASE',
  timeoutMs:  5000,
  l1Address:  '0xYourWalletAddress',  // 可选
});
```

---

## 模块一：账户 `mm.account`

### 查询余额

```ts
const balance = await client.mm.account.getBalance({});
console.log(`可用: ${balance.available}  锁定: ${balance.locked}`);
```

### 查询所有持仓

返回账户当前持有的所有预测市场头寸，包含盈亏、当前价格等信息。

```ts
const position = await client.mm.account.getPositions({});
console.log(`
  市场:      ${position.marketTitle}
  结果:      ${position.outcomeName}
  持仓量:    ${position.size}
  均价:      ${position.avgPrice}
  当前价:    ${position.curPrice}
  未实现PnL: ${position.cashPnl} (${position.percentPnl}%)
  已实现PnL: ${position.percentRealizedPnl}%
  可赎回:    ${position.redeemable}
`);
```

### 查询持仓总价值

```ts
const { totalValue } = await client.mm.account.getPositionsTotalValue({});
console.log(`持仓总价值: ${totalValue}`);
```

### 查询单个持仓（by tokenId）

```ts
const position = await client.mm.account.getPosition({
  tokenId: 'token-xxx',
});
console.log(`持仓量: ${position.size}  均价: ${position.avgPrice}  已实现PnL: ${position.realizedPnl}`);
```

---

## 模块二：市场 `mm.markets`

### 获取市场列表

支持按状态、是否接受挂单过滤，使用游标分页。

```ts
const result = await client.mm.markets.getMarkets({
  status:          'active',
  acceptingOrders: true,
  limit:           20,
});

console.log(`共 ${result.count} 个市场`);

for (const market of result.data) {
  console.log(`[${market.id}] ${market.question}`);
  console.log(`  状态: ${market.status}  最小下单量: ${market.minimumOrderSize}  tick: ${market.tickSize}`);
  for (const outcome of market.outcomes) {
    console.log(`  - ${outcome.name}  tokenId=${outcome.tokenId}  price=${outcome.price}`);
  }
}

// 翻页
if (result.next_cursor) {
  const nextPage = await client.mm.markets.getMarkets({
    cursor: result.next_cursor,
    limit:  20,
  });
}
```

### 获取单个市场详情

```ts
const market = await client.mm.markets.getMarketById({
  id: 'market-id-xxx',
});
console.log(market.question, market.outcomes);
```

---

## 模块三：行情 `mm.market`

### 查询订单簿

返回指定 token 的当前买卖盘深度。

```ts
const orderbook = await client.mm.market.getOrderbook({
  marketId: 'market-id-xxx',
  tokenId:  'token-xxx',
});

console.log('卖单 (asks):');
orderbook.asks.forEach(a => console.log(`  ${a.price} x ${a.size}`));

console.log('买单 (bids):');
orderbook.bids.forEach(b => console.log(`  ${b.price} x ${b.size}`));
```

### 查询最新成交价

返回指定市场下所有 outcome 的最新成交价，key 为 tokenId。

```ts
const prices = await client.mm.market.getLastTradePrices({
  marketId: 'market-id-xxx',
});
// { 'token-yes': '0.62', 'token-no': '0.38' }
```

### 查询价格历史

```ts
const history = await client.mm.market.getPriceHistory({
  outcomeId: 'token-xxx',
  interval:  '1h',   // 时间粒度
  fidelity:  100,    // 返回数据点数量上限
});

history.forEach(point => {
  console.log(`${point.timestamp}  price=${point.price}`);
});
```

---

## 模块四：订单 `mm.order`

### 单笔下单

```ts
const order = await client.mm.order.placeOrder({
  marketId:  'market-id-xxx',
  tokenId:   'token-xxx',
  side:      'BUY',   // 'BUY' | 'SELL'
  orderType: 'GTC',   // 'GTC' | 'FOK' | 'GTD' | 'FAK'
  price:     0.47,
  size:      100,
  postOnly:  true,    // 可选，仅挂单不吃单
});
console.log(`订单ID: ${order.id}  状态: ${order.status}`);
```

**orderType 说明：**

| 类型  | 含义 |
|-------|------|
| `GTC` | Good Till Cancelled，挂单直到手动撤销 |
| `FOK` | Fill Or Kill，立即全部成交否则撤销 |
| `GTD` | Good Till Date，到期自动撤销，需传 `expiration` |
| `FAK` | Fill And Kill，立即成交可成交部分，剩余撤销 |

### 批量下单

单次最多提交 **15** 个订单。

```ts
const results = await client.mm.order.batchPlaceOrders({
  orders: [
    { marketId, tokenId, side: 'BUY',  orderType: 'GTC', price: 0.44, size: 50 },
    { marketId, tokenId, side: 'BUY',  orderType: 'GTC', price: 0.41, size: 40 },
    { marketId, tokenId, side: 'SELL', orderType: 'GTC', price: 0.56, size: 50 },
    { marketId, tokenId, side: 'SELL', orderType: 'GTC', price: 0.59, size: 40 },
  ],
});

results.forEach(r => {
  console.log(`订单 ${r.id}  状态: ${r.status}  成交笔数: ${r.trades.length}`);
});
```

### 查询订单列表

```ts
const { items, total, page, pageSize } = await client.mm.order.getOrders({
  marketId,
  status:   'open',  // 'open' | 'filled' | 'cancelled' | 'expired'
  page:     1,
  pageSize: 20,
});

console.log(`共 ${total} 笔，当前第 ${page} 页`);
items.forEach(o => {
  console.log(`[${o.id}] ${o.side} ${o.size}@${o.price}  已成交: ${o.filledSize}  剩余: ${o.remainingSize}`);
});
```

### 查询单笔订单

```ts
const order = await client.mm.order.getOrder({
  id: 'order-id-xxx',
});
console.log(order.status, order.filledSize);
```

### 撤销单笔订单

```ts
const result = await client.mm.order.cancelOrder({
  orderId: 'order-id-xxx',
});
console.log(result.results[0].success);
```

### 批量撤单（by orderIds）

```ts
const result = await client.mm.order.batchCancelOrders({
  orderIds: ['order-id-1', 'order-id-2', 'order-id-3'],
});
result.results.forEach(r => {
  console.log(`${r.orderId}  成功: ${r.success}  ${r.reason ?? ''}`);
});
```

### 撤销某市场全部订单（by tokenId）

```ts
const result = await client.mm.order.cancelMarketOrders({
  tokenId: 'token-xxx',
});
console.log(`撤单 ${result.results.length} 笔`);
```

### 撤销所有订单

```ts
const result = await client.mm.order.cancelAllOrders({});
console.log(`撤单 ${result.results.length} 笔`);
```

---

## 模块五：成交记录 `mm.trade`

查询作为 Maker 的历史成交，支持游标分页。

```ts
const result = await client.mm.trade.getMakerTrades({
  marketId,
  tokenId,
  limit: 20,
});

console.log(`共 ${result.count} 笔成交`);
result.data.forEach(t => {
  console.log(`[${t.id}] ${t.side} ${t.size}@${t.price}  链上状态: ${t.status}`);
});

// 翻页
if (result.next_cursor) {
  const next = await client.mm.trade.getMakerTrades({
    cursor: result.next_cursor,
    limit:  20,
  });
}
```

**链上状态（ChainStatus）：**

| 状态        | 含义           |
|-------------|----------------|
| `pending`   | 等待上链       |
| `submitted` | 已提交至链上   |
| `confirmed` | 链上已确认     |
| `failed`    | 链上执行失败   |

---

## 模块六：WebSocket 实时推送

SDK 内置 WebSocket 客户端，支持订单、持仓、行情的实时推送。

### 连接与认证

```ts
await client.connect();
await client.authenticate();
```

### 内置频道常量

| 常量                                 | 频道名      | 说明               |
|--------------------------------------|-------------|--------------------|
| `ApiClient.CHANNAL_ORDERS`           | `orders`    | 私有：订单更新     |
| `ApiClient.CHANNAL_POSITIONS`        | `positions` | 私有：持仓更新     |
| `ApiClient.CHANNAL_TRADE_PREFIX`     | `trade`     | 公共：成交推送前缀 |
| `ApiClient.CHANNAL_ORDERBOOK_PREFIX` | `orderbook` | 公共：盘口推送前缀 |

### 构建行情频道名

公共行情频道需通过 `buildChannel` 拼接 tokenId 生成完整频道名。

```ts
const tradeChannel     = client.buildChannel('trade',     tokenId);
const orderbookChannel = client.buildChannel('orderbook', tokenId);
// e.g. "trade:token-xxx" / "orderbook:token-xxx"
```

### 订阅 / 取消订阅

```ts
// 订阅
client.subscribe([
  ApiClient.CHANNAL_ORDERS,
  ApiClient.CHANNAL_POSITIONS,
  tradeChannel,
  orderbookChannel,
]);

// 取消订阅
client.unsubscribe([tradeChannel, orderbookChannel]);
```

### 监听消息

```ts
// 所有消息（调试用）
client.onMessage(msg => {
  console.log('raw:', msg);
});

// 订阅成功回调
client.onSubscribed(msg => {
  console.log('已订阅:', msg.channel);
});

// 取消订阅回调
client.onUnsubscribed(msg => {
  console.log('已取消:', msg.channel);
});

// 指定频道的数据更新
client.onUpdate(ApiClient.CHANNAL_ORDERS, msg => {
  console.log('订单更新:', msg.data);
});

client.onUpdate(ApiClient.CHANNAL_POSITIONS, msg => {
  console.log('持仓更新:', msg.data);
});

client.onUpdate(orderbookChannel, msg => {
  console.log('Orderbook 更新:', msg.data);
});

client.onUpdate(tradeChannel, msg => {
  console.log('成交更新:', msg.data);
});
```

### 等待单次消息（Promise 风格）

适合启动时等待初始快照，或做简单的请求-响应交互。

```ts
const msg = await client.waitOnce(
  msg => msg.channel === orderbookChannel,
  10_000, // 超时 ms，超时后 reject
);
console.log('首条 orderbook 快照:', msg.data);
```

### 关闭连接

```ts
client.close();
```

---

## 附录：订单状态（ExchangeOrderStatus）

| 状态               | 含义     |
|--------------------|----------|
| `pending`          | 待处理   |
| `open`             | 挂单中   |
| `partially_filled` | 部分成交 |
| `filled`           | 完全成交 |
| `cancelled`        | 已撤销   |
| `rejected`         | 已拒绝   |