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.
- stock_sdk-0.1.0/PKG-INFO +14 -0
- stock_sdk-0.1.0/README.md +0 -0
- stock_sdk-0.1.0/setup.cfg +4 -0
- stock_sdk-0.1.0/setup.py +21 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/__init__.py +35 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/cli.py +428 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/client/__init__.py +14 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/client/baseClient.py +83 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/client/extendedClient.py +111 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/client/macExtendedClient.py +12 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/client/macMixin.py +268 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/client/macStandardClient.py +12 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/client/standardClient.py +313 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/client/transport.py +391 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/commands/__init__.py +4 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/commands/doc_demo.py +620 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/commands/market_monitor.py +301 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/const.py +534 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/enums/__init__.py +5 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/enums/industry_code_enum.py +183 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/__init__.py +0 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/baseParser.py +32 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/__init__.py +39 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/category_list.py +41 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/chart_sampling.py +28 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/count.py +12 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/file.py +13 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/goods.py +90 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/history_tick_chart.py +32 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/history_transaction.py +35 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/kline.py +32 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/kline2.py +32 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/list.py +27 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/quotes.py +54 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/quotes2.py +18 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/quotes_list.py +10 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/quotes_single.py +15 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/server.py +54 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/table.py +16 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/table_detail.py +7 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/ex_quotation/tick_chart.py +47 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/GoodsList.py +37 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/__init__.py +36 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/board_list.py +41 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/board_members_quotes.py +14 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/file_query.py +38 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/kline_offset.py +27 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/server_info.py +81 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_auction.py +35 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_bar.py +61 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_belong_board.py +42 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_capital_flow.py +38 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_info.py +39 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_quotes.py +89 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_tick_chart.py +50 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_tick_charts.py +62 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/symbol_transaction.py +37 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/mac_quotation/unusual.py +45 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/__init__.py +63 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/auction.py +27 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/chart_sampling.py +29 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/company_info.py +227 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/count.py +17 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/file.py +35 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/history_orders.py +35 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/history_tick_chart.py +38 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/history_transaction.py +41 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/history_transaction_with_trans.py +42 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/index_info.py +79 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/index_momentum.py +25 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/kline.py +64 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/kline_offset.py +7 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/list.py +33 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/list2.py +31 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/quotes.py +16 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/quotes_detail.py +97 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/quotes_encrypt.py +105 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/quotes_list.py +100 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/server.py +195 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/stock.py +24 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/tick_chart.py +36 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/top_board.py +41 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/transaction.py +42 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/unusual.py +37 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/parser/quotation/volume_profile.py +101 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/tdxClient.py +1031 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/utils/__init__.py +0 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/utils/base_reader.py +20 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/utils/bitmap.py +282 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/utils/block_reader.py +135 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/utils/cache.py +52 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/utils/heartbeat.py +48 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/utils/help.py +328 -0
- stock_sdk-0.1.0/stock_sdk/opentdx/utils/log.py +22 -0
- stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/PKG-INFO +14 -0
- stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/SOURCES.txt +102 -0
- stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/dependency_links.txt +1 -0
- stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/requires.txt +4 -0
- stock_sdk-0.1.0/stock_sdk/stock_sdk.egg-info/top_level.txt +1 -0
- stock_sdk-0.1.0/stock_sdk.egg-info/PKG-INFO +14 -0
- stock_sdk-0.1.0/stock_sdk.egg-info/SOURCES.txt +0 -0
- stock_sdk-0.1.0/stock_sdk.egg-info/dependency_links.txt +1 -0
- stock_sdk-0.1.0/stock_sdk.egg-info/requires.txt +4 -0
- stock_sdk-0.1.0/stock_sdk.egg-info/top_level.txt +1 -0
stock_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
stock_sdk-0.1.0/setup.py
ADDED
|
@@ -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)
|