本文档提供执行首次覆盖技能中任务4(图表生成)的逐步说明。
目的:为报告生成25-35张专业金融图表。
先决条件:⚠️ 开始前请验证
⚠️ 关键:除非任务1、2、3均已完成,否则不要开始此任务
本任务需要前三个任务的全部输出。在缺少这些输出的情况下开始将导致图表不完整。
如果任务1、2或3中的任何一项未完成:立即停止并告知用户需要首先完成哪些任务。具体要求为:
不要尝试创建占位图表或由于数据缺失而跳过图表。
输出:25-35张专业图表文件(PNG/JPG,300 DPI)
开始前——检查所有先决条件:
如果任何验证失败:
重要:任务5(报告组装)将在整个报告中嵌入所有创建的图表。报告需要密集的视觉内容(每200-300字一张图表),因此要创建全面的图表覆盖。
以下4张图表是关键可视化元素,必须存在:
创建以下全部25张图表。每张在任务5中都有特定用途:
投资摘要部分(1张图表):
财务表现部分(6张图表):
公司101部分(7张图表):
竞争与市场部分(2张图表):
情景分析部分(2张图表):
估值部分(7张图表):
总计:25张必需图表
添加以下图表以获得更大的视觉密度和叙事效果(达到26-35张总计):
总计范围:25-35张图表(25张必需 + 0-10张可选)
了解每张图表数据的来源:
重要:需要全部三个任务(1、2、3)完成加上外部数据访问才能创建所有25张必需图表。
安装所需库:
pip install matplotlib seaborn pandas numpy plotly创建 Python 脚本头部:
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from matplotlib.patches import Rectangle
import warnings
warnings.filterwarnings('ignore')
# Set global style
plt.style.use('seaborn-v0_8-darkgrid')
sns.set_palette("husl")
# Global settings
DPI = 300
FIGURE_WIDTH = 10
FIGURE_HEIGHT = 6
TITLE_FONT_SIZE = 14
AXIS_FONT_SIZE = 12
LABEL_FONT_SIZE = 10# Revenue by Product (from Task 2 model)
years = [2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029]
# Extract from Excel or define manually from model
product_a = [100, 120, 145, 175, 210, 252, 302, 363, 435, 522]
product_b = [80, 95, 115, 138, 165, 198, 238, 285, 342, 411]
product_c = [50, 62, 78, 98, 122, 153, 191, 239, 299, 374]
product_d = [30, 38, 48, 61, 77, 97, 122, 153, 191, 239]
# Revenue by Geography
north_america = [150, 180, 220, 265, 320, 384, 461, 553, 664, 797]
europe = [80, 95, 115, 140, 170, 204, 245, 294, 353, 423]
asia_pacific = [40, 50, 63, 80, 101, 127, 159, 199, 249, 311]
rest_of_world = [20, 25, 32, 40, 51, 64, 80, 100, 125, 156]# Margin evolution
gross_margin = [58.0, 59.2, 60.5, 61.8, 63.0, 64.5, 66.0, 67.0, 67.5, 68.0]
ebitda_margin = [12.0, 15.5, 18.8, 22.0, 25.0, 28.0, 30.5, 32.0, 33.0, 34.0]
fcf_margin = [8.0, 11.0, 14.5, 18.0, 21.0, 24.0, 26.5, 28.0, 29.0, 30.0]# DCF Sensitivity (from Task 3 valuation)
wacc_values = [7.0, 8.0, 9.0, 10.0, 11.0, 12.0]
terminal_growth = [1.5, 2.0, 2.5, 3.0, 3.5]
# Price per share matrix (rows = WACC, columns = terminal growth)
dcf_sensitivity = np.array([
[66, 71, 76, 82, 89],
[58, 62, 67, 72, 78],
[52, 55, 59, 63, 68],
[47, 50, 53, 56, 60],
[42, 45, 48, 51, 54],
[39, 41, 44, 46, 49]
])# Valuation Football Field (from Task 3)
valuation_methods = ['DCF Analysis', 'Trading Comps\n(NTM)', 'Precedent\nTransactions']
valuation_low = [48, 45, 52]
valuation_high = [62, 57, 66]
current_price = 50
target_price = 55def create_revenue_by_product_chart():
"""Create revenue by product stacked area chart"""
fig, ax = plt.subplots(figsize=(10, 6))
# Create stacked area chart
ax.stackplot(years, product_a, product_b, product_c, product_d,
labels=['Product A', 'Product B', 'Product C', 'Product D'],
colors=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'],
alpha=0.8)
# Formatting
ax.set_xlabel('Year', fontsize=12, fontweight='bold')
ax.set_ylabel('Revenue ($M)', fontsize=12, fontweight='bold')
ax.set_title('Figure 3 - Revenue by Product/Segment (2020-2029E)',
fontsize=14, fontweight='bold', pad=20)
# Legend
ax.legend(loc='upper left', frameon=False, fontsize=10)
# Grid
ax.grid(axis='y', alpha=0.3, linestyle='--')
ax.set_axisbelow(True)
# Remove top and right spines
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Add vertical line to separate historical from projected
ax.axvline(x=2024, color='gray', linestyle='--', linewidth=1, alpha=0.5)
ax.text(2024.2, ax.get_ylim()[1]*0.95, 'Projected →',
fontsize=9, color='gray', ha='left')
# Source line
fig.text(0.12, 0.02, 'Source: Company data, [Firm] estimates',
fontsize=9, style='italic', color='gray')
# Save
plt.tight_layout()
plt.savefig('chart_03_revenue_by_product_stacked_area.png',
dpi=300, bbox_inches='tight', facecolor='white')
plt.close()
print("Created: chart_03_revenue_by_product_stacked_area.png")
create_revenue_by_product_chart()def create_revenue_by_geography_chart():
"""Create revenue by geography stacked bar chart"""
years_labels = ['2020', '2021', '2022', '2023', '2024',
'2025E', '2026E', '2027E', '2028E', '2029E']
fig, ax = plt.subplots(figsize=(10, 6))
# Create stacked bar chart
width = 0.6
x = np.arange(len(years_labels))
p1 = ax.bar(x, north_america, width, label='North America', color='#1f77b4')
p2 = ax.bar(x, europe, width, bottom=north_america,
label='Europe', color='#ff7f0e')
p3 = ax.bar(x, asia_pacific, width,
bottom=np.array(north_america) + np.array(europe),
label='Asia-Pacific', color='#2ca02c')
p4 = ax.bar(x, rest_of_world, width,
bottom=np.array(north_america) + np.array(europe) + np.array(asia_pacific),
label='Rest of World', color='#d62728')
# Formatting
ax.set_xlabel('Year', fontsize=12, fontweight='bold')
ax.set_ylabel('Revenue ($M)', fontsize=12, fontweight='bold')
ax.set_title('Figure 4 - Revenue by Geography (2020-2029E)',
fontsize=14, fontweight='bold', pad=20)
ax.set_xticks(x)
ax.set_xticklabels(years_labels, rotation=45, ha='right')
# Legend
ax.legend(loc='upper left', frameon=False, fontsize=10)
# Grid
ax.grid(axis='y', alpha=0.3, linestyle='--')
ax.set_axisbelow(True)
# Remove top and right spines
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Source line
fig.text(0.12, 0.02, 'Source: Company data, [Firm] estimates',
fontsize=9, style='italic', color='gray')
# Save
plt.tight_layout()
plt.savefig('chart_04_revenue_by_geography_stacked_bar.png',
dpi=300, bbox_inches='tight', facecolor='white')
plt.close()
print("Created: chart_04_revenue_by_geography_stacked_bar.png")
create_revenue_by_geography_chart()def create_dcf_sensitivity_heatmap():
"""Create DCF sensitivity analysis heatmap"""
# Create DataFrame
df = pd.DataFrame(dcf_sensitivity,
index=[f'{w}%' for w in wacc_values],
columns=[f'{g}%' for g in terminal_growth])
fig, ax = plt.subplots(figsize=(8, 6))
# Create heatmap
sns.heatmap(df, annot=True, fmt='d', cmap='RdYlGn',
cbar_kws={'label': 'Price per Share ($)'},
linewidths=0.5, linecolor='white',
ax=ax, vmin=35, vmax=95)
# Formatting
ax.set_xlabel('Terminal Growth Rate', fontsize=12, fontweight='bold')
ax.set_ylabel('WACC', fontsize=12, fontweight='bold')
ax.set_title('Figure 28 - DCF Sensitivity Analysis ($/share)',
fontsize=14, fontweight='bold', pad=20)
# Rotate y-axis labels
plt.yticks(rotation=0)
# Source line
fig.text(0.12, 0.02, 'Source: [Firm] estimates',
fontsize=9, style='italic', color='gray')
# Save
plt.tight_layout()
plt.savefig('chart_28_dcf_sensitivity_heatmap.png',
dpi=300, bbox_inches='tight', facecolor='white')
plt.close()
print("Created: chart_28_dcf_sensitivity_heatmap.png")
create_dcf_sensitivity_heatmap()def create_valuation_football_field():
"""Create valuation football field chart"""
fig, ax = plt.subplots(figsize=(10, 5))
# Create horizontal bars
y_positions = np.arange(len(valuation_methods))
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
for i, (method, low, high, color) in enumerate(
zip(valuation_methods, valuation_low, valuation_high, colors)):
ax.barh(i, high - low, left=low, height=0.6,
color=color, alpha=0.7, label=method)
# Add value labels at ends
ax.text(low - 1, i, f'${low}', va='center', ha='right', fontsize=10)
ax.text(high + 1, i, f'${high}', va='center', ha='left', fontsize=10)
# Add current price line
ax.axvline(x=current_price, color='red', linestyle='--', linewidth=2,
label=f'Current: ${current_price}', alpha=0.7)
# Add target price line
ax.axvline(x=target_price, color='black', linestyle='-', linewidth=2,
label=f'Target: ${target_price}')
# Formatting
ax.set_yticks(y_positions)
ax.set_yticklabels(valuation_methods, fontsize=11)
ax.set_xlabel('Price Per Share ($)', fontsize=12, fontweight='bold')
ax.set_title('Figure 32 - Valuation Football Field',
fontsize=14, fontweight='bold', pad=20)
# Set x-axis limits
ax.set_xlim(40, 70)
# Remove spines
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
# Grid
ax.grid(axis='x', alpha=0.3, linestyle='--')
ax.set_axisbelow(True)
# Legend
ax.legend(loc='upper right', frameon=False, fontsize=9)
# Source line
fig.text(0.12, 0.02, 'Source: [Firm] estimates',
fontsize=9, style='italic', color='gray')
# Save
plt.tight_layout()
plt.savefig('chart_32_valuation_football_field.png',
dpi=300, bbox_inches='tight', facecolor='white')
plt.close()
print("Created: chart_32_valuation_football_field.png")
create_valuation_football_field()完成25张必需图表,从必需列表中创建所有剩余图表。每张图表在任务5中都有特定用途。
# chart_01: Stock Price Performance (12-24 months)
# - Line chart showing stock price over time vs. market index
# - Used on Page 1 of final report# chart_02: Revenue Growth Trajectory
# chart_10: Gross Margin Evolution
# chart_11: EBITDA Margin Progression
# chart_12: Free Cash Flow Trend
# chart_14: Scenario Comparison (Bull/Base/Bear)# chart_05: Company Overview/Timeline
# chart_06: Key Milestones Timeline
# chart_07: Organizational Structure
# chart_08: Product Portfolio Overview
# chart_09: Customer Segmentation
# chart_15: Market Size Evolution (TAM)
# chart_16: Competitive Positioning Matrix# chart_17: Market Share Breakdown
# chart_18: Competitive Benchmarking# chart_13: Operating Metrics Dashboard# chart_29: DCF Valuation Waterfall
# chart_30: Trading Comps Scatter Plot
# chart_31: Peer Multiples Comparison
# chart_33: Price Target Scenarios
# chart_34: Historical Valuation Multiples在所有图表中使用一致的格式:
可选:从此列表中添加1-10张附加图表以获得更大的视觉密度:
# chart_19: Customer Acquisition Trends
# chart_20: Unit Economics Evolution
# chart_21: Product Roadmap Timeline
# chart_22: Geographic Expansion Map
# chart_23: R&D Investment Trends
# chart_24: Sales & Marketing Efficiency
# chart_25: Working Capital Trends
# chart_26: Debt Maturity Schedule
# chart_27: Ownership Structure
# chart_35: Analyst Price Target Distribution这些可选图表提供额外的视觉叙事,并有助于在任务5中实现"每200-300字一张图表"的密度目标。
创建一个记录所有图文的文本文件:
def create_chart_index():
"""Create index of all charts"""
# 25 REQUIRED CHARTS
required_charts = [
"chart_01_stock_price_performance.png - Stock Price Performance (12-24M)",
"chart_02_revenue_growth_trajectory.png - Revenue Growth Trajectory",
"chart_03_revenue_by_product_stacked_area.png - Revenue by Product [MANDATORY]",
"chart_04_revenue_by_geography_stacked_bar.png - Revenue by Geography [MANDATORY]",
"chart_05_company_overview.png - Company Overview/Timeline",
"chart_06_key_milestones_timeline.png - Key Milestones Timeline",
"chart_07_organizational_structure.png - Organizational Structure",
"chart_08_product_portfolio.png - Product Portfolio Overview",
"chart_09_customer_segmentation.png - Customer Segmentation",
"chart_10_gross_margin_evolution.png - Gross Margin Evolution",
"chart_11_ebitda_margin_progression.png - EBITDA Margin Progression",
"chart_12_free_cash_flow_trend.png - Free Cash Flow Trend",
"chart_13_operating_metrics_dashboard.png - Operating Metrics Dashboard",
"chart_14_scenario_comparison.png - Scenario Comparison (Bull/Base/Bear)",
"chart_15_market_size_evolution.png - Market Size Evolution (TAM)",
"chart_16_competitive_positioning.png - Competitive Positioning Matrix",
"chart_17_market_share.png - Market Share Breakdown",
"chart_18_competitive_benchmarking.png - Competitive Benchmarking",
"chart_28_dcf_sensitivity_heatmap.png - DCF Sensitivity Heatmap [MANDATORY]",
"chart_29_dcf_waterfall.png - DCF Valuation Waterfall",
"chart_30_trading_comps_scatter.png - Trading Comps Scatter Plot",
"chart_31_peer_multiples_comparison.png - Peer Multiples Comparison",
"chart_32_valuation_football_field.png - Valuation Football Field [MANDATORY]",
"chart_33_price_target_scenarios.png - Price Target Scenarios",
"chart_34_historical_valuation_multiples.png - Historical Valuation Multiples",
]
# 10 OPTIONAL CHARTS (for 26-35 range)
optional_charts = [
"chart_19_customer_acquisition_trends.png - Customer Acquisition Trends [OPTIONAL]",
"chart_20_unit_economics_evolution.png - Unit Economics Evolution [OPTIONAL]",
"chart_21_product_roadmap_timeline.png - Product Roadmap Timeline [OPTIONAL]",
"chart_22_geographic_expansion_map.png - Geographic Expansion Map [OPTIONAL]",
"chart_23_rd_investment_trends.png - R&D Investment Trends [OPTIONAL]",
"chart_24_sales_marketing_efficiency.png - Sales & Marketing Efficiency [OPTIONAL]",
"chart_25_working_capital_trends.png - Working Capital Trends [OPTIONAL]",
"chart_26_debt_maturity_schedule.png - Debt Maturity Schedule [OPTIONAL]",
"chart_27_ownership_structure.png - Ownership Structure [OPTIONAL]",
"chart_35_analyst_price_targets.png - Analyst Price Target Distribution [OPTIONAL]",
]
with open('chart_index.txt', 'w') as f:
f.write("CHART INDEX FOR [COMPANY] EQUITY RESEARCH REPORT\n")
f.write("=" * 60 + "\n\n")
f.write("4 MANDATORY CHARTS (Must be present):\n")
f.write("- chart_03: Revenue by Product (Stacked Area) ⭐\n")
f.write("- chart_04: Revenue by Geography (Stacked Bar) ⭐\n")
f.write("- chart_28: DCF Sensitivity (Heatmap) ⭐\n")
f.write("- chart_32: Valuation Football Field ⭐\n\n")
f.write("25 REQUIRED CHARTS:\n")
for chart in required_charts:
f.write(f" {chart}\n")
f.write("\n10 OPTIONAL CHARTS (for 26-35 total):\n")
for chart in optional_charts:
f.write(f" {chart}\n")
f.write("\n" + "=" * 60 + "\n")
f.write("NOTE: Task 5 will embed ALL charts created (25-35) throughout\n")
f.write("the report for visual density (1 chart every 200-300 words).\n")
print("Created: chart_index.txt")
create_chart_index()运行验证检查:
import os
def verify_charts():
"""Verify all charts were created successfully"""
mandatory_charts = [
'chart_03_revenue_by_product_stacked_area.png',
'chart_04_revenue_by_geography_stacked_bar.png',
'chart_28_dcf_sensitivity_heatmap.png',
'chart_32_valuation_football_field.png'
]
print("\n" + "="*60)
print("CHART GENERATION VERIFICATION")
print("="*60)
# Check mandatory charts
print("\n1. MANDATORY CHARTS:")
all_mandatory_present = True
for chart in mandatory_charts:
if os.path.exists(chart):
size = os.path.getsize(chart) / 1024 # KB
print(f" OK {chart} ({size:.1f} KB)")
else:
print(f" MISSING: {chart}")
all_mandatory_present = False
# Count total charts
chart_files = [f for f in os.listdir('.') if f.startswith('chart_') and f.endswith('.png')]
print(f"\n2. TOTAL CHARTS: {len(chart_files)}")
print(f" Target: 25-35 charts")
print(f" Status: {'PASS' if 25 <= len(chart_files) <= 35 else 'WARNING'}")
# Check file sizes (should be > 50KB for 300 DPI)
print("\n3. FILE SIZE CHECK:")
small_files = []
for chart in chart_files[:5]: # Sample first 5
size = os.path.getsize(chart) / 1024
if size < 50:
small_files.append(chart)
print(f" {chart}: {size:.1f} KB")
if small_files:
print(f" WARNING: {len(small_files)} files may be low resolution")
else:
print(f" All sampled files have adequate size")
# Final verdict
print("\n" + "="*60)
if all_mandatory_present and 25 <= len(chart_files) <= 35:
print("VERIFICATION PASSED - Ready for Task 5")
else:
print("VERIFICATION FAILED - Review missing charts")
print("="*60 + "\n")
verify_charts()折线图:时间序列趋势(营收、利润率、股价)
堆叠面积图:按产品的营收 ⭐、市场规模构成
堆叠柱状图:按地域的营收 ⭐、季度分解
热力图:DCF 敏感性 ⭐、相关性矩阵
水平柱状图:估值足球场图 ⭐、同业排名
瀑布图:营收桥接、利润率分析、DCF 构建
散点/气泡图:增长 vs. 估值、竞争定位
2×2 矩阵图:竞争定位、产品组合
始终使用以下格式:
chart_[编号]_[描述].png
示例:
chart_01_stock_price_performance.png
chart_03_revenue_by_product_stacked_area.png
chart_28_dcf_sensitivity_heatmap.png
按图表在报告中的位置顺序编号,而非创建顺序。
问题:图表看起来像素化
解决方案:确保在 plt.savefig() 中使用
dpi=300
问题:标签或标题在边缘处被截断
解决方案:在 plt.savefig() 中使用
bbox_inches='tight'
问题:颜色看起来不专业 解决方案:使用成熟色板如 Tableau10,或定义自定义企业颜色
问题:轴标签重叠
解决方案:旋转标签(例如
rotation=45)或减小字号
问题:图表周围空白过多
解决方案:在保存前使用
plt.tight_layout()
一个成功的图表包应:
请记住:任务5将把所有创建的图表(25-35张)嵌入到整个报告中以实现视觉密度。
完成任务4后,交付物包括:
25张必需图表文件(最低要求):
10张可选图表文件(用于达到26-35张总计):
图表索引(1个文本文件):
所有图表文件必须:
最后一步:打包所有图表
创建包含所有图表文件和图表索引的 zip 文件:
[公司]_Charts_[日期].zip
├── chart_01_stock_price_performance.png
├── chart_02_revenue_growth_trajectory.png
├── chart_03_revenue_by_product_stacked_area.png ⭐
├── chart_04_revenue_by_geography_stacked_bar.png ⭐
├── chart_05_company_overview.png
├── ...(全部25-35个图表文件)
├── chart_28_dcf_sensitivity_heatmap.png ⭐
├── chart_32_valuation_football_field.png ⭐
├── chart_34_historical_valuation_multiples.png
└── chart_index.txt
示例:Tesla_Charts_2024-10-28.zip
为何重要:任务5将把所有创建的图表(25-35张)嵌入到整个报告中。报告需要视觉密度(每200-300字一张图表),因此所有图表都有其用途——无论是用于特定的分析部分,还是用于视觉叙事和页面密度。
完成任务4后,zip 文件将用于:
4张强制图表对于报告的估值和财务分析部分至关重要。