cyqnt-trd 0.1.2__py3-none-any.whl → 0.1.7__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 (40) hide show
  1. cyqnt_trd/CHANGELOG_0.1.7.md +111 -0
  2. cyqnt_trd/__init__.py +1 -1
  3. cyqnt_trd/backtesting/factor_test.py +3 -2
  4. cyqnt_trd/get_data/__init__.py +16 -1
  5. cyqnt_trd/get_data/get_futures_data.py +3 -3
  6. cyqnt_trd/get_data/get_kline_with_factor.py +808 -0
  7. cyqnt_trd/get_data/get_web3_trending_data.py +389 -0
  8. cyqnt_trd/online_trading/__init__.py +1 -0
  9. cyqnt_trd/online_trading/realtime_price_tracker.py +142 -2
  10. cyqnt_trd/trading_signal/example_usage.py +123 -7
  11. cyqnt_trd/trading_signal/factor/__init__.py +23 -0
  12. cyqnt_trd/trading_signal/factor/adx_factor.py +116 -0
  13. cyqnt_trd/trading_signal/factor/ao_factor.py +66 -0
  14. cyqnt_trd/trading_signal/factor/bbp_factor.py +68 -0
  15. cyqnt_trd/trading_signal/factor/cci_factor.py +65 -0
  16. cyqnt_trd/trading_signal/factor/ema_factor.py +102 -0
  17. cyqnt_trd/trading_signal/factor/macd_factor.py +97 -0
  18. cyqnt_trd/trading_signal/factor/momentum_factor.py +44 -0
  19. cyqnt_trd/trading_signal/factor/stochastic_factor.py +76 -0
  20. cyqnt_trd/trading_signal/factor/stochastic_tsi_factor.py +129 -0
  21. cyqnt_trd/trading_signal/factor/uo_factor.py +92 -0
  22. cyqnt_trd/trading_signal/factor/williams_r_factor.py +60 -0
  23. cyqnt_trd/trading_signal/factor_details.json +107 -0
  24. cyqnt_trd/trading_signal/selected_alpha/alpha1.py +4 -2
  25. cyqnt_trd/trading_signal/selected_alpha/alpha15.py +4 -2
  26. cyqnt_trd/trading_signal/selected_alpha/generate_alphas.py +1 -0
  27. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/METADATA +16 -12
  28. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/RECORD +34 -23
  29. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/WHEEL +1 -1
  30. test/real_time_trade.py +467 -10
  31. test/test_now_factor.py +1082 -0
  32. test/track_k_line_continue.py +372 -0
  33. cyqnt_trd/test_script/get_symbols_by_volume.py +0 -227
  34. cyqnt_trd/test_script/test_alpha.py +0 -261
  35. cyqnt_trd/test_script/test_kline_data.py +0 -479
  36. test/test_example_usage.py +0 -381
  37. test/test_get_data.py +0 -310
  38. test/test_realtime_price_tracker.py +0 -546
  39. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/licenses/LICENSE +0 -0
  40. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,372 @@
1
+ """
2
+ 实时跟踪K线,并显示最近3个周期的涨跌和涨跌幅度
3
+
4
+ 使用方法:
5
+ python track_k_line_continue.py --symbol BTCUSDT --interval 1m
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import asyncio
11
+ import argparse
12
+ from datetime import datetime
13
+ from typing import Optional
14
+
15
+ # 添加项目根目录到路径
16
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
+ if project_root not in sys.path:
18
+ sys.path.insert(0, project_root)
19
+
20
+ # 导入实时价格跟踪器
21
+ try:
22
+ from cyqnt_trd.online_trading.realtime_price_tracker import RealtimePriceTracker
23
+ except ImportError:
24
+ # 如果导入失败,尝试从test_script导入
25
+ try:
26
+ from cyqnt_trd.test_script.realtime_price_tracker import RealtimePriceTracker
27
+ except ImportError as e:
28
+ print(f"导入错误: {e}")
29
+ print("\n提示:请确保已安装 cyqnt_trd package 或项目路径正确")
30
+ sys.exit(1)
31
+
32
+
33
+ def _parse_interval_minutes(interval: str) -> int:
34
+ """
35
+ 解析时间间隔字符串,返回分钟数
36
+
37
+ Args:
38
+ interval: 时间间隔字符串,例如 '1m', '5m', '10m', '1h', '1d'
39
+
40
+ Returns:
41
+ 对应的分钟数
42
+ """
43
+ interval_map = {
44
+ "1m": 1,
45
+ "3m": 3,
46
+ "5m": 5,
47
+ "10m": 10,
48
+ "15m": 15,
49
+ "30m": 30,
50
+ "1h": 60,
51
+ "2h": 120,
52
+ "4h": 240,
53
+ "6h": 360,
54
+ "8h": 480,
55
+ "12h": 720,
56
+ "1d": 1440,
57
+ "3d": 4320,
58
+ "1w": 10080,
59
+ "1M": 43200, # 近似值
60
+ }
61
+ return interval_map.get(interval, 1)
62
+
63
+
64
+ def calculate_price_change(data_df, latest_kline: Optional[dict], interval: str, periods: int = 3) -> Optional[list]:
65
+ """
66
+ 计算最近N个周期的涨跌和涨跌幅度(以当前时间为终点)
67
+
68
+ Args:
69
+ data_df: 包含K线数据的DataFrame
70
+ latest_kline: 最新的K线数据(可能未完成)
71
+ interval: 时间间隔,例如 '1m', '5m', '10m'
72
+ periods: 要计算的周期数(默认3)
73
+
74
+ Returns:
75
+ 包含每个周期涨跌信息的列表,每个元素为:
76
+ {
77
+ 'period': 周期编号(1表示最近1个周期,2表示最近2个周期,以此类推),
78
+ 'time': 时间字符串(周期开始时间 - 结束时间),
79
+ 'open_price': 开盘价,
80
+ 'close_price': 收盘价,
81
+ 'change': 涨跌金额,
82
+ 'change_pct': 涨跌幅度(百分比),
83
+ 'direction': '涨' 或 '跌'
84
+ }
85
+ """
86
+ if data_df is None or len(data_df) == 0:
87
+ return None
88
+
89
+ from datetime import timedelta
90
+
91
+ # 解析间隔分钟数
92
+ interval_minutes = _parse_interval_minutes(interval)
93
+
94
+ # 获取当前时间
95
+ current_time = datetime.now()
96
+
97
+ # 计算当前时间所在的K线周期开始时间(按标准时间边界对齐)
98
+ # 例如10m周期::00, :10, :20, :30, :40, :50
99
+ current_minute = current_time.minute
100
+ period_start_minute = (current_minute // interval_minutes) * interval_minutes
101
+ current_period_start = current_time.replace(
102
+ minute=period_start_minute,
103
+ second=0,
104
+ microsecond=0
105
+ )
106
+ current_period_end = current_period_start + timedelta(minutes=interval_minutes)
107
+
108
+ results = []
109
+ prev_close = None
110
+
111
+ # 计算最近N个周期
112
+ for i in range(periods):
113
+ # 计算第i+1个周期的开始和结束时间
114
+ period_start = current_period_start - timedelta(minutes=interval_minutes * i)
115
+ period_end = period_start + timedelta(minutes=interval_minutes)
116
+
117
+ # 如果是当前周期(i == 0),使用latest_kline(可能未完成)
118
+ if i == 0 and latest_kline:
119
+ period_data = {
120
+ 'open_time': int(period_start.timestamp() * 1000),
121
+ 'open_time_str': period_start.strftime('%Y-%m-%d %H:%M:%S'),
122
+ 'open_price': latest_kline['open_price'],
123
+ 'close_price': latest_kline['close_price'],
124
+ 'high_price': latest_kline['high_price'],
125
+ 'low_price': latest_kline['low_price'],
126
+ 'volume': latest_kline['volume'],
127
+ }
128
+ else:
129
+ # 在data_df中查找对应的K线
130
+ period_start_ms = int(period_start.timestamp() * 1000)
131
+ period_end_ms = int(period_end.timestamp() * 1000)
132
+
133
+ # 查找在周期时间范围内的K线(允许±60秒的容差,因为合并的K线可能有时间偏差)
134
+ tolerance_ms = 60 * 1000 # 60秒容差
135
+ matching_kline = data_df[
136
+ (data_df['open_time'] >= period_start_ms - tolerance_ms) &
137
+ (data_df['open_time'] <= period_start_ms + tolerance_ms) &
138
+ (data_df['open_time'] < period_end_ms)
139
+ ]
140
+
141
+ if len(matching_kline) == 0:
142
+ # 如果找不到,尝试找在周期结束时间之前,且最接近period_start的K线
143
+ before_kline = data_df[data_df['open_time'] < period_end_ms]
144
+ if len(before_kline) > 0:
145
+ # 计算每个K线与period_start的时间差
146
+ time_diff = (before_kline['open_time'] - period_start_ms).abs()
147
+ # 选择时间差最小的,但要求时间差不超过一个周期的一半(更宽松)
148
+ max_diff = interval_minutes * 60 * 1000 / 2
149
+ valid_kline = before_kline[time_diff <= max_diff]
150
+ if len(valid_kline) > 0:
151
+ closest_idx = time_diff[time_diff <= max_diff].idxmin()
152
+ matching_kline = valid_kline.loc[[closest_idx]]
153
+ else:
154
+ # 如果没有在合理范围内的,选择最接近的(但不超过一个周期)
155
+ max_diff_full = interval_minutes * 60 * 1000
156
+ valid_kline_full = before_kline[time_diff <= max_diff_full]
157
+ if len(valid_kline_full) > 0:
158
+ closest_idx = time_diff[time_diff <= max_diff_full].idxmin()
159
+ matching_kline = valid_kline_full.loc[[closest_idx]]
160
+
161
+ if len(matching_kline) == 0:
162
+ # 如果还是找不到,跳过这个周期
163
+ # 打印调试信息以便排查
164
+ print(f"⚠️ 警告:无法找到周期 {i+1} 的K线数据 ({period_start.strftime('%H:%M')}-{period_end.strftime('%H:%M')})")
165
+ continue
166
+
167
+ period_data = {
168
+ 'open_time': matching_kline.iloc[0]['open_time'],
169
+ 'open_time_str': matching_kline.iloc[0]['open_time_str'],
170
+ 'open_price': matching_kline.iloc[0]['open_price'],
171
+ 'close_price': matching_kline.iloc[0]['close_price'],
172
+ 'high_price': matching_kline.iloc[0]['high_price'],
173
+ 'low_price': matching_kline.iloc[0]['low_price'],
174
+ 'volume': matching_kline.iloc[0]['volume'],
175
+ }
176
+
177
+ # 计算涨跌:每个周期都计算周期内的涨跌(收盘价 - 开盘价)
178
+ change = period_data['close_price'] - period_data['open_price']
179
+ change_pct = (change / period_data['open_price'] * 100) if period_data['open_price'] > 0 else 0
180
+
181
+ # 格式化时间范围字符串
182
+ # 对于当前周期(i==0),结束时间显示为当前时间
183
+ if i == 0:
184
+ time_range = f"{period_start.strftime('%H:%M')}-{current_time.strftime('%H:%M')}"
185
+ else:
186
+ time_range = f"{period_start.strftime('%H:%M')}-{period_end.strftime('%H:%M')}"
187
+
188
+ results.append({
189
+ 'period': i + 1, # 1表示最近1个周期
190
+ 'time': time_range,
191
+ 'open_price': period_data['open_price'],
192
+ 'close_price': period_data['close_price'],
193
+ 'change': change,
194
+ 'change_pct': change_pct,
195
+ 'direction': '涨' if change >= 0 else '跌'
196
+ })
197
+
198
+ # 更新prev_close为当前周期的收盘价
199
+ prev_close = period_data['close_price']
200
+
201
+ return results if results else None
202
+
203
+
204
+ def print_price_changes(kline_dict: dict, data_df, interval: str, periods: int = 3):
205
+ """
206
+ 打印最近N个周期的涨跌信息
207
+
208
+ Args:
209
+ kline_dict: 最新K线数据字典
210
+ data_df: 包含历史K线数据的DataFrame
211
+ interval: 时间间隔,例如 '1m', '5m', '10m'
212
+ periods: 要显示的周期数
213
+ """
214
+ print("\n" + "="*80)
215
+ print(f"📊 K线实时跟踪 - {kline_dict['open_time_str']}")
216
+ print("="*80)
217
+ print(f"当前价格: {kline_dict['close_price']:.2f}")
218
+ print(f"开盘价: {kline_dict['open_price']:.2f}")
219
+ print(f"最高价: {kline_dict['high_price']:.2f}")
220
+ print(f"最低价: {kline_dict['low_price']:.2f}")
221
+ print(f"成交量: {kline_dict['volume']:.2f}")
222
+ print("="*80)
223
+
224
+ # 计算最近N个周期的涨跌(以当前时间为终点)
225
+ price_changes = calculate_price_change(data_df, latest_kline=kline_dict, interval=interval, periods=periods)
226
+
227
+ if price_changes:
228
+ print(f"\n📈 最近{periods}个周期的涨跌情况:")
229
+ print("-"*80)
230
+ for change_info in price_changes:
231
+ period_num = change_info['period']
232
+ direction_emoji = "🟢" if change_info['direction'] == '涨' else "🔴"
233
+ print(f"周期 #{period_num} ({change_info['time']}):")
234
+ print(f" 开盘价: {change_info['open_price']:.2f}")
235
+ print(f" 收盘价: {change_info['close_price']:.2f}")
236
+ print(f" 涨跌: {direction_emoji} {change_info['direction']} {abs(change_info['change']):.2f} ({change_info['change_pct']:+.2f}%)")
237
+ print()
238
+ else:
239
+ print(f"\n⚠️ 数据不足,无法计算最近{periods}个周期的涨跌")
240
+ print(f" 当前数据量: {len(data_df) if data_df is not None else 0} 条")
241
+
242
+ print("="*80 + "\n")
243
+
244
+
245
+ async def track_klines(symbol: str, interval: str = "1m", periods: int = 3, ssl_verify: bool = False):
246
+ """
247
+ 实时跟踪K线并显示涨跌信息
248
+
249
+ Args:
250
+ symbol: 交易对符号,例如 'BTCUSDT', 'ETHUSDT'
251
+ interval: 时间间隔,例如 '1m', '5m', '1h'
252
+ periods: 要显示的最近周期数(默认3)
253
+ ssl_verify: 是否验证SSL证书(默认False,用于开发/测试)
254
+ """
255
+ # 创建实时价格跟踪器
256
+ tracker = RealtimePriceTracker(
257
+ symbol=symbol,
258
+ interval=interval,
259
+ lookback_periods=100, # 保留最近100个周期的历史数据
260
+ ssl_verify=ssl_verify,
261
+ market_type="futures"
262
+ )
263
+
264
+ # 定义回调函数
265
+ def on_new_kline(kline_dict, data_df):
266
+ """新K线到来时的回调函数"""
267
+ print_price_changes(kline_dict, data_df, interval=interval, periods=periods)
268
+
269
+ # 注册回调函数
270
+ tracker.register_on_new_kline(on_new_kline)
271
+
272
+ print("="*80)
273
+ print("🚀 实时K线跟踪已启动")
274
+ print("="*80)
275
+ print(f"交易对: {symbol}")
276
+ print(f"时间间隔: {interval}")
277
+ print(f"显示最近周期数: {periods}")
278
+ print("="*80)
279
+ print("\n等待新K线数据...\n")
280
+
281
+ # 启动跟踪器(不运行 forever,而是手动控制)
282
+ await tracker.start()
283
+
284
+ # 定义定时打印任务
285
+ async def periodic_print():
286
+ """每分钟打印一次当前状态"""
287
+ while tracker.is_running:
288
+ await asyncio.sleep(60*5) # 等待60秒
289
+ if not tracker.is_running:
290
+ break
291
+
292
+ # 获取最新数据
293
+ latest_kline = tracker.latest_kline
294
+ data_df = tracker.get_data()
295
+
296
+ if latest_kline and data_df is not None and len(data_df) > 0:
297
+ # 使用最新K线数据打印
298
+ current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
299
+ print("\n" + "="*80)
300
+ print(f"📊 K线实时跟踪 - {current_time} (定时更新)")
301
+ print("="*80)
302
+ print(f"当前价格: {latest_kline['close_price']:.2f}")
303
+ print(f"开盘价: {latest_kline['open_price']:.2f}")
304
+ print(f"最高价: {latest_kline['high_price']:.2f}")
305
+ print(f"最低价: {latest_kline['low_price']:.2f}")
306
+ print(f"成交量: {latest_kline['volume']:.2f}")
307
+ print("="*80)
308
+
309
+ # 计算最近N个周期的涨跌(以当前时间为终点)
310
+ price_changes = calculate_price_change(data_df, latest_kline=latest_kline, interval=interval, periods=periods)
311
+
312
+ if price_changes:
313
+ print(f"\n📈 最近{periods}个周期的涨跌情况:")
314
+ print("-"*80)
315
+ for change_info in price_changes:
316
+ period_num = change_info['period']
317
+ direction_emoji = "🟢" if change_info['direction'] == '涨' else "🔴"
318
+ print(f"周期 #{period_num} ({change_info['time']}):")
319
+ print(f" 开盘价: {change_info['open_price']:.2f}")
320
+ print(f" 收盘价: {change_info['close_price']:.2f}")
321
+ print(f" 涨跌: {direction_emoji} {change_info['direction']} {abs(change_info['change']):.2f} ({change_info['change_pct']:+.2f}%)")
322
+ print()
323
+ else:
324
+ print(f"\n⚠️ 数据不足,无法计算最近{periods}个周期的涨跌")
325
+ print(f" 当前数据量: {len(data_df) if data_df is not None else 0} 条")
326
+
327
+ print("="*80 + "\n")
328
+
329
+ # 创建定时打印任务
330
+ print_task = asyncio.create_task(periodic_print())
331
+
332
+ # 运行跟踪器
333
+ try:
334
+ # 保持运行直到中断
335
+ while tracker.is_running:
336
+ await asyncio.sleep(1)
337
+ except KeyboardInterrupt:
338
+ print("\n\n收到中断信号,正在停止...")
339
+ finally:
340
+ # 取消定时任务
341
+ print_task.cancel()
342
+ try:
343
+ await print_task
344
+ except asyncio.CancelledError:
345
+ pass
346
+
347
+ await tracker.stop()
348
+ print("✅ 跟踪已停止")
349
+
350
+
351
+ def main():
352
+ """主函数"""
353
+ parser = argparse.ArgumentParser(description='实时跟踪K线,并显示最近3个周期的涨跌和涨跌幅度')
354
+ parser.add_argument('--symbol', type=str, default='ETHUSDT', help='交易对符号,例如 BTCUSDT, ETHUSDT')
355
+ parser.add_argument('--interval', type=str, default='10m', help='时间间隔,例如 1m, 5m, 30m, 1h')
356
+ parser.add_argument('--periods', type=int, default=4, help='要显示的最近周期数(默认3)')
357
+ parser.add_argument('--ssl-verify', action='store_true', help='是否验证SSL证书(默认不验证)')
358
+
359
+ args = parser.parse_args()
360
+
361
+ # 运行异步跟踪
362
+ asyncio.run(track_klines(
363
+ symbol=args.symbol,
364
+ interval=args.interval,
365
+ periods=args.periods,
366
+ ssl_verify=args.ssl_verify
367
+ ))
368
+
369
+
370
+ if __name__ == "__main__":
371
+ main()
372
+
@@ -1,227 +0,0 @@
1
- import os
2
- import logging
3
- from typing import List, Dict
4
-
5
- from binance_sdk_spot.spot import Spot, ConfigurationRestAPI, SPOT_REST_API_PROD_URL
6
- from binance_sdk_derivatives_trading_usds_futures.derivatives_trading_usds_futures import (
7
- DerivativesTradingUsdsFutures,
8
- ConfigurationRestAPI as FuturesConfigurationRestAPI,
9
- DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL,
10
- )
11
-
12
- # Configure logging
13
- logging.basicConfig(
14
- level=logging.INFO,
15
- format='%(asctime)s - %(levelname)s - %(message)s'
16
- )
17
-
18
-
19
- def get_spot_symbols_by_volume() -> List[Dict]:
20
- """
21
- 获取所有现货交易对列表,并按24小时交易量排序
22
-
23
- Returns:
24
- 按交易量降序排列的交易对列表,每个元素包含 symbol 和 volume
25
- """
26
- try:
27
- # 创建现货客户端配置
28
- spot_config = ConfigurationRestAPI(
29
- api_key=os.getenv("API_KEY", "yNCZdF58V32y7oL2EATCIUKlmn8wkQ8ywoQukGIR7w4nkXBLldUFgld68I2xN0fj"),
30
- api_secret=os.getenv("API_SECRET", "xktvKv6fcTxcgGeLrAmC3MMpX5qcDntzvBByVTPTyHEsNThg7rHoRW48qQhUpP0k"),
31
- base_path=os.getenv("BASE_PATH", SPOT_REST_API_PROD_URL),
32
- )
33
-
34
- # 初始化现货客户端
35
- spot_client = Spot(config_rest_api=spot_config)
36
-
37
- # 获取所有交易对的24小时交易量统计
38
- logging.info("正在获取现货交易对24小时交易量数据...")
39
- ticker_response = spot_client.rest_api.ticker24hr()
40
- ticker_data = ticker_response.data()
41
-
42
- # 处理响应数据:可能是列表或单个对象,可能是 Pydantic 模型或字典
43
- if not isinstance(ticker_data, list):
44
- ticker_data = [ticker_data]
45
-
46
- # 提取交易对和交易量信息
47
- symbols_with_volume = []
48
- for ticker in ticker_data:
49
- # 如果是 Pydantic 模型,转换为字典
50
- if hasattr(ticker, 'model_dump'):
51
- ticker_dict = ticker.model_dump(by_alias=True)
52
- elif hasattr(ticker, 'dict'):
53
- ticker_dict = ticker.dict(by_alias=True)
54
- elif isinstance(ticker, dict):
55
- ticker_dict = ticker
56
- else:
57
- # 尝试直接访问属性
58
- ticker_dict = {
59
- 'symbol': getattr(ticker, 'symbol', getattr(ticker, 'Symbol', '')),
60
- 'quoteVolume': getattr(ticker, 'quote_volume', getattr(ticker, 'quoteVolume', '0'))
61
- }
62
-
63
- symbol = ticker_dict.get('symbol', '')
64
- # 使用 quoteVolume (以报价货币计的交易量) 作为排序依据
65
- quote_volume = ticker_dict.get('quoteVolume', ticker_dict.get('quote_volume', '0'))
66
- volume = float(quote_volume) if quote_volume else 0.0
67
- symbols_with_volume.append({
68
- 'symbol': symbol,
69
- 'volume': volume,
70
- 'volume_str': f"{volume:,.2f}"
71
- })
72
-
73
- # 按交易量降序排序
74
- symbols_with_volume.sort(key=lambda x: x['volume'], reverse=True)
75
-
76
- logging.info(f"成功获取 {len(symbols_with_volume)} 个现货交易对")
77
- return symbols_with_volume
78
-
79
- except Exception as e:
80
- logging.error(f"获取现货交易对列表时出错: {e}")
81
- import traceback
82
- logging.error(traceback.format_exc())
83
- return []
84
-
85
-
86
- def get_futures_symbols_by_volume() -> List[Dict]:
87
- """
88
- 获取所有合约交易对列表,并按24小时交易量排序
89
-
90
- Returns:
91
- 按交易量降序排列的交易对列表,每个元素包含 symbol 和 volume
92
- """
93
- try:
94
- # 创建合约客户端配置
95
- futures_config = FuturesConfigurationRestAPI(
96
- api_key=os.getenv("API_KEY", ""),
97
- api_secret=os.getenv("API_SECRET", ""),
98
- base_path=os.getenv(
99
- "BASE_PATH", DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL
100
- ),
101
- )
102
-
103
- # 初始化合约客户端
104
- futures_client = DerivativesTradingUsdsFutures(config_rest_api=futures_config)
105
-
106
- # 获取所有交易对的24小时交易量统计
107
- logging.info("正在获取合约交易对24小时交易量数据...")
108
- ticker_response = futures_client.rest_api.ticker24hr_price_change_statistics()
109
- ticker_data = ticker_response.data()
110
-
111
- # 处理响应数据:可能是列表或单个对象,可能是 Pydantic 模型或字典
112
- if not isinstance(ticker_data, list):
113
- ticker_data = [ticker_data]
114
-
115
- # 提取交易对和交易量信息
116
- symbols_with_volume = []
117
- for ticker in ticker_data:
118
- # 如果是 Pydantic 模型,转换为字典
119
- if hasattr(ticker, 'model_dump'):
120
- ticker_dict = ticker.model_dump(by_alias=True)
121
- elif hasattr(ticker, 'dict'):
122
- ticker_dict = ticker.dict(by_alias=True)
123
- elif isinstance(ticker, dict):
124
- ticker_dict = ticker
125
- else:
126
- # 尝试直接访问属性
127
- ticker_dict = {
128
- 'symbol': getattr(ticker, 'symbol', getattr(ticker, 'Symbol', '')),
129
- 'quoteVolume': getattr(ticker, 'quote_volume', getattr(ticker, 'quoteVolume', '0'))
130
- }
131
-
132
- symbol = ticker_dict.get('symbol', '')
133
- # 使用 quoteVolume (以报价货币计的交易量) 作为排序依据
134
- quote_volume = ticker_dict.get('quoteVolume', ticker_dict.get('quote_volume', '0'))
135
- volume = float(quote_volume) if quote_volume else 0.0
136
- symbols_with_volume.append({
137
- 'symbol': symbol,
138
- 'volume': volume,
139
- 'volume_str': f"{volume:,.2f}"
140
- })
141
-
142
- # 按交易量降序排序
143
- symbols_with_volume.sort(key=lambda x: x['volume'], reverse=True)
144
-
145
- logging.info(f"成功获取 {len(symbols_with_volume)} 个合约交易对")
146
- return symbols_with_volume
147
-
148
- except Exception as e:
149
- logging.error(f"获取合约交易对列表时出错: {e}")
150
- import traceback
151
- logging.error(traceback.format_exc())
152
- return []
153
-
154
-
155
- def print_symbols_list(symbols: List[Dict], title: str, top_n: int = 20):
156
- """
157
- 打印交易对列表
158
-
159
- Args:
160
- symbols: 交易对列表
161
- title: 标题
162
- top_n: 显示前N个
163
- """
164
- print(f"\n{'='*80}")
165
- print(f"{title}")
166
- print(f"{'='*80}")
167
- print(f"总共 {len(symbols)} 个交易对,显示前 {min(top_n, len(symbols))} 个:")
168
- print(f"{'排名':<6} {'交易对':<20} {'24h交易量 (USDT)':>25}")
169
- print(f"{'-'*80}")
170
-
171
- for idx, item in enumerate(symbols[:top_n], 1):
172
- print(f"{idx:<6} {item['symbol']:<20} {item['volume_str']:>25}")
173
-
174
- if len(symbols) > top_n:
175
- print(f"\n... 还有 {len(symbols) - top_n} 个交易对未显示")
176
-
177
-
178
- def main():
179
- """主函数"""
180
- print("\n" + "="*80)
181
- print("获取 Binance 交易对列表并按交易量排序")
182
- print("="*80)
183
-
184
- # 获取现货交易对列表
185
- spot_symbols = get_spot_symbols_by_volume()
186
- if spot_symbols:
187
- print_symbols_list(spot_symbols, "现货交易对列表(按24小时交易量排序)", top_n=50)
188
-
189
- # 获取合约交易对列表
190
- futures_symbols = get_futures_symbols_by_volume()
191
- if futures_symbols:
192
- print_symbols_list(futures_symbols, "合约交易对列表(按24小时交易量排序)", top_n=50)
193
-
194
- # 返回结果供其他脚本使用
195
- return {
196
- 'spot': spot_symbols,
197
- 'futures': futures_symbols
198
- }
199
-
200
-
201
- if __name__ == "__main__":
202
- result = main()
203
-
204
- # 可选:保存到文件
205
- import json
206
- from datetime import datetime
207
-
208
- output_dir = "/Users/user/Desktop/repo/cyqnt_trd/tmp"
209
- if not os.path.exists(output_dir):
210
- os.makedirs(output_dir)
211
-
212
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
213
-
214
- # 保存现货列表
215
- if result['spot']:
216
- spot_file = os.path.join(output_dir, f"spot_symbols_by_volume_{timestamp}.json")
217
- with open(spot_file, 'w', encoding='utf-8') as f:
218
- json.dump(result['spot'], f, indent=2, ensure_ascii=False)
219
- print(f"\n现货交易对列表已保存到: {spot_file}")
220
-
221
- # 保存合约列表
222
- if result['futures']:
223
- futures_file = os.path.join(output_dir, f"futures_symbols_by_volume_{timestamp}.json")
224
- with open(futures_file, 'w', encoding='utf-8') as f:
225
- json.dump(result['futures'], f, indent=2, ensure_ascii=False)
226
- print(f"合约交易对列表已保存到: {futures_file}")
227
-