future_candlestick_hatch_analysis.py 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481
  1. """
  2. 期货烛台影线形态分析
  3. 研究期货合约中带有明显影线的K线对未来价格走势的预测能力
  4. 本程序实现完整的分析流程:
  5. 1. 数据结构设置 - 获取主力期货合约数据
  6. 2. 数据收集 - 扩展时间范围获取OHLC数据
  7. 3. 实体长度计算 - 计算历史平均实体长度作为阈值
  8. 4. 影线形态识别 - 识别满足条件的K线形态
  9. 5. 未来表现分析 - 分析影线方向与价格走势的关系
  10. 6. 结果输出 - 生成影线形态分析结果CSV
  11. 注:程序使用动态获取的主力合约进行分析,确保分析结果基于真实的交易活动
  12. 作者: jukuan研究团队
  13. 日期: 2025-09
  14. 适用平台: 聚宽在线研究平台
  15. """
  16. import pandas as pd
  17. import numpy as np
  18. from jqdata import *
  19. import datetime
  20. import warnings
  21. warnings.filterwarnings('ignore')
  22. # =====================================================================================
  23. # 分析配置参数 - 集中配置部分
  24. # =====================================================================================
  25. class CandlestickHatchConfig:
  26. """期货烛台影线形态分析配置参数"""
  27. # ==================== 时间范围设置 ====================
  28. ANALYSIS_START_DATE = datetime.datetime(2025, 8, 1) # 分析开始日期
  29. ANALYSIS_END_DATE = datetime.datetime(2025, 8, 20) # 分析结束日期
  30. # ==================== 时间扩展参数 ====================
  31. HISTORICAL_DAYS = 365 # 历史数据回溯期:优势期前天数
  32. FORWARD_DAYS = 15 # 正向分析期:优势期后天数
  33. FUTURE_MONITOR_DAYS = 15 # 未来表现跟踪期:交易日数
  34. # ==================== 影线形态参数 ====================
  35. HATCH_TO_BODY_RATIO = 1.2 # 影线与实体长度比率阈值
  36. OPPOSITE_HATCH_RATIO = 0.5 # 相反方向影线与实体长度比率阈值
  37. # ==================== 形态验证阈值 ====================
  38. MAX_REVENUE_THRESHOLD = 0.02 # 最大盈利阈值
  39. MAX_LOSS_THRESHOLD = 0.01 # 最大亏损阈值
  40. # ==================== 分析规模控制 ====================
  41. MAX_ANALYSIS_CONTRACTS = -1 # 最大分析合约数量限制
  42. # ==================== 输出设置 ====================
  43. OUTPUT_ENCODING = 'utf-8-sig' # 输出文件编码格式
  44. VERBOSE_LOGGING = True # 是否打印详细日志
  45. # ==================== 交易仓位管理 ====================
  46. INITIAL_TRADE_AMOUNT = 20000 # 单笔交易初始金额
  47. LOSS_RATE = 0.01 # 亏损交易的亏损率(可配置)
  48. PROFIT_DRAWDOWN_RATE = 0.02 # 盈利交易的回撤率(可配置)
  49. @classmethod
  50. def print_config(cls):
  51. """打印当前配置信息"""
  52. print("=== 期货烛台影线形态分析配置 ===")
  53. print(f"分析时间范围: {cls.ANALYSIS_START_DATE.strftime('%Y-%m-%d')} 至 {cls.ANALYSIS_END_DATE.strftime('%Y-%m-%d')}")
  54. print(f"历史数据回溯期: {cls.HISTORICAL_DAYS}天")
  55. print(f"前向分析期: {cls.FORWARD_DAYS}天")
  56. print(f"未来表现跟踪期: {cls.FUTURE_MONITOR_DAYS}个交易日")
  57. print(f"影线与实体长度比率阈值: {cls.HATCH_TO_BODY_RATIO}")
  58. print(f"相反方向影线比率阈值: {cls.OPPOSITE_HATCH_RATIO}")
  59. print(f"最大盈利阈值: {cls.MAX_REVENUE_THRESHOLD}")
  60. print(f"最大亏损阈值: {cls.MAX_LOSS_THRESHOLD}")
  61. print(f"最大分析合约数: {cls.MAX_ANALYSIS_CONTRACTS}")
  62. print(f"合约类型: 主力合约")
  63. print(f"详细日志: {'开启' if cls.VERBOSE_LOGGING else '关闭'}")
  64. print(f"初始交易金额: {cls.INITIAL_TRADE_AMOUNT}")
  65. print(f"亏损交易亏损率: {cls.LOSS_RATE}")
  66. print(f"盈利交易回撤率: {cls.PROFIT_DRAWDOWN_RATE}")
  67. print("=" * 50)
  68. class FutureCandlestickHatchAnalyzer:
  69. """期货烛台影线形态分析器"""
  70. def __init__(self, config=None):
  71. """初始化分析器"""
  72. # 使用配置类参数
  73. if config is None:
  74. config = CandlestickHatchConfig
  75. self.config = config
  76. self.historical_days = config.HISTORICAL_DAYS
  77. self.forward_days = config.FORWARD_DAYS
  78. self.future_monitor_days = config.FUTURE_MONITOR_DAYS
  79. self.hatch_to_body_ratio = config.HATCH_TO_BODY_RATIO
  80. self.opposite_hatch_ratio = config.OPPOSITE_HATCH_RATIO
  81. self.max_revenue_threshold = config.MAX_REVENUE_THRESHOLD
  82. self.max_loss_threshold = config.MAX_LOSS_THRESHOLD
  83. self.analysis_start_date = config.ANALYSIS_START_DATE
  84. self.analysis_end_date = config.ANALYSIS_END_DATE
  85. self.max_analysis_contracts = config.MAX_ANALYSIS_CONTRACTS
  86. self.output_encoding = config.OUTPUT_ENCODING
  87. self.verbose_logging = config.VERBOSE_LOGGING
  88. # 交易仓位管理参数
  89. self.initial_trade_amount = config.INITIAL_TRADE_AMOUNT
  90. self.loss_rate = config.LOSS_RATE
  91. self.profit_drawdown_rate = config.PROFIT_DRAWDOWN_RATE
  92. # 期货品种与交易所的映射关系
  93. self.exchange_map = {
  94. # 大连商品交易所 (XDCE)
  95. 'A': 'XDCE', 'B': 'XDCE', 'C': 'XDCE', 'CS': 'XDCE',
  96. 'I': 'XDCE', 'J': 'XDCE', 'JD': 'XDCE', 'JM': 'XDCE', 'L': 'XDCE',
  97. 'M': 'XDCE', 'P': 'XDCE', 'PP': 'XDCE', 'PG': 'XDCE', 'RR': 'XDCE',
  98. 'V': 'XDCE', 'Y': 'XDCE', 'EB': 'XDCE', 'EG': 'XDCE', 'LH': 'XDCE',
  99. # 郑州商品交易所 (XZCE)
  100. 'AP': 'XZCE', 'CF': 'XZCE', 'CY': 'XZCE', 'FG': 'XZCE', 'JR': 'XZCE',
  101. 'LR': 'XZCE', 'MA': 'XZCE', 'OI': 'XZCE', 'PM': 'XZCE', 'RI': 'XZCE',
  102. 'RM': 'XZCE', 'RS': 'XZCE', 'SF': 'XZCE', 'SM': 'XZCE', 'SR': 'XZCE',
  103. 'TA': 'XZCE', 'WH': 'XZCE', 'ZC': 'XZCE', 'CJ': 'XZCE',
  104. 'ME': 'XZCE', 'PF': 'XZCE', 'PK': 'XZCE', 'RO': 'XZCE', 'SA': 'XZCE',
  105. 'TC': 'XZCE', 'UR': 'XZCE', 'WS': 'XZCE', 'WT': 'XZCE',
  106. # 上海期货交易所 (XSGE)
  107. 'AG': 'XSGE', 'AL': 'XSGE', 'AU': 'XSGE', 'BU': 'XSGE', 'CU': 'XSGE',
  108. 'FU': 'XSGE', 'HC': 'XSGE', 'NI': 'XSGE', 'PB': 'XSGE', 'RB': 'XSGE',
  109. 'RU': 'XSGE', 'SN': 'XSGE', 'SP': 'XSGE', 'SS': 'XSGE', 'WR': 'XSGE',
  110. 'ZN': 'XSGE',
  111. # 上海国际能源交易中心 (XINE)
  112. 'BC': 'XINE', 'LU': 'XINE', 'NR': 'XINE', 'SC': 'XINE',
  113. # 中金所 (CCFX)
  114. 'IC': 'CCFX', 'IF': 'CCFX', 'IH': 'CCFX', 'T': 'CCFX', 'TF': 'CCFX', 'TS': 'CCFX',
  115. # 广期所 (GFEX)
  116. 'SI': 'GFEX', 'LC': 'GFEX', 'PS': 'GFEX'
  117. }
  118. # 存储结果的字典
  119. self.contract_data = {} # 主合约数据结构 - dict1
  120. self.hatch_patterns = {} # 影线形态数据 - dict2
  121. # 投资组合管理
  122. self.portfolio_results = [] # 存储所有交易结果
  123. if self.verbose_logging:
  124. print("初始化期货烛台影线形态分析器")
  125. print(f"配置参数 - 历史数据回溯期: {self.historical_days}天, 前向分析期: {self.forward_days}天")
  126. print(f"影线与实体长度比率阈值: {self.hatch_to_body_ratio}, 相反影线比率阈值: {self.opposite_hatch_ratio}")
  127. print(f"最大盈利阈值: {self.max_revenue_threshold}, 最大亏损阈值: {self.max_loss_threshold}")
  128. print(f"监控窗口: {self.future_monitor_days}个交易日")
  129. print(f"最大分析合约数: {self.max_analysis_contracts}")
  130. print(f"合约类型: 主力合约")
  131. def _is_trading_day(self, check_date):
  132. """
  133. 检查是否为交易日
  134. 参数:
  135. check_date (date): 要检查的日期
  136. 返回:
  137. bool: 是否为交易日
  138. """
  139. try:
  140. # 使用聚宽API检查交易日
  141. trade_days = get_trade_days(end_date=check_date, count=1)
  142. if len(trade_days) > 0:
  143. return trade_days[0].date() == check_date
  144. return False
  145. except:
  146. # 如果API调用失败,简单判断是否为工作日
  147. return check_date.weekday() < 5
  148. def _get_symbol_dominant_history(self, symbol, start_date, end_date):
  149. """
  150. 获取单个标的的主力合约历史变化
  151. 参数:
  152. symbol (str): 标的编码
  153. start_date (date): 开始日期
  154. end_date (date): 结束日期
  155. 返回:
  156. list: 主力合约记录列表
  157. """
  158. results = []
  159. current_date = start_date
  160. current_dominant = None
  161. period_start = None
  162. # if self.verbose_logging:
  163. # print(f" 获取 {symbol} 主力合约历史...")
  164. # 按日遍历时间范围
  165. while current_date <= end_date:
  166. # 跳过非交易日
  167. if not self._is_trading_day(current_date):
  168. current_date += datetime.timedelta(days=1)
  169. continue
  170. try:
  171. # 获取当日主力合约
  172. dominant_contract = get_dominant_future(symbol, current_date)
  173. if dominant_contract is None:
  174. current_date += datetime.timedelta(days=1)
  175. continue
  176. # 如果是新的主力合约
  177. if dominant_contract != current_dominant:
  178. # 如果不是第一个合约,结束上一个合约的记录
  179. if current_dominant is not None and period_start is not None:
  180. results.append({
  181. 'symbol': symbol,
  182. 'dominant_contract': current_dominant,
  183. 'start_date': period_start,
  184. 'end_date': current_date - datetime.timedelta(days=1)
  185. })
  186. # 开始新合约的记录
  187. current_dominant = dominant_contract
  188. period_start = current_date
  189. # if self.verbose_logging:
  190. # print(f" {current_date}: 主力合约变更为 {dominant_contract}")
  191. except Exception as e:
  192. if self.verbose_logging:
  193. print(f" 获取 {current_date} 的主力合约时出错: {str(e)}")
  194. current_date += datetime.timedelta(days=1)
  195. # 处理最后一个合约
  196. if current_dominant is not None and period_start is not None:
  197. results.append({
  198. 'symbol': symbol,
  199. 'dominant_contract': current_dominant,
  200. 'start_date': period_start,
  201. 'end_date': end_date
  202. })
  203. return results
  204. def get_dominant_contracts_history(self, symbol_list, start_date, end_date):
  205. """
  206. 获取指定时间范围内多个标的的主力合约历史变化
  207. 参数:
  208. symbol_list (list): 标的编码列表
  209. start_date (date): 开始日期
  210. end_date (date): 结束日期
  211. 返回:
  212. pandas.DataFrame: 包含主力合约变化历史的数据框
  213. """
  214. # if self.verbose_logging:
  215. # print(f"获取主力合约数据...")
  216. # print(f"标的列表: {symbol_list}")
  217. # print(f"时间范围: {start_date} 至 {end_date}")
  218. # 存储所有结果的列表
  219. all_results = []
  220. # 逐个处理每个标的
  221. for symbol in symbol_list:
  222. # if self.verbose_logging:
  223. # print(f"\n处理标的: {symbol}")
  224. try:
  225. # 获取该标的在指定时间范围内的主力合约历史
  226. symbol_results = self._get_symbol_dominant_history(symbol, start_date, end_date)
  227. all_results.extend(symbol_results)
  228. except Exception as e:
  229. if self.verbose_logging:
  230. print(f"处理标的 {symbol} 时发生错误: {str(e)}")
  231. continue
  232. # 将结果转换为DataFrame
  233. if all_results:
  234. result_df = pd.DataFrame(all_results)
  235. result_df = result_df.sort_values(['symbol', 'start_date']).reset_index(drop=True)
  236. if self.verbose_logging:
  237. print(f"\n成功获取 {len(result_df)} 条主力合约记录")
  238. return result_df
  239. else:
  240. if self.verbose_logging:
  241. print("\n未获取到任何主力合约数据")
  242. return pd.DataFrame(columns=['symbol', 'dominant_contract', 'start_date', 'end_date'])
  243. def build_contract_structure(self):
  244. """
  245. 构建期货主力合约数据结构 (dict1)
  246. 返回结构:Key=完整合约代码, Value=[start_date, end_date]
  247. """
  248. if self.verbose_logging:
  249. print("\n=== 步骤1: 构建期货主力合约数据结构 ===")
  250. # 分析时间范围
  251. analysis_start = self.analysis_start_date
  252. analysis_end = self.analysis_end_date
  253. if self.verbose_logging:
  254. print(f"分析时间范围: {analysis_start.strftime('%Y-%m-%d')} 至 {analysis_end.strftime('%Y-%m-%d')}")
  255. print(f"合约类型: 主力合约")
  256. # 构建主力合约数据结构
  257. contract_dict = self._build_dominant_contract_structure(analysis_start, analysis_end)
  258. self.contract_data = contract_dict
  259. if self.verbose_logging:
  260. print(f"构建完成,共{len(contract_dict)}个期货合约")
  261. return contract_dict
  262. def _build_dominant_contract_structure(self, analysis_start, analysis_end):
  263. """构建基于主力合约的数据结构"""
  264. if self.verbose_logging:
  265. print("使用主力合约获取逻辑...")
  266. # 获取要分析的期货品种列表
  267. if self.max_analysis_contracts > 0:
  268. symbol_list = list(self.exchange_map.keys())[:self.max_analysis_contracts]
  269. else:
  270. symbol_list = list(self.exchange_map.keys())
  271. # 获取主力合约历史数据
  272. dominant_df = self.get_dominant_contracts_history(
  273. symbol_list,
  274. analysis_start.date(),
  275. analysis_end.date()
  276. )
  277. contract_dict = {}
  278. if not dominant_df.empty:
  279. # 将DataFrame转换为字典结构
  280. for _, row in dominant_df.iterrows():
  281. contract_code = row['dominant_contract']
  282. start_date = row['start_date'] if isinstance(row['start_date'], datetime.date) else row['start_date'].date()
  283. end_date = row['end_date'] if isinstance(row['end_date'], datetime.date) else row['end_date'].date()
  284. contract_dict[contract_code] = [start_date, end_date]
  285. # if self.verbose_logging:
  286. # print(f" 添加主力合约: {contract_code} ({start_date} 至 {end_date})")
  287. return contract_dict
  288. def collect_extended_data(self):
  289. """
  290. 数据收集:为每个合约收集扩展时间范围的数据
  291. 扩展期限:从(start_date-365天)到(end_date+15天)
  292. """
  293. if self.verbose_logging:
  294. print("\n=== 步骤2: 收集扩展时间范围的数据 ===")
  295. extended_data = {}
  296. for contract_code, period in self.contract_data.items():
  297. start_date, end_date = period
  298. # 计算扩展期限
  299. start_date_2 = start_date - datetime.timedelta(days=self.historical_days)
  300. end_date_2 = end_date + datetime.timedelta(days=self.forward_days)
  301. # if self.verbose_logging:
  302. # print(f"获取 {contract_code} 数据")
  303. # print(f" 原始期间: {start_date} 至 {end_date}")
  304. # print(f" 扩展期间: {start_date_2} 至 {end_date_2}")
  305. try:
  306. # 获取日线数据
  307. data = get_price(
  308. contract_code,
  309. start_date=start_date_2,
  310. end_date=end_date_2,
  311. frequency='daily',
  312. fields=['open', 'close', 'high', 'low', 'volume'],
  313. skip_paused=False,
  314. panel=False
  315. )
  316. if data is not None and len(data) > 0:
  317. extended_data[contract_code] = {
  318. 'data': data,
  319. 'original_start': start_date,
  320. 'original_end': end_date,
  321. 'extended_start': start_date_2,
  322. 'extended_end': end_date_2
  323. }
  324. # if self.verbose_logging:
  325. # print(f" 成功获取{len(data)}条数据记录")
  326. # else:
  327. # if self.verbose_logging:
  328. # print(f" 未获取到数据")
  329. except Exception as e:
  330. if self.verbose_logging:
  331. print(f" 获取数据失败: {str(e)}")
  332. continue
  333. if self.verbose_logging:
  334. print(f"数据收集完成,共{len(extended_data)}个合约有效数据")
  335. return extended_data
  336. def calculate_body_thresholds(self, extended_data):
  337. """
  338. 计算实体长度阈值:使用历史数据计算平均实体长度
  339. """
  340. if self.verbose_logging:
  341. print("\n=== 步骤3: 计算实体长度阈值 ===")
  342. threshold_data = {}
  343. for contract_code, contract_info in extended_data.items():
  344. data = contract_info['data']
  345. start_date = contract_info['original_start']
  346. start_date_2 = contract_info['extended_start']
  347. # if self.verbose_logging:
  348. # print(f"计算 {contract_code} 的实体长度阈值")
  349. # print(f" 历史期间: {start_date_2} 至 {start_date}")
  350. # 筛选历史数据:date >= start_date_2 且 date < start_date
  351. historical_mask = (data.index.date >= start_date_2) & (data.index.date < start_date)
  352. historical_data = data[historical_mask]
  353. if len(historical_data) == 0:
  354. if self.verbose_logging:
  355. print(f" 历史数据无效,跳过")
  356. continue
  357. # 计算实体长度
  358. historical_data = historical_data.copy()
  359. historical_data['body_length'] = abs(historical_data['close'] - historical_data['open'])
  360. # 计算平均实体长度作为阈值
  361. body_threshold = historical_data['body_length'].mean()
  362. threshold_data[contract_code] = {
  363. 'contract_info': contract_info,
  364. 'body_threshold': body_threshold,
  365. 'historical_days': len(historical_data)
  366. }
  367. # if self.verbose_logging:
  368. # print(f" 实体长度阈值: {body_threshold:.4f} (基于{len(historical_data)}个历史交易日)")
  369. if self.verbose_logging:
  370. print("实体长度阈值计算完成")
  371. return threshold_data
  372. def identify_hatch_patterns(self, threshold_data):
  373. """
  374. 影线形态识别:扫描符合条件的K线
  375. 条件:实体长度 >= 阈值 且 影线长度 >= 1.2 × 实体长度
  376. """
  377. if self.verbose_logging:
  378. print(f"\n=== 步骤4: 识别影线形态 ===")
  379. pattern_dict = {} # dict2结构
  380. for contract_code, threshold_info in threshold_data.items():
  381. contract_info = threshold_info['contract_info']
  382. data = contract_info['data']
  383. body_threshold = threshold_info['body_threshold']
  384. start_date = contract_info['original_start'] # 使用原始开始日期
  385. end_date = contract_info['original_end']
  386. # if self.verbose_logging:
  387. # print(f"分析 {contract_code} 的影线形态")
  388. # print(f" 扫描期间: {start_date} 至 {end_date}")
  389. # print(f" 实体长度阈值: {body_threshold:.4f}")
  390. # 筛选扫描数据:从start_date到end_date(含)
  391. scan_mask = (data.index.date >= start_date) & (data.index.date <= end_date)
  392. scan_data = data[scan_mask]
  393. contract_patterns = {}
  394. pattern_count = 0
  395. for idx, (signal_date, row) in enumerate(scan_data.iterrows()):
  396. # 计算K线各项数值
  397. open_price = row['open']
  398. close_price = row['close']
  399. high_price = row['high']
  400. low_price = row['low']
  401. # 计算实体长度
  402. body_length = abs(close_price - open_price)
  403. # 检查实体长度是否满足阈值
  404. if body_length < body_threshold:
  405. continue
  406. # 计算影线长度
  407. upper_hatch = high_price - max(open_price, close_price)
  408. lower_hatch = min(open_price, close_price) - low_price
  409. # 检查影线条件
  410. hatch_direction = None
  411. hatch_length = 0
  412. # 检查上影线条件:上影线长度符合要求 且 下影线长度小于实体长度的一半
  413. if (upper_hatch >= self.hatch_to_body_ratio * body_length and
  414. lower_hatch < self.opposite_hatch_ratio * body_length):
  415. hatch_direction = 'up'
  416. hatch_length = upper_hatch
  417. # 检查下影线条件:下影线长度符合要求 且 上影线长度小于实体长度的一半
  418. elif (lower_hatch >= self.hatch_to_body_ratio * body_length and
  419. upper_hatch < self.opposite_hatch_ratio * body_length):
  420. hatch_direction = 'down'
  421. hatch_length = lower_hatch
  422. # 如果满足影线条件,记录形态
  423. if hatch_direction is not None:
  424. pattern_count += 1
  425. # dict3结构
  426. pattern_info = {
  427. 'date': signal_date.strftime('%Y-%m-%d'),
  428. 'high': float(high_price),
  429. 'low': float(low_price),
  430. 'open': float(open_price),
  431. 'close': float(close_price), # 标准收盘价
  432. 'body_len': float(body_length),
  433. 'hatch_dire': hatch_direction,
  434. 'hatch_len': float(hatch_length)
  435. }
  436. contract_patterns[signal_date] = pattern_info
  437. # if self.verbose_logging:
  438. # opposite_hatch_len = lower_hatch if hatch_direction == 'up' else upper_hatch
  439. # print(f" 发现形态: {signal_date.strftime('%Y-%m-%d')} {hatch_direction}影线 实体:{body_length:.4f} 影线:{hatch_length:.4f} 相反影线:{opposite_hatch_len:.4f}")
  440. if contract_patterns:
  441. pattern_dict[contract_code] = contract_patterns
  442. # if self.verbose_logging:
  443. # print(f" 检测到{pattern_count}个有效影线形态")
  444. # else:
  445. # if self.verbose_logging:
  446. # print(f" 未检测到有效影线形态")
  447. # if self.verbose_logging:
  448. # total_patterns = sum(len(patterns) for patterns in pattern_dict.values())
  449. # print(f"\n影线形态识别完成,共{total_patterns}个形态")
  450. self.hatch_patterns = pattern_dict
  451. return pattern_dict
  452. def analyze_future_performance(self, threshold_data, pattern_dict):
  453. """
  454. 未来表现分析:分析影线形态对未来价格走势的预测能力
  455. 交易方向:上影线做空,下影线做多
  456. """
  457. if self.verbose_logging:
  458. print(f"\n=== 步骤5: 分析未来表现 ===")
  459. enhanced_pattern_dict = {}
  460. for contract_code, contract_patterns in pattern_dict.items():
  461. # 获取对应的数据信息
  462. contract_info = threshold_data[contract_code]['contract_info']
  463. data = contract_info['data']
  464. end_date_2 = contract_info['extended_end']
  465. if self.verbose_logging:
  466. print(f"\n分析 {contract_code} 的未来表现")
  467. print(f" 监控窗口: {self.future_monitor_days}个交易日")
  468. enhanced_patterns = {}
  469. for signal_date, pattern_info in contract_patterns.items():
  470. hatch_direction = pattern_info['hatch_dire']
  471. standard_close = pattern_info['close'] # 标准收盘价
  472. print(f"形态日期:{signal_date.strftime('%Y-%m-%d')}, 标准收盘价: {standard_close}")
  473. # 在数据中找到信号日期的位置
  474. signal_idx = None
  475. for idx, (date, row) in enumerate(data.iterrows()):
  476. if date.date() == signal_date.date():
  477. signal_idx = idx
  478. break
  479. if signal_idx is None:
  480. if self.verbose_logging:
  481. print(f" 未找到信号日期 {signal_date.strftime('%Y-%m-%d')} 在数据中")
  482. continue
  483. # 分析未来N天的表现
  484. future_analysis = self._analyze_future_n_days(
  485. data, signal_idx, hatch_direction, standard_close,
  486. self.future_monitor_days, end_date_2
  487. )
  488. # 计算最大收益和最大风险(原逻辑:与影线方向相反交易)
  489. max_revenue_info, min_revenue_info = self._calculate_max_revenue_risk(future_analysis, hatch_direction, standard_close)
  490. # 计算最大收益和最大风险(新逻辑:与影线方向相同交易)
  491. max_revenue_info_2, min_revenue_info_2 = self._calculate_max_revenue_risk_2(future_analysis, hatch_direction, standard_close)
  492. # if self.verbose_logging:
  493. # print(f" 原逻辑最大收益: {max_revenue_info['revenue']:.4f} ({max_revenue_info['date']})")
  494. # print(f" 原逻辑最大亏损: {min_revenue_info['revenue']:.4f} ({min_revenue_info['date']})")
  495. # print(f" 新逻辑最大收益: {max_revenue_info_2['revenue']:.4f} ({max_revenue_info_2['date']})")
  496. # print(f" 新逻辑最大亏损: {min_revenue_info_2['revenue']:.4f} ({min_revenue_info_2['date']})")
  497. # 计算conclusion字段(增强版,包含日损失检查)
  498. conclusion = self._calculate_conclusion(max_revenue_info, min_revenue_info, future_analysis, hatch_direction, standard_close)
  499. # 计算conclusion2字段(新逻辑:与影线方向相同交易)
  500. conclusion_2 = self._calculate_conclusion_2(max_revenue_info_2, min_revenue_info_2, future_analysis, hatch_direction, standard_close)
  501. # 计算新增字段
  502. body_direction, direction_consistency = self._calculate_direction_fields(pattern_info, hatch_direction)
  503. # 计算影线与实体长度比率
  504. hatch_to_body_ratio = pattern_info['hatch_len'] / pattern_info['body_len'] if pattern_info['body_len'] > 0 else 0
  505. # 计算移动平均线
  506. ma_values = self._calculate_moving_averages(data, signal_idx)
  507. # 计算基于收盘价与5K移动平均线关系的价格方向
  508. price_direction = self._calculate_price_direction(standard_close, ma_values['ma5'])
  509. # 计算趋势强度
  510. trend_strength = self._calculate_trend_strength(ma_values)
  511. # 动态仓位计算
  512. remaining_amount = self._calculate_dynamic_position(
  513. conclusion, max_revenue_info['revenue'], self.initial_trade_amount
  514. )
  515. # 记录到投资组合(包含两种交易逻辑)
  516. self._add_portfolio_trade(
  517. contract_code,
  518. signal_date.strftime('%Y-%m-%d'),
  519. conclusion,
  520. conclusion_2,
  521. max_revenue_info['revenue'],
  522. max_revenue_info_2['revenue'],
  523. self.initial_trade_amount,
  524. remaining_amount
  525. )
  526. # 增强pattern_info
  527. enhanced_pattern = pattern_info.copy()
  528. enhanced_pattern.update({
  529. 'max_revenue_date': max_revenue_info['date'],
  530. 'max_revenue': max_revenue_info['revenue'],
  531. 'min_revenue_date': min_revenue_info['date'],
  532. 'min_revenue': min_revenue_info['revenue'],
  533. 'max_revenue_date2': max_revenue_info_2['date'],
  534. 'max_revenue2': max_revenue_info_2['revenue'],
  535. 'min_revenue_date2': min_revenue_info_2['date'],
  536. 'min_revenue2': min_revenue_info_2['revenue'],
  537. 'conclusion': conclusion,
  538. 'conclusion2': conclusion_2,
  539. 'body_direction': body_direction,
  540. 'direction_consistency': direction_consistency,
  541. 'price_direction': price_direction, # 新增:基于收盘价与5K移动平均线关系的价格方向
  542. 'trade_amount': float(self.initial_trade_amount),
  543. 'remaining_amount': float(remaining_amount),
  544. 'profit_loss': float(remaining_amount - self.initial_trade_amount),
  545. 'hatch_body_ratio': float(hatch_to_body_ratio),
  546. 'ma5': ma_values['ma5'],
  547. 'ma10': ma_values['ma10'],
  548. 'ma20': ma_values['ma20'],
  549. 'ma30': ma_values['ma30'],
  550. 'trend_strength': trend_strength
  551. })
  552. enhanced_patterns[signal_date] = enhanced_pattern
  553. if self.verbose_logging:
  554. print(f" {signal_date.strftime('%Y-%m-%d')} {hatch_direction}影线:")
  555. print(f" 原逻辑 - 最大收益: {max_revenue_info['revenue']:.4f} ({max_revenue_info['date']})")
  556. print(f" 原逻辑 - 最大亏损: {min_revenue_info['revenue']:.4f} ({min_revenue_info['date']})")
  557. print(f" 原逻辑 - 结论: {conclusion}")
  558. print(f" 新逻辑 - 最大收益: {max_revenue_info_2['revenue']:.4f} ({max_revenue_info_2['date']})")
  559. print(f" 新逻辑 - 最大亏损: {min_revenue_info_2['revenue']:.4f} ({min_revenue_info_2['date']})")
  560. print(f" 新逻辑 - 结论: {conclusion_2}")
  561. print(f" 实体方向: {body_direction}")
  562. print(f" 方向一致性: {direction_consistency}")
  563. print(f" 价格方向: {price_direction} (收盘价vs5K线)")
  564. if enhanced_patterns:
  565. enhanced_pattern_dict[contract_code] = enhanced_patterns
  566. if self.verbose_logging:
  567. print(f" 完成{len(enhanced_patterns)}个形态的未来表现分析")
  568. if self.verbose_logging:
  569. total_patterns = sum(len(patterns) for patterns in enhanced_pattern_dict.values())
  570. print(f"\n未来表现分析完成,共{total_patterns}个形态")
  571. return enhanced_pattern_dict
  572. def _analyze_future_n_days(self, data, signal_idx, hatch_direction, standard_close, monitor_days, end_date_2):
  573. """分析信号后未来N天的价格表现"""
  574. future_prices = []
  575. data_list = list(data.iterrows())
  576. for i in range(1, monitor_days + 1):
  577. future_idx = signal_idx + i
  578. if future_idx < len(data_list):
  579. future_date, future_row = data_list[future_idx]
  580. # 检查是否超出扩展结束日期
  581. if future_date.date() > end_date_2:
  582. break
  583. future_prices.append({
  584. 'date': future_date.strftime('%Y-%m-%d'),
  585. 'high': float(future_row['high']),
  586. 'low': float(future_row['low'])
  587. })
  588. else:
  589. break
  590. return future_prices
  591. def _calculate_max_revenue_risk(self, future_analysis, hatch_direction, standard_close):
  592. """计算最大收益和最大风险(原逻辑:与影线方向相反交易)"""
  593. if not future_analysis:
  594. return {'date': None, 'revenue': 0.0}, {'date': None, 'revenue': 0.0}
  595. max_revenue = float('-inf')
  596. min_revenue = float('inf')
  597. max_revenue_date = None
  598. min_revenue_date = None
  599. for day_data in future_analysis:
  600. date = day_data['date']
  601. high = day_data['high']
  602. low = day_data['low']
  603. if hatch_direction == 'up':
  604. # 上影线做空:收益 = (标准收盘价 - 最低价) / 标准收盘价,风险 = (标准收盘价 - 最高价) / 标准收盘价
  605. revenue = (standard_close - low) / standard_close
  606. risk = (standard_close - high) / standard_close
  607. else:
  608. # 下影线做多:收益 = (最高价 - 标准收盘价) / 标准收盘价,风险 = (最低价 - 标准收盘价) / 标准收盘价
  609. revenue = (high - standard_close) / standard_close
  610. risk = (low - standard_close) / standard_close
  611. # 更新最大收益
  612. if revenue > max_revenue:
  613. max_revenue = round(revenue, 4)
  614. max_revenue_date = date
  615. # 更新最大风险(最大亏损,即最小收益的相反数)
  616. if risk < min_revenue:
  617. min_revenue = round(risk, 4)
  618. min_revenue_date = date
  619. return {'date': max_revenue_date, 'revenue': float(max_revenue)}, {'date': min_revenue_date, 'revenue': float(min_revenue)}
  620. def _calculate_max_revenue_risk_2(self, future_analysis, hatch_direction, standard_close):
  621. """计算最大收益和最大风险(新逻辑:与影线方向相同交易)"""
  622. if not future_analysis:
  623. return {'date': None, 'revenue': 0.0}, {'date': None, 'revenue': 0.0}
  624. max_revenue_2 = float('-inf')
  625. min_revenue_2 = float('inf')
  626. max_revenue_date_2 = None
  627. min_revenue_date_2 = None
  628. for day_data in future_analysis:
  629. date = day_data['date']
  630. high = day_data['high']
  631. low = day_data['low']
  632. if hatch_direction == 'up':
  633. # 上影线做多:收益 = (最高价 - 标准收盘价) / 标准收盘价,风险 = (最低价 - 标准收盘价) / 标准收盘价
  634. revenue_2 = (high - standard_close) / standard_close
  635. risk_2 = (low - standard_close) / standard_close
  636. else:
  637. # 下影线做空:收益 = (标准收盘价 - 最低价) / 标准收盘价,风险 = (标准收盘价 - 最高价) / 标准收盘价
  638. revenue_2 = (standard_close - low) / standard_close
  639. risk_2 = (standard_close - high) / standard_close
  640. # 更新最大收益
  641. if revenue_2 > max_revenue_2:
  642. max_revenue_2 = round(revenue_2, 4)
  643. max_revenue_date_2 = date
  644. # 更新最大风险(最大亏损,即最小收益的相反数)
  645. if risk_2 < min_revenue_2:
  646. min_revenue_2 = round(risk_2, 4)
  647. min_revenue_date_2 = date
  648. return {'date': max_revenue_date_2, 'revenue': float(max_revenue_2)}, {'date': min_revenue_date_2, 'revenue': float(min_revenue_2)}
  649. def _calculate_conclusion(self, max_revenue_info, min_revenue_info, future_analysis, hatch_direction, standard_close):
  650. """
  651. 计算形态的结论:是否为有效的预测信号(增强版)
  652. 逻辑:
  653. - 若满足以下条件则返回 "true":
  654. 1. (最大盈利日期 < 最大亏损日期)且(最大盈利 > max_revenue_threshold)
  655. 2. 或者(最大盈利日期 > 最大亏损日期)且(最大盈利 > max_revenue_threshold)且(最大亏损 < max_loss_threshold)
  656. - 增强条件:如果在最大收益日期之前的任何一天,损失超过了MAX_LOSS_THRESHOLD,则返回 "false"
  657. - 否则返回 "false"
  658. """
  659. max_revenue_date = max_revenue_info['date']
  660. max_revenue = max_revenue_info['revenue']
  661. min_revenue_date = min_revenue_info['date']
  662. min_revenue = min_revenue_info['revenue']
  663. # 如果日期为空,返回false
  664. if max_revenue_date is None or min_revenue_date is None:
  665. return "false"
  666. # 转换日期字符串为datetime对象进行比较
  667. try:
  668. from datetime import datetime
  669. max_revenue_dt = datetime.strptime(max_revenue_date, '%Y-%m-%d')
  670. min_revenue_dt = datetime.strptime(min_revenue_date, '%Y-%m-%d')
  671. except:
  672. return "false"
  673. # 增强检查:检查最大收益日期之前是否有任何一天的损失超过阈值
  674. if future_analysis and self._check_early_excessive_loss(future_analysis, max_revenue_date, hatch_direction, standard_close):
  675. return "false"
  676. # 条件1:(最大盈利日期 < 最大亏损日期)且(最大盈利 > 阈值)
  677. condition1 = (max_revenue_dt < min_revenue_dt) and (max_revenue > self.max_revenue_threshold)
  678. # 条件2:(最大盈利日期 > 最大亏损日期)且(最大盈利 > 阈值)且(最大亏损 < 阈值)
  679. condition2 = (max_revenue_dt > min_revenue_dt) and (max_revenue > self.max_revenue_threshold) and (abs(min_revenue) < self.max_loss_threshold)
  680. return "true" if (condition1 or condition2) else "false"
  681. def _check_early_excessive_loss(self, future_analysis, max_revenue_date, hatch_direction, standard_close):
  682. """
  683. 检查在最大收益日期之前是否有任何一天的损失超过阈值(原逻辑:与影线方向相反交易)
  684. 参数:
  685. future_analysis: 未来价格分析数据
  686. max_revenue_date: 最大收益日期
  687. hatch_direction: 影线方向
  688. standard_close: 标准收盘价
  689. 返回:
  690. bool: True表示存在早期过度损失
  691. """
  692. from datetime import datetime
  693. try:
  694. max_revenue_dt = datetime.strptime(max_revenue_date, '%Y-%m-%d')
  695. except:
  696. return False
  697. for day_data in future_analysis:
  698. day_date_str = day_data['date']
  699. try:
  700. day_dt = datetime.strptime(day_date_str, '%Y-%m-%d')
  701. # 只检查最大收益日期之前的日期
  702. if day_dt >= max_revenue_dt:
  703. continue
  704. high = day_data['high']
  705. low = day_data['low']
  706. if hatch_direction == 'up':
  707. # 上影线做空:亏损 = (标准收盘价 - 最高价) / 标准收盘价,当价格上涨时为负值(亏损)
  708. loss_value = (standard_close - high) / standard_close
  709. else:
  710. # 下影线做多:亏损 = (最低价 - 标准收盘价) / 标准收盘价,当价格下跌时为负值(亏损)
  711. loss_value = (low - standard_close) / standard_close
  712. # 检查亏损是否超过阈值:亏损为负值,阈值为正值,所以检查 abs(亏损) > 阈值
  713. if loss_value < 0 and abs(loss_value) > self.max_loss_threshold:
  714. return True
  715. except:
  716. continue
  717. return False
  718. def _calculate_conclusion_2(self, max_revenue_info_2, min_revenue_info_2, future_analysis, hatch_direction, standard_close):
  719. """
  720. 计算形态的结论2:是否为有效的预测信号(新逻辑:与影线方向相同交易)
  721. 逻辑:
  722. - 若满足以下条件则返回 "true":
  723. 1. (最大盈利日期 < 最大亏损日期)且(最大盈利 > max_revenue_threshold)
  724. 2. 或者(最大盈利日期 > 最大亏损日期)且(最大盈利 > max_revenue_threshold)且(最大亏损 < max_loss_threshold)
  725. - 增强条件:如果在最大收益日期之前的任何一天,损失超过了MAX_LOSS_THRESHOLD,则返回 "false"
  726. - 否则返回 "false"
  727. """
  728. max_revenue_date_2 = max_revenue_info_2['date']
  729. max_revenue_2 = max_revenue_info_2['revenue']
  730. min_revenue_date_2 = min_revenue_info_2['date']
  731. min_revenue_2 = min_revenue_info_2['revenue']
  732. # 如果日期为空,返回false
  733. if max_revenue_date_2 is None or min_revenue_date_2 is None:
  734. return "false"
  735. # 转换日期字符串为datetime对象进行比较
  736. try:
  737. from datetime import datetime
  738. max_revenue_dt_2 = datetime.strptime(max_revenue_date_2, '%Y-%m-%d')
  739. min_revenue_dt_2 = datetime.strptime(min_revenue_date_2, '%Y-%m-%d')
  740. except:
  741. return "false"
  742. # 增强检查:检查最大收益日期之前是否有任何一天的损失超过阈值(新逻辑)
  743. if future_analysis and self._check_early_excessive_loss_2(future_analysis, max_revenue_date_2, hatch_direction, standard_close):
  744. return "false"
  745. # 条件1:(最大盈利日期 < 最大亏损日期)且(最大盈利 > 阈值)
  746. condition1 = (max_revenue_dt_2 < min_revenue_dt_2) and (max_revenue_2 > self.max_revenue_threshold)
  747. # 条件2:(最大盈利日期 > 最大亏损日期)且(最大盈利 > 阈值)且(最大亏损 < 阈值)
  748. condition2 = (max_revenue_dt_2 > min_revenue_dt_2) and (max_revenue_2 > self.max_revenue_threshold) and (abs(min_revenue_2) < self.max_loss_threshold)
  749. return "true" if (condition1 or condition2) else "false"
  750. def _check_early_excessive_loss_2(self, future_analysis, max_revenue_date_2, hatch_direction, standard_close):
  751. """
  752. 检查在最大收益日期之前是否有任何一天的损失超过阈值(新逻辑:与影线方向相同交易)
  753. 参数:
  754. future_analysis: 未来价格分析数据
  755. max_revenue_date_2: 最大收益日期
  756. hatch_direction: 影线方向
  757. standard_close: 标准收盘价
  758. 返回:
  759. bool: True表示存在早期过度损失
  760. """
  761. from datetime import datetime
  762. try:
  763. max_revenue_dt_2 = datetime.strptime(max_revenue_date_2, '%Y-%m-%d')
  764. except:
  765. return False
  766. for day_data in future_analysis:
  767. day_date_str = day_data['date']
  768. try:
  769. day_dt = datetime.strptime(day_date_str, '%Y-%m-%d')
  770. # 只检查最大收益日期之前的日期
  771. if day_dt >= max_revenue_dt_2:
  772. continue
  773. high = day_data['high']
  774. low = day_data['low']
  775. if hatch_direction == 'up':
  776. # 上影线做多:亏损 = (最低价 - 标准收盘价) / 标准收盘价,当价格下跌时为负值(亏损)
  777. loss_value = (low - standard_close) / standard_close
  778. else:
  779. # 下影线做空:亏损 = (标准收盘价 - 最高价) / 标准收盘价,当价格上涨时为负值(亏损)
  780. loss_value = (standard_close - high) / standard_close
  781. # 检查亏损是否超过阈值:亏损为负值,阈值为正值,所以检查 abs(亏损) > 阈值
  782. if loss_value < 0 and abs(loss_value) > self.max_loss_threshold:
  783. return True
  784. except:
  785. continue
  786. return False
  787. def _calculate_direction_fields(self, pattern_info, hatch_direction):
  788. """
  789. 计算body_direction和direction_consistency字段
  790. 参数:
  791. pattern_info: K线形态信息
  792. hatch_direction: 影线方向 ('up' 或 'down')
  793. 返回:
  794. tuple: (body_direction, direction_consistency)
  795. """
  796. open_price = pattern_info['open']
  797. close_price = pattern_info['close']
  798. # 计算body_direction:实体方向
  799. if close_price > open_price:
  800. body_direction = 'up'
  801. elif close_price < open_price:
  802. body_direction = 'down'
  803. else:
  804. body_direction = 'flat' # 十字星情况
  805. # 计算direction_consistency:检查实体方向与影线方向是否相反
  806. if body_direction == 'flat':
  807. direction_consistency = 'false' # 十字星情况无法判断一致性
  808. elif (body_direction == 'up' and hatch_direction == 'down') or \
  809. (body_direction == 'down' and hatch_direction == 'up'):
  810. direction_consistency = 'true' # 相反,符合预期
  811. else:
  812. direction_consistency = 'false' # 不相反
  813. return body_direction, direction_consistency
  814. def _calculate_price_direction(self, close_price, ma5_value):
  815. """
  816. 计算基于收盘价与5K移动平均线关系的价格方向
  817. 参数:
  818. close_price: 形态形成日的收盘价
  819. ma5_value: 5日移动平均线价格
  820. 返回:
  821. str: 价格方向分类 ("上涨" 或 "下跌")
  822. """
  823. if ma5_value is None:
  824. return "数据不足" # 如果MA5数据不足,返回数据不足
  825. if close_price >= ma5_value:
  826. return "上涨"
  827. else:
  828. return "下跌"
  829. def _calculate_dynamic_position(self, conclusion, max_revenue, trade_amount):
  830. """
  831. 计算动态仓位调整后的剩余资金
  832. 参数:
  833. conclusion: 交易结论 ('true' 为正确, 'false' 为错误)
  834. max_revenue: 最大盈利率
  835. trade_amount: 当前交易金额
  836. 返回:
  837. float: 调整后的剩余资金
  838. """
  839. if conclusion == "false": # 亏损交易(错误结果)
  840. remaining_amount = trade_amount * (1 - self.loss_rate)
  841. if self.verbose_logging:
  842. print(f" 亏损交易,剩余资金: {remaining_amount:.2f} (亏损率: {self.loss_rate})")
  843. else: # 盈利交易(正确结果)
  844. remaining_amount = trade_amount * (1 + max_revenue - self.profit_drawdown_rate)
  845. if self.verbose_logging:
  846. print(f" 盈利交易,剩余资金: {remaining_amount:.2f} (最大利润: {max_revenue:.4f}, 回撤率: {self.profit_drawdown_rate})")
  847. return remaining_amount
  848. def _add_portfolio_trade(self, contract_code, date, conclusion, conclusion_2, max_revenue, max_revenue_2, trade_amount, remaining_amount):
  849. """
  850. 添加交易记录到投资组合(支持两种交易逻辑)
  851. 参数:
  852. contract_code: 合约代码
  853. date: 交易日期
  854. conclusion: 原逻辑交易结论
  855. conclusion_2: 新逻辑交易结论
  856. max_revenue: 原逻辑最大盈利率
  857. max_revenue_2: 新逻辑最大盈利率
  858. trade_amount: 交易金额
  859. remaining_amount: 剩余金额(基于原逻辑计算)
  860. """
  861. # 计算新逻辑的剩余金额
  862. remaining_amount_2 = self._calculate_dynamic_position(
  863. conclusion_2, max_revenue_2, trade_amount
  864. )
  865. trade_record = {
  866. 'contract_code': contract_code,
  867. 'date': date,
  868. 'conclusion': conclusion,
  869. 'conclusion2': conclusion_2,
  870. 'max_revenue': max_revenue,
  871. 'max_revenue2': max_revenue_2,
  872. 'trade_amount': trade_amount,
  873. 'remaining_amount': remaining_amount,
  874. 'remaining_amount2': remaining_amount_2,
  875. 'profit_loss': remaining_amount - trade_amount,
  876. 'profit_loss2': remaining_amount_2 - trade_amount
  877. }
  878. self.portfolio_results.append(trade_record)
  879. def _calculate_moving_averages(self, data, signal_idx):
  880. """
  881. 计算形态形成日的移动平均线
  882. 参数:
  883. data: 价格数据DataFrame
  884. signal_idx: 信号日期在数据中的索引
  885. 返回:
  886. dict: 包含各移动平均线的字典
  887. """
  888. if signal_idx < 29: # 至少需要30天数据来计算30日移动平均
  889. return {'ma5': None, 'ma10': None, 'ma20': None, 'ma30': None}
  890. # 获取到信号日期为止的收盘价数据
  891. data_list = list(data.iterrows())
  892. closes = []
  893. for i in range(max(0, signal_idx - 29), signal_idx + 1): # 取30天数据
  894. if i < len(data_list):
  895. _, row = data_list[i]
  896. closes.append(row['close'])
  897. if len(closes) < 30:
  898. return {'ma5': None, 'ma10': None, 'ma20': None, 'ma30': None}
  899. # 计算移动平均线(使用最后的N天数据)
  900. ma5 = sum(closes[-5:]) / 5 if len(closes) >= 5 else None
  901. ma10 = sum(closes[-10:]) / 10 if len(closes) >= 10 else None
  902. ma20 = sum(closes[-20:]) / 20 if len(closes) >= 20 else None
  903. ma30 = sum(closes[-30:]) / 30 if len(closes) >= 30 else None
  904. return {
  905. 'ma5': round(ma5, 2) if ma5 is not None else None,
  906. 'ma10': round(ma10, 2) if ma10 is not None else None,
  907. 'ma20': round(ma20, 2) if ma20 is not None else None,
  908. 'ma30': round(ma30, 2) if ma30 is not None else None
  909. }
  910. def _calculate_trend_strength(self, ma_values):
  911. """
  912. 计算趋势强度评估
  913. 参数:
  914. ma_values: 移动平均线字典
  915. 返回:
  916. str: 趋势强度分类
  917. """
  918. ma5 = ma_values['ma5']
  919. ma10 = ma_values['ma10']
  920. ma20 = ma_values['ma20']
  921. ma30 = ma_values['ma30']
  922. # 如果有任何移动平均线为空,返回"数据不足"
  923. if None in [ma5, ma10, ma20, ma30]:
  924. return "数据不足"
  925. # 进行6项趋势比较
  926. comparisons = [
  927. ma5 >= ma10, # 5K≥10K
  928. ma10 >= ma20, # 10K≥20K
  929. ma20 >= ma30, # 20K≥30K
  930. ma5 >= ma20, # 5K≥20K
  931. ma10 >= ma30, # 10K≥30K
  932. ma5 >= ma30 # 5K≥30K
  933. ]
  934. # 计算上涨趋势的数量
  935. uptrend_count = sum(comparisons)
  936. # 根据上涨趋势数量进行分类
  937. if uptrend_count == 6 or uptrend_count == 0:
  938. return "强趋势"
  939. elif uptrend_count == 3:
  940. return "震荡"
  941. elif uptrend_count == 5 or uptrend_count == 1:
  942. return "中趋势"
  943. elif uptrend_count == 4 or uptrend_count == 2:
  944. return "弱趋势"
  945. else:
  946. return "未知" # 理论上不应该到这里
  947. def calculate_portfolio_summary(self):
  948. """
  949. 计算投资组合整体表现汇总(支持两种交易逻辑对比)
  950. 返回:
  951. dict: 投资组合汇总信息
  952. """
  953. if not self.portfolio_results:
  954. return {
  955. 'total_trades': 0,
  956. 'logic1': {
  957. 'successful_trades': 0,
  958. 'failed_trades': 0,
  959. 'success_rate': 0.0,
  960. 'total_profit_loss': 0.0,
  961. 'final_capital': self.initial_trade_amount,
  962. 'total_return': 0.0
  963. },
  964. 'logic2': {
  965. 'successful_trades': 0,
  966. 'failed_trades': 0,
  967. 'success_rate': 0.0,
  968. 'total_profit_loss': 0.0,
  969. 'final_capital': self.initial_trade_amount,
  970. 'total_return': 0.0
  971. }
  972. }
  973. total_trades = len(self.portfolio_results)
  974. initial_capital = total_trades * self.initial_trade_amount
  975. # 原逻辑(与影线方向相反交易)统计
  976. successful_trades_1 = sum(1 for trade in self.portfolio_results if trade['conclusion'] == 'true')
  977. failed_trades_1 = total_trades - successful_trades_1
  978. success_rate_1 = (successful_trades_1 / total_trades) * 100 if total_trades > 0 else 0
  979. total_profit_loss_1 = sum(trade['profit_loss'] for trade in self.portfolio_results)
  980. final_capital_1 = initial_capital + total_profit_loss_1
  981. total_return_1 = (total_profit_loss_1 / initial_capital * 100) if initial_capital > 0 else 0
  982. # 新逻辑(与影线方向相同交易)统计
  983. successful_trades_2 = sum(1 for trade in self.portfolio_results if trade['conclusion2'] == 'true')
  984. failed_trades_2 = total_trades - successful_trades_2
  985. success_rate_2 = (successful_trades_2 / total_trades) * 100 if total_trades > 0 else 0
  986. total_profit_loss_2 = sum(trade['profit_loss2'] for trade in self.portfolio_results)
  987. final_capital_2 = initial_capital + total_profit_loss_2
  988. total_return_2 = (total_profit_loss_2 / initial_capital * 100) if initial_capital > 0 else 0
  989. portfolio_summary = {
  990. 'total_trades': total_trades,
  991. 'initial_capital': initial_capital,
  992. 'logic1': {
  993. 'name': '原逻辑(与影线方向相反交易)',
  994. 'successful_trades': successful_trades_1,
  995. 'failed_trades': failed_trades_1,
  996. 'success_rate': round(success_rate_1, 2),
  997. 'total_profit_loss': round(total_profit_loss_1, 2),
  998. 'final_capital': round(final_capital_1, 2),
  999. 'total_return': round(total_return_1, 2)
  1000. },
  1001. 'logic2': {
  1002. 'name': '新逻辑(与影线方向相同交易)',
  1003. 'successful_trades': successful_trades_2,
  1004. 'failed_trades': failed_trades_2,
  1005. 'success_rate': round(success_rate_2, 2),
  1006. 'total_profit_loss': round(total_profit_loss_2, 2),
  1007. 'final_capital': round(final_capital_2, 2),
  1008. 'total_return': round(total_return_2, 2)
  1009. }
  1010. }
  1011. if self.verbose_logging:
  1012. print(f"\n=== 投资组合双向交易逻辑对比分析 ===")
  1013. print(f"总交易次数: {total_trades}")
  1014. print(f"初始资本: {initial_capital:.2f}")
  1015. print(f"\n--- 原逻辑(与影线方向相反交易)---")
  1016. print(f"成功交易: {successful_trades_1}")
  1017. print(f"失败交易: {failed_trades_1}")
  1018. print(f"成功率: {success_rate_1:.2f}%")
  1019. print(f"总盈亏: {total_profit_loss_1:.2f}")
  1020. print(f"最终资本: {final_capital_1:.2f}")
  1021. print(f"总回报率: {total_return_1:.2f}%")
  1022. print(f"\n--- 新逻辑(与影线方向相同交易)---")
  1023. print(f"成功交易: {successful_trades_2}")
  1024. print(f"失败交易: {failed_trades_2}")
  1025. print(f"成功率: {success_rate_2:.2f}%")
  1026. print(f"总盈亏: {total_profit_loss_2:.2f}")
  1027. print(f"最终资本: {final_capital_2:.2f}")
  1028. print(f"总回报率: {total_return_2:.2f}%")
  1029. print(f"\n--- 逻辑对比 ---")
  1030. print(f"成功率差异: {success_rate_2 - success_rate_1:.2f}% (新逻辑 - 原逻辑)")
  1031. print(f"回报率差异: {total_return_2 - total_return_1:.2f}% (新逻辑 - 原逻辑)")
  1032. return portfolio_summary
  1033. def generate_csv_output(self, enhanced_pattern_dict):
  1034. """
  1035. 生成CSV输出文件
  1036. """
  1037. if self.verbose_logging:
  1038. print("\n=== 步骤6: 生成CSV输出文件 ===")
  1039. # 准备CSV数据(按指定顺序排列字段)
  1040. csv_data = []
  1041. for contract_code, patterns in enhanced_pattern_dict.items():
  1042. for signal_date, pattern_info in patterns.items():
  1043. csv_data.append({
  1044. # 按更新的字段顺序:contract_code, date, conclusion, conclusion2, body_direction, direction_consistency, price_direction, body_len, hatch_len, hatch/body, close, open, high, low, max_revenue, max_revenue_date, min_revenue, min_revenue_date, max_revenue2, max_revenue_date2, min_revenue2, min_revenue_date2, profit_loss, remaining_amount, trade_amount
  1045. 'contract_code': contract_code,
  1046. 'date': pattern_info['date'],
  1047. 'conclusion': pattern_info['conclusion'],
  1048. 'conclusion2': pattern_info['conclusion2'],
  1049. 'body_direction': pattern_info['body_direction'],
  1050. 'direction_consistency': pattern_info['direction_consistency'],
  1051. 'price_direction': pattern_info['price_direction'], # 新增:基于收盘价与5K移动平均线关系的价格方向
  1052. 'body_len': pattern_info['body_len'],
  1053. 'hatch_len': pattern_info['hatch_len'],
  1054. 'hatch/body': pattern_info['hatch_body_ratio'],
  1055. 'close': pattern_info['close'],
  1056. 'open': pattern_info['open'],
  1057. 'high': pattern_info['high'],
  1058. 'low': pattern_info['low'],
  1059. 'max_revenue': pattern_info['max_revenue'],
  1060. 'max_revenue_date': pattern_info['max_revenue_date'],
  1061. 'min_revenue': pattern_info['min_revenue'],
  1062. 'min_revenue_date': pattern_info['min_revenue_date'],
  1063. 'max_revenue2': pattern_info['max_revenue2'],
  1064. 'max_revenue_date2': pattern_info['max_revenue_date2'],
  1065. 'min_revenue2': pattern_info['min_revenue2'],
  1066. 'min_revenue_date2': pattern_info['min_revenue_date2'],
  1067. 'profit_loss': pattern_info['profit_loss'],
  1068. 'remaining_amount': pattern_info['remaining_amount'],
  1069. 'trade_amount': pattern_info['trade_amount']
  1070. })
  1071. if csv_data:
  1072. # 创建DataFrame并保存为CSV,严格按照用户指定的列顺序
  1073. df = pd.DataFrame(csv_data)
  1074. # 定义更新的确切列顺序(包含新的price_direction字段)
  1075. column_order = [
  1076. 'contract_code', 'date', 'conclusion', 'conclusion2', 'body_direction',
  1077. 'direction_consistency', 'price_direction', 'body_len', 'hatch_len', 'hatch/body',
  1078. 'close', 'open', 'high', 'low', 'max_revenue', 'max_revenue_date',
  1079. 'min_revenue', 'min_revenue_date', 'max_revenue2', 'max_revenue_date2',
  1080. 'min_revenue2', 'min_revenue_date2', 'profit_loss', 'remaining_amount', 'trade_amount'
  1081. ]
  1082. # 重新排列DataFrame的列顺序
  1083. df = df[column_order]
  1084. timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
  1085. filename = f'future_candlestick_hatch_analysis_{timestamp}.csv'
  1086. df.to_csv(filename, index=False, encoding=self.output_encoding)
  1087. if self.verbose_logging:
  1088. print(f"分析结果已保存至: {filename}")
  1089. print(f"总计 {len(csv_data)} 条记录")
  1090. return filename
  1091. else:
  1092. if self.verbose_logging:
  1093. print("没有数据可以输出")
  1094. return None
  1095. def run_complete_analysis(self):
  1096. """执行完整的分析流程"""
  1097. if self.verbose_logging:
  1098. print("开始执行期货烛台影线形态分析")
  1099. print("=" * 60)
  1100. try:
  1101. # 步骤1: 构建合约数据结构
  1102. contract_structure = self.build_contract_structure()
  1103. if not contract_structure:
  1104. if self.verbose_logging:
  1105. print("未获取到有效合约,分析终止")
  1106. return None
  1107. # 步骤2: 收集扩展数据
  1108. extended_data = self.collect_extended_data()
  1109. if not extended_data:
  1110. if self.verbose_logging:
  1111. print("未获取到有效数据,分析终止")
  1112. return None
  1113. # 步骤3: 计算实体长度阈值
  1114. threshold_data = self.calculate_body_thresholds(extended_data)
  1115. if not threshold_data:
  1116. if self.verbose_logging:
  1117. print("未能计算有效阈值,分析终止")
  1118. return None
  1119. # 步骤4: 识别影线形态
  1120. pattern_dict = self.identify_hatch_patterns(threshold_data)
  1121. if not pattern_dict:
  1122. if self.verbose_logging:
  1123. print("未检测到有效影线形态,分析终止")
  1124. return None
  1125. # 步骤5: 分析未来表现
  1126. enhanced_pattern_dict = self.analyze_future_performance(threshold_data, pattern_dict)
  1127. # 步骤6: 生成CSV输出
  1128. output_filename = self.generate_csv_output(enhanced_pattern_dict)
  1129. # 步骤7: 计算投资组合汇总
  1130. portfolio_summary = self.calculate_portfolio_summary()
  1131. # 分析汇总
  1132. total_contracts = len(contract_structure)
  1133. valid_contracts = len(threshold_data)
  1134. total_patterns = sum(len(patterns) for patterns in enhanced_pattern_dict.values())
  1135. # 计算模式有效性指标
  1136. effective_patterns = 0
  1137. for contract_patterns in enhanced_pattern_dict.values():
  1138. for pattern_info in contract_patterns.values():
  1139. if pattern_info.get('conclusion') == 'true':
  1140. effective_patterns += 1
  1141. effective_rate = (effective_patterns / total_patterns * 100) if total_patterns > 0 else 0
  1142. if self.verbose_logging:
  1143. print("\n" + "=" * 60)
  1144. print("分析完成汇总:")
  1145. print(f"总合约数: {total_contracts}")
  1146. print(f"有效合约数: {valid_contracts}")
  1147. print(f"检测形态数: {total_patterns}")
  1148. print(f"有效形态数: {effective_patterns}")
  1149. print(f"有效率: {effective_rate:.1f}%")
  1150. print(f"输出文件: {output_filename}")
  1151. return {
  1152. 'contract_structure': contract_structure,
  1153. 'threshold_data': threshold_data,
  1154. 'pattern_results': enhanced_pattern_dict,
  1155. 'output_filename': output_filename,
  1156. 'portfolio_summary': portfolio_summary,
  1157. 'summary': {
  1158. 'total_contracts': total_contracts,
  1159. 'valid_contracts': valid_contracts,
  1160. 'total_patterns': total_patterns,
  1161. 'effective_patterns': effective_patterns,
  1162. 'effective_rate': effective_rate
  1163. }
  1164. }
  1165. except Exception as e:
  1166. if self.verbose_logging:
  1167. print(f"分析过程中出现错误: {str(e)}")
  1168. import traceback
  1169. traceback.print_exc()
  1170. return None
  1171. # =====================================================================================
  1172. # 主程序入口
  1173. # =====================================================================================
  1174. def run_candlestick_hatch_analysis(config=None):
  1175. """运行期货烛台影线形态分析"""
  1176. if config is None:
  1177. config = CandlestickHatchConfig
  1178. # 打印配置信息
  1179. config.print_config()
  1180. # 创建分析器并运行
  1181. analyzer = FutureCandlestickHatchAnalyzer(config)
  1182. results = analyzer.run_complete_analysis()
  1183. return results
  1184. # 执行分析
  1185. if __name__ == "__main__":
  1186. print("期货烛台影线形态双向交易分析工具")
  1187. print("研究带有明显影线的K线对未来价格走势的预测能力")
  1188. print("双向交易逻辑对比测试:")
  1189. print(" 原逻辑:上影线做空,下影线做多(与影线方向相反交易)")
  1190. print(" 新逻辑:上影线做多,下影线做空(与影线方向相同交易)")
  1191. print("适用于聚宽在线研究平台")
  1192. results = run_candlestick_hatch_analysis()
  1193. if results:
  1194. print("\n✅ 分析执行成功!")
  1195. summary = results['summary']
  1196. portfolio = results['portfolio_summary']
  1197. print(f"📊 结果摘要:")
  1198. print(f" - 分析合约数: {summary['total_contracts']}")
  1199. print(f" - 有效数据合约: {summary['valid_contracts']}")
  1200. print(f" - 检测形态总数: {summary['total_patterns']}")
  1201. print(f" - 有效形态数: {summary['effective_patterns']}")
  1202. print(f" - 有效率: {summary['effective_rate']:.1f}%")
  1203. print(f" - 输出文件: {results['output_filename']}")
  1204. print(f"\n💰 双向交易逻辑对比分析:")
  1205. print(f" - 总交易次数: {portfolio['total_trades']}")
  1206. print(f" - 初始资本: {portfolio['initial_capital']:.2f}")
  1207. print(f"\n 📈 原逻辑(与影线方向相反交易):")
  1208. print(f" 成功率: {portfolio['logic1']['success_rate']:.2f}%")
  1209. print(f" 总盈亏: {portfolio['logic1']['total_profit_loss']:.2f}")
  1210. print(f" 总回报率: {portfolio['logic1']['total_return']:.2f}%")
  1211. print(f"\n 📉 新逻辑(与影线方向相同交易):")
  1212. print(f" 成功率: {portfolio['logic2']['success_rate']:.2f}%")
  1213. print(f" 总盈亏: {portfolio['logic2']['total_profit_loss']:.2f}")
  1214. print(f" 总回报率: {portfolio['logic2']['total_return']:.2f}%")
  1215. print(f"\n 🔍 逻辑对比:")
  1216. success_diff = portfolio['logic2']['success_rate'] - portfolio['logic1']['success_rate']
  1217. return_diff = portfolio['logic2']['total_return'] - portfolio['logic1']['total_return']
  1218. print(f" 成功率差异: {success_diff:+.2f}% (新逻辑 - 原逻辑)")
  1219. print(f" 回报率差异: {return_diff:+.2f}% (新逻辑 - 原逻辑)")
  1220. else:
  1221. print("\n❌ 分析执行失败,请检查错误信息")