# 易行量化 1.1.2 开发文档 天至科技 · 易行量化。本文为全部公开教程和接口参考。交易由本地 SDK 直连期货公司,网站不接收交易密码。 # 认识易行量化 易行量化(Python 包名 exsdk)是天至科技提供的 Python 期货、期权行情与直连 CTP 交易 SDK。本文档对应 **1.1.2**,面向人工开发者及 AI 编程助手。先学习快速开始与运行模型,再按任务查阅接口参考、数据字典及完整示例。 ## 能力与边界 | 能力 | 期货 | 期权 | | --- | --- | --- | | 合约查询与详情 | 支持 | 支持 | | 实时行情与盘口 | 支持 | 支持 | | 固定窗口 K 线 | 支持 | 暂不支持 | | 按交易日下载历史 K 线 | 支持 | 暂不支持 | | 普通投机限价委托与撤单 | 支持 | 支持 | | 行权、弃权、特殊交易操作 | 不提供 | 不提供 | SDK 不提供云端策略托管、收益保证、回测引擎或托管的交易账户。官网的专业版代表 SDK 认证权益,不代表不同的行情等级。 ## 系统如何连接 1. 本地 Python 程序使用 Token 向账户服务验证专业版或体验权益。 2. SDK 从行情服务查询合约、行情和历史 K 线,并维持行情订阅。 3. 配置 CTP 后,本地 SDK 直接连接期货公司柜台。交易账号、密码、委托不经过官网或行情服务。 4. 策略、指标计算和交易决策留在本地。SDK 不另行持久化订单、成交与持仓;CTP 原生库可能在系统临时目录留下运行所需的流文件。 ## 推荐学习路线 - [安装与环境](./installation.md) → [快速开始](./quickstart.md):成功运行第一个行情程序。 - [更新模型与生命周期](./concepts.md):理解数据何时更新、快照如何使用。 - [合约查询](./symbols.md) → [行情](./quotes.md) → [K 线](./klines.md) → [历史下载](./history.md)。 - 需要交易时学习 [CTP 账户与交易数据](./trading.md)、[下单与撤单](./orders.md)、[错误处理](./errors.md)。 - 编写代码时配合 [完整接口参考](./api-reference.md)、[数据字典](./data-reference.md) 与 [示例下载](./examples.md)。 ## 给 AI 助手的约定 优先使用当前文档里的方法签名,不使用旧版本“下单返回订单号列表”的写法。下单、撤单返回 `list[TradingResult]`;`SUBMITTED` 仅表示提交,不等于接受或成交;`UNKNOWN` 不得自动重发。 使用 `/llms.txt` 获取文档索引,使用 `/llms-full.txt` 获取全部教程与接口参考;单页 Markdown 位于 `/docs/.md`。网页和 Markdown 使用同一份内容。 --- # 安装与环境 ## 支持的平台 - Python **3.12 及以上**,64 位解释器。 - Windows x86_64、Linux x86_64、macOS Apple Silicon arm64。 - SDK 通过平台 wheel 分发,不提供源码安装包;不支持的平台不能依靠下载源码自行编译安装。 - 使用 venv 隔离项目依赖;不要在同一个解释器内混装不同架构的二进制包。 ## 安装与检查 ```bash python --version python -m venv .venv ``` Linux / macOS: ```bash source .venv/bin/activate python -m pip install --upgrade pip python -m pip install exsdk python -c "import exsdk; print(exsdk.__version__)" ``` Windows PowerShell: ```powershell .venv\Scripts\Activate.ps1 python -m pip install --upgrade pip python -m pip install exsdk python -c "import exsdk; print(exsdk.__version__)" ``` 本文以 1.1.2 为基准。需要可复现环境时使用 `python -m pip install exsdk==1.1.2`。若无匹配分发包,请检查 Python 版本、系统架构和可用 wheel。 ## 设置 Token 在官网使用手机号登录。首次登录自动开通账户,享 15 天专业版体验。进入个人中心 → SDK Token 创建令牌,明文只显示一次。不要将令牌提交到 Git 或写入共享 Notebook。 SDK 构造函数接收一个非空 Token 字符串,可以显式传入。将 `YOUR_TOKEN` 替换为刚刚创建的完整令牌: ```python from exsdk import ExApi with ExApi(token="YOUR_TOKEN") as api: print(api.query_symbols("FUTURE")) ``` ### 可选:通过环境变量传入 `EXSDK_TOKEN` 只是本教程约定的环境变量名,不是 Python 或 SDK 内置变量。SDK 不会自动创建或读取它;下面由你手动设置,再通过自己的 Python 代码读取并传给 `ExApi`。运行脚本的进程必须能够继承该变量;在终端设置后,请在同一个终端运行脚本。 Linux / macOS: ```bash export EXSDK_TOKEN='替换为自己的令牌' ``` Windows PowerShell: ```powershell $env:EXSDK_TOKEN = '替换为自己的令牌' ``` Python 显式读取并传入: ```python import os from exsdk import ExApi token = os.environ["EXSDK_TOKEN"] with ExApi(token=token) as api: print(api.query_symbols("FUTURE")) ``` 环境变量不存在时,`os.environ["EXSDK_TOKEN"]` 会抛出 `KeyError`,这是本地配置问题,不是 SDK 服务故障。也可以改用自己的变量名,并让设置和读取的名称保持一致。 ## 网络与认证前提 SDK 源码的账户认证地址已统一为 `https://skyzhi.com`,认证接口为 `/api/token/verify`,行情服务为 `https://api.skyzhi.com`。本地运行 exweb 并不自动部署这些域名;需要先将主域名部署到本系统,并使用认证地址已更新的 SDK 安装包。本次源码修改尚未发布为新安装包,已安装的旧包需要在新包发布后更新。不要通过修改 SDK 私有变量绕过认证。 行情和 CTP 柜台连接均要求对应网络可达。CTP 的交易前置、AppID、AuthCode、账号和密码由期货公司提供,全部配置在本地,勿提交至官网。 --- # 快速开始 前提:已安装 SDK,在个人中心 → SDK Token 创建并保存令牌,认证域名已接入有效账户服务。将下方 `YOUR_TOKEN` 替换为自己的完整令牌再运行;它是占位文本,不是可用令牌。以下代码自动查询主力期货,避免把固定月份的过期合约直接用于程序。 SDK 通过 `ExApi(token=...)` 接收令牌,不会自动创建或读取 `EXSDK_TOKEN`。若希望使用环境变量保存令牌,参见 [安装与环境](./installation.md) 的可选用法。 ## 第一个行情程序 ```python from exsdk import ExApi with ExApi(token="YOUR_TOKEN") as api: symbols = api.query_symbols("FUTURE", exchange_id="SHFE", is_main=True) if not symbols: raise RuntimeError("没有找到合约,请检查交易所或查询条件") symbol = symbols[0] print("订阅", symbol) quote = api.watch_quote(symbol) while api.wait_update(): if quote.is_changing("last_price"): snapshot = quote.data() print(snapshot.timestamp, snapshot.last_price) ``` `watch_quote()` 创建订阅句柄;`wait_update()` 推进并提交数据;`is_changing()` 检查最近一批变化;`data()` 获取不会随之后更新改变的独立快照。非交易时段可能长时间没有变化,不能据此推断连接失败。 ## 同时观察 K 线 在同一个 `with` 内创建所有需要的句柄,再使用一层统一的更新循环: ```python quote = api.watch_quote(symbol) klines = api.watch_kline(symbol, "1m", data_length=200) while api.wait_update(): if quote.is_changing("last_price"): print("最新价", quote.data().last_price) if klines.is_changing("timestamp"): frame = klines.data() if not frame.empty: print("窗口末行", frame.iloc[-1].to_dict()) ``` K 线的最后一行可能还未结束,后续仍会改变。`timestamp` 变化表示时间窗口发生变化,不是“该末行永远不再修订”的承诺。需要严格完成的 K 线时,应结合周期结束时间、当前行情时间以及策略规则判断。 ## 有限等待 ```python import time updated = api.wait_update(deadline=time.time() + 5) if updated: print(quote.data()) else: print("本次等待到期,未提交更新") ``` `deadline` 是 Unix 秒的绝对时间,不能直接传 `5` 表示五秒。一次返回 `False` 不意味着账户断开,也不重置恢复预算。 ## 下一步 阅读 [更新模型](./concepts.md) 与 [接口参考](./api-reference.md),再使用 [完整示例](./examples.md)。只读行情程序无需创建 `CtpAccount`。如果没有配置交易账户,不要调用资金或持仓观察接口。 --- # 更新模型与生命周期 ## 一个实例,一条更新循环 `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`。使用独立线程运行同步程序时,必须在那个线程里创建、使用并关闭整个实例。 --- # 合约查询 ## 查询列表 ```python futures = api.query_symbols("FUTURE") main_futures = api.query_symbols("FUTURE", exchange_id="SHFE", is_main=True) rebar = api.query_symbols("FUTURE", exchange_id="SHFE", product_id="rb") options = api.query_symbols("OPTION", exchange_id="DCE") ``` `ins_class` 必填,只能是 `"FUTURE"` 或 `"OPTION"`。需要两类时分别查询。`exchange_id`、`product_id` 默认 `None`,表示不筛选;传入时必须是非空字符串。`is_main` 支持 `True`、`False`、`None`,不接受数字代替布尔值。期权的 `is_main` 均为 `False`。 返回 `list[str]`,可能为空。订阅前先检查长度,不假定列表顺序具有策略含义。 ## 合约代码 格式为 `交易所.合约`。例如 `SHFE.rb2610`、`DCE.a2611-C-3700` 仅用于说明格式,并不保证该合约在你运行程序时仍然有效。交易所和品种代码大小写按服务返回值保留,不把整段代码统一转成大写或小写。 ## 查询详情 ```python symbols = api.query_symbols("FUTURE", is_main=True) if symbols: info = api.query_symbol_info(symbols[0]) print(info["symbol"], info["ins_class"]) print(info["data"]) ``` 顶层字段:`symbol: str`、`exchange_id: str`、`product_id: str`、`ins_class: str`、`is_main: bool`、`data: dict`。 `data` 是合约详细信息。期权还包含标的、认购/认沽类别、行权价等信息;具体键由数据服务提供,SDK 没有为 `data` 定义固定静态字段集合。程序应检查实际返回值,不假定未在当前服务确认过的键必然存在。 这两个方法都是调用时的一次性查询,不返回观察句柄,无需调用 `.data()` 或 `.is_changing()`,也不会在 `wait_update()` 后原地更新查询结果。 --- # 实时行情 ## 订阅与读取 ```python quote = api.watch_quote(symbol) while api.wait_update(): if quote.is_changing(["last_price", "volume"]): q = quote.data() print(q.symbol, q.last_price, q.volume, q.open_interest) ``` 期货和期权使用同一个方法。字段完整列表见 [Quote 数据字典](./data-reference.md#quote)。无需手动轮询请求行情;由 `wait_update()` 推进订阅。 ## 初始值与缺失值 尚未收到行情时,`timestamp` 为 `0`、`trading_day` 为空字符串,价格及数值字段可能是 `math.nan`,盘口可能为空。不要用 `if q.last_price` 判断价格有效性,应使用 `math.isfinite()`。 ```python import math import time q = quote.data() valid = q.timestamp > 0 and math.isfinite(q.last_price) recent = valid and 0 <= time.time() * 1000 - q.timestamp <= 10_000 if recent: print("有效且在示例 10 秒时效范围内", q.last_price) ``` 这里的 10 秒是示例策略阈值,不是 SDK 承诺。休市期间最新一笔行情本来就可能很旧;阈值应由实际场景决定。 ## 盘口 `bids`、`asks` 是 `(价格, 数量)` 的列表,档位数由实际行情提供,不保证固定五档。读取第一档前检查长度: ```python q = quote.data() if q.bids: bid_price, bid_volume = q.bids[0] print("买一", bid_price, bid_volume) if q.asks: ask_price, ask_volume = q.asks[0] print("卖一", ask_price, ask_volume) ``` ## 时间与断线 行情 `timestamp` 为 Unix 毫秒,`trading_day` 为 `YYYYMMDD` 交易日。交易日可能与自然日不同。不要直接把毫秒当作 Python 时间戳秒。 断线重连保留最后一次快照,重连过程不等于获取到了新的数据。检查字段变化、时间戳及恢复后的可用数据再执行依赖行情的策略逻辑。 --- # K 线与指标 ## 创建固定长度窗口 ```python bars = api.watch_kline(symbol, "1m", data_length=200) ``` 仅支持期货。周期只能是 `"1m"`、`"5m"`、`"10m"`、`"15m"`、`"30m"`、`"1h"`、`"4h"`、`"1d"`。长度为 1–10000 的整数,默认 200;`True` 不算有效长度。 创建时同步初始化历史窗口,初始化可能等待网络;初始化整体上限为 180 秒。相同合约、周期、长度复用同一句柄,不同长度返回不同句柄。期权没有 K 线能力,误传期权可能得到空数据。 ## DataFrame 结构 固定列为 `timestamp`、`trading_day`、`open`、`high`、`low`、`close`、`volume`、`turnover`、`open_interest`。 `timestamp` 表示周期结束的 Unix 毫秒,日线使用交易日北京时间零点。最后一行可能仍在形成。不要将日线时间戳解释为交易所收盘瞬间。 ```python frame = bars.data() print(frame.attrs["symbol"], frame.attrs["period"]) if not frame.empty: print(frame.iloc[-1].to_dict()) ``` ## 计算移动平均 ```python import math bars = api.watch_kline(symbol, "1m", data_length=200) while api.wait_update(): if bars.is_changing(["timestamp", "close"]): frame = bars.data() if len(frame) >= 20: frame["ma20"] = frame["close"].rolling(20).mean() latest = frame["ma20"].iloc[-1] if math.isfinite(latest): print("当前末行 MA20", latest) ``` 指标添加在当前快照,不会进入 SDK 的下一份快照。需要每次重新计算或由业务层维护指标状态。`rolling(20)` 数据不足时产生 NaN,程序需要检查。 ## 监测窗口推进 `bars.is_changing("timestamp")` 可检测时间列变化;`bars.is_changing("close")` 可检测价格变化。同一周期的末行可多次变化。面向已完成 K 线的策略必须额外建立完成判定和去重规则,不能把每次收盘价变化都当成新周期。 --- # 历史 K 线下载 ## 完整调用 ```python import os from pathlib import Path from exsdk import ExApi output = Path("data") output.mkdir(parents=True, exist_ok=True) with ExApi(os.environ["EXSDK_TOKEN"]) as api: rows = api.tools.download_kline_data( symbol=os.environ["EXSDK_SYMBOL"], period="1m", start="20260901", end="20260909", csv_file_name=output / "history.csv", ) print("写入行数", rows) ``` 运行前把 `EXSDK_SYMBOL` 设置为需要下载的期货合约,并选择该合约有数据的日期范围。方法同步完成,不需要额外调用 `wait_update()`。 ## 日期、周期与范围 - `start`、`end` 都是 `YYYYMMDD` 格式的有效交易日字符串,起止日都包含,且 `start <= end`。 - 夜盘归属其交易日,不按自然日切分。 - 周期与 `watch_kline()` 相同;仅支持期货。 - SDK 自动分页。单个交易日超过查询上限时会报错,不静默丢弃数据。 ## CSV 格式 输出按时间升序,相同时间戳只写入一行。列与 K 线 DataFrame 相同,UTF-8 BOM 编码、不含 pandas 索引,缺失数值写为空单元格。没有数据时写出表头并返回 `0`。 ```python import pandas as pd frame = pd.read_csv("data/history.csv", encoding="utf-8-sig") print(frame.head()) ``` ## 文件与并发行为 `csv_file_name` 接受字符串或 `pathlib.Path`;父目录必须已存在。成功覆盖同名文件,失败保留原文件并清理临时文件。目录不可写、空间不足等会以 `ExError` 报告。 下载不会更新已建立的观察句柄,也不会暂停实时流;大量订阅时长时间下载可能积压尚未消费的推送。建议批量历史任务使用独立程序,避免阻塞正在驱动交易的更新循环。当前尚未结束的 K 线可能在下载后继续变化。 --- # CTP 账户与交易数据 交易凭据仅在本地使用。先从期货公司获取交易前置、AppID 和 AuthCode,再创建 `CtpAccount`。SDK 登录后会自动确认当日结算。 ## 创建账户并等待同步 ```python import os import time from exsdk import ExApi, CtpAccount account = CtpAccount( broker_id=os.environ["CTP_BROKER_ID"], user_id=os.environ["CTP_USER_ID"], password=os.environ["CTP_PASSWORD"], front_url=os.environ["CTP_FRONT_URL"], app_id=os.environ["CTP_APP_ID"], auth_code=os.environ["CTP_AUTH_CODE"], ) with ExApi(os.environ["EXSDK_TOKEN"], account) as api: funds = api.watch_account() positions = api.watch_position() orders = api.watch_order() trades = api.watch_trade() deadline = time.time() + 60 while api.trading_status() != "READY": if not api.wait_update(deadline=deadline): raise TimeoutError("CTP 账户同步未在预期时间内完成") print("可用资金", funds.data().available) print("持仓映射", positions.data()) ``` 六个账户字段均必填、非空、不能包含 NUL。前置地址必须以 `tcp://` 开头;不要填入行情前置代替交易前置。 ## 交易状态 | 状态 | 含义与操作 | | --- | --- | | `SYNCING` | 首次登录或重连后同步中,尚不能提交委托 | | `READY` | 登录及一致性同步完成,可提交委托,但不保证资金或行情是即时值 | | `DISCONNECTED` | CTP 连接中断,旧快照仍保留,不作为当前状态 | | `ERROR` | 无法自动恢复,需要处理错误并新建 ExApi | `trading_status()` 读取最近发布状态,不主动查询。资金默认每 5 秒安排刷新,实际到达可能更晚。一次成交引起的订单成交量、成交和持仓更新会在同一轮发布,资金更新可能在后续轮次才到达。 ## 只读映射的正确访问 ```python positions = api.watch_position(symbol) orders = api.watch_order(symbol) trades = api.watch_trade(symbol) # 上述创建动作放在循环之前。 while api.wait_update(): if api.trading_status() != "READY": continue position = positions.data().get(symbol) if position is not None: print(position.pos_long, position.available_long) for order_id, order in orders.data().items(): print(order_id, order.status) for trade_id, trade in trades.data().items(): print(trade_id, trade.price, trade.volume) ``` 持仓按合约代码索引,订单按本地 `order_id` 索引,成交按 CTP `TradeID` 索引。即使指定单个合约,`data()` 仍返回 Mapping。可提前观察尚无持仓的合约;持仓归零后仍保留对象。 订单与成交只包含当前交易日普通投机记录,包括同一账户其他客户端产生的记录。字段含义、未知值和单位详见 [数据字典](./data-reference.md)。 ## 断线与同步 重连期间不发布部分查询结果;恢复 `READY` 后提交一致快照。`data()` 不会因断线自动清空,但旧数据不能表示当前账户状态。在非 READY 时本地下单拒绝通过返回结果说明,不改写订单快照。 --- # 下单、撤单与结果跟踪 下单会向真实期货公司提交委托。先在适当测试环境充分验证策略。本文片段假定已经创建 CTP 账户、观察订单并完成同步;完整下载示例要求显式传入 `--execute` 才会下单。 ## 限价委托 ```python if api.trading_status() != "READY": raise RuntimeError("交易账户尚未就绪") results = api.insert_order( symbol=symbol, direction="BUY", offset="OPEN", volume=1, limit_price=limit_price, ) tracked_ids = set() for result in results: print(result.status, result.order_id, result.message) if result.status in {"SUBMITTED", "UNKNOWN"} and result.order_id: tracked_ids.add(result.order_id) ``` `direction` 为 `BUY` / `SELL`;输入 `offset` 为 `OPEN` / `CLOSE`。`volume` 为正整数手数,`limit_price` 为符合最小变动价位的有效限价。SDK 仅支持普通投机限价委托。 ## 即时提交结果 | TradingResult.status | 含义 | 正确处理 | | --- | --- | --- | | `SUBMITTED` | 已交给交易接口 | 等待订单回报;不等于柜台接受或成交 | | `REJECTED` | 本地拒绝或明确未发送 | 检查 message;下单不会创建订单,order_id 为 None | | `UNKNOWN` | 提交结果不明 | 保留订单号等待自然回报,**不自动重发** | 返回值为 `list[TradingResult]`,并非订单号列表。拆单会产生多个子单结果;前一子单失败或结果不明时,剩余子单可能返回未发送的拒绝结果。`CLOSE` 由 SDK 按合约和可平持仓规则处理,实际订单快照中的 offset 还可能是 `CLOSE_TODAY` / `CLOSE_YESTERDAY`。 ## 跟踪最终状态 在下单前创建 `orders = api.watch_order()`,之后通过同一个更新循环跟踪: ```python while tracked_ids: api.wait_update() if api.trading_status() != "READY": continue snapshot = orders.data() for order_id in list(tracked_ids): order = snapshot.get(order_id) if order is None: continue print(order_id, order.status, order.volume_traded, order.status_msg) if order.status in {"FILLED", "CANCELLED", "REJECTED"}: tracked_ids.remove(order_id) ``` 示例是观察逻辑,业务程序应另行提供等待期限和后续处理。`PENDING` 待回报;`ALIVE` 尚未确认终态;`FILLED` 全部成交;`CANCELLED` 未全成且不再排队;`REJECTED` 提交失败或被拒。取消后的 `volume_left` 仍可大于零。 ## 撤单 ```python results = api.cancel_order(order_id) for result in results: print(result.status, result.order_id, result.message) ``` 传本地 `order_id`,不是 `order_sys_id`。不存在、已结束或已经在撤单处理中的订单返回 `[]`。尚未确认或缺少系统订单号时立即拒绝,不缓存撤单意图。最终结果查看订单的 `status`、`cancel_pending`、`cancel_error`,不能把撤单 `SUBMITTED` 当作已撤成功。 ## 错误与退出 下单或撤单的业务拒绝一般不抛异常、不自动重试、不单独改变会话状态。线程错误、关闭后调用等仍可立即报错。关闭 `ExApi` 不会撤销订单;如果策略退出时需要撤单,应自行发起并跟踪结果,不盲目重发委托。 --- # 错误处理与恢复 运行故障与交易操作结果分别处理。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 连接;应用仍需明确处理存量委托与退出。 --- # 完整接口参考 适用于 exsdk 1.1.2。`Literal` 表示允许值集合,`Mapping` 表示只读映射,`pd.DataFrame` 指 pandas DataFrame。示例片段里的 `token`、`symbol`、`limit_price`、`order_id` 由调用方提供,片段放在正确的 ExApi 生命周期内运行。完整可独立执行程序见 [示例下载](./examples.md)。 ## 通用调用约定 所有实例方法在创建实例的线程使用;不可在运行中的 asyncio 事件循环内调用同步方法。观察类接口先创建句柄、再调用 wait_update 推进。交易类读取需配置 CtpAccount。上下文方式 `with ExApi(token, account) as api:` 退出会关闭资源,但不会撤销委托。 ## ExApi ```text ExApi(token: str, account: CtpAccount | None=None) ``` 创建同步 SDK 实例。请使用 with 管理生命周期。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `token` | `str` | `必填` | 用户中心签发的非空 SDK Token;不能只包含空白。 | | `account` | `CtpAccount \| None` | `None` | CTP 账户配置,不传时仅使用行情。 | ### 返回与行为 ExApi 实例。无需交易时不传 account。 无效 Token 抛 TypeError;处于运行中的 asyncio 循环或不正确线程上下文会报错。网络故障以 ExError 报告。 ### 示例 ```python with ExApi(token) as api: print(api.query_symbols("FUTURE")) ``` ## ExApi.trading_status ```text ExApi.trading_status() -> Literal['SYNCING', 'READY', 'DISCONNECTED', 'ERROR'] ``` 读取最近发布的交易状态,不主动查询。 ### 参数 无。 ### 返回与行为 SYNCING、READY、DISCONNECTED、ERROR 四种字符串之一。 需要配置 CTP;READY 不代表资金和行情即时。 ### 示例 ```python if api.trading_status() == "READY": print("可以提交委托") ``` ## ExApi.watch_account ```text ExApi.watch_account() -> Watch[Account] ``` 创建资金观察句柄。 ### 参数 无。 ### 返回与行为 Watch[Account];data() 返回独立资金快照。 需要 CTP。未知字段为 None,资金来自完整柜台查询,默认每 5 秒安排刷新。 ### 示例 ```python funds = api.watch_account() # 在 wait_update 后读取 print(funds.data().available) ``` ## ExApi.watch_position ```text ExApi.watch_position(symbol: str | None=None) -> Watch[Mapping[str, Position]] ``` 观察全部或某合约的持仓。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `symbol` | `str \| None` | `None` | 交易所.合约格式;查询结果中的有效代码。可选时 None 表示不过滤合约。 | ### 返回与行为 Watch[Mapping[str, Position]];键为 symbol。 指定 symbol 仍返回 Mapping;无效合约格式抛 ValueError。归零对象继续保留。 ### 示例 ```python positions = api.watch_position(symbol) position = positions.data().get(symbol) ``` ## ExApi.watch_order ```text ExApi.watch_order(symbol: str | None=None) -> Watch[Mapping[str, Order]] ``` 观察当前交易日全部或某合约订单。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `symbol` | `str \| None` | `None` | 交易所.合约格式;查询结果中的有效代码。可选时 None 表示不过滤合约。 | ### 返回与行为 Watch[Mapping[str, Order]];键为本地 order_id。 需要 CTP;包括同一账户其他客户端的普通投机订单。 ### 示例 ```python orders = api.watch_order() order = orders.data().get(order_id) ``` ## ExApi.watch_trade ```text ExApi.watch_trade(symbol: str | None=None) -> Watch[Mapping[str, Trade]] ``` 观察当前交易日全部或某合约真实成交。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `symbol` | `str \| None` | `None` | 交易所.合约格式;查询结果中的有效代码。可选时 None 表示不过滤合约。 | ### 返回与行为 Watch[Mapping[str, Trade]];键为原始 CTP TradeID。 需要 CTP;不包含估算成交,不以订单提交推断成交。 ### 示例 ```python trades = api.watch_trade() for trade_id, trade in trades.data().items(): print(trade_id, trade.volume) ``` ## ExApi.insert_order ```text ExApi.insert_order(symbol: str, direction: Literal['BUY', 'SELL'], offset: Literal['OPEN', 'CLOSE'], volume: int, limit_price: float) -> list[TradingResult] ``` 提交普通投机限价委托;可能按平仓规则拆成多个子单。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `symbol` | `str` | `必填` | 交易所.合约格式;查询结果中的有效代码。可选时 None 表示不过滤合约。 | | `direction` | `Literal['BUY', 'SELL']` | `必填` | BUY 买入或 SELL 卖出。 | | `offset` | `Literal['OPEN', 'CLOSE']` | `必填` | OPEN 开仓或 CLOSE 平仓。 | | `volume` | `int` | `必填` | 正整数手数。 | | `limit_price` | `float` | `必填` | 有效限价,须符合合约最小变动价位。 | ### 返回与行为 list[TradingResult],每个子单一个即时提交结果。 SUBMITTED 不等于成交;UNKNOWN 不自动重发。未配置账户或非 READY 等业务拒绝返回 REJECTED。调用上下文错误仍可抛异常。 ### 示例 ```python results = api.insert_order(symbol, "BUY", "OPEN", 1, limit_price) for result in results: print(result.status, result.order_id, result.message) ``` ## ExApi.cancel_order ```text ExApi.cancel_order(order_id: str) -> list[TradingResult] ``` 按本地订单号发起撤单。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `order_id` | `str` | `必填` | 本地订单标识 FrontID:SessionID:OrderRef,不是交易所系统订单号。 | ### 返回与行为 list[TradingResult];不存在、结束或已有撤单在处理时返回 []。 未确认或无系统编号立即拒绝,不缓存撤单意图。最终看订单 status/cancel_pending/cancel_error。 ### 示例 ```python for result in api.cancel_order(order_id): print(result.status, result.message) ``` ## ExApi.query_symbols ```text ExApi.query_symbols(ins_class: Literal['FUTURE', 'OPTION'], exchange_id: str | None=None, product_id: str | None=None, is_main: bool | None=None) -> list[str] ``` 一次性查询合约列表,不是持续观察对象。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `ins_class` | `Literal['FUTURE', 'OPTION']` | `必填` | FUTURE 或 OPTION,区分期货和期权,必填。 | | `exchange_id` | `str \| None` | `None` | 交易所代码,如 SHFE;None 不筛选。 | | `product_id` | `str \| None` | `None` | 品种代码,如 rb;None 不筛选。 | | `is_main` | `bool \| None` | `None` | True 仅主力,False 仅非主力,None 不限;期权均为非主力。 | ### 返回与行为 list[str],可能为空。 无效 ins_class、筛选值抛 ValueError;响应格式或运行故障抛 ExError。 ### 示例 ```python symbols = api.query_symbols("FUTURE", exchange_id="SHFE", is_main=True) ``` ## ExApi.query_symbol_info ```text ExApi.query_symbol_info(symbol: str) -> dict ``` 一次性查询单个合约详细信息。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `symbol` | `str` | `必填` | 交易所.合约格式;查询结果中的有效代码。可选时 None 表示不过滤合约。 | ### 返回与行为 dict,顶层字段为 symbol、exchange_id、product_id、ins_class、is_main、data。data 的详细键由服务提供。 无效代码抛 ValueError,响应无效或请求失败抛 ExError。 ### 示例 ```python info = api.query_symbol_info(symbol) print(info["ins_class"], info["data"]) ``` ## ExApi.watch_quote ```text ExApi.watch_quote(symbol: str) -> Watch[Quote] ``` 创建期货或期权实时行情观察句柄,相同合约复用。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `symbol` | `str` | `必填` | 交易所.合约格式;查询结果中的有效代码。可选时 None 表示不过滤合约。 | ### 返回与行为 Watch[Quote],data() 返回独立行情快照。 初始 timestamp=0,数值可能 NaN,盘口可空;检查时效。无效代码抛 ValueError。 ### 示例 ```python quote = api.watch_quote(symbol) while api.wait_update(): if quote.is_changing("last_price"): print(quote.data().last_price) ``` ## ExApi.watch_kline ```text ExApi.watch_kline(symbol: str, period: Literal['1m', '5m', '10m', '15m', '30m', '1h', '4h', '1d'], data_length: int=200) -> Watch[pd.DataFrame] ``` 初始化并订阅期货固定长度 K 线窗口。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `symbol` | `str` | `必填` | 交易所.合约格式;查询结果中的有效代码。可选时 None 表示不过滤合约。 | | `period` | `Literal['1m', '5m', '10m', '15m', '30m', '1h', '4h', '1d']` | `必填` | 1m / 5m / 10m / 15m / 30m / 1h / 4h / 1d。 | | `data_length` | `int` | `200` | 固定窗口长度,1–10000 的整数,默认 200。 | ### 返回与行为 Watch[pandas.DataFrame],列见数据字典,attrs 包含 symbol、period。 期权不支持;无效周期、长度抛 ValueError。初始化整体上限 180 秒,故障抛 ExError。相同三元参数复用句柄。 ### 示例 ```python bars = api.watch_kline(symbol, "1m", data_length=200) frame = bars.data() if not frame.empty: print(frame.iloc[-1]) ``` ## ExApi.wait_update ```text ExApi.wait_update(deadline: float | None=None) -> bool ``` 推进 I/O 并等待下一批可见数据更新。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `deadline` | `float \| None` | `None` | Unix 秒绝对时间;None 表示不设置本次等待期限。 | ### 返回与行为 bool,提交更新返回 True;本次等待期限到达而无更新返回 False。 False 不表示故障,不重置恢复额度。运行故障抛 ExError;deadline 不是相对秒数。 ### 示例 ```python import time if api.wait_update(deadline=time.time() + 5): print(quote.data()) ``` ## ExApi.close ```text ExApi.close() -> None ``` 主动关闭实例并释放行情、交易资源。 ### 参数 无。 ### 返回与行为 None。 关闭不撤销已提交订单。优先使用 with,异常退出时会清理资源。 ### 示例 ```python try: print(api.query_symbols("FUTURE")) finally: api.close() ``` ## Watch.data ```text Watch.data() -> T ``` 读取最近提交数据的独立快照;不触发查询。 ### 参数 无。 ### 返回与行为 由句柄决定:Quote、DataFrame、Account 或只读 Mapping[str, Position/Order/Trade]。 旧快照不再变化;断线时保留旧数据。交易未知字段为 None;DataFrame 指标列不带入下一次快照。 ### 示例 ```python snapshot = quote.data() print(snapshot.last_price) ``` ## Watch.is_changing ```text Watch.is_changing(key: str | list[str] | None=None) -> bool ``` 检查最近一次 wait_update 提交中对象或指定字段是否变化。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `key` | `str \| list[str] \| None` | `None` | None 检查整体;字符串检查单字段;字符串列表检查任意指定字段。 | ### 返回与行为 bool。字段列表按任意一个变化判断。 使用字段名,不把快照本身传入。集合成员增减也构成集合变化。 ### 示例 ```python if quote.is_changing(["last_price", "volume"]): print(quote.data()) ``` ## api.tools.download_kline_data ```text api.tools.download_kline_data(symbol: str, period: Literal['1m', '5m', '10m', '15m', '30m', '1h', '4h', '1d'], start: str, end: str, csv_file_name: str | os.PathLike) -> int ``` 同步按交易日分页下载期货 K 线到 CSV。 ### 参数 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `symbol` | `str` | `必填` | 交易所.合约格式;查询结果中的有效代码。可选时 None 表示不过滤合约。 | | `period` | `Literal['1m', '5m', '10m', '15m', '30m', '1h', '4h', '1d']` | `必填` | 1m / 5m / 10m / 15m / 30m / 1h / 4h / 1d。 | | `start` | `str` | `必填` | 起始交易日,YYYYMMDD,包含当天。 | | `end` | `str` | `必填` | 结束交易日,YYYYMMDD,包含当天,不能早于 start。 | | `csv_file_name` | `str \| os.PathLike` | `必填` | 输出 CSV 文件路径,字符串或 PathLike;父目录必须存在。 | ### 返回与行为 int,写入数据行数;无数据写出表头并返回 0。 只支持期货;无效日期/周期/路径类型抛 TypeError 或 ValueError;查询、保存故障抛 ExError。成功覆盖文件,失败保留原文件。 ### 示例 ```python rows = api.tools.download_kline_data( symbol, "1m", "20260901", "20260909", "history.csv" ) print(rows) ``` ## 上下文管理与 Watch 类型 `ExApi.__enter__()` 返回当前实例,`ExApi.__exit__(...)` 关闭实例并清理资源。业务程序优先使用 with。 `Watch[T]` 由 SDK 的 watch 方法创建,不应由调用方直接构造;只使用 data() 与 is_changing() 公开方法。辅助对象 api.tools 由 ExApi 自动创建,不自行实例化 ExTools。 --- # 数据类型与字段 单位、缺失值和时间含义都是接口的一部分。交易视图字段未知时为 None,行情数值初始为 NaN;不要把未知值当作 0。 ## Quote 合约行情快照;通过观察句柄的 data() 获取,不随后续更新改变。 Attributes: symbol: 合约代码,如 SHFE.rb2610 或 DCE.a2611-C-3700。 trading_day: 交易日,格式 YYYYMMDD。 timestamp: 行情时间,Unix 毫秒。 open: 开盘价。 highest: 最高价。 lowest: 最低价。 last_price: 最新价。 pre_settlement: 昨结算价。 pre_close: 昨收盘价。 upper_limit: 涨停价。 lower_limit: 跌停价。 volume: 成交量。 turnover: 成交额。 open_interest: 持仓量。 bids: 买盘档位,每档为(价格,数量)。 asks: 卖盘档位,每档为(价格,数量)。 | 字段 | 类型 | 含义 | | --- | --- | --- | | `symbol` | `str` | 合约代码,如 SHFE.rb2610 或 DCE.a2611-C-3700。 | | `trading_day` | `str` | 交易日,格式 YYYYMMDD。 | | `timestamp` | `int` | 行情时间,Unix 毫秒。 | | `open` | `float` | 开盘价。 | | `highest` | `float` | 最高价。 | | `lowest` | `float` | 最低价。 | | `last_price` | `float` | 最新价。 | | `pre_settlement` | `float` | 昨结算价。 | | `pre_close` | `float` | 昨收盘价。 | | `upper_limit` | `float` | 涨停价。 | | `lower_limit` | `float` | 跌停价。 | | `volume` | `float` | 成交量。 | | `turnover` | `float` | 成交额。 | | `open_interest` | `float` | 持仓量。 | | `bids` | `list` | 买盘档位,每档为(价格,数量)。 | | `asks` | `list` | 卖盘档位,每档为(价格,数量)。 | 初始 symbol 由订阅决定,trading_day="",timestamp=0,数值字段为 NaN,盘口为空列表。对象字段只读,快照不随后续更新变化。盘口不保证固定档数。 ## ExError SDK 数据或交易服务异常,status 为可选的响应状态码。 ```text ExError(message: str, *, status: int | None = None) ``` 继承 Exception;str(error) 为可供业务读取的提示,error.status 为可选 HTTP 状态码。内部重新包装异常时 status 可能为 None,不能只依赖此字段分类所有故障。 ## CtpAccount CTP 直连账户配置,交易凭据不会发送至行情服务。 Args: broker_id: 期货公司代码。 user_id: 资金账号。 password: 交易密码。 front_url: 交易前置地址,格式为 tcp://地址:端口。 app_id: 期货公司提供的客户端标识。 auth_code: 期货公司提供的客户端授权码。 | 字段 | 类型 | 含义 | | --- | --- | --- | | `broker_id` | `str` | 期货公司代码。 | | `user_id` | `str` | 资金账号。 | | `password` | `str` | 交易密码。 | | `front_url` | `str` | 交易前置地址,格式为 tcp://地址:端口。 | | `app_id` | `str` | 期货公司提供的客户端标识。 | | `auth_code` | `str` | 期货公司提供的客户端授权码。 | 全部参数必填,front_url 必须以 tcp:// 开头。password/auth_code 不出现在默认 repr 中;仍不要输出整个配置。校验失败抛 ValueError。 ## Account 只读资金快照;金额单位为人民币元,未知字段为 None。 金额仅由完整柜台查询更新,不根据行情或成交估算;交易日来自登录回报。 | 字段 | 类型 | 含义 | | --- | --- | --- | | `account_id` | `str \| None` | 资金账号。 | | `trading_day` | `str \| None` | 交易日,格式 YYYYMMDD。 | | `balance` | `float \| None` | 账户权益。 | | `available` | `float \| None` | 可用资金。 | | `margin` | `float \| None` | 已占用保证金。 | | `frozen_margin` | `float \| None` | 冻结保证金。 | | `commission` | `float \| None` | 当日累计实际手续费。 | | `frozen_commission` | `float \| None` | 冻结手续费。 | | `close_profit` | `float \| None` | 当日平仓盈亏。 | | `position_profit` | `float \| None` | 持仓盈亏。 | | `deposit` | `float \| None` | 当日入金。 | | `withdraw` | `float \| None` | 当日出金。 | | `updated_at` | `int \| None` | 最近一次完整查询完成的本地时间,Unix 毫秒。 | ## Position 只读持仓快照;数量单位为手。 登录查询建立基线,之后根据真实成交本地更新;未知数量为 None。 | 字段 | 类型 | 含义 | | --- | --- | --- | | `symbol` | `str` | 合约代码,如 SHFE.rb2610。 | | `pos_long` | `int \| None` | 多头持仓。 | | `pos_short` | `int \| None` | 空头持仓。 | | `pos_long_today` | `int \| None` | 剩余多头今仓。 | | `pos_short_today` | `int \| None` | 剩余空头今仓。 | | `available_long` | `int \| None` | 本地参考可平多头手数:多头持仓减已知有效卖出平仓委托的冻结量;最终由柜台校验。 | | `available_short` | `int \| None` | 本地参考可平空头手数:空头持仓减已知有效买入平仓委托的冻结量;最终由柜台校验。 | ## Order 只读订单快照;断线后观察句柄保留最后一次已提交的数据。 | 字段 | 类型 | 含义 | | --- | --- | --- | | `order_id` | `str` | FrontID:SessionID:OrderRef 格式的本地订单标识。 | | `order_sys_id` | `str \| None` | CTP 返回的交易所订单号 OrderSysID,尚未收到时为 None。 | | `symbol` | `str` | 合约代码。 | | `direction` | `str \| None` | BUY 买入,SELL 卖出。 | | `offset` | `str \| None` | 实际委托开平标志:OPEN、CLOSE、CLOSE_TODAY 或 CLOSE_YESTERDAY。 | | `limit_price` | `float \| None` | 委托限价。 | | `volume_original` | `int \| None` | 原始委托数量,单位为手。 | | `volume_traded` | `int \| None` | 已成交数量,单位为手。 | | `volume_left` | `int \| None` | 未成交数量;撤单结束后仍可大于零。 | | `status` | `Literal['PENDING', 'ALIVE', 'FILLED', 'CANCELLED', 'REJECTED'] \| None` | 待回报、尚未确认终态、全部成交、未全成且不再排队、提交失败或被拒;未知时为 None。 | | `status_msg` | `str \| None` | 最近一次订单状态说明。 | | `cancel_pending` | `bool` | 撤单请求待发送或等待柜台结果时为 True。 | | `cancel_error` | `str \| None` | 撤单失败原因;没有失败时为 None。 | | `insert_time` | `int \| None` | 委托时间,Unix 毫秒。 | status 允许 PENDING、ALIVE、FILLED、CANCELLED、REJECTED。CANCELLED 不代表剩余数量为零;cancel_pending / cancel_error 反映撤单过程,不是新的成交。 ## Trade 只读真实成交快照,不包含估算手续费;断线后保留最后一次已提交的数据。 | 字段 | 类型 | 含义 | | --- | --- | --- | | `trade_id` | `str` | CTP 返回的原始成交号 TradeID。 | | `order_sys_id` | `str` | CTP 返回的交易所订单号 OrderSysID。 | | `symbol` | `str` | 合约代码。 | | `direction` | `str \| None` | BUY 买入,SELL 卖出。 | | `offset` | `str \| None` | OPEN 开仓,CLOSE 普通平仓,CLOSE_TODAY 平今,CLOSE_YESTERDAY 平昨。 | | `price` | `float \| None` | 实际成交价格。 | | `volume` | `int \| None` | 实际成交数量,单位为手。 | | `trade_time` | `int \| None` | 成交时间,Unix 毫秒。 | trade_id 为原始 CTP TradeID;通过 order_sys_id 关联 Order.order_sys_id。成交不包含估算手续费。 ## TradingResult 一次交易操作的即时提交结果;不是最终委托或撤单结果。 | 字段 | 类型 | 含义 | | --- | --- | --- | | `status` | `Literal['SUBMITTED', 'REJECTED', 'UNKNOWN']` | SUBMITTED / REJECTED / UNKNOWN,即时提交结果。 | | `order_id` | `str \| None` | 关联本地订单号;下单本地拒绝为 None。 | | `message` | `str` | 即时结果说明,默认空字符串。 | ## K 线 DataFrame | 列 | 含义 | | --- | --- | | timestamp | K 线周期结束时间,Unix 毫秒;日线使用交易日北京时间零点 | | trading_day | YYYYMMDD 交易日 | | open / high / low / close | 开盘 / 最高 / 最低 / 收盘价 | | volume | 成交量 | | turnover | 成交额 | | open_interest | 持仓量 | frame.attrs 包含 symbol 与 period。数据为独立快照,最新一行可能仍在变化;新建指标列不会进入下一份 SDK 快照。 ## 合约详情字典 query_symbol_info() 顶层固定含 symbol、exchange_id、product_id、ins_class、is_main、data。data 的内部键按服务实际返回读取,SDK 不承诺固定的静态字段集合。 ## 只读集合 watch_position、watch_order、watch_trade 的 data() 返回只读 Mapping。分别按 symbol、order_id、trade_id 索引;即使指定单个合约也返回映射。不要向映射或交易快照字段赋值。 --- # 完整示例与下载 下载示例使用环境变量,不包含真实令牌或交易密码。运行前请按 [安装与环境](./installation.md) 手动设置 `EXSDK_TOKEN`;这是示例约定的变量名,不是 SDK 内置变量。脚本调用 `os.environ` 读取后,再将令牌传入 `ExApi`。完成配置后,每个文件均可独立运行。 ## 行情与分析 | 文件 | 内容 | 运行方式 | | --- | --- | --- | | [quickstart.py](/examples/quickstart.py) | 自动发现主力合约并观察行情 | `python quickstart.py` | | [quotes.py](/examples/quotes.py) | 期货 / 期权行情、盘口与时间戳 | `python quotes.py --kind FUTURE` 或 `python quotes.py --kind OPTION` | | [klines.py](/examples/klines.py) | K 线快照及 MA20 | `python klines.py` | | [history.py](/examples/history.py) | 按交易日导出 CSV | `python history.py --symbol 合约代码 --start 20260901 --end 20260909 --output history.csv` | 默认自动选取的第一份合约仅用于接口演示,不代表交易推荐。历史示例的合约和日期必须按实际数据范围指定。 ## 交易与撤单 | 文件 | 内容 | 运行方式 | | --- | --- | --- | | [account.py](/examples/account.py) | CTP 同步、资金、持仓和订单快照,只读 | `python account.py` | | [trading.py](/examples/trading.py) | 限价委托与结果跟踪 | 默认不下单;阅读脚本后显式传入 `--execute` 及合约、价格 | | [cancel.py](/examples/cancel.py) | 按本地订单号撤单并跟踪 | 默认不撤单;显式指定 `--execute --order-id 本地订单号` | CTP 示例需要本地设置 `CTP_BROKER_ID`、`CTP_USER_ID`、`CTP_PASSWORD`、`CTP_FRONT_URL`、`CTP_APP_ID`、`CTP_AUTH_CODE`。使用 `Ctrl+C` 可退出观察程序,但退出不自动撤销已提交的订单。 ## 常见改编方式 - 用真实查询结果或明确指定的有效合约替换样例合约。 - 在 `wait_update()` 发布快照后再计算业务指标,避免使用旧快照作出交易决策。 - 多个合约在循环之前创建句柄,通过一层更新循环处理。 - 将 `UNKNOWN` 与网络超时的处理交给订单跟踪与人工核对,不做无条件重发。