cyqnt-trd 0.1.2__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 (147) hide show
  1. cyqnt_trd/__init__.py +26 -0
  2. cyqnt_trd/backtesting/README.md +264 -0
  3. cyqnt_trd/backtesting/__init__.py +12 -0
  4. cyqnt_trd/backtesting/factor_test.py +332 -0
  5. cyqnt_trd/backtesting/framework.py +311 -0
  6. cyqnt_trd/backtesting/strategy_backtest.py +545 -0
  7. cyqnt_trd/diagnose_api.py +28 -0
  8. cyqnt_trd/get_data/__init__.py +15 -0
  9. cyqnt_trd/get_data/get_futures_data.py +472 -0
  10. cyqnt_trd/get_data/get_trending_data.py +771 -0
  11. cyqnt_trd/online_trading/__init__.py +13 -0
  12. cyqnt_trd/online_trading/realtime_price_tracker.py +1001 -0
  13. cyqnt_trd/test.py +119 -0
  14. cyqnt_trd/test_script/README.md +411 -0
  15. cyqnt_trd/test_script/get_network_info.py +192 -0
  16. cyqnt_trd/test_script/get_symbols_by_volume.py +227 -0
  17. cyqnt_trd/test_script/realtime_price_tracker.py +839 -0
  18. cyqnt_trd/test_script/test_alpha.py +261 -0
  19. cyqnt_trd/test_script/test_kline_data.py +479 -0
  20. cyqnt_trd/test_script/test_order.py +1360 -0
  21. cyqnt_trd/trading_signal/README.md +276 -0
  22. cyqnt_trd/trading_signal/__init__.py +17 -0
  23. cyqnt_trd/trading_signal/example_test_alpha.py +430 -0
  24. cyqnt_trd/trading_signal/example_usage.py +431 -0
  25. cyqnt_trd/trading_signal/factor/__init__.py +18 -0
  26. cyqnt_trd/trading_signal/factor/ma_factor.py +75 -0
  27. cyqnt_trd/trading_signal/factor/rsi_factor.py +56 -0
  28. cyqnt_trd/trading_signal/selected_alpha/__init__.py +158 -0
  29. cyqnt_trd/trading_signal/selected_alpha/alpha1.py +87 -0
  30. cyqnt_trd/trading_signal/selected_alpha/alpha10.py +90 -0
  31. cyqnt_trd/trading_signal/selected_alpha/alpha100.py +74 -0
  32. cyqnt_trd/trading_signal/selected_alpha/alpha101.py +86 -0
  33. cyqnt_trd/trading_signal/selected_alpha/alpha11.py +86 -0
  34. cyqnt_trd/trading_signal/selected_alpha/alpha12.py +86 -0
  35. cyqnt_trd/trading_signal/selected_alpha/alpha13.py +86 -0
  36. cyqnt_trd/trading_signal/selected_alpha/alpha14.py +87 -0
  37. cyqnt_trd/trading_signal/selected_alpha/alpha15.py +87 -0
  38. cyqnt_trd/trading_signal/selected_alpha/alpha16.py +86 -0
  39. cyqnt_trd/trading_signal/selected_alpha/alpha17.py +88 -0
  40. cyqnt_trd/trading_signal/selected_alpha/alpha18.py +88 -0
  41. cyqnt_trd/trading_signal/selected_alpha/alpha19.py +87 -0
  42. cyqnt_trd/trading_signal/selected_alpha/alpha2.py +86 -0
  43. cyqnt_trd/trading_signal/selected_alpha/alpha20.py +88 -0
  44. cyqnt_trd/trading_signal/selected_alpha/alpha21.py +89 -0
  45. cyqnt_trd/trading_signal/selected_alpha/alpha22.py +87 -0
  46. cyqnt_trd/trading_signal/selected_alpha/alpha23.py +88 -0
  47. cyqnt_trd/trading_signal/selected_alpha/alpha24.py +88 -0
  48. cyqnt_trd/trading_signal/selected_alpha/alpha25.py +86 -0
  49. cyqnt_trd/trading_signal/selected_alpha/alpha26.py +87 -0
  50. cyqnt_trd/trading_signal/selected_alpha/alpha27.py +88 -0
  51. cyqnt_trd/trading_signal/selected_alpha/alpha28.py +88 -0
  52. cyqnt_trd/trading_signal/selected_alpha/alpha29.py +87 -0
  53. cyqnt_trd/trading_signal/selected_alpha/alpha3.py +86 -0
  54. cyqnt_trd/trading_signal/selected_alpha/alpha30.py +87 -0
  55. cyqnt_trd/trading_signal/selected_alpha/alpha31.py +90 -0
  56. cyqnt_trd/trading_signal/selected_alpha/alpha32.py +86 -0
  57. cyqnt_trd/trading_signal/selected_alpha/alpha33.py +86 -0
  58. cyqnt_trd/trading_signal/selected_alpha/alpha34.py +87 -0
  59. cyqnt_trd/trading_signal/selected_alpha/alpha35.py +88 -0
  60. cyqnt_trd/trading_signal/selected_alpha/alpha36.py +86 -0
  61. cyqnt_trd/trading_signal/selected_alpha/alpha37.py +86 -0
  62. cyqnt_trd/trading_signal/selected_alpha/alpha38.py +87 -0
  63. cyqnt_trd/trading_signal/selected_alpha/alpha39.py +87 -0
  64. cyqnt_trd/trading_signal/selected_alpha/alpha4.py +86 -0
  65. cyqnt_trd/trading_signal/selected_alpha/alpha40.py +86 -0
  66. cyqnt_trd/trading_signal/selected_alpha/alpha41.py +86 -0
  67. cyqnt_trd/trading_signal/selected_alpha/alpha42.py +86 -0
  68. cyqnt_trd/trading_signal/selected_alpha/alpha43.py +86 -0
  69. cyqnt_trd/trading_signal/selected_alpha/alpha44.py +87 -0
  70. cyqnt_trd/trading_signal/selected_alpha/alpha45.py +88 -0
  71. cyqnt_trd/trading_signal/selected_alpha/alpha46.py +89 -0
  72. cyqnt_trd/trading_signal/selected_alpha/alpha47.py +86 -0
  73. cyqnt_trd/trading_signal/selected_alpha/alpha48.py +74 -0
  74. cyqnt_trd/trading_signal/selected_alpha/alpha49.py +88 -0
  75. cyqnt_trd/trading_signal/selected_alpha/alpha5.py +86 -0
  76. cyqnt_trd/trading_signal/selected_alpha/alpha50.py +86 -0
  77. cyqnt_trd/trading_signal/selected_alpha/alpha51.py +88 -0
  78. cyqnt_trd/trading_signal/selected_alpha/alpha52.py +87 -0
  79. cyqnt_trd/trading_signal/selected_alpha/alpha53.py +86 -0
  80. cyqnt_trd/trading_signal/selected_alpha/alpha54.py +86 -0
  81. cyqnt_trd/trading_signal/selected_alpha/alpha55.py +88 -0
  82. cyqnt_trd/trading_signal/selected_alpha/alpha56.py +86 -0
  83. cyqnt_trd/trading_signal/selected_alpha/alpha57.py +86 -0
  84. cyqnt_trd/trading_signal/selected_alpha/alpha58.py +74 -0
  85. cyqnt_trd/trading_signal/selected_alpha/alpha59.py +74 -0
  86. cyqnt_trd/trading_signal/selected_alpha/alpha6.py +86 -0
  87. cyqnt_trd/trading_signal/selected_alpha/alpha60.py +89 -0
  88. cyqnt_trd/trading_signal/selected_alpha/alpha61.py +88 -0
  89. cyqnt_trd/trading_signal/selected_alpha/alpha62.py +86 -0
  90. cyqnt_trd/trading_signal/selected_alpha/alpha63.py +74 -0
  91. cyqnt_trd/trading_signal/selected_alpha/alpha64.py +86 -0
  92. cyqnt_trd/trading_signal/selected_alpha/alpha65.py +86 -0
  93. cyqnt_trd/trading_signal/selected_alpha/alpha66.py +86 -0
  94. cyqnt_trd/trading_signal/selected_alpha/alpha67.py +74 -0
  95. cyqnt_trd/trading_signal/selected_alpha/alpha68.py +86 -0
  96. cyqnt_trd/trading_signal/selected_alpha/alpha69.py +74 -0
  97. cyqnt_trd/trading_signal/selected_alpha/alpha7.py +88 -0
  98. cyqnt_trd/trading_signal/selected_alpha/alpha70.py +74 -0
  99. cyqnt_trd/trading_signal/selected_alpha/alpha71.py +92 -0
  100. cyqnt_trd/trading_signal/selected_alpha/alpha72.py +86 -0
  101. cyqnt_trd/trading_signal/selected_alpha/alpha73.py +91 -0
  102. cyqnt_trd/trading_signal/selected_alpha/alpha74.py +86 -0
  103. cyqnt_trd/trading_signal/selected_alpha/alpha75.py +86 -0
  104. cyqnt_trd/trading_signal/selected_alpha/alpha76.py +74 -0
  105. cyqnt_trd/trading_signal/selected_alpha/alpha77.py +92 -0
  106. cyqnt_trd/trading_signal/selected_alpha/alpha78.py +86 -0
  107. cyqnt_trd/trading_signal/selected_alpha/alpha79.py +74 -0
  108. cyqnt_trd/trading_signal/selected_alpha/alpha8.py +87 -0
  109. cyqnt_trd/trading_signal/selected_alpha/alpha80.py +74 -0
  110. cyqnt_trd/trading_signal/selected_alpha/alpha81.py +86 -0
  111. cyqnt_trd/trading_signal/selected_alpha/alpha82.py +74 -0
  112. cyqnt_trd/trading_signal/selected_alpha/alpha83.py +86 -0
  113. cyqnt_trd/trading_signal/selected_alpha/alpha84.py +86 -0
  114. cyqnt_trd/trading_signal/selected_alpha/alpha85.py +86 -0
  115. cyqnt_trd/trading_signal/selected_alpha/alpha86.py +86 -0
  116. cyqnt_trd/trading_signal/selected_alpha/alpha87.py +74 -0
  117. cyqnt_trd/trading_signal/selected_alpha/alpha88.py +92 -0
  118. cyqnt_trd/trading_signal/selected_alpha/alpha89.py +74 -0
  119. cyqnt_trd/trading_signal/selected_alpha/alpha9.py +90 -0
  120. cyqnt_trd/trading_signal/selected_alpha/alpha90.py +74 -0
  121. cyqnt_trd/trading_signal/selected_alpha/alpha91.py +74 -0
  122. cyqnt_trd/trading_signal/selected_alpha/alpha92.py +92 -0
  123. cyqnt_trd/trading_signal/selected_alpha/alpha93.py +74 -0
  124. cyqnt_trd/trading_signal/selected_alpha/alpha94.py +86 -0
  125. cyqnt_trd/trading_signal/selected_alpha/alpha95.py +86 -0
  126. cyqnt_trd/trading_signal/selected_alpha/alpha96.py +92 -0
  127. cyqnt_trd/trading_signal/selected_alpha/alpha97.py +74 -0
  128. cyqnt_trd/trading_signal/selected_alpha/alpha98.py +87 -0
  129. cyqnt_trd/trading_signal/selected_alpha/alpha99.py +86 -0
  130. cyqnt_trd/trading_signal/selected_alpha/alpha_utils.py +342 -0
  131. cyqnt_trd/trading_signal/selected_alpha/create_all_alphas.py +279 -0
  132. cyqnt_trd/trading_signal/selected_alpha/generate_alphas.py +133 -0
  133. cyqnt_trd/trading_signal/selected_alpha/test_alpha.py +261 -0
  134. cyqnt_trd/trading_signal/signal/__init__.py +20 -0
  135. cyqnt_trd/trading_signal/signal/factor_based_signal.py +387 -0
  136. cyqnt_trd/trading_signal/signal/ma_signal.py +163 -0
  137. cyqnt_trd/utils/__init__.py +3 -0
  138. cyqnt_trd/utils/set_user.py +33 -0
  139. cyqnt_trd-0.1.2.dist-info/METADATA +148 -0
  140. cyqnt_trd-0.1.2.dist-info/RECORD +147 -0
  141. cyqnt_trd-0.1.2.dist-info/WHEEL +5 -0
  142. cyqnt_trd-0.1.2.dist-info/licenses/LICENSE +21 -0
  143. cyqnt_trd-0.1.2.dist-info/top_level.txt +2 -0
  144. test/real_time_trade.py +746 -0
  145. test/test_example_usage.py +381 -0
  146. test/test_get_data.py +310 -0
  147. test/test_realtime_price_tracker.py +546 -0
@@ -0,0 +1,1360 @@
1
+ import os
2
+ import logging
3
+ from typing import Optional, Dict, Any, Tuple
4
+ from datetime import datetime
5
+
6
+ from binance_sdk_spot.spot import Spot, ConfigurationRestAPI, SPOT_REST_API_PROD_URL
7
+ from binance_sdk_spot.rest_api.models import NewOrderSideEnum, NewOrderTypeEnum, NewOrderTimeInForceEnum
8
+
9
+ from binance_sdk_derivatives_trading_usds_futures.derivatives_trading_usds_futures import (
10
+ DerivativesTradingUsdsFutures,
11
+ ConfigurationRestAPI as FuturesConfigurationRestAPI,
12
+ DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL,
13
+ )
14
+ from binance_sdk_derivatives_trading_usds_futures.rest_api.models import (
15
+ NewOrderSideEnum as FuturesNewOrderSideEnum,
16
+ NewOrderTimeInForceEnum as FuturesNewOrderTimeInForceEnum,
17
+ )
18
+
19
+ from cyqnt_trd.utils.set_user import set_user
20
+
21
+ # Configure logging
22
+ logging.basicConfig(
23
+ level=logging.INFO,
24
+ format='%(asctime)s - %(levelname)s - %(message)s'
25
+ )
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # 默认API密钥(如果环境变量未设置则使用)
29
+ DEFAULT_API_KEY = os.getenv("API_KEY", "KB6hxLqPAvkV8DBJq6xY1tnyXR7bLxPbCQMX6zjUMwQbrujdfKlShgJ9uGQqPsrn")
30
+ DEFAULT_API_SECRET = os.getenv("API_SECRET", "Gv7l5ht1nyfl3Npw4q4zaT4FWPGCAOiSw8EldeSTXdQUQrsxLlE22Yi5ttoj9eaD")
31
+
32
+
33
+ def get_spot_balance(asset: Optional[str] = None) -> Dict[str, Any]:
34
+ """
35
+ 获取现货账户余额
36
+
37
+ Args:
38
+ asset: 资产名称,如 "BTC", "USDT",如果为 None 则返回所有资产
39
+
40
+ Returns:
41
+ 余额信息字典
42
+ """
43
+ try:
44
+ spot_config = ConfigurationRestAPI(
45
+ api_key=DEFAULT_API_KEY,
46
+ api_secret=DEFAULT_API_SECRET,
47
+ base_path=os.getenv("BASE_PATH", SPOT_REST_API_PROD_URL),
48
+ )
49
+
50
+ spot_client = Spot(config_rest_api=spot_config)
51
+
52
+ response = spot_client.rest_api.get_account(omit_zero_balances=False)
53
+ data = response.data()
54
+
55
+ # 处理响应数据
56
+ if hasattr(data, 'model_dump'):
57
+ account_data = data.model_dump(by_alias=True)
58
+ elif hasattr(data, 'dict'):
59
+ account_data = data.dict(by_alias=True)
60
+ elif isinstance(data, dict):
61
+ account_data = data
62
+ else:
63
+ account_data = {"raw_data": str(data)}
64
+
65
+ # 提取余额信息
66
+ balances = {}
67
+ if 'balances' in account_data:
68
+ for balance in account_data['balances']:
69
+ asset_name = balance.get('asset', '')
70
+ free = float(balance.get('free', 0))
71
+ locked = float(balance.get('locked', 0))
72
+ total = free + locked
73
+
74
+ if asset is None or asset_name == asset:
75
+ balances[asset_name] = {
76
+ 'free': free,
77
+ 'locked': locked,
78
+ 'total': total
79
+ }
80
+
81
+ return {
82
+ "success": True,
83
+ "balances": balances if asset is None else balances.get(asset, {}),
84
+ "account_data": account_data
85
+ }
86
+
87
+ except Exception as e:
88
+ logger.error(f"获取现货余额失败: {e}")
89
+ import traceback
90
+ logger.error(traceback.format_exc())
91
+ return {
92
+ "success": False,
93
+ "error": str(e)
94
+ }
95
+
96
+
97
+ def get_futures_balance(asset: Optional[str] = None) -> Dict[str, Any]:
98
+ """
99
+ 获取合约账户余额
100
+
101
+ Args:
102
+ asset: 资产名称,如 "USDT",如果为 None 则返回所有资产
103
+
104
+ Returns:
105
+ 余额信息字典
106
+ """
107
+ try:
108
+ # 使用 set_user() 函数获取配置,确保使用正确的 API 密钥
109
+ futures_config = set_user(
110
+ api_key=DEFAULT_API_KEY,
111
+ api_secret=DEFAULT_API_SECRET,
112
+ )
113
+
114
+ futures_client = DerivativesTradingUsdsFutures(config_rest_api=futures_config)
115
+
116
+ response = futures_client.rest_api.futures_account_balance_v2()
117
+ data = response.data()
118
+
119
+ # 处理响应数据 - 合约余额API返回的是列表
120
+ balance_list = []
121
+ if isinstance(data, list):
122
+ # 如果是列表,遍历每个元素并转换为字典
123
+ for item in data:
124
+ if hasattr(item, 'model_dump'):
125
+ balance_list.append(item.model_dump(by_alias=True))
126
+ elif hasattr(item, 'dict'):
127
+ balance_list.append(item.dict(by_alias=True))
128
+ elif isinstance(item, dict):
129
+ balance_list.append(item)
130
+ else:
131
+ # 如果是Pydantic模型,尝试直接访问属性
132
+ balance_dict = {}
133
+ if hasattr(item, 'asset'):
134
+ balance_dict['asset'] = item.asset
135
+ if hasattr(item, 'balance'):
136
+ balance_dict['balance'] = item.balance
137
+ if hasattr(item, 'available_balance'):
138
+ balance_dict['availableBalance'] = item.available_balance
139
+ elif hasattr(item, 'availableBalance'):
140
+ balance_dict['availableBalance'] = item.availableBalance
141
+ balance_list.append(balance_dict)
142
+ elif isinstance(data, dict):
143
+ balance_list = [data]
144
+ else:
145
+ # 单个对象
146
+ if hasattr(data, 'model_dump'):
147
+ balance_list = [data.model_dump(by_alias=True)]
148
+ elif hasattr(data, 'dict'):
149
+ balance_list = [data.dict(by_alias=True)]
150
+ else:
151
+ balance_list = [{"raw_data": str(data)}]
152
+
153
+ # 提取余额信息
154
+ balances = {}
155
+ for balance in balance_list:
156
+ # 尝试多种方式获取asset字段
157
+ if isinstance(balance, dict):
158
+ asset_name = balance.get('asset') or balance.get('Asset') or ''
159
+ else:
160
+ asset_name = getattr(balance, 'asset', None) or getattr(balance, 'Asset', None) or ''
161
+
162
+ if not asset_name:
163
+ continue # 跳过没有资产名称的项
164
+
165
+ # 尝试多种方式获取balance字段
166
+ if isinstance(balance, dict):
167
+ balance_amt = (
168
+ float(balance.get('balance', 0)) if isinstance(balance.get('balance'), (str, int, float)) else
169
+ float(balance.get('Balance', 0)) if isinstance(balance.get('Balance'), (str, int, float)) else
170
+ 0.0
171
+ )
172
+ else:
173
+ balance_amt = float(getattr(balance, 'balance', 0) or getattr(balance, 'Balance', 0) or 0)
174
+
175
+ # 尝试多种方式获取available_balance字段
176
+ if isinstance(balance, dict):
177
+ available = (
178
+ float(balance.get('availableBalance', balance_amt)) if isinstance(balance.get('availableBalance'), (str, int, float)) else
179
+ float(balance.get('available_balance', balance_amt)) if isinstance(balance.get('available_balance'), (str, int, float)) else
180
+ float(balance.get('AvailableBalance', balance_amt)) if isinstance(balance.get('AvailableBalance'), (str, int, float)) else
181
+ balance_amt
182
+ )
183
+ else:
184
+ available = float(
185
+ getattr(balance, 'availableBalance', None) or
186
+ getattr(balance, 'available_balance', None) or
187
+ getattr(balance, 'AvailableBalance', None) or
188
+ balance_amt
189
+ )
190
+
191
+ if asset_name: # 只处理有效的资产名称
192
+ if asset is None or asset_name == asset:
193
+ balances[asset_name] = {
194
+ 'balance': balance_amt,
195
+ 'available': available
196
+ }
197
+
198
+ return {
199
+ "success": True,
200
+ "balances": balances if asset is None else balances.get(asset, {}),
201
+ "balance_data": balance_list
202
+ }
203
+
204
+ except Exception as e:
205
+ logger.error(f"获取合约余额失败: {e}")
206
+ import traceback
207
+ logger.error(traceback.format_exc())
208
+ return {
209
+ "success": False,
210
+ "error": str(e)
211
+ }
212
+
213
+
214
+ def check_spot_balance(symbol: str, side: str, quantity: float, price: Optional[float] = None) -> Tuple[bool, str, float]:
215
+ """
216
+ 检查现货账户余额是否足够
217
+
218
+ Args:
219
+ symbol: 交易对,如 "BTCUSDT"
220
+ side: 买卖方向,"BUY" 或 "SELL"
221
+ quantity: 数量
222
+ price: 价格(限价单需要)
223
+
224
+ Returns:
225
+ (是否足够, 错误信息, 可用余额)
226
+ """
227
+ # 从交易对中提取基础货币和报价货币
228
+ # 常见报价货币列表(按长度从长到短排序,避免误匹配)
229
+ quote_assets = ["USDT", "BUSD", "USDC", "BTC", "ETH", "BNB", "DAI", "PAX", "TUSD"]
230
+
231
+ base_asset = ""
232
+ quote_asset = "USDT" # 默认报价货币
233
+
234
+ # 尝试匹配报价货币
235
+ for quote in quote_assets:
236
+ if symbol.endswith(quote):
237
+ base_asset = symbol[:-len(quote)]
238
+ quote_asset = quote
239
+ break
240
+
241
+ # 如果没匹配到,尝试获取交易对信息(需要API调用,这里简化处理)
242
+ if not base_asset:
243
+ # 假设是USDT交易对
244
+ base_asset = symbol.replace("USDT", "")
245
+ quote_asset = "USDT"
246
+
247
+ balance_result = get_spot_balance()
248
+
249
+ if not balance_result.get("success"):
250
+ return False, f"无法获取账户余额: {balance_result.get('error')}", 0.0
251
+
252
+ balances = balance_result.get("balances", {})
253
+
254
+ if side.upper() == "BUY":
255
+ # 买入需要报价货币(如USDT)
256
+ required = quantity * (price if price else 1.0) # 限价单用价格,市价单用1.0(实际会更多)
257
+ available = balances.get(quote_asset, {}).get('free', 0.0)
258
+
259
+ if available < required:
260
+ return False, f"余额不足: 需要 {required} {quote_asset}, 可用 {available} {quote_asset}", available
261
+ return True, "", available
262
+
263
+ else: # SELL
264
+ # 卖出需要基础货币(如BTC)
265
+ available = balances.get(base_asset, {}).get('free', 0.0)
266
+
267
+ if available < quantity:
268
+ return False, f"余额不足: 需要 {quantity} {base_asset}, 可用 {available} {base_asset}", available
269
+ return True, "", available
270
+
271
+
272
+ def get_futures_position_mode() -> Dict[str, Any]:
273
+ """
274
+ 获取合约账户的持仓模式
275
+
276
+ Returns:
277
+ 持仓模式信息字典,包含 dualSidePosition (True表示对冲模式,False表示单向模式)
278
+ """
279
+ try:
280
+ # 使用 set_user() 函数获取配置,确保使用正确的 API 密钥
281
+ futures_config = set_user(
282
+ api_key=DEFAULT_API_KEY,
283
+ api_secret=DEFAULT_API_SECRET,
284
+ )
285
+
286
+ futures_client = DerivativesTradingUsdsFutures(config_rest_api=futures_config)
287
+
288
+ response = futures_client.rest_api.get_current_position_mode()
289
+ data = response.data()
290
+
291
+ # 处理响应数据
292
+ if hasattr(data, 'model_dump'):
293
+ mode_data = data.model_dump(by_alias=True)
294
+ elif hasattr(data, 'dict'):
295
+ mode_data = data.dict(by_alias=True)
296
+ elif isinstance(data, dict):
297
+ mode_data = data
298
+ else:
299
+ mode_data = {"raw_data": str(data)}
300
+
301
+ # 提取持仓模式
302
+ dual_side = mode_data.get('dualSidePosition', False)
303
+ if isinstance(dual_side, str):
304
+ dual_side = dual_side.lower() in ['true', '1', 'yes']
305
+
306
+ return {
307
+ "success": True,
308
+ "dual_side_position": bool(dual_side),
309
+ "mode": "Hedge Mode" if dual_side else "One-way Mode",
310
+ "mode_data": mode_data
311
+ }
312
+
313
+ except Exception as e:
314
+ logger.error(f"获取持仓模式失败: {e}")
315
+ import traceback
316
+ logger.error(traceback.format_exc())
317
+ return {
318
+ "success": False,
319
+ "error": str(e),
320
+ "dual_side_position": False, # 默认假设是单向模式
321
+ "mode": "One-way Mode"
322
+ }
323
+
324
+
325
+ def check_futures_balance(symbol: str, side: str, quantity: float, price: Optional[float] = None) -> Tuple[bool, str, float]:
326
+ """
327
+ 检查合约账户余额是否足够
328
+
329
+ Args:
330
+ symbol: 交易对,如 "BTCUSDT"
331
+ side: 买卖方向,"BUY" 或 "SELL"
332
+ quantity: 数量
333
+ price: 价格(限价单需要,用于计算所需保证金)
334
+
335
+ Returns:
336
+ (是否足够, 错误信息, 可用余额)
337
+ """
338
+ # 合约通常使用USDT作为保证金
339
+ balance_result = get_futures_balance("USDT")
340
+
341
+ if not balance_result.get("success"):
342
+ return False, f"无法获取账户余额: {balance_result.get('error')}", 0.0
343
+
344
+ balance_info = balance_result.get("balances", {})
345
+ if not balance_info:
346
+ return False, "未找到USDT余额", 0.0
347
+
348
+ available = balance_info.get('available', 0.0)
349
+
350
+ # 简单检查:合约需要保证金,这里简化处理
351
+ # 实际应该根据杠杆和价格计算所需保证金
352
+ estimated_margin = quantity * (price if price else 1.0) * 0.1 # 假设10倍杠杆,需要10%保证金
353
+
354
+ if available < estimated_margin:
355
+ return False, f"余额可能不足: 估算需要 {estimated_margin} USDT 保证金, 可用 {available} USDT", available
356
+
357
+ return True, "", available
358
+
359
+
360
+ def test_spot_order(
361
+ symbol: str,
362
+ side: str,
363
+ order_type: str = "MARKET",
364
+ quantity: Optional[float] = None,
365
+ price: Optional[float] = None,
366
+ time_in_force: str = "GTC"
367
+ ) -> Dict[str, Any]:
368
+ """
369
+ 测试现货下单接口
370
+
371
+ Args:
372
+ symbol: 交易对,如 "BTCUSDT"
373
+ side: 买卖方向,"BUY" 或 "SELL"
374
+ order_type: 订单类型,"MARKET" 或 "LIMIT",默认 "MARKET"
375
+ quantity: 数量(对于 MARKET 订单,可以使用 quantity 或 quoteOrderQty)
376
+ price: 价格(LIMIT 订单必需)
377
+ time_in_force: 有效期,"GTC", "IOC", "FOK",默认 "GTC"
378
+
379
+ Returns:
380
+ 订单响应数据字典
381
+ """
382
+ try:
383
+ # 创建现货客户端配置
384
+ spot_config = ConfigurationRestAPI(
385
+ api_key=DEFAULT_API_KEY,
386
+ api_secret=DEFAULT_API_SECRET,
387
+ base_path=os.getenv("BASE_PATH", SPOT_REST_API_PROD_URL),
388
+ )
389
+
390
+ # 初始化现货客户端
391
+ spot_client = Spot(config_rest_api=spot_config)
392
+
393
+ # 验证参数
394
+ if side.upper() not in ["BUY", "SELL"]:
395
+ raise ValueError(f"Invalid side: {side}. Must be 'BUY' or 'SELL'")
396
+
397
+ if order_type.upper() == "LIMIT" and price is None:
398
+ raise ValueError("Price is required for LIMIT orders")
399
+
400
+ if order_type.upper() == "MARKET" and quantity is None:
401
+ raise ValueError("Quantity is required for MARKET orders")
402
+
403
+ # 检查余额
404
+ logger.info("正在检查账户余额...")
405
+ has_balance, balance_msg, available = check_spot_balance(symbol, side, quantity, price)
406
+ if not has_balance:
407
+ logger.warning(f"余额检查失败: {balance_msg}")
408
+ logger.warning(f"可用余额: {available}")
409
+ # 不直接返回错误,让用户决定是否继续
410
+ # return {
411
+ # "success": False,
412
+ # "error": balance_msg,
413
+ # "available_balance": available
414
+ # }
415
+ else:
416
+ logger.info(f"余额检查通过,可用余额: {available}")
417
+
418
+ # 准备下单参数
419
+ order_params = {
420
+ "symbol": symbol,
421
+ "side": NewOrderSideEnum[side.upper()].value,
422
+ "type": NewOrderTypeEnum[order_type.upper()].value,
423
+ }
424
+
425
+ # 添加数量
426
+ if quantity is not None:
427
+ order_params["quantity"] = quantity
428
+
429
+ # 添加价格(LIMIT 订单)
430
+ if order_type.upper() == "LIMIT" and price is not None:
431
+ order_params["price"] = price
432
+ order_params["time_in_force"] = NewOrderTimeInForceEnum[time_in_force.upper()].value
433
+
434
+ logger.info(f"正在下单 - 现货 {side} {order_type} {symbol}")
435
+ logger.info(f"订单参数: {order_params}")
436
+
437
+ # 下单
438
+ response = spot_client.rest_api.new_order(**order_params)
439
+
440
+ # 获取响应数据
441
+ rate_limits = response.rate_limits
442
+ data = response.data()
443
+
444
+ logger.info(f"下单成功 - Rate Limits: {rate_limits}")
445
+
446
+ # 处理响应数据
447
+ if hasattr(data, 'model_dump'):
448
+ order_result = data.model_dump(by_alias=True)
449
+ elif hasattr(data, 'dict'):
450
+ order_result = data.dict(by_alias=True)
451
+ elif isinstance(data, dict):
452
+ order_result = data
453
+ else:
454
+ order_result = {"raw_data": str(data)}
455
+
456
+ logger.info(f"订单响应: {order_result}")
457
+
458
+ return {
459
+ "success": True,
460
+ "rate_limits": rate_limits,
461
+ "order": order_result
462
+ }
463
+
464
+ except Exception as e:
465
+ logger.error(f"现货下单失败: {e}")
466
+ import traceback
467
+ logger.error(traceback.format_exc())
468
+ return {
469
+ "success": False,
470
+ "error": str(e),
471
+ "traceback": traceback.format_exc()
472
+ }
473
+
474
+
475
+ def test_futures_order(
476
+ symbol: str,
477
+ side: str,
478
+ order_type: str = "MARKET",
479
+ quantity: Optional[float] = None,
480
+ price: Optional[float] = None,
481
+ time_in_force: str = "GTC",
482
+ position_side: Optional[str] = None,
483
+ reduce_only: Optional[str] = None
484
+ ) -> Dict[str, Any]:
485
+ """
486
+ 测试合约下单接口
487
+
488
+ Args:
489
+ symbol: 交易对,如 "BTCUSDT"
490
+ side: 买卖方向,"BUY" 或 "SELL"
491
+ order_type: 订单类型,"MARKET" 或 "LIMIT",默认 "MARKET"
492
+ quantity: 数量
493
+ price: 价格(LIMIT 订单必需)
494
+ time_in_force: 有效期,"GTC", "IOC", "FOK",默认 "GTC"
495
+ position_side: 持仓方向,"LONG", "SHORT", "BOTH"(仅对冲模式)
496
+ reduce_only: 是否只减仓,"true" 或 "false"
497
+
498
+ Returns:
499
+ 订单响应数据字典
500
+ """
501
+ try:
502
+ # 创建合约客户端配置
503
+ # 使用 set_user() 函数获取配置,确保使用正确的 API 密钥
504
+ futures_config = set_user(
505
+ api_key=DEFAULT_API_KEY,
506
+ api_secret=DEFAULT_API_SECRET,
507
+ )
508
+
509
+ # 初始化合约客户端
510
+ futures_client = DerivativesTradingUsdsFutures(config_rest_api=futures_config)
511
+
512
+ # 验证参数
513
+ if side.upper() not in ["BUY", "SELL"]:
514
+ raise ValueError(f"Invalid side: {side}. Must be 'BUY' or 'SELL'")
515
+
516
+ if order_type.upper() == "LIMIT" and price is None:
517
+ raise ValueError("Price is required for LIMIT orders")
518
+
519
+ if quantity is None:
520
+ raise ValueError("Quantity is required")
521
+
522
+ # 检查余额
523
+ logger.info("正在检查账户余额...")
524
+ has_balance, balance_msg, available = check_futures_balance(symbol, side, quantity, price)
525
+ if not has_balance:
526
+ logger.warning(f"余额检查失败: {balance_msg}")
527
+ logger.warning(f"可用余额: {available}")
528
+ # 不直接返回错误,让用户决定是否继续
529
+ # return {
530
+ # "success": False,
531
+ # "error": balance_msg,
532
+ # "available_balance": available
533
+ # }
534
+ else:
535
+ logger.info(f"余额检查通过,可用余额: {available}")
536
+
537
+ # 检查持仓模式
538
+ logger.info("正在检查持仓模式...")
539
+ position_mode_result = get_futures_position_mode()
540
+ if position_mode_result.get("success"):
541
+ is_hedge_mode = position_mode_result.get("dual_side_position", False)
542
+ mode_name = position_mode_result.get("mode", "Unknown")
543
+ logger.info(f"当前持仓模式: {mode_name}")
544
+
545
+ # 如果是对冲模式且未指定position_side,自动设置
546
+ if is_hedge_mode and position_side is None:
547
+ # 对冲模式下,BUY对应LONG,SELL对应SHORT
548
+ auto_position_side = "LONG" if side.upper() == "BUY" else "SHORT"
549
+ logger.info(f"对冲模式自动设置 position_side: {auto_position_side}")
550
+ position_side = auto_position_side
551
+ else:
552
+ logger.warning(f"无法获取持仓模式: {position_mode_result.get('error')}")
553
+ logger.warning("假设为单向模式,如果失败请手动指定 position_side")
554
+
555
+ # 准备下单参数
556
+ order_params = {
557
+ "symbol": symbol,
558
+ "side": FuturesNewOrderSideEnum[side.upper()].value,
559
+ "type": order_type.upper(), # 合约使用字符串类型
560
+ }
561
+
562
+ # 添加数量
563
+ order_params["quantity"] = quantity
564
+
565
+ # 添加价格(LIMIT 订单)
566
+ if order_type.upper() == "LIMIT" and price is not None:
567
+ order_params["price"] = price
568
+ order_params["time_in_force"] = FuturesNewOrderTimeInForceEnum[time_in_force.upper()].value
569
+
570
+ # 添加持仓方向(对冲模式必需)
571
+ if position_side is not None:
572
+ from binance_sdk_derivatives_trading_usds_futures.rest_api.models import NewOrderPositionSideEnum
573
+ order_params["position_side"] = NewOrderPositionSideEnum[position_side.upper()].value
574
+
575
+ # 添加只减仓标志(可选)
576
+ if reduce_only is not None:
577
+ order_params["reduce_only"] = reduce_only
578
+
579
+ logger.info(f"正在下单 - 合约 {side} {order_type} {symbol}")
580
+ logger.info(f"订单参数: {order_params}")
581
+
582
+ # 下单
583
+ response = futures_client.rest_api.new_order(**order_params)
584
+
585
+ # 获取响应数据
586
+ rate_limits = response.rate_limits
587
+ data = response.data()
588
+
589
+ logger.info(f"下单成功 - Rate Limits: {rate_limits}")
590
+
591
+ # 处理响应数据
592
+ if hasattr(data, 'model_dump'):
593
+ order_result = data.model_dump(by_alias=True)
594
+ elif hasattr(data, 'dict'):
595
+ order_result = data.dict(by_alias=True)
596
+ elif isinstance(data, dict):
597
+ order_result = data
598
+ else:
599
+ order_result = {"raw_data": str(data)}
600
+
601
+ logger.info(f"订单响应: {order_result}")
602
+
603
+ return {
604
+ "success": True,
605
+ "rate_limits": rate_limits,
606
+ "order": order_result
607
+ }
608
+
609
+ except Exception as e:
610
+ logger.error(f"合约下单失败: {e}")
611
+ import traceback
612
+ logger.error(traceback.format_exc())
613
+ return {
614
+ "success": False,
615
+ "error": str(e),
616
+ "traceback": traceback.format_exc()
617
+ }
618
+
619
+
620
+ def test_spot_buy_market(symbol: str, quantity: float) -> Dict[str, Any]:
621
+ """测试现货市价买入"""
622
+ logger.info("=" * 80)
623
+ logger.info("测试现货市价买入")
624
+ logger.info("=" * 80)
625
+ return test_spot_order(symbol=symbol, side="BUY", order_type="MARKET", quantity=quantity)
626
+
627
+
628
+ def test_spot_sell_market(symbol: str, quantity: float) -> Dict[str, Any]:
629
+ """测试现货市价卖出"""
630
+ logger.info("=" * 80)
631
+ logger.info("测试现货市价卖出")
632
+ logger.info("=" * 80)
633
+ return test_spot_order(symbol=symbol, side="SELL", order_type="MARKET", quantity=quantity)
634
+
635
+
636
+ def test_spot_buy_limit(symbol: str, quantity: float, price: float) -> Dict[str, Any]:
637
+ """测试现货限价买入"""
638
+ logger.info("=" * 80)
639
+ logger.info("测试现货限价买入")
640
+ logger.info("=" * 80)
641
+ return test_spot_order(symbol=symbol, side="BUY", order_type="LIMIT", quantity=quantity, price=price)
642
+
643
+
644
+ def test_spot_sell_limit(symbol: str, quantity: float, price: float) -> Dict[str, Any]:
645
+ """测试现货限价卖出"""
646
+ logger.info("=" * 80)
647
+ logger.info("测试现货限价卖出")
648
+ logger.info("=" * 80)
649
+ return test_spot_order(symbol=symbol, side="SELL", order_type="LIMIT", quantity=quantity, price=price)
650
+
651
+
652
+ def test_futures_buy_market(symbol: str, quantity: float) -> Dict[str, Any]:
653
+ """测试合约市价买入"""
654
+ logger.info("=" * 80)
655
+ logger.info("测试合约市价买入")
656
+ logger.info("=" * 80)
657
+ return test_futures_order(symbol=symbol, side="BUY", order_type="MARKET", quantity=quantity)
658
+
659
+
660
+ def test_futures_sell_market(symbol: str, quantity: float) -> Dict[str, Any]:
661
+ """测试合约市价卖出"""
662
+ logger.info("=" * 80)
663
+ logger.info("测试合约市价卖出")
664
+ logger.info("=" * 80)
665
+ return test_futures_order(symbol=symbol, side="SELL", order_type="MARKET", quantity=quantity)
666
+
667
+
668
+ def test_futures_buy_limit(symbol: str, quantity: float, price: float) -> Dict[str, Any]:
669
+ """测试合约限价买入"""
670
+ logger.info("=" * 80)
671
+ logger.info("测试合约限价买入")
672
+ logger.info("=" * 80)
673
+ return test_futures_order(symbol=symbol, side="BUY", order_type="LIMIT", quantity=quantity, price=price)
674
+
675
+
676
+ def test_futures_sell_limit(symbol: str, quantity: float, price: float) -> Dict[str, Any]:
677
+ """测试合约限价卖出"""
678
+ logger.info("=" * 80)
679
+ logger.info("测试合约限价卖出")
680
+ logger.info("=" * 80)
681
+ return test_futures_order(symbol=symbol, side="SELL", order_type="LIMIT", quantity=quantity, price=price)
682
+
683
+
684
+ def get_spot_open_orders(symbol: Optional[str] = None) -> Dict[str, Any]:
685
+ """
686
+ 获取现货当前挂单
687
+
688
+ Args:
689
+ symbol: 交易对,如 "BTCUSDT",如果为 None 则返回所有交易对的挂单
690
+
691
+ Returns:
692
+ 挂单列表
693
+ """
694
+ try:
695
+ spot_config = ConfigurationRestAPI(
696
+ api_key=DEFAULT_API_KEY,
697
+ api_secret=DEFAULT_API_SECRET,
698
+ base_path=os.getenv("BASE_PATH", SPOT_REST_API_PROD_URL),
699
+ )
700
+
701
+ spot_client = Spot(config_rest_api=spot_config)
702
+
703
+ response = spot_client.rest_api.get_open_orders(symbol=symbol)
704
+ data = response.data()
705
+
706
+ # 处理响应数据
707
+ orders_list = []
708
+ if isinstance(data, list):
709
+ for order in data:
710
+ if hasattr(order, 'model_dump'):
711
+ orders_list.append(order.model_dump(by_alias=True))
712
+ elif hasattr(order, 'dict'):
713
+ orders_list.append(order.dict(by_alias=True))
714
+ elif isinstance(order, dict):
715
+ orders_list.append(order)
716
+ elif isinstance(data, dict):
717
+ orders_list = [data]
718
+ else:
719
+ if hasattr(data, 'model_dump'):
720
+ orders_list = [data.model_dump(by_alias=True)]
721
+ elif hasattr(data, 'dict'):
722
+ orders_list = [data.dict(by_alias=True)]
723
+
724
+ return {
725
+ "success": True,
726
+ "orders": orders_list,
727
+ "count": len(orders_list)
728
+ }
729
+
730
+ except Exception as e:
731
+ logger.error(f"获取现货挂单失败: {e}")
732
+ import traceback
733
+ logger.error(traceback.format_exc())
734
+ return {
735
+ "success": False,
736
+ "error": str(e),
737
+ "orders": [],
738
+ "count": 0
739
+ }
740
+
741
+
742
+ def get_futures_open_orders(symbol: Optional[str] = None) -> Dict[str, Any]:
743
+ """
744
+ 获取合约当前挂单
745
+
746
+ Args:
747
+ symbol: 交易对,如 "BTCUSDT",如果为 None 则返回所有交易对的挂单
748
+
749
+ Returns:
750
+ 挂单列表
751
+ """
752
+ try:
753
+ # 使用 set_user() 函数获取配置,确保使用正确的 API 密钥
754
+ futures_config = set_user(
755
+ api_key=DEFAULT_API_KEY,
756
+ api_secret=DEFAULT_API_SECRET,
757
+ )
758
+
759
+ futures_client = DerivativesTradingUsdsFutures(config_rest_api=futures_config)
760
+
761
+ response = futures_client.rest_api.current_all_open_orders(symbol=symbol)
762
+ data = response.data()
763
+
764
+ # 处理响应数据
765
+ orders_list = []
766
+ if isinstance(data, list):
767
+ for order in data:
768
+ if hasattr(order, 'model_dump'):
769
+ orders_list.append(order.model_dump(by_alias=True))
770
+ elif hasattr(order, 'dict'):
771
+ orders_list.append(order.dict(by_alias=True))
772
+ elif isinstance(order, dict):
773
+ orders_list.append(order)
774
+ elif isinstance(data, dict):
775
+ orders_list = [data]
776
+ else:
777
+ if hasattr(data, 'model_dump'):
778
+ orders_list = [data.model_dump(by_alias=True)]
779
+ elif hasattr(data, 'dict'):
780
+ orders_list = [data.dict(by_alias=True)]
781
+
782
+ return {
783
+ "success": True,
784
+ "orders": orders_list,
785
+ "count": len(orders_list)
786
+ }
787
+
788
+ except Exception as e:
789
+ logger.error(f"获取合约挂单失败: {e}")
790
+ import traceback
791
+ logger.error(traceback.format_exc())
792
+ return {
793
+ "success": False,
794
+ "error": str(e),
795
+ "orders": [],
796
+ "count": 0
797
+ }
798
+
799
+
800
+ def cancel_spot_order(
801
+ symbol: str,
802
+ order_id: Optional[int] = None,
803
+ orig_client_order_id: Optional[str] = None
804
+ ) -> Dict[str, Any]:
805
+ """
806
+ 撤销现货订单
807
+
808
+ Args:
809
+ symbol: 交易对,如 "BTCUSDT"
810
+ order_id: 订单ID
811
+ orig_client_order_id: 客户端订单ID
812
+
813
+ Returns:
814
+ 撤单响应数据字典
815
+ """
816
+ try:
817
+ if order_id is None and orig_client_order_id is None:
818
+ raise ValueError("必须提供 order_id 或 orig_client_order_id 之一")
819
+
820
+ spot_config = ConfigurationRestAPI(
821
+ api_key=DEFAULT_API_KEY,
822
+ api_secret=DEFAULT_API_SECRET,
823
+ base_path=os.getenv("BASE_PATH", SPOT_REST_API_PROD_URL),
824
+ )
825
+
826
+ spot_client = Spot(config_rest_api=spot_config)
827
+
828
+ cancel_params = {"symbol": symbol}
829
+ if order_id is not None:
830
+ cancel_params["order_id"] = order_id
831
+ if orig_client_order_id is not None:
832
+ cancel_params["orig_client_order_id"] = orig_client_order_id
833
+
834
+ logger.info(f"正在撤销现货订单 - {symbol}")
835
+ logger.info(f"撤单参数: {cancel_params}")
836
+
837
+ response = spot_client.rest_api.delete_order(**cancel_params)
838
+
839
+ rate_limits = response.rate_limits
840
+ data = response.data()
841
+
842
+ logger.info(f"撤单成功 - Rate Limits: {rate_limits}")
843
+
844
+ # 处理响应数据
845
+ if hasattr(data, 'model_dump'):
846
+ cancel_result = data.model_dump(by_alias=True)
847
+ elif hasattr(data, 'dict'):
848
+ cancel_result = data.dict(by_alias=True)
849
+ elif isinstance(data, dict):
850
+ cancel_result = data
851
+ else:
852
+ cancel_result = {"raw_data": str(data)}
853
+
854
+ logger.info(f"撤单响应: {cancel_result}")
855
+
856
+ return {
857
+ "success": True,
858
+ "rate_limits": rate_limits,
859
+ "order": cancel_result
860
+ }
861
+
862
+ except Exception as e:
863
+ logger.error(f"现货撤单失败: {e}")
864
+ import traceback
865
+ logger.error(traceback.format_exc())
866
+ return {
867
+ "success": False,
868
+ "error": str(e),
869
+ "traceback": traceback.format_exc()
870
+ }
871
+
872
+
873
+ def cancel_futures_order(
874
+ symbol: str,
875
+ order_id: Optional[int] = None,
876
+ orig_client_order_id: Optional[str] = None
877
+ ) -> Dict[str, Any]:
878
+ """
879
+ 撤销合约订单
880
+
881
+ Args:
882
+ symbol: 交易对,如 "BTCUSDT"
883
+ order_id: 订单ID
884
+ orig_client_order_id: 客户端订单ID
885
+
886
+ Returns:
887
+ 撤单响应数据字典
888
+ """
889
+ try:
890
+ if order_id is None and orig_client_order_id is None:
891
+ raise ValueError("必须提供 order_id 或 orig_client_order_id 之一")
892
+
893
+ # 使用 set_user() 函数获取配置,确保使用正确的 API 密钥
894
+ futures_config = set_user(
895
+ api_key=DEFAULT_API_KEY,
896
+ api_secret=DEFAULT_API_SECRET,
897
+ )
898
+
899
+ futures_client = DerivativesTradingUsdsFutures(config_rest_api=futures_config)
900
+
901
+ cancel_params = {"symbol": symbol}
902
+ if order_id is not None:
903
+ cancel_params["order_id"] = order_id
904
+ if orig_client_order_id is not None:
905
+ cancel_params["orig_client_order_id"] = orig_client_order_id
906
+
907
+ logger.info(f"正在撤销合约订单 - {symbol}")
908
+ logger.info(f"撤单参数: {cancel_params}")
909
+
910
+ response = futures_client.rest_api.cancel_order(**cancel_params)
911
+
912
+ rate_limits = response.rate_limits
913
+ data = response.data()
914
+
915
+ logger.info(f"撤单成功 - Rate Limits: {rate_limits}")
916
+
917
+ # 处理响应数据
918
+ if hasattr(data, 'model_dump'):
919
+ cancel_result = data.model_dump(by_alias=True)
920
+ elif hasattr(data, 'dict'):
921
+ cancel_result = data.dict(by_alias=True)
922
+ elif isinstance(data, dict):
923
+ cancel_result = data
924
+ else:
925
+ cancel_result = {"raw_data": str(data)}
926
+
927
+ logger.info(f"撤单响应: {cancel_result}")
928
+
929
+ return {
930
+ "success": True,
931
+ "rate_limits": rate_limits,
932
+ "order": cancel_result
933
+ }
934
+
935
+ except Exception as e:
936
+ logger.error(f"合约撤单失败: {e}")
937
+ import traceback
938
+ logger.error(traceback.format_exc())
939
+ return {
940
+ "success": False,
941
+ "error": str(e),
942
+ "traceback": traceback.format_exc()
943
+ }
944
+
945
+
946
+ def cancel_all_spot_orders(symbol: str) -> Dict[str, Any]:
947
+ """
948
+ 撤销现货某个交易对的所有挂单
949
+
950
+ Args:
951
+ symbol: 交易对,如 "BTCUSDT"
952
+
953
+ Returns:
954
+ 撤单响应数据字典
955
+ """
956
+ try:
957
+ spot_config = ConfigurationRestAPI(
958
+ api_key=DEFAULT_API_KEY,
959
+ api_secret=DEFAULT_API_SECRET,
960
+ base_path=os.getenv("BASE_PATH", SPOT_REST_API_PROD_URL),
961
+ )
962
+
963
+ spot_client = Spot(config_rest_api=spot_config)
964
+
965
+ logger.info(f"正在撤销现货所有挂单 - {symbol}")
966
+
967
+ response = spot_client.rest_api.delete_open_orders(symbol=symbol)
968
+
969
+ rate_limits = response.rate_limits
970
+ data = response.data()
971
+
972
+ logger.info(f"批量撤单成功 - Rate Limits: {rate_limits}")
973
+
974
+ # 处理响应数据
975
+ if hasattr(data, 'model_dump'):
976
+ cancel_result = data.model_dump(by_alias=True)
977
+ elif hasattr(data, 'dict'):
978
+ cancel_result = data.dict(by_alias=True)
979
+ elif isinstance(data, dict):
980
+ cancel_result = data
981
+ else:
982
+ cancel_result = {"raw_data": str(data)}
983
+
984
+ logger.info(f"批量撤单响应: {cancel_result}")
985
+
986
+ return {
987
+ "success": True,
988
+ "rate_limits": rate_limits,
989
+ "result": cancel_result
990
+ }
991
+
992
+ except Exception as e:
993
+ logger.error(f"现货批量撤单失败: {e}")
994
+ import traceback
995
+ logger.error(traceback.format_exc())
996
+ return {
997
+ "success": False,
998
+ "error": str(e),
999
+ "traceback": traceback.format_exc()
1000
+ }
1001
+
1002
+
1003
+ def show_spot_open_orders(symbol: Optional[str] = None):
1004
+ """
1005
+ 显示现货当前挂单
1006
+
1007
+ Args:
1008
+ symbol: 交易对,如 "BTCUSDT",如果为 None 则显示所有交易对的挂单
1009
+ """
1010
+ print("\n" + "=" * 80)
1011
+ print(f"现货当前挂单" + (f" - {symbol}" if symbol else " (所有交易对)"))
1012
+ print("=" * 80)
1013
+
1014
+ result = get_spot_open_orders(symbol)
1015
+
1016
+ if not result.get("success"):
1017
+ print(f"获取挂单失败: {result.get('error')}")
1018
+ return
1019
+
1020
+ orders = result.get("orders", [])
1021
+
1022
+ if not orders:
1023
+ print("当前没有挂单")
1024
+ return
1025
+
1026
+ print(f"共有 {len(orders)} 个挂单:")
1027
+ print(f"{'订单ID':>15} {'客户端订单ID':>25} {'交易对':>12} {'方向':>6} {'类型':>8} {'数量':>15} {'价格':>15} {'状态':>10}")
1028
+ print("-" * 120)
1029
+
1030
+ for order in orders:
1031
+ order_id = order.get('orderId', order.get('order_id', ''))
1032
+ client_order_id = order.get('clientOrderId', order.get('client_order_id', ''))
1033
+ symbol_name = order.get('symbol', '')
1034
+ side = order.get('side', '')
1035
+ order_type = order.get('type', order.get('order_type', ''))
1036
+ quantity = order.get('origQty', order.get('orig_qty', order.get('quantity', 0)))
1037
+ price = order.get('price', 0)
1038
+ status = order.get('status', '')
1039
+
1040
+ print(f"{order_id:>15} {client_order_id:>25} {symbol_name:>12} {side:>6} {order_type:>8} {quantity:>15} {price:>15} {status:>10}")
1041
+
1042
+ print("=" * 80)
1043
+
1044
+
1045
+ def get_futures_order_ids(symbol: Optional[str] = None) -> list:
1046
+ """
1047
+ 获取合约当前挂单的订单ID列表
1048
+
1049
+ Args:
1050
+ symbol: 交易对,如 "BTCUSDT",如果为 None 则返回所有交易对的挂单ID
1051
+
1052
+ Returns:
1053
+ 订单ID列表
1054
+ """
1055
+ result = get_futures_open_orders(symbol)
1056
+
1057
+ if not result.get("success"):
1058
+ logger.warning(f"获取挂单失败: {result.get('error')}")
1059
+ return []
1060
+
1061
+ orders = result.get("orders", [])
1062
+ order_ids = []
1063
+
1064
+ for order in orders:
1065
+ order_id = order.get('orderId', order.get('order_id', ''))
1066
+ if order_id:
1067
+ order_ids.append(order_id)
1068
+
1069
+ return order_ids
1070
+
1071
+
1072
+ def show_futures_open_orders(symbol: Optional[str] = None) -> list:
1073
+ """
1074
+ 显示合约当前挂单并返回订单ID列表
1075
+
1076
+ Args:
1077
+ symbol: 交易对,如 "BTCUSDT",如果为 None 则显示所有交易对的挂单
1078
+
1079
+ Returns:
1080
+ 订单ID列表
1081
+ """
1082
+ print("\n" + "=" * 80)
1083
+ print(f"合约当前挂单" + (f" - {symbol}" if symbol else " (所有交易对)"))
1084
+ print("=" * 80)
1085
+
1086
+ result = get_futures_open_orders(symbol)
1087
+
1088
+ if not result.get("success"):
1089
+ print(f"获取挂单失败: {result.get('error')}")
1090
+ return []
1091
+
1092
+ orders = result.get("orders", [])
1093
+
1094
+ if not orders:
1095
+ print("当前没有挂单")
1096
+ return []
1097
+
1098
+ print(f"共有 {len(orders)} 个挂单:")
1099
+ print(f"{'订单ID':>15} {'客户端订单ID':>25} {'交易对':>12} {'方向':>6} {'类型':>8} {'数量':>15} {'价格':>15} {'持仓方向':>10} {'状态':>10}")
1100
+ print("-" * 130)
1101
+
1102
+ order_ids = []
1103
+ for order in orders:
1104
+ order_id = order.get('orderId', order.get('order_id', ''))
1105
+ client_order_id = order.get('clientOrderId', order.get('client_order_id', ''))
1106
+ symbol_name = order.get('symbol', '')
1107
+ side = order.get('side', '')
1108
+ order_type = order.get('type', order.get('order_type', ''))
1109
+ quantity = order.get('origQty', order.get('orig_qty', order.get('quantity', 0)))
1110
+ price = order.get('price', 0)
1111
+ position_side = order.get('positionSide', order.get('position_side', ''))
1112
+ status = order.get('status', '')
1113
+
1114
+ if order_id:
1115
+ order_ids.append(order_id)
1116
+
1117
+ print(f"{order_id:>15} {client_order_id:>25} {symbol_name:>12} {side:>6} {order_type:>8} {quantity:>15} {price:>15} {position_side:>10} {status:>10}")
1118
+
1119
+ print("=" * 80)
1120
+
1121
+ if order_ids:
1122
+ print(f"\n订单ID列表: {order_ids}")
1123
+ print(f"订单ID数量: {len(order_ids)}")
1124
+
1125
+ return order_ids
1126
+
1127
+
1128
+ def show_spot_balances(assets: Optional[list] = None):
1129
+ """
1130
+ 显示现货账户余额
1131
+
1132
+ Args:
1133
+ assets: 要显示的资产列表,如 ["BTC", "USDT"],如果为 None 则显示所有非零余额
1134
+ """
1135
+ print("\n" + "=" * 80)
1136
+ print("现货账户余额")
1137
+ print("=" * 80)
1138
+
1139
+ result = get_spot_balance()
1140
+
1141
+ if not result.get("success"):
1142
+ print(f"获取余额失败: {result.get('error')}")
1143
+ return
1144
+
1145
+ balances = result.get("balances", {})
1146
+
1147
+ if assets:
1148
+ # 显示指定资产
1149
+ for asset in assets:
1150
+ balance_info = balances.get(asset, {})
1151
+ if balance_info:
1152
+ free = balance_info.get('free', 0)
1153
+ locked = balance_info.get('locked', 0)
1154
+ total = balance_info.get('total', 0)
1155
+ print(f"{asset:>8} - 可用: {free:>20.8f}, 锁定: {locked:>20.8f}, 总计: {total:>20.8f}")
1156
+ else:
1157
+ print(f"{asset:>8} - 余额为 0")
1158
+ else:
1159
+ # 显示所有非零余额
1160
+ print(f"{'资产':>8} {'可用余额':>25} {'锁定余额':>25} {'总余额':>25}")
1161
+ print("-" * 80)
1162
+ for asset, balance_info in sorted(balances.items()):
1163
+ free = balance_info.get('free', 0)
1164
+ locked = balance_info.get('locked', 0)
1165
+ total = balance_info.get('total', 0)
1166
+ if total > 0: # 只显示非零余额
1167
+ print(f"{asset:>8} {free:>25.8f} {locked:>25.8f} {total:>25.8f}")
1168
+
1169
+ print("=" * 80)
1170
+
1171
+
1172
+ def show_futures_balances(assets: Optional[list] = None):
1173
+ """
1174
+ 显示合约账户余额
1175
+
1176
+ Args:
1177
+ assets: 要显示的资产列表,如 ["USDT"],如果为 None 则显示所有非零余额
1178
+ """
1179
+ print("\n" + "=" * 80)
1180
+ print("合约账户余额")
1181
+ print("=" * 80)
1182
+
1183
+ result = get_futures_balance()
1184
+
1185
+ if not result.get("success"):
1186
+ print(f"获取余额失败: {result.get('error')}")
1187
+ return
1188
+
1189
+ balances = result.get("balances", {})
1190
+
1191
+ if assets:
1192
+ # 显示指定资产
1193
+ for asset in assets:
1194
+ balance_info = balances.get(asset, {})
1195
+ if balance_info:
1196
+ balance = balance_info.get('balance', 0)
1197
+ available = balance_info.get('available', 0)
1198
+ print(f"{asset:>8} - 余额: {balance:>20.8f}, 可用: {available:>20.8f}")
1199
+ else:
1200
+ print(f"{asset:>8} - 余额为 0")
1201
+ else:
1202
+ # 显示所有非零余额
1203
+ print(f"{'资产':>8} {'余额':>25} {'可用余额':>25}")
1204
+ print("-" * 80)
1205
+ for asset, balance_info in sorted(balances.items()):
1206
+ balance = balance_info.get('balance', 0)
1207
+ available = balance_info.get('available', 0)
1208
+ if balance > 0: # 只显示非零余额
1209
+ print(f"{asset:>8} {balance:>25.8f} {available:>25.8f}")
1210
+
1211
+ print("=" * 80)
1212
+
1213
+
1214
+ def main():
1215
+ """主函数 - 运行所有测试"""
1216
+ print("\n" + "=" * 80)
1217
+ print("Binance 下单接口测试")
1218
+ print("=" * 80)
1219
+
1220
+ # 显示账户余额
1221
+ show_spot_balances()
1222
+ show_futures_balances()
1223
+
1224
+ # 显示当前挂单
1225
+ show_spot_open_orders()
1226
+ show_futures_open_orders()
1227
+
1228
+ # 测试参数(请根据实际情况修改)
1229
+ test_symbol = "BNBUSDT"
1230
+ test_quantity = 0.01 # 测试数量(请根据实际情况调整)
1231
+ test_price = 500.0 # 测试价格(请根据实际情况调整)
1232
+
1233
+ results = []
1234
+
1235
+ # 测试现货买卖
1236
+ print("\n" + "-" * 80)
1237
+ print("现货交易测试")
1238
+ print("-" * 80)
1239
+
1240
+ # 注意:以下测试会实际下单,请谨慎使用!
1241
+ # 建议先使用测试网络或小额资金测试
1242
+
1243
+ # 1. 现货市价买入(注释掉以避免实际下单)
1244
+ # result = test_spot_buy_market(test_symbol, test_quantity)
1245
+ # results.append(("现货市价买入", result))
1246
+
1247
+ # 2. 现货市价卖出(注释掉以避免实际下单)
1248
+ # result = test_spot_sell_market(test_symbol, test_quantity)
1249
+ # results.append(("现货市价卖出", result))
1250
+
1251
+ # 3. 现货限价买入(注释掉以避免实际下单)
1252
+ # result = test_spot_buy_limit(test_symbol, test_quantity, test_price)
1253
+ # results.append(("现货限价买入", result))
1254
+
1255
+ # 4. 现货限价卖出(注释掉以避免实际下单)
1256
+ # result = test_spot_sell_limit(test_symbol, test_quantity, test_price)
1257
+ # results.append(("现货限价卖出", result))
1258
+
1259
+ # 测试合约买卖
1260
+ print("\n" + "-" * 80)
1261
+ print("合约交易测试")
1262
+ print("-" * 80)
1263
+
1264
+ # 5. 合约市价买入(注释掉以避免实际下单)
1265
+ # result = test_futures_buy_market(test_symbol, test_quantity)
1266
+ # results.append(("合约市价买入", result))
1267
+
1268
+ # 6. 合约市价卖出(注释掉以避免实际下单)
1269
+ # result = test_futures_sell_market(test_symbol, test_quantity)
1270
+ # results.append(("合约市价卖出", result))
1271
+
1272
+ # 7. 合约限价买入(注释掉以避免实际下单)
1273
+ # result = test_futures_buy_limit(test_symbol, test_quantity, test_price)
1274
+ # results.append(("合约限价买入", result))
1275
+
1276
+ # 8. 合约限价卖出(注释掉以避免实际下单)
1277
+ # result = test_futures_sell_limit(test_symbol, test_quantity, test_price)
1278
+ # results.append(("合约限价卖出", result))
1279
+
1280
+ print("\n" + "=" * 80)
1281
+ print("测试完成")
1282
+ print("=" * 80)
1283
+ print("\n注意:所有测试函数已注释,以避免实际下单。")
1284
+ print("如需测试,请取消相应函数的注释,并确保:")
1285
+ print("1. 使用测试网络或小额资金")
1286
+ print("2. 检查交易对、数量和价格参数")
1287
+ print("3. 确保账户有足够的余额")
1288
+ print("=" * 80)
1289
+
1290
+ # 打印结果摘要
1291
+ if results:
1292
+ print("\n测试结果摘要:")
1293
+ for test_name, result in results:
1294
+ status = "✓ 成功" if result.get("success") else "✗ 失败"
1295
+ print(f"{test_name}: {status}")
1296
+ if not result.get("success"):
1297
+ print(f" 错误: {result.get('error', 'Unknown error')}")
1298
+
1299
+
1300
+ if __name__ == "__main__":
1301
+ # 示例:单独测试某个功能
1302
+ # 取消注释以下行来测试特定功能
1303
+
1304
+ # 测试现货市价买入
1305
+ # test_spot_buy_market("BTCUSDT", 0.001)
1306
+
1307
+ # 测试现货市价卖出
1308
+ # test_spot_sell_market("BTCUSDT", 0.001)
1309
+
1310
+ # 测试现货限价买入
1311
+ # test_spot_buy_limit("BTCUSDT", 0.001, 50000.0)
1312
+
1313
+ # 测试现货限价卖出
1314
+ # test_spot_sell_limit("BTCUSDT", 0.001, 50000.0)
1315
+
1316
+ # 测试合约市价买入
1317
+ # test_futures_buy_market("BTCUSDT", 0.001)
1318
+
1319
+ # 测试合约市价卖出
1320
+ # test_futures_sell_market("BTCUSDT", 0.001)
1321
+
1322
+ # 测试合约限价买入
1323
+ # test_futures_buy_limit("BTCUSDT", 0.001, 50000.0)
1324
+
1325
+ # 测试合约限价卖出
1326
+ test_futures_sell_limit("BNBUSDT", 0.01, 1000.0)
1327
+
1328
+ # 撤单功能示例
1329
+ # 查看当前挂单
1330
+ # show_spot_open_orders("BTCUSDT")
1331
+ # show_futures_open_orders("BNBUSDT")
1332
+
1333
+ # 获取合约订单ID列表
1334
+ # order_ids = get_futures_order_ids("BNBUSDT")
1335
+ # print(f"订单ID列表: {order_ids}")
1336
+
1337
+ # 或者显示并获取订单ID
1338
+ # order_ids = show_futures_open_orders("BNBUSDT")
1339
+ # if order_ids:
1340
+ # print(f"\n获取到的订单ID: {order_ids}")
1341
+ # cancel_futures_order("BNBUSDT", order_id=order_ids[0])
1342
+
1343
+ # 撤销现货订单(通过订单ID)
1344
+ # cancel_spot_order("BNBUSDT", order_id=order_ids[0])
1345
+
1346
+ # 撤销现货订单(通过客户端订单ID)
1347
+ # cancel_spot_order("BTCUSDT", orig_client_order_id="my_order_123")
1348
+
1349
+ # 撤销合约订单(通过订单ID)
1350
+ # cancel_futures_order("BTCUSDT", order_id=12345678)
1351
+
1352
+ # 撤销合约订单(通过客户端订单ID)
1353
+ # cancel_futures_order("BTCUSDT", orig_client_order_id="my_order_123")
1354
+
1355
+ # 撤销现货某个交易对的所有挂单
1356
+ # cancel_all_spot_orders("BTCUSDT")
1357
+
1358
+ # 运行主函数
1359
+ main()
1360
+