Tqsdk.net
1.0.0
dotnet add package Tqsdk.net --version 1.0.0
NuGet\Install-Package Tqsdk.net -Version 1.0.0
<PackageReference Include="Tqsdk.net" Version="1.0.0" />
<PackageVersion Include="Tqsdk.net" Version="1.0.0" />
<PackageReference Include="Tqsdk.net" />
paket add Tqsdk.net --version 1.0.0
#r "nuget: Tqsdk.net, 1.0.0"
#:package Tqsdk.net@1.0.0
#addin nuget:?package=Tqsdk.net&version=1.0.0
#tool nuget:?package=Tqsdk.net&version=1.0.0
Tqsdk.net
天勤 TqSdk(tqsdk-python)的 C# 复刻。API 命名、用法与语义对齐 tqsdk:
类名/方法名一一对应(TqApi、TqAuth、GetQuote、WaitUpdate、IsChanging…),
字段名沿用 tqsdk 的 snake_case(last_price、bid_price1…),未收到行情前价格为 NaN、数量为 0。
当前进度:行情 + K 线 + 历史 K 线 + 模拟交易 + 指标算法模块 均已实现并通过真实服务器/数值比对验证。
- 真实连接天勤行情网关(free-openmd,permessage-deflate 已自行实现);
- 实时 K 线序列(
GetKlineSerial,set_chart/klines协议); - 历史 K 线获取(
GetKlineHistory,独立实现,参考 tqsdk-rs 的kline_history); TqSim本地模拟账户:撮合、保证金、手续费、浮动/平仓盈亏、持仓/账户计算;insert_order、get_account/get_position/get_order/get_trade、TargetPosTask;- 断线指数退避重连(1s → 2s → 4s … 上限 30s);
- 指标算法模块(
TqTa/TqTaFunc):复刻tqsdk.ta约 80 个指标 +tqsdk.tafunc约 60 个底层算子,数值与真实 tqsdk 逐元素比对 87/87 一致。
目录结构
csharp/
├── Tqsdk.slnx 解决方案
├── Tqsdk.net/ 类库(AssemblyName: Tqsdk.net,命名空间: Tqsdk)
│ ├── TqApi.cs 核心 API:连接、订阅、wait_update/is_changing、diff 合并、重连
│ ├── TqAuth.cs 快期账户认证(登录 + 名称服务)
│ ├── TqEntity.cs 业务对象基类(snake_case 字段存储 + 变更追踪)
│ ├── Quote.cs 盘口行情(字段对齐 tqsdk objs.Quote)
│ ├── Kline.cs / KlineSerial.cs K 线单根与实时序列(支持 iloc[-1] 读取最新一根)
│ ├── KlineHistory.cs 历史 K 线获取器(独立于实时 K 线,参考 tqsdk-rs)
│ ├── Account.cs / Position.cs / Order.cs / Trade.cs 账户/持仓/委托/成交实体
│ ├── TqSim.cs 本地模拟交易引擎(撮合/保证金/盈亏)
│ ├── TargetPosTask.cs 目标持仓调仓任务(对齐 tqsdk.TargetPosTask)
│ ├── TqWebSocketClient.cs 最小 WebSocket 客户端(支持 permessage-deflate)
│ ├── PermessageDeflateDecoder.cs permessage-deflate 解压(上下文接管)
│ ├── TqNotify.cs 通知(连接建立/断开/服务器消息)
│ └── TqException.cs 异常类型
└── TqsdkConsole/ 控制台测试程序(镜像 Python app.py 用法)
构建与运行
cd csharp
dotnet build
# 设置快期账户凭证(环境变量,不写入代码)
# PowerShell:
$env:TQ_USER="lianshan"; $env:TQ_PASSWORD="你的密码"
# bash:
TQ_USER=lianshan TQ_PASSWORD=你的密码 dotnet run --project TqsdkConsole
控制台输出:订阅 DCE.v2701,last_price 变化时打印时间/最新价/五档/成交量,
并演示 WaitUpdate 超时返回 False、IsChanging 不带字段名等语义。
用法(与 tqsdk 相同)
// 行情 + 本地模拟交易(账户初始资金 100 万)
var sim = new TqSim(1_000_000);
using var api = new TqApi(new TqAuth("快期账户", "密码"), account: sim);
var quote = api.GetQuote("DCE.v2701");
Console.WriteLine(quote.LastPrice); // NaN(未收到行情前,与 tqsdk 一致)
// K 线序列:.Last 等价于 pandas 的 iloc[-1]
var kline = api.GetKlineSerial("DCE.v2701", 60, 10);
// 历史 K 线(独立于实时订阅):一次性获取最近 300 根历史分钟线,参考 tqsdk-rs 的 kline_history
var hist = api.GetKlineHistory("DCE.v2701", 60, 300);
while (api.WaitUpdate())
if (hist.Ready)
{
Console.WriteLine($"历史 {hist.Count} 根,最早 {hist.First?.Datetime} 最新 {hist.Last?.Datetime},还有更早数据: {hist.MoreData}");
break;
}
// 也可按 K 线 id 或时间定位更早的窗口:
// var old = api.GetKlineHistory("DCE.v2701", 60, 500, leftKlineId: hist.LeftId - 500);
// var byTime = api.GetKlineHistory("DCE.v2701", 60, 300, focusDateTime: new DateTime(2026, 9, 1));
// 模拟下单:市价开 3 手
var order = api.InsertOrder("DCE.v2701", "BUY", "OPEN", 3);
var pos = api.GetPosition("DCE.v2701");
var acc = api.GetAccount();
while (api.WaitUpdate(TimeSpan.FromSeconds(3)))
{
if (api.IsChanging(quote, "last_price"))
Console.WriteLine($"{quote.Datetime} {quote.LastPrice}");
}
api.Close(); // 或 using 自动释放
API 对照表
| tqsdk (Python) | Tqsdk.net (C#) | 状态 |
|---|---|---|
TqApi(auth=TqAuth(user, pwd)) |
new TqApi(new TqAuth(user, pwd), account: sim) |
✅ |
api.get_quote(symbol) |
api.GetQuote(symbol) |
✅ |
api.get_quote_list(symbols) |
api.GetQuoteList(symbols) |
✅ |
api.wait_update() / wait_update(deadline) |
api.WaitUpdate() / WaitUpdate(TimeSpan) |
✅ |
api.is_changing(obj, "key") |
api.IsChanging(obj, "key") |
✅ |
api.close() |
api.Close() / using |
✅ |
TqAuth(user, pwd) |
new TqAuth(user, pwd) |
✅ |
Quote(全部字段,初值 NaN/0/"") |
Quote(同名字段) |
✅ |
api.get_kline_serial(symbol, dur, length) |
api.GetKlineSerial(symbol, dur, length) |
✅ |
tqsdk-rs: kline_history(...) / kline_history_with_focus(...) |
api.GetKlineHistory(symbol, dur, count, leftKlineId?, focusDateTime?, focusPosition?) |
✅ |
api.get_account() / TqSim |
api.GetAccount() / new TqSim(...) |
✅ |
api.get_position(symbol) |
api.GetPosition(symbol) |
✅ |
api.insert_order(...) |
api.InsertOrder(...) |
✅ |
TargetPosTask(api, symbol, target) |
new TargetPosTask(api, symbol, target, sim) |
✅ |
api.get_order / get_trade |
api.GetOrder / GetTrade |
✅ |
api.get_instrument_list() |
api.GetInstrumentList() |
✅(free 网关返回已订阅合约) |
Quote(全部字段,初值 NaN/0/"") |
Tick 实体(逐笔行情,字段对齐 tqsdk objs.Tick) |
✅ |
api.get_tick_serial(symbol, data_length) |
api.GetTickSerial(symbol, data_length) |
✅(免费账户可用,盘中返回最近若干笔) |
api.get_kline_data_series(symbol, dur, start, end) |
api.GetKlineDataSeries(symbol, dur, startDt, endDt) |
✅(免费账户取近期区间;更深历史需专业版) |
api.get_tick_data_series(symbol, start, end) |
api.GetTickDataSeries(symbol, startDt, endDt) |
⚠️ 仅限专业版(免费网关不提供历史 Tick,只返回近期留存) |
api.cancel_order(order_id) |
api.CancelOrder(orderId) |
✅(需 TqSim) |
新增:Tick 序列与按时间段序列
GetTickSerial(symbol, dataLength=200)(对应 tqsdkget_tick_serial):通过set_chart(duration=0)订阅逐笔行情, 数据落在ticks.<symbol>.data.<id>,维护最近dataLength笔的滚动窗口。返回TickSerial,用.Last读最新一笔、支持负索引[-1]。 免费账户可用(盘中返回最近若干笔;收盘后返回当日最后会话的留存 Tick)。GetKlineDataSeries(symbol, durationSeconds, startDt, endDt)(对应 tqsdkget_kline_data_series,官方标“专业版”): 底层走set_chart(focus_datetime=起点, focus_position=0, view_width由时间跨度推算),结果按[start, end]过滤。 免费账户可获取近期区间(分钟线约 2 个月、日线约 9 个月,见GetKlineHistory实测);更深历史需专业版账户。 返回KlineDataSeries,Ready/Count/Bars/First/Last均只在[start, end]内统计。GetTickDataSeries(symbol, startDt, endDt)(对应 tqsdkget_tick_data_series,⚠️ 仅限专业版): 协议同GetTickDataSeries(set_chartduration=0+focus_datetime),数据落在ticks.<symbol>.data并按[start, end]过滤。 免费行情网关不提供历史 Tick,免费账户调用只会返回近期留存 Tick(无法取到请求的跨历史区间),必须购买天勤专业版才能获取跨历史 Tick。说明:tqsdk 官方把
get_kline_data_series/get_tick_data_series用tq_dl特征硬门槛限制为专业版(客户端检查,免费账户直接抛“仅限专业版”; 本库不复制该客户端门槛,直接走底层set_chart,因此在免费账户上也能拉到网关“愿意给”的近期数据,更深历史仍取决于账户权限。CancelOrder(orderId)(对应 tqsdkcancel_order):需构造TqApi时挂接TqSim;仅对仍ALIVE的委托生效,撤单后状态置FINISHED并触发账户重算。
指标算法模块(TqTa / TqTaFunc)
复刻 tqsdk.ta(约 80 个常用技术指标)与 tqsdk.tafunc(约 60 个底层系列算子)。
输入 K 线序列(IReadOnlyList<Kline>,通常来自 KlineSerial.Bars),输出 TaDataFrame(按列名取序列)或 double[]。
var bars = api.GetKlineSerial("DCE.v2701", 60, 300).Bars; // IReadOnlyList<Kline>
// 底层算子(TqTaFunc):单/双序列运算,参数与返回值均为 double[];先用列提取器取序列
double[] close = TqTa.C(bars);
double[] high = TqTa.H(bars);
double[] ma5 = TqTaFunc.Ma(close, 5);
double[] hh9 = TqTaFunc.Hhv(high, 9);
// 指标(TqTa):返回 TaDataFrame,按列名取值(NaN 语义与 tqsdk/pandas 一致)
var macd = TqTa.MACD(bars, 12, 26, 9);
double[] dif = macd["dif"];
double[] dea = macd["dea"];
double[] bar = macd["bar"];
var kdj = TqTa.KDJ(bars, 9, 3, 3);
double[] k = kdj["k"], d = kdj["d"], j = kdj["j"];
string[] ccl = TqTa.CCL(bars); // 持仓异动:返回文字标记数组
覆盖范围
- 趋势/均线:
MAEMASMAEMA2TRMABOLLENVHCLEXPMADMABBIBBIBOLLDKXPUBUMIKEB3612 - 摆动/动量:
MACDKDJKDSLOWKDWRLWRRSIBIASCCIROCPRICEOSCPSYMIMTMMICDRCRCCDDPODBCDMASSZDZBSRMI - 能量/成交量:
ATRDMISARCRVRARBRMFIADTMDDISRDMOBVADPVTWVADWADCJLOPICCLVOSCVROCVRSIMVLONSHORTCDPQHLSRASI - 底层算子
TqTaFunc:RefMaSmaEmaEma2StdTrmaHhvLlvMedianAvedevHarmeanNumPowAbsMinMaxCrossUpCrossDownCountExistEveryBarLast
NaN 语义(与 pandas 严格对齐)
rolling(n)系列(Ma/Std/Hhv/Llv/Median/Avedev/Harmean/…)采用min_periods=n:前 n−1 个为NaN。Sma/Ema/Ewm均为ewm(..., adjust=False):首个非NaN作种子递推;前置NaN输出NaN;中间NaN保持上一有效输出(不向下游传播)。CumSum与pandas.cumsum()一致:运行和跳过NaN(不累加),但NaN位置本身输出NaN。Ref(x, n)越界为NaN;故CrossUp/CrossDown首位置恒为0,BarLast从未成立处为-1。
数值验证:用确定性合成 OHLCV(种子 20261023,150 根)经真实 tqsdk 生成参考值(tools/gen_ta_ref.py → tools/ta_ref.json),
TqTaVerify 逐元素比对(NaN=NaN,相对容差 1e-6)——22 个底层算子 + 65 个指标共 87 项全部一致。
cd csharp && dotnet run --project TqTaVerify -- ../tools/ta_ref.json
未移植:期权 BS 定价类(BS_VALUE / OPTION_GREEKS / OPTION_VALUE / OPTION_IMPV / VOLATILITY_CURVE)——
依赖 scipy 且需要 quote 列,不属于常规 K 线指标,暂未复刻。
通信协议说明(调试结论)
- 认证:
POST https://auth.shinnytech.com/.../token(client_id=shinny_tq)获取 JWT access_token; 再GET https://api.shinnytech.com/ns?stock=false&backtest=false获取行情网关地址 (免费账户:wss://free-openmd.shinnytech.com/t/md/front/mobile)。 - 连接:WebSocket 请求头带
Authorization: Bearer <token>,无需登录报文; 服务器连接后首先回复{"aid":"rsp_login"}。 - 订阅/拉取:发送
{"aid":"subscribe_quote","ins_list":"DCE.v2701"}, 之后每次wait_update前发送{"aid":"peek_message"}。 服务器是 peek 驱动的:每次 peek 必有响应,价格有变化时随响应推送。 - permessage-deflate 强制要求:天勤行情服务器对不带
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits的握手直接返回 400, 且所有服务器帧均压缩(RSV1=1)、启用了 deflate 上下文接管。 .NET 内置ClientWebSocket不支持该扩展,因此本项目自行实现了TqWebSocketClient(TcpClient + SslStream + 帧编解码 + permessage-deflate 解压)。 - diff 合并:
rtn_data报文中的data数组逐项合并进实体; 字段null表示删除(对应 tqsdk 的 None),"-"字符串按 NaN 处理。 - K 线:
set_chart订阅后,数据落在klines.<symbol>.<duration_ns>.data.<id>,charts.<chart_id>携带ready/left_id/right_id/state;本库按 id 有序插入并支持Last读取最新一根。 注意klines.<symbol>.<duration>是该合约+周期全局共享的节点,实时订阅与历史获取都从它读数据。 Tick 同理:set_chart(duration=0)订阅后数据落在ticks.<symbol>.data.<id>,charts.<chart_id>.ready标记就绪;ticks节点无 duration 子层级,按symbol路由——GetTickSerial维护最近data_length笔滚动窗口,GetTickDataSeries按[start,end]过滤。 - 历史 K 线(
GetKlineHistory,参考 tqsdk-rs):通过set_chart的left_kline_id(按 K 线 id 定位, 向右取view_width根)或focus_datetime+focus_position(0-100,按时间定位)请求某个历史窗口; 依据charts.<chart_id>的ready/more_data(即mdhis_more_data) 判断窗口是否加载完成、是否还有更早数据, 必要时按left_id - view_width继续向前回退(walk-back,步数上限 64,单块宽度定位窗口时收紧到 2000 以避开免费网关单块投递上限)拼接更早历史。view_width按 tqsdk-rs 规则 clamp(锚定最新 [200,10000];按left_kline_id定位历史窗口时 [200,2000])。- 历史深度由行情网关决定,不是客户端限制:
more_data是网关告诉客户端"还有更早数据吗"的信号,一旦为false任何客户端都必须停。 免费free-openmd网关实测保留期:分钟线约 2 个月(一次取满 10000 根回到 ~2026-09-03)、日线约 9 个月(~2026-01-19,more_data=False)。 故免费账户下分钟线拿不到半年/一年;日线可覆盖近半年(含在 9 个月范围内),完整近一年差约 3 个月。 - tqsdk-rs 之所以"能取很深",是因为它通常配合专业/付费行情源(其
TqConfig可自定义md_url行情网关地址, 历史保留期年级别且持续more_data=true),而非它绕过限制。其kline_history本身不实现自动 walk-back, 需调用者用更小left_kline_id反复订阅拼接——本库已复刻该机制(OnChartUpdate自动向前回退)。 - 若要分钟级半年/一年或完整日线一年:须接入天勤专业版/极速行情(付费,历史网关
nfmd才提供深历史; 免费账户连nfmd会"连上即断",已用官方downloader实测印证)。
- 历史深度由行情网关决定,不是客户端限制:
- 断线重连:指数退避(1s → 2s → 4s … 上限 30s),达到上限次数(
MaxReconnectAttempts)后放弃。
模拟交易(TqSim)实现要点
- 撮合:与 tqsdk 一致——市价单以对手盘价全部成交,限价单达到对手盘价即全部成交(无部分成交);
支持
OPEN/CLOSE/CLOSETODAY,上期所/能源中心区分今老仓。 - 保证金:
最新价 × 合约乘数 × MarginRatio(默认 10%),MarginRatio可改。 - 手续费:
CommissionPerLot(每手,默认 0),SetCommission(rate)设置。 - 盈亏:浮动盈亏
(最新价 − 持仓均价) × 手数 × 合约乘数;平仓盈亏在平仓时计入close_profit。 - 合约乘数来源:优先取实时行情
quote.volume_multiple;若行情未下发(如免费行情网关),回退到持仓创建时的记录值, 再回退到TqSim.VolumeMultipleFallback(默认 1,可针对合约设置,例如 PVC/聚丙烯=5、豆粕/鸡蛋=10)。ins_query静态字段请求已在GetQuote/GetQuoteList时自动发送,真实账户可据此拿到真实乘数。 - 健壮性:撮合/重算对未初始化数值字段做了 NaN 防御(累加字段初始化为 0,持仓为 0 的一侧不参与
NaN×0计算), 避免NaN + x = NaN污染账户权益。
已知限制
- 历史 K 线深度:
GetKlineHistory取“最近 N 根”在免费网关可用(实测请求 1000 根正常返回,约覆盖 3 个交易日; 社区经验免费账户单序列上限约 8000 根)。免费网关对已返回的完整窗口置more_data=false,不会触发向前回退; 通过left_kline_id/focus_datetime定位更早窗口能否命中取决于网关数据范围。历史为快照式,加载中途断线需重新调用以恢复。 - 免费行情网关(
free-openmd)对部分合约不保证下发volume_multiple/instrument_name等静态字段, 此时 TqSim 的保证金/盈亏按乘数1(或回退5)计算,可能与真实交易所乘数有偏差;接入实盘/极速行情可消除此偏差。 - 期权(
OPTION)模拟交易暂不支持(与 tqsdk TqSim 限制一致)。 - 真实期货公司账户(非 TqSim)交易链路未实现。
- Tick 历史:
GetTickDataSeries(按时间段 Tick)为专业版能力;免费网关不提供历史 Tick,免费账户调用只返回近期留存、取不到跨历史区间。 另get_kline_data_series/get_tick_data_series在官方 tqsdk 由tq_dl特征门槛限制为专业版,本库直接走底层set_chart,故免费账户也能拉到近期数据,更深历史仍取决于账户权限。 - 回测/复盘(TqBacktest / TqReplay) 与 EDB/宏观数据(api.query) 等尚未迁移(属连接模式 / 独立数据服务,非单纯方法复刻)。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 94 | 10/26/2026 |