03_绘制期权损益分析图.py 11 KB

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