stock-sdk 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.
Files changed (104) hide show
  1. stock_sdk-0.1.0/PKG-INFO +14 -0
  2. stock_sdk-0.1.0/README.md +0 -0
  3. stock_sdk-0.1.0/setup.cfg +4 -0
  4. stock_sdk-0.1.0/setup.py +21 -0
  5. stock_sdk-0.1.0/stock_sdk/opentdx/__init__.py +35 -0
  6. stock_sdk-0.1.0/stock_sdk/opentdx/cli.py +428 -0
  7. stock_sdk-0.1.0/stock_sdk/opentdx/client/__init__.py +14 -0
  8. stock_sdk-0.1.0/stock_sdk/opentdx/client/baseClient.py +83 -0
  9. stock_sdk-0.1.0/stock_sdk/opentdx/client/extendedClient.py +111 -0
  10. stock_sdk-0.1.0/stock_sdk/opentdx/client/macExtendedClient.py +12 -0
  11. stock_sdk-0.1.0/stock_sdk/opentdx/client/macMixin.py +268 -0
  12. stock_sdk-0.1.0/stock_sdk/opentdx/client/macStandardClient.py +12 -0
  13. stock_sdk-0.1.0/stock_sdk/opentdx/client/standardClient.py +313 -0
  14. stock_sdk-0.1.0/stock_sdk/opentdx/client/transport.py +391 -0
  15. stock_sdk-0.1.0/stock_sdk/opentdx/commands/__init__.py +4 -0
  16. stock_sdk-0.1.0/stock_sdk/opentdx/commands/doc_demo.py +620 -0
  17. stock_sdk-0.1.0/stock_sdk/opentdx/commands/market_monitor.py +301 -0
  18. stock_sdk-0.1.0/stock_sdk/opentdx/const.py +534 -0
  19. stock_sdk-0.1.0/stock_sdk/opentdx/enums/__init__.py +5 -0
  20. stock_sdk-0.1.0/stock_sdk/opentdx/enums/industry_code_enum.py +183 -0
  21. stock_sdk-0.1.0/stock_sdk/opentdx/parser/__init__.py +0 -0
  22. stock_sdk-0.1.0/stock_sdk/opentdx/parser/baseParser.py +32 -0
  23. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/__init__.py +39 -0
  24. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/category_list.py +41 -0
  25. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/chart_sampling.py +28 -0
  26. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/count.py +12 -0
  27. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/file.py +13 -0
  28. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/goods.py +90 -0
  29. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/history_tick_chart.py +32 -0
  30. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/history_transaction.py +35 -0
  31. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/kline.py +32 -0
  32. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/kline2.py +32 -0
  33. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/list.py +27 -0
  34. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/quotes.py +54 -0
  35. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/quotes2.py +18 -0
  36. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/quotes_list.py +10 -0
  37. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/quotes_single.py +15 -0
  38. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/server.py +54 -0
  39. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/table.py +16 -0
  40. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/table_detail.py +7 -0
  41. stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/tick_chart.py +47 -0
  42. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/GoodsList.py +37 -0
  43. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/__init__.py +36 -0
  44. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/board_list.py +41 -0
  45. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/board_members_quotes.py +14 -0
  46. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/file_query.py +38 -0
  47. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/kline_offset.py +27 -0
  48. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/server_info.py +81 -0
  49. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_auction.py +35 -0
  50. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_bar.py +61 -0
  51. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_belong_board.py +42 -0
  52. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_capital_flow.py +38 -0
  53. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_info.py +39 -0
  54. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_quotes.py +89 -0
  55. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_tick_chart.py +50 -0
  56. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_tick_charts.py +62 -0
  57. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_transaction.py +37 -0
  58. stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/unusual.py +45 -0
  59. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/__init__.py +63 -0
  60. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/auction.py +27 -0
  61. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/chart_sampling.py +29 -0
  62. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/company_info.py +227 -0
  63. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/count.py +17 -0
  64. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/file.py +35 -0
  65. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/history_orders.py +35 -0
  66. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/history_tick_chart.py +38 -0
  67. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/history_transaction.py +41 -0
  68. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/history_transaction_with_trans.py +42 -0
  69. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/index_info.py +79 -0
  70. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/index_momentum.py +25 -0
  71. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/kline.py +64 -0
  72. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/kline_offset.py +7 -0
  73. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/list.py +33 -0
  74. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/list2.py +31 -0
  75. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/quotes.py +16 -0
  76. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/quotes_detail.py +97 -0
  77. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/quotes_encrypt.py +105 -0
  78. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/quotes_list.py +100 -0
  79. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/server.py +195 -0
  80. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/stock.py +24 -0
  81. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/tick_chart.py +36 -0
  82. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/top_board.py +41 -0
  83. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/transaction.py +42 -0
  84. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/unusual.py +37 -0
  85. stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/volume_profile.py +101 -0
  86. stock_sdk-0.1.0/stock_sdk/opentdx/tdxClient.py +1031 -0
  87. stock_sdk-0.1.0/stock_sdk/opentdx/utils/__init__.py +0 -0
  88. stock_sdk-0.1.0/stock_sdk/opentdx/utils/base_reader.py +20 -0
  89. stock_sdk-0.1.0/stock_sdk/opentdx/utils/bitmap.py +282 -0
  90. stock_sdk-0.1.0/stock_sdk/opentdx/utils/block_reader.py +135 -0
  91. stock_sdk-0.1.0/stock_sdk/opentdx/utils/cache.py +52 -0
  92. stock_sdk-0.1.0/stock_sdk/opentdx/utils/heartbeat.py +48 -0
  93. stock_sdk-0.1.0/stock_sdk/opentdx/utils/help.py +328 -0
  94. stock_sdk-0.1.0/stock_sdk/opentdx/utils/log.py +22 -0
  95. stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/PKG-INFO +14 -0
  96. stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/SOURCES.txt +102 -0
  97. stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/dependency_links.txt +1 -0
  98. stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/requires.txt +4 -0
  99. stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/top_level.txt +1 -0
  100. stock_sdk-0.1.0/stock_sdk.egg-info/PKG-INFO +14 -0
  101. stock_sdk-0.1.0/stock_sdk.egg-info/SOURCES.txt +0 -0
  102. stock_sdk-0.1.0/stock_sdk.egg-info/dependency_links.txt +1 -0
  103. stock_sdk-0.1.0/stock_sdk.egg-info/requires.txt +4 -0
  104. stock_sdk-0.1.0/stock_sdk.egg-info/top_level.txt +1 -0
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: stock-sdk
3
+ Version: 0.1.0
4
+ Summary: Hamuna股票数据SDK
5
+ Author: hamuna
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: eltdx
8
+ Requires-Dist: httpx>=0.27.0
9
+ Requires-Dist: pandas>=2.0.0
10
+ Requires-Dist: fake-useragent>=1.5.1
11
+ Dynamic: author
12
+ Dynamic: requires-dist
13
+ Dynamic: requires-python
14
+ Dynamic: summary
File without changes
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ """hamuna-stock-sdk - Hamuna股票数据SDK"""
2
+
3
+ from setuptools import setup, find_packages
4
+
5
+ VERSION = "0.1.0"
6
+
7
+ setup(
8
+ name="stock-sdk",
9
+ version=VERSION,
10
+ description="Hamuna股票数据SDK",
11
+ author="hamuna",
12
+ packages=find_packages(where='stock_sdk'),
13
+ package_dir={'': 'stock_sdk'},
14
+ python_requires=">=3.12",
15
+ install_requires=[
16
+ "eltdx",
17
+ "httpx>=0.27.0",
18
+ "pandas>=2.0.0",
19
+ "fake-useragent>=1.5.1",
20
+ ]
21
+ )
@@ -0,0 +1,35 @@
1
+ from .tdxClient import TdxClient
2
+ from .client.standardClient import StandardClient as QuotationClient
3
+ from .client.extendedClient import ExtendedClient as exQuotationClient
4
+ from .client.macStandardClient import MacStandardClient as macQuotationClient
5
+ from .client.macExtendedClient import MacExtendedClient as macExQuotationClient
6
+ from .const import (
7
+ MARKET,
8
+ CATEGORY,
9
+ PERIOD,
10
+ ADJUST,
11
+ FILTER_TYPE,
12
+ SORT_TYPE,
13
+ BLOCK_FILE_TYPE,
14
+ BOARD_TYPE,
15
+ EX_BOARD_TYPE,
16
+ EX_MARKET,
17
+ )
18
+
19
+ __all__ = [
20
+ "TdxClient",
21
+ "QuotationClient",
22
+ "exQuotationClient",
23
+ "macQuotationClient",
24
+ "macExQuotationClient",
25
+ "MARKET",
26
+ "CATEGORY",
27
+ "PERIOD",
28
+ "ADJUST",
29
+ "FILTER_TYPE",
30
+ "SORT_TYPE",
31
+ "BLOCK_FILE_TYPE",
32
+ "BOARD_TYPE",
33
+ "EX_BOARD_TYPE",
34
+ "EX_MARKET",
35
+ ]
@@ -0,0 +1,428 @@
1
+ import json
2
+ from datetime import date, datetime
3
+ from decimal import Decimal
4
+
5
+ import click
6
+ import pandas as pd
7
+
8
+ from opentdx.commands.doc_demo import run_interactive
9
+ from opentdx.commands import run_market_monitor
10
+ from opentdx.tdxClient import TdxClient
11
+ from opentdx.client.macStandardClient import MacStandardClient
12
+ from opentdx.client.macExtendedClient import MacExtendedClient
13
+ from opentdx.const import ADJUST, EX_MARKET, MARKET, PERIOD, BOARD_TYPE, EX_BOARD_TYPE
14
+
15
+
16
+ def _json_default(obj):
17
+ if isinstance(obj, (datetime, date)):
18
+ return obj.isoformat()
19
+ if isinstance(obj, Decimal):
20
+ return float(obj)
21
+ if hasattr(obj, 'value'):
22
+ return obj.value
23
+ return str(obj)
24
+
25
+
26
+ def _output(result, json_fmt):
27
+ if json_fmt:
28
+ click.echo(json.dumps(result, default=_json_default, ensure_ascii=False))
29
+ elif isinstance(result, list):
30
+ if result and isinstance(result[0], dict):
31
+ click.echo(pd.DataFrame(result).to_string())
32
+ else:
33
+ click.echo(str(result))
34
+ elif isinstance(result, dict):
35
+ click.echo(pd.DataFrame([result]).to_string())
36
+ else:
37
+ click.echo(str(result))
38
+
39
+
40
+ def _parse_market(s: str):
41
+ try:
42
+ return getattr(MARKET, s.upper())
43
+ except AttributeError:
44
+ try:
45
+ return getattr(EX_MARKET, s.upper())
46
+ except AttributeError:
47
+ raise click.BadParameter(f"无效市场: {s}")
48
+
49
+
50
+ def _parse_period(s: str):
51
+ try:
52
+ return getattr(PERIOD, s.upper())
53
+ except AttributeError:
54
+ raise click.BadParameter(f"无效周期: {s}")
55
+
56
+
57
+ def _parse_adjust(s: str):
58
+ try:
59
+ return getattr(ADJUST, s.upper())
60
+ except AttributeError:
61
+ raise click.BadParameter(f"无效复权: {s}")
62
+
63
+
64
+ def _parse_codes(s: str):
65
+ """解析 'SZ 000001,SH 600000' 格式"""
66
+ pairs = []
67
+ parts = [p.strip() for p in s.split(',')]
68
+ for part in parts:
69
+ tokens = part.split()
70
+ if len(tokens) != 2:
71
+ raise click.BadParameter(f"格式应为 'MARKET CODE,...' 如 'SZ 000001,SH 600000'")
72
+ pairs.append((_parse_market(tokens[0]), tokens[1]))
73
+ return pairs
74
+
75
+
76
+ @click.group()
77
+ def cli():
78
+ """OpenTDX - TDX stock data client CLI"""
79
+ pass
80
+
81
+
82
+ @cli.command()
83
+ def doc():
84
+ """交互式接口文档"""
85
+ run_interactive()
86
+
87
+
88
+ @cli.command(name='mm')
89
+ @click.option('--search', default='', help='搜索关键词(匹配代码、名称或异动描述)')
90
+ @click.option('--interval', default=3, help='查询间隔(秒),默认3秒')
91
+ @click.option('--count', default=1000, help='每个市场获取的监控记录数,默认1000条')
92
+ @click.option('--split/--no-split', default=False, help='是否使用分隔符显示')
93
+ def market_monitor(interval, count, split, search):
94
+ """实时监控市场异动数据(每3秒刷新)"""
95
+ run_market_monitor(interval=interval, count=count, split=split, search=search)
96
+
97
+
98
+ # ==================== A股行情 ====================
99
+
100
+ @cli.command()
101
+ @click.argument('market')
102
+ @click.argument('code')
103
+ @click.option('--period', default='DAILY', help='周期 (DAILY/WEEKLY/MONTHLY/MIN_5/MIN_15/MIN_30/MIN_60/MINS)')
104
+ @click.option('--count', default=10, type=int, help='数量')
105
+ @click.option('--adjust', default='NONE', help='复权 (NONE/QFQ/HFQ)')
106
+ @click.option('--times', default=1, type=int, help='多周期倍数(MINS/DAYS 时生效)')
107
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
108
+ def kline(market, code, period, count, adjust, times, json_fmt):
109
+ """A股K线数据
110
+
111
+ opentdx kline SZ 000001 --period DAILY --count 10
112
+ opentdx kline SH 600519 --period MIN_30 --count 50
113
+ opentdx kline SZ 000001 --adjust QFQ
114
+ """
115
+ with TdxClient() as c:
116
+ result = c.stock_kline(_parse_market(market), code, _parse_period(period),
117
+ count=count, adjust=_parse_adjust(adjust), times=times)
118
+ _output(result, json_fmt)
119
+
120
+
121
+ @cli.command()
122
+ @click.argument('codes')
123
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
124
+ def quote(codes, json_fmt):
125
+ """股票报价
126
+
127
+ opentdx quote "SZ 000001, SH 600000"
128
+ opentdx quote "SZ 000001"
129
+ """
130
+ with TdxClient() as c:
131
+ result = c.stock_quotes(_parse_codes(codes))
132
+ _output(result, json_fmt)
133
+
134
+
135
+ @cli.command()
136
+ @click.argument('codes')
137
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
138
+ def index(codes, json_fmt):
139
+ """指数信息
140
+
141
+ opentdx index "SH 999999, SZ 399001"
142
+ """
143
+ with TdxClient() as c:
144
+ result = c.index_info(_parse_codes(codes))
145
+ _output(result, json_fmt)
146
+
147
+
148
+ @cli.command(name='stock-list')
149
+ @click.argument('market')
150
+ @click.option('--count', default=20, type=int, help='数量')
151
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
152
+ def stock_list(market, count, json_fmt):
153
+ """股票列表
154
+
155
+ opentdx stock-list SZ --count 10
156
+ """
157
+ with TdxClient() as c:
158
+ result = c.stock_list(_parse_market(market), count=count)
159
+ _output(result, json_fmt)
160
+
161
+
162
+ @cli.command()
163
+ @click.argument('market')
164
+ @click.option('--count', default=10, type=int, help='数量')
165
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
166
+ def unusual(market, count, json_fmt):
167
+ """异动数据
168
+
169
+ opentdx unusual SZ --count 20
170
+ """
171
+ with TdxClient() as c:
172
+ result = c.stock_unusual(_parse_market(market), count=count)
173
+ _output(result, json_fmt)
174
+
175
+
176
+ @cli.command()
177
+ @click.argument('market')
178
+ @click.argument('code')
179
+ @click.option('--date', default=None, help='日期 YYYY-MM-DD(默认实时)')
180
+ @click.option('--count', default=20, type=int, help='数量')
181
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
182
+ def transaction(market, code, date, count, json_fmt):
183
+ """逐笔成交
184
+
185
+ opentdx transaction SZ 000001 --count 50
186
+ opentdx transaction SZ 000001 --date 2026-03-03
187
+ """
188
+ d = date.fromisoformat(date) if date else None
189
+ with TdxClient() as c:
190
+ result = c.stock_transaction(_parse_market(market), code, date=d)
191
+ result = result[:count]
192
+ _output(result, json_fmt)
193
+
194
+
195
+ @cli.command()
196
+ @click.argument('market')
197
+ @click.argument('code')
198
+ @click.option('--date', default=None, help='日期 YYYY-MM-DD(默认实时)')
199
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
200
+ def tick(market, code, date, json_fmt):
201
+ """分时图
202
+
203
+ opentdx tick SZ 000001
204
+ opentdx tick SH 999999 --date 2026-03-16
205
+ """
206
+ d = date.fromisoformat(date) if date else None
207
+ with TdxClient() as c:
208
+ result = c.stock_tick_chart(_parse_market(market), code, date=d)
209
+ _output(result, json_fmt)
210
+
211
+
212
+ @cli.command()
213
+ @click.argument('market')
214
+ @click.argument('code')
215
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
216
+ def auction(market, code, json_fmt):
217
+ """竞价数据
218
+
219
+ opentdx auction SZ 000001
220
+ """
221
+ with TdxClient() as c:
222
+ result = c.stock_auction(_parse_market(market), code)
223
+ _output(result, json_fmt)
224
+
225
+
226
+ # ==================== 扩展市场 ====================
227
+
228
+ @cli.command(name='g-kline')
229
+ @click.argument('market')
230
+ @click.argument('code')
231
+ @click.option('--period', default='DAILY', help='周期')
232
+ @click.option('--count', default=10, type=int, help='数量')
233
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
234
+ def g_kline(market, code, period, count, json_fmt):
235
+ """扩展市场K线(港股/美股/期货)
236
+
237
+ opentdx g-kline US_STOCK TSLA --period DAILY --count 10
238
+ opentdx g-kline HK_MAIN_BOARD 00700
239
+ """
240
+ with TdxClient() as c:
241
+ result = c.goods_kline(_parse_market(market), code, _parse_period(period), count=count)
242
+ _output(result, json_fmt)
243
+
244
+
245
+ @cli.command(name='g-quote')
246
+ @click.argument('codes')
247
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
248
+ def g_quote(codes, json_fmt):
249
+ """扩展市场报价
250
+
251
+ opentdx g-quote "US_STOCK TSLA, HK_MAIN_BOARD 00700"
252
+ """
253
+ with TdxClient() as c:
254
+ result = c.goods_quotes(_parse_codes(codes))
255
+ _output(result, json_fmt)
256
+
257
+
258
+ @cli.command(name='goods-list')
259
+ @click.argument('market', type=int)
260
+ @click.option('--count', default=20, type=int, help='数量')
261
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
262
+ def goods_list(market, count, json_fmt):
263
+ """扩展市场商品列表(期货合约等)
264
+
265
+ opentdx goods-list 1 --count 10 (上期所)
266
+ opentdx goods-list 30 --count 5 (橡胶)
267
+ """
268
+ from opentdx.parser.mac_quotation.GoodsList import GoodsList
269
+ from opentdx.client import ExtendedClient
270
+
271
+ client = ExtendedClient()
272
+ if client.connect() is None:
273
+ raise click.ClickException("连接扩展市场服务器失败")
274
+ client.login()
275
+ try:
276
+ result = client.call(GoodsList(market=market, count=count))
277
+ _output(result, json_fmt)
278
+ finally:
279
+ client.disconnect()
280
+
281
+
282
+ # ==================== MAC 协议 ====================
283
+
284
+ @cli.command()
285
+ @click.argument('board_type', default='HY')
286
+ @click.option('--count', default=20, type=int, help='数量')
287
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
288
+ def board(board_type, count, json_fmt):
289
+ """板块列表
290
+
291
+ opentdx board HY --count 10
292
+ opentdx board DQ (地区板块)
293
+ opentdx board GN (概念板块)
294
+ opentdx board HK_ALL (港股板块, 需扩展行情)
295
+ opentdx board US_ALL (美股板块, 需扩展行情)
296
+
297
+ 类型: HY/HY2/GN/FG/DQ/OTHER/YJ_LEVEL1/YJ_LEVEL2/YJ_LEVEL3/ALL
298
+ HK_ALL/HK_GN/HK_HY/US_ALL/US_GN/US_HY
299
+ """
300
+ try:
301
+ bt = getattr(BOARD_TYPE, board_type.upper())
302
+ client = MacStandardClient()
303
+ except AttributeError:
304
+ try:
305
+ bt = getattr(EX_BOARD_TYPE, board_type.upper())
306
+ client = MacExtendedClient()
307
+ except AttributeError:
308
+ raise click.BadParameter(f"无效板块类型: {board_type}")
309
+
310
+ if client.connect() is None:
311
+ raise click.ClickException("连接服务器失败")
312
+ try:
313
+ result = client.get_board_list(bt, count=count)
314
+ _output(result, json_fmt)
315
+ finally:
316
+ client.disconnect()
317
+
318
+
319
+ @cli.command(name='board-members')
320
+ @click.argument('symbol', default='881001')
321
+ @click.option('--count', default=20, type=int, help='数量')
322
+ @click.option('--sort', default='CHANGE_PCT', help='排序字段 (CODE/PRICE/VOLUME/AMOUNT/CHANGE_PCT/TURNOVER_RATE/ACTIVITY)')
323
+ @click.option('--order', default='DESC', help='排序方向 (ASC/DESC/NONE)')
324
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
325
+ def board_members(symbol, count, sort, order, json_fmt):
326
+ """板块成分股行情
327
+
328
+ opentdx board-members 880761 --count 10
329
+ opentdx board-members 881394 --sort VOLUME --count 20
330
+ opentdx board-members HK0281 (港股板块)
331
+ opentdx board-members US0495 (美股板块)
332
+ """
333
+ from opentdx.const import SORT_TYPE, SORT_ORDER
334
+
335
+ try:
336
+ st = getattr(SORT_TYPE, sort.upper())
337
+ except AttributeError:
338
+ raise click.BadParameter(f"无效排序字段: {sort}")
339
+ try:
340
+ so = getattr(SORT_ORDER, order.upper())
341
+ except AttributeError:
342
+ raise click.BadParameter(f"无效排序方向: {order}")
343
+
344
+ # 根据板块代码自动选择客户端
345
+ if symbol.upper().startswith('HK') or symbol.upper().startswith('US'):
346
+ client = MacExtendedClient()
347
+ else:
348
+ client = MacStandardClient()
349
+
350
+ if client.connect() is None:
351
+ raise click.ClickException("连接服务器失败")
352
+ try:
353
+ result = client.get_board_members_quotes(symbol, count=count, sort_type=st, sort_order=so)
354
+ _output(result, json_fmt)
355
+ finally:
356
+ client.disconnect()
357
+
358
+
359
+ @cli.command(name='s-bars')
360
+ @click.argument('market')
361
+ @click.argument('code')
362
+ @click.option('--period', default='DAILY', help='周期')
363
+ @click.option('--count', default=10, type=int, help='数量')
364
+ @click.option('--adjust', default='NONE', help='复权 (NONE/QFQ/HFQ)')
365
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
366
+ def s_bars(market, code, period, count, adjust, json_fmt):
367
+ """统一K线(A股/港股/美股通用)
368
+
369
+ opentdx s-bars SZ 000001 --period DAILY --count 10
370
+ opentdx s-bars HK_MAIN_BOARD 00700 --period DAILY
371
+ opentdx s-bars US_STOCK TSLA --period WEEKLY --count 20
372
+ """
373
+ mkt = _parse_market(market)
374
+ client = MacExtendedClient() if isinstance(mkt, EX_MARKET) else MacStandardClient()
375
+ if client.connect() is None:
376
+ raise click.ClickException("连接服务器失败")
377
+ try:
378
+ result = client.get_symbol_bars(mkt, code, _parse_period(period),
379
+ count=count, fq=_parse_adjust(adjust))
380
+ _output(result, json_fmt)
381
+ finally:
382
+ client.disconnect()
383
+
384
+
385
+ @cli.command(name='s-quotes')
386
+ @click.argument('codes')
387
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
388
+ def s_quotes(codes, json_fmt):
389
+ """统一报价(A股/港股/美股通用,可自定义字段)
390
+
391
+ opentdx s-quotes "SZ 000001, SH 600000"
392
+ opentdx s-quotes "US_STOCK TSLA, HK_MAIN_BOARD 00700"
393
+ """
394
+ pairs = _parse_codes(codes)
395
+ # 根据第一个 code 的市场类型选择客户端
396
+ mkt = pairs[0][0]
397
+ client = MacExtendedClient() if isinstance(mkt, EX_MARKET) else MacStandardClient()
398
+ if client.connect() is None:
399
+ raise click.ClickException("连接服务器失败")
400
+ try:
401
+ result = client.get_symbol_quotes(pairs)
402
+ _output(result['stocks'] if isinstance(result, dict) else result, json_fmt)
403
+ finally:
404
+ client.disconnect()
405
+
406
+
407
+ @cli.command()
408
+ @click.argument('market')
409
+ @click.option('--count', default=10, type=int, help='数量')
410
+ @click.option('--json', 'json_fmt', is_flag=True, default=False, help='JSON 输出')
411
+ def monitor(market, count, json_fmt):
412
+ """主力监控
413
+
414
+ opentdx monitor SH --count 10
415
+ opentdx monitor SZ --count 20
416
+ """
417
+ client = MacStandardClient()
418
+ if client.connect() is None:
419
+ raise click.ClickException("连接服务器失败")
420
+ try:
421
+ result = client.get_market_monitor(_parse_market(market), count=count)
422
+ _output(result, json_fmt)
423
+ finally:
424
+ client.disconnect()
425
+
426
+
427
+ if __name__ == '__main__':
428
+ cli()
@@ -0,0 +1,14 @@
1
+ from .standardClient import StandardClient
2
+ from .extendedClient import ExtendedClient
3
+ from .macStandardClient import MacStandardClient
4
+ from .macExtendedClient import MacExtendedClient
5
+
6
+ QuotationClient = StandardClient
7
+ exQuotationClient = ExtendedClient
8
+ macQuotationClient = MacStandardClient
9
+ macExQuotationClient = MacExtendedClient
10
+
11
+ __all__ = [
12
+ 'StandardClient', 'ExtendedClient', 'MacStandardClient', 'MacExtendedClient',
13
+ 'QuotationClient', 'exQuotationClient', 'macQuotationClient', 'macExQuotationClient',
14
+ ]
@@ -0,0 +1,83 @@
1
+ from concurrent.futures import Future
2
+
3
+ from .transport import Transport
4
+
5
+
6
+ class BaseClient:
7
+ """基础客户端,持有 Transport 实例并提供通用基础设施"""
8
+
9
+ def __init__(self, hosts, port=7709, multithread=False, heartbeat=False,
10
+ auto_retry=False, raise_exception=False, nonblocking=False):
11
+ self._t = Transport(multithread, heartbeat, auto_retry, raise_exception, nonblocking)
12
+ self._t.hosts = hosts
13
+ self._port = port
14
+
15
+ @property
16
+ def heartbeat_thread(self):
17
+ return self._t.heartbeat_thread
18
+
19
+ @property
20
+ def ip(self):
21
+ return self._t.ip
22
+
23
+ @property
24
+ def port(self):
25
+ return self._t.port
26
+
27
+ @property
28
+ def connected(self):
29
+ return self._t.connected
30
+
31
+ @property
32
+ def heartbeat(self):
33
+ return self._t.heartbeat
34
+
35
+ @property
36
+ def auto_retry(self):
37
+ return self._t.auto_retry
38
+
39
+ @auto_retry.setter
40
+ def auto_retry(self, value):
41
+ self._t.auto_retry = value
42
+
43
+ @property
44
+ def retry_strategy(self):
45
+ return self._t.retry_strategy
46
+
47
+ @retry_strategy.setter
48
+ def retry_strategy(self, value):
49
+ self._t.retry_strategy = value
50
+
51
+ @property
52
+ def raise_exception(self):
53
+ return self._t.raise_exception
54
+
55
+ @raise_exception.setter
56
+ def raise_exception(self, value):
57
+ self._t.raise_exception = value
58
+
59
+ def connect(self, ip=None, time_out=5, bind_port=None, bind_ip='0.0.0.0'):
60
+ self._t.set_heartbeat_callback(self._do_heartbeat if hasattr(self, '_do_heartbeat') else None)
61
+ result = self._t.connect(ip, self._port, time_out, bind_port, bind_ip)
62
+ return self if result is not None else None
63
+
64
+ def disconnect(self):
65
+ return self._t.disconnect()
66
+
67
+ def call(self, parser, timeout=None):
68
+ resp = self._t.send(parser.serialize())
69
+ if resp is None:
70
+ return None
71
+ if isinstance(resp, Future):
72
+ try:
73
+ resp = resp.result(timeout=timeout)
74
+ except Exception:
75
+ if self.raise_exception:
76
+ raise
77
+ return None
78
+ return parser.deserialize(resp)
79
+
80
+ def _download_file_impl(self, fetch_cls, filename: str, filesize=0, report_hook=None):
81
+ def fetch(fn, offset):
82
+ return self.call(fetch_cls(fn, offset))
83
+ return self._t.download_file(fetch, filename, filesize, report_hook)