# 错误处理与恢复

运行故障与交易操作结果分别处理。SDK 使用固定业务提示，不在日志、异常消息或订单错误字段中输出服务地址、原始柜台错误文本、底层异常或堆栈。不会修改应用程序的异常钩子；应用自行打印的 Python traceback 仍可能包含调用位置。

## 交易操作

- 下单、撤单统一返回 `list[TradingResult]`，结果对象为只读，包含 `status`、`order_id`、`message`。
- `SUBMITTED`：已交给交易接口，不表示柜台接受、成交或撤单成功。
- `REJECTED`：本地拒绝或明确未提交。下单不创建订单，`order_id=None`；撤单保留原订单，返回其订单号。本地错误只通过返回值说明，不写入订单。
- `UNKNOWN`：提交结果不明，保留订单号和跟踪状态，等待自然回报，不重发。
- 非 `READY`、参数无效、合约不可交易或可平数量不足：立即返回拒绝。
- 不存在、已结束或已有撤单在处理的订单：撤单静默返回 `[]`。
- 尚未确认或没有订单系统编号：撤单立即返回拒绝，不缓存撤单意图、不在后续回报时补发。

上述操作失败不抛异常、不记录日志、不自动重试，也不单独把交易会话变为 `ERROR`。拆单的各子单分别返回结果；前一子单提交失败或结果不明时，剩余子单返回未发送的拒绝结果。没有配置交易账户时，下单直接返回拒绝，不创建交易会话。

返回值立即可读，不需要调用 `wait_update()`。提交后收到的柜台拒单、撤单失败仍通过 `wait_update()` 更新对应订单；返回对象不会随之改变。`data()` 保持独立快照语义。线程错误、实例已关闭、在异步事件循环中误用同步 API 等调用方式错误仍立即报错。

这是公共接口变更：原先下单返回订单号列表、撤单返回 `None`；调用方现在应先判断结果状态，再使用 `result.order_id` 跟踪已提交或结果不明的订单。

## 会话与查询

普通行情读取请求、运行期间账户查询最多重试 3 次，总时间不超过 120 秒；行情连接、交易连接及同步恢复最多重试 5 次，总时间不超过 180 秒。次数或时间先达到上限即停止恢复。恢复中的行情补数使用连接的恢复额度，不再嵌套一轮 HTTP 重试。

首次可恢复故障记录一条简短日志；恢复完成记录一条恢复日志。正常启动、同步步骤、订单细节不记录日志。明确不可恢复或重试耗尽时抛出 `ExError`，不重复记录一条异常日志。交易会话最终失败先发布 `ERROR`，然后由 `wait_update()` 抛出异常。

恢复额度仅在数据恢复完成后重置。用户等待超时仍返回 `False`，不重置额度。初始化 K 线额外有 180 秒总上限。SDK 的事件循环由同步调用推进；策略长时间不调用 SDK 时，故障检查会在下次推进时执行。关闭、任务取消和用户中断不触发重试。

登录同步仍使用原有订单、成交、持仓及资金一致性检查。此次修改不改变成交去重、今昨仓分配、冻结量计算和行情合并规则。关闭时继续清理其余资源；在已有异常退出 `with` 时，清理失败不覆盖原异常。

## 请求校验

行情请求校验失败时检查 SDK 版本和系统时间。HTTP 401/403 不重试，网络与临时服务故障使用有界恢复。

## 账户验证

SDK 首次使用行情前验证 Token，之后每 30 分钟复核一次。Token 无效或账户停用时停止行情并报错；临时故障最多重试 3 次，总等待不超过 120 秒。验证失败不会主动关闭 CTP 交易连接。


## 应用层异常捕获

```python
import os
from exsdk import ExApi, ExError

try:
    with ExApi(os.environ["EXSDK_TOKEN"]) as api:
        print(api.query_symbols("FUTURE"))
except ExError as error:
    print("SDK 运行故障", str(error), error.status)
except (ValueError, TypeError) as error:
    print("检查本地参数", error)
```

不要捕获异常后无条件重建实例并重发订单。账户验证失败停止行情，不会主动关闭 CTP 连接；应用仍需明确处理存量委托与退出。
