513 lines
22 KiB
Python
513 lines
22 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
瓦拉英语 2026年6-12月收入预测
|
||
基于:历史趋势 + 教育行业季节性 + 达人复播频次 + 新达人拓展
|
||
"""
|
||
import matplotlib
|
||
matplotlib.use('Agg')
|
||
import matplotlib.pyplot as plt
|
||
import matplotlib.ticker as mticker
|
||
import numpy as np
|
||
import pandas as pd
|
||
from matplotlib.patches import FancyBboxPatch
|
||
import warnings
|
||
warnings.filterwarnings('ignore')
|
||
|
||
# ============================================================
|
||
# 1. 历史数据
|
||
# ============================================================
|
||
months_hist = ['9月', '10月', '11月', '12月', '1月', '2月', '3月', '4月', '5月']
|
||
months_hist_num = [9, 10, 11, 12, 13, 14, 15, 16, 17] # 2025-09=9, ... 2026-05=17
|
||
|
||
# 全渠道
|
||
gmv_hist = [617592, 812179, 493983, 395373, 397533, 415884, 2879026, 4970605, 1894740]
|
||
gsv_hist = [369716, 506534, 294240, 301432, 311279, 205390, 2136625, 2919674, 1390617]
|
||
refund_hist = [247876, 305645, 199743, 93941, 86254, 210494, 742401, 2050931, 504123]
|
||
orders_hist = [313, 421, 462, 233, 222, 216, 958, 1665, 623]
|
||
users_hist = [308, 412, 400, 229, 216, 212, 906, 1552, 588]
|
||
|
||
# 达人直播
|
||
daren_gmv = [598899, 765617, 452373, 177911, 217891, 353823, 2521415, 4497095, 1604719]
|
||
daren_orders = [301, 383, 227, 89, 109, 177, 767, 1429, 490]
|
||
daren_count = [7, 10, 15, 4, 6, 3, 10, 25, 17]
|
||
daren_new = [7, 7, 8, 0, 2, 2, 2, 20, 7]
|
||
daren_repeat = [0, 3, 7, 4, 4, 1, 8, 5, 10]
|
||
daren_refund = [39.5, 38.9, 43.6, 25.8, 29.4, 55.9, 27.9, 44.8, 30.6]
|
||
|
||
# 非达人渠道
|
||
non_daren_gmv = [g - d for g, d in zip(gmv_hist, daren_gmv)]
|
||
# = [18693, 46562, 41610, 217462, 179642, 62061, 357611, 473510, 290021]
|
||
|
||
# 新注册用户
|
||
new_users_hist = [1529, 2412, 2971, 3523, 1938, 1743, 4166, 5668, 2609]
|
||
|
||
# ============================================================
|
||
# 2. 教育行业季节性因子
|
||
# ============================================================
|
||
# 基于中国在线教育行业规律:
|
||
# - 6月: 期末/中高考,家长关注点在考试,教育消费下降 (0.75)
|
||
# - 7月: 暑假开始,需求爆发 (1.4)
|
||
# - 8月: 暑假高峰 (1.5)
|
||
# - 9月: 开学季,需求稳健 (1.0)
|
||
# - 10月: 国庆黄金周 + 中秋,促销期 (1.15)
|
||
# - 11月: 双11大促 (1.25)
|
||
# - 12月: 年末冲刺 + 寒假预售 (1.05)
|
||
|
||
seasonal_factor = {
|
||
6: 0.75, # 考试季
|
||
7: 1.40, # 暑假启动
|
||
8: 1.50, # 暑假高峰
|
||
9: 1.00, # 开学季
|
||
10: 1.15, # 国庆+双11预热
|
||
11: 1.25, # 双11
|
||
12: 1.05, # 年末寒假预售
|
||
}
|
||
|
||
# ============================================================
|
||
# 3. 预测模型
|
||
# ============================================================
|
||
def forecast_revenue():
|
||
"""三层预测模型"""
|
||
months_fc = ['6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||
months_fc_num = [18, 19, 20, 21, 22, 23, 24] # 2026-06=18
|
||
|
||
results = {'conservative': {}, 'base': {}, 'optimistic': {}}
|
||
|
||
for scenario, params in [
|
||
('conservative', {
|
||
'core_daren_base': 650000, # 核心达人月均贡献(晚柠+念妈)
|
||
'star_daren_base': 250000, # 学霸系月均贡献
|
||
'other_repeat_base': 80000, # 其他复发达人月均
|
||
'new_daren_per_month': 4, # 每月新达人
|
||
'new_daren_first_gmv': 60000, # 新达人首场平均GMV
|
||
'new_daren_retention': 0.25, # 新达人次月留存率
|
||
'non_daren_growth': 1.05, # 非达人渠道月环比
|
||
'non_daren_base': 290000, # 5月非达人基准
|
||
'refund_rate_target': 0.38, # 目标退费率
|
||
}),
|
||
('base', {
|
||
'core_daren_base': 750000,
|
||
'star_daren_base': 350000,
|
||
'other_repeat_base': 150000,
|
||
'new_daren_per_month': 6,
|
||
'new_daren_first_gmv': 80000,
|
||
'new_daren_retention': 0.35,
|
||
'non_daren_growth': 1.10,
|
||
'non_daren_base': 290000,
|
||
'refund_rate_target': 0.35,
|
||
}),
|
||
('optimistic', {
|
||
'core_daren_base': 900000,
|
||
'star_daren_base': 500000,
|
||
'other_repeat_base': 250000,
|
||
'new_daren_per_month': 8,
|
||
'new_daren_first_gmv': 100000,
|
||
'new_daren_retention': 0.45,
|
||
'non_daren_growth': 1.15,
|
||
'non_daren_base': 290000,
|
||
'refund_rate_target': 0.32,
|
||
}),
|
||
]:
|
||
# Initialize new daren pipeline
|
||
# Simulate: new darens from past months that may return
|
||
# May had 7 new darens, assume they contribute in future
|
||
new_daren_pool = [] # list of (month_index, gmv_first)
|
||
|
||
gmv_pred = []
|
||
gsv_pred = []
|
||
|
||
for i, m in enumerate(months_fc_num):
|
||
month_idx = i + 1 # 1-7
|
||
season = seasonal_factor[m % 12 or 12]
|
||
actual_month = m % 12 or 12
|
||
|
||
# === 达人直播预测 ===
|
||
# Core darens (晚柠, 念妈) - stable monthly
|
||
core = params['core_daren_base'] * season
|
||
|
||
# Star darens (学霸系) - more variable, season-dependent
|
||
star = params['star_daren_base'] * season
|
||
|
||
# Other repeat darens - growing slowly
|
||
other_repeat = params['other_repeat_base'] * (1 + 0.05 * month_idx) * season
|
||
|
||
# New darens this month
|
||
new_this_month = params['new_daren_per_month'] * season / seasonal_factor.get(actual_month, 1.0)
|
||
# But season mainly affects GMV per daren, not count
|
||
new_this_month = max(2, int(new_this_month * 0.8 + 0.5))
|
||
new_gmv = new_this_month * params['new_daren_first_gmv'] * season
|
||
|
||
# Returning new darens from pool
|
||
returning_gmv = 0
|
||
returning_count = 0
|
||
still_active = []
|
||
for nm, gmv_first in new_daren_pool:
|
||
if np.random.random() < params['new_daren_retention']:
|
||
# Returning daren, GMV typically 50-80% of first
|
||
retention_factor = 0.6 + 0.2 * np.random.random()
|
||
returning_gmv += gmv_first * retention_factor * season
|
||
returning_count += 1
|
||
still_active.append((nm, gmv_first * retention_factor))
|
||
|
||
# Add new darens to pool for future
|
||
new_daren_pool = still_active
|
||
for _ in range(new_this_month):
|
||
new_daren_pool.append((month_idx, params['new_daren_first_gmv']))
|
||
|
||
# Total daren GMV
|
||
daren_gmv_pred = core + star + other_repeat + new_gmv + returning_gmv
|
||
|
||
# === 非达人渠道预测 ===
|
||
non_daren_pred = params['non_daren_base'] * (params['non_daren_growth'] ** month_idx) * season
|
||
|
||
# === 总计 ===
|
||
total_gmv = daren_gmv_pred + non_daren_pred
|
||
|
||
# Apply refund rate to get GSV
|
||
# Jun-Sep: higher refund from summer rush;
|
||
# Oct-Dec: lower refund (双11/年末更理性)
|
||
if actual_month in [6, 7, 8]:
|
||
refund_adj = params['refund_rate_target'] + 0.05
|
||
elif actual_month in [11]:
|
||
refund_adj = params['refund_rate_target'] - 0.03 # 双11促销退费略高但可控
|
||
else:
|
||
refund_adj = params['refund_rate_target']
|
||
|
||
total_gsv = total_gmv * (1 - refund_adj)
|
||
|
||
gmv_pred.append(round(total_gmv))
|
||
gsv_pred.append(round(total_gsv))
|
||
|
||
results[scenario] = {
|
||
'gmv': gmv_pred,
|
||
'gsv': gsv_pred,
|
||
}
|
||
|
||
return results, months_fc
|
||
|
||
# ============================================================
|
||
# 4. 执行预测 & 生成图表
|
||
# ============================================================
|
||
np.random.seed(42)
|
||
results, months_fc = forecast_revenue()
|
||
|
||
# 打印预测表
|
||
print("=" * 90)
|
||
print("📊 瓦拉英语 2026年6-12月收入预测")
|
||
print("=" * 90)
|
||
print()
|
||
print(f"{'月份':<8} {'保守-GMV':>12} {'保守-GSV':>12} {'基准-GMV':>12} {'基准-GSV':>12} {'乐观-GMV':>12} {'乐观-GSV':>12}")
|
||
print("-" * 90)
|
||
total = {'c_gmv': 0, 'c_gsv': 0, 'b_gmv': 0, 'b_gsv': 0, 'o_gmv': 0, 'o_gsv': 0}
|
||
for i, m in enumerate(months_fc):
|
||
c_gmv = results['conservative']['gmv'][i]
|
||
c_gsv = results['conservative']['gsv'][i]
|
||
b_gmv = results['base']['gmv'][i]
|
||
b_gsv = results['base']['gsv'][i]
|
||
o_gmv = results['optimistic']['gmv'][i]
|
||
o_gsv = results['optimistic']['gsv'][i]
|
||
print(f"{m:<8} ¥{c_gmv:>10,} ¥{c_gsv:>10,} ¥{b_gmv:>10,} ¥{b_gsv:>10,} ¥{o_gmv:>10,} ¥{o_gsv:>10,}")
|
||
total['c_gmv'] += c_gmv
|
||
total['c_gsv'] += c_gsv
|
||
total['b_gmv'] += b_gmv
|
||
total['b_gsv'] += b_gsv
|
||
total['o_gmv'] += o_gmv
|
||
total['o_gsv'] += o_gsv
|
||
print("-" * 90)
|
||
print(f"{'合计':<8} ¥{total['c_gmv']:>10,} ¥{total['c_gsv']:>10,} ¥{total['b_gmv']:>10,} ¥{total['b_gsv']:>10,} ¥{total['o_gmv']:>10,} ¥{total['o_gsv']:>10,}")
|
||
print()
|
||
|
||
# 对比历史
|
||
hist_total_gmv = sum(gmv_hist)
|
||
hist_total_gsv = sum(gsv_hist)
|
||
print(f"历史累计 (2025.09-2026.05): GMV ¥{hist_total_gmv:,.0f} | GSV ¥{hist_total_gsv:,.0f}")
|
||
print(f"月均 (9个月): GMV ¥{hist_total_gmv/9:,.0f} | GSV ¥{hist_total_gsv/9:,.0f}")
|
||
print()
|
||
print("预测 vs 历史对比:")
|
||
for s, label in [('conservative', '保守'), ('base', '基准'), ('optimistic', '乐观')]:
|
||
t_gmv = total[f'{s[0]}_gmv']
|
||
t_gsv = total[f'{s[0]}_gsv']
|
||
print(f" {label}: 月均 GMV ¥{t_gmv/7:,.0f} | 7个月 GMV ¥{t_gmv:,.0f} ({t_gmv/hist_total_gmv*100:.0f}% of 历史9个月)")
|
||
|
||
# ============================================================
|
||
# 5. 生成可视化图表
|
||
# ============================================================
|
||
plt.rcParams['font.family'] = ['DejaVu Sans']
|
||
plt.rcParams['axes.unicode_minus'] = False
|
||
|
||
# Color palette
|
||
colors = {
|
||
'hist': '#5B9BD5',
|
||
'cons': '#ED7D31',
|
||
'base': '#4472C4',
|
||
'opt': '#70AD47',
|
||
'gsv': '#A5A5A5',
|
||
'bg': '#F8F9FA',
|
||
'grid': '#E0E0E0',
|
||
}
|
||
|
||
fig = plt.figure(figsize=(20, 24))
|
||
fig.suptitle('瓦拉英语 2026年6-12月 收入预测报告', fontsize=22, fontweight='bold', y=0.98)
|
||
|
||
# -------------------------------------------------------
|
||
# Chart 1: 历史趋势 + 三种预测场景
|
||
# -------------------------------------------------------
|
||
ax1 = fig.add_subplot(3, 2, 1)
|
||
all_months = months_hist + months_fc
|
||
all_x = list(range(1, len(all_months) + 1))
|
||
|
||
# Historical GMV bars
|
||
bars_hist = ax1.bar(range(1, 10), [g/10000 for g in gmv_hist], color=colors['hist'],
|
||
alpha=0.8, label='历史 GMV', width=0.6)
|
||
# Historical GSV line
|
||
ax1.plot(range(1, 10), [g/10000 for g in gsv_hist], 'o-', color=colors['gsv'],
|
||
linewidth=2, markersize=6, label='历史 GSV')
|
||
|
||
# Forecast GMV (base scenario)
|
||
fc_x = list(range(10, 17))
|
||
ax1.bar(fc_x, [g/10000 for g in results['base']['gmv']], color='#FFC000',
|
||
alpha=0.6, label='预测 GMV(基准)', width=0.6)
|
||
ax1.plot(fc_x, [g/10000 for g in results['base']['gsv']], 's--', color='#C55A11',
|
||
linewidth=2, markersize=6, label='预测 GSV(基准)')
|
||
|
||
# Separator line
|
||
ax1.axvline(x=9.5, color='red', linestyle='--', linewidth=1.5, alpha=0.5)
|
||
ax1.text(9.5, ax1.get_ylim()[1]*0.95, '← 历史 | 预测 →', ha='center', fontsize=9, color='red')
|
||
|
||
# Conservative & Optimistic ranges
|
||
ax1.fill_between(fc_x,
|
||
[g/10000 for g in results['conservative']['gmv']],
|
||
[g/10000 for g in results['optimistic']['gmv']],
|
||
alpha=0.15, color='green', label='预测区间(保守-乐观)')
|
||
|
||
ax1.set_xticks(all_x)
|
||
ax1.set_xticklabels(all_months, rotation=0)
|
||
ax1.set_ylabel('万元', fontsize=11)
|
||
ax1.set_title('月度 GMV/GSV 趋势与预测', fontsize=14, fontweight='bold')
|
||
ax1.legend(loc='upper left', fontsize=8)
|
||
ax1.grid(axis='y', alpha=0.3)
|
||
ax1.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f'¥{x:.0f}万'))
|
||
|
||
# Value labels on bars
|
||
for bar, val in zip(bars_hist, gmv_hist):
|
||
ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
|
||
f'¥{val/10000:.0f}万', ha='center', fontsize=7, fontweight='bold')
|
||
|
||
# -------------------------------------------------------
|
||
# Chart 2: 三场景月度对比
|
||
# -------------------------------------------------------
|
||
ax2 = fig.add_subplot(3, 2, 2)
|
||
x = np.arange(len(months_fc))
|
||
width = 0.25
|
||
|
||
bars1 = ax2.bar(x - width, [g/10000 for g in results['conservative']['gmv']],
|
||
width, color=colors['cons'], alpha=0.85, label='保守 GMV')
|
||
bars2 = ax2.bar(x, [g/10000 for g in results['base']['gmv']],
|
||
width, color=colors['base'], alpha=0.85, label='基准 GMV')
|
||
bars3 = ax2.bar(x + width, [g/10000 for g in results['optimistic']['gmv']],
|
||
width, color=colors['opt'], alpha=0.85, label='乐观 GMV')
|
||
|
||
# GSV lines
|
||
ax2.plot(x - width, [g/10000 for g in results['conservative']['gsv']],
|
||
'v-', color='#C55A11', markersize=6, linewidth=1.5, label='保守 GSV')
|
||
ax2.plot(x, [g/10000 for g in results['base']['gsv']],
|
||
's-', color='#2F5496', markersize=6, linewidth=1.5, label='基准 GSV')
|
||
ax2.plot(x + width, [g/10000 for g in results['optimistic']['gsv']],
|
||
'^-', color='#375623', markersize=6, linewidth=1.5, label='乐观 GSV')
|
||
|
||
ax2.set_xticks(x)
|
||
ax2.set_xticklabels(months_fc)
|
||
ax2.set_ylabel('万元', fontsize=11)
|
||
ax2.set_title('6-12月 三场景月度对比', fontsize=14, fontweight='bold')
|
||
ax2.legend(loc='upper left', fontsize=7, ncol=2)
|
||
ax2.grid(axis='y', alpha=0.3)
|
||
ax2.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f'¥{x:.0f}万'))
|
||
|
||
# -------------------------------------------------------
|
||
# Chart 3: 渠道贡献拆解(基准场景)
|
||
# -------------------------------------------------------
|
||
ax3 = fig.add_subplot(3, 2, 3)
|
||
|
||
# Calculate channel breakdown for base scenario
|
||
channels_data = {
|
||
'核心达人\n(晚柠+念妈)': [],
|
||
'学霸系达人': [],
|
||
'其他复发达人': [],
|
||
'新拓达人': [],
|
||
'非达人渠道': [],
|
||
}
|
||
|
||
season = [seasonal_factor.get(m % 12 or 12, 1.0) for m in [18, 19, 20, 21, 22, 23, 24]]
|
||
base_params = {
|
||
'core_daren_base': 750000,
|
||
'star_daren_base': 350000,
|
||
'other_repeat_base': 150000,
|
||
'new_daren_per_month': 6,
|
||
'new_daren_first_gmv': 80000,
|
||
'non_daren_growth': 1.10,
|
||
'non_daren_base': 290000,
|
||
}
|
||
|
||
for i, s in enumerate(season):
|
||
core = base_params['core_daren_base'] * s / 10000
|
||
star = base_params['star_daren_base'] * s / 10000
|
||
other = base_params['other_repeat_base'] * (1 + 0.05 * (i+1)) * s / 10000
|
||
new_d = base_params['new_daren_per_month'] * base_params['new_daren_first_gmv'] * s / 10000
|
||
non_d = base_params['non_daren_base'] * (base_params['non_daren_growth'] ** (i+1)) * s / 10000
|
||
|
||
channels_data['核心达人\n(晚柠+念妈)'].append(core)
|
||
channels_data['学霸系达人'].append(star)
|
||
channels_data['其他复发达人'].append(other)
|
||
channels_data['新拓达人'].append(new_d)
|
||
channels_data['非达人渠道'].append(non_d)
|
||
|
||
x_fc = np.arange(len(months_fc))
|
||
bottom = np.zeros(len(months_fc))
|
||
channel_colors = ['#1F4E79', '#2E75B6', '#9DC3E6', '#BDD7EE', '#F4B183']
|
||
|
||
for i, (name, vals) in enumerate(channels_data.items()):
|
||
ax3.bar(x_fc, vals, 0.6, bottom=bottom, label=name, color=channel_colors[i], alpha=0.85)
|
||
bottom += np.array(vals)
|
||
|
||
# Add total labels
|
||
for i, (m, total_val) in enumerate(zip(months_fc, bottom)):
|
||
ax3.text(i, total_val + 2, f'¥{total_val:.0f}万', ha='center', fontsize=8, fontweight='bold')
|
||
|
||
ax3.set_xticks(x_fc)
|
||
ax3.set_xticklabels(months_fc)
|
||
ax3.set_ylabel('万元', fontsize=11)
|
||
ax3.set_title('基准场景:GMV 渠道贡献拆解', fontsize=14, fontweight='bold')
|
||
ax3.legend(loc='upper left', fontsize=8)
|
||
ax3.grid(axis='y', alpha=0.3)
|
||
|
||
# -------------------------------------------------------
|
||
# Chart 4: 季节性因子 & 达人运营指标
|
||
# -------------------------------------------------------
|
||
ax4 = fig.add_subplot(3, 2, 4)
|
||
|
||
# Top: Seasonal factors
|
||
months_all_label = ['6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||
sf_values = [seasonal_factor[m % 12 or 12] for m in [18, 19, 20, 21, 22, 23, 24]]
|
||
# Normalize to percentage
|
||
sf_pct = [(v - 1) * 100 for v in sf_values]
|
||
bar_colors_sf = ['#D64545' if v < 0 else '#4472C4' if v <= 0.15 else '#70AD47' for v in sf_pct]
|
||
|
||
ax4_2 = ax4.twinx()
|
||
|
||
bars_sf = ax4.bar(months_all_label, sf_values, color=bar_colors_sf, alpha=0.3, width=0.6, label='季节性因子')
|
||
ax4.axhline(y=1.0, color='gray', linestyle='--', linewidth=1, alpha=0.5)
|
||
|
||
# Bottom: Daren metrics
|
||
daren_metrics_label = ['达人\n总开播数', '复发达人\n占比', '新达人\n首场GMV', '非达人\n渠道占比']
|
||
daren_metrics_value = [25, 55, 8, 18] # projections
|
||
ax4_2.bar(daren_metrics_label, daren_metrics_value, color=['#2E75B6', '#70AD47', '#FFC000', '#ED7D31'],
|
||
alpha=0.7, width=0.5)
|
||
|
||
ax4.set_ylabel('季节性因子', fontsize=11, color='#4472C4')
|
||
ax4_2.set_ylabel('运营指标', fontsize=11, color='#ED7D31')
|
||
ax4.set_title('季节性因子 & 关键运营指标', fontsize=14, fontweight='bold')
|
||
ax4.tick_params(axis='y', labelcolor='#4472C4')
|
||
ax4_2.tick_params(axis='y', labelcolor='#ED7D31')
|
||
ax4.set_ylim(0, 2.0)
|
||
|
||
# Add value labels
|
||
for bar, val in zip(bars_sf, sf_values):
|
||
ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.02,
|
||
f'{val:.2f}', ha='center', fontsize=8, fontweight='bold')
|
||
|
||
# -------------------------------------------------------
|
||
# Chart 5: 历史退款率 & 预测改善
|
||
# -------------------------------------------------------
|
||
ax5 = fig.add_subplot(3, 2, 5)
|
||
|
||
# Historical refund rate (whole channel)
|
||
hist_refund_rate = [r*100/g for r, g in zip(refund_hist, gmv_hist)]
|
||
hist_daren_refund = daren_refund
|
||
|
||
x_hist = range(1, 10)
|
||
ax5.plot(x_hist, hist_refund_rate, 'o-', color='#D64545', linewidth=2, markersize=8, label='全渠道退款率')
|
||
ax5.plot(x_hist, hist_daren_refund, 's--', color='#ED7D31', linewidth=2, markersize=6, label='达人直播退款率')
|
||
|
||
# Forecast refund rate targets
|
||
x_fc_all = range(10, 17)
|
||
for scenario_name, target in [('保守', 0.38), ('基准', 0.35), ('乐观', 0.32)]:
|
||
adj_rates = []
|
||
for m in [18, 19, 20, 21, 22, 23, 24]:
|
||
actual = m % 12 or 12
|
||
if actual in [6, 7, 8]:
|
||
adj_rates.append((target + 0.05) * 100)
|
||
elif actual == 11:
|
||
adj_rates.append((target - 0.03) * 100)
|
||
else:
|
||
adj_rates.append(target * 100)
|
||
ls = '-' if scenario_name == '基准' else '--'
|
||
ax5.plot(x_fc_all, adj_rates, ls, marker='s', linewidth=2, markersize=5,
|
||
label=f'预测退款率({scenario_name})', alpha=0.8)
|
||
|
||
ax5.axvline(x=9.5, color='gray', linestyle='--', linewidth=1, alpha=0.5)
|
||
ax5.set_xticks(list(x_hist) + list(x_fc_all))
|
||
ax5.set_xticklabels(months_hist + months_fc, rotation=0)
|
||
ax5.set_ylabel('%', fontsize=11)
|
||
ax5.set_title('退款率趋势 & 预测目标', fontsize=14, fontweight='bold')
|
||
ax5.legend(loc='upper right', fontsize=8)
|
||
ax5.grid(alpha=0.3)
|
||
ax5.set_ylim(0, 65)
|
||
|
||
# -------------------------------------------------------
|
||
# Chart 6: 累计收入预测 vs 历史 Key Metrics 仪表盘
|
||
# -------------------------------------------------------
|
||
ax6 = fig.add_subplot(3, 2, 6)
|
||
ax6.axis('off')
|
||
|
||
# Summary text
|
||
summary_text = f"""
|
||
═══════════════════════════════════════════════════════
|
||
📊 预测总结与关键假设
|
||
═══════════════════════════════════════════════════════
|
||
|
||
📈 预测区间(2026年6-12月,7个月)
|
||
┌──────────┬──────────────┬──────────────┐
|
||
│ 场景 │ 累计 GMV │ 累计 GSV │
|
||
├──────────┼──────────────┼──────────────┤
|
||
│ 保守 │ ¥{total['c_gmv']:>10,} │ ¥{total['c_gsv']:>10,} │
|
||
│ 基准 │ ¥{total['b_gmv']:>10,} │ ¥{total['b_gsv']:>10,} │
|
||
│ 乐观 │ ¥{total['o_gmv']:>10,} │ ¥{total['o_gsv']:>10,} │
|
||
└──────────┴──────────────┴──────────────┘
|
||
|
||
🔑 关键假设(基准场景)
|
||
• 核心达人:晚柠/念妈 月均 GMV ¥75万
|
||
• 学霸系:学霸老王/三人行 月均 GMV ¥35万
|
||
• 每月新拓达人 6 位,首场均价 ¥8万
|
||
• 新达人次月留存率 35%
|
||
• 非达人渠道(端内+销售)月环比 +10%
|
||
• 目标退费率 35%(当前波动 30-45%)
|
||
|
||
📅 季节性峰值
|
||
• 7-8月暑假高峰(因子 1.4-1.5)
|
||
• 11月双11大促(因子 1.25)
|
||
• 6月考试季低谷(因子 0.75)
|
||
|
||
⚠️ 风险因素
|
||
• 达人退款率波动(2月曾达 55.9%)
|
||
• 学霸系达人合作可持续性不确定
|
||
• 行业淡季(6月)收入可能低于预测
|
||
• 新达人质量参差不齐
|
||
|
||
🎯 建议
|
||
• 6月重点:储备暑假达人资源,减少开播
|
||
• 7-8月重点:全力投放,学霸系+新达人密集排期
|
||
• 9-10月:保持节奏,端内渠道发力
|
||
• 11月:双11大促,全渠道协同
|
||
• 加强达人退款管控,目标控制在35%以内
|
||
═══════════════════════════════════════════════════════
|
||
"""
|
||
|
||
ax6.text(0.02, 0.98, summary_text, transform=ax6.transAxes, fontsize=9,
|
||
verticalalignment='top', fontfamily='monospace',
|
||
bbox=dict(boxstyle='round', facecolor='#F0F4F8', alpha=0.8))
|
||
|
||
plt.tight_layout(rect=[0, 0, 1, 0.95])
|
||
plt.savefig('/root/.openclaw/workspace/output/revenue_forecast_2026.png', dpi=150, bbox_inches='tight',
|
||
facecolor='white', edgecolor='none')
|
||
print("\n✅ 图表已保存: /root/.openclaw/workspace/output/revenue_forecast_2026.png")
|