analysis_chart.py 11 KB

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