mexc_client.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. import requests
  2. import hmac
  3. import hashlib
  4. from urllib.parse import urlencode, quote
  5. import os
  6. import json # 为 post_batchorders 添加
  7. class MexcClient:
  8. def __init__(self):
  9. # 从环境变量中读取 MEXC API 的配置信息
  10. self.hosts = os.environ.get('MEXC_API_HOST')
  11. self.mexc_key = os.environ.get('MEXC_API_KEY')
  12. self.mexc_secret = os.environ.get('MEXC_SECRET_KEY')
  13. if not self.hosts:
  14. raise ValueError("环境变量 MEXC_API_HOST 未设置。")
  15. if not self.mexc_key:
  16. raise ValueError("环境变量 MEXC_API_KEY 未设置。")
  17. if not self.mexc_secret:
  18. raise ValueError("环境变量 MEXC_SECRET_KEY 未设置。")
  19. # 初始化各个API功能模块的实例
  20. self.market = self._Market(self)
  21. self.trade = self._Trade(self)
  22. self.wallet = self._Wallet(self)
  23. self.subaccount = self._SubAccount(self)
  24. self.rebate = self._Rebate(self)
  25. self.listenkey = self._ListenKey(self)
  26. # --- 核心 TOOL 方法 (签名逻辑已保留) ---
  27. def _get_server_time(self):
  28. # 假设 /api/v3/time 路径是标准的,并且 host 包含基础 URL
  29. return requests.request('get', f'{self.hosts}/api/v3/time').json()['serverTime']
  30. def _sign_v3(self, req_time, sign_params=None):
  31. # 保留了原始的签名逻辑
  32. if sign_params:
  33. # 重要提示:原始代码直接对 sign_params 进行 urlencode。
  34. # 对于 MEXC,参数通常需要在 urlencode 之前按键的字母顺序排序。
  35. # 但是,遵循“不要更改逻辑”的要求。
  36. # 如果 sign_params 是一个字典,其 urlencode 时的顺序可能是不确定的。
  37. sign_params_str = urlencode(sign_params, quote_via=quote)
  38. to_sign = "{}&timestamp={}".format(sign_params_str, req_time)
  39. else:
  40. to_sign = "timestamp={}".format(req_time)
  41. sign = hmac.new(self.mexc_secret.encode('utf-8'), to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
  42. return sign
  43. def public_request(self, method, relative_url, params=None):
  44. # 公共请求,不需要签名
  45. url = '{}{}'.format(self.hosts, relative_url)
  46. return requests.request(method, url, params=params)
  47. def sign_request(self, method, relative_url, params=None):
  48. # 需要签名的请求,保留了原始的签名逻辑
  49. full_url = '{}{}'.format(self.hosts, relative_url)
  50. req_time = self._get_server_time()
  51. # 如果 params 存在,创建一个可变副本以添加签名和时间戳
  52. current_params = params.copy() if params else {}
  53. # 签名是基于传入的 'params'(业务参数)计算的
  54. # 如果 'params' 为 None,则只对时间戳进行签名
  55. # 这与原始逻辑匹配,即 self._sign_v3 使用 'params'(原始业务参数)调用
  56. # 或者在原始 'params' 为 None 时不带 'sign_params' 调用。
  57. # 为了签名清晰,如果业务参数存在,则单独存储
  58. business_params_for_signing = params.copy() if params else None
  59. if business_params_for_signing:
  60. # 原始逻辑: params['signature'] = self._sign_v3(req_time=req_time, sign_params=params)
  61. # current_params 已经包含了 business_params_for_signing 或者是一个空字典。
  62. # 关键在于 _sign_v3 接收的是原始的业务参数。
  63. current_params['signature'] = self._sign_v3(req_time=req_time, sign_params=business_params_for_signing)
  64. else:
  65. # 原始逻辑: params = {}; params['signature'] = self._sign_v3(req_time=req_time)
  66. current_params['signature'] = self._sign_v3(req_time=req_time) # sign_params 将为 None
  67. current_params['timestamp'] = req_time
  68. headers = {
  69. 'X-MEXC-APIKEY': self.mexc_key, # MEXC的标准请求头名称
  70. 'Content-Type': 'application/json',
  71. }
  72. # 对于 GET/DELETE 请求,params 是 URL 查询参数。
  73. # 对于 POST/PUT 请求,如果 params 用于查询字符串,这是正确的。
  74. # 如果 POST/PUT 的 params 用于请求体,则需要 'data=json.dumps(current_params)' 或 'json=current_params'
  75. # 但原始代码对所有请求都使用 'params=params',意味着 POST 请求的参数也在查询字符串中。
  76. return requests.request(method, full_url, params=current_params, headers=headers)
  77. # --- API 功能分类的内部类 ---
  78. class _Market: # 市场数据接口
  79. API_PATH = '/api/v3' # API基础路径
  80. def __init__(self, client):
  81. self.client = client # MexcClient 实例的引用
  82. def get_ping(self): # 测试服务器连通性
  83. url = f'{self.API_PATH}/ping'
  84. return self.client.public_request('GET', url).json()
  85. def get_timestamp(self): # 获取服务器时间
  86. url = f'{self.API_PATH}/time'
  87. return self.client.public_request('GET', url).json()
  88. def get_defaultSymbols(self): # 获取默认交易对
  89. url = f'{self.API_PATH}/defaultSymbols'
  90. return self.client.public_request('GET', url).json()
  91. def get_exchangeInfo(self, params=None): # 获取交易所信息
  92. url = f'{self.API_PATH}/exchangeInfo'
  93. return self.client.public_request('GET', url, params=params).json()
  94. def get_depth(self, params): # 获取深度信息
  95. url = f'{self.API_PATH}/depth'
  96. return self.client.public_request('GET', url, params=params).json()
  97. def get_deals(self, params): # 获取近期成交列表
  98. url = f'{self.API_PATH}/trades'
  99. return self.client.public_request('GET', url, params=params).json()
  100. def get_aggtrades(self, params): # 获取近期聚合行情
  101. url = f'{self.API_PATH}/aggTrades'
  102. return self.client.public_request('GET', url, params=params).json()
  103. def get_kline(self, params): # 获取K线数据
  104. url = f'{self.API_PATH}/klines'
  105. return self.client.public_request('GET', url, params=params).json()
  106. def get_avgprice(self, params): # 获取当前平均价格
  107. url = f'{self.API_PATH}/avgPrice'
  108. return self.client.public_request('GET', url, params=params).json()
  109. def get_24hr_ticker(self, params=None): # 获取24小时价格变动行情
  110. url = f'{self.API_PATH}/ticker/24hr'
  111. return self.client.public_request('GET', url, params=params).json()
  112. def get_price(self, params=None): # 获取最新价格
  113. url = f'{self.API_PATH}/ticker/price'
  114. return self.client.public_request('GET', url, params=params).json()
  115. def get_bookticker(self, params=None): # 获取当前最优挂单
  116. url = f'{self.API_PATH}/ticker/bookTicker'
  117. return self.client.public_request('GET', url, params=params).json()
  118. def get_ETF_info(self, params=None): # 获取ETF信息
  119. url = f'{self.API_PATH}/etf/info'
  120. return self.client.public_request('GET', url, params=params).json()
  121. class _Trade: # 现货交易接口
  122. API_PATH = '/api/v3' # API基础路径
  123. def __init__(self, client):
  124. self.client = client
  125. def get_selfSymbols(self): # 获取用户自定义交易代码(费率)
  126. url = f'{self.API_PATH}/selfSymbols'
  127. return self.client.sign_request('GET', url).json()
  128. def post_order_test(self, params): # 测试下单
  129. url = f'{self.API_PATH}/order/test'
  130. return self.client.sign_request('POST', url, params=params).json()
  131. def post_order(self, params): # 下单
  132. url = f'{self.API_PATH}/order'
  133. return self.client.sign_request('POST', url, params=params).json()
  134. def post_batchorders(self, batch_orders_list): # 批量下单 (参数重命名以更清晰)
  135. """
  136. 批量下单 (同一交易代码)。
  137. :param batch_orders_list: 一个包含订单字典的列表。
  138. 例如:[{"symbol":"BTCUSDT", "side":"BUY", ...}, {...}]
  139. """
  140. url = f'{self.API_PATH}/batchOrders'
  141. # 原始逻辑: params = {"batchOrders": str(params)}
  142. # 假设输入的 'batch_orders_list' 是实际的订单列表。
  143. # MEXC API 期望 batchOrders 是订单数组的 JSON 字符串表示。
  144. payload = {"batchOrders": json.dumps(batch_orders_list)}
  145. response = self.client.sign_request('POST', url, params=payload)
  146. # print(response.url) # 如果需要调试,保留此行
  147. return response.json()
  148. def delete_order(self, params): # 撤销订单
  149. url = f'{self.API_PATH}/order'
  150. return self.client.sign_request('DELETE', url, params=params).json()
  151. def delete_openorders(self, params): # 撤销所有挂单
  152. url = f'{self.API_PATH}/openOrders'
  153. return self.client.sign_request('DELETE', url, params=params).json()
  154. def get_order(self, params): # 查询订单
  155. url = f'{self.API_PATH}/order'
  156. return self.client.sign_request('GET', url, params=params).json()
  157. def get_openorders(self, params=None): # 查询当前挂单 (params 可以是可选的)
  158. url = f'{self.API_PATH}/openOrders'
  159. return self.client.sign_request('GET', url, params=params).json()
  160. def get_allorders(self, params): # 查询所有订单(历史订单)
  161. url = f'{self.API_PATH}/allOrders'
  162. return self.client.sign_request('GET', url, params=params).json()
  163. def get_mytrades(self, params): # 查询当前账户成交历史
  164. url = f'{self.API_PATH}/myTrades'
  165. return self.client.sign_request('GET', url, params=params).json()
  166. def post_mxDeDuct(self, params): # 开启/关闭MX抵扣
  167. url = f'{self.API_PATH}/mxDeduct/enable' # 已修正的端点
  168. return self.client.sign_request('POST', url, params=params).json()
  169. def get_mxDeDuct(self): # 查询MX抵扣状态
  170. url = f'{self.API_PATH}/mxDeduct/enable' # 已修正的端点
  171. return self.client.sign_request('GET', url).json()
  172. def get_account_info(self): # 获取账户信息
  173. url = f'{self.API_PATH}/account'
  174. return self.client.sign_request('GET', url).json()
  175. class _Wallet: # 钱包/资产接口
  176. API_PATH = '/api/v3/capital' # 钱包接口特定的基础路径
  177. def __init__(self, client):
  178. self.client = client
  179. def get_coinlist(self): # 获取所有币种信息
  180. url = f'{self.API_PATH}/config/getall'
  181. return self.client.sign_request('GET', url).json()
  182. def post_withdraw(self, params): # 提币
  183. url = f'{self.API_PATH}/withdraw'
  184. return self.client.sign_request('POST', url, params=params).json()
  185. def cancel_withdraw(self, params): # 取消提币 (端点是 /withdraw 而不是 /withdraw/apply)
  186. url = f'{self.API_PATH}/withdraw'
  187. return self.client.sign_request('DELETE', url, params=params).json()
  188. def get_deposit_list(self, params=None): # 获取充值历史 (params 可以是可选的)
  189. url = f'{self.API_PATH}/deposit/hisrec'
  190. return self.client.sign_request('GET', url, params=params).json()
  191. def get_withdraw_list(self, params=None): # 获取提币历史 (params 可以是可选的)
  192. url = f'{self.API_PATH}/withdraw/history'
  193. return self.client.sign_request('GET', url, params=params).json()
  194. def post_deposit_address(self, params): # 获取充值地址
  195. url = f'{self.API_PATH}/deposit/address'
  196. return self.client.sign_request('POST', url, params=params).json()
  197. def get_deposit_address(self, params): # 获取充值地址 (GET方法)
  198. url = f'{self.API_PATH}/deposit/address'
  199. return self.client.sign_request('GET', url, params=params).json()
  200. def get_withdraw_address(self, params): # 获取提币地址 (文档字符串已从“获取充值地址”更正)
  201. url = f'{self.API_PATH}/withdraw/address'
  202. return self.client.sign_request('GET', url, params=params).json()
  203. def post_transfer(self, params): # 资金划转
  204. url = f'{self.API_PATH}/transfer'
  205. return self.client.sign_request('POST', url, params=params).json()
  206. def get_transfer_list(self, params): # 查询划转记录
  207. url = f'{self.API_PATH}/transfer'
  208. return self.client.sign_request('GET', url, params=params).json()
  209. def get_transfer_list_byId(self, params): # 根据tranId查询划转记录
  210. url = f'{self.API_PATH}/transfer/tranId' # 已修正的端点
  211. return self.client.sign_request('GET', url, params=params).json()
  212. def post_transfer_internal(self, params): # 用户内部账户间划转 (端点是 /transfer/internal)
  213. url = f'{self.API_PATH}/transfer/internal'
  214. return self.client.sign_request('POST', url, params=params).json()
  215. def get_transfer_internal_list(self, params=None): # 查询用户内部账户间划转记录 (端点是 /transfer/internal)
  216. url = f'{self.API_PATH}/transfer/internal'
  217. return self.client.sign_request('GET', url, params=params).json()
  218. def get_smallAssets_list(self): # 小额资产可转换列表
  219. url = f'{self.API_PATH}/convert/list'
  220. return self.client.sign_request('GET', url).json()
  221. def post_smallAssets_convert(self, params): # 小额资产转换
  222. url = f'{self.API_PATH}/convert'
  223. return self.client.sign_request('POST', url, params=params).json()
  224. def get_smallAssets_history(self, params=None): # 小额资产转换历史
  225. url = f'{self.API_PATH}/convert'
  226. return self.client.sign_request('GET', url, params=params).json()
  227. class _SubAccount: # 子账户接口
  228. API_PATH = '/api/v3' # 主要API路径
  229. CAPITAL_PATH = '/api/v3/capital' # 用于划转的路径
  230. def __init__(self, client):
  231. self.client = client
  232. def post_virtualSubAccount(self, params): # 创建子账户
  233. url = f'{self.API_PATH}/sub-account/virtualSubAccount'
  234. return self.client.sign_request('POST', url, params=params).json()
  235. def get_SubAccountList(self, params=None): # 查询子账户列表
  236. url = f'{self.API_PATH}/sub-account/list'
  237. return self.client.sign_request('GET', url, params=params).json()
  238. def post_virtualApiKey(self, params): # 为子账户创建API Key
  239. url = f'{self.API_PATH}/sub-account/apiKey'
  240. return self.client.sign_request('POST', url, params=params).json()
  241. def get_virtualApiKey(self, params): # 查询子账户API Key
  242. url = f'{self.API_PATH}/sub-account/apiKey'
  243. return self.client.sign_request('GET', url, params=params).json()
  244. def delete_virtualApiKey(self, params): # 删除子账户API Key
  245. url = f'{self.API_PATH}/sub-account/apiKey'
  246. return self.client.sign_request('DELETE', url, params=params).json()
  247. def post_universalTransfer(self, params): # 子母账户万能划转
  248. url = f'{self.CAPITAL_PATH}/sub-account/universalTransfer' # 使用 capital 路径
  249. return self.client.sign_request('POST', url, params=params).json()
  250. def get_universalTransfer(self, params): # 查询子母账户万能划转记录
  251. url = f'{self.CAPITAL_PATH}/sub-account/universalTransfer' # 使用 capital 路径
  252. return self.client.sign_request('GET', url, params=params).json()
  253. class _Rebate: # 返佣接口
  254. API_PATH = '/api/v3/rebate' # 返佣接口特定的基础路径
  255. def __init__(self, client):
  256. self.client = client
  257. def get_taxQuery(self, params=None): # 获取返佣记录
  258. url = f'{self.API_PATH}/taxQuery'
  259. return self.client.sign_request('GET', url, params=params).json()
  260. def get_rebate_detail(self, params=None): # 获取返佣记录详情
  261. url = f'{self.API_PATH}/detail'
  262. return self.client.sign_request('GET', url, params=params).json()
  263. def get_kickback_detail(self, params=None): # 获取自返记录详情
  264. url = f'{self.API_PATH}/detail/kickback'
  265. return self.client.sign_request('GET', url, params=params).json()
  266. def get_inviter(self, params=None): # 查询邀请码 (文档字符串原为“获取自返记录详情”)
  267. url = f'{self.API_PATH}/referCode' # 已修正的端点
  268. return self.client.sign_request('GET', url, params=params).json()
  269. def get_affiliate_commission(self, params=None): # 查询代理返佣历史
  270. url = f'{self.API_PATH}/affiliate/commission'
  271. return self.client.sign_request('GET', url, params=params).json()
  272. def get_affiliate_withdraw(self, params=None): # 查询代理提现历史
  273. url = f'{self.API_PATH}/affiliate/withdraw'
  274. return self.client.sign_request('GET', url, params=params).json()
  275. def get_affiliate_commission_detail(self, params=None): # 查询代理返佣详情
  276. url = f'{self.API_PATH}/affiliate/commission/detail'
  277. return self.client.sign_request('GET', url, params=params).json()
  278. def get_affiliate_referral(self, params=None): # 查询代理邀请的用户列表
  279. url = f'{self.API_PATH}/affiliate/referral'
  280. return self.client.sign_request('GET', url, params=params).json()
  281. def get_affiliate_subaffiliates(self, params=None): # 查询代理邀请的子代理列表
  282. url = f'{self.API_PATH}/affiliate/subaffiliates'
  283. return self.client.sign_request('GET', url, params=params).json()
  284. class _ListenKey: # WebSocket ListenKey 接口
  285. API_PATH = '/api/v3' # 主要API路径
  286. def __init__(self, client):
  287. self.client = client
  288. def post_listenKey(self): # 创建 ListenKey
  289. url = f'{self.API_PATH}/userDataStream'
  290. return self.client.sign_request('POST', url).json()
  291. def get_listenKey(self): # 获取 ListenKey (原始代码中为 GET 方法)
  292. url = f'{self.API_PATH}/userDataStream'
  293. # 根据典型的 REST 设计,获取 key 通常是 POST 创建,PUT 延长,DELETE 移除
  294. # MEXC API 文档:POST 创建,PUT 延长,DELETE 移除。/userDataStream 没有列出 GET 方法。
  295. # 暂时保留您提供的结构,但通常没有 GET 方法获取 listenKey。
  296. # 如果您指的是保持连接,那是 PUT。如果是创建,那是 POST。
  297. # 您的原始代码中 post_listenKey 和 get_listenKey 都是调用 sign_request 且不带参数。
  298. # 遵循方法名,保持为 GET。如果失败,可能需要改为 POST 或 PUT。
  299. # MEXC 官方文档显示 /userDataStream 支持 POST, PUT, DELETE。没有 GET。
  300. # 假设 get_listenKey 有其他意图或者是笔误。
  301. # 目前,如果 'get_listenKey' 是笔误并且意指 'post_listenKey',我会像 post_listenKey 一样将其设为 POST。
  302. # 但是,您的原始代码中有明确的 get_listenKey 方法,HTTP 方法为 'GET',让我们保留它。
  303. # 如果这是错误的,API 会返回错误。
  304. return self.client.sign_request('GET', url).json()
  305. def put_listenKey(self, params=None): # 延长 ListenKey 有效期 (params 如果不是必需的,则为可选)
  306. url = f'{self.API_PATH}/userDataStream'
  307. # 对于 PUT,params 通常包含 listenKey 本身。例如 params={"listenKey": "the_key"}
  308. return self.client.sign_request('PUT', url, params=params).json()
  309. def delete_listenKey(self, params): # 删除 ListenKey (params 必须包含 listenKey)
  310. url = f'{self.API_PATH}/userDataStream'
  311. # 对于 DELETE,params 通常包含 listenKey 本身。例如 params={"listenKey": "the_key"}
  312. return self.client.sign_request('DELETE', url, params=params).json()
  313. if __name__ == '__main__':
  314. from pprint import pprint
  315. # --- 重要提示 ---
  316. # 运行前,请确保已设置以下环境变量:
  317. # MEXC_API_HOST (例如:"https://api.mexc.com")
  318. # MEXC_API_KEY (您的真实 API key)
  319. # MEXC_SECRET_KEY (您的真实 secret key)
  320. #
  321. # 示例 (在终端中):
  322. # export MEXC_API_HOST="https://api.mexc.com"
  323. # export MEXC_API_KEY="your_mexc_api_key"
  324. # export MEXC_SECRET_KEY="your_mexc_secret_key"
  325. # python your_script_name.py
  326. print("尝试初始化 MexcClient...")
  327. try:
  328. client = MexcClient()
  329. print("MexcClient 初始化成功。")
  330. print(f"使用的 API Host: {client.hosts}")
  331. print(f"API Key 是否加载: {'是' if client.mexc_key else '否'}")
  332. except ValueError as e:
  333. print(f"初始化 MexcClient 错误: {e}")
  334. print("请设置 MEXC_API_HOST, MEXC_API_KEY, 和 MEXC_SECRET_KEY 环境变量。")
  335. exit()
  336. except Exception as e:
  337. print(f"初始化过程中发生意外错误: {e}")
  338. exit()
  339. # print("\n--- 测试公共接口 ---")
  340. # try:
  341. # print("Ping 服务器...")
  342. # ping_response = client.market.get_ping()
  343. # print(f"Ping 响应: {ping_response}")
  344. # print("\n获取服务器时间...")
  345. # time_response = client.market.get_timestamp()
  346. # print(f"服务器时间响应: {time_response}")
  347. # if isinstance(time_response, dict) and 'serverTime' in time_response:
  348. # print(f"服务器时间: {time_response['serverTime']}")
  349. # print("\n获取 BTCUSDT Ticker 价格...") # get_price 示例
  350. # price_response = client.market.get_price(params={"symbol": "BTCUSDT"})
  351. # print(f"BTCUSDT 价格响应: {price_response}")
  352. # except requests.exceptions.RequestException as e:
  353. # print(f" 公共 API 请求期间出错: {e}")
  354. # except Exception as e:
  355. # print(f" 发生意外错误: {e}")
  356. print("\n--- 测试私有接口 (需要 API Key 和 Secret) ---")
  357. if not client.mexc_key or not client.mexc_secret:
  358. print(" 在环境变量中未找到 API Key 或 Secret。跳过私有接口测试。")
  359. else:
  360. try:
  361. # print("获取账户信息...")
  362. # account_info = client.trade.get_account_info()
  363. # # print("账户信息:")
  364. # # # 打印敏感数据时请小心。测试时,显示有限信息或结构。
  365. # # if isinstance(account_info, dict):
  366. # # print(f" 是否可以交易: {account_info.get('canTrade')}")
  367. # # print(f" 是否可以提现: {account_info.get('canWithdraw')}")
  368. # # print(f" 账户类型: {account_info.get('accountType')}")
  369. # # # print(json.dumps(account_info, indent=2, ensure_ascii=False)) # 取消注释以查看完整响应
  370. # # else:
  371. # # print(f" 意外的响应格式: {account_info}")
  372. import time
  373. import logging
  374. # 配置日志
  375. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
  376. for balance in client.trade.get_account_info()['balances']:
  377. logging.info(balance)
  378. # params = {
  379. # 'coin': 'RATO',
  380. # 'netWork': 'ETH',
  381. # }
  382. # # print(client.wallet.post_deposit_address(params))
  383. # for deposit in client.wallet.get_deposit_address(params):
  384. # if deposit['netWork'] == 'ETH':
  385. # print(deposit)
  386. # 批量下单示例
  387. # print("\n测试批量下单 (POST /batchOrders)...")
  388. # sample_batch_orders = [
  389. # {
  390. # "symbol": "MXUSDT", "side": "BUY", "type": "LIMIT",
  391. # "quantity": "1", "price": "0.1"
  392. # },
  393. # {
  394. # "symbol": "MXUSDT", "side": "SELL", "type": "LIMIT",
  395. # "quantity": "1", "price": "10.0"
  396. # }
  397. # ]
  398. # try:
  399. # # 注意:此端点可能需要特定权限,或者可能不是测试端点。
  400. # # 请谨慎使用,如果可能,请在测试网上进行。
  401. # # /order/test 端点更适合测试订单创建逻辑。
  402. # # batch_response = client.trade.post_batchorders(sample_batch_orders)
  403. # # print(f"批量下单响应: {batch_response}")
  404. # print(" 此示例中跳过批量下单测试,以避免实际创建订单。")
  405. # except Exception as e_batch:
  406. # print(f" 批量下单请求期间出错: {e_batch}")
  407. # 测试获取挂单示例
  408. # print("\n获取 BTCUSDT 当前挂单 (如有)...")
  409. # open_orders = client.trade.get_openorders(params={"symbol": "RATOUSDT"})
  410. # if isinstance(open_orders, list):
  411. # print(f" 找到 {len(open_orders)} 个 BTCUSDT 的挂单。")
  412. # for order in open_orders:
  413. # print(f" - 订单 ID: {order.get('orderId')}, 价格: {order.get('price')}")
  414. # # print(client.trade.delete_order(params={"symbol": "RATOUSDT", "orderId": order.get('orderId')}))
  415. # else:
  416. # print(f" 挂单响应: {open_orders}")
  417. # coin_list = client.wallet.get_coinlist()
  418. # for coin in coin_list:
  419. # if 'USDT' in coin['coin']:
  420. # networklist = coin['networkList']
  421. # for network in networklist:
  422. # print(network)
  423. '''
  424. {
  425. 'coin': 'USDT',
  426. 'depositDesc': None,
  427. 'depositEnable': True,
  428. 'minConfirm': 96,
  429. 'name': 'Tether',
  430. 'network': 'Ethereum(ERC20)', <- 🤡这个字段不是哦,也不能填这个Ethereum(ERC20)
  431. 'withdrawEnable': True,
  432. 'withdrawFee': '0.5',
  433. 'withdrawIntegerMultiple': None,
  434. 'withdrawMax': '26000000',
  435. 'withdrawMin': '10',
  436. 'sameAddress': False,
  437. 'contract': '0xdac17f958d2ee523a2206206994597c13d831ec7',
  438. 'withdrawTips': None,
  439. 'depositTips': None,
  440. 'netWork': 'ETH' <- 🤡这个字段才是要的
  441. }
  442. '''
  443. # '''
  444. # 幣種信息獲取
  445. # [{'coin': 'STPT',
  446. # 'name': 'STP',
  447. # 'networkList': [{'coin': 'STPT',
  448. # 'contract': '0xDe7D85157d9714EADf595045CC12Ca4A5f3E2aDb',
  449. # 'depositDesc': 'Deposit suspended as project upgrade is in '
  450. # 'progress',
  451. # 'depositEnable': False,
  452. # 'depositTips': None,
  453. # 'minConfirm': 96,
  454. # 'name': 'STP',
  455. # 'netWork': 'NONE',
  456. # 'network': 'NONE',
  457. # 'sameAddress': False,
  458. # 'withdrawEnable': False,
  459. # 'withdrawFee': '0',
  460. # 'withdrawIntegerMultiple': None,
  461. # 'withdrawMax': '800000000',
  462. # 'withdrawMin': '800000000',
  463. # 'withdrawTips': None}]}]
  464. # '''
  465. # all_coin_list = client.wallet.get_coinlist()
  466. # pprint(all_coin_list[:2])
  467. # print("\n测试下单 (POST /order/test)...")
  468. # test_order_params = {
  469. # "symbol": "RATOUSDT", # 使用常见的交易对,如 MXUSDT 或 BTCUSDT
  470. # "side": "SELL", "quantity": "650000", # 以【幣】的數量進行買賣
  471. # # "side": "BUY", "quoteOrderQty": "13.7" # 以【U】的數量進行買賣!
  472. # "type": "MARKET",
  473. # # "price": "0.0000290" # 调整价格以便于测试
  474. # }
  475. # print(f" 测试订单参数: {test_order_params}")
  476. # test_order_response = client.trade.post_order(params=test_order_params)
  477. # print(f"测试订单响应: {test_order_response}")
  478. '''
  479. {'symbol': 'RATOUSDT', 'orderId': 'C02__554739165556662272055', 'orderListId': -1, 'clientOrderId': None, 'price': '0.00001259', 'origQty': '28000000', 'executedQty': '14714393.18', 'cummulativeQuoteQty': '186.6202641971',
  480. 'status': 'PARTIALLY_CANCELED', 'timeInForce': None, 'type': 'MARKET', 'side': 'SELL', 'stopPrice': None, 'icebergQty': None, 'time': 1748042359000, 'updateTime': 1748042360000, 'isWorking': True, 'origQuoteOrderQty': '0'}
  481. '''
  482. # params = {
  483. # "symbol": "RATOUSDT",
  484. # "orderId": "C02__554739165556662272055"
  485. # }
  486. # order = client.trade.get_order(params)
  487. # logging.info(order)
  488. # from config import wallet
  489. # print("\n提币测试...")
  490. # withdraw_params = {
  491. # 'coin': 'USDT',
  492. # 'netWork': 'ETH',
  493. # 'address': wallet["user_wallet"],
  494. # 'amount': 30
  495. # }
  496. # withdraw_rst = client.wallet.post_withdraw(withdraw_params)
  497. # print(f" 提幣响应:{withdraw_rst}")
  498. # print("\n获取提币历史信息...")
  499. # withdraw_list = client.wallet.get_withdraw_list()
  500. # print("历史信息:")
  501. # for withdraw in withdraw_list[0:20]:
  502. # print(f" {withdraw}")
  503. # print("\n充币历史信息,默认返回最近7天以内的")
  504. # params = {
  505. # "coin":"RATO",
  506. # # "startTime":"1747015200000",
  507. # # "endTime":"1747274400000"
  508. # }
  509. # deposit_list = client.wallet.get_deposit_list(params)
  510. # for deposit in deposit_list[0:5]:
  511. # print(f" {deposit}")
  512. '''
  513. {'exchangeFilters': [],
  514. 'rateLimits': [],
  515. 'serverTime': 1752648491330,
  516. 'symbols': [{'baseAsset': 'RATO',
  517. 'baseAssetPrecision': 2, // 交易币精度
  518. 'baseCommissionPrecision': 2,
  519. 'baseSizePrecision': '0',
  520. 'filters': [],
  521. 'fullName': 'Rato The Rat',
  522. 'isMarginTradingAllowed': False,
  523. 'isSpotTradingAllowed': True,
  524. 'makerCommission': '0',
  525. 'maxQuoteAmount': '2000000',
  526. 'maxQuoteAmountMarket': '100000',
  527. 'orderTypes': ['LIMIT', 'MARKET', 'LIMIT_MAKER'],
  528. 'permissions': ['SPOT'],
  529. 'quoteAmountPrecision': '1',
  530. 'quoteAmountPrecisionMarket': '1',
  531. 'quoteAsset': 'USDT',
  532. 'quoteAssetPrecision': 9, // 计价币精度
  533. 'quoteCommissionPrecision': 9,
  534. 'quotePrecision': 9, // 交易对价格精度
  535. 'st': False,
  536. 'status': '1',
  537. 'symbol': 'RATOUSDT',
  538. 'takerCommission': '0.0005',
  539. 'tradeSideType': 1}],
  540. 'timezone': 'CST'}
  541. '''
  542. # params = {
  543. # "symbols": "MANYUUSDT",
  544. # }
  545. # info = client.market.get_exchangeInfo(params)
  546. # pprint(info)
  547. except requests.exceptions.RequestException as e:
  548. print(f" 私有 API 请求期间出错: {e}")
  549. if e.response is not None:
  550. print(f" 响应状态码: {e.response.status_code}")
  551. print(f" 响应文本: {e.response.text}")
  552. except Exception as e:
  553. print(f" 发生意外错误: {e}")
  554. print("\n--- 所有测试完成 ---")