├── README.md ├── __init__.py ├── __pycache__ ├── __init__.cpython-37.pyc ├── base_socket_client.cpython-37.pyc ├── errors.cpython-37.pyc ├── exhq.cpython-37.pyc ├── heartbeat.cpython-37.pyc ├── helper.cpython-37.pyc ├── hq.cpython-37.pyc ├── log.cpython-37.pyc └── params.cpython-37.pyc ├── base_socket_client.py ├── bin ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── get_tdx_trader_server.cpython-37.pyc │ ├── hqbenchmark.cpython-37.pyc │ ├── hqget.cpython-37.pyc │ └── hqreader.cpython-37.pyc ├── get_tdx_trader_server.py ├── hqbenchmark.py ├── hqget.py └── hqreader.py ├── config ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── hosts.cpython-37.pyc └── hosts.py ├── crawler ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── base_crawler.cpython-37.pyc │ └── history_financial_crawler.cpython-37.pyc ├── base_crawler.py └── history_financial_crawler.py ├── errors.py ├── exhq.py ├── heartbeat.py ├── helper.py ├── hq.py ├── log.py ├── params.py ├── parser ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── base.cpython-37.pyc │ ├── ex_get_history_instrument_bars_range.cpython-37.pyc │ ├── ex_get_history_minute_time_data.cpython-37.pyc │ ├── ex_get_history_transaction_data.cpython-37.pyc │ ├── ex_get_instrument_bars.cpython-37.pyc │ ├── ex_get_instrument_count.cpython-37.pyc │ ├── ex_get_instrument_info.cpython-37.pyc │ ├── ex_get_instrument_quote.cpython-37.pyc │ ├── ex_get_instrument_quote_list.cpython-37.pyc │ ├── ex_get_markets.cpython-37.pyc │ ├── ex_get_minute_time_data.cpython-37.pyc │ ├── ex_get_transaction_data.cpython-37.pyc │ ├── ex_setup_commands.cpython-37.pyc │ ├── get_block_info.cpython-37.pyc │ ├── get_company_info_category.cpython-37.pyc │ ├── get_company_info_content.cpython-37.pyc │ ├── get_finance_info.cpython-37.pyc │ ├── get_history_minute_time_data.cpython-37.pyc │ ├── get_history_transaction_data.cpython-37.pyc │ ├── get_index_bars.cpython-37.pyc │ ├── get_minute_time_data.cpython-37.pyc │ ├── get_report_file.cpython-37.pyc │ ├── get_security_bars.cpython-37.pyc │ ├── get_security_count.cpython-37.pyc │ ├── get_security_list.cpython-37.pyc │ ├── get_security_quotes.cpython-37.pyc │ ├── get_transaction_data.cpython-37.pyc │ ├── get_xdxr_info.cpython-37.pyc │ ├── raw_parser.cpython-37.pyc │ └── setup_commands.cpython-37.pyc ├── base.py ├── ex_get_history_instrument_bars_range.py ├── ex_get_history_minute_time_data.py ├── ex_get_history_transaction_data.py ├── ex_get_instrument_bars.py ├── ex_get_instrument_count.py ├── ex_get_instrument_info.py ├── ex_get_instrument_quote.py ├── ex_get_instrument_quote_list.py ├── ex_get_markets.py ├── ex_get_minute_time_data.py ├── ex_get_transaction_data.py ├── ex_setup_commands.py ├── get_block_info.py ├── get_company_info_category.py ├── get_company_info_content.py ├── get_finance_info.py ├── get_history_minute_time_data.py ├── get_history_transaction_data.py ├── get_index_bars.py ├── get_minute_time_data.py ├── get_report_file.py ├── get_security_bars.py ├── get_security_count.py ├── get_security_list.py ├── get_security_quotes.py ├── get_transaction_data.py ├── get_xdxr_info.py ├── raw_parser.py └── setup_commands.py ├── pool ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── hqpool.cpython-37.pyc │ └── ippool.cpython-37.pyc ├── hqpool.py └── ippool.py ├── reader ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── base_reader.cpython-37.pyc │ ├── block_reader.cpython-37.pyc │ ├── daily_bar_reader.cpython-37.pyc │ ├── exhq_daily_bar_reader.cpython-37.pyc │ ├── gbbq_reader.cpython-37.pyc │ ├── history_financial_reader.cpython-37.pyc │ ├── lc_min_bar_reader.cpython-37.pyc │ └── min_bar_reader.cpython-37.pyc ├── base_reader.py ├── block_reader.py ├── daily_bar_reader.py ├── exhq_daily_bar_reader.py ├── gbbq_reader.py ├── history_financial_reader.py ├── lc_min_bar_reader.py └── min_bar_reader.py ├── trade ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── trade.cpython-37.pyc └── trade.py └── util ├── __init__.py ├── __pycache__ ├── __init__.cpython-37.pyc ├── best_ip.cpython-37.pyc ├── date_util.cpython-37.pyc └── trade_date.cpython-37.pyc ├── best_ip.py ├── date_util.py └── trade_date.py /README.md: -------------------------------------------------------------------------------- 1 | # pytdx_backup 2 | 1. 看到原作者rainx把它的pytdx代码archived了,为了让他的代码继续发光发热,开一个远程库备份一下。 3 | 2. 原作者的说明文档在这里:https://rainx.gitbooks.io/pytdx/content/ 4 | 3. 备份说明文档在这里:https://counsel-chai.gitbook.io/pytdx-1/ 5 | 4. 原作者代码地址:https://github.com/rainx/pytdx 6 | 5. 原readme文档地址:https://github.com/rainx/pytdx/commit/14b1ad3534593952d1d698ffa706f4f13a4ed156#diff-04c6e90faac2675aa89e2176d2eec7d8 7 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__init__.py -------------------------------------------------------------------------------- /__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/base_socket_client.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/base_socket_client.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/errors.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/errors.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/exhq.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/exhq.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/heartbeat.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/heartbeat.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/helper.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/helper.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/hq.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/hq.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/log.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/log.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/params.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/__pycache__/params.cpython-37.pyc -------------------------------------------------------------------------------- /base_socket_client.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # 4 | # Just for practising 5 | # 6 | 7 | 8 | import os 9 | import socket 10 | import sys 11 | import pandas as pd 12 | 13 | if __name__ == '__main__': 14 | sys.path.insert(0, os.path.dirname( 15 | os.path.dirname(os.path.realpath(__file__)))) 16 | 17 | from pytdx.log import DEBUG, log 18 | from pytdx.errors import TdxConnectionError, TdxFunctionCallError 19 | 20 | import threading 21 | import datetime 22 | import time 23 | from pytdx.heartbeat import HqHeartBeatThread 24 | import functools 25 | from pytdx.parser.raw_parser import RawParser 26 | 27 | 28 | CONNECT_TIMEOUT = 5.000 29 | RECV_HEADER_LEN = 0x10 30 | DEFAULT_HEARTBEAT_INTERVAL = 10.0 31 | 32 | 33 | """ 34 | In [7]: 0x7e 35 | Out[7]: 126 36 | 37 | In [5]: len(body) 38 | Out[5]: 8066 39 | 40 | In [6]: len(body)/126 41 | Out[6]: 64.01587301587301 42 | 43 | In [7]: len(body)%126 44 | Out[7]: 2 45 | 46 | In [8]: (len(body)-2)/126 47 | Out[8]: 64.0 48 | """ 49 | 50 | 51 | def update_last_ack_time(func): 52 | @functools.wraps(func) 53 | def wrapper(self, *args, **kw): 54 | self.last_ack_time = time.time() 55 | log.debug("last ack time update to " + str(self.last_ack_time)) 56 | current_exception = None 57 | try: 58 | ret = func(self, *args, **kw) 59 | except Exception as e: 60 | current_exception = e 61 | log.debug("hit exception on req exception is " + str(e)) 62 | if self.auto_retry: 63 | for time_interval in self.retry_strategy.gen(): 64 | try: 65 | time.sleep(time_interval) 66 | self.disconnect() 67 | self.connect(self.ip, self.port) 68 | ret = func(self, *args, **kw) 69 | if ret: 70 | return ret 71 | except Exception as retry_e: 72 | current_exception = retry_e 73 | log.debug( 74 | "hit exception on *retry* req exception is " + str(retry_e)) 75 | 76 | log.debug("perform auto retry on req ") 77 | 78 | self.last_transaction_failed = True 79 | ret = None 80 | if self.raise_exception: 81 | to_raise = TdxFunctionCallError("calling function error") 82 | to_raise.original_exception = current_exception if current_exception else None 83 | raise to_raise 84 | """ 85 | 如果raise_exception=True 抛出异常 86 | 如果raise_exception=False 返回None 87 | """ 88 | return ret 89 | return wrapper 90 | 91 | 92 | class RetryStrategy(object): 93 | @classmethod 94 | def gen(cls): 95 | raise NotImplementedError("need to override") 96 | 97 | 98 | class DefaultRetryStrategy(RetryStrategy): 99 | """ 100 | 默认的重试策略,您可以通过写自己的重试策略替代本策略, 改策略主要实现gen方法,该方法是一个生成器, 101 | 返回下次重试的间隔时间, 单位为秒,我们会使用 time.sleep在这里同步等待之后进行重新connect,然后再重新发起 102 | 源请求,直到gen结束。 103 | """ 104 | @classmethod 105 | def gen(cls): 106 | # 默认重试4次 ... 时间间隔如下 107 | for time_interval in [0.1, 0.5, 1, 2]: 108 | yield time_interval 109 | 110 | 111 | class TrafficStatSocket(socket.socket): 112 | """ 113 | 实现支持流量统计的socket类 114 | """ 115 | 116 | def __init__(self, sock, mode): 117 | super(TrafficStatSocket, self).__init__(sock, mode) 118 | # 流量统计相关 119 | self.send_pkg_num = 0 # 发送次数 120 | self.recv_pkg_num = 0 # 接收次数 121 | self.send_pkg_bytes = 0 # 发送字节 122 | self.recv_pkg_bytes = 0 # 接收字节数 123 | self.first_pkg_send_time = None # 第一个数据包发送时间 124 | 125 | self.last_api_send_bytes = 0 # 最近的一次api调用的发送字节数 126 | self.last_api_recv_bytes = 0 # 最近一次api调用的接收字节数 127 | 128 | 129 | class BaseSocketClient(object): 130 | 131 | def __init__(self, multithread=False, heartbeat=False, auto_retry=False, raise_exception=False): 132 | self.need_setup = True 133 | if multithread or heartbeat: 134 | self.lock = threading.Lock() 135 | else: 136 | self.lock = None 137 | 138 | self.client = None 139 | self.heartbeat = heartbeat 140 | self.heartbeat_thread = None 141 | self.stop_event = None 142 | self.heartbeat_interval = DEFAULT_HEARTBEAT_INTERVAL # 默认10秒一个心跳包 143 | self.last_ack_time = time.time() 144 | self.last_transaction_failed = False 145 | self.ip = None 146 | self.port = None 147 | 148 | # 是否重试 149 | self.auto_retry = auto_retry 150 | # 可以覆盖这个属性,使用新的重试策略 151 | self.retry_strategy = DefaultRetryStrategy() 152 | # 是否在函数调用出错的时候抛出异常 153 | self.raise_exception = raise_exception 154 | 155 | def connect(self, ip='101.227.73.20', port=7709, time_out=CONNECT_TIMEOUT, bindport=None, bindip='0.0.0.0'): 156 | """ 157 | 158 | :param ip: 服务器ip 地址 159 | :param port: 服务器端口 160 | :param time_out: 连接超时时间 161 | :param bindport: 绑定的本地端口 162 | :param bindip: 绑定的本地ip 163 | :return: 是否连接成功 True/False 164 | """ 165 | 166 | self.client = TrafficStatSocket(socket.AF_INET, socket.SOCK_STREAM) 167 | self.client.settimeout(time_out) 168 | log.debug("connecting to server : %s on port :%d" % (ip, port)) 169 | try: 170 | self.ip = ip 171 | self.port = port 172 | if bindport is not None: 173 | self.client.bind((bindip, bindport)) 174 | self.client.connect((ip, port)) 175 | except socket.timeout as e: 176 | # print(str(e)) 177 | log.debug("connection expired") 178 | if self.raise_exception: 179 | raise TdxConnectionError("connection timeout error") 180 | return False 181 | except Exception as e: 182 | if self.raise_exception: 183 | raise TdxConnectionError("other errors") 184 | return False 185 | 186 | log.debug("connected!") 187 | 188 | if self.need_setup: 189 | self.setup() 190 | 191 | if self.heartbeat: 192 | self.stop_event = threading.Event() 193 | self.heartbeat_thread = HqHeartBeatThread( 194 | self, self.stop_event, self.heartbeat_interval) 195 | self.heartbeat_thread.start() 196 | return self 197 | 198 | def disconnect(self): 199 | 200 | if self.heartbeat_thread and \ 201 | self.heartbeat_thread.is_alive(): 202 | self.stop_event.set() 203 | 204 | if self.client: 205 | log.debug("disconnecting") 206 | try: 207 | self.client.shutdown(socket.SHUT_RDWR) 208 | self.client.close() 209 | self.client = None 210 | except Exception as e: 211 | log.debug(str(e)) 212 | if self.raise_exception: 213 | raise TdxConnectionError("disconnect err") 214 | log.debug("disconnected") 215 | 216 | def close(self): 217 | """ 218 | disconnect的别名,为了支持 with closing(obj): 语法 219 | :return: 220 | """ 221 | self.disconnect() 222 | 223 | def get_traffic_stats(self): 224 | """ 225 | 获取流量统计的信息 226 | :return: 227 | """ 228 | if self.client.first_pkg_send_time is not None: 229 | total_seconds = (datetime.datetime.now() - 230 | self.client.first_pkg_send_time).total_seconds() 231 | if total_seconds != 0: 232 | send_bytes_per_second = self.client.send_pkg_bytes // total_seconds 233 | recv_bytes_per_second = self.client.recv_pkg_bytes // total_seconds 234 | else: 235 | send_bytes_per_second = None 236 | recv_bytes_per_second = None 237 | else: 238 | total_seconds = None 239 | send_bytes_per_second = None 240 | recv_bytes_per_second = None 241 | 242 | return { 243 | "send_pkg_num": self.client.send_pkg_num, 244 | "recv_pkg_num": self.client.recv_pkg_num, 245 | "send_pkg_bytes": self.client.send_pkg_bytes, 246 | "recv_pkg_bytes": self.client.recv_pkg_bytes, 247 | "first_pkg_send_time": self.client.first_pkg_send_time, 248 | "total_seconds": total_seconds, 249 | "send_bytes_per_second": send_bytes_per_second, 250 | "recv_bytes_per_second": recv_bytes_per_second, 251 | "last_api_send_bytes": self.client.last_api_send_bytes, 252 | "last_api_recv_bytes": self.client.last_api_recv_bytes, 253 | } 254 | 255 | # for debuging and testing protocol 256 | def send_raw_pkg(self, pkg): 257 | cmd = RawParser(self.client, lock=self.lock) 258 | cmd.setParams(pkg) 259 | return cmd.call_api() 260 | 261 | def __enter__(self): 262 | return self 263 | 264 | def __exit__(self, exc_type, exc_val, exc_tb): 265 | self.close() 266 | 267 | def to_df(self, v): 268 | if isinstance(v, list): 269 | return pd.DataFrame(data=v) 270 | elif isinstance(v, dict): 271 | return pd.DataFrame(data=[v, ]) 272 | else: 273 | return pd.DataFrame(data=[{'value': v}]) 274 | -------------------------------------------------------------------------------- /bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/bin/__init__.py -------------------------------------------------------------------------------- /bin/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/bin/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /bin/__pycache__/get_tdx_trader_server.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/bin/__pycache__/get_tdx_trader_server.cpython-37.pyc -------------------------------------------------------------------------------- /bin/__pycache__/hqbenchmark.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/bin/__pycache__/hqbenchmark.cpython-37.pyc -------------------------------------------------------------------------------- /bin/__pycache__/hqget.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/bin/__pycache__/hqget.cpython-37.pyc -------------------------------------------------------------------------------- /bin/__pycache__/hqreader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/bin/__pycache__/hqreader.cpython-37.pyc -------------------------------------------------------------------------------- /bin/get_tdx_trader_server.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | from __future__ import unicode_literals 4 | 5 | import os 6 | import tempfile 7 | import click 8 | import struct 9 | import six 10 | import zipfile 11 | import uuid 12 | import shutil 13 | 14 | if six.PY2: 15 | from urllib import urlretrieve 16 | else: 17 | from urllib.request import urlretrieve 18 | 19 | 20 | 21 | TRADE_DLL_KEY = "http://rainx1982.coding.me/tts/Trade.dll" 22 | TDX_TRADE_SEVER_KEY = "http://rainx1982.coding.me/tts/TdxTradeServer-Release-1.8_20180326103850.zip" 23 | 24 | 25 | 26 | def main(): 27 | 28 | # 1 give me a tmp dir 29 | 30 | base_dir = tempfile.gettempdir() 31 | dll_path = os.path.join(base_dir, "dll") 32 | download_path = os.path.join(base_dir, "download") 33 | try: 34 | if not os.path.isdir(dll_path): 35 | os.makedirs(dll_path) 36 | finally: 37 | pass 38 | 39 | try: 40 | if not os.path.isdir(download_path): 41 | os.makedirs(download_path) 42 | finally: 43 | pass 44 | 45 | # 2 确认是否要安装 46 | 47 | to_say = """ 48 | 你好,您执行本命令将会启动TdxTradeServer程序的安装流程,安装程序会安装TdxTradeServer以及配置好其依赖的trade.dll, 49 | 50 | 注意: trade.dll来源于网络,TdxTradeServer仅对trade.dll做简单的封装,使其可以用于rest api ,并提供pytdx调用。 51 | 本程序没有对通达信的传输协议做任何研究,所有trade.dll和其绑定方式来源于网络。 52 | 53 | [rest ] TdxTradeServer : https://github.com/rainx/TdxTradeServer 54 | [client api ] pytdx : https://github.com/rainx/pytdx 55 | 56 | 是否继续,将下载对应的trade.dll并配置。 57 | 58 | Created by rainx with love! 59 | 60 | """ 61 | click.secho(to_say, fg='green') 62 | 63 | yes_to_continue() 64 | 65 | se("开始下载trade.dll...") 66 | trade_dll_template = os.path.join(dll_path, "trade.dll") 67 | urlretrieve(TRADE_DLL_KEY, trade_dll_template) 68 | se("下载完成....") 69 | 70 | se("为了可以使用trade.dll,需要绑定账号") 71 | acc = click.prompt("请输入您的账号") 72 | se("您输入的账号是 {}".format(acc), fg="green") 73 | sig = make_sig(acc) 74 | se("正在生成可用的trade.dll绑定:sig is [{}]".format(sig)) 75 | with open(trade_dll_template, 'rb') as f: 76 | content = f.read() 77 | 78 | real_trade_dll_name = "trade_pytdx_{}.dll".format(acc) 79 | real_trade_dll_path = os.path.join(dll_path, real_trade_dll_name) 80 | lenof_sig = len(sig) 81 | 82 | with open(real_trade_dll_path, "wb") as f: 83 | start_offset = 1132713 84 | f.write(content[:start_offset]) 85 | f.write(sig) 86 | f.write(content[start_offset + lenof_sig:]) 87 | se("写入完成,文件名称为 : {}".format(real_trade_dll_path)) 88 | 89 | se("开始下载TdxTradeServer....") 90 | download_and_setup_tdx_trade_server(download_path, dll_path, real_trade_dll_name) 91 | 92 | 93 | def download_and_setup_tdx_trade_server(download_path, dll_path, real_trade_dll_name): 94 | zip_file_path = os.path.join(download_path, "tts.zip") 95 | urlretrieve(TDX_TRADE_SEVER_KEY, zip_file_path) 96 | print(download_path) 97 | 98 | if os.path.isfile(zip_file_path): 99 | se("下载完成") 100 | else: 101 | raise SystemExit("下载失败") 102 | 103 | se("开始解压") 104 | zf = zipfile.ZipFile(file=zip_file_path) 105 | zf.extractall(dll_path) 106 | zf.close() 107 | se("解压完成") 108 | 109 | config_file_content, bind_ip, bind_port, enc_key, enc_iv = gen_config_file(real_trade_dll_name) 110 | 111 | config_file_name = "TdxTradeServer.ini" 112 | with open(os.path.join(dll_path, config_file_name), "w") as f: 113 | f.write(config_file_content) 114 | se("配置文件写入完成,文件名 TdxTradeServer.ini") 115 | while True: 116 | _dir = click.prompt("请选择程序放置的路径", "C:\\TdxTradeServer") 117 | if os.path.exists(_dir): 118 | click.secho("该目录已存在,请选择一个新的路径") 119 | else: 120 | break 121 | 122 | os.makedirs(_dir) 123 | os.rmdir(_dir) 124 | shutil.copytree(dll_path, _dir) 125 | se("复制完成! 请在路径 {} 下运行 TdxTradeServer.exe 启动服务".format(_dir), fg="green") 126 | 127 | se("客户端您可以使用pytdx的trade模块进行连接,下面是一小段示例代码演示如何初始化对象") 128 | 129 | demo_code = """ 130 | import os 131 | from pytdx.trade import TdxTradeApi 132 | api = TdxTradeApi(endpoint="http://{}:{}/api", enc_key=b"{}", enc_iv=b"{}") 133 | print("---Ping---") 134 | result = api.ping() 135 | print(result) 136 | 137 | print("---登入---") 138 | acc = os.getenv("TDX_ACCOUNT", "") ###### 你的账号 139 | password = os.getenv("TDX_PASS", "") ###### 你的密码 140 | result = api.logon("", 7708, 141 | "8.23", 32, 142 | acc, acc, password, "") 143 | 144 | print(result) 145 | 146 | if result["success"]: 147 | client_id = result["data"]["client_id"] 148 | 149 | for i in (0,1,2,3,4,5,6,7,8,12,13,14,15): 150 | print("---查询信息 cate=%d--" % i) 151 | print(api.data_to_df(api.query_data(client_id, i))) 152 | 153 | 154 | print("---查询报价---") 155 | print(api.data_to_df(api.get_quote(client_id, '600315'))) 156 | 157 | print("---登出---") 158 | print(api.logoff(client_id)) 159 | """.format(bind_ip, bind_port, enc_key, enc_iv) 160 | 161 | demo_sample = """ 162 | from pytdx.trade import TdxTradeApi 163 | api = TdxTradeApi(endpoint="http://{}:{}/api", enc_key=b"{}", enc_iv=b"{}") 164 | """.format(bind_ip, bind_port, enc_key, enc_iv) 165 | 166 | print("-"*30) 167 | print(demo_sample) 168 | print("-"*30) 169 | 170 | demo_path = os.path.join(_dir, "demo.py") 171 | with open(demo_path, "w") as f: 172 | f.write(demo_code) 173 | se("pytdx demo 演示代码在 {}".format(demo_path),fg="blue") 174 | se("注意 v1.5版本之后已经支持多账号版本,关于如何配置使用多账号版本,请参考 https://github.com/rainx/TdxTradeServer", fg="red") 175 | se("Happy Trading!", fg="green") 176 | 177 | 178 | def gen_config_file(real_trade_dll_name): 179 | se("开始生成配置文件..") 180 | random_uuid = uuid.uuid1().hex 181 | enc_key = random_uuid[:16] 182 | enc_iv = random_uuid[16:] 183 | se("生成的enc_key = [{}] , enc_iv = [{}]".format(enc_key, enc_iv)) 184 | bind_ip = click.prompt('请输入绑定的ip地址', default="127.0.0.1") 185 | bind_port = click.prompt('请输入绑定的端口号', default="19820") 186 | config_file_content = """bind={} 187 | port={} 188 | trade_dll_path={} 189 | transport_enc_key={} 190 | transport_enc_iv={} 191 | """.format(bind_ip, bind_port, real_trade_dll_name, enc_key, enc_iv) 192 | 193 | return config_file_content, bind_ip, bind_port, enc_key, enc_iv 194 | 195 | 196 | 197 | def yes_to_continue(): 198 | while True: 199 | c = click.prompt('是否继续,继续请输入y, 退出输入n? ', default="y") 200 | if c.lower() == 'n': 201 | click.secho("您选择了退出") 202 | raise SystemExit("need to exit") 203 | elif c.lower() == "y" or c == "": 204 | return 205 | 206 | def make_sig(acc): 207 | 208 | if type(acc) is six.text_type: 209 | acc = acc.encode("utf-8") 210 | 211 | a3 = 0x55e 212 | # 奇数位 213 | gpdm = acc[::2] 214 | # print("奇数位 :{}".format(gpdm)) 215 | 216 | result = b"" 217 | for c in gpdm: 218 | 219 | if six.PY2: 220 | (c,) = struct.unpack("b", c) 221 | 222 | _next = True 223 | a = c 224 | b = a3 >> 0x8 225 | c = a ^ b 226 | a3 = (0x207f * (a3 + c) - 0x523d) & 0xffff 227 | j = 64 228 | while _next: 229 | j += 1 230 | if j > 90: 231 | break 232 | k = 91 233 | while _next: 234 | k -= 1 235 | if k < 65: 236 | break 237 | 238 | temp = 1755 + c - k 239 | if temp % 26 == 0 and temp // 26 == j: 240 | 241 | result += struct.pack("bb", j, k) 242 | _next = False 243 | return result 244 | 245 | 246 | def se(*args, **kwargs): 247 | _args = list(args) 248 | _args[0] = "[ pytdx ] " + _args[0] 249 | click.secho(*_args, **kwargs) 250 | 251 | if __name__ == '__main__': 252 | try: 253 | main() 254 | # gen_config_file() 255 | except SystemExit: 256 | exit() 257 | 258 | -------------------------------------------------------------------------------- /bin/hqbenchmark.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import click 3 | 4 | from concurrent.futures import ThreadPoolExecutor 5 | from pytdx.hq import TdxHq_API 6 | import datetime 7 | import time 8 | from pytdx.util.best_ip import select_best_ip 9 | 10 | GET_QUOTES_PER_GROUPS=80 11 | 12 | @click.command() 13 | @click.argument("ips", nargs=-1) 14 | def main(ips): 15 | 16 | if len(ips) == 0: 17 | best_ip = select_best_ip() 18 | ips = [best_ip] 19 | print("Using default ip: {}".format(best_ip)) 20 | 21 | def single_client_benchmark(ip): 22 | 23 | def _log(msg): 24 | click.echo("HQ_BENCHMARK: [{:15s}] {} ".format(ip, datetime.datetime.now()) + msg) 25 | 26 | def _grouped_list(stocks): 27 | return [stocks[i:i + GET_QUOTES_PER_GROUPS] for i in range(0, len(stocks), GET_QUOTES_PER_GROUPS)] 28 | 29 | _log("start benchmark") 30 | 31 | total_time = connecting_time = get_security_count_time = get_security_list_time = get_security_quotes_time = num = 0 32 | 33 | start_time = time.time() 34 | last_time = start_time 35 | 36 | try: 37 | api = TdxHq_API(multithread=True) 38 | 39 | port = 7709 40 | 41 | if ":" in ip: 42 | ip, port = ip.split(':') 43 | port = int(port) 44 | 45 | with api.connect(ip=ip, port=port): 46 | _log("connected") 47 | cur_time = time.time() 48 | connecting_time = cur_time - last_time 49 | last_time = cur_time 50 | _log("connecting time is {}".format(connecting_time)) 51 | 52 | num = api.get_security_count(0) 53 | _log("all shenzhen market stock count is {}".format(num)) 54 | 55 | cur_time = time.time() 56 | get_security_count_time = cur_time - last_time 57 | last_time = cur_time 58 | _log("get_security_count_time is {}".format(get_security_count_time)) 59 | 60 | all = [] 61 | for i in range((num // 1000) + 1): 62 | offset = i * 1000 63 | section = api.get_security_list(0, offset) 64 | all = all + section 65 | 66 | cur_time = time.time() 67 | get_security_list_time = cur_time - last_time 68 | last_time = cur_time 69 | 70 | _log("get_security_list_time is {}".format(get_security_list_time)) 71 | 72 | codes = [one['code'] for one in all] 73 | 74 | results = [] 75 | for stocks in _grouped_list(codes): 76 | req_list = [(0, code) for code in stocks] 77 | one_results = api.get_security_quotes(req_list) 78 | results = results + one_results 79 | 80 | cur_time = time.time() 81 | get_security_quotes_time = cur_time - last_time 82 | last_time = cur_time 83 | _log("get_security_quotes_time is {}".format(get_security_quotes_time)) 84 | 85 | total_time = last_time - start_time 86 | 87 | _log("total_time is {}".format(total_time)) 88 | 89 | _log("end benchmark") 90 | except Exception as e: 91 | _log("hit exception " + str(e)) 92 | 93 | return { 94 | "ip": ip, 95 | "total_time": total_time, 96 | "connecting_time": connecting_time, 97 | "get_security_count_time": get_security_count_time, 98 | "get_security_list_time": get_security_list_time, 99 | "get_security_quotes_time": get_security_quotes_time, 100 | "security_count": num 101 | } 102 | 103 | with ThreadPoolExecutor(max_workers=len(ips)) as executor: 104 | results = executor.map(single_client_benchmark, ips) 105 | 106 | 107 | 108 | rows = [] 109 | rows.append(("IP", "Total", "Connecting", "Get Count", "Get List", "Get Quotes")) 110 | for result in results: 111 | rows.append( 112 | [result['ip'], 113 | 114 | "{:0.6f}".format(result['total_time']), 115 | "{:0.6f}".format(result['connecting_time']), 116 | 117 | "{:0.6f} ({})".format(result['get_security_count_time'], result['security_count']), 118 | "{:0.6f}".format(result['get_security_list_time']), 119 | 120 | "{:0.6f}".format(result['get_security_quotes_time'])] 121 | ) 122 | 123 | print("=" * 40) 124 | print_table(rows) 125 | 126 | 127 | 128 | # helper function from http://blog.paphus.com/blog/2012/09/04/simple-ascii-tables-in-python/ 129 | def print_table(lines, separate_head=True): 130 | """Prints a formatted table given a 2 dimensional array""" 131 | #Count the column width 132 | widths = [] 133 | for line in lines: 134 | for i,size in enumerate([len(x) for x in line]): 135 | while i >= len(widths): 136 | widths.append(0) 137 | if size > widths[i]: 138 | widths[i] = size 139 | 140 | #Generate the format string to pad the columns 141 | print_string = "" 142 | for i,width in enumerate(widths): 143 | print_string += "{" + str(i) + ":" + str(width) + "} | " 144 | if (len(print_string) == 0): 145 | return 146 | print_string = print_string[:-3] 147 | 148 | #Print the actual data 149 | for i,line in enumerate(lines): 150 | print(print_string.format(*line)) 151 | if (i == 0 and separate_head): 152 | print("-"*(sum(widths)+3*(len(widths)-1))) 153 | 154 | 155 | if __name__ == '__main__': 156 | main() -------------------------------------------------------------------------------- /bin/hqreader.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from __future__ import unicode_literals, division 3 | import click 4 | import sys 5 | import os 6 | if __name__ == '__main__': 7 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) 8 | 9 | from pytdx.reader import TdxDailyBarReader, TdxFileNotFoundException, TdxNotAssignVipdocPathException 10 | from pytdx.reader import TdxMinBarReader 11 | from pytdx.reader import TdxLCMinBarReader 12 | from pytdx.reader import TdxExHqDailyBarReader 13 | from pytdx.reader import GbbqReader 14 | from pytdx.reader import BlockReader 15 | from pytdx.reader import CustomerBlockReader 16 | from pytdx.reader.history_financial_reader import HistoryFinancialReader 17 | import pandas as pd 18 | 19 | # 让pandas 显示全部数据 20 | pd.set_option('display.max_columns', None) 21 | pd.set_option('display.max_rows', None) 22 | 23 | 24 | Help_Text = ''' 25 | 数据文件格式, 26 | - daily 代表日K线 27 | - ex_daily 代表扩展行情的日线 28 | - min 代表5分钟或者1分钟线 29 | - lc 代表lc1, lc5格式的分钟线 30 | - gbbq 股本变迁文件 31 | - block 读取板块股票列表文件 32 | - customblock 读取自定义板块列表 33 | - history_financial 或者 hf 历史财务信息 如 gpcw20170930.dat 或者 gpcw20170930.zip 34 | ''' 35 | 36 | @click.command() 37 | @click.argument("input", type=click.Path(exists=True)) 38 | @click.option("-o", '--output', help="") 39 | @click.option("-d", "--datatype", default="daily", help=Help_Text) 40 | def main(input, output, datatype): 41 | """ 42 | 通达信数据文件读取 43 | """ 44 | 45 | if datatype == 'daily': 46 | reader = TdxDailyBarReader() 47 | elif datatype == 'ex_daily': 48 | reader = TdxExHqDailyBarReader() 49 | elif datatype == 'lc': 50 | reader = TdxLCMinBarReader() 51 | elif datatype == 'gbbq': 52 | reader = GbbqReader() 53 | elif datatype == 'block': 54 | reader = BlockReader() 55 | elif datatype == 'customblock': 56 | reader = CustomerBlockReader() 57 | elif datatype == 'history_financial' or datatype == 'hf': 58 | reader = HistoryFinancialReader() 59 | else: 60 | reader = TdxMinBarReader() 61 | 62 | try: 63 | df = reader.get_df(input) 64 | if output: 65 | click.echo("写入到文件 : " + output) 66 | df.to_csv(output) 67 | else: 68 | print(df) 69 | except Exception as e: 70 | print(str(e)) 71 | 72 | if __name__ == '__main__': 73 | main() -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/config/__init__.py -------------------------------------------------------------------------------- /config/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/config/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /config/__pycache__/hosts.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/config/__pycache__/hosts.cpython-37.pyc -------------------------------------------------------------------------------- /config/hosts.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | 5 | hq_hosts = [ 6 | ("长城国瑞电信1", "218.85.139.19", 7709), 7 | ("长城国瑞电信2", "218.85.139.20", 7709), 8 | ("长城国瑞网通", "58.23.131.163", 7709), 9 | ("上证云成都电信一", "218.6.170.47", 7709), 10 | ("上证云北京联通一", "123.125.108.14", 7709), 11 | ("上海电信主站Z1", "180.153.18.170", 7709), 12 | ("上海电信主站Z2", "180.153.18.171", 7709), 13 | ("上海电信主站Z80", "180.153.18.172", 80), 14 | ("北京联通主站Z1", "202.108.253.130", 7709), 15 | ("北京联通主站Z2", "202.108.253.131", 7709), 16 | ("北京联通主站Z80", "202.108.253.139", 80), 17 | ("杭州电信主站J1", "60.191.117.167", 7709), 18 | ("杭州电信主站J2", "115.238.56.198", 7709), 19 | ("杭州电信主站J3", "218.75.126.9", 7709), 20 | ("杭州电信主站J4", "115.238.90.165", 7709), 21 | ("杭州联通主站J1", "124.160.88.183", 7709), 22 | ("杭州联通主站J2", "60.12.136.250", 7709), 23 | ("杭州华数主站J1", "218.108.98.244", 7709), 24 | ("杭州华数主站J2", "218.108.47.69", 7709), 25 | ("义乌移动主站J1", "223.94.89.115", 7709), 26 | ("青岛联通主站W1", "218.57.11.101", 7709), 27 | ("青岛电信主站W1", "58.58.33.123", 7709), 28 | ("深圳电信主站Z1", "14.17.75.71", 7709), 29 | ("云行情上海电信Z1", "114.80.63.12", 7709), 30 | ("云行情上海电信Z2", "114.80.63.35", 7709), 31 | ("上海电信主站Z3", "180.153.39.51", 7709), 32 | ('招商证券深圳行情', '119.147.212.81', 7709), 33 | ('华泰证券(南京电信)', '221.231.141.60', 7709), 34 | ('华泰证券(上海电信)', '101.227.73.20', 7709), 35 | ('华泰证券(上海电信二)', '101.227.77.254', 7709), 36 | ('华泰证券(深圳电信)', '14.215.128.18', 7709), 37 | ('华泰证券(武汉电信)', '59.173.18.140', 7709), 38 | ('华泰证券(天津联通)', '60.28.23.80', 7709), 39 | ('华泰证券(沈阳联通)', '218.60.29.136', 7709), 40 | ('华泰证券(南京联通)', '122.192.35.44', 7709), 41 | ('华泰证券(南京联通)', '122.192.35.44', 7709), 42 | ('安信', '112.95.140.74', 7709), 43 | ('安信', '112.95.140.92', 7709), 44 | ('安信', '112.95.140.93', 7709), 45 | ('安信', '114.80.149.19', 7709), 46 | ('安信', '114.80.149.21', 7709), 47 | ('安信', '114.80.149.22', 7709), 48 | ('安信', '114.80.149.91', 7709), 49 | ('安信', '114.80.149.92', 7709), 50 | ('安信', '121.14.104.60', 7709), 51 | ('安信', '121.14.104.66', 7709), 52 | ('安信', '123.126.133.13', 7709), 53 | ('安信', '123.126.133.14', 7709), 54 | ('安信', '123.126.133.21', 7709), 55 | ('安信', '211.139.150.61', 7709), 56 | ('安信', '59.36.5.11', 7709), 57 | ('广发', '119.29.19.242', 7709), 58 | ('广发', '123.138.29.107', 7709), 59 | ('广发', '123.138.29.108', 7709), 60 | ('广发', '124.232.142.29', 7709), 61 | ('广发', '183.57.72.11', 7709), 62 | ('广发', '183.57.72.12', 7709), 63 | ('广发', '183.57.72.13', 7709), 64 | ('广发', '183.57.72.15', 7709), 65 | ('广发', '183.57.72.21', 7709), 66 | ('广发', '183.57.72.22', 7709), 67 | ('广发', '183.57.72.23', 7709), 68 | ('广发', '183.57.72.24', 7709), 69 | ('广发', '183.60.224.177', 7709), 70 | ('广发', '183.60.224.178', 7709), 71 | ('国泰君安', '113.105.92.100', 7709), 72 | ('国泰君安', '113.105.92.101', 7709), 73 | ('国泰君安', '113.105.92.102', 7709), 74 | ('国泰君安', '113.105.92.103', 7709), 75 | ('国泰君安', '113.105.92.104', 7709), 76 | ('国泰君安', '113.105.92.99', 7709), 77 | ('国泰君安', '117.34.114.13', 7709), 78 | ('国泰君安', '117.34.114.14', 7709), 79 | ('国泰君安', '117.34.114.15', 7709), 80 | ('国泰君安', '117.34.114.16', 7709), 81 | ('国泰君安', '117.34.114.17', 7709), 82 | ('国泰君安', '117.34.114.18', 7709), 83 | ('国泰君安', '117.34.114.20', 7709), 84 | ('国泰君安', '117.34.114.27', 7709), 85 | ('国泰君安', '117.34.114.30', 7709), 86 | ('国泰君安', '117.34.114.31', 7709), 87 | ('国信', '182.131.3.252', 7709), 88 | ('国信', '183.60.224.11', 7709), 89 | ('国信', '58.210.106.91', 7709), 90 | ('国信', '58.63.254.216', 7709), 91 | ('国信', '58.63.254.219', 7709), 92 | ('国信', '58.63.254.247', 7709), 93 | ('海通', '123.125.108.90', 7709), 94 | ('海通', '175.6.5.153', 7709), 95 | ('海通', '182.118.47.151', 7709), 96 | ('海通', '182.131.3.245', 7709), 97 | ('海通', '202.100.166.27', 7709), 98 | ('海通', '222.161.249.156', 7709), 99 | ('海通', '42.123.69.62', 7709), 100 | ('海通', '58.63.254.191', 7709), 101 | ('海通', '58.63.254.217', 7709), 102 | ('华林', '120.55.172.97', 7709), 103 | ('华林', '139.217.20.27', 7709), 104 | ('华林', '202.100.166.21', 7709), 105 | ('华林', '202.96.138.90', 7709), 106 | ('华林', '218.106.92.182', 7709), 107 | ('华林', '218.106.92.183', 7709), 108 | ('华林', '220.178.55.71', 7709), 109 | ('华林', '220.178.55.86', 7709), 110 | 111 | 112 | 113 | ] -------------------------------------------------------------------------------- /crawler/__init__.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | """ 4 | 主要放置一些需要爬取的数据 5 | """ -------------------------------------------------------------------------------- /crawler/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/crawler/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /crawler/__pycache__/base_crawler.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/crawler/__pycache__/base_crawler.cpython-37.pyc -------------------------------------------------------------------------------- /crawler/__pycache__/history_financial_crawler.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/crawler/__pycache__/history_financial_crawler.cpython-37.pyc -------------------------------------------------------------------------------- /crawler/base_crawler.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import tempfile 4 | import six 5 | import math 6 | 7 | if six.PY2: 8 | from urllib2 import urlopen, Request 9 | else: 10 | from urllib.request import urlopen, Request 11 | 12 | 13 | 14 | def demo_reporthook(downloaded, total_size): 15 | print("Downloaded {}, Total is {}".format(downloaded, total_size)) 16 | 17 | class BaseCralwer: 18 | 19 | def __init__(self, mode="http"): 20 | self.mode = "http" 21 | 22 | def fetch_and_parse(self, reporthook = None, path_to_download=None, proxies=None, chunksize=1024 * 50, *args, **kwargs): 23 | """ 24 | function to get data , 25 | :param reporthook 使用urllib.request 的report_hook 来汇报下载进度 \ 26 | 参考 https://docs.python.org/3/library/urllib.request.html#module-urllib.request 27 | :param path_to_download 数据文件下载的地址,如果没有提供,则下载到临时文件中,并在解析之后删除 28 | :param proxies urllib格式的代理服务器设置 29 | :return: 解析之后的数据结果 30 | """ 31 | if (self.mode == "http"): 32 | download_file = self.fetch_via_http(reporthook=reporthook, path_to_download=path_to_download, proxies=proxies, chunksize=chunksize, *args, **kwargs) 33 | else: 34 | download_file = self.get_content(reporthook=reporthook, path_to_download=path_to_download, chunksize=chunksize, *args, **kwargs); 35 | 36 | result = self.parse(download_file, *args, **kwargs) 37 | try: 38 | download_file.close() 39 | except: 40 | pass 41 | return result 42 | 43 | def fetch_via_http(self, reporthook = None, path_to_download=None, proxies=None, chunksize=1024 * 50, *args, **kwargs): 44 | if path_to_download is None: 45 | download_file = tempfile.NamedTemporaryFile(delete=True) 46 | else: 47 | download_file = open(path_to_download, 'wb') 48 | 49 | url = self.get_url(*args, **kwargs) 50 | 51 | request = Request(url) 52 | request.add_header('Referer', url) 53 | request.add_header('User-Agent', r"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36") 54 | res = urlopen(request) 55 | 56 | if six.PY2: 57 | resinfo = res.info() 58 | else: 59 | resinfo = res 60 | 61 | if resinfo.getheader('Content-Length') is not None: 62 | total_size = int(resinfo.getheader('Content-Length').strip()) 63 | downloaded = 0 64 | 65 | while True: 66 | chunk = res.read(chunksize) 67 | downloaded += len(chunk) 68 | if reporthook is not None: 69 | reporthook(downloaded,total_size) 70 | if not chunk: 71 | break 72 | download_file.write(chunk) 73 | else: 74 | content = res.read() 75 | download_file.write(content) 76 | 77 | download_file.seek(0) 78 | return download_file 79 | 80 | 81 | def get_url(self, *args, **kwargs): 82 | raise NotImplementedError("will impl in subclass") 83 | 84 | def get_content(self, reporthook = None, path_to_download=None, proxies=None, chunksize=1024 * 50, *args, **kwargs): 85 | raise NotImplementedError("will impl in subclass") 86 | 87 | def parse(self, download_file, *args, **kwargs): 88 | raise NotImplementedError("will impl in subclass") -------------------------------------------------------------------------------- /crawler/history_financial_crawler.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from struct import calcsize, unpack 4 | from pytdx.crawler.base_crawler import BaseCralwer 5 | import shutil 6 | import tempfile 7 | import random 8 | import os 9 | import six 10 | import pandas as pd 11 | 12 | if six.PY2: 13 | import zipfile 14 | 15 | """ 16 | https://github.com/rainx/pytdx/issues/133 17 | 18 | 获取历史财务数据的接口,参考上面issue里面 @datochan 的方案和代码 19 | 20 | """ 21 | 22 | class HistoryFinancialListCrawler(BaseCralwer): 23 | 24 | def __init__(self): 25 | self.mode = "content" 26 | 27 | def get_url(self, *args, **kwargs): 28 | return "https://gitee.com/yutiansut/QADATA/raw/master/financial/content.txt" 29 | 30 | def get_content(self, reporthook=None, path_to_download=None, proxies=None, chunksize=1024 * 50, *args, **kwargs): 31 | from pytdx.hq import TdxHq_API 32 | api = TdxHq_API() 33 | api.need_setup = False 34 | # calc.tdx.com.cn, calc2.tdx.com.cn 35 | with api.connect(ip="120.76.152.87"): 36 | content = api.get_report_file_by_size("tdxfin/gpcw.txt") 37 | if path_to_download is None: 38 | download_file = tempfile.NamedTemporaryFile(delete=True) 39 | else: 40 | download_file = open(path_to_download, 'wb') 41 | download_file.write(content) 42 | download_file.seek(0) 43 | return download_file 44 | 45 | def parse(self, download_file, *args, **kwargs): 46 | content = download_file.read() 47 | content = content.decode("utf-8") 48 | 49 | def list_to_dict(l): 50 | return { 51 | 'filename': l[0], 52 | 'hash': l[1], 53 | 'filesize': int(l[2]) 54 | } 55 | result = [list_to_dict(l) for l in [line.strip().split(",") for line in content.strip().split('\n')]] 56 | return result 57 | 58 | 59 | class HistoryFinancialCrawler(BaseCralwer): 60 | 61 | def __init__(self): 62 | self.mode = "content" 63 | 64 | def get_url(self, *args, **kwargs): 65 | if 'filename' in kwargs: 66 | filename = kwargs['filename'] 67 | else: 68 | raise Exception("Param filename is not set") 69 | 70 | return "http://data.yutiansut.com/{}".format(filename) 71 | 72 | 73 | def get_content(self, reporthook=None, path_to_download=None, proxies=None, chunksize=1024 * 50, *args, **kwargs): 74 | if 'filename' in kwargs: 75 | filename = kwargs['filename'] 76 | else: 77 | raise Exception("Param filename is not set") 78 | 79 | if "filesize" in kwargs: 80 | filesize = kwargs["filesize"] 81 | else: 82 | filesize = 0 83 | 84 | from pytdx.hq import TdxHq_API 85 | api = TdxHq_API() 86 | api.need_setup = False 87 | # calc.tdx.com.cn, calc2.tdx.com.cn 88 | with api.connect(ip="120.76.152.87"): 89 | content = api.get_report_file_by_size("tdxfin/" + filename, filesize=filesize, reporthook=reporthook) 90 | if path_to_download is None: 91 | download_file = tempfile.NamedTemporaryFile(delete=True) 92 | else: 93 | download_file = open(path_to_download, 'wb') 94 | download_file.write(content) 95 | download_file.seek(0) 96 | return download_file 97 | 98 | def parse(self, download_file, *args, **kwargs): 99 | 100 | header_pack_format = '<1hI1H3L' 101 | 102 | if download_file.name.endswith('.zip'): 103 | tmpdir_root = tempfile.gettempdir() 104 | subdir_name = "pytdx_" + str(random.randint(0, 1000000)) 105 | tmpdir = os.path.join(tmpdir_root, subdir_name) 106 | shutil.rmtree(tmpdir, ignore_errors=True) 107 | os.makedirs(tmpdir) 108 | if six.PY2: 109 | with zipfile.ZipFile(download_file.name, 'r') as zf: 110 | zf.extractall(tmpdir) 111 | else: 112 | shutil.unpack_archive(download_file.name, extract_dir=tmpdir) 113 | # only one file endswith .dat should be in zip archives 114 | datfile = None 115 | for _file in os.listdir(tmpdir): 116 | if _file.endswith(".dat"): 117 | datfile = open(os.path.join(tmpdir, _file), "rb") 118 | 119 | if datfile is None: 120 | raise Exception("no dat file found in zip archive") 121 | else: 122 | datfile = download_file 123 | header_size = calcsize(header_pack_format) 124 | stock_item_size = calcsize("<6s1c1L") 125 | data_header = datfile.read(header_size) 126 | stock_header = unpack(header_pack_format, data_header) 127 | max_count = stock_header[2] 128 | report_date = stock_header[1] 129 | report_size = stock_header[4] 130 | report_fields_count = int(report_size / 4) 131 | report_pack_format = '<{}f'.format(report_fields_count) 132 | 133 | results = [] 134 | for stock_idx in range(0, max_count): 135 | datfile.seek(header_size + stock_idx * calcsize("<6s1c1L")) 136 | si = datfile.read(stock_item_size) 137 | stock_item = unpack("<6s1c1L", si) 138 | code = stock_item[0].decode("utf-8") 139 | foa = stock_item[2] 140 | datfile.seek(foa) 141 | 142 | info_data = datfile.read(calcsize(report_pack_format)) 143 | cw_info = unpack(report_pack_format, info_data) 144 | one_record = (code, report_date) + cw_info 145 | results.append(one_record) 146 | 147 | if download_file.name.endswith('.zip'): 148 | datfile.close() 149 | shutil.rmtree(tmpdir, ignore_errors=True) 150 | return results 151 | 152 | def to_df(self, data): 153 | if len(data) == 0: 154 | return None 155 | 156 | total_lengh = len(data[0]) 157 | col = ['code', 'report_date'] 158 | 159 | length = total_lengh - 2 160 | for i in range(0, length): 161 | col.append("col" + str(i + 1)) 162 | 163 | 164 | df = pd.DataFrame(data=data, columns=col) 165 | df.set_index('code', inplace=True) 166 | return df 167 | 168 | 169 | if __name__ == '__main__': 170 | import pandas as pd 171 | from pytdx.crawler.base_crawler import demo_reporthook 172 | crawler = HistoryFinancialListCrawler() 173 | # 174 | list_data = crawler.fetch_and_parse(reporthook=demo_reporthook) 175 | df = pd.DataFrame(data=list_data) 176 | 177 | print(df["filename"]) 178 | print(df["filename"].str.contains("gpcw20190630.zip").any()) 179 | 180 | # 读取其中一个 181 | 182 | # filename = list_data[1]['filename'] 183 | # filesize = list_data[1]["filesize"] 184 | 185 | # datacrawler = HistoryFinancialCrawler() 186 | # pd.set_option('display.max_columns', None) 187 | 188 | # result = datacrawler.fetch_and_parse(reporthook=demo_reporthook, filename=filename, filesize=filesize, path_to_download="/tmp/tmpfile.zip") 189 | # print(result) 190 | # with open(r"/tmp/tmpfile.zip", "rb") as fp: 191 | # result = datacrawler.parse(download_file=fp) 192 | # print(datacrawler.to_df(data=result)) 193 | -------------------------------------------------------------------------------- /errors.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | class TdxConnectionError(Exception): 5 | """ 6 | 当连接服务器出错的时候,会抛出的异常 7 | """ 8 | pass 9 | 10 | class TdxFunctionCallError(Exception): 11 | """ 12 | 当行数调用出错的时候 13 | """ 14 | def __init__(self, *args, **kwargs): 15 | super(TdxFunctionCallError, self).__init__(*args, **kwargs) 16 | self.original_exception = None 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /exhq.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # 4 | # Just for practising 5 | # 6 | 7 | 8 | import os 9 | import socket 10 | import sys 11 | import pandas as pd 12 | 13 | if __name__ == '__main__': 14 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) 15 | 16 | from pytdx.log import DEBUG, log 17 | from pytdx.parser.ex_setup_commands import ExSetupCmd1 18 | from pytdx.parser.ex_get_markets import GetMarkets 19 | from pytdx.parser.ex_get_instrument_count import GetInstrumentCount 20 | from pytdx.parser.ex_get_instrument_quote import GetInstrumentQuote 21 | from pytdx.parser.ex_get_minute_time_data import GetMinuteTimeData 22 | from pytdx.parser.ex_get_history_minute_time_data import GetHistoryMinuteTimeData 23 | from pytdx.parser.ex_get_transaction_data import GetTransactionData 24 | from pytdx.parser.ex_get_history_transaction_data import GetHistoryTransactionData 25 | from pytdx.parser.ex_get_instrument_bars import GetInstrumentBars 26 | from pytdx.parser.ex_get_instrument_info import GetInstrumentInfo 27 | from pytdx.parser.ex_get_history_instrument_bars_range import GetHistoryInstrumentBarsRange 28 | from pytdx.parser.ex_get_instrument_quote_list import GetInstrumentQuoteList 29 | 30 | 31 | from pytdx.params import TDXParams 32 | 33 | import threading 34 | import datetime 35 | from pytdx.base_socket_client import BaseSocketClient, update_last_ack_time 36 | 37 | 38 | """ 39 | In [7]: 0x7e 40 | Out[7]: 126 41 | 42 | In [5]: len(body) 43 | Out[5]: 8066 44 | 45 | In [6]: len(body)/126 46 | Out[6]: 64.01587301587301 47 | 48 | In [7]: len(body)%126 49 | Out[7]: 2 50 | 51 | In [8]: (len(body)-2)/126 52 | Out[8]: 64.0 53 | """ 54 | 55 | 56 | class TdxExHq_API(BaseSocketClient): 57 | 58 | def setup(self): 59 | ExSetupCmd1(self.client).call_api() 60 | 61 | # API LIST 62 | 63 | @update_last_ack_time 64 | def get_markets(self): 65 | cmd = GetMarkets(self.client) 66 | return cmd.call_api() 67 | 68 | @update_last_ack_time 69 | def get_instrument_count(self): 70 | cmd = GetInstrumentCount(self.client) 71 | return cmd.call_api() 72 | 73 | @update_last_ack_time 74 | def get_instrument_quote(self, market, code): 75 | cmd = GetInstrumentQuote(self.client) 76 | cmd.setParams(market, code) 77 | return cmd.call_api() 78 | 79 | @update_last_ack_time 80 | def get_instrument_bars(self, category, market, code, start=0, count=700): 81 | cmd = GetInstrumentBars(self.client) 82 | cmd.setParams(category, market, code, start=start, count=count) 83 | return cmd.call_api() 84 | 85 | @update_last_ack_time 86 | def get_minute_time_data(self, market, code): 87 | cmd = GetMinuteTimeData(self.client) 88 | cmd.setParams(market, code) 89 | return cmd.call_api() 90 | 91 | @update_last_ack_time 92 | def get_history_minute_time_data(self, market, code, date): 93 | cmd = GetHistoryMinuteTimeData(self.client) 94 | cmd.setParams(market, code, date=date) 95 | return cmd.call_api() 96 | 97 | @update_last_ack_time 98 | def get_transaction_data(self, market, code, start=0, count=1800): 99 | cmd = GetTransactionData(self.client) 100 | cmd.setParams(market, code, start=start, count=count) 101 | return cmd.call_api() 102 | 103 | @update_last_ack_time 104 | def get_history_transaction_data(self, market, code, date, start=0, count=1800): 105 | cmd = GetHistoryTransactionData(self.client) 106 | cmd.setParams(market, code, date, start=start, count=count) 107 | return cmd.call_api() 108 | 109 | @update_last_ack_time 110 | def get_history_instrument_bars_range(self, market, code, start, end): 111 | cmd = GetHistoryInstrumentBarsRange(self.client) 112 | cmd.setParams(market, code, start, end) 113 | return cmd.call_api() 114 | 115 | @update_last_ack_time 116 | def get_instrument_info(self, start, count=100): 117 | cmd = GetInstrumentInfo(self.client) 118 | cmd.setParams(start, count) 119 | return cmd.call_api() 120 | 121 | @update_last_ack_time 122 | def get_instrument_quote_list(self, market, category, start=0, count=80): 123 | cmd = GetInstrumentQuoteList(self.client) 124 | cmd.setParams(market, category, start, count) 125 | return cmd.call_api() 126 | 127 | def do_heartbeat(self): 128 | self.get_instrument_count() 129 | 130 | 131 | if __name__ == '__main__': 132 | import pprint 133 | 134 | api = TdxExHq_API() 135 | with api.connect('121.14.110.210', 7727): 136 | # log.info("获取市场代码") 137 | # pprint.pprint(api.to_df(api.get_markets())) 138 | # log.info("查询市场中商品数量") 139 | # pprint.pprint(api.get_instrument_count()) 140 | # log.info("查询五档行情") 141 | #pprint.pprint(api.to_df(api.get_instrument_quote(47, "IF1709"))) 142 | #pprint.pprint(api.get_instrument_quote(8, "10000889")) 143 | #pprint.pprint(api.get_instrument_quote(31, "00020")) 144 | # log.info("查询分时行情") 145 | #api.get_minute_time_data(47, "IFL0") 146 | #pprint.pprint(api.to_df(api.get_minute_time_data(47, "IFL0"))) 147 | #pprint.pprint(api.to_df(api.get_minute_time_data(8, "10000889")).tail()) 148 | #pprint.pprint(api.get_minute_time_data(31, "00020")) 149 | log.info("查询历史分时行情") 150 | pprint.pprint(api.to_df(api.get_history_minute_time_data(31, "00020", 20170811)).tail()) 151 | log.info("查询分时成交") 152 | pprint.pprint(api.to_df(api.get_transaction_data(31, "00020")).tail()) 153 | 154 | log.info("查询历史分时成交") 155 | pprint.pprint(api.to_df(api.get_history_transaction_data(31, "00020", 20170811)).tail()) 156 | #data = api.get_history_minute_time_data(47, 'IFL0', 20170811) 157 | # pprint.pprint(data) 158 | 159 | # log.info("查询k线") 160 | #pprint.pprint(api.to_df(api.get_instrument_bars(TDXParams.KLINE_TYPE_DAILY, 8, "10000843"))) 161 | #pprint.pprint(api.to_df(api.get_instrument_bars(TDXParams.KLINE_TYPE_DAILY, 31, "00700"))) 162 | # log.info("查询代码列表") 163 | #pprint.pprint(api.to_df(api.get_instrument_info(10000, 98))) 164 | -------------------------------------------------------------------------------- /heartbeat.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from threading import Thread 4 | import random 5 | from pytdx.log import DEBUG, log 6 | import time 7 | 8 | # 参考 :https://stackoverflow.com/questions/6524459/stopping-a-thread-after-a-certain-amount-of-time 9 | 10 | 11 | DEFAULT_HEARTBEAT_INTERVAL = 10.0 # 10秒一个heartbeat 12 | 13 | class HqHeartBeatThread(Thread): 14 | 15 | def __init__(self, api, stop_event, heartbeat_interval=DEFAULT_HEARTBEAT_INTERVAL): 16 | self.api = api 17 | self.client = api.client 18 | self.stop_event = stop_event 19 | self.heartbeat_interval = heartbeat_interval 20 | super(HqHeartBeatThread, self).__init__() 21 | 22 | def run(self): 23 | while not self.stop_event.is_set(): 24 | self.stop_event.wait(self.heartbeat_interval) 25 | if self.client and (time.time() - self.api.last_ack_time > self.heartbeat_interval): 26 | try: 27 | # 发送一个获取股票数量的包作为心跳包 28 | self.api.do_heartbeat() 29 | except Exception as e: 30 | log.debug(str(e)) 31 | 32 | 33 | -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import struct 4 | import six 5 | 6 | 7 | #### XXX: 分析了一下,貌似是类似utf-8的编码方式保存有符号数字 8 | def get_price(data, pos): 9 | pos_byte = 6 10 | bdata = indexbytes(data, pos) 11 | intdata = bdata & 0x3f 12 | if bdata & 0x40: 13 | sign = True 14 | else: 15 | sign = False 16 | 17 | if bdata & 0x80: 18 | while True: 19 | pos += 1 20 | bdata = indexbytes(data, pos) 21 | intdata += (bdata & 0x7f) << pos_byte 22 | pos_byte += 7 23 | 24 | if bdata & 0x80: 25 | pass 26 | else: 27 | break 28 | 29 | pos += 1 30 | 31 | if sign: 32 | intdata = -intdata 33 | 34 | return intdata, pos 35 | 36 | 37 | def get_volume(ivol): 38 | logpoint = ivol >> (8 * 3) 39 | hheax = ivol >> (8 * 3); # [3] 40 | hleax = (ivol >> (8 * 2)) & 0xff; # [2] 41 | lheax = (ivol >> 8) & 0xff; # [1] 42 | lleax = ivol & 0xff; # [0] 43 | 44 | dbl_1 = 1.0 45 | dbl_2 = 2.0 46 | dbl_128 = 128.0 47 | 48 | dwEcx = logpoint * 2 - 0x7f; 49 | dwEdx = logpoint * 2 - 0x86; 50 | dwEsi = logpoint * 2 - 0x8e; 51 | dwEax = logpoint * 2 - 0x96; 52 | if dwEcx < 0: 53 | tmpEax = - dwEcx 54 | else: 55 | tmpEax = dwEcx 56 | 57 | dbl_xmm6 = 0.0 58 | dbl_xmm6 = pow(2.0, tmpEax) 59 | if dwEcx < 0: 60 | dbl_xmm6 = 1.0 / dbl_xmm6 61 | 62 | dbl_xmm4 = 0 63 | if hleax > 0x80: 64 | tmpdbl_xmm3 = 0.0 65 | tmpdbl_xmm1 = 0.0 66 | dwtmpeax = dwEdx + 1 67 | tmpdbl_xmm3 = pow(2.0, dwtmpeax) 68 | dbl_xmm0 = pow(2.0, dwEdx) * 128.0 69 | dbl_xmm0 += (hleax & 0x7f) * tmpdbl_xmm3 70 | dbl_xmm4 = dbl_xmm0 71 | 72 | else: 73 | dbl_xmm0 = 0.0 74 | if dwEdx >= 0: 75 | dbl_xmm0 = pow(2.0, dwEdx) * hleax 76 | else: 77 | dbl_xmm0 = (1 / pow(2.0, dwEdx)) * hleax 78 | dbl_xmm4 = dbl_xmm0 79 | 80 | dbl_xmm3 = pow(2.0, dwEsi) * lheax 81 | dbl_xmm1 = pow(2.0, dwEax) * lleax 82 | if hleax & 0x80: 83 | dbl_xmm3 *= 2.0 84 | dbl_xmm1 *= 2.0 85 | 86 | dbl_ret = dbl_xmm6 + dbl_xmm4 + dbl_xmm3 + dbl_xmm1 87 | return dbl_ret 88 | 89 | 90 | def get_datetime(category, buffer, pos): 91 | year = 0 92 | month = 0 93 | day = 0 94 | hour = 15 95 | minute = 0 96 | if category < 4 or category == 7 or category == 8: 97 | (zipday, tminutes) = struct.unpack("> 11) + 2004 99 | month = int((zipday % 2048) / 100) 100 | day = (zipday % 2048) % 100 101 | 102 | hour = int(tminutes / 60) 103 | minute = tminutes % 60 104 | else: 105 | (zipday,) = struct.unpack(" 59: 45 | second = 0 46 | date = datetime.datetime(year, month, day, hour, minute, second) 47 | 48 | if value == 0: 49 | direction = 1 50 | if zengcang > 0: 51 | if volume > zengcang: 52 | nature_name = "多开" 53 | elif volume == zengcang: 54 | nature_name = "双开" 55 | elif zengcang == 0: 56 | nature_name = "多换" 57 | else: 58 | if volume == -zengcang: 59 | nature_name = "双平" 60 | else: 61 | nature_name = "空平" 62 | elif value == 1: 63 | direction = -1 64 | if zengcang > 0: 65 | if volume > zengcang: 66 | nature_name = "空开" 67 | elif volume == zengcang: 68 | nature_name = "双开" 69 | elif zengcang == 0: 70 | nature_name = "空换" 71 | else: 72 | if volume == -zengcang: 73 | nature_name = "双平" 74 | else: 75 | nature_name = "多平" 76 | else: 77 | direction = 0 78 | if zengcang > 0: 79 | if volume > zengcang: 80 | nature_name = "开仓" 81 | elif volume == zengcang: 82 | nature_name = "双开" 83 | elif zengcang < 0: 84 | if volume > -zengcang: 85 | nature_name = "平仓" 86 | elif volume == -zengcang: 87 | nature_name = "双平" 88 | else: 89 | nature_name = "换手" 90 | 91 | if market in [31,48]: 92 | if nature == 0: 93 | direction = 1 94 | nature_name = 'B' 95 | elif nature == 256: 96 | direction = -1 97 | nature_name = 'S' 98 | else: #512 99 | direction = 0 100 | nature_name = '' 101 | 102 | result.append(OrderedDict([ 103 | ("date", date), 104 | ("hour", hour), 105 | ("minute", minute), 106 | ("price", price), 107 | ("volume", volume), 108 | ("zengcang", zengcang), 109 | ("natrue_name", nature_name), 110 | ("nature_name", nature_name), #修正了nature_name的拼写错误(natrue), 为了保持兼容性,原有的natrue_name还会保留一段时间 111 | ("direction", direction), 112 | ("nature", nature), 113 | 114 | ])) 115 | 116 | return result 117 | 118 | 119 | if __name__ == '__main__': 120 | 121 | from pytdx.exhq import TdxExHq_API 122 | 123 | api = TdxExHq_API() 124 | with api.connect('121.14.110.210', 7727): 125 | # print(api.to_df(api.get_history_transaction_data(4, 'SR61099D', 20171025))[["date","price","volume",'zengcang','nature','t1','t2']]) 126 | 127 | print(api.to_df(api.get_history_transaction_data(47, 'IFL0', 20170811))) 128 | #print(api.to_df(api.get_history_transaction_data(31, "01918", 20171026))[["date","price","volume",'zengcang','nature']]) 129 | #api.to_df(api.get_history_transaction_data(47, 'IFL0', 20170810)).to_excel('//Users//wy//data//iflo.xlsx') -------------------------------------------------------------------------------- /parser/ex_get_instrument_bars.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from pytdx.parser.base import BaseParser 4 | from pytdx.helper import get_datetime, get_volume, get_price 5 | from collections import OrderedDict 6 | import six 7 | import struct 8 | 9 | class GetInstrumentBars(BaseParser): 10 | 11 | # ff232f49464c30007401a9130400010000000000f000 12 | """ 13 | 14 | first: 15 | 16 | 0000 01 01 08 6a 01 01 16 00 16 00 ...j...... 17 | 18 | 19 | second: 20 | 0000 ff 23 2f 49 46 4c 30 00 74 01 a9 13 04 00 01 00 .#/IFL0.t....... 21 | 0010 00 00 00 00 f0 00 ...... 22 | 23 | 0000 ff 23 28 42 41 42 41 00 00 00 a9 13 04 00 01 00 .#(BABA......... 24 | 0010 00 00 00 00 f0 00 ...... 25 | 26 | 0000 ff 23 28 42 41 42 41 00 00 00 a9 13 03 00 01 00 .#(BABA......... 27 | 0010 00 00 00 00 f0 00 ...... 28 | 29 | 0000 ff 23 08 31 30 30 30 30 38 34 33 13 04 00 01 00 .#.10000843..... 30 | 0010 00 00 00 00 f0 00 ...... 31 | """ 32 | 33 | def setup(self): 34 | pass 35 | #self.client.send(bytearray.fromhex('01 01 08 6a 01 01 16 00 16 00')) 36 | 37 | def setParams(self, category, market, code, start, count): 38 | if type(code) is six.text_type: 39 | code = code.encode("utf-8") 40 | pkg = bytearray.fromhex('01 01 08 6a 01 01 16 00 16 00') 41 | pkg.extend(bytearray.fromhex("ff 23")) 42 | 43 | self.category = category 44 | 45 | #pkg = bytearray.fromhex("ff 23") 46 | 47 | #count 48 | last_value = 0x00f00000 49 | pkg.extend(struct.pack(' 59: 36 | second = 0 37 | 38 | date = datetime.datetime.combine(datetime.date.today(), datetime.time(hour,minute,second)) 39 | 40 | value = direction // 10000 41 | 42 | if value == 0: 43 | direction = 1 44 | if zengcang > 0: 45 | if volume > zengcang: 46 | nature_name = "多开" 47 | elif volume == zengcang: 48 | nature_name = "双开" 49 | elif zengcang == 0: 50 | nature_name = "多换" 51 | else: 52 | if volume == -zengcang: 53 | nature_name = "双平" 54 | else: 55 | nature_name = "空平" 56 | elif value == 1: 57 | direction = -1 58 | if zengcang > 0: 59 | if volume > zengcang: 60 | nature_name = "空开" 61 | elif volume == zengcang: 62 | nature_name = "双开" 63 | elif zengcang == 0: 64 | nature_name = "空换" 65 | else: 66 | if volume == -zengcang: 67 | nature_name = "双平" 68 | else: 69 | nature_name = "多平" 70 | else: 71 | direction = 0 72 | if zengcang > 0: 73 | if volume > zengcang: 74 | nature_name = "开仓" 75 | elif volume == zengcang: 76 | nature_name = "双开" 77 | elif zengcang < 0: 78 | if volume > -zengcang: 79 | nature_name = "平仓" 80 | elif volume == -zengcang: 81 | nature_name = "双平" 82 | else: 83 | nature_name = "换手" 84 | 85 | if market in [31,48]: 86 | if nature == 0: 87 | direction = 1 88 | nature_name = 'B' 89 | elif nature == 256: 90 | direction = -1 91 | nature_name = 'S' 92 | else: #512 93 | direction = 0 94 | nature_name = '' 95 | 96 | 97 | result.append(OrderedDict([ 98 | ("date", date), 99 | ("hour", hour), 100 | ("minute", minute), 101 | ("second", second), 102 | ("price", price), 103 | ("volume", volume), 104 | ("zengcang", zengcang), 105 | ("nature", nature), 106 | ("nature_mark", nature // 10000), 107 | ("nature_value", nature % 10000), 108 | ("nature_name", nature_name), 109 | ("direction", direction), 110 | ])) 111 | 112 | return result 113 | 114 | 115 | if __name__ == "__main__": 116 | from pytdx.exhq import TdxExHq_API 117 | 118 | api = TdxExHq_API() 119 | with api.connect('121.14.110.210', 7727): 120 | print(api.to_df(api.get_transaction_data(47, 'IFL9'))) 121 | # print(api.to_df(api.get_transaction_data(31, "00020"))) 122 | -------------------------------------------------------------------------------- /parser/ex_setup_commands.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from pytdx.parser.base import BaseParser 4 | from pytdx.helper import get_datetime, get_volume, get_price 5 | from collections import OrderedDict 6 | import struct 7 | 8 | 9 | class ExSetupCmd1(BaseParser): 10 | 11 | def setup(self): 12 | self.send_pkg = bytearray.fromhex("01 01 48 65 00 01 52 00 52 00 54 24 1f 32 c6 e5" 13 | "d5 3d fb 41 1f 32 c6 e5 d5 3d fb 41 1f 32 c6 e5" 14 | "d5 3d fb 41 1f 32 c6 e5 d5 3d fb 41 1f 32 c6 e5" 15 | "d5 3d fb 41 1f 32 c6 e5 d5 3d fb 41 1f 32 c6 e5" 16 | "d5 3d fb 41 1f 32 c6 e5 d5 3d fb 41 cc e1 6d ff" 17 | "d5 ba 3f b8 cb c5 7a 05 4f 77 48 ea") 18 | 19 | def parseResponse(self, body_buf): 20 | pass -------------------------------------------------------------------------------- /parser/get_block_info.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from pytdx.parser.base import BaseParser 4 | from pytdx.reader.block_reader import BlockReader,BlockReader_TYPE_FLAT 5 | from pytdx.helper import get_datetime, get_volume, get_price 6 | from collections import OrderedDict 7 | import struct 8 | import six 9 | 10 | 11 | 12 | class GetBlockInfoMeta(BaseParser): 13 | def setParams(self, block_file): 14 | if type(block_file) is six.text_type: 15 | block_file = block_file.encode("utf-8") 16 | pkg = bytearray.fromhex(u'0C 39 18 69 00 01 2A 00 2A 00 C5 02') 17 | pkg.extend(struct.pack(u"<{}s".format(0x2a - 2), block_file)) 18 | self.send_pkg = pkg 19 | 20 | 21 | def parseResponse(self, body_buf): 22 | (size, _, hash_value, _ ) = struct.unpack(u" 0: 27 | return { 28 | "chunksize": chunksize, 29 | "chunkdata": body_buf[4:] 30 | } 31 | else: 32 | return { 33 | "chunksize": 0 34 | } 35 | 36 | 37 | if __name__ == "__main__": 38 | from pytdx.hq import TdxHq_API 39 | api = TdxHq_API() 40 | api.need_setup = False 41 | # calc.tdx.com.cn, calc2.tdx.com.cn 42 | with api.connect(ip="120.76.152.87"): 43 | # response = api.get_report_file(r"tdxfin/gpcw19980630.zip", 386003) 44 | content = api.get_report_file_by_size("tdxfin/gpcw.txt") 45 | # content = api.get_report_file_by_size("tdxfin/gpcw19980630.zip", 386073) 46 | print(content) 47 | -------------------------------------------------------------------------------- /parser/get_security_bars.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | from pytdx.parser.base import BaseParser 5 | from pytdx.helper import get_datetime, get_volume, get_price 6 | from collections import OrderedDict 7 | import struct 8 | import six 9 | 10 | """ 11 | Notice:,如果一个股票当天停牌,那天的K线还是能取到,成交量为0 12 | 13 | 14 | param: category=9, market=0, stockcode=000001, start=0, count=10 15 | send: 0c01086401011c001c002d0500003030303030310900010000000a0000000000000000000000 16 | recv: b1cb74000c01086401002d05aa00aa000a006ec73301b28c011e3254a081ad4816d6984d6fc7330154ae0182024ab0a51d4978090c4e70c733015414285e8003bb488b59a64d71c73301140086015ec059274945cb154e74c73301006828724060f648ae0edc4d75c73301000a1e7c40f6da48a37dc24d76c7330100680ad0018052b748ad68a24d77c7330100680072a0f0a448f8b9914d78c733010054285ee0a48b48c294764d7bc733010aa401b8014a001def4874abd44d 17 | 18 | """ 19 | 20 | class GetSecurityBarsCmd(BaseParser): 21 | 22 | def setParams(self, category, market, code, start, count): 23 | if type(code) is six.text_type: 24 | code = code.encode("utf-8") 25 | 26 | self.category = category 27 | 28 | values = ( 29 | 0x10c, 30 | 0x01016408, 31 | 0x1c, 32 | 0x1c, 33 | 0x052d, 34 | market, 35 | code, 36 | category, 37 | 1, 38 | start, 39 | count, 40 | 0, 0, 0 # I + I + H total 10 zero 41 | ) 42 | 43 | pkg = struct.pack(" 3.0 14 | 15 | 2434.0062499046326 ---> 2.6 16 | 17 | 1218.0031249523163 ---> 2.3 18 | 19 | """ 20 | """ 21 | 22 | 1 除权除息 002175 2008-05-29 23 | 2 送配股上市 000656 2015-04-29 24 | 3 非流通股上市 000656 2010-02-10 25 | 4 未知股本变动 600642 1993-07-19 26 | 5 股本变化 000656 2017-06-30 27 | 6 增发新股 600887 2002-08-20 28 | 7 股份回购 600619 2000-09-08 29 | 8 增发新股上市 600186 2001-02-14 30 | 9 转配股上市 600811 2017-07-25 31 | 10 可转债上市 600418 2006-07-07 32 | 11 扩缩股 600381 2014-06-27 33 | 12 非流通股缩股 600339 2006-04-10 34 | 13 送认购权证 600008 2006-04-19 35 | 14 送认沽权证 000932 2006-03-01 36 | 37 | """ 38 | 39 | 40 | XDXR_CATEGORY_MAPPING = { 41 | 1 : "除权除息", 42 | 2 : "送配股上市", 43 | 3 : "非流通股上市", 44 | 4 : "未知股本变动", 45 | 5 : "股本变化", 46 | 6 : "增发新股", 47 | 7 : "股份回购", 48 | 8 : "增发新股上市", 49 | 9 : "转配股上市", 50 | 10 : "可转债上市", 51 | 11 : "扩缩股", 52 | 12 : "非流通股缩股", 53 | 13 : "送认购权证", 54 | 14 : "送认沽权证" 55 | } 56 | 57 | 58 | class GetXdXrInfo(BaseParser): 59 | 60 | def setParams(self, market, code): 61 | if type(code) is six.text_type: 62 | code = code.encode("utf-8") 63 | pkg = bytearray.fromhex(u'0c 1f 18 76 00 01 0b 00 0b 00 0f 00 01 00') 64 | pkg.extend(struct.pack(" 33000.00000 97 | # b'\x00\xc0\x0fF' => 9200.00000 98 | # b'\x00@\x83E' => 4200.0000 99 | 100 | suogu = None 101 | panqianliutong, panhouliutong, qianzongguben, houzongguben = None, None, None, None 102 | songzhuangu, fenhong, peigu, peigujia = None, None, None, None 103 | fenshu, xingquanjia = None, None 104 | if category == 1: 105 | fenhong, peigujia, songzhuangu, peigu = struct.unpack("= self.api_call_max_retry_times: 75 | log.info("(method_name=%s) max retry times(%d) reached" % (method_name, self.api_call_max_retry_times)) 76 | raise TdxHqApiCallMaxRetryTimesReachedException("(method_name=%s) max retry times reached" % method_name) 77 | old_api_ip = self.api.ip 78 | new_api_ip = None 79 | if self.hot_failover_api: 80 | new_api_ip = self.hot_failover_api.ip 81 | log.info("api call from init client (ip=%s) err, perform rotate to (ip =%s)..." %(old_api_ip, new_api_ip)) 82 | self.api.disconnect() 83 | self.api = self.hot_failover_api 84 | log.info("retry times is " + str(self.api_call_max_retry_times)) 85 | # 从池里再次获取备用ip 86 | new_ips = self.ippool.get_ips() 87 | 88 | choise_ip = None 89 | for _test_ip in new_ips: 90 | if _test_ip[0] == old_api_ip or _test_ip[0] == new_api_ip: 91 | continue 92 | choise_ip = _test_ip 93 | break 94 | 95 | if choise_ip: 96 | self.hot_failover_api = self.hq_cls(multithread=True, heartbeat=True) 97 | self.hot_failover_api.connect(*choise_ip) 98 | else: 99 | self.hot_failover_api = None 100 | # 阻塞0.2秒,然后递归调用自己 101 | time.sleep(self.api_retry_interval) 102 | result = self.do_hq_api_call(method_name, *args, **kwargs) 103 | self.api_call_retry_times += 1 104 | 105 | else: 106 | self.api_call_retry_times = 0 107 | 108 | return result 109 | 110 | def connect(self, ipandport, hot_failover_ipandport): 111 | log.debug("setup ip pool") 112 | self.ippool.setup() 113 | log.debug("connecting to primary api") 114 | self.api.connect(*ipandport) 115 | log.debug("connecting to hot backup api") 116 | self.hot_failover_api.connect(*hot_failover_ipandport) 117 | return self 118 | 119 | def disconnect(self): 120 | log.debug("primary api disconnected") 121 | self.api.disconnect() 122 | log.debug("hot backup api disconnected") 123 | self.hot_failover_api.disconnect() 124 | log.debug("ip pool released") 125 | self.ippool.teardown() 126 | 127 | def close(self): 128 | """ 129 | disconnect的别名,为了支持 with closing(obj): 语法 130 | :return: 131 | """ 132 | self.disconnect() 133 | 134 | def __enter__(self): 135 | return self 136 | 137 | def __exit__(self, exc_type, exc_val, exc_tb): 138 | self.close() 139 | 140 | 141 | if __name__ == '__main__': 142 | 143 | from pytdx.hq import TdxHq_API 144 | from pytdx.pool.ippool import AvailableIPPool 145 | from pytdx.config.hosts import hq_hosts 146 | import random 147 | import logging 148 | import pprint 149 | log.setLevel(logging.DEBUG) 150 | ch = logging.StreamHandler() 151 | ch.setLevel(logging.DEBUG) 152 | # create formatter 153 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 154 | # add formatter to ch 155 | ch.setFormatter(formatter) 156 | log.addHandler(ch) 157 | 158 | ips = [(v[1], v[2]) for v in hq_hosts] 159 | 160 | # 获取5个随机ip作为ip池 161 | random.shuffle(ips) 162 | ips5 = ips[:5] 163 | 164 | ippool = AvailableIPPool(TdxHq_API, ips5) 165 | 166 | primary_ip, hot_backup_ip = ippool.sync_get_top_n(2) 167 | 168 | print("make pool api") 169 | api = TdxHqPool_API(TdxHq_API, ippool) 170 | print("make pool api done") 171 | print("send api call to primary ip %s, %s" % (str(primary_ip), str(hot_backup_ip))) 172 | with api.connect(primary_ip, hot_backup_ip): 173 | ret = api.get_xdxr_info(0, '000001') 174 | print("send api call done") 175 | pprint.pprint(ret) 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /pool/ippool.py: -------------------------------------------------------------------------------- 1 | #utf-8 2 | 3 | import random 4 | import threading 5 | from functools import partial 6 | from pytdx.log import DEBUG, log 7 | import time 8 | from collections import OrderedDict 9 | 10 | """ 11 | ips 应该还是一个 (ip ,port) 对的列表,如 12 | 13 | [ 14 | (ip1, port1), 15 | (ip2, port2), 16 | (ip3, port3), 17 | ] 18 | 19 | """ 20 | 21 | class BaseIPPool(object): 22 | 23 | def __init__(self, hq_class): 24 | self.hq_class = hq_class 25 | 26 | def setup(self): 27 | pass 28 | 29 | def teardown(self): 30 | pass 31 | 32 | def sync_get_top_n(self, num): 33 | pass 34 | 35 | def add_to_pool(self, ip): 36 | pass 37 | 38 | 39 | class RandomIPPool(BaseIPPool): 40 | """ 41 | 获取一个随机的优先级列表 42 | """ 43 | 44 | def __init__(self, hq_class, ips): 45 | """ 46 | :param ips: ip should be a list 47 | """ 48 | super(RandomIPPool, self).__init__(hq_class) 49 | self.ips = ips 50 | 51 | def get_ips(self): 52 | random.shuffle(self.ips) 53 | return self.ips 54 | 55 | def sync_get_top_n(self, num): 56 | ips= self.get_ips() 57 | return ips[:num] 58 | 59 | def add_to_pool(self, ip): 60 | if ip not in self.ips: 61 | self.ips.append(ip) 62 | 63 | 64 | class AvailableIPPool(BaseIPPool): 65 | """ 66 | 测试可连接性,并根据连接速度排序 67 | 我们启动一个新的线程,周期性的进行更新 68 | """ 69 | 70 | def __init__(self, hq_class, ips): 71 | super(AvailableIPPool, self).__init__(hq_class) 72 | self.ips = ips 73 | self.sorted_ips = None 74 | self.worker_thread = None 75 | self.sorted_ips_lock = threading.Lock() 76 | self.stop_event = threading.Event() 77 | self.wait_interval = 20 * 60 78 | 79 | def setup(self): 80 | super(AvailableIPPool, self).setup() 81 | 82 | self.worker_thread = threading.Thread(target=self.run) 83 | self.worker_thread.start() 84 | 85 | def get_ips(self): 86 | if not self.sorted_ips: 87 | return self.ips 88 | else: 89 | return list(self.sorted_ips.values()) 90 | 91 | def teardown(self): 92 | self.stop_event.set() 93 | if self.worker_thread.is_alive(): 94 | self.worker_thread.join() 95 | self.worker_thread = None 96 | 97 | def run(self): 98 | log.debug("pool thread start ") 99 | while not self.stop_event.is_set(): 100 | _available_ips = self.get_all_available_ips() 101 | sorted_keys = sorted(_available_ips) 102 | with self.sorted_ips_lock: 103 | self.sorted_ips = OrderedDict((key, _available_ips[key]) for key in sorted_keys) 104 | self.stop_event.wait(self.wait_interval) 105 | 106 | def get_all_available_ips(self): 107 | """ 108 | 循环测试所有连接的连接速度和有效性 109 | :return: 110 | """ 111 | _available_ips = OrderedDict() 112 | for ip in self.ips: 113 | ip_addr, port = ip 114 | api = self.hq_class(multithread=False, heartbeat=False) 115 | try: 116 | with api.connect(ip_addr, port): 117 | start_ts = time.time() 118 | api.do_heartbeat() 119 | end_ts = time.time() 120 | diff_ts = end_ts - start_ts 121 | _available_ips[diff_ts] = ip 122 | log.debug("time diff is %f for %s" % (diff_ts, _available_ips)) 123 | except Exception as e: 124 | log.debug("can not use %s:%d the exception is %s" % (ip_addr, port, str(e))) 125 | continue 126 | return _available_ips 127 | 128 | def sync_get_top_n(self, num): 129 | _ips = list(self.get_all_available_ips().values()) 130 | return _ips[:min(len(_ips), num)] 131 | 132 | def add_to_pool(self, ip): 133 | if ip not in self.ips: 134 | self.ips.append(ip) 135 | 136 | 137 | if __name__ == "__main__": 138 | from pytdx.hq import TdxHq_API 139 | from pytdx.config.hosts import hq_hosts 140 | import logging 141 | log.setLevel(logging.DEBUG) 142 | ch = logging.StreamHandler() 143 | ch.setLevel(logging.DEBUG) 144 | # create formatter 145 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 146 | # add formatter to ch 147 | ch.setFormatter(formatter) 148 | log.addHandler(ch) 149 | 150 | ips = [(v[1], v[2]) for v in hq_hosts] 151 | pool = AvailableIPPool(TdxHq_API, ips) 152 | pool.wait_interval = 60 * 5 153 | pool.setup() 154 | sleep_time = 130 155 | log.debug("ready to sleep %d" % sleep_time ) 156 | time.sleep(sleep_time) 157 | log.debug("sleep done") 158 | ips = pool.get_ips() 159 | log.debug(str(pool.get_ips())) 160 | log.debug("ready to teardown") 161 | pool.teardown() 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /reader/__init__.py: -------------------------------------------------------------------------------- 1 | from pytdx.reader.daily_bar_reader import TdxDailyBarReader, TdxFileNotFoundException, TdxNotAssignVipdocPathException 2 | from pytdx.reader.min_bar_reader import TdxMinBarReader 3 | from pytdx.reader.lc_min_bar_reader import TdxLCMinBarReader 4 | from pytdx.reader.exhq_daily_bar_reader import TdxExHqDailyBarReader 5 | from pytdx.reader.gbbq_reader import GbbqReader 6 | from pytdx.reader.block_reader import BlockReader 7 | from pytdx.reader.block_reader import CustomerBlockReader 8 | from pytdx.reader.history_financial_reader import HistoryFinancialReader 9 | 10 | __all__ = [ 11 | 'TdxDailyBarReader', 12 | 'TdxFileNotFoundException', 13 | 'TdxNotAssignVipdocPathException', 14 | 'TdxMinBarReader', 15 | 'TdxLCMinBarReader', 16 | 'TdxExHqDailyBarReader', 17 | 'GbbqReader', 18 | 'BlockReader', 19 | 'CustomerBlockReader', 20 | 'HistoryFinancialReader' 21 | ] -------------------------------------------------------------------------------- /reader/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /reader/__pycache__/base_reader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/base_reader.cpython-37.pyc -------------------------------------------------------------------------------- /reader/__pycache__/block_reader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/block_reader.cpython-37.pyc -------------------------------------------------------------------------------- /reader/__pycache__/daily_bar_reader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/daily_bar_reader.cpython-37.pyc -------------------------------------------------------------------------------- /reader/__pycache__/exhq_daily_bar_reader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/exhq_daily_bar_reader.cpython-37.pyc -------------------------------------------------------------------------------- /reader/__pycache__/gbbq_reader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/gbbq_reader.cpython-37.pyc -------------------------------------------------------------------------------- /reader/__pycache__/history_financial_reader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/history_financial_reader.cpython-37.pyc -------------------------------------------------------------------------------- /reader/__pycache__/lc_min_bar_reader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/lc_min_bar_reader.cpython-37.pyc -------------------------------------------------------------------------------- /reader/__pycache__/min_bar_reader.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/reader/__pycache__/min_bar_reader.cpython-37.pyc -------------------------------------------------------------------------------- /reader/base_reader.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from __future__ import unicode_literals, division 3 | import struct 4 | 5 | 6 | class TdxFileNotFoundException(Exception): 7 | pass 8 | 9 | class TdxNotAssignVipdocPathException(Exception): 10 | pass 11 | 12 | 13 | class BaseReader(object): 14 | 15 | def unpack_records(self, format, data): 16 | record_struct = struct.Struct(format) 17 | return (record_struct.unpack_from(data, offset) 18 | for offset in range(0, len(data), record_struct.size)) 19 | 20 | def get_df(self, code_or_file, exchange=None): 21 | raise NotImplementedError('not yet') -------------------------------------------------------------------------------- /reader/block_reader.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import struct 3 | from pytdx.reader.base_reader import BaseReader 4 | from collections import OrderedDict 5 | import pandas as pd 6 | import os 7 | from io import BytesIO 8 | 9 | """ 10 | 参考这个 http://blog.csdn.net/Metal1/article/details/44352639 11 | 12 | """ 13 | 14 | BlockReader_TYPE_FLAT = 0 15 | BlockReader_TYPE_GROUP = 1 16 | 17 | class BlockReader(BaseReader): 18 | 19 | def get_df(self, fname, result_type=BlockReader_TYPE_FLAT): 20 | result = self.get_data(fname, result_type) 21 | return pd.DataFrame(result) 22 | 23 | def get_data(self, fname, result_type=BlockReader_TYPE_FLAT): 24 | 25 | result = [] 26 | 27 | if type(fname) is not bytearray: 28 | with open(fname, "rb") as f: 29 | data = f.read() 30 | else: 31 | data = fname 32 | 33 | pos = 384 34 | (num, ) = struct.unpack(" 0: 114 | source = source + b'\x00' * need_to_padding 115 | enc_data = encrypter.update(source) + encrypter.finalize() 116 | b64_enc_data = base64.encodebytes(enc_data) 117 | return urllib.parse.quote(b64_enc_data) 118 | 119 | def decrypt(self, source): 120 | decrypter = self._cipher.decryptor() 121 | source = urllib.parse.unquote(source) 122 | source = base64.decodebytes(source.encode("utf-8")) 123 | data_bytes = decrypter.update(source) + decrypter.finalize() 124 | return data_bytes.rstrip(b"\x00").decode(self._encoding) 125 | 126 | def data_to_df(self, result): 127 | if 'data' in result: 128 | data = result['data'] 129 | return pd.DataFrame(data=data) 130 | 131 | #------ functions 132 | 133 | def ping(self): 134 | 135 | return self.call("ping", {}) 136 | 137 | def logon(self, ip, port, version, yyb_id, account_id, trade_account, jy_passwrod, tx_password): 138 | return self.call("logon",{ 139 | "ip": ip, 140 | "port": port, 141 | "version": version, 142 | "yyb_id": yyb_id, 143 | "account_no": account_id, 144 | "trade_account": trade_account, 145 | "jy_password": jy_passwrod, 146 | "tx_password": tx_password 147 | }) 148 | 149 | def logoff(self, client_id): 150 | return self.call("logoff", { 151 | "client_id": client_id 152 | }) 153 | 154 | def query_data(self, client_id, category): 155 | return self.call("query_data", { 156 | "client_id": client_id, 157 | "category": category 158 | }) 159 | 160 | def send_order(self, client_id, category, price_type, gddm, zqdm, price, quantity): 161 | return self.call("send_order", { 162 | 'client_id': client_id, 163 | 'category': category, 164 | 'price_type': price_type, 165 | 'gddm': gddm, 166 | 'zqdm': zqdm, 167 | 'price': price, 168 | 'quantity': quantity 169 | }) 170 | 171 | def cancel_order(self, client_id, exchange_id, hth): 172 | return self.call("cancel_order", { 173 | 'client_id': client_id, 174 | 'exchange_id': exchange_id, 175 | 'hth': hth 176 | }) 177 | 178 | def get_quote(self, client_id, code): 179 | return self.call("get_quote", { 180 | 'client_id': client_id, 181 | 'code': code, 182 | }) 183 | 184 | def repay(self, client_id, amount): 185 | return self.call("repay", { 186 | 'client_id': client_id, 187 | 'amount': amount 188 | }) 189 | 190 | def query_history_data(self, client_id, category, begin_date, end_date): 191 | return self.call('query_history_data', { 192 | 'client_id': client_id, 193 | 'category': category, 194 | 'begin_date': begin_date, 195 | 'end_date': end_date 196 | }) 197 | 198 | def query_datas(self, client_id, categories): 199 | return self.call('query_datas', { 200 | 'client_id': client_id, 201 | 'categories': categories 202 | }) 203 | 204 | def get_quotes(self, client_id, codes): 205 | return self.call("get_quotes", { 206 | 'client_id': client_id, 207 | 'zqdms': codes 208 | }) 209 | 210 | def send_orders(self, client_id, orders): 211 | """ 212 | 发送订单 213 | :param client_id: 214 | :param orders: 215 | 格式 216 | [ 217 | { 218 | "category": xx, 219 | "price_type" :xx, 220 | "price": xx, 221 | "gddm": xx, 222 | "zqdm": xx, 223 | "quantity": xx, 224 | }, 225 | { 226 | .... 227 | }, 228 | { 229 | .... 230 | } 231 | ] 232 | 233 | :return: 234 | """ 235 | return self.call("send_orders", { 236 | 'client_id': client_id, 237 | "orders": orders 238 | }) 239 | 240 | def cancel_orders(self, client_id, orders): 241 | """ 242 | 撤销订单 243 | :param client_id: 244 | :param orders: 245 | 格式 246 | [ 247 | { 248 | "exchange_id": xx, 249 | "hth": xx 250 | }, 251 | { 252 | .... 253 | }, 254 | { 255 | .... 256 | } 257 | ] 258 | 259 | :return: 260 | """ 261 | return self.call("cancel_orders", { 262 | 'client_id': client_id, 263 | "orders": orders 264 | }) 265 | 266 | def get_active_clients(self): 267 | return self.call(func="get_active_clients") 268 | 269 | if __name__ == "__main__": 270 | import os 271 | #api = TdxTradeApi(endpoint="http://10.11.5.215:10092/api", enc_key=b"4f1cf3fec4c84c84", enc_iv=b"0c78abc083b011e7") 272 | api = TdxTradeApi(endpoint="http://10.11.5.215:10092/api") 273 | print("---Ping---") 274 | result = api.ping() 275 | print(result) 276 | 277 | print("---登入---") 278 | acc = os.getenv("TDX_ACCOUNT", "") 279 | password = os.getenv("TDX_PASS", "") 280 | result = api.logon("202.108.253.186", 7708, 281 | "8.23", 32, 282 | acc, acc, password, "") 283 | 284 | print(result) 285 | 286 | if result["success"]: 287 | client_id = result["data"]["client_id"] 288 | 289 | for i in (0,1,2,3,4,5,6,7,8,12,13,14,15): 290 | print("---查询信息 cate=%d--" % i) 291 | print(api.data_to_df(api.query_data(client_id, i))) 292 | 293 | 294 | print("---查询报价---") 295 | print(api.data_to_df(api.get_quote(client_id, '600315'))) 296 | 297 | print("---批量查询报价---") 298 | print(api.data_to_df(api.get_quotes(client_id, ['600315', '000001']))) 299 | 300 | print("---批量查询信息") 301 | print(api.data_to_df(api.query_datas(client_id, [0,1,2]))) 302 | 303 | print("---登出---") 304 | print(api.logoff(client_id)) 305 | 306 | 307 | 308 | 309 | 310 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | 3 | 4 | from .trade_date import trade_date_sse 5 | from .date_util import get_real_trade_date 6 | #from pytdx.util.best_ip import select_best_ip, ping 7 | # comment to avoid recycle ref -------------------------------------------------------------------------------- /util/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/util/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /util/__pycache__/best_ip.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/util/__pycache__/best_ip.cpython-37.pyc -------------------------------------------------------------------------------- /util/__pycache__/date_util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/util/__pycache__/date_util.cpython-37.pyc -------------------------------------------------------------------------------- /util/__pycache__/trade_date.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wps1112/pytdx_backup/daa6d37f3ab27b42cd2d36b91656ce960a5a3385/util/__pycache__/trade_date.cpython-37.pyc -------------------------------------------------------------------------------- /util/best_ip.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | # see https://github.com/rainx/pytdx/issues/38 IP寻优的简单办法 3 | # by yutianst 4 | 5 | import datetime 6 | from pytdx.hq import TdxHq_API 7 | from pytdx.exhq import TdxExHq_API 8 | 9 | stock_ip = [{'ip': '106.120.74.86', 'port': 7711, 'name': '北京行情主站1'}, 10 | {'ip': '113.105.73.88', 'port': 7709, 'name': '深圳行情主站'}, 11 | {'ip': '113.105.73.88', 'port': 7711, 'name': '深圳行情主站'}, 12 | {'ip': '114.80.80.222', 'port': 7711, 'name': '上海行情主站'}, 13 | {'ip': '117.184.140.156', 'port': 7711, 'name': '移动行情主站'}, 14 | {'ip': '119.147.171.206', 'port': 443, 'name': '广州行情主站'}, 15 | {'ip': '119.147.171.206', 'port': 80, 'name': '广州行情主站'}, 16 | {'ip': '218.108.50.178', 'port': 7711, 'name': '杭州行情主站'}, 17 | {'ip': '221.194.181.176', 'port': 7711, 'name': '北京行情主站2'}, 18 | {'ip': '106.120.74.86', 'port': 7709}, 19 | {'ip': '112.95.140.74', 'port': 7709}, 20 | {'ip': '112.95.140.92', 'port': 7709}, 21 | {'ip': '112.95.140.93', 'port': 7709}, 22 | {'ip': '113.05.73.88', 'port': 7709}, 23 | {'ip': '114.67.61.70', 'port': 7709}, 24 | {'ip': '114.80.149.19', 'port': 7709}, 25 | {'ip': '114.80.149.22', 'port': 7709}, 26 | {'ip': '114.80.149.84', 'port': 7709}, 27 | {'ip': '114.80.80.222', 'port': 7709}, 28 | {'ip': '115.238.56.198', 'port': 7709}, 29 | {'ip': '115.238.90.165', 'port': 7709}, 30 | {'ip': '117.184.140.156', 'port': 7709}, 31 | {'ip': '119.147.164.60', 'port': 7709}, 32 | {'ip': '119.147.171.206', 'port': 7709}, 33 | {'ip': '119.29.51.30', 'port': 7709}, 34 | {'ip': '121.14.104.70', 'port': 7709}, 35 | {'ip': '121.14.104.72', 'port': 7709}, 36 | {'ip': '121.14.110.194', 'port': 7709}, 37 | {'ip': '121.14.2.7', 'port': 7709}, 38 | {'ip': '123.125.108.23', 'port': 7709}, 39 | {'ip': '123.125.108.24', 'port': 7709}, 40 | {'ip': '124.160.88.183', 'port': 7709}, 41 | {'ip': '180.153.18.17', 'port': 7709}, 42 | {'ip': '180.153.18.170', 'port': 7709}, 43 | {'ip': '180.153.18.171', 'port': 7709}, 44 | {'ip': '180.153.39.51', 'port': 7709}, 45 | {'ip': '218.108.47.69', 'port': 7709}, 46 | {'ip': '218.108.50.178', 'port': 7709}, 47 | {'ip': '218.108.98.244', 'port': 7709}, 48 | {'ip': '218.75.126.9', 'port': 7709}, 49 | {'ip': '218.9.148.108', 'port': 7709}, 50 | {'ip': '221.194.181.176', 'port': 7709}, 51 | {'ip': '59.173.18.69', 'port': 7709}, 52 | {'ip': '60.12.136.250', 'port': 7709}, 53 | {'ip': '60.191.117.167', 'port': 7709}, 54 | {'ip': '60.28.29.69', 'port': 7709}, 55 | {'ip': '61.135.142.73', 'port': 7709}, 56 | {'ip': '61.135.142.88', 'port': 7709}, 57 | {'ip': '61.152.107.168', 'port': 7721}, 58 | {'ip': '61.152.249.56', 'port': 7709}, 59 | {'ip': '61.153.144.179', 'port': 7709}, 60 | {'ip': '61.153.209.138', 'port': 7709}, 61 | {'ip': '61.153.209.139', 'port': 7709}, 62 | {'ip': 'hq.cjis.cn', 'port': 7709}, 63 | {'ip': 'hq1.daton.com.cn', 'port': 7709}, 64 | {'ip': 'jstdx.gtjas.com', 'port': 7709}, 65 | {'ip': 'shtdx.gtjas.com', 'port': 7709}, 66 | {'ip': 'sztdx.gtjas.com', 'port': 7709}, 67 | {'ip': '113.105.142.162', 'port': 7721}, 68 | {'ip': '23.129.245.199', 'port': 7721}] 69 | 70 | future_ip = [{'ip': '106.14.95.149', 'port': 7727, 'name': '扩展市场上海双线'}, 71 | {'ip': '112.74.214.43', 'port': 7727, 'name': '扩展市场深圳双线1'}, 72 | {'ip': '119.147.86.171', 'port': 7727, 'name': '扩展市场深圳主站'}, 73 | {'ip': '119.97.185.5', 'port': 7727, 'name': '扩展市场武汉主站1'}, 74 | {'ip': '120.24.0.77', 'port': 7727, 'name': '扩展市场深圳双线2'}, 75 | {'ip': '124.74.236.94', 'port': 7721}, 76 | {'ip': '202.103.36.71', 'port': 443, 'name': '扩展市场武汉主站2'}, 77 | {'ip': '47.92.127.181', 'port': 7727, 'name': '扩展市场北京主站'}, 78 | {'ip': '59.175.238.38', 'port': 7727, 'name': '扩展市场武汉主站3'}, 79 | {'ip': '61.152.107.141', 'port': 7727, 'name': '扩展市场上海主站1'}, 80 | {'ip': '61.152.107.171', 'port': 7727, 'name': '扩展市场上海主站2'}, 81 | {'ip': '119.147.86.171', 'port': 7721, 'name': '扩展市场深圳主站'}, 82 | {'ip': '47.107.75.159', 'port': 7727, 'name': '扩展市场深圳双线3'}] 83 | 84 | def ping(ip, port=7709, type_='stock'): 85 | api = TdxHq_API() 86 | apix = TdxExHq_API() 87 | __time1 = datetime.datetime.now() 88 | try: 89 | if type_ in ['stock']: 90 | with api.connect(ip, port, time_out=0.7): 91 | res = api.get_security_list(0, 1) 92 | #print(len(res)) 93 | if res is not None: 94 | if len(res) > 800: 95 | print('GOOD RESPONSE {}'.format(ip)) 96 | return datetime.datetime.now() - __time1 97 | else: 98 | print('BAD RESPONSE {}'.format(ip)) 99 | return datetime.timedelta(9, 9, 0) 100 | else: 101 | 102 | print('BAD RESPONSE {}'.format(ip)) 103 | return datetime.timedelta(9, 9, 0) 104 | elif type_ in ['future']: 105 | with apix.connect(ip, port, time_out=0.7): 106 | res = apix.get_instrument_count() 107 | if res is not None: 108 | if res > 20000: 109 | print('GOOD RESPONSE {}'.format(ip)) 110 | return datetime.datetime.now() - __time1 111 | else: 112 | print('️Bad FUTUREIP REPSONSE {}'.format(ip)) 113 | return datetime.timedelta(9, 9, 0) 114 | else: 115 | print('️Bad FUTUREIP REPSONSE {}'.format(ip)) 116 | return datetime.timedelta(9, 9, 0) 117 | except Exception as e: 118 | if isinstance(e, TypeError): 119 | print(e) 120 | print('Tushare内置的pytdx版本和最新的pytdx 版本不同, 请重新安装pytdx以解决此问题') 121 | print('pip uninstall pytdx') 122 | print('pip install pytdx') 123 | 124 | else: 125 | print('BAD RESPONSE {}'.format(ip)) 126 | return datetime.timedelta(9, 9, 0) 127 | 128 | 129 | 130 | def select_best_ip(_type='stock'): 131 | """目前这里给的是单线程的选优, 如果需要多进程的选优/ 最优ip缓存 可以参考 132 | https://github.com/QUANTAXIS/QUANTAXIS/blob/master/QUANTAXIS/QAFetch/QATdx.py#L106 133 | 134 | 135 | Keyword Arguments: 136 | _type {str} -- [description] (default: {'stock'}) 137 | 138 | Returns: 139 | [type] -- [description] 140 | """ 141 | best_ip = { 142 | 'stock': { 143 | 'ip': None, 'port': None 144 | }, 145 | 'future': { 146 | 'ip': None, 'port': None 147 | } 148 | } 149 | ip_list = stock_ip if _type== 'stock' else future_ip 150 | 151 | data = [ping(x['ip'], x['port'], _type) for x in ip_list] 152 | results = [] 153 | for i in range(len(data)): 154 | # 删除ping不通的数据 155 | if data[i] < datetime.timedelta(0, 9, 0): 156 | results.append((data[i], ip_list[i])) 157 | # 按照ping值从小大大排序 158 | results = [x[1] for x in sorted(results, key=lambda x: x[0])] 159 | 160 | return results[0] 161 | 162 | if __name__ == '__main__': 163 | ip = select_best_ip('stock') 164 | print(ip) 165 | ip = select_best_ip('future') 166 | print(ip) -------------------------------------------------------------------------------- /util/date_util.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from .trade_date import trade_date_sse 3 | 4 | import datetime 5 | 6 | 7 | def get_real_trade_date(date, towards): 8 | """ 9 | 获取真实的交易日期,其中,第三个参数towards是表示向前/向后推 10 | towards=1 日期向后迭代 11 | towards=-1 日期向前迭代 12 | @yutiansut 13 | """ 14 | if towards == 1: 15 | while date not in trade_date_sse: 16 | date = str(datetime.datetime.strptime( 17 | date, '%Y-%m-%d') + datetime.timedelta(days=1))[0:10] 18 | else: 19 | return date 20 | elif towards == -1: 21 | while date not in trade_date_sse: 22 | date = str(datetime.datetime.strptime( 23 | date, '%Y-%m-%d') - datetime.timedelta(days=1))[0:10] 24 | else: 25 | return date 26 | --------------------------------------------------------------------------------