get_dominant_future.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # 主力合约跟踪工具
  2. # 用于获取指定时间范围内某个或某些标的的主力合约变化信息
  3. from jqdata import *
  4. import pandas as pd
  5. from datetime import datetime, timedelta
  6. import warnings
  7. warnings.filterwarnings('ignore')
  8. def get_dominant_contracts_history(symbol_list, time_range):
  9. """
  10. 获取指定时间范围内某个或某些标的的主力合约历史变化
  11. 参数:
  12. symbol_list (list): 标的编码列表,如 ['FU', 'BU']
  13. time_range (list): 时间范围,格式为 ['20240101', '20250101']
  14. 返回:
  15. pandas.DataFrame: 包含以下列的数据框
  16. - symbol: 原始标的编码
  17. - dominant_contract: 主力合约完整编码
  18. - start_date: 主力合约开始日期
  19. - end_date: 主力合约结束日期
  20. """
  21. # 验证输入参数
  22. if not isinstance(symbol_list, list) or len(symbol_list) == 0:
  23. raise ValueError("symbol_list必须是非空列表")
  24. if not isinstance(time_range, list) or len(time_range) != 2:
  25. raise ValueError("time_range必须是包含两个日期的列表")
  26. # 转换日期格式
  27. try:
  28. start_date = datetime.strptime(time_range[0], '%Y%m%d').date()
  29. end_date = datetime.strptime(time_range[1], '%Y%m%d').date()
  30. except ValueError:
  31. raise ValueError("日期格式错误,请使用YYYYMMDD格式")
  32. if start_date >= end_date:
  33. raise ValueError("开始日期必须早于结束日期")
  34. print(f"开始获取主力合约数据...")
  35. print(f"标的列表: {symbol_list}")
  36. print(f"时间范围: {start_date} 至 {end_date}")
  37. # 存储所有结果的列表
  38. all_results = []
  39. # 逐个处理每个标的
  40. for symbol in symbol_list:
  41. print(f"\n处理标的: {symbol}")
  42. try:
  43. # 获取该标的在指定时间范围内的主力合约历史
  44. symbol_results = _get_symbol_dominant_history(symbol, start_date, end_date)
  45. all_results.extend(symbol_results)
  46. except Exception as e:
  47. print(f"处理标的 {symbol} 时发生错误: {str(e)}")
  48. continue
  49. # 将结果转换为DataFrame
  50. if all_results:
  51. result_df = pd.DataFrame(all_results)
  52. result_df = result_df.sort_values(['symbol', 'start_date']).reset_index(drop=True)
  53. print(f"\n成功获取 {len(result_df)} 条主力合约记录")
  54. return result_df
  55. else:
  56. print("\n未获取到任何主力合约数据")
  57. return pd.DataFrame(columns=['symbol', 'dominant_contract', 'start_date', 'end_date'])
  58. def _get_symbol_dominant_history(symbol, start_date, end_date):
  59. """
  60. 获取单个标的的主力合约历史变化
  61. 参数:
  62. symbol (str): 标的编码
  63. start_date (date): 开始日期
  64. end_date (date): 结束日期
  65. 返回:
  66. list: 主力合约记录列表
  67. """
  68. results = []
  69. current_date = start_date
  70. current_dominant = None
  71. period_start = None
  72. # 按日遍历时间范围
  73. while current_date <= end_date:
  74. # 跳过非交易日
  75. if not _is_trading_day(current_date):
  76. current_date += timedelta(days=1)
  77. continue
  78. try:
  79. # 获取当日主力合约
  80. dominant_contract = get_dominant_future(symbol, current_date)
  81. if dominant_contract is None:
  82. current_date += timedelta(days=1)
  83. continue
  84. # 如果是新的主力合约
  85. if dominant_contract != current_dominant:
  86. # 如果不是第一个合约,结束上一个合约的记录
  87. if current_dominant is not None and period_start is not None:
  88. results.append({
  89. 'symbol': symbol,
  90. 'dominant_contract': current_dominant,
  91. 'start_date': period_start,
  92. 'end_date': current_date - timedelta(days=1)
  93. })
  94. # 开始新合约的记录
  95. current_dominant = dominant_contract
  96. period_start = current_date
  97. print(f" {current_date}: 主力合约变更为 {dominant_contract}")
  98. except Exception as e:
  99. print(f" 获取 {current_date} 的主力合约时出错: {str(e)}")
  100. current_date += timedelta(days=1)
  101. # 处理最后一个合约
  102. if current_dominant is not None and period_start is not None:
  103. results.append({
  104. 'symbol': symbol,
  105. 'dominant_contract': current_dominant,
  106. 'start_date': period_start,
  107. 'end_date': end_date
  108. })
  109. return results
  110. def _is_trading_day(check_date):
  111. """
  112. 检查是否为交易日
  113. 参数:
  114. check_date (date): 要检查的日期
  115. 返回:
  116. bool: 是否为交易日
  117. """
  118. try:
  119. # 使用聚宽API检查交易日
  120. trade_days = get_trade_days(end_date=check_date, count=1)
  121. if len(trade_days) > 0:
  122. return trade_days[0].date() == check_date
  123. return False
  124. except:
  125. # 如果API调用失败,简单判断是否为工作日
  126. return check_date.weekday() < 5
  127. def get_dominant_contracts_summary(symbol_list, time_range):
  128. """
  129. 获取主力合约变化的汇总信息
  130. 参数:
  131. symbol_list (list): 标的编码列表
  132. time_range (list): 时间范围
  133. 返回:
  134. dict: 汇总信息
  135. """
  136. df = get_dominant_contracts_history(symbol_list, time_range)
  137. if df.empty:
  138. return {}
  139. summary = {}
  140. for symbol in symbol_list:
  141. symbol_data = df[df['symbol'] == symbol]
  142. if not symbol_data.empty:
  143. summary[symbol] = {
  144. '主力合约数量': len(symbol_data),
  145. '合约列表': symbol_data['dominant_contract'].tolist(),
  146. '平均持续天数': (symbol_data['end_date'] - symbol_data['start_date']).dt.days.mean()
  147. }
  148. else:
  149. summary[symbol] = {
  150. '主力合约数量': 0,
  151. '合约列表': [],
  152. '平均持续天数': 0
  153. }
  154. return summary
  155. def export_to_csv(symbol_list, time_range, filename=None):
  156. """
  157. 导出主力合约数据到CSV文件
  158. 参数:
  159. symbol_list (list): 标的编码列表
  160. time_range (list): 时间范围
  161. filename (str): 导出文件名,如果为None则自动生成
  162. 返回:
  163. str: 导出的文件路径
  164. """
  165. df = get_dominant_contracts_history(symbol_list, time_range)
  166. if filename is None:
  167. timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
  168. symbols_str = '_'.join(symbol_list)
  169. filename = f"dominant_contracts_{symbols_str}_{time_range[0]}_{time_range[1]}_{timestamp}.csv"
  170. df.to_csv(filename, index=False, encoding='utf-8-sig')
  171. print(f"数据已导出到: {filename}")
  172. return filename