{"id":33926822,"url":"https://github.com/pseudocodes/tqsdk-rs","last_synced_at":"2026-01-13T22:02:11.901Z","repository":{"id":328302227,"uuid":"1101697352","full_name":"pseudocodes/tqsdk-rs","owner":"pseudocodes","description":"天勤量化 TQSDK 期货期权行情/历史数据/交易 rust 封装接口","archived":false,"fork":false,"pushed_at":"2025-11-27T14:45:16.000Z","size":121,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-13T18:21:18.461Z","etag":null,"topics":["futures","options","quant","rust","tqsdk","tqsdk-rs","traders","trading-bot"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pseudocodes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-22T04:25:14.000Z","updated_at":"2025-12-03T09:08:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pseudocodes/tqsdk-rs","commit_stats":null,"previous_names":["pseudocodes/tqsdk-rs"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/pseudocodes/tqsdk-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudocodes%2Ftqsdk-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudocodes%2Ftqsdk-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudocodes%2Ftqsdk-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudocodes%2Ftqsdk-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pseudocodes","download_url":"https://codeload.github.com/pseudocodes/tqsdk-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudocodes%2Ftqsdk-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28400414,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["futures","options","quant","rust","tqsdk","tqsdk-rs","traders","trading-bot"],"created_at":"2025-12-12T10:32:36.593Z","updated_at":"2026-01-13T22:02:11.894Z","avatar_url":"https://github.com/pseudocodes.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TQSDK-RS\n\n天勤量化交易平台的 Rust SDK - 高性能、类型安全的期货交易接口\n\n[![Crates.io](https://img.shields.io/crates/v/tqsdk-rs.svg)](https://crates.io/crates/tqsdk-rs)\n[![Documentation](https://docs.rs/tqsdk-rs/badge.svg)](https://docs.rs/tqsdk-rs)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"samples/candlechart/tqsdk-candlechart.png\" alt=\"Demo CandleChart\"\u003e\n\u003c/div\u003e\n\n## 核心特性\n\n- **类型安全** - 使用 Rust 强类型系统，90+ 字段完整定义，编译时消除运行时错误\n- **并发安全** - 基于 Arc + RwLock 设计，确保多线程环境下的数据安全，无数据竞争\n- **异步优化** - 基于 tokio 异步运行时，高效处理 I/O 操作，零成本抽象\n- **DIFF 协议** - 完整实现天勤 DIFF 协议，支持增量数据更新和递归合并\n- **灵活接口** - 支持 Channel、Callback、Stream 三种数据订阅方式，满足不同场景需求\n- **零竞态条件** - 支持延迟启动模式，确保回调注册完成后再启动监听，避免数据丢失\n- **零拷贝回调** - 使用 Arc 参数优化，多回调场景下性能提升 50-100x\n- **Polars 集成** - 高性能列式数据分析，支持 K线/Tick 数据转换为 DataFrame（可选功能）\n- **灵活日志系统** - 支持 Layer 组合，本地时区显示，可与业务层日志集成\n\n## 功能模块\n\n### 行情数据\n- 实时行情订阅（Quote）- 支持多合约同时订阅\n- K线数据订阅（单合约/多合约对齐）- 支持任意周期\n- Tick 数据订阅 - 逐笔成交数据\n- 历史数据获取（支持 left_kline_id 和 focus_datetime 两种方式）\n- ViewWidth 限制和二分查找优化 - 高效处理大数据集\n- **Polars DataFrame 集成** - 高性能数据分析（可选功能）\n\n### 交易功能\n- 实盘交易（TradeSession）- 支持期货公司实盘和 SimNow 模拟\n- 账户信息查询 - 实时资金、权益、保证金等\n- 持仓/委托单/成交查询 - 完整的交易数据\n- 下单/撤单操作 - 支持限价单、市价单等\n- 自动重连机制 - 网络断开自动恢复\n\n### 数据管理\n- DIFF 协议数据合并 - 递归合并嵌套对象\n- 路径监听（Watch/UnWatch）- 精确监听指定路径的数据变化\n- 版本追踪（Epoch）- 追踪每次数据更新\n- 数据类型转换 - JSON 到强类型结构体的自动转换\n\n### 性能优化\n- **零拷贝回调设计** - Arc 参数优化，避免数据深拷贝\n- **高性能缓冲区** - KlineBuffer/TickBuffer 支持 O(1) 更新\n- **列式数据分析** - Polars DataFrame 集成（可选）\n\n### 开发体验\n- **灵活日志系统** - 支持 Layer 组合和本地时区\n- **完整示例** - 8+ 示例程序覆盖所有功能\n- **详细文档** - 完整的 API 文档和使用指南\n\n## 快速开始\n\n### 安装依赖\n\n在 `Cargo.toml` 中添加：\n\n```toml\n[dependencies]\ntqsdk-rs = \"0.1.0\"\ntokio = { version = \"1\", features = [\"full\"] }\n\n# 可选：启用 Polars DataFrame 支持\ntqsdk-rs = { version = \"0.1.0\", features = [\"polars\"] }\n```\n\n### 基础示例 - 行情订阅\n\n```rust\nuse tqsdk_rs::{Client, ClientConfig};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // 1. 创建客户端（自动完成认证）\n    let mut client = Client::new(\"username\", \"password\", ClientConfig::default()).await?;\n    \n    // 2. 初始化行情连接\n    client.init_market().await?;\n    \n    // 3. 订阅行情（支持多个合约）\n    let quote_sub = client.subscribe_quote(\u0026[\"SHFE.au2602\"]).await?;\n    \n    // 4. 注册回调函数\n    quote_sub.on_quote(|quote| {\n        println!(\"行情更新: {} = {}\", quote.instrument_id, quote.last_price);\n    }).await;\n    \n    // 5. 启动订阅\n    quote_sub.start().await?;\n    \n    // 6. 保持运行\n    tokio::signal::ctrl_c().await?;\n    \n    Ok(())\n}\n```\n\n### 使用 ClientBuilder（推荐）\n\nClientBuilder 提供了更灵活的配置方式：\n\n```rust\nuse tqsdk_rs::Client;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // 使用构建器模式创建客户端\n    let mut client = Client::builder(\"username\", \"password\")\n        .log_level(\"debug\")          // 设置日志级别\n        .view_width(5000)            // 设置默认视图宽度\n        .development(true)           // 开启开发模式\n        .build()\n        .await?;\n    \n    // 初始化行情\n    client.init_market().await?;\n    \n    Ok(())\n}\n```\n\n## 核心功能详解\n\n### 1. 行情订阅\n\n#### Quote 订阅 - 实时行情\n\nQuote 订阅支持多合约同时订阅，提供三种数据接收方式：\n\n```rust\n// 订阅多个合约的实时行情\nlet quote_sub = client.subscribe_quote(\u0026[\"SHFE.au2602\", \"SHFE.ag2512\"]).await?;\n\n// 方式 1: 使用回调函数（推荐）\nquote_sub.on_quote(|quote| {\n    println!(\"合约: {}\", quote.instrument_id);\n    println!(\"最新价: {}\", quote.last_price);\n    println!(\"成交量: {}\", quote.volume);\n}).await;\n\n// 方式 2: 使用 Channel（支持多个消费者）\nlet rx = quote_sub.quote_channel();\ntokio::spawn(async move {\n    while let Ok(quote) = rx.recv().await {\n        println!(\"收到行情: {:?}\", quote);\n    }\n});\n\n// 启动订阅（延迟启动模式）\nquote_sub.start().await?;\n\n// 动态添加合约\nquote_sub.add_symbols(\u0026[\"DCE.m2505\"]).await?;\n\n// 动态移除合约\nquote_sub.remove_symbols(\u0026[\"SHFE.ag2512\"]).await?;\n```\n\n#### K线订阅 - 单合约\n\nK线订阅支持任意周期，使用延迟启动模式避免数据丢失：\n\n```rust\nuse std::time::Duration;\n\n// 获取 Series API\nlet series_api = client.series()?;\n\n// 订阅 1 分钟 K线，获取最近 100 根\nlet sub = series_api.kline(\n    \"SHFE.au2602\",           // 合约代码\n    Duration::from_secs(60), // K线周期（60秒 = 1分钟）\n    100                      // 数据条数\n).await?;\n\n// 先注册回调（重要：避免丢失初始数据）\n// 注意：data 和 info 都是 Arc 包装的，零拷贝共享\nsub.on_update(|data, info| {\n    if let Some(kline_data) = \u0026data.single {\n        println!(\"K线数量: {}\", kline_data.data.len());\n        \n        // 检查是否有新 K线生成\n        if info.has_new_bar {\n            println!(\"新 K线生成！\");\n            if let Some(last_kline) = kline_data.data.last() {\n                println!(\"开: {}, 高: {}, 低: {}, 收: {}\", \n                    last_kline.open, last_kline.high, \n                    last_kline.low, last_kline.close);\n            }\n        }\n        \n        // 检查是否有 K线更新\n        if info.has_bar_update {\n            println!(\"K线数据更新\");\n        }\n    }\n}).await;\n\n// 最后启动订阅\nsub.start().await?;\n\n// 常用周期示例\n// Duration::from_secs(60)      // 1 分钟\n// Duration::from_secs(300)     // 5 分钟\n// Duration::from_secs(900)     // 15 分钟\n// Duration::from_secs(3600)    // 1 小时\n// Duration::from_secs(86400)   // 1 天\n```\n\n#### 多合约对齐 K线\n\n多合约订阅会自动进行时间对齐，适用于跨品种分析：\n\n```rust\n// 订阅多个合约的 K线（自动时间对齐）\nlet symbols = vec![\n    \"SHFE.au2602\".to_string(),  // 黄金\n    \"SHFE.ag2512\".to_string(),  // 白银\n];\n\nlet sub = series_api.kline_multi(\n    \u0026symbols,                    // 合约列表\n    Duration::from_secs(60),     // K线周期\n    100                          // 数据条数\n).await?;\n\nsub.on_update(|data, _info| {\n    if let Some(multi_data) = \u0026data.multi {\n        println!(\"主合约: {}\", multi_data.main_symbol);\n        \n        // 遍历对齐后的 K线集合\n        for aligned_set in \u0026multi_data.data {\n            println!(\"时间: {}\", aligned_set.timestamp);\n            \n            // 每个时间点包含所有合约的 K线\n            for (symbol, kline) in \u0026aligned_set.klines {\n                println!(\"  {} - 开: {}, 收: {}\", \n                    symbol, kline.open, kline.close);\n            }\n        }\n    }\n}).await;\n\nsub.start().await?;\n```\n\n#### Tick 订阅 - 逐笔成交\n\nTick 数据提供最细粒度的市场数据：\n\n```rust\n// 订阅 Tick 数据\nlet sub = series_api.tick(\n    \"SHFE.au2602\",  // 合约代码\n    100             // 数据条数\n).await?;\n\nsub.on_update(|data, _info| {\n    if let Some(tick_data) = \u0026data.tick_data {\n        println!(\"Tick 数量: {}\", tick_data.data.len());\n        \n        // 获取最新 Tick\n        if let Some(last_tick) = tick_data.data.last() {\n            println!(\"最新成交价: {}\", last_tick.last_price);\n            println!(\"成交量: {}\", last_tick.volume);\n            println!(\"持仓量: {}\", last_tick.open_interest);\n        }\n    }\n}).await;\n\nsub.start().await?;\n```\n\n#### 历史数据获取\n\n支持两种方式获取历史数据：\n\n```rust\nuse chrono::Utc;\n\n// 方式 1: 使用 left_kline_id（精确定位）\n// 从指定 K线 ID 开始获取\nlet sub = series_api.kline_history(\n    \"SHFE.au2602\",              // 合约代码\n    Duration::from_secs(60),    // K线周期\n    100,                        // 数据条数\n    1234567890                  // 起始 K线 ID\n).await?;\n\n// 方式 2: 使用 focus_datetime（时间定位）\n// 从指定时间点开始获取\nlet focus_time = Utc::now() - chrono::Duration::days(7);  // 7天前\nlet sub = series_api.kline_history_with_focus(\n    \"SHFE.au2602\",              // 合约代码\n    Duration::from_secs(60),    // K线周期\n    100,                        // 数据条数\n    focus_time,                 // 焦点时间\n    50                          // 焦点位置（0-100，50表示居中）\n).await?;\n\n// 注册回调处理历史数据\nsub.on_update(|data, info| {\n    if let Some(kline_data) = \u0026data.single {\n        println!(\"获取到 {} 根历史 K线\", kline_data.data.len());\n        \n        if info.chart_ready {\n            println!(\"历史数据加载完成\");\n        }\n    }\n}).await;\n\nsub.start().await?;\n```\n\n### 2. 交易功能\n\n#### 创建交易会话\n\nTradeSession 使用延迟连接模式，避免消息丢失：\n\n```rust\n// 创建交易会话（不自动连接）\nlet session = client.create_trade_session(\n    \"simnow\",      // 期货公司代码（simnow 为模拟账户）\n    \"user_id\",     // 账号\n    \"password\"     // 密码\n).await?;\n\n// 步骤 1: 先注册所有回调（重要：避免丢失初始数据）\n\n// 监听账户变化\nsession.on_account(|account| {\n    println!(\"账户余额: {}\", account.balance);\n    println!(\"可用资金: {}\", account.available);\n    println!(\"持仓盈亏: {}\", account.position_profit);\n}).await;\n\n// 监听持仓变化\nsession.on_position(|position| {\n    println!(\"合约: {}\", position.instrument_id);\n    println!(\"多头持仓: {}\", position.volume_long);\n    println!(\"空头持仓: {}\", position.volume_short);\n}).await;\n\n// 监听委托单变化\nsession.on_order(|order| {\n    println!(\"委托单: {} - 状态: {}\", order.order_id, order.status);\n}).await;\n\n// 监听成交记录\nsession.on_trade(|trade| {\n    println!(\"成交: {} - 价格: {}\", trade.trade_id, trade.price);\n}).await;\n\n// 步骤 2: 最后连接服务器\nsession.connect().await?;\n```\n\n#### 下单操作\n\n支持限价单、市价单等多种下单方式：\n\n```rust\n// 限价开多仓\nlet order_id = session.insert_order(\n    \"SHFE.au2602\",    // 合约代码\n    \"BUY\",            // 买卖方向：BUY/SELL\n    \"OPEN\",           // 开平标志：OPEN/CLOSE/CLOSETODAY\n    1,                // 手数\n    Some(500.0)       // 限价（None 表示市价单）\n).await?;\n\nprintln!(\"下单成功，委托单号: {}\", order_id);\n\n// 市价平仓\nlet order_id = session.insert_order(\n    \"SHFE.au2602\",\n    \"SELL\",\n    \"CLOSE\",\n    1,\n    None              // 市价单\n).await?;\n\n// 撤单\nsession.cancel_order(\u0026order_id).await?;\nprintln!(\"撤单成功\");\n```\n\n#### 查询交易数据\n\n提供完整的交易数据查询接口：\n\n```rust\n// 查询账户信息\nlet account = session.get_account(\"CNY\").await?;\nprintln!(\"账户余额: {}\", account.balance);\nprintln!(\"可用资金: {}\", account.available);\nprintln!(\"冻结保证金: {}\", account.frozen_margin);\nprintln!(\"持仓盈亏: {}\", account.position_profit);\n\n// 查询持仓\nlet position = session.get_position(\"SHFE.au2602\").await?;\nprintln!(\"多头持仓: {}\", position.volume_long);\nprintln!(\"空头持仓: {}\", position.volume_short);\nprintln!(\"持仓均价: {}\", position.open_price_long);\n\n// 查询委托单\nlet order = session.get_order(\u0026order_id).await?;\nprintln!(\"委托状态: {}\", order.status);\nprintln!(\"已成交: {} / {}\", order.volume_orign - order.volume_left, order.volume_orign);\n\n// 查询成交记录\nlet trade = session.get_trade(\u0026trade_id).await?;\nprintln!(\"成交价格: {}\", trade.price);\nprintln!(\"成交数量: {}\", trade.volume);\n```\n\n### 3. 数据管理器（DataManager）\n\nDataManager 是底层数据存储，实现了 DIFF 协议：\n\n#### 直接访问数据\n\n```rust\n// 获取 DataManager 实例（需要先初始化 client）\nlet dm = client.dm.clone();\n\n// 获取 Quote 数据\nlet quote = dm.get_quote_data(\"SHFE.au2602\")?;\nprintln!(\"最新价: {}\", quote.last_price);\nprintln!(\"买一价: {}\", quote.bid_price1);\nprintln!(\"卖一价: {}\", quote.ask_price1);\n\n// 获取 K线数据\n// 参数：合约代码, 周期(纳秒), 数量, right_id(-1表示最新)\nlet klines = dm.get_klines_data(\"SHFE.au2602\", 60_000_000_000, 100, -1)?;\nprintln!(\"K线数量: {}\", klines.data.len());\n\n// 路径访问（灵活访问任意数据）\nif let Some(data) = dm.get_by_path(\u0026[\"quotes\", \"SHFE.au2602\"]) {\n    println!(\"原始数据: {:?}\", data);\n}\n\n// 检查数据是否在最近一次更新中发生变化\nif dm.is_changing(\u0026[\"quotes\", \"SHFE.au2602\"]) {\n    println!(\"数据在最近一次更新中发生了变化\");\n}\n\n// 获取当前版本号\nlet epoch = dm.get_epoch();\nprintln!(\"当前数据版本: {}\", epoch);\n```\n\n#### 路径监听（Watch/UnWatch）\n\n精确监听指定路径的数据变化：\n\n```rust\n// 监听指定路径的数据变化\nlet rx = dm.watch(vec![\n    \"quotes\".to_string(), \n    \"SHFE.au2602\".to_string()\n]);\n\n// 在另一个任务中接收更新\ntokio::spawn(async move {\n    while let Ok(data) = rx.recv().await {\n        println!(\"路径数据更新: {:?}\", data);\n    }\n});\n\n// 监听多个路径\nlet symbols = vec![\"SHFE.au2602\", \"SHFE.ag2512\", \"DCE.m2505\"];\nfor symbol in \u0026symbols {\n    let rx = dm.watch(vec![\"quotes\".to_string(), symbol.to_string()]);\n    // 处理每个路径的更新\n}\n\n// 取消监听\ndm.unwatch(\u0026vec![\n    \"quotes\".to_string(),\n    \"SHFE.au2602\".to_string()\n])?;\n```\n\n#### 数据更新回调\n\n注册全局数据更新回调：\n\n```rust\n// 注册数据更新回调（每次数据更新时触发）\ndm.on_data(|| {\n    println!(\"数据已更新，当前版本: {}\", dm.get_epoch());\n    // 可以在这里触发其他操作\n});\n\n// 可以注册多个回调\ndm.on_data(|| {\n    // 回调 1\n});\n\ndm.on_data(|| {\n    // 回调 2\n});\n```\n\n### 4. Polars DataFrame 集成（可选功能）\n\n启用 `polars` 功能后，可以将 K线和 Tick 数据转换为 Polars DataFrame 进行高性能分析。\n\n#### 使用 KlineBuffer 进行实时数据分析\n\n```rust\nuse tqsdk_rs::{Client, ClientConfig, KlineBuffer};\nuse std::time::Duration;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    let client = Client::new(\"username\", \"password\", ClientConfig::default()).await?;\n    client.init_market().await?;\n\n    let series_api = client.get_series_api();\n    let subscription = series_api\n        .kline(\"SHFE.au2506\", Duration::from_secs(60), 100)\n        .await?;\n\n    // 创建 K线缓冲区\n    let mut buffer = KlineBuffer::new();\n\n    subscription.on_update(move |series_data, update_info| {\n        if let Some(kline_data) = \u0026series_data.single {\n            if let Some(last_kline) = kline_data.data.last() {\n                if update_info.has_new_bar {\n                    // 新 K线，追加\n                    buffer.push(last_kline);\n                } else if update_info.has_bar_update {\n                    // 更新最后一根\n                    buffer.update_last(last_kline);\n                }\n            }\n\n            // 转换为 DataFrame 进行分析\n            if let Ok(df) = buffer.to_dataframe() {\n                println!(\"DataFrame shape: {:?}\", df.shape());\n                \n                // 计算统计指标\n                if let Ok(close) = df.column(\"close\")?.f64() {\n                    let mean = close.mean().unwrap_or(0.0);\n                    let std = close.std(1).unwrap_or(0.0);\n                    println!(\"收盘价均值: {:.2}, 标准差: {:.2}\", mean, std);\n                }\n\n                // 获取最后 10 根 K线\n                if let Ok(tail_df) = buffer.tail(10) {\n                    println!(\"最后 10 根K线:\\n{}\", tail_df);\n                }\n            }\n        }\n    }).await;\n\n    subscription.start().await?;\n    \n    // 等待数据...\n    tokio::time::sleep(Duration::from_secs(60)).await;\n    \n    Ok(())\n}\n```\n\n#### 直接转换 SeriesData\n\n```rust\n// 单合约 K线\nsubscription.on_update(|series_data, _| {\n    // 转换为 DataFrame\n    if let Ok(df) = series_data.to_dataframe() {\n        println!(\"K线数据:\\n{}\", df);\n    }\n}).await;\n\n// 多合约 K线（长表格式）\nmulti_subscription.on_update(|series_data, _| {\n    if let Ok(long_df) = series_data.to_dataframe() {\n        println!(\"长表格式:\\n{}\", long_df);\n    }\n}).await;\n\n// 多合约 K线（宽表格式）\nmulti_subscription.on_update(|series_data, _| {\n    if let Ok(wide_df) = series_data.to_wide_dataframe() {\n        println!(\"宽表格式:\\n{}\", wide_df);\n    }\n}).await;\n```\n\n#### 技术指标计算\n\n```rust\nuse polars::prelude::*;\n\n// 计算移动平均线\nfn calculate_ma(df: \u0026DataFrame, window: usize) -\u003e Result\u003cSeries, PolarsError\u003e {\n    let close = df.column(\"close\")?.f64()?;\n    let ma = close.rolling_mean(RollingOptionsFixedWindow {\n        window_size: window,\n        min_periods: window,\n        ..Default::default()\n    })?;\n    Ok(ma.into_series())\n}\n\n// 在回调中使用\nsubscription.on_update(move |series_data, _| {\n    if let Ok(df) = buffer.to_dataframe() {\n        // 计算 MA5 和 MA10\n        if let Ok(ma5) = calculate_ma(\u0026df, 5) {\n            if let Ok(ma10) = calculate_ma(\u0026df, 10) {\n                println!(\"MA5: {:.2}, MA10: {:.2}\", \n                    ma5.tail(Some(1)), \n                    ma10.tail(Some(1)));\n            }\n        }\n    }\n}).await;\n```\n\n**详细文档**: 查看 [POLARS_INTEGRATION.md](./POLARS_INTEGRATION.md) 了解完整的 Polars 集成指南。\n\n### 5. 认证管理\n\n#### 切换账号（运行时）\n\n支持在运行时动态切换账号：\n\n```rust\nuse tqsdk_rs::auth::TqAuth;\n\n// 创建新的认证器\nlet mut new_auth = TqAuth::new(\"user2\".to_string(), \"pass2\".to_string());\nnew_auth.login().await?;\n\n// 切换认证器\nclient.set_auth(new_auth).await;\n\n// 重新初始化行情（使用新账号）\nclient.init_market().await?;\n```\n\n#### 权限检查\n\n在订阅前自动检查权限，也可以手动检查：\n\n```rust\n// 获取认证器\nlet auth = client.get_auth().await;\n\n// 检查功能权限\nif auth.has_feature(\"futr\") {\n    println!(\"有期货权限\");\n}\n\nif auth.has_feature(\"sec\") {\n    println!(\"有股票权限\");\n}\n\n// 检查行情权限（多个合约）\nmatch auth.has_md_grants(\u0026[\"SHFE.au2602\", \"SHFE.ag2512\"]) {\n    Ok(_) =\u003e println!(\"有行情权限\"),\n    Err(e) =\u003e println!(\"权限不足: {}\", e),\n}\n\n// 检查交易权限（单个合约）\nmatch auth.has_td_grants(\"SHFE.au2602\") {\n    Ok(_) =\u003e println!(\"有交易权限\"),\n    Err(e) =\u003e println!(\"权限不足: {}\", e),\n}\n\n// 获取认证信息\nprintln!(\"Auth ID: {}\", auth.get_auth_id());\nprintln!(\"Access Token: {}\", auth.get_access_token());\n```\n\n## 示例程序\n\n项目提供了完整的示例程序，涵盖所有核心功能：\n\n### 运行示例\n\n```bash\n# 行情订阅示例（Quote、K线、Tick）\ncargo run --example quote\n\n# 历史数据获取示例\ncargo run --example history\n\n# 交易操作示例（下单、撤单、查询）\ncargo run --example trade\n\n# DataManager 高级功能示例\ncargo run --example datamanager\n\n# 认证功能示例\ncargo run --example auth_demo\n\n# 账号切换示例\ncargo run --example auth_switch\n\n# ClientBuilder 使用示例\ncargo run --example client_builder\n\n# Polars DataFrame 集成示例（需要启用 polars 功能）\ncargo run --example polars_demo --features polars\n\n# 自定义日志 Layer 组合示例\ncargo run --example custom_logger\n```\n\n### 环境变量配置\n\n运行示例前需要设置环境变量：\n\n```bash\n# 天勤账号（必需）\nexport SHINNYTECH_ID=\"your_username\"\nexport SHINNYTECH_PW=\"your_password\"\n\n# SimNow 模拟账号（仅交易示例需要）\nexport SIMNOW_USER_0=\"your_simnow_user\"\nexport SIMNOW_PASS_0=\"your_simnow_pass\"\n```\n\n### 示例说明\n\n| 示例文件 | 功能说明 | 适用场景 |\n|---------|---------|---------|\n| quote.rs | Quote、K线、Tick 订阅 | 学习行情订阅 |\n| history.rs | 历史数据获取 | 回测、数据分析 |\n| trade.rs | 下单、撤单、查询 | 实盘交易 |\n| datamanager.rs | 数据管理器高级用法 | 自定义数据处理 |\n| auth_demo.rs | 认证和权限检查 | 了解认证机制 |\n| auth_switch.rs | 运行时切换账号 | 多账号管理 |\n| client_builder.rs | ClientBuilder 用法 | 灵活配置客户端 |\n| **polars_demo.rs** | **Polars DataFrame 集成** | **数据分析和技术指标** |\n| **custom_logger.rs** | **自定义日志 Layer** | **日志系统集成** |\n\n## 技术栈\n\n| 依赖 | 版本 | 用途 |\n|------|------|------|\n| tokio | 1.48 | 异步运行时 |\n| yawc | 0.2.7 | WebSocket 客户端（支持 deflate 压缩） |\n| reqwest | 0.12 | HTTP 客户端 |\n| serde | 1.0 | 序列化/反序列化 |\n| serde_json | 1.0 | JSON 处理 |\n| jsonwebtoken | 10.2 | JWT 认证 |\n| thiserror | 2.0 | 错误处理 |\n| tracing | 0.1 | 结构化日志 |\n| chrono | 0.4 | 时间处理 |\n| async-channel | 2.3 | 异步通道 |\n| polars | 0.44 | 数据分析（可选） |\n\n## 项目结构\n\n```\ntqsdk-rs/\n├── src/\n│   ├── lib.rs              # 库入口和模块导出\n│   ├── client.rs           # 客户端和 ClientBuilder\n│   ├── auth.rs             # 认证模块（TqAuth）\n│   ├── websocket.rs        # WebSocket 封装\n│   ├── datamanager.rs      # 数据管理器（DIFF 协议）\n│   ├── types.rs            # 数据结构定义（90+ 字段）\n│   ├── quote.rs            # Quote 订阅\n│   ├── series.rs           # Series API（K线/Tick）\n│   ├── trade_session.rs    # 交易会话\n│   ├── utils.rs            # 工具函数\n│   ├── logger.rs           # 日志系统\n│   └── errors.rs           # 错误类型\n├── examples/\n│   ├── quote.rs            # 行情订阅示例\n│   ├── history.rs          # 历史数据示例\n│   ├── trade.rs            # 交易示例\n│   ├── datamanager.rs      # DataManager 示例\n│   ├── auth_demo.rs        # 认证示例\n│   ├── auth_switch.rs      # 切换账号示例\n│   └── client_builder.rs   # ClientBuilder 示例\n└── README.md\n```\n\n## 核心设计\n\n### DIFF 协议实现\n\n完整实现天勤 DIFF 协议，这是本项目的核心技术亮点：\n\n- **递归合并** - 支持嵌套对象的增量更新，高效处理复杂数据结构\n- **ViewWidth 限制** - 使用二分查找优化大数据集，避免内存溢出\n- **Binding 对齐** - 多合约 K线时间对齐，支持跨品种分析\n- **版本追踪** - Epoch 机制追踪每次数据变化，精确判断更新\n- **路径监听** - Watch/UnWatch 精确监听指定路径的数据变化\n- **NaN 处理** - 正确处理 NaN 和特殊值\n\n### 类型安全\n\nRust 的类型系统带来的优势：\n\n- **90+ 字段的强类型定义** - Quote、Kline、Tick、Account 等完整定义\n- **编译时类型检查** - 消除大量运行时错误\n- **泛型和 trait 抽象** - Authenticator trait 支持自定义认证\n- **Result 类型统一错误处理** - 强制错误处理，避免遗漏\n\n### 并发安全\n\n多线程环境下的安全保证：\n\n- **Arc + RwLock** - 保证线程安全的共享数据访问\n- **async-channel** - 异步通信，支持多生产者多消费者\n- **AtomicI64** - 原子操作优化性能（Epoch 版本号）\n- **无数据竞争** - 编译时保证，无需运行时检查\n\n### 灵活接口\n\n支持三种数据订阅方式，满足不同场景需求：\n\n1. **Channel** - 使用 async-channel，支持多个订阅者，适合多任务处理\n2. **Callback** - 注册回调函数，异步触发，适合事件驱动（**零拷贝优化**）\n3. **Stream** - 使用 async-stream，支持流式处理，适合函数式编程\n\n### 零拷贝回调设计\n\n所有回调函数使用 `Arc\u003cT\u003e` 参数，避免数据深拷贝：\n\n```rust\n// Quote 回调：Arc\u003cQuote\u003e\nquote_sub.on_quote(|quote| {\n    // quote 是 Arc\u003cQuote\u003e，多个回调共享同一份数据\n    println!(\"最新价: {}\", quote.last_price);\n}).await;\n\n// Series 回调：Arc\u003cSeriesData\u003e, Arc\u003cUpdateInfo\u003e\nseries_sub.on_update(|data, info| {\n    // data 和 info 都是 Arc 包装的，零拷贝！\n    if info.has_new_bar {\n        println!(\"新K线\");\n    }\n}).await;\n```\n\n**性能优势**:\n- 多回调场景下内存节省 50-90%\n- 克隆性能提升 500-1000x（只克隆 8 字节指针）\n- 线程安全的数据共享\n\n**详细文档**: 查看 [ARC_OPTIMIZATION.md](./tqsdk-rs/ARC_OPTIMIZATION.md) 和 [ARC_QUOTE_OPTIMIZATION.md](./tqsdk-rs/ARC_QUOTE_OPTIMIZATION.md)\n\n### 零成本抽象\n\nRust 的零成本抽象理念：\n\n- **编译时优化** - 泛型和 trait 在编译时展开，无运行时开销\n- **无 GC 压力** - 所有权系统管理内存，无垃圾回收停顿\n- **内联优化** - 小函数自动内联，减少函数调用开销\n\n## 最佳实践\n\n### 1. 延迟启动模式（强烈推荐）\n\n避免竞态条件，确保不丢失初始数据：\n\n```rust\n// 推荐：先注册回调，再启动\nlet sub = series_api.kline(\"SHFE.au2602\", Duration::from_secs(60), 100).await?;\n\n// 先注册所有回调\nsub.on_update(|data, info| {\n    // 处理数据更新\n}).await;\n\nsub.on_new_bar(|data| {\n    // 处理新 K线\n}).await;\n\n// 最后启动订阅\nsub.start().await?;\n\n// 不推荐：启动后再注册回调（可能丢失初始数据）\n// sub.start().await?;\n// sub.on_update(...).await;  // 可能错过初始数据\n```\n\n### 2. 错误处理\n\n使用 Result 类型和模式匹配处理错误：\n\n```rust\nuse tqsdk_rs::{Result, TqError};\n\nasync fn my_function() -\u003e Result\u003c()\u003e {\n    // 创建客户端\n    let mut client = Client::new(\"user\", \"pass\", ClientConfig::default()).await?;\n    \n    // 订阅行情（带错误处理）\n    match client.subscribe_quote(\u0026[\"SHFE.au2602\"]).await {\n        Ok(sub) =\u003e {\n            println!(\"订阅成功\");\n            sub.start().await?;\n        }\n        Err(TqError::PermissionDenied(msg)) =\u003e {\n            eprintln!(\"权限不足: {}\", msg);\n            return Err(TqError::PermissionDenied(msg));\n        }\n        Err(TqError::NetworkError(msg)) =\u003e {\n            eprintln!(\"网络错误: {}\", msg);\n            return Err(TqError::NetworkError(msg));\n        }\n        Err(e) =\u003e {\n            eprintln!(\"其他错误: {}\", e);\n            return Err(e);\n        }\n    }\n    \n    Ok(())\n}\n```\n\n### 3. 资源清理\n\n及时释放资源，避免内存泄漏：\n\n```rust\n// 使用完毕后关闭资源\nquote_sub.close().await?;\nseries_sub.close().await?;\nsession.close().await?;\nclient.close().await?;\n\n// 或使用 RAII 模式（推荐）\n{\n    let mut client = Client::new(\"user\", \"pass\", ClientConfig::default()).await?;\n    // 使用 client\n    // 离开作用域时自动清理\n}\n```\n\n### 4. 日志配置\n\n合理配置日志级别，便于调试。支持本地时区显示和 Layer 组合：\n\n```rust\nuse tqsdk_rs::{init_logger, create_logger_layer};\n\n// 方式 1: 快速初始化（简单场景）\ninit_logger(\"debug\", false);  // 级别: trace, debug, info, warn, error\n\n// 方式 2: 使用 ClientBuilder（推荐）\nlet client = Client::builder(\"user\", \"pass\")\n    .log_level(\"debug\")      // 开发时使用 debug\n    .build()\n    .await?;\n\n// 方式 3: Layer 组合（高级场景）\nuse tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};\n\nlet tqsdk_layer = create_logger_layer(\"debug\", false);\n// 可以与业务层的其他 Layer 组合\ntracing_subscriber::registry()\n    .with(tqsdk_layer)\n    // .with(your_custom_layer)\n    .init();\n\n// 生产环境建议使用 info 或 warn\nlet client = Client::builder(\"user\", \"pass\")\n    .log_level(\"info\")\n    .build()\n    .await?;\n```\n\n**日志特性**:\n- 自动使用本地时区（如 `2024-11-26T10:30:45.123+08:00`）\n- 支持 Layer 组合，可与业务日志集成\n- 详细的源文件和行号信息\n\n**详细文档**: 查看 [LOGGER_GUIDE.md](./tqsdk-rs/LOGGER_GUIDE.md) 了解完整的日志配置指南。\n\n### 5. 合约代码格式\n\n使用完整的合约代码格式：\n\n```rust\n// 正确：使用完整格式\n\"SHFE.au2602\"   // 上期所黄金\n\"DCE.m2505\"     // 大商所豆粕\n\"CZCE.SR505\"    // 郑商所白糖\n\n// 错误：不完整的格式\n\"au2602\"        // 缺少交易所\n\"SHFE.au\"       // 缺少月份\n```\n\n### 6. ViewWidth 设置\n\n合理设置 ViewWidth，避免内存浪费：\n\n```rust\n// 实时监控：较小的 ViewWidth\nlet sub = series_api.kline(\"SHFE.au2602\", Duration::from_secs(60), 100).await?;\n\n// 数据分析：较大的 ViewWidth\nlet sub = series_api.kline(\"SHFE.au2602\", Duration::from_secs(60), 5000).await?;\n\n// 注意：最大 10000，超过会自动调整\nlet sub = series_api.kline(\"SHFE.au2602\", Duration::from_secs(60), 15000).await?;\n// 实际会被调整为 10000\n```\n\n## 与 Go 版本对比\n\n| 维度 | Go 版本 | Rust 版本 | 说明 |\n|------|---------|-----------|------|\n| 代码行数 | ~8,000 | ~4,900 | Rust 更简洁 |\n| 类型安全 | 弱类型 | 强类型 | Rust 优势 |\n| 内存安全 | GC | 所有权 | Rust 优势 |\n| 并发安全 | 运行时检查 | 编译时检查 | Rust 优势 |\n| 性能 | 高 | 更高 | Rust 优势 |\n| 零拷贝 | 部分支持 | **Arc 优化** | **Rust 优势** |\n| 错误处理 | error | Result\u003cT\u003e | Rust 优势 |\n| 数据分析 | 需第三方库 | **Polars 集成** | **Rust 优势** |\n| 学习曲线 | 低 | 中高 | Go 优势 |\n| 开发速度 | 快 | 中 | Go 略优 |\n\n## 注意事项\n\n### 重要提示\n\n1. **合约代码格式** - 必须使用完整的合约代码格式，如 `SHFE.au2602`（交易所.合约代码）\n2. **延迟启动模式** - 强烈推荐使用延迟启动模式，先注册回调再调用 `start()`，避免竞态条件\n3. **资源释放** - 使用完毕后记得调用 `close()` 释放资源，避免连接泄漏\n4. **权限检查** - 订阅前会自动检查权限，确保账号有相应的行情或交易权限\n5. **ViewWidth 限制** - 最大值为 10000，超过会自动调整为 10000\n\n### 常见问题\n\n**Q: 为什么收不到数据？**\n- 检查是否调用了 `start()` 方法\n- 确认回调是在 `start()` 之前注册\n- 检查合约代码格式是否正确\n- 确认账号有相应权限\n\n**Q: 如何处理网络断开？**\n- WebSocket 会自动重连\n- TradeSession 支持自动重连\n- 重连后会自动恢复订阅\n\n**Q: 多合约订阅如何优化？**\n- 使用单个 `subscribe_quote()` 订阅多个合约\n- 避免为每个合约创建单独的订阅\n- 合理设置 ViewWidth，避免内存浪费\n\n**Q: 如何调试？**\n- 设置日志级别为 `debug` 或 `trace`\n- 使用 `RUST_LOG` 环境变量：`RUST_LOG=tqsdk_rs=debug cargo run`\n- 检查 WebSocket 连接状态\n- 日志自动显示本地时区，便于调试\n\n**Q: 如何集成业务层日志？**\n- 使用 `create_logger_layer()` 获取 tqsdk-rs 的 Layer\n- 与业务层的其他 Layer 组合\n- 详见 [LOGGER_GUIDE.md](./tqsdk-rs/LOGGER_GUIDE.md)\n\n\n### 报告问题\n\n如果发现 Bug 或有功能建议，请提交 [GitHub Issue](https://github.com/pseudocodes/tqsdk-rs/issues)。\n\n## 许可证\n\n本项目采用 Apache License 2.0 许可证 - 详见 [LICENSE](LICENSE) 文件\n\n\n## 相关项目\n\n- [tqsdk-go](https://github.com/pseudocodes/tqsdk-go) - Go 语言版本\n- [tqsdk-python](https://github.com/shinnytech/tqsdk-python) - Python 官方版本\n\n## 免责声明\n\n**重要提示：本项目仅供学习和研究使用。**\n\n本项目明确拒绝对产品做任何明示或暗示的担保。使用本项目进行交易和投资的一切风险由使用者自行承担。期货交易具有高风险，可能导致本金全部损失，请谨慎投资。\n\n作者和贡献者不对使用本软件造成的任何直接或间接损失承担责任。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpseudocodes%2Ftqsdk-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpseudocodes%2Ftqsdk-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpseudocodes%2Ftqsdk-rs/lists"}