# 更新模型与生命周期

## 一个实例，一条更新循环

`ExApi(token, account=None)` 提供同步 API。所有调用必须在创建实例的线程中执行，不能在运行中的 asyncio 事件循环里直接调用。SDK 的内部 I/O 由同步调用推进，长时间 CPU 计算或阻塞业务代码会延迟更新和故障检查。

```python
with ExApi(token) as api:
    quote = api.watch_quote(symbol)
    bars = api.watch_kline(symbol, "1m")
    while api.wait_update():
        # 在同一次提交上检查多个观察对象。
        if quote.is_changing("last_price"):
            consume_quote(quote.data())
        if bars.is_changing():
            consume_bars(bars.data())
```

示例中的 `consume_quote`、`consume_bars` 是你的业务函数，SDK 不提供这些方法。需要多策略时自行设计调度，不要跨线程共享同一个实例，也不要给每个句柄各写一层无限循环。

## wait_update 的语义

`wait_update(deadline=None) -> bool` 等待并发布下一批用户可见更新；到达调用方指定的绝对期限且没有更新时返回 `False`。无期限时可能持续等待。SDK 错误通过异常报告，不用 `False` 表示致命错误。

它也负责推进连接恢复、CTP 同步及交易回报。创建观察句柄不代表已有可交易的、最新的数据。

## data 返回独立快照

- `Quote`、资金、持仓、订单、成交均通过快照读取；保存的快照不随未来 `wait_update()` 改变。
- K 线返回独立 pandas DataFrame，带有 `attrs["symbol"]` 与 `attrs["period"]`。在快照上增加指标列不会保留到下一次 `data()`。
- 持仓、订单、成交返回只读 Mapping，即使指定了单个合约，也不是单个对象。
- 断线时仍保留最后一次快照。数据存在不等于有效；交易先检查 `trading_status()`，行情先检查 `timestamp`。

## is_changing 检查最近一次提交

`is_changing(key=None)`：不传字段检查对象变化；字符串检查单字段；字符串列表检查其中任意字段变化。K 线使用列名，例如 `"timestamp"`、`"close"`。集合句柄可按对象字段检查，成员增减也属于集合变化。

```python
if quote.is_changing(["last_price", "volume"]):
    current = quote.data()
    print(current.last_price, current.volume)
```

同一合约多次订阅行情返回同一句柄。K 线按 `(symbol, period, data_length)` 区分句柄；不同长度是不同窗口。仅使用观察句柄的方法，不访问 `_quotes`、`_serials` 等私有结构。

## 清理资源

优先使用 `with ExApi(...) as api:`。也可以在 `try/finally` 中调用 `api.close()`。关闭释放资源，**不会撤销已提交委托**。如有未结束订单，退出前依据业务规则明确处理，而不是认为关闭连接等于撤单。

创建实例后跨线程、关闭后继续使用，或在 asyncio 事件循环里调用同步 API，属于使用方式错误，可能直接抛出 `RuntimeError`。使用独立线程运行同步程序时，必须在那个线程里创建、使用并关闭整个实例。
