Metadata-Version: 2.4
Name: gj_kingstar_api
Version: 1.0.6
Summary: 金仕达行情 SDK（支持 QGate 股票/股指备用源）
Author: diy
Author-email: 471293853@qq.com
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: protobuf>=4.24.4
Requires-Dist: websockets>=11.0.3
Requires-Dist: PyYAML>=6.0
Dynamic: author
Dynamic: author-email
Dynamic: description
Dynamic: description-content-type
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# gj_kingstar_api

`gj_kingstar_api` 是行情订阅 Python SDK。SDK 保留原有金仕达单站点、多站点高可用、自动重连和订阅恢复能力，并在 `1.0.6` 中增加 QGate 作为股票/股指（`CS`）的手动备用行情源。

## 1. 能力范围

- 金仕达单站点和多站点连接。
- 金仕达站点探活、按权重选路、故障重连和订阅恢复。
- `Future`、`Option`、`CS` 行情订阅和退订。
- QGate 股票/股指备用源，当前覆盖沪市和深市。
- 运行中手动切换 `CS` 行情源，`Future`、`Option` 不随 `CS` 切换。
- YAML/JSON 配置文件和 `gj-kingstar` 运维命令。
- 金仕达与 QGate 统一输出 `GTick`，统一经 `MarketListener.on_tick()` 回调。

> 当前不是自动跨源故障切换。金仕达内部主备站点仍可自动切换；金仕达与 QGate 之间由用户手动切换。

## 2. 安装与版本确认

正式发行包名与 Python 导入名均为 `gj_kingstar_api`：

| 项目 | 名称 |
| --- | --- |
| 正式发行包名 | `gj_kingstar_api` |
| Python 导入名 | `gj_kingstar_api` |
| 命令行入口 | `gj-kingstar` |
| 当前版本 | `1.0.6` |

安装 wheel：

```bash
python -m pip install ./gj_kingstar_api-1.0.6-py3-none-any.whl
```

确认实际导入路径和版本：

```bash
python -c "import gj_kingstar_api; print(gj_kingstar_api.__version__); print(gj_kingstar_api.__file__)"
```

预期版本为 `1.0.6`。在源码目录执行测试时，Python 可能优先导入当前目录源码；验证已安装包时应切换到项目目录以外的位置，并检查 `__file__` 是否位于 `site-packages`。

## 3. 最小使用示例

```python
import logging
import time

from gj_kingstar_api import KingstarClient, MarketListener


class MyMarketListener(MarketListener):
    def on_subscribe_tick_data(self, flow_no):
        print(f"订阅响应 flow_no={flow_no}")

    def on_unsubscribe_tick_data(self, flow_no):
        print(f"退订响应 flow_no={flow_no}")

    def on_tick(self, flow_no, ticks):
        for tick in ticks:
            print(tick.gtick2json())

    def on_site_switch(self, old_site, new_site, reason):
        print(f"金仕达站点切换: {old_site} -> {new_site}, reason={reason}")

    def on_connection_status(self, status, message):
        print(f"连接状态: {status}, message={message}")


logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)

client = KingstarClient(
    url="ws://10.77.75.48:20002",
    user="your_user",
    pwd="your_password",
)
client.add_listener(MyMarketListener())
client.connect()

try:
    client.subscribe_tick("SHFE", "Future", ["ag2607"])
    client.subscribe_tick("SH", "CS", ["600000", "000300"])
    client.subscribe_tick("SZ", "CS", ["000001", "399006"])
    time.sleep(60)
finally:
    client.close()
```

## 4. 初始化方式

### 4.1 单站点

```python
client = KingstarClient(
    url="ws://10.77.75.48:20002",
    user="your_user",
    pwd="your_password",
)
```

### 4.2 多站点

```python
sites = [
    {"url": "ws://10.77.75.48:20002", "weight": 100, "name": "主站"},
    {"url": "ws://118.89.84.249:20002", "weight": 50, "name": "备站"},
]

client = KingstarClient(
    sites=sites,
    user="your_user",
    pwd="your_password",
    auto_switch=True,
    probe_interval=30,
    max_reconnect=10,
    heartbeat_interval=30,
)
```

### 4.3 配置文件

仓库提供完整样例 [`kingstar_config.example.yaml`](kingstar_config.example.yaml)。复制为业务配置后填写账号密码：

```python
client = KingstarClient.from_config(
    "kingstar_config.yaml",
    user="your_user",
    pwd="your_password",
)
client.add_listener(MyMarketListener())
client.connect()
```

`from_config()` 支持 `.yaml`、`.yml` 和 `.json`。显式传入的 `user`、`pwd` 优先于配置文件中的同名字段。

## 5. 订阅与退订

### 5.1 方法签名

```python
client.subscribe_tick(exchange_id, type, instrument_id)
client.unsubscribe_tick(exchange_id, type, instrument_id)
```

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `exchange_id` | `str` | 交易所代码。股票/股指建议使用 `SH`、`SZ`；期货、期权按金仕达交易所代码传入，例如 `SHFE` |
| `type` | `str` | 严格使用 `Future`、`Option` 或 `CS` |
| `instrument_id` | `str` 或 `list[str]` | 单个代码或代码列表；股票代码必须保留前导零 |

示例：

```python
client.subscribe_tick("SH", "CS", ["600000", "000300", "000852"])
client.subscribe_tick("SZ", "CS", ["000001", "002380", "399001", "399006"])
client.subscribe_tick("SHFE", "Future", ["ag2607"])
client.subscribe_tick("SHFE", "Option", ["实际有效期权代码"])
```

QGate 的沪深行情服务按交易所主题推送，SDK 根据用户订阅代码构造服务端筛选器并在本地再次校验。用户不需要传 QGate topic。

## 6. 股票/股指行情源切换

### 6.1 初始行情源

配置文件中的 `market_source.cs` 决定客户端启动时的 `CS` 行情源：

```yaml
market_source:
  cs: kingstar  # kingstar 或 qgate

qgate:
  host: your_qgate_host
  websocket_port: 14690
  heartbeat_interval: 20
  latest_push: false
```

- `kingstar`：股票、股指、期货、期权均从金仕达开始。
- `qgate`：股票/股指从 QGate 开始；后续订阅期货或期权时仍会启动金仕达连接。
- `latest_push: false`：不主动请求最近缓存行情，减少历史快照干扰。

### 6.2 修改配置文件

查看配置中的当前源：

```bash
gj-kingstar --config kingstar_config.yaml source show
```

修改为 QGate：

```bash
gj-kingstar --config kingstar_config.yaml source set cs qgate
```

修改回金仕达：

```bash
gj-kingstar --config kingstar_config.yaml source set cs kingstar
```

`source switch` 是 `source set` 的同义命令。目标源与当前源相同时命令会返回“无需切换”，不会重复写入。

> CLI 只修改配置文件，不会通知已经运行的 Python 进程。修改后的配置会在下次 `KingstarClient.from_config()` 创建客户端时生效。

### 6.3 运行中立即切换

```python
result = client.set_source("CS", "qgate")
print(result.to_dict())

result = client.set_source("CS", "kingstar")
print(result.to_dict())
```

返回 `SourceSwitchResult`：

| 字段 | 说明 |
| --- | --- |
| `changed` | 是否发生真实切换；目标源已是当前源时为 `False` |
| `source_type` | 当前固定为 `CS` |
| `old_source` | 切换前源 |
| `new_source` | 切换后源 |
| `message` | 中文结果摘要 |
| `details` | 订阅快照、迁移数量和连接保留状态等结构化明细 |

切换规则：

1. 只迁移已记录的 `CS` 股票/股指订阅。
2. `Future`、`Option` 始终留在金仕达。
3. 切到 QGate 时先启动目标源，再尝试退订金仕达 `CS`，避免金仕达不可用时阻塞切换。
4. 金仕达仍有期货或期权订阅时保持金仕达连接；没有任何剩余订阅时关闭其底层连接。
5. 切回金仕达时先恢复金仕达 `CS` 订阅，成功后再退订并清理 QGate。

## 7. 回调和输出

业务监听器继承 `MarketListener`：

| 回调 | 入参 | 用途 |
| --- | --- | --- |
| `on_subscribe_tick_data(flow_no)` | 流水号 | 订阅响应通知 |
| `on_unsubscribe_tick_data(flow_no)` | 流水号 | 退订响应通知 |
| `on_tick(flow_no, ticks)` | 流水号、`list[GTick]` | 行情数据 |
| `on_site_switch(old_site, new_site, reason)` | 旧站点、新站点、原因 | 金仕达内部站点切换通知 |
| `on_connection_status(status, message)` | 状态、说明 | 连接状态通知 |

SDK 核心路径不使用 `print()` 输出业务结果。命令行工具会打印操作回显；Python SDK 的行情数据只通过监听器回调交给用户。日志使用标准库 `logging`，是否输出到控制台或文件由调用方配置。

文件和控制台同时记录示例：

```python
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler("market_sdk.log", encoding="utf-8"),
    ],
)
```

关键日志可通过模块名前缀区分来源：`gj_kingstar_api.kingstar_client`、`gj_kingstar_api.qgate_market_client`、`gj_kingstar_api.qgate_websocket_client` 和 `gj_kingstar_api.site_manager`。

## 8. GTick 字段

`on_tick()` 中每个元素均为 `GTick`。调用 `tick.gtick2json()` 得到以下字段：

| 字段 | 类型 | 说明 | QGate 口径 |
| --- | --- | --- | --- |
| `trading_day` | `str` | 交易日 | 网关交易日 |
| `trade_timestamp` | `int` | 行情时间戳，毫秒 | `QuoteInfo.data_timestamp`，缺失时使用行情内时间戳 |
| `exchange_id` | `str` | 交易所 | `SH` 或 `SZ` |
| `instrument_id` | `str` | 证券/合约代码 | 保留 6 位代码及前导零 |
| `unique_instrument_id` | `str` | 唯一标识 | 股票/股指形如 `SH|S|600000` |
| `last_price` | `float` | 最新价 | 股票 2 位、股指 4 位；不做 100 倍换算 |
| `open_price` | `float` | 开盘价 | 同上 |
| `highest_price` | `float` | 最高价 | 同上 |
| `lowest_price` | `float` | 最低价 | 同上 |
| `upper_limit_price` | `float` | 涨停价 | 网关缺失时为 `0` |
| `lower_limit_price` | `float` | 跌停价 | 网关缺失时为 `0` |
| `pre_settlement_price` | `float` | 昨结算价 | QGate 股票/股指暂填 `0` |
| `pre_close_price` | `float` | 昨收盘价 | 股票 2 位、股指 4 位 |
| `volume` | `int` | 现手 | QGate 暂填 `0` |
| `total_volume` | `int` | 累计成交量 | QGate 股数除以 100，四舍五入为手 |
| `turnover` | `float` | 现额 | QGate 暂填 `0` |
| `total_turnover` | `float` | 累计成交额 | QGate 原始累计成交额，保留 2 位 |
| `open_interest` | `int` | 仓差 | QGate 股票/股指填 `0` |
| `total_open_interest` | `int` | 总持仓量 | 金仕达直接输出协议值；QGate 读取 `StockQuote.iOpenInterest`（field 28），当前股票/股指包未携带时为 `0` |
| `a1` / `a1_v` | `float` / `int` | 卖一价/卖一量 | 价格按品种精度；数量由股转换为手 |
| `b1` / `b1_v` | `float` / `int` | 买一价/买一量 | 价格按品种精度；数量由股转换为手 |
| `delta` / `gamma` / `vega` / `theta` / `rho` | `float` | 期权希腊字母 | QGate 股票/股指填 `0` |

证券名称不属于当前 `GTick` 对外字段，因此金仕达和 QGate 均不通过 `gtick2json()` 返回证券名称。

## 9. QGate 接入约定

- 地址：由部署方提供，通过 `qgate.host` 和 `qgate.websocket_port` 配置。
- WebSocket 二进制帧是完整 QGate 包：12 字节包头加 protobuf payload。
- 深市主题：`15 (STT_STOCK_INDEX_SZ)`。
- 沪市主题：`16 (STT_STOCK_INDEX_SH)`。
- 证券代码筛选字段：`QuoteInfo.resv21`，字段号 `1201`，类型 `QFFTT_STRING`。
- 单代码使用 `EQ + value_string`；多代码使用 `IN + value_set`。
- 代码按字符串原样发送，例如 `000001` 不能转换为整数 `1`。

这些协议细节由 SDK 封装，普通用户只需要按 `subscribe_tick("SH"/"SZ", "CS", codes)` 调用。

## 10. 异常处理与清理

业务程序应使用 `try/finally` 保证客户端关闭：

```python
try:
    client.connect()
    client.subscribe_tick("SH", "CS", ["600000"])
    # 业务主循环
finally:
    client.close()
```

常见排查顺序：

1. 用版本命令确认导入的是目标 wheel，而不是项目源码或旧 `site-packages`。
2. 检查金仕达站点、QGate 地址和交易时段。
3. 检查股票代码前导零、交易所和 `type` 大小写。
4. 查看连接、认证、订阅响应和源切换日志。
5. QGate 返回 `10033 topic unsupported` 时，先确认网关服务是否正常，再核对交易所到 topic 的映射。
6. 无实时行情时，区分“服务未推送”“代码无更新”“筛选未命中”和“连接已断开”。

## 11. 当前已知限制

- QGate 沪市部分股票的涨跌停字段当前未赋值，对外为 `0`。
- QGate 当前没有可直接映射到金仕达 `GTick.volume`、`GTick.turnover` 的现手、现额字段，暂填 `0`。
- QGate 当前接入的是股票/股指主题，实盘样本未携带总持仓量 field 28，因此 `total_open_interest` 为 protobuf 默认值 `0`；已保留 `iOpenInterest` 映射，后续接入携带该字段的期货/期权主题时可直接输出协议值。
- 金仕达与 QGate 的切换由用户手动触发，不提供自动跨源切换。
- 最新价格和数量口径修复仍需使用重新构建的 `1.0.6` wheel 完成最终实盘复验；不要用旧安装包日志证明新修复已生效。

版本变更见 [`CHANGELOG.md`](CHANGELOG.md)。详细测试过程、截图和内部环境证据不随发行包对外提供。

## 12. 运行环境

- Python >= 3.7
- protobuf >= 4.24.4
- websockets >= 11.0.3
- PyYAML >= 6.0

`requirements.txt` 固定的是本版本开发和测试使用的依赖版本；`setup.py` 中记录 SDK 的最低运行依赖。
