cfquant 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. LTtx/__init__.py +2 -0
  2. LTtx/tx/Config.txt +8 -0
  3. LTtx/tx/LTtx_server.py +1177 -0
  4. LTtx/tx/__init__.py +5 -0
  5. LTtx/tx/tx.py +1935 -0
  6. cfquant/__init__.py +26 -0
  7. cfquant/account_routing.py +119 -0
  8. cfquant/channels.py +76 -0
  9. cfquant/cli.py +266 -0
  10. cfquant/client.py +365 -0
  11. cfquant/config.py +86 -0
  12. cfquant/hub.py +304 -0
  13. cfquant/logging_i18n.py +348 -0
  14. cfquant/normal_bridge.py +676 -0
  15. cfquant/pipe_bridge.py +249 -0
  16. cfquant/pipe_client.py +223 -0
  17. cfquant/pipe_hub.py +602 -0
  18. cfquant/pipe_transport.py +493 -0
  19. cfquant/protocol.py +238 -0
  20. cfquant/qmt_bridge.py +1081 -0
  21. cfquant/runtime_report.py +334 -0
  22. cfquant/tx.py +1898 -0
  23. cfquant/tx_trade_bridge.py +1905 -0
  24. cfquant/version.py +5 -0
  25. cfquant/xtconstant.py +262 -0
  26. cfquant/xtdata.py +462 -0
  27. cfquant/xttrader.py +955 -0
  28. cfquant/xttype.py +586 -0
  29. cfquant-0.2.0.dist-info/LICENSE +21 -0
  30. cfquant-0.2.0.dist-info/METADATA +144 -0
  31. cfquant-0.2.0.dist-info/RECORD +90 -0
  32. cfquant-0.2.0.dist-info/WHEEL +5 -0
  33. cfquant-0.2.0.dist-info/entry_points.txt +4 -0
  34. cfquant-0.2.0.dist-info/top_level.txt +6 -0
  35. cfquant_pipe_hub.py +45 -0
  36. cfquant_web_server.py +12137 -0
  37. qmt_scripts/CFQUANT.py +507 -0
  38. qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +830 -0
  39. qmt_scripts/CFQUANT_LITE.py +4928 -0
  40. qmt_scripts/CFQUANT_TRADE_LOWLAT.py +382 -0
  41. qmt_scripts/__init__.py +1 -0
  42. qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_CTYPE_ALL_LOWLAT_SH.py +866 -0
  43. qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_CTYPE_ALL_LOWLAT_SZ.py +866 -0
  44. qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_LITE_SH.py +5020 -0
  45. qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_LITE_SZ.py +5020 -0
  46. qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_TRADE_LOWLAT_SH.py +436 -0
  47. qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_TRADE_LOWLAT_SZ.py +436 -0
  48. qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/readme.md +95 -0
  49. web_dashboard/__init__.py +1 -0
  50. web_dashboard/app.js +9847 -0
  51. web_dashboard/avatars/amber-pulse.svg +15 -0
  52. web_dashboard/avatars/copper-grid.svg +15 -0
  53. web_dashboard/avatars/market-blue.svg +15 -0
  54. web_dashboard/avatars/rose-circuit.svg +15 -0
  55. web_dashboard/avatars/signal-green.svg +15 -0
  56. web_dashboard/avatars/slate-wave.svg +15 -0
  57. web_dashboard/avatars/teal-orbit.svg +16 -0
  58. web_dashboard/avatars/violet-node.svg +15 -0
  59. web_dashboard/favicon.svg +5 -0
  60. web_dashboard/image/qmt_deploy/image-10.png +0 -0
  61. web_dashboard/image/qmt_deploy/image-11.png +0 -0
  62. web_dashboard/image/qmt_deploy/image-12.png +0 -0
  63. web_dashboard/image/qmt_deploy/image-13.png +0 -0
  64. web_dashboard/image/qmt_deploy/image-14.png +0 -0
  65. web_dashboard/image/qmt_deploy/image-15.png +0 -0
  66. web_dashboard/image/qmt_deploy/image-16.png +0 -0
  67. web_dashboard/image/qmt_deploy/image-17.png +0 -0
  68. web_dashboard/image/qmt_deploy/image-18.png +0 -0
  69. web_dashboard/image/qmt_deploy/image-19.png +0 -0
  70. web_dashboard/image/qmt_deploy/image-20.png +0 -0
  71. web_dashboard/image/qmt_deploy/image-21.png +0 -0
  72. web_dashboard/image/qmt_deploy/image-4.png +0 -0
  73. web_dashboard/image/qmt_deploy/image-5.png +0 -0
  74. web_dashboard/image/qmt_deploy/image-6.png +0 -0
  75. web_dashboard/image/qmt_deploy/image-7.png +0 -0
  76. web_dashboard/image/qmt_deploy/image-8.png +0 -0
  77. web_dashboard/image/qmt_deploy/image-9.png +0 -0
  78. web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-1.png +0 -0
  79. web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-2.png +0 -0
  80. web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-3.png +0 -0
  81. web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image.png +0 -0
  82. web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783479325179.png +0 -0
  83. web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783479745262.png +0 -0
  84. web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783480178700.png +0 -0
  85. web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783480428759.png +0 -0
  86. web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783489807217.png +0 -0
  87. web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783490503491.png +0 -0
  88. web_dashboard/index.html +2317 -0
  89. web_dashboard/styles.css +7817 -0
  90. web_dashboard/vendor/mermaid.min.js +2029 -0
LTtx/tx/tx.py ADDED
@@ -0,0 +1,1935 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Thu Sep 22 11:09:51 2022
4
+
5
+ @author: Administrator
6
+ """
7
+ import socket
8
+ import threading
9
+ import json
10
+ import queue
11
+ import struct
12
+ import time
13
+ import os
14
+ import hashlib
15
+ import random
16
+ import datetime
17
+ import sys
18
+ import subprocess
19
+ import importlib
20
+ import pandas as pd
21
+
22
+
23
+ def _hidden_subprocess_kwargs():
24
+ if os.name != "nt":
25
+ return {}
26
+ kwargs = {}
27
+ create_no_window = getattr(subprocess, "CREATE_NO_WINDOW", 0)
28
+ if create_no_window:
29
+ kwargs["creationflags"] = create_no_window
30
+ try:
31
+ startupinfo = subprocess.STARTUPINFO()
32
+ startupinfo.dwFlags |= getattr(subprocess, "STARTF_USESHOWWINDOW", 1)
33
+ startupinfo.wShowWindow = getattr(subprocess, "SW_HIDE", 0)
34
+ kwargs["startupinfo"] = startupinfo
35
+ except Exception:
36
+ pass
37
+ return kwargs
38
+
39
+
40
+
41
+ class txl:
42
+ def __init__(self,ip,port,tocken,show=True,check_version=False,loss_callback=None):
43
+ '''
44
+ ip:服务器IP,
45
+ port:服务器端口,
46
+ tocken:认证秘钥,任意字符串
47
+ show:是否输出日志信息,默认为输出日志
48
+ check_version:检查版本,如果有新版本将会拉取最新的版本,但需要手动重启一次主程序,谨慎使用
49
+ loss_callback:(待开发)在运行过程中,如果和服务端发生断网时的回调函数,传入后有助于处理连接断开时的处理动作
50
+
51
+ '''
52
+ self.run_on = True
53
+ self.orjson_on = None
54
+ self.channel_list = False
55
+ self.sys_print_on = show
56
+ self.clean_day = None
57
+ self.id = time.strftime("%Y%m%d%H%M%S")+'_'+self.create_channel(5)
58
+ self.log_que = queue.Queue()
59
+ self.log_dir = self._default_log_dir()
60
+ self.mkdir(self.log_dir)
61
+ msg = '包引入成功'
62
+ self.save_log(msg)
63
+ self.loss_callback = loss_callback
64
+ self.ip = ip
65
+ self.port = port
66
+ self.tocken = tocken
67
+ self.start_tx_on = False
68
+ self.txg = False
69
+ self.init_txg = False#初始化txg
70
+ self.__judge_python_version()
71
+ self.txg_running = False
72
+ self.__tx = False
73
+ self.__tx_plus = False
74
+ self.push_count = 0
75
+ self.timeout = 2
76
+ self.heartbeat = 1
77
+ self.tx_que_plus = queue.Queue(maxsize=0)
78
+ self.tx_running = False
79
+ self.file_tx = None
80
+ #ZMQ模式
81
+ self.__ZMQ = None
82
+ self.__ZMQ_broadcast = None
83
+ self.__txg_heartbeat_on = True#通信系统Push模式心跳检测线程状态
84
+ self.__txg_heartbeat_time = time.time()
85
+ self.version = '8.0.1'
86
+ self.__version__ = self.version
87
+ self.check_version_on = check_version
88
+ self.dict_TradeDay = {}
89
+ self.check_version()
90
+ self.current_file = sys.modules[__name__].__file__ #当前文件名
91
+ self.current_dir = os.path.dirname(os.path.abspath(self.current_file))#当前目录
92
+ self.local_ip = self.get_local_ip()
93
+ self.__version__msg = '更新于2026-07-07'
94
+ self.txg_dict = {}#存储txg对象
95
+ msg = '通信系统V%s加载成功,Have fun!当前版本更新于%s'%(self.__version__,self.__version__msg)
96
+ self.sys_print(msg)
97
+ self.server2 = False#判断服务器是不是新版本,看是否要用批量发送
98
+ self.start()
99
+ self.td_df = pd.DataFrame()#存放交易日期数据,如果没有调用则不会产生数据
100
+ self.pre_TradeDay = {'now_date':''}#存放前一个交易日
101
+ self.d_push_tx = False
102
+
103
+ def start(self,):
104
+ '''
105
+ 启动对应的组件
106
+ '''
107
+ threading.Thread(target = self.__main_log).start()
108
+
109
+
110
+ def auto_import(self,package_name, import_name=None):
111
+ import_name = import_name or package_name
112
+ try:
113
+ self.orjson_on = True
114
+ return importlib.import_module(import_name)
115
+ except ImportError:
116
+ self.sys_price(f"[TxLink自动安装] 缺少依赖 {package_name},正在安装...请不要退出,安装完成后程序将正常运行")
117
+ try:
118
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package_name], **_hidden_subprocess_kwargs())
119
+ self.orjson_on = True
120
+ return importlib.import_module(import_name)
121
+ except:
122
+ self.sys_price('TxLink自动安装orjson模块失败,采用json模块,若要体验高性能,请手动pip install orjson后重试')
123
+ import json
124
+ self.orjson_on = False
125
+ return json
126
+
127
+
128
+ def __judge_python_version(self):
129
+ '''
130
+ python版本判断
131
+ -对于3.7以下的版本,用queue.Queue和普通的json
132
+ -对于3.7以上的版本,用deque加速
133
+ '''
134
+ if sys.version_info >= (3, 8):
135
+ try:
136
+ self.json = self.auto_import('orjson')
137
+ data = self.json.dumps('abc')
138
+ if isinstance(data, bytes):#二次验证导包的情况
139
+ msg = '采用orjson进行数据交互'
140
+ self.orjson_on = True
141
+ else:
142
+ msg = '采用json进行数据交互'
143
+ self.orjson_on = False
144
+ self.save_log(msg,)
145
+ except Exception as e:
146
+ msg = '自动引入orjson出错>>>%s,采用json进行数据交互'%(e)
147
+ self.save_log(msg,)
148
+ self.json = json
149
+ self.orjson_on = False
150
+ else:
151
+ msg = 'python版本小于3.8,采用json进行数据交互'
152
+ self.save_log(msg,)
153
+ self.json = json
154
+ self.orjson_on = False
155
+ if sys.version_info >= (3, 7):
156
+ msg = 'python版本大于3.7,采用SimpleQueue进行数据传输'
157
+ self.Q = queue.SimpleQueue()
158
+ self.tx_que = queue.SimpleQueue()
159
+ self.save_log(msg)
160
+ else:
161
+ msg = 'python版本大于3.7,采用Queue进行数据传输'
162
+ self.Q = queue.Queue()
163
+ self.tx_que = queue.Queue()
164
+ self.save_log(msg)
165
+
166
+ def clean_log(self):
167
+ '''
168
+ 清理30天前的日志
169
+ '''
170
+ if self.clean_day != self.get_nowdate():
171
+ try:
172
+ retention_days = int(os.environ.get("CFQUANT_TX_LOG_RETENTION_DAYS") or os.environ.get("CFQUANT_LOG_RETENTION_DAYS") or "30")
173
+ cutoff = time.time() - max(1, retention_days) * 86400
174
+ for file in os.listdir(self.log_dir):
175
+ file_name = os.path.join(self.log_dir, file)
176
+ if os.path.isfile(file_name) and os.path.getmtime(file_name) < cutoff:
177
+ self.delete_file(file_name)
178
+ except Exception as e:
179
+ msg = '日志自动清楚报错了>>>>>为了不影响使用体验,请查看一下原因>>>>%s'%(e)
180
+ self.sys_print(msg,show_force=True)
181
+ self.clean_day = self.get_nowdate()
182
+
183
+ def _default_log_dir(self):
184
+ tx_log_dir = os.environ.get("CFQUANT_TX_LOG_DIR")
185
+ if tx_log_dir:
186
+ return os.path.abspath(tx_log_dir)
187
+ log_dir = os.environ.get("CFQUANT_LOG_DIR")
188
+ if log_dir:
189
+ return os.path.abspath(os.path.join(log_dir, "tx_log"))
190
+ return os.path.abspath(os.path.join(os.getcwd(), "log", "tx_log"))
191
+
192
+ def delete_file(self,file_name):
193
+ msg = '删除tx运行日志%s'%(file_name)
194
+ os.remove(file_name)
195
+ self.sys_print(msg,show_force=True)
196
+
197
+
198
+
199
+ def save_log(self,msg,):
200
+ self.log_que.put((self.id,msg))
201
+
202
+ def __main_log(self):
203
+ self.clean_log()
204
+ while self.run_on:
205
+ try:
206
+ data = str(self.log_que.get())+'\n'
207
+ if self.log_que.qsize() > 100:
208
+ self.clean_log()
209
+ for i in range(100):
210
+ data = data + self.log_que.get() +'\n'
211
+ with open(os.path.join(self.log_dir, '%s.log'%(self.get_nowdate())),'a+',encoding='utf-8') as f:
212
+ f.write(data)
213
+ except:#过滤掉日志记录出错
214
+ #直接跳过日志记录,不影响主程序
215
+ pass
216
+
217
+ def check_version(self,):
218
+ '''
219
+ 检查本地客户端版本是不是最新的
220
+ '''
221
+ if self.check_version_on:
222
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
223
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192*100)
224
+ client.connect(('pc.zltnet.top',2099))
225
+ tem_dict = {'con_type':'check_version',
226
+ 'tocken':'LTtx',
227
+ }
228
+ client.sendall(json.dumps(tem_dict).encode('utf-8'))
229
+ server_msg = client.recv(1024).decode()
230
+ if server_msg['LTtx_lastest'] != self.__version__:
231
+ self.sys_print('从LTtx官网获取最新tx.py')
232
+ self.__get_lastest_file()
233
+ self.sys_print('最新版本获取完成,程序2秒后重启')
234
+ time.sleep(2)
235
+ os._exit(1)
236
+ else:
237
+ self.sys_print('当前版本已经是最新')
238
+
239
+ def update_tx_version(self):
240
+ '''
241
+ 更新客户端到最新版本
242
+
243
+ '''
244
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
245
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192*100)
246
+ client.connect(('pc.zltnet.top',2099))
247
+ tem_dict = {'con_type':'check_version',
248
+ 'tocken':'LTtx',
249
+ }
250
+ client.sendall(json.dumps(tem_dict).encode('utf-8'))
251
+ server_msg = json.loads(client.recv(1024).decode())
252
+ if server_msg['LTtx_lastest'] != self.__version__:
253
+ self.sys_print('从LTtx官网获取最新tx.py')
254
+ self.__get_lastest_file()
255
+ self.sys_print('最新版本获取完成,程序2秒后重启')
256
+ time.sleep(2)
257
+ os._exit(1)
258
+ else:
259
+ self.sys_print('当前版本已经是最新')
260
+
261
+ def sys_print(self,data,show_force=False):
262
+ msg = '%sLTtx[info]>>>>:%s,'%(self.get_nowtime(),data)
263
+ if self.sys_print_on or show_force:
264
+ print(msg)
265
+ self.save_log(msg)
266
+
267
+ def send_file(self,file_name,show_on=True):
268
+ '''
269
+ 将本地文件上传至数据中心
270
+
271
+ Parameters
272
+ ----------
273
+ file_name : TYPE
274
+ 文件路径,通常为./data/file.txt.
275
+ show_on : TYPE, optional
276
+ 是否Print进度,默认开启. The default is True.
277
+
278
+ Returns
279
+ -------
280
+ code int.
281
+ 返回0表示成功,其他表示错误,参见msg
282
+ msg string
283
+ 提示信息
284
+
285
+ '''
286
+ if os.path.isfile(file_name):
287
+ if show_on:
288
+ self.sys_print('识别到文件存在')
289
+ self.sys_print(file_name)
290
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
291
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192*100)
292
+ client.connect((self.ip,self.port))
293
+ hash_md5 = hashlib.md5()
294
+ with open(file_name, "rb") as f:
295
+ for chunk in iter(lambda: f.read(4096), b""):
296
+ hash_md5.update(chunk)
297
+ file_hash = hash_md5.hexdigest()
298
+ tem_dict = {'con_type':'file_mode',
299
+ 'tocken':self.tocken,
300
+ 'file_name':file_name.rsplit('/',1)[-1],
301
+ 'file_hash':file_hash,
302
+ 'file_mode':'upload_file',
303
+ }
304
+ client.sendall(json.dumps(tem_dict).encode('utf-8'))
305
+ server_msg = client.recv(1024).decode()
306
+ if server_msg == 'i am ok':
307
+ t1 = time.time()
308
+ file = open(file_name, 'rb')
309
+ file_data = file.read(1024)
310
+ while file_data:
311
+ client.send(file_data)
312
+ file_data = file.read(1024)
313
+ file.close()
314
+ # client.send(b'file send finish')
315
+ # client.recv(1024).decode()
316
+ client.close()
317
+ if show_on:
318
+ self.sys_print('file send done! usetime:%ss'%(round(time.time()-t1,6)))
319
+ else:
320
+ self.sys_print('服务端拒绝了本次文件传输请求')
321
+ else:
322
+ self.sys_print('文件不存在,请重新传入,当前收到的文件名:')
323
+ self.sys_print(file_name)
324
+
325
+ def __get_lastest_file(self,file_name='tx.py',file_path='./',show_on=False):
326
+ '''
327
+ 从服务端下载文件
328
+
329
+ Parameters
330
+ ----------
331
+ file_name : TYPE
332
+ 要下载的文件名.
333
+ file_path : TYPE, optional
334
+ 文件保存路径,不存在的路径将会被创建. The default is './'.
335
+ show_on : TYPE, optional
336
+ 是否Print进度,默认开启. The default is True.
337
+ Returns
338
+ -------
339
+ code int.
340
+ 返回0表示成功,其他表示错误,参见msg
341
+ msg string
342
+ 提示信息
343
+
344
+ '''
345
+ file_path = self.current_dir
346
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
347
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192*100)
348
+ client.connect(('pc.zltnet.top',2099))
349
+ tem_dict = {'con_type':'file_mode',
350
+ 'tocken':'LTtx',
351
+ 'file_name':file_name,
352
+ 'file_mode':'download_file',
353
+ }
354
+ client.sendall(json.dumps(tem_dict).encode('utf-8'))
355
+ server_msg = client.recv(1024).decode()
356
+ if server_msg == 'file exist':
357
+ t1 = time.time()
358
+ file = open(file_path+file_name+'.tmp','wb')
359
+ file_data = client.recv(1024)
360
+ while file_data:
361
+ file.write(file_data)
362
+ file_data = client.recv(1024)
363
+
364
+ file.close()
365
+ if os.path.isfile(file_path+file_name):
366
+ os.remove(file_path+file_name)
367
+ os.rename(file_path+file_name+ '.tmp', file_path+file_name)
368
+ code = 0
369
+ msg = '%s文件接收完成,用时%ss'%(file_name,round(time.time()-t1,6))
370
+ if show_on:
371
+ print(code,msg)
372
+ return code,msg
373
+ else:
374
+ code = -1
375
+ msg = '服务端该文件不存在,请先上传'
376
+ print(code,msg)
377
+ return code,msg
378
+
379
+
380
+ def recv_file(self,file_name,file_path='./',show_on=False):
381
+ '''
382
+ 从服务端下载文件
383
+
384
+ Parameters
385
+ ----------
386
+ file_name : TYPE
387
+ 要下载的文件名.
388
+ file_path : TYPE, optional
389
+ 文件保存路径,不存在的路径将会被创建. The default is './'.
390
+ show_on : TYPE, optional
391
+ 是否Print进度,默认开启. The default is True.
392
+ Returns
393
+ -------
394
+ code int.
395
+ 返回0表示成功,其他表示错误,参见msg
396
+ msg string
397
+ 提示信息
398
+
399
+ '''
400
+ if file_path[-1] != '/':
401
+ file_path = file_path + '/'
402
+ if os.path.isdir(file_path):
403
+ pass
404
+ else:
405
+ self.sys_print(('文件路径不存在,自动创建该路径'))
406
+ try:
407
+ os.mkdir(file_path)
408
+ except Exception as e:
409
+ raise TypeError('文件路径自动创建失败,失败原因:%s'%(e))
410
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
411
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192*100)
412
+ client.connect((self.ip,self.port))
413
+ tem_dict = {'con_type':'file_mode',
414
+ 'tocken':self.tocken,
415
+ 'file_name':file_name,
416
+ 'file_mode':'download_file',
417
+ }
418
+ client.sendall(json.dumps(tem_dict).encode('utf-8'))
419
+ server_msg = client.recv(1024).decode()
420
+ if server_msg == 'file exist':
421
+ t1 = time.time()
422
+ file = open(file_path+file_name+'.tmp','wb')
423
+ file_data = client.recv(1024)
424
+ while file_data:
425
+ file.write(file_data)
426
+ file_data = client.recv(1024)
427
+ file.close()
428
+ code = 0
429
+ msg = ('%s文件接收完成,文件MD5检验通过'%(file_name))
430
+ if os.path.isfile(file_path+file_name):
431
+ os.remove(file_path+file_name)
432
+ os.rename(file_path+file_name+ '.tmp', file_path+file_name)
433
+ code = 0
434
+ msg = '%s文件接收完成,,用时%ss'%(file_name,round(time.time()-t1,6))
435
+ if show_on:
436
+ print(code,msg)
437
+ return code,msg
438
+ else:
439
+ code = -1
440
+ msg = '服务端该文件不存在,请先上传'
441
+ self.sys_print((code,msg))
442
+ return code,msg
443
+
444
+ def start_tx(self,mode=None):
445
+ '''
446
+ - 开启数据推送功能,开启后可以使用get,put,push等函数
447
+ - 如果要订阅数据,通过start_txg进行开启订阅,即可收到对应的数据
448
+ '''
449
+ if not self.run_on:
450
+ msg = '识别到此前已经close过txl,重新运行self.run_on'
451
+ self.sys_print(msg,show_force=True)
452
+ self.run_on = True
453
+ if not mode:
454
+ self.tx_running = True
455
+ while self.run_on:
456
+ try:
457
+ msg = 'start_tx正在连接(%s,%s)LTtx服务器,请稍后'%(self.ip,self.port)
458
+ self.sys_print(msg,show_force=True)
459
+ if self.__tx == False:
460
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
461
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192*100)
462
+ # 设置立即发送(关闭 Nagle 算法,降低延迟)
463
+ client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
464
+ client.connect((self.ip,self.port))
465
+ tem_dict = {'con_type':'put_mode','tocken':self.tocken,
466
+ 'local_ip':self.local_ip,
467
+ 'current_file':self.current_file,'current_dir':self.current_dir,
468
+ }
469
+ self.sys_print(tem_dict)
470
+ client.sendall(json.dumps(tem_dict).encode('utf-8'))
471
+ if self.recv_msg_start_tx(client):
472
+ self.__tx = client
473
+ t0 = threading.Thread(target=self.main_tx_que)
474
+ t0.start()
475
+ thp0 = threading.Thread(target = self.start_tx_hearbeat)
476
+ thp0.start()
477
+ msg = 'start_tx连接成功,have fun'
478
+ self.sys_print(msg,show_force=True)
479
+ break
480
+ else:
481
+ self.close_connection(self.__tx)
482
+ self.__tx = False
483
+ else:
484
+ code = -1
485
+ msg = '请勿重复连接tx'
486
+ result = {'code':code,'msg':msg}
487
+ return result
488
+ except Exception as e:
489
+ msg = '服务端未启动,将在1秒后继续尝试start_tx,%s'%(e)
490
+ self.sys_print(msg,show_force=True)
491
+ time.sleep(1)
492
+
493
+ def start_plus(self,):
494
+ '''
495
+ 开启高性能式,该模式打开后可以使用push_plus函数,推送数据的延迟和效率会更高
496
+ '''
497
+ if not self.run_on:
498
+ msg = '识别到此前已经close过txl,重新运行self.run_on'
499
+ self.sys_print(msg,show_force=True)
500
+ while self.run_on:
501
+ try:
502
+ msg = (self.get_nowtime(),'start_flash正在连接(%s,%s)LTtx服务器,请稍后'%(self.ip,self.port))
503
+ self.sys_print(msg)
504
+ if self.__tx_plus == False:
505
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
506
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192*100)
507
+ client.connect((self.ip,self.port))
508
+ self.__tx_plus = client
509
+ tem_dict = {'con_type':'plus_mode','tocken':self.tocken,
510
+ 'local_ip':self.local_ip,
511
+ 'current_file':self.current_file,'current_dir':self.current_dir,
512
+ }
513
+ self.__tx_plus.sendall(json.dumps(tem_dict).encode('utf-8'))
514
+ if self.recv_msg_start_tx(client):
515
+ t0 = threading.Thread(target=self.main_tx_que_plus)
516
+ t0.start()
517
+ thp0 = threading.Thread(target = self.start_tx_hearbeat_plus)
518
+ thp0.start()
519
+ break
520
+ else:
521
+ self.close_connection(self.__tx_plus)
522
+ self.__tx_plus = False
523
+ else:
524
+ code = -1
525
+ msg = '请勿重复连接'
526
+ result = {'code':code,'msg':msg}
527
+ self.sys_print(result)
528
+ return result
529
+
530
+ except Exception as e:
531
+ if type(e) == ConnectionRefusedError:
532
+ msg = (self.get_nowtime(),'服务端未启动,将在1秒后继续尝试start_plus')
533
+ self.sys_print(msg)
534
+ msg = (self.get_nowtime(),str(e))
535
+ self.sys_print(msg)
536
+ time.sleep(1)
537
+
538
+ def start_txg(self,channel_list,pwd=''):
539
+ '''
540
+ - 订阅数据,多个频道用@进行连接,比如test@lttx5表示同时订阅两个频道的消息,
541
+ - 对应的数据都放在Q这个队列中,可以通过下方txl.Q.get()进行获取,txl这里指你实例化以后的对象
542
+ 用法示例:
543
+ tx1 = txl('192.168.1.65',2049,'test')
544
+ tx1.start_txg('test@lttx5')
545
+ while True:
546
+ data = tx1.Q.get().split('|') #消息通过“|”进行切割,前面为key,后面为value
547
+ print(data)#这里出来的是一个列表
548
+ #下方即可做相应的数据处理
549
+ '''
550
+ if not self.tx_running:
551
+ self.start_tx()#启动tx,确保心跳正确
552
+ if not self.run_on:
553
+ msg = '识别到此前已经close过txl,重新运行self.run_on'
554
+ self.sys_print(msg,show_force=True)
555
+ while self.run_on:
556
+ try:
557
+ if self.init_txg==False:
558
+ self.channel_list = channel_list
559
+ for who in self.channel_list.split('@'):
560
+ if len(who) == 0:
561
+ continue
562
+ self.__connect_txg(who,pwd=pwd)
563
+ if self.txg != False:
564
+ break
565
+ else:
566
+ code = -1
567
+ msg = '请勿重复连接txg'
568
+ result = {'code':code,'msg':msg}
569
+ self.sys_print(result)
570
+ return result
571
+ except Exception as e:
572
+ if type(e) == ConnectionRefusedError:
573
+ msg = (self.get_nowtime(),'服务端未启动,将在1秒后继续尝试start_txg')
574
+ self.sys_print(msg)
575
+ time.sleep(1)
576
+
577
+ def __re_start_txg(self,channel_list,pwd=''):
578
+ '''
579
+ 服务重连
580
+ '''
581
+ if not self.run_on:
582
+ msg = '识别到此前已经close过txl,重新运行self.run_on'
583
+ self.sys_print(msg,show_force=True)
584
+ while self.run_on:
585
+ try:
586
+ if self.init_txg==False:
587
+ msg = '通过start_tx的心跳包进行重连'
588
+ self.sys_print(msg,show_force=True)
589
+ self.channel_list = channel_list
590
+ for who in self.channel_list.split('@'):
591
+ if len(who) == 0:
592
+ continue
593
+ self.__connect_txg(who,pwd=pwd)
594
+ if self.txg != False:
595
+ break
596
+ else:
597
+ code = -1
598
+ msg = '请勿重复连接txg'
599
+ result = {'code':code,'msg':msg}
600
+ self.sys_price(result)
601
+ return result
602
+ except Exception as e:
603
+ if type(e) == ConnectionRefusedError:
604
+ msg = (self.get_nowtime(),'服务端未启动,将在1秒后继续尝试start_txg')
605
+ self.sys_print(msg)
606
+ time.sleep(1)
607
+
608
+
609
+ def __connect_txg(self,who,pwd=''):
610
+ '''
611
+ 连接txg
612
+ '''
613
+ while self.run_on:
614
+ if who in self.txg_dict and self.txg_dict[who]['txg_running']:
615
+ self.sys_print((self.get_nowtime(),'订阅频道%s失败,该频道已经存在'%(who)))
616
+ return
617
+ try:
618
+ msg = '正在订阅%s频道'%(who)
619
+ self.sys_print(msg)
620
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
621
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024*1024*100)
622
+ self.txg_running = True
623
+ client.connect((self.ip,self.port,))
624
+ self.channel_pwd = pwd
625
+ tem_dict = {'con_type':'push_mode','who':who,'pwd':pwd,'tocken':self.tocken,
626
+ 'client_version':2,
627
+ 'local_ip':self.local_ip,
628
+ 'current_file':self.current_file,'current_dir':self.current_dir,
629
+ }
630
+ client.send(json.dumps(tem_dict).encode('utf-8'))
631
+ if self.recv_msg_start_txg(client,timeout=1) == True:
632
+ if who not in self.txg_dict:
633
+ self.txg = client
634
+ self.txg_dict[who] = {'con':client,'txg_running':True}
635
+ msg = 'start_txg连接成功,订阅频道%s'%(who)
636
+ self.sys_print(msg,show_force=True)
637
+ self.__start_thread(target = self.recv_msg_broadcast, args = (who,client,pwd))
638
+ self.__start_thread(target = self.main_broadcast_heartbeat,args=(client, who, pwd))
639
+ break
640
+ else:
641
+ if not self.txg_dict[who]['txg_running']:
642
+ self.txg = client
643
+ msg = 'start_txg连接成功,订阅频道%s'%(who)
644
+ self.txg_dict[who]['con'] = client
645
+ self.txg_dict[who]['txg_running'] = True
646
+ self.sys_print(msg,show_force=True)
647
+ self.__start_thread(target = self.recv_msg_broadcast, args = (who,client,pwd))
648
+ self.__start_thread(target = self.main_broadcast_heartbeat,args=(client, who, pwd))
649
+ break
650
+ else:
651
+ msg = '由于%s已经存在,本次连接主动断开'%(who)
652
+ self.sys_print(msg)
653
+ self.close_connection(client,name=msg)
654
+ break
655
+ else:
656
+ msg = (who,'start_txg连接失败,没有收到正常的数据,可能是认证不通过,请确认您的token是否正确')
657
+ self.sys_print(msg)
658
+ break
659
+ except Exception as e:
660
+ msg = '在订阅%s连接txg时服务器出错了>>>>>%s,将在1秒后重试'%(who,e)
661
+ self.sys_print(msg,show_force=1)
662
+ time.sleep(1)
663
+
664
+
665
+ def reconnect_callback(self,dict_data,callback=None):
666
+ '''
667
+ 断线重连回调函数
668
+
669
+ 待开发
670
+ '''
671
+
672
+ def reconnect_success_callback(self,dict_data,callback=None):
673
+ '''
674
+ 断线重连成功回调函数
675
+
676
+ 待开发
677
+
678
+ Parameters
679
+ ----------
680
+ dict_data : TYPE
681
+ DESCRIPTION.
682
+ callback : TYPE, optional
683
+ DESCRIPTION. The default is None.
684
+
685
+ Returns
686
+ -------
687
+ None.
688
+
689
+ '''
690
+ pass
691
+
692
+
693
+ def add_txg(self,channel_name,pwd=''):
694
+ '''
695
+ 增加txg的订阅,同txg,用@符号连接多个信道
696
+ 如 'test@test1'
697
+ '''
698
+ if not self.run_on:
699
+ msg = '识别到此前已经close过txl,重新运行self.run_on'
700
+ self.sys_print(msg,show_force=True)
701
+ if '@' in channel_name:
702
+ for who in channel_name.split('@'):
703
+ self.__connect_txg(who,pwd=pwd)
704
+ else:
705
+ who = channel_name
706
+ self.__connect_txg(who,pwd=pwd)
707
+
708
+ def __close_txg(self,who):
709
+ txg = self.txg_dict[who]['con']
710
+ self.txg_dict[who]['txg_running'] = False
711
+ self.close_connection(txg,name=who)
712
+
713
+ def cancel_txg(self,who):
714
+ '''
715
+ 取消txg的订阅,同txg,用@符号连接多个信道
716
+ '''
717
+ if who not in self.txg_dict:
718
+ tem_dict = {'code':-1,
719
+ 'msg':'当前%s频道未连接,取消失败'%(who),
720
+ 'value':{},
721
+ }
722
+ else:
723
+ tem_dict = {'code':0,
724
+ 'msg':'%s频道取消成功'%(who),
725
+ 'value':{},
726
+ }
727
+ self.__close_txg(who)
728
+
729
+ return tem_dict
730
+
731
+ def start_MQ(self,pub_port=5555):
732
+ import zmq
733
+ '''
734
+ 开启ZMQ模式,该模式是将ZMQ与LTtx进行功能上的整合,当前版本的断线重连机制完全依赖于ZMQ自身的
735
+ 断线重连机制
736
+
737
+ Parameters
738
+ ----------
739
+ pub_port : int
740
+ 传入服务端的ZMQ发布端口,通常为5555.
741
+
742
+ Returns
743
+ -------
744
+ None.
745
+
746
+ '''
747
+ if not self.__ZMQ:
748
+ context = zmq.Context()
749
+
750
+ # 创建发布者套接字并连接到代理
751
+ socket = context.socket(zmq.PUB)
752
+ socket.setsockopt(zmq.RCVHWM, 1000000)
753
+ socket.setsockopt(zmq.SNDHWM, 1000000)
754
+ socket.connect('tcp://%s:%s'%(self.ip,pub_port))
755
+ socket.send_string('test dsfadsafdsafsda')
756
+ self.__ZMQ = socket
757
+ print(self.get_nowtime(),'zmq connect ok')
758
+ else:
759
+ # code = -1
760
+ msg = 'start_MQ已经连接,请不要重复连接'
761
+ msg = (self.get_nowtime(),msg)
762
+ self.sys_print(msg)
763
+
764
+
765
+
766
+ def start_MQ_broadcast(self,channel_list,sub_port=5556):
767
+ '''
768
+ 开启ZMQ的订阅模式,同start_MQ
769
+
770
+ Parameters
771
+ ----------
772
+ channel_list : string
773
+ 和start_txg()类似,传入要订阅的信道列表,用@进行分割.
774
+ sub_port : int, optional
775
+ 传入服务端的ZMQ订阅端口,通常为5556
776
+
777
+ Returns
778
+ -------
779
+ None.
780
+
781
+ '''
782
+ import zmq
783
+ if not self.__ZMQ_broadcast:
784
+ context = zmq.Context()
785
+
786
+ # 创建订阅者套接字并连接到代理
787
+ socket = context.socket(zmq.SUB)
788
+ socket.setsockopt(zmq.RCVHWM, 1000000)
789
+ socket.setsockopt(zmq.SNDHWM, 1000000)
790
+ socket.connect("tcp://%s:%s"%(self.ip,sub_port))
791
+ self.zmq_channel_list = channel_list.split('@')
792
+ for channel in self.zmq_channel_list:
793
+ if len(channel) > 0:
794
+ # 订阅特定主题
795
+ socket.setsockopt_string(zmq.SUBSCRIBE, channel)
796
+ self.__ZMQ_broadcast = socket
797
+ threading.Thread(target = self.main_recv_msg_from_zmq).start()
798
+ else:
799
+ # code = -1
800
+ msg = 'MQ_broadcast已经连接,请不要重复连接'
801
+ print(self.get_nowtime(),msg)
802
+
803
+ def main_recv_msg_from_zmq(self):
804
+ print(self.get_nowtime(),'开始从ZMQ中接收数据')
805
+ while self.run_on:
806
+ data = self.__ZMQ_broadcast.recv_string().split('|',1)
807
+ if data[0] in self.zmq_channel_list:
808
+ self.Q.put(data[1])
809
+
810
+ def start_tx_hearbeat(self):
811
+ while self.run_on:
812
+ if self.__tx:
813
+ self.push('heartbeat','1','heartbeat0')
814
+ time.sleep(self.timeout)
815
+ else:
816
+ break
817
+
818
+ def start_tx_hearbeat_plus(self):
819
+ msg = ('start_tx_plus的heartbeat子线程启动')
820
+ self.sys_print(msg)
821
+ while self.run_on:
822
+ if self.__tx_plus:
823
+ self.push_plus('heartbeat','t','test22')
824
+ time.sleep(2)
825
+ else:
826
+ break
827
+
828
+ def __start_thread(self,target,args):
829
+ thp1 = threading.Thread(target=target,args=args)
830
+ # thp1.setDaemon(True)
831
+ thp1.start()
832
+
833
+
834
+ def recv_msg_start_tx(self,client):
835
+
836
+ data = client.recv(1024)
837
+ dict_data = json.loads(data)
838
+ code = dict_data['code']
839
+ self.id_code = str(dict_data['id_code'])+'@'
840
+ if 'server_version' in dict_data:
841
+ if dict_data['server_version'] > 2:
842
+ self.server2 = True
843
+ if code == 0:
844
+ return True
845
+ else:
846
+ return False
847
+
848
+ def recv_msg_start_txg(self,client,timeout=None):
849
+ if timeout:
850
+ client.settimeout(timeout)
851
+ data = client.recv(1024,)
852
+ dict_data = json.loads(data)
853
+ code = dict_data['code']
854
+ if timeout:
855
+ client.settimeout(None)
856
+ if code == 0:
857
+ return True
858
+ else:
859
+ return False
860
+
861
+ def recv_data_from_tx(self,client):
862
+ recv_bytes = client.recv(8,socket.MSG_WAITALL)
863
+ bytes_len = struct.unpack("Q",recv_bytes)[0]
864
+ recv_data = client.recv(bytes_len,socket.MSG_WAITALL).decode('utf-8',errors='replace')
865
+ recv_data = json.loads(recv_data)
866
+ return recv_data
867
+
868
+ def get(self,key):
869
+ '''
870
+ 获取云变量'key'对应的值,如果不存在则返回None
871
+ '''
872
+ if self.__tx == False:
873
+ code = -1
874
+ msg = '当前tx未连接,请先执行start_tx()'
875
+ result = {'code':code,'msg':msg}
876
+ return result
877
+ else:
878
+ send_data = {'func':'get','value':key}
879
+ msg = self.json.dumps(send_data)
880
+ self.send_data(self.__tx,msg)
881
+ result = self.recv_data_from_tx(self.__tx)
882
+ result = json.loads(result)
883
+ if 'value' in result:
884
+ result = result['value']
885
+ self.heartbeat = 0
886
+ return result
887
+
888
+ def get_dict_value(self,var,key):
889
+ '''
890
+ 获取云变量字典“key"中对应的key值,如果不存在则返回None
891
+ '''
892
+ if self.__tx == False:
893
+ code = -1
894
+ msg = '当前tx未连接,请先执行start_tx()'
895
+ result = {'code':code,'msg':msg}
896
+ return result
897
+ else:
898
+ send_data = {'func':'get_dict_value','value':(var,key)}
899
+ msg = self.json.dumps(send_data)
900
+ self.send_data(self.__tx,msg)
901
+ result = self.recv_data_from_tx(self.__tx)
902
+ result = json.loads(result)
903
+ if 'value' in result:
904
+ result = result['value']
905
+ self.heartbeat = 0
906
+ return result
907
+
908
+ def get_list_value(self,key:str,index:int):
909
+ '''
910
+ 获取云变量列表"key"中对应下标的为index的值,index传入整型,和list使用方法一致,如果不存在则返回为None。
911
+ '''
912
+ if self.__tx == False:
913
+ code = -1
914
+ msg = '当前tx未连接,请先执行start_tx()'
915
+ result = {'code':code,'msg':msg}
916
+ return result
917
+ else:
918
+ send_data = {'func':'get_list_value','value':(key,index)}
919
+ msg = self.json.dumps(send_data)
920
+ self.send_data(self.__tx,msg)
921
+ result = self.recv_data_from_tx(self.__tx)
922
+ result = json.loads(result)
923
+ if 'value' in result:
924
+ result = result['value']
925
+ self.heartbeat = 0
926
+ return result
927
+
928
+
929
+ def get_df(self,key):
930
+ import pandas as pd
931
+ if self.__tx == False:
932
+ code = -1
933
+ msg = '当前tx未连接,请先执行start_tx()'
934
+ result = {'code':code,'msg':msg}
935
+ return result
936
+ else:
937
+ send_data = {'func':'get_dataframe','value':key}
938
+ msg = self.json.dumps(send_data)
939
+ self.send_data(self.__tx,msg)
940
+ result = self.recv_data_from_tx(self.__tx)
941
+ result = json.loads(result)
942
+ if 'value' in result:
943
+ result = result['value']
944
+ result = pd.DataFrame(json.loads(result))
945
+ self.heartbeat = 0
946
+ return result
947
+
948
+
949
+ def put(self,key,data):
950
+ try:
951
+ if self.__tx == False:
952
+ code = -1
953
+ msg = '当前tx未连接,请先执行start_tx()'
954
+ result = {'code':code,'msg':msg}
955
+ return result
956
+ else:
957
+ send_data = {'func':'put','value':(key,data)}
958
+ msg = self.json.dumps(send_data)
959
+ self.send_data(self.__tx,msg)
960
+ except:
961
+ msg = 'Error:和服务端失去连接,即将重连'
962
+ self.sys_print(msg,show_force=1)
963
+ self.__tx = False
964
+ self.start_tx()
965
+
966
+ def put_df(self,key,df):
967
+ try:
968
+ if self.__tx == False:
969
+ code = -1
970
+ msg = '当前tx未连接,请先执行start_tx()'
971
+ result = {'code':code,'msg':msg}
972
+ return result
973
+ else:
974
+ send_data = {'func':'put_dataframe','value':(key,json.dumps(df.to_dict(orient='records')))}
975
+ msg = self.json.dumps(send_data)
976
+ self.send_data(self.__tx,msg)
977
+ except:
978
+ msg = 'Error:put_df时和服务端失去连接,即将重连'
979
+ self.sys_print(msg,show_force=1)
980
+ self.__tx = False
981
+ self.start_tx()
982
+
983
+
984
+
985
+ def list_append(self,var,data):
986
+ '''
987
+ 对云端变量类型为列表的var进行列表append操作,相当于本地列表的基础操作,无返回值,默认成功
988
+
989
+ Parameters
990
+ ----------
991
+ key : TYPE
992
+ DESCRIPTION.
993
+ data : TYPE
994
+ DESCRIPTION.
995
+
996
+ Returns
997
+ -------
998
+ result : TYPE
999
+ DESCRIPTION.
1000
+
1001
+ '''
1002
+ try:
1003
+ if self.__tx == False:
1004
+ code = -1
1005
+ msg = '当前tx未连接,请先执行start_tx()'
1006
+ result = {'code':code,'msg':msg}
1007
+ return result
1008
+ else:
1009
+ send_data = {'func':'list_append','value':(var,data)}
1010
+ msg = self.json.dumps(send_data)
1011
+ self.send_data(self.__tx,msg)
1012
+ except:
1013
+ msg = 'Error:list_append和服务端失去连接,即将重连'
1014
+ self.sys_print(msg,show_force=1)
1015
+ self.__tx = False
1016
+ self.start_tx()
1017
+
1018
+ def list_remove(self,var,data):
1019
+ '''
1020
+ 对云端变量类型为列表的var进行列表remove操作,相当于本地列表的基础操作,无返回值,默认成功
1021
+
1022
+ Parameters
1023
+ ----------
1024
+ key : TYPE
1025
+ DESCRIPTION.
1026
+ data : TYPE
1027
+ DESCRIPTION.
1028
+
1029
+ Returns
1030
+ -------
1031
+ result : TYPE
1032
+ DESCRIPTION.
1033
+
1034
+ '''
1035
+ try:
1036
+ if self.__tx == False:
1037
+ code = -1
1038
+ msg = '当前tx未连接,请先执行start_tx()'
1039
+ result = {'code':code,'msg':msg}
1040
+ return result
1041
+ else:
1042
+ send_data = {'func':'list_remove','value':(var,data)}
1043
+ msg = self.json.dumps(send_data)
1044
+ self.send_data(self.__tx,msg)
1045
+ except:
1046
+ msg = 'Error:list_remove时和服务端失去连接,即将重连'
1047
+ self.sys_print(msg,show_force=1)
1048
+ self.__tx = False
1049
+ self.start_tx()
1050
+
1051
+ def list_pop(self,var,data):
1052
+ '''
1053
+ 对云端变量类型为列表的var进行列表pop操作,相当于本地列表的基础操作,无返回值,默认成功
1054
+
1055
+ Parameters
1056
+ ----------
1057
+ key : TYPE
1058
+ DESCRIPTION.
1059
+ data : TYPE
1060
+ DESCRIPTION.
1061
+
1062
+ Returns
1063
+ -------
1064
+ result : TYPE
1065
+ DESCRIPTION.
1066
+
1067
+ '''
1068
+ try:
1069
+ if self.__tx == False:
1070
+ code = -1
1071
+ msg = '当前tx未连接,请先执行start_tx()'
1072
+ result = {'code':code,'msg':msg}
1073
+ return result
1074
+ else:
1075
+ send_data = {'func':'list_pop','value':(var,data)}
1076
+ msg = self.json.dumps(send_data)
1077
+ self.send_data(self.__tx,msg)
1078
+ except:
1079
+ msg = 'Error:listpop和服务端失去连接,即将重连'
1080
+ self.sys_print(msg,show_force=1)
1081
+ self.__tx = False
1082
+ self.start_tx()
1083
+
1084
+ def dict_change(self,var,key,value):
1085
+ '''
1086
+ 对云端变量为var的字典进行字典操作,同基础的字典操作,无返回值,默认成功
1087
+
1088
+ Parameters
1089
+ ----------
1090
+ var : TYPE
1091
+ DESCRIPTION.
1092
+ key : TYPE
1093
+ DESCRIPTION.
1094
+ value : TYPE
1095
+ DESCRIPTION.
1096
+
1097
+ Returns
1098
+ -------
1099
+ result : TYPE
1100
+ DESCRIPTION.
1101
+
1102
+ '''
1103
+ try:
1104
+ if self.__tx == False:
1105
+ code = -1
1106
+ msg = '当前tx未连接,请先执行start_tx()'
1107
+ result = {'code':code,'msg':msg}
1108
+ return result
1109
+ else:
1110
+ send_data = {'func':'dict_change','value':(var,key,value)}
1111
+ msg = self.json.dumps(send_data)
1112
+ self.send_data(self.__tx,msg)
1113
+ except:
1114
+ msg = 'Error:dict_change和服务端失去连接,即将重连'
1115
+ self.sys_print(msg,show_force=1)
1116
+ self.__tx = False
1117
+ self.start_tx()
1118
+
1119
+ def push(self,key,data,who=None):
1120
+ try:
1121
+ if self.__tx == False:
1122
+ code = -1
1123
+ msg = '当前tx未连接,请先执行start_tx()'
1124
+ result = {'code':code,'msg':msg}
1125
+ return result
1126
+ else:
1127
+ send_data = {'func':'push','value':'%s|%s'%(key,data),'who':who}
1128
+ msg = self.json.dumps(send_data)
1129
+ self.send_data(self.__tx,msg)
1130
+
1131
+ except Exception as e:
1132
+ raise ConnectionAbortedError('与服务器连接断开,push函数出错,请注意你传入的数据类型必须为字符串,请查看上方的报错内容>>>%s'%(e))
1133
+
1134
+ def push_plus(self,key,data,who=''):
1135
+ '''
1136
+ 超级push函数,会比push函数更快,效率更高,确保你传入的参数均为字符串,否则推送不成功
1137
+
1138
+ Parameters
1139
+ ----------
1140
+ key : TYPE
1141
+ DESCRIPTION.
1142
+ data : TYPE
1143
+ DESCRIPTION.
1144
+ who : TYPE, optional
1145
+ DESCRIPTION. The default is None.
1146
+
1147
+ Raises
1148
+ ------
1149
+ ConnectionAbortedError
1150
+ DESCRIPTION.
1151
+
1152
+ Returns
1153
+ -------
1154
+ result : TYPE
1155
+ DESCRIPTION.
1156
+
1157
+ '''
1158
+ try:
1159
+ if self.__tx_plus == False:
1160
+ code = -1
1161
+ msg = '当前tx_plus未连接,请先执行start_tx_plus()'
1162
+ result = {'code':code,'msg':msg}
1163
+ return result
1164
+ else:
1165
+ msg = key+'|'+data+':who:'+who
1166
+ self.send_data_plus(self.__tx_plus,msg)
1167
+ except Exception as e:
1168
+ self.sys_print('push_plus出错了>>>>>%s'%(e))
1169
+ raise ConnectionAbortedError('与服务器连接断开,push_plus函数出错,请确保你传入的参数均为字符串,请查看上方的报错内容')
1170
+
1171
+ def MQ_push(self,var,data,who):
1172
+ '''
1173
+ 采用ZMQ模式进行push,速度更快,一百万次推送耗时在1.8秒左右,但对于数据安全性没有保障,适合大通量的行情推送,在测试时发现有数据不能
1174
+ 完全到达的情况,请自行做好数据校验机制
1175
+
1176
+ Parameters
1177
+ ----------
1178
+ var : TYPE
1179
+ DESCRIPTION.
1180
+ data : TYPE
1181
+ DESCRIPTION.
1182
+ who : TYPE
1183
+ DESCRIPTION.
1184
+
1185
+ Returns
1186
+ -------
1187
+ None.
1188
+
1189
+ '''
1190
+ if self.__ZMQ:
1191
+ self.__ZMQ.send_string('%s|%s|%s'%(who,var,data))
1192
+ else:
1193
+ return (-1,'请先执行start_MQ()')
1194
+
1195
+ def recv_msg_put(self,client):
1196
+ data = client.recv(1024)
1197
+ dict_data = json.loads(data)
1198
+ return dict_data
1199
+
1200
+ def send_data(self,client, msg):
1201
+ if not self.orjson_on:
1202
+ msg = msg.encode('utf-8')
1203
+ self.tx_que.put(msg)
1204
+
1205
+ def send_data_plus(self,client, msg):
1206
+ self.tx_que_plus.put(msg)
1207
+
1208
+ def __connect_d_push(self,):
1209
+ '''
1210
+ d_push连接
1211
+ '''
1212
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1213
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192*100)
1214
+ # 设置立即发送(关闭 Nagle 算法,降低延迟)
1215
+ client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1216
+ client.connect((self.ip,self.port))
1217
+ tem_dict = {'con_type':'put_mode','tocken':self.tocken,
1218
+ 'local_ip':self.local_ip,
1219
+ 'current_file':self.current_file,'current_dir':self.current_dir,
1220
+ }
1221
+ self.sys_print(tem_dict)
1222
+ client.sendall(json.dumps(tem_dict).encode('utf-8'))
1223
+ if self.recv_msg_start_tx(client):
1224
+ self.__d_push_tx = client
1225
+ self.sys_print('d_push模式启动成功')
1226
+ def close_d_push(self):
1227
+ '''
1228
+ 主动关闭d_push
1229
+ '''
1230
+ self.close_connection(self.__d_push_tx)
1231
+ self.__d_push_tx = False
1232
+ self.sys_print('d_push模式主动关闭')
1233
+
1234
+ def d_push(self,key,data,who):
1235
+ '''
1236
+ 绕过多线程直接在当前线程进行消息推送,适合在QMT里直接调用,只适合在127.0.0.1里使用
1237
+ '''
1238
+ if not self.__d_push_tx:
1239
+ # if self.ip != '127.0.0.1':
1240
+ # raise OSError('d_push只能在127.0.0.1使用')
1241
+ self.__connect_d_push()
1242
+
1243
+ send_data = {'func':'push','value':'%s|%s'%(key,data),'who':who}
1244
+ msg = self.json.dumps(send_data)
1245
+ if not self.orjson_on:
1246
+ msg = msg.encode('utf-8')
1247
+ data_len = len(msg)
1248
+ struct_bytes = struct.pack('Q', data_len)
1249
+ try:
1250
+ self.__d_push_tx.sendall(struct_bytes)
1251
+ self.__d_push_tx.sendall(msg)
1252
+ except Exception as e:
1253
+ try:
1254
+ self.close_d_push()
1255
+ except:
1256
+ msg = 'd_push关闭异常,请留意'
1257
+ self.__d_push_tx = False
1258
+ self.__connect_d_push()
1259
+ self.sys_print('d_push异常>>>%s'%(str(e)))
1260
+
1261
+ def main_tx_que(self):
1262
+ while self.run_on:
1263
+ try:
1264
+ msg = self.tx_que.get()
1265
+ data_len = len(msg)
1266
+ struct_bytes = struct.pack('Q', data_len)
1267
+ self.__tx.sendall(struct_bytes)
1268
+ self.__tx.sendall(msg)
1269
+ except Exception as e:
1270
+ if self.run_on:
1271
+ msg = 'start_tx在发送数据时出错了,开始重连>>>>%s'%(e)
1272
+ self.sys_print(msg,show_force=True)
1273
+ self.__tx = False
1274
+ self.close_tx()
1275
+ self.start_tx()
1276
+ time.sleep(2)
1277
+ break
1278
+
1279
+ def main_tx_que_plus(self):
1280
+ while self.run_on:
1281
+ try:
1282
+ old_msg = self.tx_que_plus.get()
1283
+ msg=old_msg.encode('utf-8')
1284
+ data_len = len(msg)
1285
+ struct_bytes = struct.pack('Q', data_len)
1286
+ self.__tx_plus.sendall(struct_bytes)
1287
+ self.__tx_plus.sendall(msg)
1288
+ except:
1289
+ self.__tx_plus = False
1290
+ self.start_plus()
1291
+ self.tx_que_plus.put(old_msg)
1292
+ break
1293
+
1294
+ def recv_msg_broadcast(self,who,client,pwd):
1295
+ while self.run_on:
1296
+ try:
1297
+ recv_data = self.recv_data_from_tx(client)
1298
+ if '|' in recv_data:
1299
+ self.Q.put(recv_data)
1300
+ else:
1301
+ self.__txg_heartbeat_time = time.time()
1302
+ except Exception as e:
1303
+ try:
1304
+ self.close_connection(client,name='txg')
1305
+ except:
1306
+ pass
1307
+ break
1308
+ # try:
1309
+ # if self.txg_dict[who]['txg_running'] == True:
1310
+ # self.txg_dict[who]['txg_running'] = False
1311
+ # data = {'txg断线时间':self.get_nowtime(),'断线原因':str(e)}
1312
+ # self.sys_print(data,show_force=True)
1313
+ # threading.Thread(target = self.loss_connect_callback,args=(data,)).start()
1314
+ # self.close_connection(client,name='txg')
1315
+ # msg = 'txg因报错主动关闭退出'
1316
+ # self.sys_print(msg,show_force=True)
1317
+ # break
1318
+ # else:
1319
+ # print('我断线了哦')
1320
+ # break
1321
+ # except Exception as e:
1322
+ # data = {'txg断线时间':self.get_nowtime(),'断线原因2':str(e),'断线逻辑':'处理断线时出错'}
1323
+ # self.sys_print(data,show_force = True)
1324
+ # break
1325
+ # time.sleep(2)#休息5秒后判断当前是不是直接的断线了,然后再重连
1326
+ # if self.__tx and self.txg_running:
1327
+ # self.sys_print("内部错误导致txg断开,即将重连")
1328
+ # self.__connect_txg(who=who,pwd=pwd)
1329
+ # self.sys_print('我已全部退出')
1330
+ def main_broadcast_heartbeat(self,client,who,pwd):
1331
+ while self.run_on:
1332
+ try:
1333
+ msg = b'1'
1334
+ client.sendall(msg)
1335
+ except Exception as e:
1336
+ msg = 'txg心跳失败,开始重连>>>>%s'%(e)
1337
+ self.txg_dict[who]['txg_running'] = False
1338
+ self.sys_print(msg)
1339
+ break
1340
+ time.sleep(1)
1341
+ print('退出心跳------')
1342
+ if who in self.txg_dict and not self.txg_dict[who]['txg_running']:
1343
+ self.__connect_txg(who,pwd=pwd)
1344
+ print('我全退出了')
1345
+
1346
+
1347
+ def create_channel(self,num=30):
1348
+ '''
1349
+ 创建一个随机信道,默认长度为30位字符串
1350
+
1351
+ Parameters
1352
+ ----------
1353
+ num : int
1354
+ 要创建的随机信道长度,默认为30位
1355
+
1356
+ Returns
1357
+ -------
1358
+ None.
1359
+
1360
+ '''
1361
+ s='abcdefghijklmnopqrstuvwxz12345678901'
1362
+ str1 = ''
1363
+ for i in range(num):
1364
+ str1 = str1 + s[random.randint(0,35)]
1365
+ return str1
1366
+
1367
+ def close_connection(self,client,name='tx'):
1368
+ try:
1369
+ # 强制停止读写操作,确保 recv 不再阻塞
1370
+ client.shutdown(socket.SHUT_RDWR)
1371
+ except socket.error as e:
1372
+ self.sys_print('关闭socket出错了>>>>,类型为:%s'%(name),e)
1373
+ finally:
1374
+ client.close()
1375
+
1376
+ def close_tx(self):
1377
+ self.tx_running = False
1378
+ if self.__tx == False:
1379
+ code = -1
1380
+ msg = '当前tx未连接'
1381
+ else:
1382
+ if self.txg_running:
1383
+ self.close_txg()
1384
+ if type(self.__tx) != bool:
1385
+ self.close_connection(self.__tx)
1386
+ self.__tx = False
1387
+ self.tx_que.put('')
1388
+ code = 0
1389
+ msg = 'tx关闭成功,txg也关闭成功'
1390
+ result = {'code':code,'msg':msg}
1391
+ return result
1392
+
1393
+ def close_tx_plus(self,):
1394
+ '''
1395
+ 关闭push_plus功能
1396
+
1397
+ Returns
1398
+ -------
1399
+ None.
1400
+
1401
+ '''
1402
+ if self.__tx_plus == False:
1403
+ code = -1
1404
+ msg = '当前tx_plus功能未开启'
1405
+ result = {'code':code,'msg':msg}
1406
+ self.sys_print(result)
1407
+ return result
1408
+ else:
1409
+ self.close_connection(self.__tx_plus)
1410
+ self.__tx_plus = False
1411
+
1412
+ code = 0
1413
+ msg = 'tx_plus关闭成功'
1414
+ result = {'code':code,'msg':msg}
1415
+ self.sys_print(result)
1416
+ return result
1417
+
1418
+ def close(self,):
1419
+ '''
1420
+ 关闭所有的连接对象,该函数会直接关闭
1421
+ '''
1422
+ if self.run_on:
1423
+ self.run_on = False
1424
+ self.close_tx()
1425
+ self.close_txg()
1426
+ self.close_tx_plus()
1427
+ msg = '手动关闭所有连接,将在1秒后退出'
1428
+ self.sys_print(msg,show_force=True)
1429
+ time.sleep(1)
1430
+
1431
+
1432
+
1433
+ def close_txg(self):
1434
+ self.txg_running = False
1435
+ self.sys_print(self.tx_running,self.__tx)
1436
+ for who in list(self.txg_dict.keys()):
1437
+ self.__close_txg(who)
1438
+ if not self.tx_running:
1439
+ self.close_tx()
1440
+ self.sys_print('关闭跟随启动的tx')
1441
+
1442
+ self.txg_dict = {}
1443
+ result = {'code':0,'msg':'所有txg关闭成功','value':{}}
1444
+ return result
1445
+
1446
+ def get_nowdate(self,):
1447
+ '''
1448
+ 返回当前的日期,格式为2024-04-04
1449
+ '''
1450
+ return time.strftime("%Y-%m-%d")
1451
+
1452
+ def get_nowmin(self,):
1453
+ '''
1454
+ 返回当前的时间,格式为19:00:00
1455
+ '''
1456
+ return time.strftime('%H:%M:%S')
1457
+
1458
+ def get_nowtime(self,):
1459
+ '''
1460
+ 返回当前时间,格式为2023-11-15 20:02:01
1461
+
1462
+ Returns
1463
+ -------
1464
+ TYPE
1465
+ DESCRIPTION.
1466
+
1467
+ '''
1468
+ return time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
1469
+
1470
+ def get_timestamp(self):
1471
+ '''
1472
+ 返回当前数字时间戳
1473
+
1474
+ Returns
1475
+ -------
1476
+ None.
1477
+
1478
+ '''
1479
+ return time.time()
1480
+
1481
+ def DatestrtingToInt(self,time_str):
1482
+ '''
1483
+ 把2023-10-15 20:02:01转为1697371321
1484
+
1485
+ Parameters
1486
+ ----------
1487
+ time_str : string
1488
+ 2023-10-15 20:02:01
1489
+
1490
+ Returns
1491
+ -------
1492
+ timestamp : int
1493
+ 返回整形时间戳1697371321.
1494
+
1495
+ '''
1496
+ dt = datetime.datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S')
1497
+ timestamp = int(dt.timestamp())
1498
+ return timestamp
1499
+ def IntTimeToString(self,ct):
1500
+ '''
1501
+ 把
1502
+ conv_time(1697371321) --> '2023-10-15 20:02:01'
1503
+ '''
1504
+ local_time = time.localtime(ct)
1505
+ data_head = time.strftime('%Y-%m-%d %H:%M:%S', local_time)
1506
+ return data_head
1507
+
1508
+ def calc_datetime_day(self,str1,str2):
1509
+ '''
1510
+ 计算2023-10-15 20:24:55和2023-10-14 20:24:55之间的天数
1511
+
1512
+ Parameters
1513
+ ----------
1514
+ str1 : TYPE
1515
+ DESCRIPTION.
1516
+ str2 : TYPE
1517
+ DESCRIPTION.
1518
+
1519
+ Returns
1520
+ -------
1521
+ TYPE
1522
+ DESCRIPTION.
1523
+
1524
+ '''
1525
+ return (datetime.datetime.strptime(str1, '%Y-%m-%d %H:%M:%S') - datetime.datetime.strptime(str2, '%Y-%m-%d %H:%M:%S')).days
1526
+
1527
+
1528
+
1529
+ def loss_connect_callback(self,data):
1530
+ if self.loss_callback:
1531
+ self.loss_callback(data)
1532
+
1533
+ def regest_loss_connect_callback(self,func):
1534
+ '''
1535
+ 该功能暂时不可用,待开发
1536
+ 注册失去连接时的回调函数,callback会传入一个字典参数,
1537
+ '''
1538
+ # self.loss_callback = func
1539
+ # msg = '连接断开回调函数注册成功,将在断网时回调'
1540
+ # self.sys_print(msg)
1541
+ pass
1542
+
1543
+
1544
+ def calc_datetime_seconds(self,str1,str2):
1545
+ '''计算秒差'''
1546
+ return (datetime.datetime.strptime(str1, '%Y-%m-%d %H:%M:%S') - datetime.datetime.strptime(str2, '%Y-%m-%d %H:%M:%S')).seconds
1547
+
1548
+ def calc_datetime_minutes(self,str1,str2):
1549
+ '''计算分钟差'''
1550
+ return (datetime.datetime.strptime(str1, '%Y-%m-%d %H:%M:%S') - datetime.datetime.strptime(str2, '%Y-%m-%d %H:%M:%S')).seconds // 60
1551
+
1552
+ def calc_zdf(N1,N2):
1553
+ '''
1554
+ 计算两个值的涨跌百分比,通常用在计算收益率中
1555
+ '''
1556
+ return (N1-N2)/N2 * 100
1557
+
1558
+ def get_day_before(self,datestr,N):
1559
+ '''向前取N天,传入2023-10-15的格式'''
1560
+ date = datetime.datetime.strptime(datestr, '%Y-%m-%d')
1561
+ date_before = date - datetime.timedelta(days=N)
1562
+ return str(date_before.date())
1563
+
1564
+ def get_day_next(self,datestr,N):
1565
+ '''
1566
+ 向前取N天,传入2023-10-14的格式
1567
+ '''
1568
+ date = datetime.datetime.strptime(datestr, '%Y-%m-%d')
1569
+ date_before = date + datetime.timedelta(days=N)
1570
+ return str(date_before.date())
1571
+
1572
+ def get_day_cha(self,datestr,N):
1573
+ '''
1574
+ 返回N天后的日期,注意N的正负号,传入"2024-04-16 15:00:00"或者"15:00:00"
1575
+ 返回值为字符串,如果N为3,返回"2024-04-19 15:00:00"
1576
+ '''
1577
+ if ' ' in datestr:
1578
+ date = datetime.datetime.strptime(datestr, '%Y-%m-%d %H:%M:%S')
1579
+ else:
1580
+ date = datetime.datetime.strptime(datestr, '%H:%M:%S')
1581
+ if N > 0:
1582
+ date_before = date + datetime.timedelta(days=N)
1583
+ else:
1584
+ date_before = date - datetime.timedelta(days=-N)
1585
+ if ' ' in datestr:
1586
+ result = str(date_before)
1587
+ else:
1588
+ result = str(date_before).split(' ')[1]
1589
+ return result
1590
+
1591
+
1592
+ def get_min_cha(self,datestr,N):
1593
+ '''
1594
+ 返回分钟差后的日期,注意N的正负号,传入"2024-04-16 15:00:00"或者"15:00:00"
1595
+ 返回值为字符串
1596
+ '''
1597
+ if ' ' in datestr:
1598
+ date = datetime.datetime.strptime(datestr, '%Y-%m-%d %H:%M:%S')
1599
+ else:
1600
+ date = datetime.datetime.strptime(datestr, '%H:%M:%S')
1601
+ if N > 0:
1602
+ date_before = date + datetime.timedelta(minutes=N)
1603
+ else:
1604
+ date_before = date - datetime.timedelta(minutes=-N)
1605
+ if ' ' in datestr:
1606
+ result = str(date_before)
1607
+ else:
1608
+ result = str(date_before).split(' ')[1]
1609
+ return result
1610
+
1611
+ def get_second_cha(self,datestr,N):
1612
+ '''
1613
+ 返回秒差后的日期,注意N的正负号,传入"2024-04-16 15:00:00"或者"15:00:00"
1614
+ 返回值为字符串
1615
+ '''
1616
+ if ' ' in datestr:
1617
+ date = datetime.datetime.strptime(datestr, '%Y-%m-%d %H:%M:%S')
1618
+ else:
1619
+ date = datetime.datetime.strptime(datestr, '%H:%M:%S')
1620
+ if N > 0:
1621
+ date_before = date + datetime.timedelta(seconds=N)
1622
+ else:
1623
+ date_before = date - datetime.timedelta(seconds=-N)
1624
+ if ' ' in datestr:
1625
+ result = str(date_before)
1626
+ else:
1627
+ result = str(date_before).split(' ')[1]
1628
+ return result
1629
+
1630
+ def judge_is_TradeDay(self,datestr=None):
1631
+ '''
1632
+ 判断datestr是不是A股交易日,需要先安装pandas_market_calendars库的支持
1633
+ 如果没有传入日期,则返回判断当天是不是交易日
1634
+
1635
+ Parameters
1636
+ ----------
1637
+ datestring : TYPE
1638
+ DESCRIPTION.
1639
+
1640
+ Returns
1641
+ bool
1642
+ -------
1643
+ 如果时交易日则返回为True,否则为False.
1644
+
1645
+ '''
1646
+ if not datestr:
1647
+ datestr = time.strftime("%Y-%m-%d")
1648
+ if datestr in self.dict_TradeDay:
1649
+ return self.dict_TradeDay[datestr]
1650
+
1651
+ import pandas_market_calendars as mcal
1652
+ sse = mcal.get_calendar('SSE')#上海证券交易所日历
1653
+ td_df = sse.schedule(start_date=datestr, end_date=datestr)
1654
+ if len(td_df) > 0:
1655
+ result = True
1656
+ else:
1657
+ result = False
1658
+ self.dict_TradeDay[datestr] = result
1659
+ return result
1660
+
1661
+ def get_last_TradeDay(self,):
1662
+ '''
1663
+ 返回中国A股市场最近的一个交易日
1664
+ '''
1665
+ import pandas_market_calendars as mcal
1666
+ sse = mcal.get_calendar('SSE')#上海证券交易所日历
1667
+ now_date = time.strftime("%Y-%m-%d")
1668
+ last_date = self.get_day_before(now_date,30)
1669
+ td_df = sse.schedule(start_date=last_date, end_date=now_date)
1670
+ result = str(td_df.index[-1]).split(' ')[0]
1671
+ return result
1672
+
1673
+ def get_TradeDay_List(self,N1,N2):
1674
+ '''
1675
+ 获取N1到N2之间的中国交易日,传入字符串时间
1676
+ N1: 2025-01-01
1677
+ N2: 2025-01-12
1678
+ '''
1679
+ import pandas_market_calendars as mcal
1680
+ sse = mcal.get_calendar('SSE')#上海证券交易所日历
1681
+ td_df = sse.schedule(start_date=N1, end_date=N2)
1682
+ l1 = []
1683
+ for i in list(td_df.index):
1684
+ l1.append(str(i).split(' ')[0])
1685
+
1686
+ return l1
1687
+
1688
+ def cal_trade_day_cha(self,str1:str,str2:str)->int:
1689
+ '''
1690
+ 获取两个日期之间的交易日天数,传入格式:
1691
+ str1:2025-12-30
1692
+ str2:2026-01-06
1693
+ 返回整数
1694
+ '''
1695
+ if str1 == str2:
1696
+ return 0
1697
+ if self.td_df.empty:
1698
+ import pandas_market_calendars as mcal
1699
+ self.sse = mcal.get_calendar('SSE')#上海证券交易所日历
1700
+ self.td_df = self.sse.schedule(start_date=str1, end_date=str2)
1701
+ if str1 < str(self.td_df.index[0]) or str2 > str(self.td_df.index[-1]):
1702
+ self.td_df = self.sse.schedule(start_date=str1, end_date=str2)
1703
+ tem_df = pd.DataFrame()
1704
+ tem_df['交易日'] = self.td_df.index
1705
+ tem_df['交易日'].apply(lambda x:str(x))
1706
+ tem_td_df = tem_df[tem_df['交易日'] >= str1]
1707
+ tem_td_df = tem_td_df[tem_td_df['交易日'] <= str2]
1708
+ result = len(tem_td_df)
1709
+ return result
1710
+
1711
+ def get_pre_TradeDay(self,N=1):
1712
+ '''
1713
+ 返回A股市场上N个交易日,默认为1,即返回昨日,0表示返回最近一个交易日
1714
+ '''
1715
+ import pandas_market_calendars as mcal
1716
+ now_date = self.get_nowdate()
1717
+ if now_date != self.pre_TradeDay['now_date']:
1718
+ self.pre_TradeDay = {'now_date':now_date}
1719
+ sse = mcal.get_calendar('SSE')#上海证券交易所日历
1720
+ now_date = time.strftime("%Y-%m-%d")
1721
+ last_date = self.get_day_before(now_date,N+30)
1722
+ td_df = sse.schedule(start_date=last_date, end_date=now_date)
1723
+ result = str(td_df.index[-(N+1)]).split(' ')[0]
1724
+ self.pre_TradeDay[N] = result
1725
+ if N not in self.pre_TradeDay:
1726
+ self.pre_TradeDay = {'now_date':now_date}
1727
+ sse = mcal.get_calendar('SSE')#上海证券交易所日历
1728
+ now_date = time.strftime("%Y-%m-%d")
1729
+ last_date = self.get_day_before(now_date,N+30)
1730
+ td_df = sse.schedule(start_date=last_date, end_date=now_date)
1731
+ result = str(td_df.index[-(N+1)]).split(' ')[0]
1732
+ self.pre_TradeDay[N] = result
1733
+ result = self.pre_TradeDay.get(N)
1734
+ return result
1735
+
1736
+ def get_local_ip(self,):
1737
+ '''返回局域网IPV4地址'''
1738
+ import socket
1739
+ try:
1740
+ # 创建一个socket对象
1741
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1742
+ # 不需要真的连接,所以使用一个不存在的地址
1743
+ s.connect(("10.255.255.255", 1))
1744
+ IP = s.getsockname()[0]
1745
+ except Exception:
1746
+ IP = "127.0.0.1" # 如果出现异常,则返回回环地址
1747
+ finally:
1748
+ s.close() # 关闭socket
1749
+ return IP
1750
+
1751
+ def get_public_ip(self,):
1752
+ '''
1753
+ 返回公网IPV4地址
1754
+ '''
1755
+ response = requests.get('https://httpbin.org/ip')
1756
+ ip = response.json().get('origin')
1757
+ return ip
1758
+
1759
+ def judge_Time_between(self,str1,str2):
1760
+ '''
1761
+ 判断当前时间是不是介于str1和str2之间
1762
+ str1格式: '15:00:05'
1763
+ str2格式: '16:00:00'
1764
+
1765
+ '''
1766
+ return str1 < time.strftime('%H:%M:%S') < str2
1767
+
1768
+
1769
+ def save_json(self,file_name,obj):
1770
+ with open(file_name,'w',encoding='utf-8') as f:
1771
+ json.dump(obj,f,ensure_ascii=False,indent=2)
1772
+
1773
+
1774
+ def load_json(self,file_name):
1775
+ with open(file_name,'r',encoding='utf-8') as f :
1776
+ data = json.load(f)
1777
+ return data
1778
+
1779
+ def save_json_fix(self,file_name,dict_obj):
1780
+ '''
1781
+ 保存json,会自动将不符合的json数据转为正常的,如numpy.float64转为正常的float64,
1782
+ 传入的对象为字典
1783
+
1784
+ 待开发
1785
+ '''
1786
+
1787
+ def mkdir(self,file_name):
1788
+ '''
1789
+ 创建一个目录,如果已经存在则会跳过
1790
+ '''
1791
+ try:
1792
+ os.makedirs(file_name, exist_ok=True)
1793
+ except:
1794
+ pass
1795
+
1796
+ @staticmethod
1797
+ def proxy(ip, port, user='', passwd='', proxy_type='http', ssh_remote_host='', ssh_remote_port=0, ssh_bind_port=10800):
1798
+ '''
1799
+ 实现在程序内部通过 http、socks5 或 ssh 建立代理。
1800
+
1801
+ - proxy_type: 可选 'http', 'socks5', 'ssh'
1802
+ - ssh_remote_host/ssh_remote_port: 仅在 proxy_type 为 ssh 时使用,表示远程跳板机地址和端口
1803
+ - ssh_bind_port: 本地映射端口(默认 10800)
1804
+
1805
+ 使用示例:
1806
+ @tx1.proxy(ip='127.0.0.1', port=1080, proxy_type='socks5')
1807
+ def get_data():
1808
+ pass
1809
+
1810
+ @tx1.proxy(ip='ssh_server_ip', port=22, user='ssh_user', passwd='ssh_pass', proxy_type='ssh',
1811
+ ssh_remote_host='8.8.8.8', ssh_remote_port=80)
1812
+ def get_data():
1813
+ pass
1814
+ '''
1815
+ from functools import wraps
1816
+ import socket
1817
+ import socks
1818
+
1819
+ def decorator(func):
1820
+ @wraps(func)
1821
+ def wrapper(*args, **kwargs):
1822
+ if proxy_type.lower() == 'ssh':
1823
+ from sshtunnel import SSHTunnelForwarder
1824
+
1825
+ server = SSHTunnelForwarder(
1826
+ (ip, port), # SSH 跳板机的 IP 和端口
1827
+ ssh_username=user,
1828
+ ssh_password=passwd,
1829
+ remote_bind_address=(ssh_remote_host, ssh_remote_port),
1830
+ local_bind_address=('127.0.0.1', ssh_bind_port)
1831
+ )
1832
+
1833
+ server.start()
1834
+ print(f"[SSH代理] 本地代理端口: {server.local_bind_port}")
1835
+
1836
+ try:
1837
+ # 对于 SSH 场景,将请求转发到本地代理端口
1838
+ proxies = {
1839
+ 'http': f'http://127.0.0.1:{server.local_bind_port}',
1840
+ 'https': f'http://127.0.0.1:{server.local_bind_port}',
1841
+ }
1842
+ kwargs['proxies'] = proxies
1843
+ return func(*args, **kwargs)
1844
+ finally:
1845
+ server.stop()
1846
+ print("[SSH代理] 隧道已关闭")
1847
+
1848
+ elif proxy_type.lower() == 'socks5':
1849
+ socks.set_default_proxy(socks.SOCKS5, ip, port, username=user, password=passwd)
1850
+ socket.socket = socks.socksocket
1851
+ return func(*args, **kwargs)
1852
+
1853
+ else: # 默认 HTTP/HTTPS
1854
+ if user and passwd:
1855
+ proxy_url = f"{proxy_type}://{user}:{passwd}@{ip}:{port}"
1856
+ else:
1857
+ proxy_url = f"{proxy_type}://{ip}:{port}"
1858
+ proxies = {
1859
+ 'http': proxy_url,
1860
+ 'https': proxy_url,
1861
+ }
1862
+ kwargs['proxies'] = proxies
1863
+ return func(*args, **kwargs)
1864
+
1865
+ return wrapper
1866
+ return decorator
1867
+
1868
+
1869
+
1870
+
1871
+ if __name__=='__main__':
1872
+ import requests
1873
+ ip = 'dm432.zl45lkjrlewklrewnet.top'#把这里替换成你服务器实际的信息即可,可以是域名,也可以是IP地址
1874
+ ip = '192.168.1.70'#把这里替换成你服务器实际的信息即可
1875
+ # ip = '127.0.0.1'
1876
+ ip = 'tx.txquant.cn'
1877
+ port = 2049
1878
+
1879
+ tocken = 'LTtx'#替换成服务端的token,默认为LTtx
1880
+ def call_back(data):
1881
+
1882
+ print(data)
1883
+
1884
+ tx1=txl(ip,port,tocken,loss_callback=call_back)
1885
+ # tx1.start_tx()
1886
+ tx1.start_txg('litao@test20@GY_HQ_test')
1887
+
1888
+ dict_count = {'count':0}#用来计数统计的,不用理会
1889
+ # print('okkkk')
1890
+ def show():
1891
+ '''
1892
+ 打印收到的数据,实际处理时不需要用这个,只是为了测试用
1893
+ '''
1894
+ while True:
1895
+ data = tx1.Q.get().split('|')
1896
+ print(data)
1897
+ try:
1898
+ dict_count['count'] = int(data[0].split('_')[-1])
1899
+ except:
1900
+ pass
1901
+ try:
1902
+ yanchi = round((time.time() - float(data[1])) * 1000,9)
1903
+ print('数据延迟>>>%s ms'%(yanchi))
1904
+ except:
1905
+ pass
1906
+ threading.Thread(target = show).start()
1907
+
1908
+
1909
+ #延迟测试
1910
+ send_len = 10
1911
+ send_len = 1 * 10**6
1912
+ tx1.push('afds_%s'%(2),time.time(),'litao')
1913
+ t1 = time.time()
1914
+
1915
+ for i in range(send_len):
1916
+ tx1.push('afds_%s'%(i),time.time(),'litao')
1917
+ # time.sleep(0.1)
1918
+ while 1:
1919
+ if tx1.tx_que.empty():
1920
+ break
1921
+ time.sleep(0.1)
1922
+ send_use_time = time.time() - t1
1923
+ while 1:
1924
+ if dict_count['count'] == send_len - 1:
1925
+ break
1926
+ time.sleep(0.1)
1927
+ recv_use_time = time.time() - t1
1928
+ print('发送%s万条time.time()数据用时>>>>>>>>>%s秒'%(send_len/10000,send_use_time))
1929
+ print('接收%s万条time.time()数据用时>>>>>>>>>%s秒'%(send_len/10000,recv_use_time))
1930
+ # while 1:
1931
+ # time.sleep(1)
1932
+
1933
+
1934
+
1935
+