| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- from datetime import timedelta, date
- from collections import defaultdict
- import logging
- # 配置日志记录
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(filename)s - %(message)s')
- class TradingCalculator:
- """
- 根据每日持仓信息计算交易日期和买卖点。
- """
- def __init__(self, pdf_files_info):
- """
- 初始化 TradingCalculator。
- Args:
- pdf_files_info (list): 包含字典的列表,每个字典代表一个 PDF 文件信息。
- 格式: [{'record_date': date, 'codes': [code1, ...], 'path': str}, ...]
- 需要确保列表按 record_date 升序排列。
- """
- self.pdf_files_info = pdf_files_info
- # 记录日期到持仓代码集合的映射
- self.holdings_by_record_date = {info['record_date']: set(info['codes']) for info in pdf_files_info}
- # 原始记录日期列表,已排序
- self.record_dates = sorted(self.holdings_by_record_date.keys())
- print(f"初始化 TradingCalculator,共有 {len(self.record_dates)} 个记录日期。")
- def determine_buy_sell_dates(self):
- """
- 根据记录日期和每日持仓,计算每个股票的买入卖出日期。
- Returns:
- list: 包含交易记录的列表。
- 格式: [{'code': str, 'buy_date': date or str, 'sell_date': date or str}, ...]
- 买入或卖出日期可能为字符串 "未知 (范围外)"。
- """
- if not self.record_dates:
- logging.warning("没有记录日期,无法计算买卖点。")
- return []
- # 按股票代码收集其出现的所有记录日期
- stock_record_dates = defaultdict(list)
- for record_date in self.record_dates:
- holdings = self.holdings_by_record_date.get(record_date, set())
- for code in holdings:
- stock_record_dates[code].append(record_date)
- transactions = []
- # record_dates 已经是排序好的完整日期列表
- all_available_dates = self.record_dates
- first_overall_record_date = all_available_dates[0]
- last_overall_record_date = all_available_dates[-1]
- for code, dates in stock_record_dates.items():
- if not dates:
- continue
- # dates 已经是 stock_record_dates 中按 key (record_date) 排序后append的,所以是排序好的
- # dates.sort() # 确保日期有序,但这里应该已经是排序好的
- # 识别连续的持有日期段(基于记录日期)
- segments = []
- current_segment = [dates[0]]
- for i in range(1, len(dates)):
- current_date = dates[i]
- last_date_in_segment = current_segment[-1]
- # 检查当前记录日期是否是上一记录日期的下一个日期(在 all_available_dates 列表中)
- try:
- last_date_index = all_available_dates.index(last_date_in_segment)
- # 确保索引+1在列表范围内,并且下一个日期就是当前日期
- if last_date_index + 1 < len(all_available_dates) and all_available_dates[last_date_index + 1] == current_date:
- # 日期连续
- current_segment.append(current_date)
- else:
- # 日期不连续,当前段结束,开始新段
- segments.append(current_segment)
- current_segment = [current_date]
- except ValueError:
- # This should not happen if dates come from self.record_dates
- logging.error(f"日期 {last_date_in_segment} 未在 record_dates 中找到,代码 {code}")
- # Treat as interruption if date is unexpected
- segments.append(current_segment)
- current_segment = [current_date]
- # 添加最后一个段
- if current_segment:
- segments.append(current_segment)
- # 根据连续段确定买卖日期
- # 买入日期是持有段开始记录日期的前一个记录日期(作为交易日)
- # 卖出日期是持有段结束记录日期的前一个记录日期(作为交易日),除非是最后一天,则卖出日期未知
- # PRD 提到交易日是上一个记录日,持仓是基于当天记录日。
- # 记录日 i 的持仓对应交易日 i-1
- # 持仓段 [记录日 i, 记录日 j] 对应的交易日段为 [记录日 i-1, 记录日 j-1]
- # 买入发生在 记录日 i-1
- # 卖出发生在 记录日 j
- for segment in segments:
- segment_start_record_date = segment[0] # 持有段的第一个记录日期
- segment_end_record_date = segment[-1] # 持有段的最后一个记录日期
- buy_date = None
- sell_date = None
- # 确定买入日期 (上一个记录日对应的交易日)
- if segment_start_record_date == first_overall_record_date:
- # 如果持有从第一个记录日开始,买入日期未知
- logging.info(f"{code} 在第一个记录日 {first_overall_record_date} 持有,买入日期未知 (范围外)。")
- buy_date = None
- else:
- # 找到 segment_start_record_date 在 all_available_dates 中的前一个记录日期
- try:
- start_date_index = all_available_dates.index(segment_start_record_date)
- if start_date_index > 0:
- # 买入日期是持有段开始记录日期的前一个记录日期
- buy_date = all_available_dates[start_date_index - 1]
- else:
- # 这应该被上面的 if 覆盖,作为备用
- logging.warning(f"{code} 的开始记录日期 {segment_start_record_date} 是记录日列表的第一个日期,买入日期未知。")
- buy_date = None
- except ValueError:
- logging.error(f"开始记录日期 {segment_start_record_date} 未在 record_dates 中找到,代码 {code}")
- buy_date = "错误" # Should not happen
- # 确定卖出日期
- if segment_end_record_date == last_overall_record_date:
- # 如果持有到最后一个记录日结束,卖出日期未知
- logging.info(f"{code} 在最后一个记录日 {last_overall_record_date} 仍持有,卖出日期未知 (范围外)。")
- sell_date = None
- else:
- # 卖出日期是持有段的最后一个记录日期本身 (因为PRD说卖出是当天)
- sell_date = segment_end_record_date
- logging.info(f"{code} 在 {sell_date} 卖出。")
- # 如果买入日期是日期对象,卖出日期也是日期对象,才添加交易
- # 如果任何一个未知,则不添加具体的交易记录,只记录日志
- if isinstance(buy_date, date) or isinstance(sell_date, date):
- transactions.append({'code': code, 'buy_date': buy_date, 'sell_date': sell_date})
- logging.info(f"{code} 的交易 ({buy_date} -> {sell_date}) 已添加。")
- else:
- logging.info(f"{code} 的交易 ({buy_date} -> {sell_date}) 因日期范围外而忽略。")
- print(f"获得了 {len(transactions)} 笔买卖交易 (含范围外未确定日期的)。")
- # 过滤掉未知日期的交易,只返回有确定买卖日期的交易
- final_transactions = [t for t in transactions if isinstance(t['buy_date'], date) and isinstance(t['sell_date'], date)]
- partial_transactions = [t for t in transactions if not isinstance(t['buy_date'], date) or not isinstance(t['sell_date'], date)]
- print(f"确定了 {len(final_transactions)} 笔有明确买卖日期的交易。")
- print(f"忽略了 {len(partial_transactions)} 笔有范围外未确定日期的交易。")
- return final_transactions
|