future_pattern_comprehensive.py 80 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783
  1. """
  2. 期货市场技术形态规律分析 - 综合版本
  3. 研究期货品种在大幅下跌后出现特定K线形态的规律,并分析后续走势
  4. 本文件包含:
  5. - 分析配置参数
  6. - 主要分析类FuturePatternAnalyzer
  7. - 基于合约切换日志的周线数据获取功能
  8. - 辅助工具函数和数据导出功能
  9. - 完整的分析流程
  10. 主要功能:
  11. 1. get_contract_weekly_data() - 基于合约切换日志获取周线数据的正式方法
  12. 2. get_weekly_data_by_contract_log() - 核心数据获取逻辑,直接使用get_bars获取原生周线数据
  13. 3. 简化的get_weekly_kline_data() - 直接使用8888加权合约,避免主力合约切换复杂性
  14. 4. validate_manual_patterns() - 手动模式验证系统,支持指定日期的模式验证
  15. 5. run_manual_pattern_validation() - 便捷的手动验证入口函数
  16. 6. _display_full_candlestick_charts() - 模式识别失败时的完整K线图显示
  17. 数据结构简化:
  18. - 移除了主力合约获取和切换逻辑
  19. - 使用单级字典结构:键为8888合约代码,值为日期范围
  20. - 所有数据检索均使用8888加权合约,简化了代码复杂性
  21. 验证模式:
  22. - 自动搜索模式:使用identify_target_pattern()自动搜索符合条件的技术形态
  23. - 手动验证模式:使用validate_manual_patterns()验证指定日期是否符合模式条件
  24. - 两种模式使用相同的验证逻辑和标准,但工作流程独立
  25. 作者: jukuan研究团队
  26. 日期: 2025-09
  27. 适用平台: 聚宽在线研究平台
  28. """
  29. import pandas as pd
  30. import numpy as np
  31. from jqdata import *
  32. import datetime
  33. import matplotlib.pyplot as plt
  34. import matplotlib.patches as patches
  35. from matplotlib.dates import DateFormatter
  36. import warnings
  37. warnings.filterwarnings('ignore')
  38. # 中文字体设置
  39. plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
  40. plt.rcParams['axes.unicode_minus'] = False
  41. # =====================================================================================
  42. # 第一部分:分析配置参数
  43. # 用户可根据需要调整以下参数来控制分析行为
  44. # =====================================================================================
  45. class AnalysisConfig:
  46. """分析配置类 - 集中管理所有分析参数"""
  47. # ==================== 分析时间设置 ====================
  48. START_DATE = '2022-01-01' # 分析起始日期 (建议至少2年数据)
  49. END_DATE = '2025-08-31' # 分析结束日期
  50. # ==================== 筛选条件设置 ====================
  51. MIN_DRAWDOWN_THRESHOLD = 0.30 # 最小回撤阈值 (0.3 = 30%)
  52. # K线形态识别条件
  53. KLINE_CONDITIONS = {
  54. 'max_open_close_diff': 0.01, # 开盘价与收盘价最大差异 (0.01 = 1%)
  55. 'min_high_open_gain': 0.03, # 最高价相对开盘价的最小涨幅 (0.03 = 3%)
  56. }
  57. # ==================== 分析参数设置 ====================
  58. ANALYSIS_EXTEND_DAYS = 30 # 扩展分析天数 (在下跌期间前后各扩展的天数)
  59. FOLLOW_UP_WEEKS = 10 # 后续跟踪周数
  60. SUCCESS_RETURN_THRESHOLD = 0.05 # 成功率计算的收益率阈值 (0.05 = 5%)
  61. # ==================== 输出设置 ====================
  62. GENERATE_CHARTS = True # 是否生成K线图
  63. MAX_CHART_COUNT = 10 # 最大图表生成数量 (避免生成过多图片)
  64. CHART_OUTPUT_DIR = './' # 图表保存目录
  65. CSV_ENCODING = 'utf-8-sig' # CSV文件编码
  66. # ==================== 期货品种过滤设置 ====================
  67. # 需要排除的期货品种 (可根据需要调整)
  68. EXCLUDED_FUTURES = [
  69. 'IF9999.CCFX', #
  70. 'LU8888.XINE', #
  71. 'NR8888.XINE', #
  72. 'LG8888.XDCE', # 原木 - 新上市品种数据较少
  73. ]
  74. # 重点关注的期货品种 (如果设置,将只分析这些品种)
  75. FOCUS_FUTURES = [] # 空列表表示分析所有品种
  76. # ==================== 分析规模控制 ====================
  77. MAX_ANALYSIS_FUTURES = 5 # 最大分析期货品种数 (-1表示分析所有品种)
  78. VERBOSE_LOGGING = True # 是否打印详细日志
  79. SHOW_PROGRESS = True # 是否在分析过程中显示进度
  80. # ==================== 数据质量控制 ====================
  81. MIN_DATA_LENGTH = 100 # 最小数据长度要求 (日线数据点数)
  82. MIN_WEEKLY_DATA = 20 # 最小周线数据要求
  83. MAX_MISSING_DATA_RATIO = 0.1 # 最大缺失数据比例
  84. @classmethod
  85. def get_config_dict(cls):
  86. """获取分析配置字典"""
  87. return {
  88. 'time_range': {
  89. 'start_date': cls.START_DATE,
  90. 'end_date': cls.END_DATE,
  91. },
  92. 'filter_conditions': {
  93. 'min_drawdown': cls.MIN_DRAWDOWN_THRESHOLD,
  94. 'kline_conditions': cls.KLINE_CONDITIONS,
  95. },
  96. 'analysis_params': {
  97. 'extend_days': cls.ANALYSIS_EXTEND_DAYS,
  98. 'follow_up_weeks': cls.FOLLOW_UP_WEEKS,
  99. 'success_threshold': cls.SUCCESS_RETURN_THRESHOLD,
  100. },
  101. 'output_settings': {
  102. 'generate_charts': cls.GENERATE_CHARTS,
  103. 'max_chart_count': cls.MAX_CHART_COUNT,
  104. 'chart_dir': cls.CHART_OUTPUT_DIR,
  105. 'csv_encoding': cls.CSV_ENCODING,
  106. },
  107. 'future_filter': {
  108. 'excluded': cls.EXCLUDED_FUTURES,
  109. 'focus_only': cls.FOCUS_FUTURES,
  110. },
  111. 'analysis_control': {
  112. 'max_futures': cls.MAX_ANALYSIS_FUTURES,
  113. 'verbose': cls.VERBOSE_LOGGING,
  114. 'show_progress': cls.SHOW_PROGRESS,
  115. },
  116. 'data_quality': {
  117. 'min_data_length': cls.MIN_DATA_LENGTH,
  118. 'min_weekly_data': cls.MIN_WEEKLY_DATA,
  119. 'max_missing_ratio': cls.MAX_MISSING_DATA_RATIO,
  120. }
  121. }
  122. @classmethod
  123. def print_config(cls):
  124. """打印当前配置信息"""
  125. print("=== 期货技术形态分析配置 ===")
  126. print(f"分析时间范围: {cls.START_DATE} 至 {cls.END_DATE}")
  127. print(f"最小回撤阈值: {cls.MIN_DRAWDOWN_THRESHOLD*100:.0f}%")
  128. print(f"开收差异阈值: {cls.KLINE_CONDITIONS['max_open_close_diff']*100:.0f}%")
  129. print(f"最高涨幅阈值: {cls.KLINE_CONDITIONS['min_high_open_gain']*100:.0f}%")
  130. print(f"后续跟踪周数: {cls.FOLLOW_UP_WEEKS}")
  131. print(f"成功率阈值: {cls.SUCCESS_RETURN_THRESHOLD*100:.0f}%")
  132. if cls.EXCLUDED_FUTURES:
  133. print(f"排除品种数: {len(cls.EXCLUDED_FUTURES)}")
  134. if cls.FOCUS_FUTURES:
  135. print(f"重点品种数: {len(cls.FOCUS_FUTURES)}")
  136. print(f"生成图表: {'是' if cls.GENERATE_CHARTS else '否'}")
  137. print(f"最大分析品种数: {'所有' if cls.MAX_ANALYSIS_FUTURES == -1 else cls.MAX_ANALYSIS_FUTURES}")
  138. # =====================================================================================
  139. # 第二部分:期货技术形态分析器主类
  140. # 实现完整的技术形态识别和分析功能
  141. # =====================================================================================
  142. class FuturePatternAnalyzer:
  143. """期货技术形态分析器 - 主要分析类"""
  144. def __init__(self, start_date=None, end_date=None, config=None):
  145. """
  146. 初始化分析器
  147. 参数:
  148. - start_date: 分析开始日期,默认使用配置
  149. - end_date: 分析结束日期,默认使用配置
  150. - config: 自定义配置,默认使用AnalysisConfig
  151. """
  152. # 使用提供的配置或默认配置
  153. if config is None:
  154. config = AnalysisConfig.get_config_dict()
  155. self.start_date = pd.to_datetime(start_date or config['time_range']['start_date'])
  156. self.end_date = pd.to_datetime(end_date or config['time_range']['end_date'])
  157. self.config = config
  158. self.future_codes = {} # 期货代码映射
  159. # 期货品种与交易所的映射关系
  160. self.exchange_map = {
  161. # 大连商品交易所 (XDCE)
  162. 'A': 'XDCE', 'B': 'XDCE', 'C': 'XDCE', 'CS': 'XDCE', 'FB': 'XDCE',
  163. 'I': 'XDCE', 'J': 'XDCE', 'JD': 'XDCE', 'JM': 'XDCE', 'L': 'XDCE',
  164. 'M': 'XDCE', 'P': 'XDCE', 'PP': 'XDCE', 'PG': 'XDCE', 'RR': 'XDCE',
  165. 'V': 'XDCE', 'Y': 'XDCE', 'EB': 'XDCE', 'EG': 'XDCE', 'LH': 'XDCE',
  166. # 郑州商品交易所 (XZCE)
  167. 'AP': 'XZCE', 'CF': 'XZCE', 'CY': 'XZCE', 'FG': 'XZCE', 'JR': 'XZCE',
  168. 'LR': 'XZCE', 'MA': 'XZCE', 'OI': 'XZCE', 'PM': 'XZCE', 'RI': 'XZCE',
  169. 'RM': 'XZCE', 'RS': 'XZCE', 'SF': 'XZCE', 'SM': 'XZCE', 'SR': 'XZCE',
  170. 'TA': 'XZCE', 'WH': 'XZCE', 'ZC': 'XZCE', 'CJ': 'XZCE',
  171. 'ME': 'XZCE', 'PF': 'XZCE', 'PK': 'XZCE', 'RO': 'XZCE', 'SA': 'XZCE',
  172. 'TC': 'XZCE', 'UR': 'XZCE', 'WS': 'XZCE', 'WT': 'XZCE',
  173. # 上海期货交易所 (XSGE)
  174. 'AG': 'XSGE', 'AL': 'XSGE', 'AU': 'XSGE', 'BU': 'XSGE', 'CU': 'XSGE',
  175. 'FU': 'XSGE', 'HC': 'XSGE', 'NI': 'XSGE', 'PB': 'XSGE', 'RB': 'XSGE',
  176. 'RU': 'XSGE', 'SN': 'XSGE', 'SP': 'XSGE', 'SS': 'XSGE', 'WR': 'XSGE',
  177. 'ZN': 'XSGE',
  178. # 上海国际能源交易中心 (XINE)
  179. 'BC': 'XINE', 'LU': 'XINE', 'NR': 'XINE', 'SC': 'XINE',
  180. # 中金所 (CCFX)
  181. 'IC': 'CCFX', 'IF': 'CCFX', 'IH': 'CCFX', 'T': 'CCFX', 'TF': 'CCFX', 'TS': 'CCFX',
  182. # 广期所 (GFEX)
  183. 'SI': 'GFEX', 'LC': 'GFEX', 'PS': 'GFEX'
  184. }
  185. self._log(f"初始化分析器 - 分析期间: {self.start_date.strftime('%Y-%m-%d')} 至 {self.end_date.strftime('%Y-%m-%d')}")
  186. def _log(self, message):
  187. """记录日志"""
  188. if self.config['analysis_control']['verbose']:
  189. print(message)
  190. def build_future_codes(self):
  191. """构建期货品种基础数据"""
  192. self._log("\n=== 步骤1: 构建期货品种基础数据 ===")
  193. # 构建8888主连合约代码
  194. for code, exchange in self.exchange_map.items():
  195. future_code = f"{code}8888.{exchange}"
  196. # 应用过滤器
  197. if future_code in self.config['future_filter']['excluded']:
  198. continue
  199. # 如果设置了重点关注品种,只添加重点品种
  200. if (self.config['future_filter']['focus_only'] and
  201. future_code not in self.config['future_filter']['focus_only']):
  202. continue
  203. self.future_codes[code] = future_code
  204. self._log(f"构建完成,共{len(self.future_codes)}个期货品种")
  205. if self.config['analysis_control']['verbose']:
  206. self._log("示例品种:")
  207. for i, (code, full_code) in enumerate(list(self.future_codes.items())[:5]):
  208. self._log(f" {code} -> {full_code}")
  209. return self.future_codes
  210. def get_future_price_data(self, future_code, start_date, end_date):
  211. """获取期货价格数据"""
  212. try:
  213. # print(f"获取{future_code}数据: {start_date} 至 {end_date}")
  214. data = get_price(
  215. future_code,
  216. start_date=start_date,
  217. end_date=end_date,
  218. frequency='daily',
  219. fields=['open', 'close', 'high', 'low', 'volume'],
  220. skip_paused=False,
  221. panel=False
  222. )
  223. # print(f"获取的最后一条数据: {data.iloc[-1]}")
  224. return data
  225. except Exception as e:
  226. if self.config['analysis_control']['verbose']:
  227. print(f" 获取{future_code}数据失败: {str(e)}")
  228. return None
  229. def calculate_drawdown(self, data):
  230. """计算最大回撤"""
  231. if data is None or len(data) == 0:
  232. return None, None, None, None
  233. # 计算累计最高价
  234. data['cum_max'] = data['high'].expanding().max()
  235. # 计算回撤
  236. data['drawdown'] = (data['low'] - data['cum_max']) / data['cum_max']
  237. # 找到最大回撤
  238. max_drawdown = data['drawdown'].min() # 最大回撤是负值,所以用min
  239. max_drawdown_idx = data['drawdown'].idxmin()
  240. # 找到最大回撤对应的高点
  241. max_high_before_dd = data.loc[:max_drawdown_idx, 'cum_max'].iloc[-1]
  242. high_point_idx = data[data['high'] == max_high_before_dd].index[0]
  243. return abs(max_drawdown), high_point_idx, max_drawdown_idx, data
  244. def find_major_decline_futures(self, min_drawdown=None):
  245. """筛选大幅下跌的期货品种"""
  246. if min_drawdown is None:
  247. min_drawdown = self.config['filter_conditions']['min_drawdown']
  248. self._log(f"\n=== 步骤2: 筛选跌幅>{min_drawdown*100}%的期货品种 ===")
  249. decline_futures = []
  250. total_count = len(self.future_codes)
  251. max_futures = self.config['analysis_control']['max_futures']
  252. analyzed_count = 0
  253. for code, future_code in self.future_codes.items():
  254. analyzed_count += 1
  255. # 最大品种数限制
  256. if max_futures > 0 and analyzed_count > max_futures:
  257. break
  258. if self.config['analysis_control']['show_progress']:
  259. display_total = min(total_count, max_futures) if max_futures > 0 else total_count
  260. print(f"分析 {code}({future_code}) [{analyzed_count}/{display_total}]", end=" ")
  261. # 获取价格数据
  262. price_data = self.get_future_price_data(future_code, self.start_date, self.end_date)
  263. if price_data is None or len(price_data) < self.config['data_quality']['min_data_length']:
  264. if self.config['analysis_control']['show_progress']:
  265. print("- 数据不足")
  266. continue
  267. # 计算最大回撤
  268. max_drawdown, high_date, low_date, data_with_dd = self.calculate_drawdown(price_data)
  269. if max_drawdown is None or max_drawdown < min_drawdown:
  270. if self.config['analysis_control']['show_progress']:
  271. print(f"- 最大回撤{max_drawdown:.1%}不符合条件")
  272. continue
  273. # 记录符合条件的品种
  274. decline_info = {
  275. '品种代码': code,
  276. '合约代码': future_code,
  277. '最大回撤': max_drawdown,
  278. '高点日期': high_date,
  279. '低点日期': low_date,
  280. '高点价格': data_with_dd.loc[high_date, 'high'],
  281. '低点价格': data_with_dd.loc[low_date, 'low'],
  282. '下跌天数': (low_date - high_date).days
  283. }
  284. decline_futures.append(decline_info)
  285. if self.config['analysis_control']['show_progress']:
  286. print(f"✓ 最大回撤{max_drawdown:.1%}, 下跌期间{high_date.strftime('%Y-%m-%d')}至{low_date.strftime('%Y-%m-%d')}")
  287. self._log(f"\n找到{len(decline_futures)}个符合条件的期货品种")
  288. return decline_futures
  289. def get_simplified_contract_mapping(self, decline_futures):
  290. """获取8888加权合约映射 - 简化版本,直接使用加权合约"""
  291. self._log("\n=== 步骤3: 创建8888加权合约映射 ===")
  292. contract_mapping = {}
  293. for future_info in decline_futures:
  294. code = future_info['品种代码']
  295. high_date = future_info['高点日期']
  296. low_date = future_info['低点日期']
  297. # 扩展日期范围前后30天
  298. extend_days = self.config['analysis_params']['extend_days']
  299. start_date = high_date - pd.Timedelta(days=extend_days)
  300. end_date = low_date + pd.Timedelta(days=extend_days)
  301. # 构建8888加权合约代码
  302. contract_8888 = self.future_codes.get(code)
  303. if contract_8888:
  304. # 直接使用8888合约,时间范围为分析期间
  305. contract_mapping[contract_8888] = [start_date, end_date]
  306. self._log(f" ✓ {code}: 使用8888加权合约 {contract_8888}")
  307. self._log(f" 时间范围: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
  308. else:
  309. self._log(f" ⚠️ {code}: 未找到对应的8888合约代码")
  310. self._log(f"完成8888合约映射,共{len(contract_mapping)}个合约")
  311. return contract_mapping
  312. def get_weekly_kline_data(self, contract_mapping):
  313. """获取周线K线数据 - 简化版本,直接使用8888加权合约"""
  314. self._log("\n=== 步骤4: 获取周线K线数据 ===")
  315. weekly_data = {}
  316. for contract_code, time_range in contract_mapping.items():
  317. start_date, end_date = time_range
  318. self._log(f"获取{contract_code}周线数据 ({start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')})")
  319. try:
  320. # 计算需要获取的周数,确保充足的数据覆盖
  321. time_diff = end_date - start_date
  322. estimated_weeks = int(time_diff.days / 7) + 20 # 额外增加20周缓冲
  323. estimated_weeks = max(estimated_weeks, 40) # 最少40周
  324. self._log(f" 计算获取{estimated_weeks}周数据")
  325. # 直接使用get_bars API获取原生周线数据,避免日线转换的精度损失
  326. weekly_bars = get_bars(
  327. security=contract_code,
  328. count=estimated_weeks,
  329. unit='1w', # 直接获取周线数据
  330. fields=['date', 'open', 'high', 'low', 'close', 'volume'],
  331. include_now=False,
  332. end_dt=end_date,
  333. fq_ref_date=None,
  334. df=True
  335. )
  336. if weekly_bars is not None and len(weekly_bars) > 0:
  337. # 确保日期类型一致,避免 'Cannot compare type Timestamp with type date' 错误
  338. # get_bars返回的date列可能是date类型,需统一转换为pandas Timestamp
  339. weekly_bars['date'] = pd.to_datetime(weekly_bars['date'])
  340. start_date = pd.to_datetime(start_date)
  341. end_date = pd.to_datetime(end_date)
  342. # 筛选在指定时间范围内的数据
  343. mask = (weekly_bars['date'] >= start_date) & (weekly_bars['date'] <= end_date)
  344. filtered_weekly_data = weekly_bars[mask].copy()
  345. if len(filtered_weekly_data) > 0:
  346. # 设置日期为索引
  347. filtered_weekly_data.set_index('date', inplace=True)
  348. # 从合约代码中提取品种代码(用于后续识别)
  349. symbol_code = contract_code.replace('8888.', '').split('.')[0]
  350. weekly_data[symbol_code] = {
  351. 'data': filtered_weekly_data,
  352. 'contract_code': contract_code,
  353. 'time_range': time_range
  354. }
  355. self._log(f" ✓ {symbol_code}({contract_code}): 获取{len(filtered_weekly_data)}根周K线")
  356. else:
  357. self._log(f" ⚠️ {contract_code}: 筛选后周线数据不足")
  358. else:
  359. self._log(f" ⚠️ {contract_code}: 未获取到周线数据")
  360. except Exception as e:
  361. self._log(f" ❌ 获取{contract_code}周线数据失败: {str(e)}")
  362. continue
  363. self._log(f"完成周线数据获取,共{len(weekly_data)}个品种")
  364. return weekly_data
  365. def get_weekly_data_by_contract_log(self, contract_switch_log, extend_weeks=10):
  366. """
  367. 基于合约切换日志获取每个合约的周线数据
  368. 参数:
  369. - contract_switch_log: 合约切换日志,格式为列表,每个元素包含:
  370. {'contract': '合约代码', 'start_date': '开始日期', 'end_date': '结束日期'}
  371. - extend_weeks: 延长周数,默认10周
  372. 返回:
  373. - dict: 每个合约的周线数据,键为合约代码,值为数据和时间范围信息
  374. """
  375. self._log(f"\n=== 基于合约切换日志获取周线数据 ===")
  376. self._log(f"延长周数: {extend_weeks}周")
  377. weekly_data_by_contract = {}
  378. for contract_info in contract_switch_log:
  379. contract_code = contract_info.get('contract')
  380. start_date = contract_info.get('start_date')
  381. end_date = contract_info.get('end_date')
  382. if not all([contract_code, start_date, end_date]):
  383. self._log(f"⚠️ 合约信息不完整,跳过: {contract_info}")
  384. continue
  385. # 转换日期格式
  386. if isinstance(start_date, str):
  387. start_date = pd.to_datetime(start_date)
  388. if isinstance(end_date, str):
  389. end_date = pd.to_datetime(end_date)
  390. # 计算延长后的时间范围
  391. extended_start_date = start_date - pd.Timedelta(weeks=extend_weeks)
  392. extended_end_date = end_date + pd.Timedelta(weeks=extend_weeks)
  393. self._log(f"处理合约: {contract_code}")
  394. self._log(f" 原始时间范围: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
  395. self._log(f" 延长时间范围: {extended_start_date.strftime('%Y-%m-%d')} 至 {extended_end_date.strftime('%Y-%m-%d')}")
  396. try:
  397. # 计算需要获取的周数,采用更稳健的方式
  398. time_diff = extended_end_date - extended_start_date
  399. estimated_weeks = int(time_diff.days / 7) + 20 # 额外增加20周作为充足缓冲
  400. # 确保最少获取60周数据(约1.2年)以提供充分的历史背景
  401. estimated_weeks = max(estimated_weeks, 60)
  402. self._log(f" 计算获取{estimated_weeks}周数据 (时间跨度{time_diff.days}天)")
  403. # 直接使用get_bars API获取原生周线数据,避免日线转换的精度损失
  404. weekly_bars = get_bars(
  405. security=contract_code,
  406. count=estimated_weeks, # 扩展的周数以确保完整覆盖
  407. unit='1w', # 直接获取周线数据,避免转换精度损失
  408. fields=['date', 'open', 'high', 'low', 'close', 'volume'],
  409. include_now=False, # 不包含当前未完成的周期
  410. end_dt=extended_end_date,
  411. fq_ref_date=None, # 不复权,保持原始价格
  412. df=True # 返回DataFrame格式便于处理
  413. )
  414. if weekly_bars is not None and len(weekly_bars) > 0:
  415. # 确保日期类型一致,避免 'Cannot compare type Timestamp with type date' 错误
  416. # get_bars返回的date列可能是date类型,需统一转换为pandas Timestamp
  417. weekly_bars['date'] = pd.to_datetime(weekly_bars['date'])
  418. extended_start_date = pd.to_datetime(extended_start_date)
  419. extended_end_date = pd.to_datetime(extended_end_date)
  420. # 筛选在延长时间范围内的数据
  421. mask = (weekly_bars['date'] >= extended_start_date) & (weekly_bars['date'] <= extended_end_date)
  422. filtered_data = weekly_bars[mask].copy()
  423. if len(filtered_data) > 0:
  424. # 设置日期为索引
  425. filtered_data.set_index('date', inplace=True)
  426. # 存储数据
  427. weekly_data_by_contract[contract_code] = {
  428. 'data': filtered_data,
  429. 'original_start': start_date,
  430. 'original_end': end_date,
  431. 'extended_start': extended_start_date,
  432. 'extended_end': extended_end_date,
  433. 'total_weeks': len(filtered_data)
  434. }
  435. self._log(f" ✓ 获取到{len(filtered_data)}根周K线数据")
  436. self._log(f" 数据时间范围: {filtered_data.index[0].strftime('%Y-%m-%d')} 至 {filtered_data.index[-1].strftime('%Y-%m-%d')}")
  437. # 添加数据质量检查
  438. original_start_ts = pd.to_datetime(start_date)
  439. original_end_ts = pd.to_datetime(end_date)
  440. original_weeks = int((original_end_ts - original_start_ts).days / 7) + 1
  441. expected_total_weeks = original_weeks + 2 * extend_weeks
  442. if len(filtered_data) < expected_total_weeks * 0.7: # 如果数据不足预期的70%
  443. self._log(f" ⚠️ 数据可能不完整,预期约{expected_total_weeks}周,实际{len(filtered_data)}周")
  444. else:
  445. self._log(f" ⚠️ 筛选后无有效数据")
  446. else:
  447. self._log(f" ⚠️ 未获取到周线数据")
  448. except Exception as e:
  449. self._log(f" ❌ 获取{contract_code}周线数据失败: {str(e)}")
  450. continue
  451. self._log(f"\n成功获取{len(weekly_data_by_contract)}个合约的周线数据")
  452. return weekly_data_by_contract
  453. def identify_target_pattern(self, weekly_data, decline_futures):
  454. """
  455. 识别目标K线形态
  456. 当未识别到任何目标形态时,会自动显示完整的烛台图(K线图)作为数据可视化备选方案。
  457. 图表包含完整的OHLC数据,直接显示不保存到本地存储。
  458. """
  459. self._log("\n=== 步骤5: 识别目标K线形态 ===")
  460. pattern_results = []
  461. kline_conditions = self.config['filter_conditions']['kline_conditions']
  462. # 创建品种代码到下跌信息的映射
  463. decline_info_map = {future['品种代码']: future for future in decline_futures}
  464. for code, data_info in weekly_data.items():
  465. data = data_info['data']
  466. contract_code = data_info['contract_code'] # 8888加权合约代码
  467. # 从decline_futures中获取下跌时间信息
  468. if code not in decline_info_map:
  469. continue
  470. decline_info = decline_info_map[code]
  471. decline_start = decline_info['高点日期']
  472. decline_end = decline_info['低点日期']
  473. extend_days = self.config['analysis_params']['extend_days']
  474. self._log(f"分析{code}的K线形态 (使用{contract_code})")
  475. for idx, (date, row) in enumerate(data.iterrows()):
  476. open_price = row['open']
  477. close_price = row['close']
  478. high_price = row['high']
  479. low_price = row['low']
  480. volume = row['volume']
  481. # 检查是否在下跌期间或附近
  482. date_in_range = (date >= decline_start - pd.Timedelta(days=extend_days) and
  483. date <= decline_end + pd.Timedelta(days=extend_days))
  484. if not date_in_range:
  485. continue
  486. # 使用统一的核心验证逻辑
  487. validation_result = self._core_pattern_validation(date, row, kline_conditions)
  488. if validation_result['validation_passed']:
  489. pattern_info = {
  490. '品种代码': code,
  491. '日期': date,
  492. '开盘价': validation_result['ohlc_data']['open'],
  493. '收盘价': validation_result['ohlc_data']['close'],
  494. '最高价': validation_result['ohlc_data']['high'],
  495. '最低价': validation_result['ohlc_data']['low'],
  496. '成交量': validation_result['ohlc_data']['volume'],
  497. '开收差异率': validation_result['indicators']['price_diff_pct']['actual_value'],
  498. '最高涨幅率': validation_result['indicators']['high_gain_pct']['actual_value'],
  499. '下跌开始日期': decline_start,
  500. '下跌结束日期': decline_end,
  501. '数据索引': idx,
  502. '使用合约': contract_code # 8888加权合约
  503. }
  504. pattern_results.append(pattern_info)
  505. price_diff_pct = validation_result['indicators']['price_diff_pct']['actual_value'] / 100
  506. high_gain_pct = validation_result['indicators']['high_gain_pct']['actual_value'] / 100
  507. self._log(f" ✓ 找到形态: {date.strftime('%Y-%m-%d')} 开收差{price_diff_pct:.1%} 最高涨{high_gain_pct:.1%} 合约{contract_code}")
  508. self._log(f"\n共识别出{len(pattern_results)}个目标K线形态")
  509. # 如果未识别到任何模式,显示完整的烛台图
  510. if len(pattern_results) == 0:
  511. self._log("\n⚠️ 未识别到任何目标形态,显示完整数据集的K线图")
  512. self._display_full_candlestick_charts(weekly_data)
  513. return pattern_results
  514. def _display_full_candlestick_charts(self, weekly_data):
  515. """显示完整的烛台图(K线图)"""
  516. self._log("=== 显示完整数据集的K线图 ===")
  517. for code, data_info in weekly_data.items():
  518. data = data_info['data']
  519. contract_code = data_info['contract_code']
  520. if data is None or len(data) == 0:
  521. self._log(f"⚠️ {code}: 无可用数据")
  522. continue
  523. self._log(f"绘制{code}({contract_code})的完整K线图,共{len(data)}根K线")
  524. try:
  525. # 创建图表
  526. fig, ax = plt.subplots(figsize=(16, 10))
  527. # 准备数据用于绘制烛台图
  528. dates = data.index
  529. opens = data['open']
  530. highs = data['high']
  531. lows = data['low']
  532. closes = data['close']
  533. volumes = data['volume']
  534. # 绘制K线图
  535. for i in range(len(data)):
  536. date = dates[i]
  537. open_price = opens.iloc[i]
  538. high_price = highs.iloc[i]
  539. low_price = lows.iloc[i]
  540. close_price = closes.iloc[i]
  541. # K线颜色:红涨绿跌
  542. color = 'red' if close_price > open_price else 'green'
  543. edge_color = 'darkred' if close_price > open_price else 'darkgreen'
  544. # 绘制影线(最高价到最低价的竖线)
  545. ax.plot([i, i], [low_price, high_price], color='black', linewidth=1)
  546. # 绘制实体(开盘价到收盘价的矩形)
  547. body_height = abs(close_price - open_price)
  548. bottom = min(open_price, close_price)
  549. # 使用矩形绘制K线实体
  550. rect = patches.Rectangle((i-0.4, bottom), 0.8, body_height,
  551. linewidth=1, edgecolor=edge_color,
  552. facecolor=color, alpha=0.8)
  553. ax.add_patch(rect)
  554. # 设置图表标题和标签
  555. ax.set_title(f'{code} ({contract_code}) 完整周线K线图\n'
  556. f'数据期间: {dates[0].strftime("%Y-%m-%d")} 至 {dates[-1].strftime("%Y-%m-%d")} '
  557. f'(共{len(data)}根K线)',
  558. fontsize=14, fontweight='bold', pad=20)
  559. ax.set_xlabel('时间', fontsize=12)
  560. ax.set_ylabel('价格', fontsize=12)
  561. ax.grid(True, alpha=0.3)
  562. # 设置x轴标签
  563. # 选择合适的标签间隔,避免过于密集
  564. step = max(1, len(data) // 10) # 显示约10个时间标签
  565. tick_positions = range(0, len(data), step)
  566. tick_labels = [dates[i].strftime('%Y-%m-%d') for i in tick_positions]
  567. ax.set_xticks(tick_positions)
  568. ax.set_xticklabels(tick_labels, rotation=45, ha='right')
  569. # 添加统计信息
  570. max_price = highs.max()
  571. min_price = lows.min()
  572. latest_close = closes.iloc[-1]
  573. total_change = (latest_close - closes.iloc[0]) / closes.iloc[0] * 100
  574. # 在图表上添加统计文本
  575. stats_text = (f'最高价: {max_price:.2f}\n'
  576. f'最低价: {min_price:.2f}\n'
  577. f'最新收盘: {latest_close:.2f}\n'
  578. f'总涨跌幅: {total_change:+.2f}%')
  579. ax.text(0.02, 0.98, stats_text, transform=ax.transAxes,
  580. verticalalignment='top', bbox=dict(boxstyle='round',
  581. facecolor='wheat', alpha=0.8), fontsize=10)
  582. # 调整布局并显示
  583. plt.tight_layout()
  584. plt.show()
  585. self._log(f"✓ 已显示{code}的完整K线图")
  586. except Exception as e:
  587. self._log(f"❌ 绘制{code}K线图时出错: {str(e)}")
  588. continue
  589. self._log("完成所有品种的K线图显示")
  590. def _core_pattern_validation(self, target_date, kline_data_row, kline_conditions):
  591. """
  592. 统一的核心模式验证逻辑
  593. 此函数包含两种模式共同的验证逻辑,消除代码重复。
  594. 无论是自动模式遍历所有K线,还是验证模式检查特定日期,
  595. 都使用相同的验证标准和计算方法。
  596. 参数:
  597. - target_date: 目标日期 (pandas Timestamp)
  598. - kline_data_row: 单根K线数据 (pandas Series,包含open、high、low、close、volume)
  599. - kline_conditions: 验证条件配置字典
  600. 返回:
  601. - dict: 包含验证结果和详细诊断信息的字典
  602. """
  603. # 提取OHLC数据
  604. open_price = kline_data_row['open']
  605. close_price = kline_data_row['close']
  606. high_price = kline_data_row['high']
  607. low_price = kline_data_row['low']
  608. volume = kline_data_row['volume']
  609. # 计算模式指标
  610. price_diff_pct = abs(close_price - open_price) / open_price
  611. high_gain_pct = (high_price - open_price) / open_price
  612. # 验证条件A: 开盘价与收盘价差异
  613. condition_a = price_diff_pct <= kline_conditions['max_open_close_diff']
  614. condition_a_threshold = kline_conditions['max_open_close_diff']
  615. # 验证条件B: 最高价相对开盘价涨幅
  616. condition_b = high_gain_pct >= kline_conditions['min_high_open_gain']
  617. condition_b_threshold = kline_conditions['min_high_open_gain']
  618. # 整体验证结果
  619. validation_passed = condition_a and condition_b
  620. # 构建详细的验证结果
  621. result = {
  622. 'date': target_date,
  623. 'validation_passed': validation_passed,
  624. 'ohlc_data': {
  625. 'open': round(open_price, 2),
  626. 'high': round(high_price, 2),
  627. 'low': round(low_price, 2),
  628. 'close': round(close_price, 2),
  629. 'volume': volume
  630. },
  631. 'indicators': {
  632. 'price_diff_pct': {
  633. 'actual_value': round(price_diff_pct * 100, 2),
  634. 'threshold': round(condition_a_threshold * 100, 2),
  635. 'unit': '%',
  636. 'condition': '≤',
  637. 'passed': condition_a,
  638. 'description': '开盘价与收盘价差异率'
  639. },
  640. 'high_gain_pct': {
  641. 'actual_value': round(high_gain_pct * 100, 2),
  642. 'threshold': round(condition_b_threshold * 100, 2),
  643. 'unit': '%',
  644. 'condition': '≥',
  645. 'passed': condition_b,
  646. 'description': '最高价相对开盘价涨幅'
  647. }
  648. }
  649. }
  650. # 生成失败原因
  651. if not validation_passed:
  652. failed_conditions = []
  653. if not condition_a:
  654. failed_conditions.append(f"开收差异率{price_diff_pct*100:.2f}% > 阈值{condition_a_threshold*100:.1f}%")
  655. if not condition_b:
  656. failed_conditions.append(f"最高涨幅{high_gain_pct*100:.2f}% < 阈值{condition_b_threshold*100:.1f}%")
  657. result['failure_reason'] = "; ".join(failed_conditions)
  658. return result
  659. def validate_manual_patterns(self, manual_config, weekly_data):
  660. """
  661. 手动验证模式功能
  662. 参数:
  663. - manual_config: 手动配置字典,格式:
  664. {'B':['2022-07-22','2024-08-23'], 'A':['2023-01-06','2023-06-09']}
  665. - weekly_data: 周线数据字典
  666. 返回:
  667. - dict: 验证结果,包含成功和失败的详细信息
  668. """
  669. self._log("\n=== 手动模式验证 ===")
  670. self._log(f"配置的模式类型数: {len(manual_config)}")
  671. validation_results = {
  672. 'successful_validations': [],
  673. 'failed_validations': [],
  674. 'summary': {}
  675. }
  676. kline_conditions = self.config['filter_conditions']['kline_conditions']
  677. for pattern_type, date_list in manual_config.items():
  678. self._log(f"\n处理模式类型 '{pattern_type}': {len(date_list)} 个日期")
  679. type_results = {
  680. 'pattern_type': pattern_type,
  681. 'total_dates': len(date_list),
  682. 'successful_count': 0,
  683. 'failed_count': 0,
  684. 'validations': []
  685. }
  686. for date_str in date_list:
  687. try:
  688. # 转换日期格式
  689. target_date = pd.to_datetime(date_str)
  690. self._log(f" 验证日期: {target_date.strftime('%Y-%m-%d')}")
  691. # 验证这个日期
  692. validation_result = self._validate_single_date(
  693. target_date, pattern_type, weekly_data, kline_conditions
  694. )
  695. type_results['validations'].append(validation_result)
  696. if validation_result['validation_passed']:
  697. type_results['successful_count'] += 1
  698. validation_results['successful_validations'].append(validation_result)
  699. self._log(f" ✓ 验证通过")
  700. else:
  701. type_results['failed_count'] += 1
  702. validation_results['failed_validations'].append(validation_result)
  703. self._log(f" ❌ 验证失败: {validation_result['failure_reason']}")
  704. except Exception as e:
  705. error_result = {
  706. 'pattern_type': pattern_type,
  707. 'target_date': date_str,
  708. 'validation_passed': False,
  709. 'failure_reason': f'日期处理错误: {str(e)}',
  710. 'error': True
  711. }
  712. type_results['validations'].append(error_result)
  713. type_results['failed_count'] += 1
  714. validation_results['failed_validations'].append(error_result)
  715. self._log(f" ❌ 日期处理错误: {str(e)}")
  716. validation_results['summary'][pattern_type] = type_results
  717. # 打印总体统计
  718. total_validations = sum(result['total_dates'] for result in validation_results['summary'].values())
  719. total_successful = len(validation_results['successful_validations'])
  720. total_failed = len(validation_results['failed_validations'])
  721. self._log(f"\n=== 验证结果汇总 ===")
  722. self._log(f"总验证数: {total_validations}")
  723. self._log(f"成功验证: {total_successful}")
  724. self._log(f"失败验证: {total_failed}")
  725. self._log(f"成功率: {(total_successful/total_validations*100) if total_validations > 0 else 0:.1f}%")
  726. # 显示失败详情
  727. if validation_results['failed_validations']:
  728. self._display_validation_failures(validation_results['failed_validations'])
  729. return validation_results
  730. def _validate_single_date(self, target_date, pattern_type, weekly_data, kline_conditions):
  731. """验证单个日期的模式 - 使用统一的核心验证逻辑"""
  732. validation_result = {
  733. 'pattern_type': pattern_type,
  734. 'target_date': target_date.strftime('%Y-%m-%d'),
  735. 'validation_passed': False,
  736. 'failure_reason': '',
  737. 'diagnostics': {}
  738. }
  739. # 查找包含目标日期的数据
  740. found_data = None
  741. for code, data_info in weekly_data.items():
  742. data = data_info['data']
  743. contract_code = data_info['contract_code']
  744. # 检查目标日期是否在数据范围内
  745. if target_date >= data.index[0] and target_date <= data.index[-1]:
  746. # 找到最接近的周线数据
  747. closest_idx = data.index.get_indexer([target_date], method='nearest')[0]
  748. if closest_idx >= 0 and closest_idx < len(data):
  749. found_data = data.iloc[closest_idx]
  750. validation_result['contract_code'] = contract_code
  751. validation_result['closest_date'] = data.index[closest_idx].strftime('%Y-%m-%d')
  752. break
  753. if found_data is None:
  754. validation_result['failure_reason'] = '未找到匹配的数据'
  755. return validation_result
  756. # 使用统一的核心验证逻辑
  757. core_result = self._core_pattern_validation(target_date, found_data, kline_conditions)
  758. # 整合核心结果到验证结果
  759. validation_result['validation_passed'] = core_result['validation_passed']
  760. validation_result['diagnostics']['ohlc_data'] = core_result['ohlc_data']
  761. validation_result['diagnostics']['indicators'] = core_result['indicators']
  762. if 'failure_reason' in core_result:
  763. validation_result['failure_reason'] = core_result['failure_reason']
  764. return validation_result
  765. def _display_validation_failures(self, failed_validations):
  766. """显示详细的验证失败信息"""
  767. self._log("\n=== 详细失败诊断 ===")
  768. for i, failure in enumerate(failed_validations, 1):
  769. if failure.get('error'):
  770. continue # 跳过错误情况
  771. self._log(f"\n失败 #{i}: 模式类型 '{failure['pattern_type']}' - 日期 {failure['target_date']}")
  772. if 'contract_code' in failure:
  773. self._log(f" 合约: {failure['contract_code']}")
  774. self._log(f" 最近日期: {failure['closest_date']}")
  775. # 显示OHLC数据
  776. if 'ohlc_data' in failure['diagnostics']:
  777. ohlc = failure['diagnostics']['ohlc_data']
  778. self._log(f" OHLC数据:")
  779. self._log(f" 开盘: {ohlc['open']:>8}")
  780. self._log(f" 最高: {ohlc['high']:>8}")
  781. self._log(f" 最低: {ohlc['low']:>8}")
  782. self._log(f" 收盘: {ohlc['close']:>8}")
  783. self._log(f" 成交量: {ohlc['volume']}")
  784. # 显示指标验证详情
  785. if 'indicators' in failure['diagnostics']:
  786. self._log(f" 指标验证:")
  787. for indicator_name, indicator_data in failure['diagnostics']['indicators'].items():
  788. status = "✓" if indicator_data['passed'] else "❌"
  789. self._log(f" {status} {indicator_data['description']}:")
  790. self._log(f" 实际值: {indicator_data['actual_value']}{indicator_data['unit']}")
  791. self._log(f" 阈值: {indicator_data['condition']} {indicator_data['threshold']}{indicator_data['unit']}")
  792. self._log(f" 结果: {'通过' if indicator_data['passed'] else '失败'}")
  793. self._log(f" 失败原因: {failure['failure_reason']}")
  794. def analyze_future_performance(self, pattern_results, weekly_data):
  795. """分析后续走势 - 逐周详细分析"""
  796. self._log("\n=== 步骤6: 分析后续10周逐周表现 ===")
  797. performance_results = []
  798. follow_up_weeks = self.config['analysis_params']['follow_up_weeks']
  799. for pattern in pattern_results:
  800. code = pattern['品种代码']
  801. pattern_date = pattern['日期']
  802. pattern_idx = pattern['数据索引']
  803. pattern_close_price = pattern['收盘价']
  804. if code not in weekly_data:
  805. continue
  806. data = weekly_data[code]['data']
  807. # 基础信息
  808. future_performance = {
  809. '品种代码': code,
  810. '形态日期': pattern_date,
  811. '形态收盘价': pattern_close_price
  812. }
  813. # 详细的逐周分析
  814. print(f"\n{code} ({pattern_date.strftime('%Y-%m-%d')}) 后续10周详细分析:")
  815. print("-" * 80)
  816. print(f"{'周次':<4} {'日期':<12} {'开盘':<8} {'收盘':<8} {'最高':<8} {'最低':<8} "
  817. f"{'周涨跌幅':<10} {'最大跌幅':<10}")
  818. print("-" * 80)
  819. # 逐周分析
  820. for weeks in range(1, follow_up_weeks + 1):
  821. future_idx = pattern_idx + weeks
  822. if future_idx < len(data):
  823. future_row = data.iloc[future_idx]
  824. week_date = data.index[future_idx]
  825. week_open = future_row['open']
  826. week_close = future_row['close']
  827. week_high = future_row['high']
  828. week_low = future_row['low']
  829. # a) 每周的涨跌幅:收盘价相对于开盘价的涨跌幅度
  830. weekly_change_pct = (week_close - week_open) / week_open * 100
  831. # b) 每周的最大跌幅:开盘价到当周最低价的最大跌幅
  832. max_decline_pct = (week_low - week_open) / week_open * 100
  833. # 存储详细数据
  834. future_performance[f'第{weeks}周_日期'] = week_date.strftime('%Y-%m-%d')
  835. future_performance[f'第{weeks}周_开盘价'] = round(week_open, 2)
  836. future_performance[f'第{weeks}周_收盘价'] = round(week_close, 2)
  837. future_performance[f'第{weeks}周_最高价'] = round(week_high, 2)
  838. future_performance[f'第{weeks}周_最低价'] = round(week_low, 2)
  839. future_performance[f'第{weeks}周_涨跌幅'] = round(weekly_change_pct, 2)
  840. future_performance[f'第{weeks}周_最大跌幅'] = round(max_decline_pct, 2)
  841. # 打印逐周详细信息
  842. print(f"{weeks:>2} {week_date.strftime('%Y-%m-%d'):<12} "
  843. f"{week_open:<8.2f} {week_close:<8.2f} {week_high:<8.2f} {week_low:<8.2f} "
  844. f"{weekly_change_pct:>+8.2f}% {max_decline_pct:>+8.2f}%")
  845. else:
  846. # 数据不足的情况
  847. for field in ['日期', '开盘价', '收盘价', '最高价', '最低价', '涨跌幅', '最大跌幅']:
  848. future_performance[f'第{weeks}周_{field}'] = None
  849. print(f"{weeks:>2} {'无数据':<12} {'--':<8} {'--':<8} {'--':<8} {'--':<8} "
  850. f"{'--':<10} {'--':<10}")
  851. print("-" * 80)
  852. # 计算统计指标
  853. weekly_changes = [future_performance[f'第{w}周_涨跌幅']
  854. for w in range(1, follow_up_weeks + 1)
  855. if future_performance[f'第{w}周_涨跌幅'] is not None]
  856. max_declines = [future_performance[f'第{w}周_最大跌幅']
  857. for w in range(1, follow_up_weeks + 1)
  858. if future_performance[f'第{w}周_最大跌幅'] is not None]
  859. if weekly_changes:
  860. avg_weekly_change = sum(weekly_changes) / len(weekly_changes)
  861. max_weekly_gain = max(weekly_changes)
  862. max_weekly_loss = min(weekly_changes)
  863. future_performance['统计_平均周涨跌幅'] = round(avg_weekly_change, 2)
  864. future_performance['统计_最大周涨幅'] = round(max_weekly_gain, 2)
  865. future_performance['统计_最大周跌幅'] = round(max_weekly_loss, 2)
  866. print(f"统计汇总:")
  867. print(f" 平均周涨跌幅: {avg_weekly_change:+.2f}%")
  868. print(f" 最大周涨幅: {max_weekly_gain:+.2f}%")
  869. print(f" 最大周跌幅: {max_weekly_loss:+.2f}%")
  870. if max_declines:
  871. avg_max_decline = sum(max_declines) / len(max_declines)
  872. worst_decline = min(max_declines)
  873. future_performance['统计_平均最大跌幅'] = round(avg_max_decline, 2)
  874. future_performance['统计_最差跌幅'] = round(worst_decline, 2)
  875. print(f" 平均最大跌幅: {avg_max_decline:+.2f}%")
  876. print(f" 最差单周跌幅: {worst_decline:+.2f}%")
  877. performance_results.append(future_performance)
  878. print("=" * 80)
  879. return performance_results
  880. def create_visualizations(self, pattern_results, weekly_data):
  881. """创建K线图可视化"""
  882. if not self.config['output_settings']['generate_charts']:
  883. return
  884. self._log("\n=== 步骤7: 生成K线图可视化 ===")
  885. max_charts = min(len(pattern_results), self.config['output_settings']['max_chart_count'])
  886. for i, pattern in enumerate(pattern_results[:max_charts]):
  887. code = pattern['品种代码']
  888. pattern_date = pattern['日期']
  889. pattern_idx = pattern['数据索引']
  890. if code not in weekly_data:
  891. continue
  892. data = weekly_data[code]['data']
  893. # 准备绘图数据(形态前后各5周)
  894. start_idx = max(0, pattern_idx - 5)
  895. end_idx = min(len(data), pattern_idx + 11)
  896. plot_data = data.iloc[start_idx:end_idx]
  897. # 创建K线图
  898. fig, ax = plt.subplots(figsize=(12, 8))
  899. # 绘制K线
  900. for j, (date, row) in enumerate(plot_data.iterrows()):
  901. open_price = row['open']
  902. close_price = row['close']
  903. high_price = row['high']
  904. low_price = row['low']
  905. # K线颜色
  906. color = 'red' if close_price > open_price else 'green'
  907. # 绘制影线
  908. ax.plot([j, j], [low_price, high_price], color='black', linewidth=1)
  909. # 绘制实体
  910. body_height = abs(close_price - open_price)
  911. if close_price > open_price:
  912. rect = patches.Rectangle((j-0.3, open_price), 0.6, body_height,
  913. linewidth=1, edgecolor='red', facecolor='red', alpha=0.7)
  914. else:
  915. rect = patches.Rectangle((j-0.3, close_price), 0.6, body_height,
  916. linewidth=1, edgecolor='green', facecolor='green', alpha=0.7)
  917. ax.add_patch(rect)
  918. # 标记目标形态
  919. if date == pattern_date:
  920. ax.scatter(j, high_price + (high_price - low_price) * 0.1,
  921. color='yellow', s=100, marker='*', zorder=5)
  922. ax.text(j, high_price + (high_price - low_price) * 0.15,
  923. '目标形态', ha='center', fontsize=10, color='red', fontweight='bold')
  924. # 设置图表
  925. ax.set_title(f'{code} 周线K线图 - 目标形态分析\n形态日期: {pattern_date.strftime("%Y-%m-%d")}',
  926. fontsize=14, fontweight='bold')
  927. ax.set_xlabel('时间', fontsize=12)
  928. ax.set_ylabel('价格', fontsize=12)
  929. ax.grid(True, alpha=0.3)
  930. # 设置x轴标签
  931. dates = [date.strftime('%m-%d') for date in plot_data.index]
  932. ax.set_xticks(range(len(dates)))
  933. ax.set_xticklabels(dates, rotation=45)
  934. # 显示图表
  935. plt.tight_layout()
  936. plt.show() # 直接在界面中显示图表
  937. self._log(f"显示图表: {code} {pattern_date.strftime('%Y-%m-%d')} K线形态分析")
  938. def generate_summary_report(self, decline_futures, pattern_results, performance_results):
  939. """生成整体规律总结报告"""
  940. self._log("\n=== 步骤8: 生成统计报告 ===")
  941. # 基础统计
  942. total_decline_futures = len(decline_futures)
  943. total_patterns = len(pattern_results)
  944. total_analyzed = len(performance_results)
  945. print(f"\n=== 期货技术形态规律分析报告 ===")
  946. print(f"分析期间: {self.start_date.strftime('%Y-%m-%d')} 至 {self.end_date.strftime('%Y-%m-%d')}")
  947. print(f"总分析期货品种数: {len(self.future_codes)}")
  948. print(f"大幅下跌品种数: {total_decline_futures}")
  949. print(f"识别出的目标形态数: {total_patterns}")
  950. print(f"完成后续走势分析数: {total_analyzed}")
  951. if total_analyzed == 0:
  952. print("无有效数据进行统计分析")
  953. return {}
  954. # 收益率统计
  955. print(f"\n=== 后续走势统计分析 ===")
  956. # 提取所有统计指标
  957. avg_weekly_changes = [r['统计_平均周涨跌幅'] for r in performance_results if '统计_平均周涨跌幅' in r]
  958. max_weekly_gains = [r['统计_最大周涨幅'] for r in performance_results if '统计_最大周涨幅' in r]
  959. max_weekly_losses = [r['统计_最大周跌幅'] for r in performance_results if '统计_最大周跌幅' in r]
  960. avg_max_declines = [r['统计_平均最大跌幅'] for r in performance_results if '统计_平均最大跌幅' in r]
  961. if avg_weekly_changes:
  962. print(f"平均周涨跌幅统计:")
  963. print(f" 平均值: {np.mean(avg_weekly_changes):.2f}%")
  964. print(f" 中位数: {np.median(avg_weekly_changes):.2f}%")
  965. print(f" 最大值: {max(avg_weekly_changes):.2f}%")
  966. print(f" 最小值: {min(avg_weekly_changes):.2f}%")
  967. positive_weeks = sum(1 for r in avg_weekly_changes if r > 0)
  968. print(f" 正收益形态比例: {positive_weeks}/{len(avg_weekly_changes)} ({positive_weeks/len(avg_weekly_changes)*100:.1f}%)")
  969. if max_weekly_gains:
  970. print(f"\n最大周涨幅统计:")
  971. print(f" 平均值: {np.mean(max_weekly_gains):.2f}%")
  972. print(f" 中位数: {np.median(max_weekly_gains):.2f}%")
  973. print(f" 最大值: {max(max_weekly_gains):.2f}%")
  974. print(f" 最小值: {min(max_weekly_gains):.2f}%")
  975. if max_weekly_losses:
  976. print(f"\n最大周跌幅统计:")
  977. print(f" 平均值: {np.mean(max_weekly_losses):.2f}%")
  978. print(f" 中位数: {np.median(max_weekly_losses):.2f}%")
  979. print(f" 最大值: {max(max_weekly_losses):.2f}%")
  980. print(f" 最小值: {min(max_weekly_losses):.2f}%")
  981. if avg_max_declines:
  982. print(f"\n平均最大跌幅统计:")
  983. print(f" 平均值: {np.mean(avg_max_declines):.2f}%")
  984. print(f" 中位数: {np.median(avg_max_declines):.2f}%")
  985. print(f" 最大值: {max(avg_max_declines):.2f}%")
  986. print(f" 最小值: {min(avg_max_declines):.2f}%")
  987. # 成功率统计 - 基于平均周涨跌幅是否为正
  988. positive_performance_count = sum(1 for r in avg_weekly_changes if r > 0) if avg_weekly_changes else 0
  989. success_rate = positive_performance_count / len(avg_weekly_changes) * 100 if avg_weekly_changes else 0
  990. # 基于最大周涨幅的成功率统计
  991. success_threshold = self.config['analysis_params']['success_threshold'] * 100
  992. high_gain_count = sum(1 for r in max_weekly_gains if r > success_threshold) if max_weekly_gains else 0
  993. high_gain_rate = high_gain_count / len(max_weekly_gains) * 100 if max_weekly_gains else 0
  994. print(f"\n=== 交易成功率分析 ===")
  995. print(f"正收益形态数量: {positive_performance_count}/{len(avg_weekly_changes) if avg_weekly_changes else 0}")
  996. print(f"正收益成功率: {success_rate:.1f}%")
  997. print(f"")
  998. print(f"高收益标准: 最大周涨幅 > {success_threshold}%")
  999. print(f"高收益形态数量: {high_gain_count}/{len(max_weekly_gains) if max_weekly_gains else 0}")
  1000. print(f"高收益成功率: {high_gain_rate:.1f}%")
  1001. return {
  1002. 'total_futures': len(self.future_codes),
  1003. 'decline_futures': total_decline_futures,
  1004. 'pattern_count': total_patterns,
  1005. 'analyzed_count': total_analyzed,
  1006. 'avg_weekly_change': np.mean(avg_weekly_changes) if avg_weekly_changes else 0,
  1007. 'avg_max_gain': np.mean(max_weekly_gains) if max_weekly_gains else 0,
  1008. 'avg_max_loss': np.mean(max_weekly_losses) if max_weekly_losses else 0,
  1009. 'avg_max_decline': np.mean(avg_max_declines) if avg_max_declines else 0,
  1010. 'success_rate': success_rate,
  1011. 'high_gain_rate': high_gain_rate,
  1012. 'positive_count': positive_performance_count,
  1013. 'high_gain_count': high_gain_count
  1014. }
  1015. def save_results(self, decline_futures, pattern_results, performance_results, summary_stats):
  1016. """保存结果到CSV文件"""
  1017. self._log("\n=== 保存分析结果 ===")
  1018. timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
  1019. encoding = self.config['output_settings']['csv_encoding']
  1020. # 保存大幅下跌期货品种
  1021. if decline_futures:
  1022. decline_df = pd.DataFrame(decline_futures)
  1023. decline_file = f'future_major_decline_{timestamp}.csv'
  1024. decline_df.to_csv(decline_file, index=False, encoding=encoding)
  1025. self._log(f"大幅下跌期货保存至: {decline_file}")
  1026. # 保存识别的K线形态
  1027. if pattern_results:
  1028. pattern_df = pd.DataFrame(pattern_results)
  1029. pattern_file = f'future_kline_patterns_{timestamp}.csv'
  1030. pattern_df.to_csv(pattern_file, index=False, encoding=encoding)
  1031. self._log(f"K线形态数据保存至: {pattern_file}")
  1032. # 保存后续走势分析
  1033. if performance_results:
  1034. performance_df = pd.DataFrame(performance_results)
  1035. performance_file = f'future_pattern_performance_{timestamp}.csv'
  1036. performance_df.to_csv(performance_file, index=False, encoding=encoding)
  1037. self._log(f"后续走势分析保存至: {performance_file}")
  1038. # 保存汇总报告
  1039. if summary_stats:
  1040. summary_df = pd.DataFrame([summary_stats])
  1041. summary_file = f'future_pattern_summary_{timestamp}.csv'
  1042. summary_df.to_csv(summary_file, index=False, encoding=encoding)
  1043. self._log(f"汇总报告保存至: {summary_file}")
  1044. def run_analysis(self, mode=1, manual_config=None):
  1045. """
  1046. 统一的分析运行入口
  1047. 参数:
  1048. - mode: 执行模式
  1049. 1 = 原始/自动模式:自动搜索符合条件的技术形态
  1050. 2 = 验证模式:验证指定日期是否符合模式条件
  1051. - manual_config: 手动配置字典,仅在mode=2时使用
  1052. 返回:
  1053. - dict: 分析结果
  1054. """
  1055. if mode == 1:
  1056. return self._run_original_mode()
  1057. elif mode == 2:
  1058. if manual_config is None:
  1059. raise ValueError("验证模式(mode=2)需要提供manual_config参数")
  1060. return self._run_validation_mode(manual_config)
  1061. else:
  1062. raise ValueError("mode参数必须为1(原始模式)或2(验证模式)")
  1063. def _run_original_mode(self):
  1064. """原始/自动模式:自动搜索符合条件的技术形态"""
  1065. print("开始期货技术形态规律分析... [原始模式]")
  1066. # 构建期货品种基础数据
  1067. self.build_future_codes()
  1068. # 筛选大幅下跌的期货品种
  1069. decline_futures = self.find_major_decline_futures()
  1070. if not decline_futures:
  1071. print("未找到符合条件的大幅下跌期货品种")
  1072. return {}
  1073. # 创建8888加权合约映射
  1074. contract_mapping = self.get_simplified_contract_mapping(decline_futures)
  1075. if not contract_mapping:
  1076. print("未能创建8888合约映射")
  1077. return {}
  1078. else:
  1079. print(f"创建8888合约映射成功,共{len(contract_mapping)}个合约")
  1080. for contract_code, time_range in contract_mapping.items():
  1081. start_date, end_date = time_range
  1082. print(f" {contract_code}: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
  1083. # 获取周线K线数据
  1084. weekly_data = self.get_weekly_kline_data(contract_mapping)
  1085. if not weekly_data:
  1086. print("未能获取周线数据")
  1087. return {}
  1088. # 识别目标K线形态
  1089. pattern_results = self.identify_target_pattern(weekly_data, decline_futures)
  1090. if not pattern_results:
  1091. print("未识别出目标K线形态")
  1092. return {}
  1093. # 分析后续走势
  1094. performance_results = self.analyze_future_performance(pattern_results, weekly_data)
  1095. # 创建可视化
  1096. self.create_visualizations(pattern_results, weekly_data)
  1097. # 生成汇总报告
  1098. summary_stats = self.generate_summary_report(decline_futures, pattern_results, performance_results)
  1099. print(f"\n=== 分析完成 ===")
  1100. print(f"共分析了{len(decline_futures)}个大幅下跌期货品种")
  1101. print(f"识别出{len(pattern_results)}个目标K线形态")
  1102. print(f"完成{len(performance_results)}个形态的后续走势分析")
  1103. return {
  1104. 'mode': 1,
  1105. 'decline_futures': decline_futures,
  1106. 'pattern_results': pattern_results,
  1107. 'performance_results': performance_results,
  1108. 'summary_stats': summary_stats
  1109. }
  1110. def _run_validation_mode(self, manual_config):
  1111. """验证模式:验证指定日期是否符合模式条件"""
  1112. print("开始期货技术形态验证分析... [验证模式]")
  1113. # 构建期货品种基础数据(仅构建需要的品种)
  1114. self.build_future_codes()
  1115. # 从配置中提取需要验证的品种代码
  1116. config_codes = list(manual_config.keys())
  1117. print(f"配置的验证目标: {config_codes}")
  1118. # 为每个配置的品种创建数据获取范围
  1119. sample_futures = []
  1120. for code in config_codes:
  1121. if code in self.future_codes:
  1122. contract_8888 = self.future_codes[code]
  1123. date_list = manual_config[code]
  1124. # 基于配置的日期计算数据获取范围
  1125. config_dates = [pd.to_datetime(date_str) for date_str in date_list]
  1126. earliest_date = min(config_dates)
  1127. latest_date = max(config_dates)
  1128. # 为了确保有足够的数据进行验证,在日期范围前后各扩展一些时间
  1129. buffer_days = 90 # 3个月缓冲
  1130. start_date = earliest_date - pd.Timedelta(days=buffer_days)
  1131. end_date = latest_date + pd.Timedelta(days=buffer_days)
  1132. sample_futures.append({
  1133. '品种代码': code,
  1134. '合约代码': contract_8888,
  1135. '高点日期': start_date,
  1136. '低点日期': end_date
  1137. })
  1138. print(f" ✓ {code} ({contract_8888}): {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
  1139. else:
  1140. print(f" ⚠️ 未找到品种 '{code}' 对应的合约代码")
  1141. print(f"实际处理的品种数: {len(sample_futures)}")
  1142. if not sample_futures:
  1143. print("❌ 配置中的品种代码都无法找到对应的合约")
  1144. return {}
  1145. # 创建简化的合约映射
  1146. contract_mapping = self.get_simplified_contract_mapping(sample_futures)
  1147. if not contract_mapping:
  1148. print("❌ 无法创建合约映射")
  1149. return {}
  1150. # 获取周线数据
  1151. weekly_data = self.get_weekly_kline_data(contract_mapping)
  1152. if not weekly_data:
  1153. print("❌ 无法获取周线数据")
  1154. return {}
  1155. print(f"✓ 成功获取 {len(weekly_data)} 个品种的数据")
  1156. # 执行手动验证
  1157. validation_results = self.validate_manual_patterns(manual_config, weekly_data)
  1158. validation_results['mode'] = 2
  1159. return validation_results
  1160. # =====================================================================================
  1161. # 第三部分:主程序运行函数
  1162. # 提供统一的程序入口和运行选项
  1163. # =====================================================================================
  1164. def run_pattern_analysis(mode=1, manual_config=None):
  1165. """
  1166. 统一的期货技术形态分析入口
  1167. 参数:
  1168. - mode: 执行模式
  1169. 1 = 原始/自动模式:自动搜索符合条件的技术形态
  1170. 2 = 验证模式:验证指定日期是否符合模式条件
  1171. - manual_config: 手动配置字典,仅在mode=2时使用
  1172. 返回:
  1173. - dict: 分析结果
  1174. """
  1175. print("期货技术形态规律分析工具")
  1176. print("="*60)
  1177. # 打印配置信息
  1178. AnalysisConfig.print_config()
  1179. print("="*60)
  1180. mode_name = "原始模式" if mode == 1 else "验证模式"
  1181. print(f"开始分析... [{mode_name}]")
  1182. # 创建分析器并运行
  1183. analyzer = FuturePatternAnalyzer(
  1184. AnalysisConfig.START_DATE,
  1185. AnalysisConfig.END_DATE
  1186. )
  1187. results = analyzer.run_analysis(mode=mode, manual_config=manual_config)
  1188. return results
  1189. # =====================================================================================
  1190. # 第四部分:辅助工具函数
  1191. # 用于处理合约切换日志和数据导出
  1192. # =====================================================================================
  1193. def parse_contract_switch_log_from_text(log_text):
  1194. """
  1195. 从文本格式的合约切换日志中解析合约信息
  1196. 参数:
  1197. - log_text: 日志文本,格式示例:
  1198. "A2203.XDCE:2022年1月26日至2022年2月22日
  1199. A2207.XDCE:2022年2月23日至2022年6月21日"
  1200. 返回:
  1201. - list: 标准化的合约切换日志
  1202. """
  1203. import re
  1204. contracts = []
  1205. lines = log_text.strip().split('\n')
  1206. for line in lines:
  1207. line = line.strip()
  1208. if not line:
  1209. continue
  1210. # 正则匹配合约代码和日期范围
  1211. pattern = r'([A-Z0-9]+\.[A-Z]+):(\d{4})年(\d{1,2})月(\d{1,2})日至(\d{4})年(\d{1,2})月(\d{1,2})日'
  1212. match = re.search(pattern, line)
  1213. if match:
  1214. contract_code = match.group(1)
  1215. start_year = int(match.group(2))
  1216. start_month = int(match.group(3))
  1217. start_day = int(match.group(4))
  1218. end_year = int(match.group(5))
  1219. end_month = int(match.group(6))
  1220. end_day = int(match.group(7))
  1221. contracts.append({
  1222. 'contract': contract_code,
  1223. 'start_date': f'{start_year}-{start_month:02d}-{start_day:02d}',
  1224. 'end_date': f'{end_year}-{end_month:02d}-{end_day:02d}'
  1225. })
  1226. return contracts
  1227. def export_contract_weekly_data(weekly_data_dict, filename_prefix='contract_weekly_data'):
  1228. """
  1229. 导出合约周线数据到CSV文件
  1230. 参数:
  1231. - weekly_data_dict: get_weekly_data_by_contract_log返回的数据字典
  1232. - filename_prefix: 文件名前缀
  1233. 注意:导出的数据是直接从get_bars API获取的原生周线数据,
  1234. 相比日线转换的数据具有更高的精度和准确性
  1235. """
  1236. import datetime
  1237. timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
  1238. for contract_code, contract_data in weekly_data_dict.items():
  1239. # 清理文件名中的特殊字符
  1240. safe_contract_name = contract_code.replace('.', '_')
  1241. filename = f'{filename_prefix}_{safe_contract_name}_{timestamp}.csv'
  1242. # 准备数据
  1243. df = contract_data['data'].copy()
  1244. df['contract'] = contract_code
  1245. df['original_start'] = contract_data['original_start'].strftime('%Y-%m-%d')
  1246. df['original_end'] = contract_data['original_end'].strftime('%Y-%m-%d')
  1247. # 保存到CSV
  1248. df.to_csv(filename, encoding='utf-8-sig')
  1249. print(f"合约 {contract_code} 的周线数据已保存至: {filename}")
  1250. def run_manual_pattern_validation(manual_config, analyzer=None):
  1251. """
  1252. 运行手动模式验证的便捷函数
  1253. 此函数只会处理manual_config中明确指定的品种和时间范围,
  1254. 不会查询额外的品种数据。现在使用统一的分析框架。
  1255. 参数:
  1256. - manual_config: 手动配置字典,格式:
  1257. {'A':['2023-01-06','2023-06-09'], 'B':['2022-07-22','2024-08-23']}
  1258. 键: 期货品种代码(如'A'、'B'、'RB'等)
  1259. 值: 需要验证的日期列表('YYYY-MM-DD'格式)
  1260. - analyzer: 可选的分析器实例,如果为None则创建新实例
  1261. 返回:
  1262. - dict: 验证结果,包含成功和失败的详细信息
  1263. 注意:
  1264. - 系统只会获取配置中指定品种的数据
  1265. - 数据获取范围基于每个品种配置的日期范围自动计算
  1266. - 不会处理未在配置中明确指定的品种
  1267. - 现在使用统一的核心验证逻辑,确保与原始模式的一致性
  1268. """
  1269. print("\n=== 手动模式验证工具 ===")
  1270. print("此功能用于验证指定日期是否符合预定义的技术形态模式")
  1271. print("使用统一的验证逻辑,与原始模式保持完全一致")
  1272. # 创建分析器实例(如果没有提供)
  1273. if analyzer is None:
  1274. analyzer = FuturePatternAnalyzer()
  1275. # 使用统一的分析入口 - 验证模式
  1276. validation_results = analyzer.run_analysis(mode=2, manual_config=manual_config)
  1277. return validation_results
  1278. def demo_manual_validation():
  1279. """演示手动模式验证功能"""
  1280. print("\n=== 手动模式验证演示 ===")
  1281. # 示例配置
  1282. demo_config = {
  1283. 'A': ['2023-01-06', '2023-06-09', '2024-07-26'],
  1284. 'B': ['2022-07-22', '2024-08-23', '2025-01-03']
  1285. }
  1286. print("使用演示配置:")
  1287. for pattern_type, dates in demo_config.items():
  1288. print(f" 模式 '{pattern_type}': {len(dates)} 个日期")
  1289. for date in dates:
  1290. print(f" - {date}")
  1291. try:
  1292. # 运行验证
  1293. results = run_manual_pattern_validation(demo_config)
  1294. if results:
  1295. print("\n✅ 验证完成")
  1296. return results
  1297. else:
  1298. print("\n⚠️ 验证未能完成")
  1299. return {}
  1300. except Exception as e:
  1301. print(f"\n❌ 验证过程中出错: {str(e)}")
  1302. import traceback
  1303. traceback.print_exc()
  1304. return {}
  1305. def get_contract_weekly_data(contract_switch_log, extend_weeks=10, export_data=False):
  1306. """
  1307. 基于合约切换日志获取周线数据的正式方法
  1308. 参数:
  1309. - contract_switch_log: 合约切换日志,可以是文本格式或列表格式
  1310. - extend_weeks: 延长周数,默认10周
  1311. - export_data: 是否导出数据到CSV文件,默认False
  1312. 返回:
  1313. - dict: 每个合约的周线数据
  1314. """
  1315. print("\n=== 基于合约切换日志获取周线数据 ===")
  1316. # 如果是文本格式,先解析
  1317. if isinstance(contract_switch_log, str):
  1318. print("解析合约切换日志:")
  1319. contracts = parse_contract_switch_log_from_text(contract_switch_log)
  1320. for contract in contracts:
  1321. print(f" {contract['contract']}: {contract['start_date']} 至 {contract['end_date']}")
  1322. else:
  1323. contracts = contract_switch_log
  1324. print(f"处理{len(contracts)}个合约的切换日志")
  1325. print(f"\n创建分析器并获取周线数据 (延长{extend_weeks}周):")
  1326. analyzer = FuturePatternAnalyzer()
  1327. try:
  1328. # 获取周线数据
  1329. weekly_data = analyzer.get_weekly_data_by_contract_log(contracts, extend_weeks=extend_weeks)
  1330. if weekly_data:
  1331. print(" ✓ 获取成功!")
  1332. for contract_code, data_info in weekly_data.items():
  1333. print(f" {contract_code}: {data_info['total_weeks']}周数据")
  1334. print(f" 原始范围: {data_info['original_start'].strftime('%Y-%m-%d')} 至 {data_info['original_end'].strftime('%Y-%m-%d')}")
  1335. print(f" 延长范围: {data_info['extended_start'].strftime('%Y-%m-%d')} 至 {data_info['extended_end'].strftime('%Y-%m-%d')}")
  1336. # 导出数据
  1337. if export_data:
  1338. export_contract_weekly_data(weekly_data)
  1339. return weekly_data
  1340. else:
  1341. print(" ⚠️ 未获取到数据")
  1342. return {}
  1343. except Exception as e:
  1344. print(f" ❌ 获取失败: {str(e)}")
  1345. import traceback
  1346. traceback.print_exc()
  1347. return {}
  1348. # =====================================================================================
  1349. # 第五部分:程序主入口 - 自动运行分析
  1350. # =====================================================================================
  1351. print("期货市场技术形态规律分析工具")
  1352. print("本工具专门研究期货品种在大幅下跌后出现特定K线形态的规律")
  1353. print("适用于聚宽在线研究平台")
  1354. print("="*80)
  1355. # =====================================================================================
  1356. # 基于合约切换日志的周线数据获取功能使用说明
  1357. # =====================================================================================
  1358. #
  1359. # 使用示例1:处理文本格式的合约切换日志
  1360. # sample_log = """
  1361. # A2203.XDCE:2022年1月26日至2022年2月22日
  1362. # A2207.XDCE:2022年2月23日至2022年6月21日
  1363. # A2209.XDCE:2022年6月22日至2022年8月30日
  1364. # """
  1365. # weekly_data = get_contract_weekly_data(sample_log, extend_weeks=10, export_data=True)
  1366. #
  1367. # 使用示例2:处理列表格式的合约切换日志
  1368. # contract_list = [
  1369. # {'contract': 'A2203.XDCE', 'start_date': '2022-01-26', 'end_date': '2022-02-22'},
  1370. # {'contract': 'A2207.XDCE', 'start_date': '2022-02-23', 'end_date': '2022-06-21'}
  1371. # ]
  1372. # weekly_data = get_contract_weekly_data(contract_list, extend_weeks=15, export_data=False)
  1373. #
  1374. # 新增功能1:模式识别失败时的完整K线图显示
  1375. # 当identify_target_pattern函数未识别到任何目标形态时,系统会自动:
  1376. # 1. 显示所有可用品种的完整烛台图
  1377. # 2. 包含OHLC数据的专业K线图表现
  1378. # 3. 添加统计信息(最高价、最低价、总涨跌幅等)
  1379. # 4. 直接在界面中显示,不保存到本地文件
  1380. #
  1381. # 新增功能2:手动模式验证系统
  1382. # 使用示例3:手动验证指定日期的模式匹配
  1383. # 现在使用统一的入口函数,通过mode参数控制执行模式
  1384. manual_config = {
  1385. 'A': ['2023-01-06', '2023-06-09', '2024-07-26'],
  1386. 'B': ['2022-07-22', '2024-08-23', '2025-01-03']
  1387. }
  1388. # 方法1: 使用统一入口函数
  1389. validation_results = run_pattern_analysis(mode=2, manual_config=manual_config)
  1390. # 方法2: 或者使用便捷函数(内部调用统一入口)
  1391. # validation_results = run_manual_pattern_validation(manual_config)
  1392. # 或运行演示: demo_manual_validation()
  1393. #
  1394. # 重构后的统一模式控制系统:
  1395. # 1. mode=1(原始模式):自动搜索符合条件的技术形态,遍历所有期货品种
  1396. # 2. mode=2(验证模式):验证指定日期是否符合模式条件,只处理配置品种
  1397. # 3. 统一的核心验证逻辑:_core_pattern_validation()消除了代码重复
  1398. # 4. 独立的执行路径:两种模式不再共享步骤1,完全独立执行
  1399. # 5. 一致的验证标准:两种模式使用完全相同的模式识别条件和计算方法
  1400. #
  1401. # 数据获取特点:
  1402. # 1. 直接使用get_bars API获取原生周线数据,避免日线转换的精度损失
  1403. # 2. 自动延长时间范围(每个合约前后各延长指定周数)
  1404. # 3. 确保获取充足的历史数据(最少60周作为背景)
  1405. # 4. 支持数据导出到CSV文件便于后续分析
  1406. # 5. 简化结构:直接使用8888加权合约,无需处理主力合约切换
  1407. #
  1408. # 简化后的工作流程:
  1409. # 1. 识别符合条件的大幅下跌期货品种
  1410. # 2. 为每个品种创建8888合约映射(单级字典结构)
  1411. # 3. 直接从8888合约获取周线数据,避免主力合约复杂性
  1412. # 4. 进行技术形态识别和后续走势分析
  1413. # 5. 当未识别到目标形态时,自动显示完整的烛台图(K线图)
  1414. # =====================================================================================
  1415. # 直接运行分析
  1416. # print("\n开始运行完整分析...")
  1417. # try:
  1418. # results = run_pattern_analysis()
  1419. # except Exception as e:
  1420. # print(f"运行过程中出现错误: {str(e)}")
  1421. # import traceback
  1422. # traceback.print_exc()
  1423. # results = None
  1424. # if results:
  1425. # print("\n✅ 分析完成!")
  1426. # if isinstance(results, dict) and 'summary_stats' in results:
  1427. # summary = results['summary_stats']
  1428. # print(f"\n📊 快速统计:")
  1429. # print(f" - 识别到技术形态: {summary.get('pattern_count', 0)}个")
  1430. # print(f" - 正收益成功率: {summary.get('success_rate', 0):.1f}%")
  1431. # print(f" - 高收益成功率: {summary.get('high_gain_rate', 0):.1f}%")
  1432. # print(f" - 平均周涨跌幅: {summary.get('avg_weekly_change', 0):+.2f}%")
  1433. # else:
  1434. # print("\n❌ 分析未能完成,请检查错误信息。")