pymecli 0.2.5__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.
- api/__init__.py +0 -0
- api/v1/__init__.py +9 -0
- api/v1/clash.py +34 -0
- api/v1/redis.py +26 -0
- cli/__init__.py +0 -0
- cli/bitget.py +55 -0
- cli/example.py +32 -0
- cli/fast.py +169 -0
- cli/gate.py +16 -0
- cli/util.py +112 -0
- core/__init__.py +0 -0
- core/clash.py +267 -0
- core/config.py +34 -0
- core/redis_client.py +137 -0
- crypto/__init__.py +0 -0
- crypto/bitget.py +124 -0
- crypto/gate.py +35 -0
- data/__init__.py +0 -0
- data/dou_dict.py +172 -0
- data/dou_list.py +93 -0
- data/main.py +5 -0
- data/template.yaml +150 -0
- models/__init__.py +0 -0
- models/douzero_model.py +45 -0
- models/ocr_model.py +57 -0
- models/response.py +37 -0
- pymecli-0.2.5.dist-info/METADATA +45 -0
- pymecli-0.2.5.dist-info/RECORD +39 -0
- pymecli-0.2.5.dist-info/WHEEL +4 -0
- pymecli-0.2.5.dist-info/entry_points.txt +6 -0
- utils/__init__.py +0 -0
- utils/elapsed.py +18 -0
- utils/helper.py +9 -0
- utils/logger.py +26 -0
- utils/mysql.py +79 -0
- utils/pd.py +20 -0
- utils/sleep.py +16 -0
- utils/text.py +33 -0
- utils/toml.py +6 -0
core/clash.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
import yaml
|
|
6
|
+
from fastapi import Depends
|
|
7
|
+
|
|
8
|
+
module_dir = Path(__file__).resolve().parent.parent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ClashConfig:
|
|
12
|
+
def __init__(self, rule_base_url: str, my_rule_base_url: str, request_proxy: str):
|
|
13
|
+
self.rule_base_url = rule_base_url.rstrip("/")
|
|
14
|
+
self.my_rule_base_url = my_rule_base_url.rstrip("/")
|
|
15
|
+
self.request_proxy = request_proxy
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ClashYamlGenerator:
|
|
19
|
+
def __init__(self, config: ClashConfig):
|
|
20
|
+
self.rule_base_url = config.rule_base_url
|
|
21
|
+
self.my_rule_base_url = config.my_rule_base_url
|
|
22
|
+
self.request_proxy = config.request_proxy
|
|
23
|
+
|
|
24
|
+
def gen(self, sub_list: list[dict]):
|
|
25
|
+
proxies = None
|
|
26
|
+
if self.request_proxy:
|
|
27
|
+
proxies = {
|
|
28
|
+
"http": self.request_proxy,
|
|
29
|
+
"https": self.request_proxy,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
with open(str(module_dir / "data/template.yaml"), "r", encoding="utf-8") as f:
|
|
33
|
+
template = yaml.safe_load(f)
|
|
34
|
+
|
|
35
|
+
template["proxy-groups"].extend(
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
"name": "全局选择",
|
|
39
|
+
"type": "select",
|
|
40
|
+
"proxies": ["自动选择", "手动选择", "轮询"]
|
|
41
|
+
+ [item["name"] for item in sub_list],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "自动选择",
|
|
45
|
+
"type": "url-test",
|
|
46
|
+
"url": "https://www.gstatic.com/generate_204",
|
|
47
|
+
"interval": 300,
|
|
48
|
+
"tolerance": 11,
|
|
49
|
+
"lazy": True,
|
|
50
|
+
"use": [f"provider.{item['name']}" for item in sub_list],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "手动选择",
|
|
54
|
+
"type": "select",
|
|
55
|
+
"use": [f"provider.{item['name']}" for item in sub_list],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "轮询",
|
|
59
|
+
"type": "load-balance",
|
|
60
|
+
"url": "https://www.gstatic.com/generate_204",
|
|
61
|
+
"interval": 300,
|
|
62
|
+
"lazy": True,
|
|
63
|
+
"strategy": "round-robin",
|
|
64
|
+
"use": [f"provider.{item['name']}" for item in sub_list],
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
userinfo = ""
|
|
69
|
+
for item in sub_list:
|
|
70
|
+
headers = {"User-Agent": item["user_agent"]} if item["user_agent"] else {}
|
|
71
|
+
|
|
72
|
+
if not item["url"]:
|
|
73
|
+
raise ValueError("Invalid subscription URL.")
|
|
74
|
+
response = requests.get(item["url"], headers=headers, proxies=proxies)
|
|
75
|
+
response.raise_for_status()
|
|
76
|
+
if not userinfo:
|
|
77
|
+
userinfo = response.headers["Subscription-Userinfo"]
|
|
78
|
+
remote_config = yaml.safe_load(response.text)
|
|
79
|
+
|
|
80
|
+
ps = remote_config.get("proxies", [])
|
|
81
|
+
if not ps:
|
|
82
|
+
raise ValueError("No proxies found in subscription.")
|
|
83
|
+
|
|
84
|
+
template["proxy-providers"][f"provider.{item['name']}"] = {
|
|
85
|
+
"type": "inline",
|
|
86
|
+
"payload": ps,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
template["proxy-groups"].append(
|
|
90
|
+
{
|
|
91
|
+
"name": item["name"],
|
|
92
|
+
"type": "url-test",
|
|
93
|
+
"url": "https://www.gstatic.com/generate_204",
|
|
94
|
+
"interval": 300,
|
|
95
|
+
"tolerance": 11,
|
|
96
|
+
"lazy": True,
|
|
97
|
+
"use": [f"provider.{item['name']}"],
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# 获取rules
|
|
102
|
+
rule_list = [
|
|
103
|
+
[f"{self.my_rule_base_url}/direct.yaml", "DIRECT"],
|
|
104
|
+
[f"{self.my_rule_base_url}/proxy.yaml", "全局选择"],
|
|
105
|
+
[
|
|
106
|
+
f"{self.my_rule_base_url}/round.yaml",
|
|
107
|
+
"轮询",
|
|
108
|
+
],
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
for item in rule_list:
|
|
112
|
+
response = requests.get(item[0], proxies=proxies)
|
|
113
|
+
response.raise_for_status()
|
|
114
|
+
remote = yaml.safe_load(response.text)
|
|
115
|
+
template["rule-providers"][os.path.basename(item[0])] = {
|
|
116
|
+
"type": "inline",
|
|
117
|
+
"behavior": "classical",
|
|
118
|
+
"payload": remote["payload"],
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
template["rules"].append(f"RULE-SET,{os.path.basename(item[0])},{item[1]}")
|
|
122
|
+
|
|
123
|
+
template["rule-providers"].update(
|
|
124
|
+
{
|
|
125
|
+
"applications": {
|
|
126
|
+
"type": "http",
|
|
127
|
+
"behavior": "classical",
|
|
128
|
+
"url": f"{self.rule_base_url}/applications.txt",
|
|
129
|
+
"path": "./ruleset/applications.yaml",
|
|
130
|
+
"interval": 86400,
|
|
131
|
+
},
|
|
132
|
+
"private": {
|
|
133
|
+
"type": "http",
|
|
134
|
+
"behavior": "domain",
|
|
135
|
+
"url": f"{self.rule_base_url}/private.txt",
|
|
136
|
+
"path": "./ruleset/private.yaml",
|
|
137
|
+
"interval": 86400,
|
|
138
|
+
},
|
|
139
|
+
"icloud": {
|
|
140
|
+
"type": "http",
|
|
141
|
+
"behavior": "domain",
|
|
142
|
+
"url": f"{self.rule_base_url}/icloud.txt",
|
|
143
|
+
"path": "./ruleset/icloud.yaml",
|
|
144
|
+
"interval": 86400,
|
|
145
|
+
},
|
|
146
|
+
"apple": {
|
|
147
|
+
"type": "http",
|
|
148
|
+
"behavior": "domain",
|
|
149
|
+
"url": f"{self.rule_base_url}/apple.txt",
|
|
150
|
+
"path": "./ruleset/apple.yaml",
|
|
151
|
+
"interval": 86400,
|
|
152
|
+
},
|
|
153
|
+
"google": {
|
|
154
|
+
"type": "http",
|
|
155
|
+
"behavior": "domain",
|
|
156
|
+
"url": f"{self.rule_base_url}/google.txt",
|
|
157
|
+
"path": "./ruleset/google.yaml",
|
|
158
|
+
"interval": 86400,
|
|
159
|
+
},
|
|
160
|
+
"proxy": {
|
|
161
|
+
"type": "http",
|
|
162
|
+
"behavior": "domain",
|
|
163
|
+
"url": f"{self.rule_base_url}/proxy.txt",
|
|
164
|
+
"path": "./ruleset/proxy.yaml",
|
|
165
|
+
"interval": 86400,
|
|
166
|
+
},
|
|
167
|
+
"direct": {
|
|
168
|
+
"type": "http",
|
|
169
|
+
"behavior": "domain",
|
|
170
|
+
"url": f"{self.rule_base_url}/direct.txt",
|
|
171
|
+
"path": "./ruleset/direct.yaml",
|
|
172
|
+
"interval": 86400,
|
|
173
|
+
},
|
|
174
|
+
"lancidr": {
|
|
175
|
+
"type": "http",
|
|
176
|
+
"behavior": "ipcidr",
|
|
177
|
+
"url": f"{self.rule_base_url}/lancidr.txt",
|
|
178
|
+
"path": "./ruleset/lancidr.yaml",
|
|
179
|
+
"interval": 86400,
|
|
180
|
+
},
|
|
181
|
+
"cncidr": {
|
|
182
|
+
"type": "http",
|
|
183
|
+
"behavior": "ipcidr",
|
|
184
|
+
"url": f"{self.rule_base_url}/cncidr.txt",
|
|
185
|
+
"path": "./ruleset/cncidr.yaml",
|
|
186
|
+
"interval": 86400,
|
|
187
|
+
},
|
|
188
|
+
"telegramcidr": {
|
|
189
|
+
"type": "http",
|
|
190
|
+
"behavior": "ipcidr",
|
|
191
|
+
"url": f"{self.rule_base_url}/telegramcidr.txt",
|
|
192
|
+
"path": "./ruleset/telegramcidr.yaml",
|
|
193
|
+
"interval": 86400,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
template["rules"].extend(
|
|
199
|
+
[
|
|
200
|
+
"RULE-SET,applications,DIRECT",
|
|
201
|
+
"DOMAIN,clash.razord.top,DIRECT",
|
|
202
|
+
"DOMAIN,yacd.haishan.me,DIRECT",
|
|
203
|
+
"RULE-SET,private,DIRECT",
|
|
204
|
+
"RULE-SET,icloud,DIRECT",
|
|
205
|
+
"RULE-SET,apple,DIRECT",
|
|
206
|
+
"RULE-SET,google,全局选择",
|
|
207
|
+
"RULE-SET,proxy,全局选择",
|
|
208
|
+
"RULE-SET,direct,DIRECT",
|
|
209
|
+
"RULE-SET,lancidr,DIRECT",
|
|
210
|
+
"RULE-SET,cncidr,DIRECT",
|
|
211
|
+
"RULE-SET,telegramcidr,全局选择",
|
|
212
|
+
"GEOIP,LAN,DIRECT,no-resolve",
|
|
213
|
+
"GEOIP,CN,DIRECT,no-resolve",
|
|
214
|
+
"MATCH,全局选择",
|
|
215
|
+
]
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return template, userinfo
|
|
219
|
+
|
|
220
|
+
def query2sub(self, urls: str, agents: str, names: str):
|
|
221
|
+
url_list = urls.split(",") if urls else []
|
|
222
|
+
agents_list = agents.split(",") if agents else []
|
|
223
|
+
name_list = names.split(",") if names else []
|
|
224
|
+
|
|
225
|
+
max_length = max(len(url_list), len(agents_list), len(name_list))
|
|
226
|
+
|
|
227
|
+
while len(url_list) < max_length:
|
|
228
|
+
url_list.append("")
|
|
229
|
+
while len(agents_list) < max_length:
|
|
230
|
+
agents_list.append("")
|
|
231
|
+
while len(name_list) < max_length:
|
|
232
|
+
name_list.append("")
|
|
233
|
+
|
|
234
|
+
sub_list = []
|
|
235
|
+
|
|
236
|
+
for i in range(max_length):
|
|
237
|
+
if not url_list[i]:
|
|
238
|
+
raise ValueError(f"Invalid subscription URL. #{i + 1}")
|
|
239
|
+
|
|
240
|
+
sub_list.append(
|
|
241
|
+
{
|
|
242
|
+
"url": url_list[i],
|
|
243
|
+
"user_agent": agents_list[i] if agents_list[i] else "",
|
|
244
|
+
"name": name_list[i] if name_list[i] else f"订阅{i}",
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
return sub_list
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
generator_instance = None
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def init_generator(config: ClashConfig):
|
|
255
|
+
global generator_instance
|
|
256
|
+
generator_instance = ClashYamlGenerator(config)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_generator():
|
|
260
|
+
global generator_instance
|
|
261
|
+
if generator_instance is None:
|
|
262
|
+
raise RuntimeError("ClashYamlGenerator not initialized")
|
|
263
|
+
return generator_instance
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def get_generator_dependency():
|
|
267
|
+
return Depends(get_generator)
|
core/config.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
|
|
3
|
+
from pydantic_settings import BaseSettings
|
|
4
|
+
|
|
5
|
+
metadata = importlib.metadata.metadata("pymecli")
|
|
6
|
+
# module_dir = Path(__file__).resolve().parent.parent
|
|
7
|
+
|
|
8
|
+
# project = read_toml(str(module_dir / "./pyproject.toml"))["project"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Settings(BaseSettings):
|
|
12
|
+
# API配置
|
|
13
|
+
API_V1_STR: str = "/api/v1"
|
|
14
|
+
NAME: str = metadata["Name"]
|
|
15
|
+
DESCRIPTION: str = (
|
|
16
|
+
f"{metadata['Summary']}, FastAPI提供: clash订阅转换、baidu.gushitong api"
|
|
17
|
+
)
|
|
18
|
+
VERSION: str = metadata["Version"]
|
|
19
|
+
|
|
20
|
+
print(f"project: {NAME}")
|
|
21
|
+
print(f"version: {VERSION}")
|
|
22
|
+
print(f"description: {DESCRIPTION}")
|
|
23
|
+
|
|
24
|
+
class Config:
|
|
25
|
+
env_prefix = "PY_ME_CLI_" # 添加环境变量前缀
|
|
26
|
+
case_sensitive = True
|
|
27
|
+
|
|
28
|
+
def reload(self):
|
|
29
|
+
new_settings = Settings()
|
|
30
|
+
for field in Settings.model_fields:
|
|
31
|
+
setattr(self, field, getattr(new_settings, field))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
settings = Settings()
|
core/redis_client.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import redis
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RedisClient:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
host=os.getenv("REDIS_HOST", "192.168.123.7"),
|
|
12
|
+
port=int(os.getenv("REDIS_PORT", 6379)),
|
|
13
|
+
db=int(os.getenv("REDIS_DB", 0)),
|
|
14
|
+
password=os.getenv("REDIS_PASSWORD"),
|
|
15
|
+
decode_responses=True,
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
初始化Redis客户端
|
|
19
|
+
:param host: Redis服务器地址
|
|
20
|
+
:param port: Redis服务器端口
|
|
21
|
+
:param db: 数据库编号
|
|
22
|
+
:param password: 密码(如果需要)
|
|
23
|
+
:param decode_responses: 是否自动解码响应(将字节转换为字符串)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
self.client = redis.Redis(
|
|
27
|
+
host=host,
|
|
28
|
+
port=port,
|
|
29
|
+
db=db,
|
|
30
|
+
password=password,
|
|
31
|
+
decode_responses=decode_responses,
|
|
32
|
+
socket_connect_timeout=5,
|
|
33
|
+
socket_timeout=5,
|
|
34
|
+
)
|
|
35
|
+
# 测试连接
|
|
36
|
+
self.client.ping()
|
|
37
|
+
|
|
38
|
+
def get_value(self, key: str) -> Optional[Any]:
|
|
39
|
+
"""
|
|
40
|
+
根据key获取值
|
|
41
|
+
:param key: Redis中的键名
|
|
42
|
+
:return: 键对应的值, 如果键不存在则返回None
|
|
43
|
+
"""
|
|
44
|
+
if not self.client:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
return self.client.get(key)
|
|
48
|
+
|
|
49
|
+
def get_all_keys(self, pattern: str = "*"):
|
|
50
|
+
"""
|
|
51
|
+
获取所有匹配的键
|
|
52
|
+
:param pattern: 键的匹配模式,默认为"*"匹配所有键
|
|
53
|
+
:return: 匹配的键列表
|
|
54
|
+
"""
|
|
55
|
+
if not self.client:
|
|
56
|
+
return []
|
|
57
|
+
|
|
58
|
+
return self.client.keys(pattern)
|
|
59
|
+
|
|
60
|
+
def set_value(self, key: str, value: Any, expire: int | None = None):
|
|
61
|
+
"""
|
|
62
|
+
设置键值对
|
|
63
|
+
:param key: 键名
|
|
64
|
+
:param value: 值
|
|
65
|
+
:param expire: 过期时间(秒),如果提供则在指定时间后过期
|
|
66
|
+
:return: 设置成功返回True, 否则返回False
|
|
67
|
+
"""
|
|
68
|
+
if not self.client:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
# 如果值是字典或列表,转换为JSON字符串存储
|
|
72
|
+
if isinstance(value, (dict, list)):
|
|
73
|
+
value = json.dumps(value, ensure_ascii=False)
|
|
74
|
+
|
|
75
|
+
result = self.client.set(key, value)
|
|
76
|
+
if expire:
|
|
77
|
+
self.client.expire(key, expire)
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
def get_hash_value(self, name: str, key: str):
|
|
81
|
+
"""
|
|
82
|
+
获取哈希类型数据中的值
|
|
83
|
+
:param name: 哈希表名称
|
|
84
|
+
:param key: 哈希表中的键
|
|
85
|
+
:return: 键对应的值
|
|
86
|
+
"""
|
|
87
|
+
if not self.client:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
return self.client.hget(name, key)
|
|
91
|
+
|
|
92
|
+
def get_list(self, key: str):
|
|
93
|
+
"""
|
|
94
|
+
获取列表类型数据
|
|
95
|
+
:param key: 列表键名
|
|
96
|
+
:return: 列表内容
|
|
97
|
+
"""
|
|
98
|
+
if not self.client:
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
return self.client.lrange(key, 0, -1)
|
|
102
|
+
|
|
103
|
+
def close(self):
|
|
104
|
+
"""
|
|
105
|
+
关闭Redis连接
|
|
106
|
+
"""
|
|
107
|
+
if self.client:
|
|
108
|
+
self.client.close()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# 使用示例
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
# 创建Redis客户端实例
|
|
114
|
+
client = RedisClient()
|
|
115
|
+
# 示例1: 获取普通键值
|
|
116
|
+
key = "baidu.finance.getbanner"
|
|
117
|
+
value = client.get_value(key)
|
|
118
|
+
print(f"键 '{key}' 的值为: {value}")
|
|
119
|
+
|
|
120
|
+
# 示例2: 获取所有键
|
|
121
|
+
all_keys = client.get_all_keys()
|
|
122
|
+
print(f"所有键: {all_keys}")
|
|
123
|
+
print(f"所有键: {type(all_keys)}")
|
|
124
|
+
|
|
125
|
+
# 示例3: 设置键值
|
|
126
|
+
client.set_value("test_key", {"name": "张三", "age": 30}, expire=3600)
|
|
127
|
+
|
|
128
|
+
# 示例4: 获取哈希值
|
|
129
|
+
hash_value = client.get_hash_value("my_hash", "field1")
|
|
130
|
+
print(f"哈希值: {hash_value}")
|
|
131
|
+
|
|
132
|
+
# 示例5: 获取列表值
|
|
133
|
+
list_value = client.get_list("my_list")
|
|
134
|
+
print(f"列表值: {list_value}")
|
|
135
|
+
|
|
136
|
+
# 关闭连接
|
|
137
|
+
client.close()
|
crypto/__init__.py
ADDED
|
File without changes
|
crypto/bitget.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from sqlalchemy import Engine
|
|
5
|
+
|
|
6
|
+
from utils.mysql import mysql_to_csv
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def tickers(url: str, symbols: list, proxy: str | None = None):
|
|
10
|
+
proxies = None
|
|
11
|
+
if proxy:
|
|
12
|
+
proxies = {
|
|
13
|
+
"http": proxy,
|
|
14
|
+
"https": proxy,
|
|
15
|
+
}
|
|
16
|
+
response = requests.get(url, proxies=proxies)
|
|
17
|
+
|
|
18
|
+
resp_json = response.json()
|
|
19
|
+
symbols_list = [
|
|
20
|
+
s.lower() + "usdt" if not s.lower().endswith("usdt") else s.lower()
|
|
21
|
+
for s in symbols
|
|
22
|
+
]
|
|
23
|
+
if response.status_code == 200 and resp_json.get("code") == "00000":
|
|
24
|
+
data = resp_json.get("data", [])
|
|
25
|
+
results = []
|
|
26
|
+
for item in data:
|
|
27
|
+
if item["symbol"].lower() in symbols_list:
|
|
28
|
+
price = Decimal(item["lastPr"])
|
|
29
|
+
results.append(
|
|
30
|
+
f"{item['symbol'].upper().rstrip('USDT')}:{format(price, 'f')}"
|
|
31
|
+
)
|
|
32
|
+
print(f"[{','.join(results)}]")
|
|
33
|
+
else:
|
|
34
|
+
print(
|
|
35
|
+
f"📌 请求失败,状态码:{response.status_code},错误信息:{resp_json.get('msg')}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def mix_tickers(symbols: list, proxy: str | None = None):
|
|
40
|
+
url = "https://api.bitget.com/api/v2/mix/market/tickers?productType=USDT-FUTURES"
|
|
41
|
+
tickers(url, symbols, proxy)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def spot_tickers(symbols: list, proxy: str | None = None):
|
|
45
|
+
url = "https://api.bitget.com/api/v2/spot/market/tickers"
|
|
46
|
+
tickers(url, symbols, proxy)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def bitget_sf_open(engine: Engine, csv_path: str):
|
|
50
|
+
query = "select * from bitget_sf where spot_open_usdt is not null and futures_open_usdt is not null and pnl is null and up_status = 0 and deleted_at is null;"
|
|
51
|
+
row_count = mysql_to_csv(
|
|
52
|
+
engine,
|
|
53
|
+
csv_path,
|
|
54
|
+
"bitget_sf",
|
|
55
|
+
query,
|
|
56
|
+
update_status=1,
|
|
57
|
+
d_column_names=["spot_client_order_id", "futures_client_order_id"],
|
|
58
|
+
pd_dtype={
|
|
59
|
+
"spot_order_id": str,
|
|
60
|
+
"futures_order_id": str,
|
|
61
|
+
"spot_tracking_no": str,
|
|
62
|
+
"futures_tracking_no": str,
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
print(f"🚀 bitget sf open count:({row_count})")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def bitget_sf_close(engine: Engine, csv_path: str):
|
|
70
|
+
query = "select * from bitget_sf where pnl is not null and up_status in (0,1);"
|
|
71
|
+
row_count = mysql_to_csv(
|
|
72
|
+
engine,
|
|
73
|
+
csv_path,
|
|
74
|
+
"bitget_sf",
|
|
75
|
+
query,
|
|
76
|
+
update_status=2,
|
|
77
|
+
d_column_names=["spot_client_order_id", "futures_client_order_id"],
|
|
78
|
+
pd_dtype={
|
|
79
|
+
"spot_order_id": str,
|
|
80
|
+
"futures_order_id": str,
|
|
81
|
+
"spot_tracking_no": str,
|
|
82
|
+
"futures_tracking_no": str,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
print(f"🚀 bitget sf close count:({row_count})")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def grid_open(engine: Engine, csv_path: str):
|
|
90
|
+
query = "select id,created_at,name,act_name,symbol,qty,cex,status,up_status,path,level,earn,cost,buy_px,benefit,sell_px,profit,order_id,client_order_id,fx_order_id,fx_client_order_id,signature,chain,open_at,close_at,mint,dex_act,dex_status,dex_fail_count from bitget where ((cost is not null or benefit is not null) and profit is null) and up_status = 0 and order_id is not null and deleted_at is null;"
|
|
91
|
+
row_count = mysql_to_csv(
|
|
92
|
+
engine,
|
|
93
|
+
csv_path,
|
|
94
|
+
"bitget",
|
|
95
|
+
query,
|
|
96
|
+
update_status=1,
|
|
97
|
+
d_column_names=["client_order_id"],
|
|
98
|
+
pd_dtype={
|
|
99
|
+
"order_id": str,
|
|
100
|
+
"fx_order_id": str,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
print(f"🧮 bitget open count:({row_count})")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def grid_close(engine: Engine, csv_path: str):
|
|
107
|
+
query = "select id,created_at,name,act_name,symbol,qty,cex,status,up_status,path,level,earn,cost,buy_px,benefit,sell_px,profit,order_id,client_order_id,fx_order_id,fx_client_order_id,signature,chain,open_at,close_at,mint,dex_act,dex_status,dex_fail_count from bitget where profit is not null and up_status in (0,1) and deleted_at is null;"
|
|
108
|
+
row_count = mysql_to_csv(
|
|
109
|
+
engine,
|
|
110
|
+
csv_path,
|
|
111
|
+
"bitget",
|
|
112
|
+
query,
|
|
113
|
+
update_status=2,
|
|
114
|
+
d_column_names=["client_order_id"],
|
|
115
|
+
pd_dtype={
|
|
116
|
+
"order_id": str,
|
|
117
|
+
"fx_order_id": str,
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
print(f"🧮 bitget close count:({row_count})")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
pass
|
crypto/gate.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from sqlalchemy import Engine
|
|
2
|
+
|
|
3
|
+
from utils.mysql import mysql_to_csv
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def gate_open(engine: Engine, csv_path: str):
|
|
7
|
+
query = "select id,created_at,name,act_name,symbol,qty,cex,status,up_status,path,level,earn,cost,buy_px,benefit,sell_px,profit,order_id,client_order_id,fx_order_id,fx_client_order_id,signature,chain,open_at,close_at,mint,dex_act,dex_status,dex_fail_count from gate where ((cost is not null or benefit is not null) and profit is null) and up_status = 0 and order_id is not null and deleted_at is null;"
|
|
8
|
+
row_count = mysql_to_csv(
|
|
9
|
+
engine,
|
|
10
|
+
csv_path,
|
|
11
|
+
"gate",
|
|
12
|
+
query,
|
|
13
|
+
update_status=1,
|
|
14
|
+
d_column_names=["client_order_id"],
|
|
15
|
+
pd_dtype={"order_id": str, "fx_order_id": str},
|
|
16
|
+
)
|
|
17
|
+
print(f"🧮 gate open count:({row_count})")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def gate_close(engine: Engine, csv_path: str):
|
|
21
|
+
query = "select id,created_at,name,act_name,symbol,qty,cex,status,up_status,path,level,earn,cost,buy_px,benefit,sell_px,profit,order_id,client_order_id,fx_order_id,fx_client_order_id,signature,chain,open_at,close_at,mint,dex_act,dex_status,dex_fail_count from gate where profit is not null and up_status in (0,1) and deleted_at is null;"
|
|
22
|
+
row_count = mysql_to_csv(
|
|
23
|
+
engine,
|
|
24
|
+
csv_path,
|
|
25
|
+
"gate",
|
|
26
|
+
query,
|
|
27
|
+
update_status=2,
|
|
28
|
+
d_column_names=["client_order_id"],
|
|
29
|
+
pd_dtype={"order_id": str, "fx_order_id": str},
|
|
30
|
+
)
|
|
31
|
+
print(f"🧮 gate close count:({row_count})")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
pass
|
data/__init__.py
ADDED
|
File without changes
|