ソースを参照

更新.gitignore以忽略data/source目录中的Excel文件,并在analysis_chart.py中增强期权分析功能,支持标注指定价格点的损益,优化绘图和报告输出逻辑,提升分析的灵活性与可读性。

maxfeng 2 ヶ月 前
コミット
c748edd74f
2 ファイル変更84 行追加11 行削除
  1. 3 1
      .gitignore
  2. 81 10
      Lib/Options/analysis_chart.py

+ 3 - 1
.gitignore

@@ -6,4 +6,6 @@ resources/part_api.html
 
 *.csv
 
-data/*
+data/*
+data/source/Input.xlsx
+data/source/Output.xlsx

+ 81 - 10
Lib/Options/analysis_chart.py

@@ -5,7 +5,7 @@ from statistics import mean
 import os
 from datetime import datetime
 
-def analyze_options(*options):
+def analyze_options(*options, mark_prices=None):
     """
     统一的期权分析方法
 
@@ -17,6 +17,8 @@ def analyze_options(*options):
     premium: 权利金
     strike_price: 行权价
     quantity: 数量
+    mark_prices: 可选,需要标注的标的价格列表,如 [1.35, 1.52]
+                 可以作为关键字参数或最后一个位置参数传递
 
     示例:
     # 单个期权
@@ -24,17 +26,34 @@ def analyze_options(*options):
 
     # 期权组合
     analyze_options(('buy', 'call', 0.08, 2.9, 1), ('sell', 'call', 0.03, 3.1, 1))
+
+    # 标注指定价格点(关键字参数方式)
+    analyze_options(('buy', 'call', 0.05, 3.0, 1), mark_prices=[2.8, 3.2])
+    
+    # 标注指定价格点(位置参数方式)
+    analyze_options(('buy', 'call', 0.05, 3.0, 1), [2.8, 3.2])
     """
 
     if not options:
         raise ValueError("请至少提供一个期权")
 
+    # 检查最后一个参数是否是 mark_prices(列表类型且不是期权格式)
+    actual_options = list(options)
+    if (mark_prices is None and len(actual_options) > 0 and 
+        isinstance(actual_options[-1], list) and 
+        len(actual_options[-1]) != 5):
+        # 最后一个参数是列表且不是期权格式,将其视为 mark_prices
+        mark_prices = actual_options.pop()
+    
+    if not actual_options:
+        raise ValueError("请至少提供一个期权")
+
     # 解析期权数据
     option_list = []
     all_strikes = []
 
-    for i, opt in enumerate(options):
-        if len(opt) != 5:
+    for i, opt in enumerate(actual_options):
+        if not isinstance(opt, (tuple, list)) or len(opt) != 5:
             raise ValueError(f"期权{i+1}格式错误,应为(direction, option_type, premium, strike_price, quantity)")
 
         direction, option_type, premium, strike_price, quantity = opt
@@ -76,10 +95,10 @@ def analyze_options(*options):
         results['combined'] = combined_profits
 
     # 绘制图表
-    _plot_results(results, option_list, prices)
+    _plot_results(results, option_list, prices, mark_prices=mark_prices)
 
     # 打印分析报告
-    _print_report(results, option_list, prices)
+    _print_report(results, option_list, prices, mark_prices=mark_prices)
 
     return pd.DataFrame(results)
 
@@ -123,7 +142,7 @@ def _calculate_profit(option, price):
     return 0
 
 
-def _plot_results(results, option_list, prices):
+def _plot_results(results, option_list, prices, mark_prices=None):
     """绘制分析图表"""
     plt.figure(figsize=(14, 10))
     plt.rcParams['axes.unicode_minus'] = False
@@ -151,9 +170,17 @@ def _plot_results(results, option_list, prices):
 
     # 找到并标注关键点(盈亏平衡点、最大收益/损失边界点)
     if 'combined' in results:
-        _mark_key_points(results['combined'], prices, 'Portfolio')
+        profits_for_marking = results['combined']
+        _mark_key_points(profits_for_marking, prices, 'Portfolio')
     elif len(option_list) == 1:
-        _mark_key_points(results['opt1'], prices, 'Option')
+        profits_for_marking = results['opt1']
+        _mark_key_points(profits_for_marking, prices, 'Option')
+    else:
+        profits_for_marking = None
+
+    # 标注指定的价格点
+    if mark_prices and profits_for_marking is not None:
+        _mark_specified_prices(mark_prices, profits_for_marking, prices, option_list)
 
     plt.xlabel('Assest Price', fontsize=12)
     plt.ylabel('Profit/Loss', fontsize=12)
@@ -226,6 +253,40 @@ def _mark_key_points(profits, prices, label_prefix):
                     fontsize=10, color='magenta', weight='bold')
 
 
+def _mark_specified_prices(mark_prices, profits, prices, option_list):
+    """标注指定的标的价格点及其对应的损益"""
+    if not mark_prices:
+        return
+    
+    # 计算每个指定价格对应的损益
+    for mark_price in mark_prices:
+        # 找到最接近的价格索引
+        price_idx = np.argmin(np.abs(prices - mark_price))
+        actual_price = prices[price_idx]
+        profit_value = profits[price_idx]
+        
+        # 绘制标记点(使用黄色星形标记)
+        plt.plot(actual_price, profit_value, '*', color='gold', markersize=15, 
+                markeredgecolor='darkorange', markeredgewidth=2, zorder=5)
+        
+        # 添加标注
+        price_range = prices.max() - prices.min()
+        profit_range = max(profits) - min(profits) if len(profits) > 0 else 1
+        
+        # 根据损益值的正负决定标注位置
+        if profit_value >= 0:
+            y_offset = profit_range * 0.15
+        else:
+            y_offset = -profit_range * 0.15
+        
+        plt.annotate(f'Price: {actual_price:.4f}\nP/L: {profit_value:.4f}',
+                    xy=(actual_price, profit_value),
+                    xytext=(actual_price + price_range * 0.05, profit_value + y_offset),
+                    arrowprops=dict(arrowstyle='->', color='darkorange', lw=1.5),
+                    fontsize=10, color='darkorange', weight='bold',
+                    bbox=dict(boxstyle='round,pad=0.5', facecolor='wheat', alpha=0.8))
+
+
 def _find_boundary_points(profits, prices, extreme_value, _extreme_type):
     """找到最大收益或最大损失的边界点"""
     boundary_points = []
@@ -268,7 +329,7 @@ def _find_boundary_points(profits, prices, extreme_value, _extreme_type):
     return boundary_points
 
 
-def _print_report(results, option_list, prices):
+def _print_report(results, option_list, prices, mark_prices=None):
     """打印分析报告"""
     print("=" * 60)
     print("期权分析报告")
@@ -314,6 +375,16 @@ def _print_report(results, option_list, prices):
     else:
         print("无盈亏平衡点")
 
+    # 打印指定价格点的损益信息
+    if mark_prices:
+        print(f"\n【指定价格点分析】")
+        for mark_price in mark_prices:
+            # 找到最接近的价格索引
+            price_idx = np.argmin(np.abs(prices - mark_price))
+            actual_price = prices[price_idx]
+            profit_value = profits[price_idx]
+            print(f"标的价格: {actual_price:.4f} -> 损益: {profit_value:.4f} (一单: {profit_value * 10000:.2f}元)")
+
     print("=" * 60)
 
-analyze_options(('sell', 'call', 0.066, 3.0, 1), ('buy', 'call', 0.2017, 2.8, 1))
+analyze_options(('buy', 'put', 0.1636, 1.55, 2), ('sell', 'put', 0.084, 1.45, 1), [1.314, 1.35, 1.52])