tickhub 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ # Exclude development files from distribution
2
+ exclude CLAUDE.md
3
+ exclude DESIGN.md
4
+ recursive-exclude docs/dev *
tickhub-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: tickhub
3
+ Version: 0.1.0
4
+ Summary: A Python SDK for real-time stock and futures market data
5
+ Requires-Python: >=3.7
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pandas>=1.3.0
8
+ Requires-Dist: requests>=2.25.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
11
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
12
+ Requires-Dist: black>=22.0.0; extra == "dev"
13
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
14
+ Requires-Dist: twine>=4.0.0; extra == "dev"
15
+ Requires-Dist: build>=0.8.0; extra == "dev"
16
+
17
+ # TickHub Python SDK
18
+
19
+ 面向股票、期货等金融数据用户提供实时市场股票信息的 Python SDK
20
+
21
+
22
+ ## 如何使用
23
+
24
+ ```python
25
+ import tickhub
26
+
27
+ # 创建客户端(传入你的 API Token)
28
+ pro = tickhub.pro("your-tickhub-token")
29
+
30
+ # 获取日线数据
31
+ df = pro.get_daily(
32
+ ts_code='000001.SZ',
33
+ start_date='20240101',
34
+ end_date='20240301'
35
+ )
36
+ print(df)
37
+
38
+ # 获取实时行情
39
+ df = pro.rt_k(ts_code='000001.SZ')
40
+ print(df)
41
+ ```
42
+
43
+ ## 开发
44
+
45
+ ### 环境要求
46
+ - Python 3.7+
47
+
48
+ ### 开发依赖
49
+
50
+ | 工具 | 用途 |
51
+ |------|------|
52
+ | pytest | 测试框架 |
53
+ | pytest-cov | 测试覆盖率 |
54
+ | black | 代码格式化 |
55
+ | ruff | 代码检查 |
56
+ | build | 构建包 |
57
+ | twine | 发布到 PyPI |
58
+
59
+ ### 本地开发
60
+
61
+ ```bash
62
+ # 克隆仓库
63
+ git clone <repository-url>
64
+ cd tickhub
65
+
66
+ # 安装开发依赖
67
+ pip install -e ".[dev]"
68
+ # 或者手动安装
69
+ pip install -e .
70
+ pip install pytest pytest-cov black ruff twine build
71
+
72
+ # 设置 API Token(复制 .env.example 为 .env 并填入真实 token)
73
+ cp .env.example .env
74
+ # 编辑 .env 文件,将 your-api-token 替换为你的真实 token
75
+
76
+ # 运行测试
77
+ pytest
78
+
79
+ # 运行示例(需要 token)
80
+ python main.py
81
+
82
+ # 格式化代码
83
+ black tickhub/ tests/
84
+
85
+ # 代码检查
86
+ ruff check tickhub/ tests/
87
+
88
+ # 构建发布
89
+ python -m build
90
+ ```
91
+
92
+ ## 文档
93
+
94
+ - [DESIGN.md](DESIGN.md) - 设计文档
95
+ - [CLAUDE.md](CLAUDE.md) - 开发指南
96
+
97
+ ## License
98
+
99
+ MIT
@@ -0,0 +1,83 @@
1
+ # TickHub Python SDK
2
+
3
+ 面向股票、期货等金融数据用户提供实时市场股票信息的 Python SDK
4
+
5
+
6
+ ## 如何使用
7
+
8
+ ```python
9
+ import tickhub
10
+
11
+ # 创建客户端(传入你的 API Token)
12
+ pro = tickhub.pro("your-tickhub-token")
13
+
14
+ # 获取日线数据
15
+ df = pro.get_daily(
16
+ ts_code='000001.SZ',
17
+ start_date='20240101',
18
+ end_date='20240301'
19
+ )
20
+ print(df)
21
+
22
+ # 获取实时行情
23
+ df = pro.rt_k(ts_code='000001.SZ')
24
+ print(df)
25
+ ```
26
+
27
+ ## 开发
28
+
29
+ ### 环境要求
30
+ - Python 3.7+
31
+
32
+ ### 开发依赖
33
+
34
+ | 工具 | 用途 |
35
+ |------|------|
36
+ | pytest | 测试框架 |
37
+ | pytest-cov | 测试覆盖率 |
38
+ | black | 代码格式化 |
39
+ | ruff | 代码检查 |
40
+ | build | 构建包 |
41
+ | twine | 发布到 PyPI |
42
+
43
+ ### 本地开发
44
+
45
+ ```bash
46
+ # 克隆仓库
47
+ git clone <repository-url>
48
+ cd tickhub
49
+
50
+ # 安装开发依赖
51
+ pip install -e ".[dev]"
52
+ # 或者手动安装
53
+ pip install -e .
54
+ pip install pytest pytest-cov black ruff twine build
55
+
56
+ # 设置 API Token(复制 .env.example 为 .env 并填入真实 token)
57
+ cp .env.example .env
58
+ # 编辑 .env 文件,将 your-api-token 替换为你的真实 token
59
+
60
+ # 运行测试
61
+ pytest
62
+
63
+ # 运行示例(需要 token)
64
+ python main.py
65
+
66
+ # 格式化代码
67
+ black tickhub/ tests/
68
+
69
+ # 代码检查
70
+ ruff check tickhub/ tests/
71
+
72
+ # 构建发布
73
+ python -m build
74
+ ```
75
+
76
+ ## 文档
77
+
78
+ - [DESIGN.md](DESIGN.md) - 设计文档
79
+ - [CLAUDE.md](CLAUDE.md) - 开发指南
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "tickhub"
3
+ version = "0.1.0"
4
+ description = "A Python SDK for real-time stock and futures market data"
5
+ readme = "README.md"
6
+ requires-python = ">=3.7"
7
+ dependencies = [
8
+ "pandas>=1.3.0",
9
+ "requests>=2.25.0",
10
+ ]
11
+
12
+ [project.optional-dependencies]
13
+ dev = [
14
+ "pytest>=7.0.0",
15
+ "pytest-cov>=4.0.0",
16
+ "black>=22.0.0",
17
+ "ruff>=0.1.0",
18
+ "twine>=4.0.0",
19
+ "build>=0.8.0",
20
+ ]
21
+
22
+ [build-system]
23
+ requires = ["setuptools>=45", "wheel"]
24
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ """TickHub 客户端测试"""
2
+
3
+ from tickhub import pro
4
+
5
+
6
+ class TestClient:
7
+ """客户端测试"""
8
+
9
+ def test_pro_function(self):
10
+ """测试 pro() 函数创建客户端"""
11
+ client = pro("test-key")
12
+ assert client.api_key == "test-key"
13
+ assert client.base_url == "http://tushare.xyz"
@@ -0,0 +1,164 @@
1
+ """TickHub 交易数据接口测试"""
2
+
3
+ import pytest
4
+ from unittest.mock import Mock
5
+ import pandas as pd
6
+
7
+ from tickhub import pro
8
+
9
+
10
+ class TestGetDaily:
11
+ """get_daily 接口测试"""
12
+
13
+ def test_get_daily_with_valid_params(self):
14
+ """测试正常获取日线数据"""
15
+ client = pro("test-key")
16
+
17
+ # Mock _request 返回值
18
+ mock_data = {
19
+ "fields": ["ts_code", "trade_date", "open", "high", "low", "close", "vol"],
20
+ "items": [
21
+ ["000001.SZ", "20240101", 10.0, 11.0, 9.5, 10.5, 1000000],
22
+ ["000001.SZ", "20240102", 10.5, 11.5, 10.0, 11.0, 1200000],
23
+ ],
24
+ }
25
+ client._request = Mock(return_value=mock_data)
26
+
27
+ df = client.get_daily(
28
+ ts_code="000001.SZ", start_date="20240101", end_date="20240102"
29
+ )
30
+
31
+ assert len(df) == 2
32
+ assert "ts_code" in df.columns
33
+ assert "trade_date" in df.columns
34
+ client._request.assert_called_once_with(
35
+ "daily",
36
+ params={
37
+ "ts_code": "000001.SZ",
38
+ "start_date": "20240101",
39
+ "end_date": "20240102",
40
+ },
41
+ )
42
+
43
+ def test_get_daily_with_trade_date(self):
44
+ """测试使用 trade_date 参数"""
45
+ client = pro("test-key")
46
+
47
+ mock_data = {
48
+ "fields": ["ts_code", "trade_date", "open", "high", "low", "close"],
49
+ "items": [["000001.SZ", "20240115", 10.0, 11.0, 9.5, 10.5]],
50
+ }
51
+ client._request = Mock(return_value=mock_data)
52
+
53
+ df = client.get_daily(ts_code="000001.SZ", trade_date="20240115")
54
+
55
+ assert len(df) == 1
56
+ client._request.assert_called_once_with(
57
+ "daily", params={"ts_code": "000001.SZ", "trade_date": "20240115"}
58
+ )
59
+
60
+ def test_get_daily_empty_response(self):
61
+ """测试空数据返回"""
62
+ client = pro("test-key")
63
+ client._request = Mock(return_value=None)
64
+
65
+ df = client.get_daily(
66
+ ts_code="000001.SZ", start_date="20240101", end_date="20240102"
67
+ )
68
+
69
+ assert len(df) == 0
70
+ assert isinstance(df, pd.DataFrame)
71
+
72
+ def test_get_daily_invalid_ts_code(self):
73
+ """测试无效 ts_code 参数"""
74
+ client = pro("test-key")
75
+
76
+ with pytest.raises(ValueError, match="ts_code must be a non-empty string"):
77
+ client.get_daily(ts_code="")
78
+
79
+ with pytest.raises(ValueError, match="ts_code must be a non-empty string"):
80
+ client.get_daily(ts_code=None)
81
+
82
+ def test_get_daily_list_response(self):
83
+ """测试返回列表格式"""
84
+ client = pro("test-key")
85
+
86
+ # 有些 API 可能直接返回列表
87
+ mock_data = [
88
+ {"ts_code": "000001.SZ", "trade_date": "20240101", "close": 10.5},
89
+ {"ts_code": "000001.SZ", "trade_date": "20240102", "close": 11.0},
90
+ ]
91
+ client._request = Mock(return_value=mock_data)
92
+
93
+ df = client.get_daily(
94
+ ts_code="000001.SZ", start_date="20240101", end_date="20240102"
95
+ )
96
+
97
+ assert len(df) == 2
98
+ assert "ts_code" in df.columns
99
+
100
+
101
+ class TestRtK:
102
+ """rt_k 接口测试"""
103
+
104
+ def test_rt_k_with_single_stock(self):
105
+ """测试获取单只股票实时行情"""
106
+ client = pro("test-key")
107
+
108
+ mock_data = {
109
+ "fields": ["ts_code", "trade_date", "open", "high", "low", "close", "vol"],
110
+ "items": [
111
+ ["000001.SZ", "20240115", 10.0, 11.0, 9.5, 10.5, 500000],
112
+ ],
113
+ }
114
+ client._request = Mock(return_value=mock_data)
115
+
116
+ df = client.rt_k(ts_code="000001.SZ")
117
+
118
+ assert len(df) == 1
119
+ assert "ts_code" in df.columns
120
+ client._request.assert_called_once_with("rt_k", params={"ts_code": "000001.SZ"})
121
+
122
+ def test_rt_k_with_wildcard(self):
123
+ """测试使用通配符批量获取"""
124
+ client = pro("test-key")
125
+
126
+ mock_data = {
127
+ "fields": ["ts_code", "trade_date", "close"],
128
+ "items": [
129
+ ["000001.SZ", "20240115", 10.5],
130
+ ["000002.SZ", "20240115", 20.5],
131
+ ["000003.SZ", "20240115", 15.5],
132
+ ],
133
+ }
134
+ client._request = Mock(return_value=mock_data)
135
+
136
+ df = client.rt_k(ts_code="00000*.SZ")
137
+
138
+ assert len(df) == 3
139
+ client._request.assert_called_once_with("rt_k", params={"ts_code": "00000*.SZ"})
140
+
141
+ def test_rt_k_no_params(self):
142
+ """测试不传参数(获取全市场)"""
143
+ client = pro("test-key")
144
+
145
+ mock_data = {
146
+ "fields": ["ts_code", "trade_date", "close"],
147
+ "items": [["000001.SZ", "20240115", 10.5]],
148
+ }
149
+ client._request = Mock(return_value=mock_data)
150
+
151
+ df = client.rt_k()
152
+
153
+ assert isinstance(df, pd.DataFrame)
154
+ client._request.assert_called_once_with("rt_k", params={})
155
+
156
+ def test_rt_k_empty_response(self):
157
+ """测试空数据返回"""
158
+ client = pro("test-key")
159
+ client._request = Mock(return_value=None)
160
+
161
+ df = client.rt_k(ts_code="000001.SZ")
162
+
163
+ assert len(df) == 0
164
+ assert isinstance(df, pd.DataFrame)
@@ -0,0 +1,67 @@
1
+ """TickHub Python SDK - 金融数据 API"""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from .pro.client import Client
7
+ from .exceptions import TickHubError, AuthenticationError
8
+
9
+
10
+ def _load_token_from_env():
11
+ """从环境变量或 .env 文件加载 Token"""
12
+ # 1. 检查环境变量
13
+ token = os.environ.get("TICKHUB_TOKEN")
14
+ if token:
15
+ return token
16
+
17
+ # 2. 检查 .env 文件
18
+ env_file = Path(".env")
19
+ if env_file.exists():
20
+ with open(env_file, "r") as f:
21
+ for line in f:
22
+ line = line.strip()
23
+ if line.startswith("TICKHUB_TOKEN="):
24
+ token = line.split("=", 1)[1].strip().strip("\"'")
25
+ if token and token != "your-api-token":
26
+ return token
27
+
28
+ return None
29
+
30
+
31
+ def pro(api_key=None):
32
+ """
33
+ 创建 TickHub Pro 客户端
34
+
35
+ Args:
36
+ api_key: API 密钥(可选,默认从环境变量或 .env 文件加载)
37
+
38
+ Returns:
39
+ Client: TickHub 客户端实例
40
+
41
+ Example:
42
+ >>> import tickhub
43
+ >>> # 方式1: 从 .env 文件加载(推荐)
44
+ >>> pro = tickhub.pro()
45
+ >>> # 方式2: 显式传入
46
+ >>> pro = tickhub.pro('your-api-token')
47
+ """
48
+ if api_key is None:
49
+ api_key = _load_token_from_env()
50
+ if api_key is None:
51
+ raise TickHubError(
52
+ "未找到 API Token。请通过以下方式之一设置:\n"
53
+ "1. 创建 .env 文件: echo 'TICKHUB_TOKEN=your-token' > .env\n"
54
+ "2. 环境变量: export TICKHUB_TOKEN='your-token'\n"
55
+ "3. 直接传入: tickhub.pro('your-token')"
56
+ )
57
+
58
+ return Client(api_key)
59
+
60
+
61
+ __version__ = "0.1.0"
62
+ __all__ = [
63
+ "pro",
64
+ "Client",
65
+ "TickHubError",
66
+ "AuthenticationError",
67
+ ]
@@ -0,0 +1,50 @@
1
+ """TickHub SDK 异常定义"""
2
+
3
+
4
+ class TickHubError(Exception):
5
+ """TickHub SDK 基础异常"""
6
+
7
+ def __init__(self, message: str, code: int = None):
8
+ super().__init__(message)
9
+ self.message = message
10
+ self.code = code
11
+
12
+ def __str__(self):
13
+ if self.code:
14
+ return f"[{self.code}] {self.message}"
15
+ return self.message
16
+
17
+
18
+ class AuthenticationError(TickHubError):
19
+ """认证失败异常 (401)"""
20
+
21
+ def __init__(self, message: str = "API Key 无效或已过期"):
22
+ super().__init__(message, code=401)
23
+
24
+
25
+ class PermissionError(TickHubError):
26
+ """权限不足异常 (403)"""
27
+
28
+ def __init__(self, message: str = "无权限访问该接口"):
29
+ super().__init__(message, code=403)
30
+
31
+
32
+ class NotFoundError(TickHubError):
33
+ """资源不存在异常 (404)"""
34
+
35
+ def __init__(self, message: str = "请求的资源不存在"):
36
+ super().__init__(message, code=404)
37
+
38
+
39
+ class RateLimitError(TickHubError):
40
+ """请求频率限制异常 (429)"""
41
+
42
+ def __init__(self, message: str = "请求过于频繁,请降低频率"):
43
+ super().__init__(message, code=429)
44
+
45
+
46
+ class ServerError(TickHubError):
47
+ """服务器内部错误 (500)"""
48
+
49
+ def __init__(self, message: str = "服务器内部错误"):
50
+ super().__init__(message, code=500)
@@ -0,0 +1,5 @@
1
+ """TickHub Pro API 客户端"""
2
+
3
+ from .client import Client
4
+
5
+ __all__ = ["Client"]
@@ -0,0 +1,96 @@
1
+ """TickHub HTTP 客户端基类"""
2
+
3
+ import requests
4
+
5
+ from ..exceptions import (
6
+ TickHubError,
7
+ AuthenticationError,
8
+ PermissionError,
9
+ NotFoundError,
10
+ RateLimitError,
11
+ ServerError,
12
+ )
13
+ from ..stock.trading import TradingMixin
14
+
15
+
16
+ class Client(TradingMixin):
17
+ """
18
+ TickHub Pro API 客户端
19
+
20
+ 提供股票、期货等金融数据的查询接口
21
+ """
22
+
23
+ def __init__(self, api_key: str, base_url: str = "http://tushare.xyz"):
24
+ """
25
+ 初始化客户端
26
+
27
+ Args:
28
+ api_key: API 密钥
29
+ base_url: API 基础 URL,默认 http://tushare.xyz
30
+ """
31
+ self.api_key = api_key
32
+ self.base_url = base_url
33
+ self.session = requests.Session()
34
+ self.session.headers.update(
35
+ {
36
+ "Content-Type": "application/json",
37
+ }
38
+ )
39
+
40
+ def _request(self, api_name: str, params: dict = None) -> dict:
41
+ """
42
+ 发送 API 请求
43
+
44
+ Args:
45
+ api_name: API 接口名称(如 daily, rt_k 等)
46
+ params: 业务参数
47
+
48
+ Returns:
49
+ dict: 响应数据
50
+
51
+ Raises:
52
+ TickHubError: 请求失败时抛出相应异常
53
+ """
54
+ # 构建请求体
55
+ payload = {
56
+ "api_name": api_name,
57
+ "token": self.api_key,
58
+ "params": params or {},
59
+ }
60
+
61
+ try:
62
+ response = self.session.post(
63
+ url=self.base_url,
64
+ json=payload,
65
+ timeout=30,
66
+ )
67
+ except requests.exceptions.Timeout:
68
+ raise TickHubError("请求超时,请稍后重试")
69
+ except requests.exceptions.ConnectionError:
70
+ raise TickHubError("网络连接失败,请检查网络")
71
+ except requests.exceptions.RequestException as e:
72
+ raise TickHubError(f"请求异常: {e}")
73
+
74
+ # 处理 HTTP 错误码
75
+ if response.status_code == 401:
76
+ raise AuthenticationError()
77
+ elif response.status_code == 403:
78
+ raise PermissionError()
79
+ elif response.status_code == 404:
80
+ raise NotFoundError()
81
+ elif response.status_code == 429:
82
+ raise RateLimitError()
83
+ elif response.status_code >= 500:
84
+ raise ServerError()
85
+
86
+ # 尝试解析 JSON 响应
87
+ try:
88
+ result = response.json()
89
+ except ValueError:
90
+ raise TickHubError("响应解析失败")
91
+
92
+ if result.get("code") not in (0, None):
93
+ msg = result.get("msg", "未知错误")
94
+ raise TickHubError(msg, code=result.get("code"))
95
+
96
+ return result.get("data", {})
@@ -0,0 +1,5 @@
1
+ """TickHub 股票数据模块"""
2
+
3
+ from .trading import TradingMixin
4
+
5
+ __all__ = ["TradingMixin"]
@@ -0,0 +1,95 @@
1
+ """股票 K 线数据接口"""
2
+
3
+ import pandas as pd
4
+
5
+
6
+ class TradingMixin:
7
+ """K线数据相关接口 Mixin"""
8
+
9
+ def get_daily(
10
+ self,
11
+ ts_code: str,
12
+ start_date: str = None,
13
+ end_date: str = None,
14
+ trade_date: str = None,
15
+ ) -> pd.DataFrame:
16
+ """
17
+ 获取股票日线数据
18
+
19
+ Args:
20
+ ts_code: 股票代码,格式 {code}.{exchange},如 000001.SZ
21
+ start_date: 开始日期,格式 YYYYMMDD
22
+ end_date: 结束日期,格式 YYYYMMDD
23
+ trade_date: 交易日期(可选),格式 YYYYMMDD
24
+
25
+ Returns:
26
+ pd.DataFrame: 日线数据
27
+
28
+ Raises:
29
+ ValueError: 参数错误
30
+
31
+ Example:
32
+ >>> df = pro.get_daily('000001.SZ', start_date='20240101', end_date='20240301')
33
+ >>> df = pro.get_daily('000001.SZ', trade_date='20240115')
34
+ """
35
+ if not ts_code or not isinstance(ts_code, str):
36
+ raise ValueError("ts_code must be a non-empty string")
37
+
38
+ params = {
39
+ "ts_code": ts_code,
40
+ }
41
+
42
+ if trade_date:
43
+ params["trade_date"] = trade_date
44
+ else:
45
+ if start_date:
46
+ params["start_date"] = start_date
47
+ if end_date:
48
+ params["end_date"] = end_date
49
+
50
+ # 调用 API
51
+ data = self._request("daily", params=params)
52
+
53
+ if not data:
54
+ return pd.DataFrame()
55
+
56
+ if isinstance(data, dict) and "fields" in data and "items" in data:
57
+ df = pd.DataFrame(data["items"], columns=data["fields"])
58
+ else:
59
+ df = pd.DataFrame(data)
60
+
61
+ return df
62
+
63
+ def rt_k(
64
+ self,
65
+ ts_code: str = None,
66
+ ) -> pd.DataFrame:
67
+ """
68
+ 获取实时日线行情
69
+
70
+ Args:
71
+ ts_code: 股票代码,支持通配符,如 00000*.SZ
72
+
73
+ Returns:
74
+ pd.DataFrame: 实时日线数据
75
+
76
+ Example:
77
+ >>> df = pro.rt_k(ts_code='000001.SZ')
78
+ >>> df = pro.rt_k(ts_code='00000*.SZ') # 批量获取
79
+ """
80
+ params = {}
81
+ if ts_code:
82
+ params["ts_code"] = ts_code
83
+
84
+ data = self._request("rt_k", params=params)
85
+
86
+ if not data:
87
+ return pd.DataFrame()
88
+
89
+ # 处理返回格式
90
+ if isinstance(data, dict) and "fields" in data and "items" in data:
91
+ df = pd.DataFrame(data["items"], columns=data["fields"])
92
+ else:
93
+ df = pd.DataFrame(data)
94
+
95
+ return df
@@ -0,0 +1,9 @@
1
+ """TickHub 工具函数模块"""
2
+
3
+ from .helpers import (
4
+ format_symbol,
5
+ parse_date,
6
+ format_date,
7
+ )
8
+
9
+ __all__ = ["format_symbol", "parse_date", "format_date"]
@@ -0,0 +1,47 @@
1
+ """工具函数"""
2
+
3
+ from datetime import date, datetime
4
+ from typing import Union
5
+
6
+
7
+ def format_symbol(symbol: str) -> str:
8
+ """格式化股票代码"""
9
+ if not symbol:
10
+ raise ValueError("symbol cannot be empty")
11
+
12
+ if "." in symbol:
13
+ return symbol.upper()
14
+
15
+ if symbol.startswith("6"):
16
+ return f"{symbol}.SH"
17
+ elif symbol.startswith(("0", "3")):
18
+ return f"{symbol}.SZ"
19
+ elif symbol.startswith(("8", "4")):
20
+ return f"{symbol}.BJ"
21
+ else:
22
+ raise ValueError(f"Cannot determine exchange for symbol: {symbol}")
23
+
24
+
25
+ def parse_date(date_input: Union[str, date, datetime]) -> date:
26
+ """解析日期输入为 date 对象"""
27
+ if isinstance(date_input, date):
28
+ return date_input
29
+
30
+ if isinstance(date_input, datetime):
31
+ return date_input.date()
32
+
33
+ if isinstance(date_input, str):
34
+ formats = ["%Y-%m-%d", "%Y%m%d", "%Y/%m/%d"]
35
+ for fmt in formats:
36
+ try:
37
+ return datetime.strptime(date_input, fmt).date()
38
+ except ValueError:
39
+ continue
40
+ raise ValueError(f"Cannot parse date: {date_input}")
41
+
42
+ raise ValueError(f"Unsupported date type: {type(date_input)}")
43
+
44
+
45
+ def format_date(d: date, fmt: str = "%Y-%m-%d") -> str:
46
+ """格式化日期为字符串"""
47
+ return d.strftime(fmt)
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: tickhub
3
+ Version: 0.1.0
4
+ Summary: A Python SDK for real-time stock and futures market data
5
+ Requires-Python: >=3.7
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pandas>=1.3.0
8
+ Requires-Dist: requests>=2.25.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
11
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
12
+ Requires-Dist: black>=22.0.0; extra == "dev"
13
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
14
+ Requires-Dist: twine>=4.0.0; extra == "dev"
15
+ Requires-Dist: build>=0.8.0; extra == "dev"
16
+
17
+ # TickHub Python SDK
18
+
19
+ 面向股票、期货等金融数据用户提供实时市场股票信息的 Python SDK
20
+
21
+
22
+ ## 如何使用
23
+
24
+ ```python
25
+ import tickhub
26
+
27
+ # 创建客户端(传入你的 API Token)
28
+ pro = tickhub.pro("your-tickhub-token")
29
+
30
+ # 获取日线数据
31
+ df = pro.get_daily(
32
+ ts_code='000001.SZ',
33
+ start_date='20240101',
34
+ end_date='20240301'
35
+ )
36
+ print(df)
37
+
38
+ # 获取实时行情
39
+ df = pro.rt_k(ts_code='000001.SZ')
40
+ print(df)
41
+ ```
42
+
43
+ ## 开发
44
+
45
+ ### 环境要求
46
+ - Python 3.7+
47
+
48
+ ### 开发依赖
49
+
50
+ | 工具 | 用途 |
51
+ |------|------|
52
+ | pytest | 测试框架 |
53
+ | pytest-cov | 测试覆盖率 |
54
+ | black | 代码格式化 |
55
+ | ruff | 代码检查 |
56
+ | build | 构建包 |
57
+ | twine | 发布到 PyPI |
58
+
59
+ ### 本地开发
60
+
61
+ ```bash
62
+ # 克隆仓库
63
+ git clone <repository-url>
64
+ cd tickhub
65
+
66
+ # 安装开发依赖
67
+ pip install -e ".[dev]"
68
+ # 或者手动安装
69
+ pip install -e .
70
+ pip install pytest pytest-cov black ruff twine build
71
+
72
+ # 设置 API Token(复制 .env.example 为 .env 并填入真实 token)
73
+ cp .env.example .env
74
+ # 编辑 .env 文件,将 your-api-token 替换为你的真实 token
75
+
76
+ # 运行测试
77
+ pytest
78
+
79
+ # 运行示例(需要 token)
80
+ python main.py
81
+
82
+ # 格式化代码
83
+ black tickhub/ tests/
84
+
85
+ # 代码检查
86
+ ruff check tickhub/ tests/
87
+
88
+ # 构建发布
89
+ python -m build
90
+ ```
91
+
92
+ ## 文档
93
+
94
+ - [DESIGN.md](DESIGN.md) - 设计文档
95
+ - [CLAUDE.md](CLAUDE.md) - 开发指南
96
+
97
+ ## License
98
+
99
+ MIT
@@ -0,0 +1,18 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ tests/test_client.py
5
+ tests/test_trading.py
6
+ tickhub/__init__.py
7
+ tickhub/exceptions.py
8
+ tickhub.egg-info/PKG-INFO
9
+ tickhub.egg-info/SOURCES.txt
10
+ tickhub.egg-info/dependency_links.txt
11
+ tickhub.egg-info/requires.txt
12
+ tickhub.egg-info/top_level.txt
13
+ tickhub/pro/__init__.py
14
+ tickhub/pro/client.py
15
+ tickhub/stock/__init__.py
16
+ tickhub/stock/trading.py
17
+ tickhub/util/__init__.py
18
+ tickhub/util/helpers.py
@@ -0,0 +1,10 @@
1
+ pandas>=1.3.0
2
+ requests>=2.25.0
3
+
4
+ [dev]
5
+ pytest>=7.0.0
6
+ pytest-cov>=4.0.0
7
+ black>=22.0.0
8
+ ruff>=0.1.0
9
+ twine>=4.0.0
10
+ build>=0.8.0
@@ -0,0 +1 @@
1
+ tickhub