analysis_chart.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import pandas as pd
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. from statistics import mean
  5. import os
  6. from datetime import datetime
  7. def analyze_options(*options):
  8. """
  9. 统一的期权分析方法
  10. 参数:
  11. *options: 一个或多个期权,每个期权格式为 (direction, option_type, premium, strike_price, quantity)
  12. 例如: ('buy', 'call', 0.0456, 2.75, 1)
  13. direction: 方向,buy 或 sell
  14. option_type: 期权类型,call 或 put
  15. premium: 权利金
  16. strike_price: 行权价
  17. quantity: 数量
  18. 示例:
  19. # 单个期权
  20. analyze_options(('buy', 'call', 0.05, 3.0, 1))
  21. # 期权组合
  22. analyze_options(('buy', 'call', 0.08, 2.9, 1), ('sell', 'call', 0.03, 3.1, 1))
  23. """
  24. if not options:
  25. raise ValueError("请至少提供一个期权")
  26. # 解析期权数据
  27. option_list = []
  28. all_strikes = []
  29. for i, opt in enumerate(options):
  30. if len(opt) != 5:
  31. raise ValueError(f"期权{i+1}格式错误,应为(direction, option_type, premium, strike_price, quantity)")
  32. direction, option_type, premium, strike_price, quantity = opt
  33. option_list.append({
  34. 'direction': direction,
  35. 'option_type': option_type,
  36. 'premium': premium,
  37. 'strike_price': strike_price,
  38. 'quantity': quantity
  39. })
  40. all_strikes.append(strike_price)
  41. # 确定价格分析区间
  42. min_strike = min(all_strikes)
  43. max_strike = max(all_strikes)
  44. price_min = min_strike * 0.7
  45. price_max = max_strike * 1.3
  46. # 生成价格序列
  47. gap = (price_max - price_min) / 1000
  48. prices = np.arange(price_min, price_max + gap, gap)
  49. # 计算每个期权的收益
  50. results = {'price': prices}
  51. for i, opt in enumerate(option_list):
  52. profits = []
  53. for price in prices:
  54. profit = _calculate_profit(opt, price)
  55. profits.append(profit)
  56. results[f'opt{i+1}'] = profits
  57. # 计算组合收益
  58. if len(option_list) > 1:
  59. combined_profits = []
  60. for j in range(len(prices)):
  61. total = sum(results[f'opt{i+1}'][j] for i in range(len(option_list)))
  62. combined_profits.append(total)
  63. results['combined'] = combined_profits
  64. # 绘制图表
  65. _plot_results(results, option_list, prices)
  66. # 打印分析报告
  67. _print_report(results, option_list, prices)
  68. return pd.DataFrame(results)
  69. def _calculate_profit(option, price):
  70. """计算单个期权在特定价格下的收益"""
  71. direction = option['direction']
  72. option_type = option['option_type']
  73. premium = option['premium']
  74. strike_price = option['strike_price']
  75. quantity = option['quantity']
  76. if direction == 'buy' and option_type == 'call':
  77. # 买入认购
  78. if price > strike_price:
  79. return (price - strike_price - premium) * quantity
  80. else:
  81. return -premium * quantity
  82. elif direction == 'sell' and option_type == 'call':
  83. # 卖出认购
  84. if price > strike_price:
  85. return -(price - strike_price - premium) * quantity
  86. else:
  87. return premium * quantity
  88. elif direction == 'buy' and option_type == 'put':
  89. # 买入认沽
  90. if price < strike_price:
  91. return (strike_price - price - premium) * quantity
  92. else:
  93. return -premium * quantity
  94. elif direction == 'sell' and option_type == 'put':
  95. # 卖出认沽
  96. if price < strike_price:
  97. return -(strike_price - price - premium) * quantity
  98. else:
  99. return premium * quantity
  100. return 0
  101. def _plot_results(results, option_list, prices):
  102. """绘制分析图表"""
  103. plt.figure(figsize=(14, 10))
  104. plt.rcParams['axes.unicode_minus'] = False
  105. colors = ['blue', 'green', 'orange', 'purple', 'brown']
  106. # 绘制单个期权曲线
  107. for i in range(len(option_list)):
  108. opt = option_list[i]
  109. opt_name = f'opt{i+1}'
  110. strategy_name = f"{opt['direction'].upper()} {opt['option_type'].upper()}"
  111. color = colors[i % len(colors)]
  112. plt.plot(prices, results[opt_name], '--', color=color, linewidth=2, alpha=0.7,
  113. label=f'{opt_name}: {strategy_name}(strike price:{opt["strike_price"]}, premium:{opt["premium"]})')
  114. # 绘制组合曲线
  115. if 'combined' in results:
  116. plt.plot(prices, results['combined'], 'r-', linewidth=3, label='Portfolio returns')
  117. # 添加零线和行权价线
  118. plt.axhline(0, color='gray', linestyle='-', alpha=0.5)
  119. for opt in option_list:
  120. plt.axvline(opt['strike_price'], color='gray', linestyle='--', alpha=0.3)
  121. # 找到并标注关键点(盈亏平衡点、最大收益/损失边界点)
  122. if 'combined' in results:
  123. _mark_key_points(results['combined'], prices, 'Portfolio')
  124. elif len(option_list) == 1:
  125. _mark_key_points(results['opt1'], prices, 'Option')
  126. plt.xlabel('Assest Price', fontsize=12)
  127. plt.ylabel('Profit/Loss', fontsize=12)
  128. if len(option_list) == 1:
  129. opt = option_list[0]
  130. title = f'{opt["direction"].upper()} {opt["option_type"].upper()} Option Analysis'
  131. else:
  132. title = f'Portfolio Analysis ({len(option_list)} options)'
  133. plt.title(title, fontsize=14, weight='bold')
  134. plt.grid(True, alpha=0.3)
  135. plt.legend()
  136. plt.tight_layout()
  137. # 保存图片到data/Options目录
  138. timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
  139. filename = f'options_analysis_{timestamp}.png'
  140. save_path = os.path.join('data', 'Options', filename)
  141. plt.savefig(save_path, dpi=300, bbox_inches='tight')
  142. print(f"图片已保存到: {save_path}")
  143. plt.show()
  144. def _mark_key_points(profits, prices, label_prefix):
  145. """标注关键点:盈亏平衡点、最大收益/损失边界点"""
  146. # 1. 标注盈亏平衡点
  147. for i in range(len(profits) - 1):
  148. if profits[i] * profits[i + 1] <= 0: # 符号改变
  149. # 线性插值找到精确平衡点
  150. p1, profit1 = prices[i], profits[i]
  151. p2, profit2 = prices[i + 1], profits[i + 1]
  152. if profit2 != profit1:
  153. breakeven_price = p1 - profit1 * (p2 - p1) / (profit2 - profit1)
  154. plt.plot(breakeven_price, 0, 'ro', markersize=10)
  155. plt.annotate(f'Equilibrium Point: {breakeven_price:.4f}',
  156. xy=(breakeven_price, 0),
  157. xytext=(breakeven_price + (prices.max() - prices.min()) * 0.05, max(profits) * 0.1),
  158. arrowprops=dict(arrowstyle='->', color='red'),
  159. fontsize=11, color='red', weight='bold')
  160. # 2. 找到最大收益和最大损失点
  161. max_profit = max(profits)
  162. min_profit = min(profits)
  163. # 3. 检查是否存在最大收益/损失的边界点
  164. # 最大收益边界点:收益达到最大值后不再增长的点
  165. max_boundary_points = _find_boundary_points(profits, prices, max_profit, 'max')
  166. # 最大损失边界点:损失达到最大值后不再增长的点
  167. min_boundary_points = _find_boundary_points(profits, prices, min_profit, 'min')
  168. # 4. 标注最大收益边界点
  169. for bp in max_boundary_points:
  170. plt.plot(bp, max_profit, 'go', markersize=10)
  171. plt.annotate(f'Max Returns: ({bp:.4f}, {max_profit:.4f})',
  172. xy=(bp, max_profit),
  173. xytext=(bp + (prices.max() - prices.min()) * 0.05, max_profit + (max_profit - min_profit) * 0.1),
  174. arrowprops=dict(arrowstyle='->', color='green'),
  175. fontsize=10, color='green', weight='bold')
  176. # 5. 标注最大损失边界点
  177. for bp in min_boundary_points:
  178. plt.plot(bp, min_profit, 'mo', markersize=10)
  179. plt.annotate(f'Max Loss: ({bp:.4f}, {min_profit:.4f})',
  180. xy=(bp, min_profit),
  181. xytext=(bp + (prices.max() - prices.min()) * 0.05, min_profit - (max_profit - min_profit) * 0.1),
  182. arrowprops=dict(arrowstyle='->', color='magenta'),
  183. fontsize=10, color='magenta', weight='bold')
  184. def _find_boundary_points(profits, prices, extreme_value, _extreme_type):
  185. """找到最大收益或最大损失的边界点"""
  186. boundary_points = []
  187. tolerance = abs(extreme_value) * 0.001 # 允许的误差范围
  188. # 找到所有接近极值的点
  189. extreme_indices = []
  190. for i, profit in enumerate(profits):
  191. if abs(profit - extreme_value) <= tolerance:
  192. extreme_indices.append(i)
  193. if not extreme_indices:
  194. return boundary_points
  195. # 找到连续区间的边界点
  196. if len(extreme_indices) > 1:
  197. # 检查是否是连续的区间
  198. continuous_regions = []
  199. current_region = [extreme_indices[0]]
  200. for i in range(1, len(extreme_indices)):
  201. if extreme_indices[i] - extreme_indices[i-1] <= 2: # 允许小的间隔
  202. current_region.append(extreme_indices[i])
  203. else:
  204. continuous_regions.append(current_region)
  205. current_region = [extreme_indices[i]]
  206. continuous_regions.append(current_region)
  207. # 对于每个连续区间,标注边界点
  208. for region in continuous_regions:
  209. if len(region) > 10: # 只有当区间足够长时才标注边界点
  210. # 左边界点
  211. left_boundary = prices[region[0]]
  212. boundary_points.append(left_boundary)
  213. # 右边界点
  214. right_boundary = prices[region[-1]]
  215. boundary_points.append(right_boundary)
  216. return boundary_points
  217. def _print_report(results, option_list, prices):
  218. """打印分析报告"""
  219. print("=" * 60)
  220. print("期权分析报告")
  221. print("=" * 60)
  222. # 期权基本信息
  223. for i, opt in enumerate(option_list):
  224. print(f"期权{i+1}: {opt['direction'].upper()} {opt['option_type'].upper()}")
  225. print(f" 行权价: {opt['strike_price']}")
  226. print(f" 权利金: {opt['premium']}")
  227. print(f" 数量: {opt['quantity']}手")
  228. # 分析关键指标
  229. if 'combined' in results:
  230. profits = results['combined']
  231. print(f"\n【组合分析】")
  232. else:
  233. profits = results['opt1']
  234. print(f"\n【单期权分析】")
  235. max_profit = max(profits)
  236. min_profit = min(profits)
  237. max_idx = profits.index(max_profit)
  238. min_idx = profits.index(min_profit)
  239. print(f"最大收益: {max_profit:.4f} (标的价格: {prices[max_idx]:.4f})")
  240. print(f"最大损失: {min_profit:.4f} (标的价格: {prices[min_idx]:.4f})")
  241. print(f"一单最大收益: {max_profit * 10000:.2f}元")
  242. print(f"一单最大亏损: {abs(min_profit) * 10000:.2f}元")
  243. # 找盈亏平衡点
  244. breakeven_points = []
  245. for i in range(len(profits) - 1):
  246. if profits[i] * profits[i + 1] <= 0:
  247. p1, profit1 = prices[i], profits[i]
  248. p2, profit2 = prices[i + 1], profits[i + 1]
  249. if profit2 != profit1:
  250. bp = p1 - profit1 * (p2 - p1) / (profit2 - profit1)
  251. breakeven_points.append(bp)
  252. if breakeven_points:
  253. print(f"盈亏平衡点: {[f'{bp:.4f}' for bp in breakeven_points]}")
  254. else:
  255. print("无盈亏平衡点")
  256. print("=" * 60)
  257. analyze_options(('sell', 'call', 0.066, 3.0, 1), ('buy', 'call', 0.2017, 2.8, 1))