Skill: leozhang1431/wework-financial-daily (version 1.0.0)

--- name: wework-financial-daily description: 每日定时生成金融分析教学课件并推送至企业微信。使用场景：(1) 每天自动生成当日最新金融数据（BTC、AAPL 等）的教学课件，(2) 推送课件到指定企业微信账号，(3) 保存 HTML 课件到本地桌面，(4) 包含价格走势图表和趋势分析。支持通过环境变量配置企业微信 token 和接收人。 --- # 企业微信每日金融分析教学课件 本技能用于每日定时生成金融分析教学课件，并自动推送到企业微信。 ## ⚙️ 配置环境变量 在运行脚本前，设置以下环境变量（**必填**）： ```bash # 企业微信 API Token（必填，从你的企业微信管理后台获取） export WEWORK_X_TOKEN="eyJhbGciOiJI..." # 企业微信接收人账号（必填，你的企业微信用户 ID 或手机号） export WEWORK_TO_USER="18018517752" ``` ### 方式一：系统环境变量（推荐） 1. `Win + R` → 输入 `sysdm.cpl` → 回车 2. "高级" → "环境变量" 3. "系统变量" → "新建"： - 变量名：`WEWORK_X_TOKEN`，变量值：`你的完整 token` - 变量名：`WEWORK_TO_USER`，变量值：`你的企业微信账号` ### 方式二：直接修改脚本 编辑 `scripts/generate_and_send.py`，修改顶部配置： ```python X_TOKEN = "你的完整 token" TO_USER = "你的企业微信账号" ``` ## 🚀 运行方式 ### 手动运行 ```powershell cd C:\Users\wwwir\.openclaw\workspace\skills\wework-financial-daily python scripts/generate_and_send.py ``` ### 双击运行（推荐） 直接双击 `一键运行.bat` 即可手动执行一次。 ### 定时任务（每天早上 9 点自动运行） **方式一：PowerShell 脚本（管理员权限）** 1. 右键点击 `SetupTask.ps1` 2. 选择"以管理员身份运行" 3. 脚本会自动创建每天 9:00 运行的定时任务 **方式二：手动创建** 详见 `README-定时任务配置.md` ## 📊 输出内容 每次运行会生成： 1. **HTML 教学课件** - 保存到桌面，包含： - 当日 BTC/USDT 和 AAPL 最新数据（动态生成） - 30 天价格走势图表（含 MA5 均线） - **五大教学模块**（详见下方） - 风险提示与免责声明 2. **企业微信推送** - Markdown 格式教学消息，包含： - 标题：【技术分析教学日报】日期 - 核心数据表格（价格、MA5/MA20、RSI14、趋势） - **五大教学模块精讲**（概念 + 今日应用） - 完整报告链接 ### 📚 五大教学模块 | 模块 | 主题 | 内容 | |------|------|------| | 模块一 | MA 均线基础 | 金叉/死叉概念、MA5/MA20 含义、今日应用 | | 模块二 | RSI 指标解读 | 超买/超卖区判断、4 个区间操作建议 | | 模块三 | 趋势识别方法 | 多头/空头趋势判断三要素 | | 模块四 | 支撑阻力位 | 画法技巧、突破确认规则、今日价位 | | 模块五 | 风险管理 | 仓位控制、止损策略、风险提示 | ## 📦 依赖包 脚本会自动检查并安装以下 Python 包： - `requests` - HTTP 请求 - `pandas` - 数据处理 - `matplotlib` - 图表绘制 - `numpy` - 数值计算 首次运行会自动安装，后续无需重复。 ## 🔧 自定义数据源 当前使用模拟数据（基于日期种子，保证当日数据一致）。如需接入真实 API： 1. 修改 `scripts/generate_and_send.py` 中的 `generate_html_report()` 函数 2. 替换模拟数据为真实 API 调用（如 CoinGecko、Yahoo Finance、Alpha Vantage 等） 3. 添加相应的 API Key 到环境变量 ## ❓ 故障排查 | 问题 | 解决方案 | |------|----------| | 推送失败 | 检查 `WEWORK_X_TOKEN` 是否正确，确认企业微信 API 地址有效 | | 图表中文乱码 | 确保系统已安装中文字体（SimHei/Microsoft YaHei） | | 依赖安装失败 | 手动运行 `pip install requests pandas matplotlib numpy -i https://pypi.tuna.tsinghua.edu.cn/simple` | | 任务不执行 | 检查任务计划程序中任务状态，确认"不管用户是否登录都要运行"已勾选 | ## 📁 相关文件 | 文件 | 说明 | |------|------| | `scripts/generate_and_send.py` | 主脚本 | | `一键运行.bat` | 手动运行快捷方式 | | `SetupTask.ps1` | 定时任务自动配置脚本 | | `README-定时任务配置.md` | 详细配置指南 | | `references/cron-setup.md` | OpenClaw Cron 配置 |

path: README-定时任务配置.md
content: # ⏰ 定时任务配置指南

## 🚀 快速开始（3 步搞定）

### 第 1 步：配置环境变量

**方式 A：使用配置向导（推荐）**

1. 双击 `配置环境变量.bat`
2. 按提示输入企业微信 Token 和接收人账号
3. 配置完成后可以选择立即测试

**方式 B：手动设置系统环境变量**

1. `Win + R` → 输入 `sysdm.cpl` → 回车
2. "高级" → "环境变量"
3. "系统变量" → "新建"：
   - 变量名：`WEWORK_X_TOKEN`，变量值：`你的完整 token`
   - 变量名：`WEWORK_TO_USER`，变量值：`你的企业微信账号`

---

### 第 2 步：手动测试运行

双击 `一键运行.bat`，确认：
- ✅ 依赖包安装成功
- ✅ HTML 报告生成到桌面
- ✅ 企业微信推送成功

---

### 第 3 步：配置定时任务

**方式 A：PowerShell 脚本（推荐）**

1. 右键点击 `SetupTask.ps1`
2. 选择"以管理员身份运行"
3. 脚本会自动创建每天 9:00 运行的定时任务

**方式 B：手动创建**

1. `Win + R` → 输入 `taskschd.msc` → 回车
2. 右侧"创建基本任务"
3. 名称：`每日金融课件推送`
4. 触发器：每天 `09:00`
5. 操作：启动程序
   - 程序：`python.exe`（自动检测）
   - 参数：`C:\Users\wwwir\.openclaw\workspace\skills\wework-financial-daily\scripts\generate_and_send.py`
6. 勾选"当单击完成时打开属性对话框"
7. 在属性中勾选：
   - ✅ "不管用户是否登录都要运行"
   - ✅ "使用最高权限运行"

---

## 🧪 测试定时任务

### 立即运行测试

```powershell
schtasks /Run /TN "每日金融课件推送"
```

### 查看任务状态

```powershell
schtasks /Query /TN "每日金融课件推送"
```

### 查看下次运行时间

```powershell
schtasks /Query /TN "每日金融课件推送" /FO LIST | findstr "Next Run"
```

---

## 🔧 管理任务

### 删除任务

```powershell
schtasks /Delete /TN "每日金融课件推送" /F
```

### 修改运行时间

1. 任务计划程序 → 找到任务
2. 右键 → "属性"
3. "触发器"选项卡 → 编辑
4. 修改时间为所需时间

### 查看运行历史

1. 任务计划程序 → 任务计划程序库
2. 找到"每日金融课件推送"
3. 点击"历史记录"选项卡

---

## ❓ 常见问题

### Q: 任务显示"就绪"但不执行
**A:** 检查是否勾选了"不管用户是否登录都要运行"，并确保以管理员身份创建任务。

### Q: 推送失败，提示 Token 无效
**A:** 
1. 检查 `WEWORK_X_TOKEN` 是否正确
2. 确认 Token 未过期
3. 手动运行 `一键运行.bat` 测试

### Q: 图表中文乱码
**A:** 确保系统已安装中文字体（SimHei/Microsoft YaHei），Windows 通常默认已安装。

### Q: 如何更改推送时间？
**A:** 在任务计划程序中修改任务触发器时间，或重新运行 `SetupTask.ps1` 修改脚本中的 `$taskTime`。

### Q: 环境变量不生效？
**A:** 
1. 系统环境变量需要重新打开命令行窗口才能生效
2. 或者直接在 `scripts/generate_and_send.py` 中硬编码配置

---

## 📱 推送效果

配置成功后，每天早上 9:00：

1. 自动生成当日金融数据报告（基于当日日期，数据每日不同）
2. 推送到企业微信
3. HTML 报告保存到桌面（`金融分析日报_YYYY-MM-DD.html`）

**用户收到消息示例：**
```
# 金融分析日报 - 2026-03-15

## 📊 核心数据
| 资产 | 最新收盘价 | MA5/MA20 | RSI14 |
|------|-----------|---------|-------|
| BTC/USDT | 42580.68 USDT | 42450/42100 | 58 |
| AAPL（苹果）| 185.72 USD | 185.2/182.5 | 62 |

## 📈 趋势分析
1. BTC/USDT：近 30 天震荡上行...
2. AAPL：近 30 天稳步上涨...
```

---

## 📂 文件说明

| 文件 | 说明 |
|------|------|
| `一键运行.bat` | 手动运行测试 |
| `配置环境变量.bat` | 环境变量配置向导 |
| `SetupTask.ps1` | 定时任务自动配置（管理员运行） |
| `scripts/generate_and_send.py` | 主脚本 |
| `README-定时任务配置.md` | 本文件 |


path: references/cron-setup.md
content: # 定时任务配置指南

## Windows 任务计划程序

### 图形界面配置

1. **打开任务计划程序**
   - Win + R → 输入 `taskschd.msc` → 回车

2. **创建基本任务**
   - 右侧操作栏 → "创建基本任务"
   - 名称：`每日金融课件推送`
   - 描述：`每天上午 9 点自动生成并推送金融分析教学课件`

3. **设置触发器**
   - 选择"每天"
   - 开始时间：`09:00:00`
   - 重复任务间隔：留空

4. **设置操作**
   - 选择"启动程序"
   - 程序/脚本：`python.exe`（填写完整路径，如 `C:\Python311\python.exe`）
   - 添加参数：`C:\Users\wwwir\.openclaw\workspace\skills\wework-financial-daily\scripts\generate_and_send.py`
   - 起始于：`C:\Users\wwwir\.openclaw\workspace\skills\wework-financial-daily`

5. **完成配置**
   - 勾选"当单击完成时打开属性对话框"
   - 在属性中勾选"不管用户是否登录都要运行"
   - 勾选"使用最高权限运行"

### 命令行配置（管理员权限）

```powershell
# 创建任务
schtasks /Create /TN "每日金融课件推送" /TR "python.exe C:\Users\wwwir\.openclaw\workspace\skills\wework-financial-daily\scripts\generate_and_send.py" /SC DAILY /ST 09:00 /RL HIGHEST /F

# 查看任务
schtasks /Query /TN "每日金融课件推送"

# 删除任务
schtasks /Delete /TN "每日金融课件推送" /F
```

## OpenClaw Cron 配置

如果使用 OpenClaw 的 cron 功能，在配置文件中添加：

```yaml
cron:
  - name: daily-financial-courseware
    schedule: "0 9 * * *"  # 每天 9:00
    command: "python C:\\Users\\wwwir\\.openclaw\\workspace\\skills\\wework-financial-daily\\scripts\\generate_and_send.py"
    env:
      WEWORK_X_TOKEN: "your-token-here"
      WEWORK_TO_USER: "your-user-id"
      FINANCIAL_OUTPUT_DIR: "C:/Users/your-name/Desktop"
```

## 环境变量配置

### 方式一：系统环境变量（推荐）

1. Win + R → 输入 `sysdm.cpl` → 回车
2. "高级" → "环境变量"
3. "系统变量" → "新建"
   - 变量名：`WEWORK_X_TOKEN`
   - 变量值：`your-x-token-here`
4. 重复添加其他变量

### 方式二：批处理包装脚本

创建 `run_daily.bat`：

```batch
@echo off
set WEWORK_X_TOKEN=your-x-token-here
set WEWORK_TO_USER=your-user-id
set FINANCIAL_OUTPUT_DIR=C:/Users/your-name/Desktop
python C:\Users\wwwir\.openclaw\workspace\skills\wework-financial-daily\scripts\generate_and_send.py
```

然后在任务计划程序中调用此批处理文件。

## 测试定时任务

### 手动触发测试

```powershell
# 立即运行任务
schtasks /Run /TN "每日金融课件推送"

# 查看运行历史
schtasks /Query /TN "每日金融课件推送" /V /FO LIST
```

### 查看日志

脚本输出会显示在任务计划程序的"历史记录"选项卡中。如需保存到文件，修改脚本添加日志：

```python
import logging
logging.basicConfig(
    filename='C:/Users/wwwir/.openclaw/workspace/skills/wework-financial-daily/daily_run.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
```

## 常见问题

### 任务不执行

1. 检查任务状态是否为"就绪"
2. 确认"不管用户是否登录都要运行"已勾选
3. 检查 Python 路径是否正确

### 环境变量未生效

1. 使用批处理包装脚本（方式二）
2. 或在任务属性中手动添加环境变量

### 权限不足

1. 勾选"使用最高权限运行"
2. 或以管理员身份创建任务


path: scripts/generate_and_send.py
content: #!/usr/bin/env python3
"""
企业微信每日金融分析教学课件生成与推送脚本
生成当日最新数据、图表，上传 MinIO，并推送到企业微信
"""

import requests
import json
import os
import time
import webbrowser
import base64
from io import BytesIO
import matplotlib
matplotlib.use('Agg')  # 非交互式后端
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from minio import Minio
from minio.error import S3Error

# ====================== 核心配置 ======================
# 企业微信配置
X_TOKEN = "eyJhbGciOiJI"  # 你的 Token（直接写在这里）
TO_USER = "18018517752"  # 你的企业微信账号
API_URL = f"https://kd.chatedu.jiaxutech.com/service/v1/wx-work/send?x-[REDACTED_SECRET]"
HEADERS = {"Content-Type": "application/json"}

# MinIO 配置（适配你的服务）
MINIO_ENDPOINT = "1.15.115.88:9000"
MINIO_ACCESS_KEY = "gWDVHdO8sAba6LTNSLCd"
MINIO_SECRET_KEY = [REDACTED_SECRET]
MINIO_SECURE = False
MINIO_BUCKET = "financereports"
# MinIO 公开访问地址（用于生成链接）
MINIO_PUBLIC_URL = "http://1.15.115.88:9090"


# ====================== MinIO 上传功能 ======================
def upload_to_minio(file_path, bucket_name=MINIO_BUCKET):
    """上传 HTML 报告到 MinIO，设置公开读取策略，返回简单公开链接"""
    print(f"\n📤 正在上传报告到 MinIO...")
    print(f"   服务地址：{MINIO_ENDPOINT}")
    print(f"   存储桶：{bucket_name}")
    
    try:
        # 初始化 MinIO 客户端
        client = Minio(
            MINIO_ENDPOINT,
            access_key=MINIO_ACCESS_KEY,
            secret_key=MINIO_SECRET_KEY,
            secure=MINIO_SECURE
        )
        
        # 检查/创建存储桶
        if not client.bucket_exists(bucket_name):
            client.make_bucket(bucket_name)
            print(f"   ✅ 存储桶 {bucket_name} 已创建")
        else:
            print(f"   ✅ 存储桶 {bucket_name} 已存在")
        
        # 设置存储桶为公开读取（一次性配置）
        from minio.commonconfig import CopySource
        policy = {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {"AWS": "*"},
                    "Action": ["s3:GetObject"],
                    "Resource": [f"arn:aws:s3:::{bucket_name}/*"]
                }
            ]
        }
        client.set_bucket_policy(bucket_name, json.dumps(policy))
        print(f"   ✅ 存储桶已设置为公开读取")
        
        # 生成英文文件名（避免中文编码问题）
        report_date = time.strftime("%Y-%m-%d")
        safe_file_name = f"finance-report-{report_date}.html"
        
        # 上传文件（指定 MIME 类型）
        content_type = "text/html; charset=utf-8"
        
        client.fput_object(
            bucket_name=bucket_name,
            object_name=safe_file_name,
            file_path=file_path,
            content_type=content_type
        )
        
        # 生成简单公开链接（无需签名）
        public_url = f"http://{MINIO_ENDPOINT}/{bucket_name}/{safe_file_name}"
        
        print(f"   ✅ 上传成功！")
        print(f"   📁 对象名称：{safe_file_name}")
        print(f"   🔗 公开链接：{public_url}")
        return public_url
        
    except S3Error as e:
        print(f"   ❌ MinIO 上传失败：{e.code} - {e.message}")
        return None
    except Exception as e:
        print(f"   ❌ 上传异常：{str(e)}")
        import traceback
        traceback.print_exc()
        return None


# ====================== 步骤 1：生成走势图表并转为 Base64 ======================
def generate_chart_base64(symbol):
    """生成模拟的 30 天价格走势图表，返回 Base64 编码字符串"""
    # 设置中文字体（避免图表中文乱码）
    plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei", "Arial Unicode MS"]
    plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题
    
    # 生成模拟的 30 天价格数据（每日刷新，基于当日时间戳）
    np.random.seed(int(time.time()))  # 时间戳作为随机种子，保证每日数据不同
    dates = pd.date_range(start=pd.Timestamp.now() - pd.Timedelta(days=30), end=pd.Timestamp.now())
    
    # 模拟不同资产的价格走势（贴合当日最新收盘价）
    if symbol == "BTC/USDT":
        btc_close = round(42000 + np.random.uniform(-500, 500), 2)
        prices = np.cumsum(np.random.randn(len(dates)) * 50) + btc_close
    elif symbol == "AAPL":
        aapl_close = round(185 + np.random.uniform(-3, 3), 2)
        prices = np.cumsum(np.random.randn(len(dates)) * 1) + aapl_close
    else:
        prices = np.cumsum(np.random.randn(len(dates))) + 100
    
    # 绘制专业的走势图表
    fig, ax = plt.subplots(figsize=(12, 5))
    ax.plot(dates, prices, color="#1E90FF", linewidth=2, label=symbol)
    
    # 添加均线（MA5）
    ma5 = pd.Series(prices).rolling(window=5).mean()
    ax.plot(dates, ma5, color="#FF6347", linewidth=1.5, label="MA5", linestyle="--")
    
    # 设置图表样式
    ax.set_title(f"{symbol} 30 天价格走势（{time.strftime('%Y-%m-%d')} 更新）", 
                 fontsize=14, fontweight="bold", pad=20)
    ax.set_xlabel("日期", fontsize=12)
    ax.set_ylabel("价格 (USD/USDT)", fontsize=12)
    ax.grid(True, alpha=0.3)
    ax.legend(loc="upper left")
    
    # 格式化 x 轴日期
    fig.autofmt_xdate()
    plt.tight_layout()
    
    # 保存图表到 BytesIO 并转为 Base64
    buffer = BytesIO()
    plt.savefig(buffer, format="png", dpi=150, bbox_inches="tight")
    buffer.seek(0)
    image_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
    plt.close(fig)
    
    return f"data:image/png;base64,{image_base64}"


# ====================== 步骤 2：生成带嵌入式图片的 HTML 报告 ======================
def generate_html_report():
    """生成包含 Base64 嵌入式图片的完整 HTML 报告，保存到桌面"""
    report_date = time.strftime("%Y-%m-%d")
    report_time = time.strftime("%Y-%m-%d %H:%M:%S")
    
    # 生成当日最新模拟金融数据（使用日期作为种子，保证当日数据一致）
    today_seed = int(time.strftime("%Y%m%d"))
    np.random.seed(today_seed)
    
    # BTC/USDT 当日最新数据
    btc_close = round(42000 + np.random.uniform(-500, 500), 2)
    btc_ma5 = round(btc_close + np.random.uniform(-200, 200), 2)
    btc_ma20 = round(btc_close + np.random.uniform(-300, 300), 2)
    btc_rsi = np.random.randint(45, 65)
    
    # AAPL 当日最新数据
    aapl_close = round(185 + np.random.uniform(-3, 3), 2)
    aapl_ma5 = round(aapl_close + np.random.uniform(-1, 1), 2)
    aapl_ma20 = round(aapl_close + np.random.uniform(-2, 2), 2)
    aapl_rsi = np.random.randint(40, 70)
    
    # 生成 Base64 嵌入式图片
    btc_chart_base64 = generate_chart_base64("BTC/USDT")
    aapl_chart_base64 = generate_chart_base64("AAPL")
    
    # 完整的 HTML 内容（带样式、图表、数据）
    html_content = f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>金融分析日报 - {report_date}</title>
    <style>
        * {{ margin: 0; padding: 0; box-sizing: border-box; }}
        body {{ font-family: "Microsoft YaHei", "Arial", sans-serif; line-height: 1.6; color: #333; background-color: #f9f9f9; padding: 30px; max-width: 1200px; margin: 0 auto; }}
        .report-header {{ text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #1E90FF; }}
        .report-title {{ font-size: 24px; font-weight: bold; color: #2c3e50; margin-bottom: 10px; }}
        .report-meta {{ color: #666; font-size: 14px; }}
        .data-section {{ background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 25px; }}
        .section-title {{ font-size: 18px; font-weight: bold; color: #1E90FF; margin-bottom: 15px; padding-left: 10px; border-left: 4px solid #1E90FF; }}
        .data-table {{ width: 100%; border-collapse: collapse; margin: 10px 0; }}
        .data-table th {{ background-color: #f2f9ff; color: #2c3e50; font-weight: bold; padding: 12px; text-align: left; border: 1px solid #e0e0e0; }}
        .data-table td {{ padding: 12px; border: 1px solid #e0e0e0; }}
        .data-table tr:hover {{ background-color: #f8f8f8; }}
        .chart-section {{ background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 25px; }}
        .chart-img {{ width: 100%; border-radius: 4px; margin: 10px 0; }}
        .chart-caption {{ text-align: center; color: #666; font-size: 14px; margin-bottom: 15px; }}
        .analysis-section {{ background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 25px; }}
        .analysis-item {{ margin-bottom: 10px; padding-left: 15px; position: relative; }}
        .analysis-item::before {{ content: "•"; color: #1E90FF; font-weight: bold; position: absolute; left: 0; }}
        .teaching-section {{ background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 25px; }}
        .teaching-module {{ background-color: #f8f9fa; padding: 15px; border-radius: 6px; margin-bottom: 15px; border-left: 4px solid #1E90FF; }}
        .teaching-module h3 {{ font-size: 16px; margin-bottom: 10px; }}
        .teaching-module p {{ line-height: 1.8; margin-bottom: 10px; }}
        .teaching-module ul {{ margin-left: 20px; line-height: 1.8; }}
        .disclaimer {{ color: #999; font-size: 12px; text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }}
    </style>
</head>
<body>
    <div class="report-header">
        <h1 class="report-title">金融分析日报 - {report_date}</h1>
        <p class="report-meta">数据周期：近 30 天 | 生成时间：{report_time}</p>
    </div>
    
    <div class="data-section">
        <h2 class="section-title">📊 核心交易数据</h2>
        <table class="data-table">
            <tr>
                <th>资产名称</th>
                <th>最新收盘价</th>
                <th>MA5/MA20</th>
                <th>RSI14</th>
            </tr>
            <tr>
                <td>BTC/USDT</td>
                <td>{btc_close} USDT</td>
                <td>{btc_ma5}/{btc_ma20}</td>
                <td>{btc_rsi}</td>
            </tr>
            <tr>
                <td>AAPL（苹果）</td>
                <td>{aapl_close} USD</td>
                <td>{aapl_ma5}/{aapl_ma20}</td>
                <td>{aapl_rsi}</td>
            </tr>
        </table>
    </div>
    
    <div class="chart-section">
        <h2 class="section-title">📈 走势图表</h2>
        <div>
            <img src="{btc_chart_base64}" alt="BTC/USDT 30 天走势" class="chart-img">
            <p class="chart-caption">BTC/USDT 30 天价格走势（含 MA5 均线）</p>
        </div>
        <div>
            <img src="{aapl_chart_base64}" alt="AAPL 30 天走势" class="chart-img">
            <p class="chart-caption">AAPL 30 天价格走势（含 MA5 均线）</p>
        </div>
    </div>
    
    <div class="analysis-section">
        <h2 class="section-title">📝 趋势分析</h2>
        <div class="analysis-item">
            BTC/USDT：近 30 天震荡上行，MA5 持续在 MA20 上方，多头趋势明显；RSI 处于{btc_rsi}（{"中性" if 45 <= btc_rsi <= 55 else "中性偏多" if btc_rsi > 55 else "中性偏空"}），{"无超买风险" if btc_rsi < 70 else "注意超买风险"}；
        </div>
        <div class="analysis-item">
            AAPL：近 30 天{"稳步上涨" if aapl_close > 185 else "震荡整理"}，MA5 与 MA20{"形成金叉" if aapl_ma5 > aapl_ma20 else "形成死叉"}，成交量稳定，短期{"偏强" if aapl_ma5 > aapl_ma20 else "偏弱"}；
        </div>
        <div class="analysis-item">
            整体建议：{"持仓为主" if btc_rsi < 70 and aapl_rsi < 70 else "谨慎观望"}，关注晚间{"非农数据" if report_date.endswith(("0", "5")) else "经济数据"}对市场的影响。
        </div>
    </div>
    
    <div class="teaching-section">
        <h2 class="section-title">📚 今日教学模块（5 讲）</h2>
        
        <div class="teaching-module">
            <h3 style="color: #1E90FF;">模块一：基础概念 - 什么是 MA 均线？</h3>
            <p><strong>移动平均线（Moving Average）</strong>是技术分析中最基础的指标之一。今日数据中，BTC 的 MA5 为 <strong>{btc_ma5}</strong>，MA20 为 <strong>{btc_ma20}</strong>。</p>
            <ul>
                <li><strong>MA5</strong>：过去 5 天的平均价格，反映短期趋势</li>
                <li><strong>MA20</strong>：过去 20 天的平均价格，反映中期趋势</li>
                <li><strong>金叉</strong>：MA5 上穿 MA20，通常视为买入信号</li>
                <li><strong>死叉</strong>：MA5 下穿 MA20，通常视为卖出信号</li>
            </ul>
            <p style="background-color: #e7f3ff; padding: 10px; border-radius: 4px; margin-top: 10px;">
                💡 <strong>今日应用：</strong>BTC 当前 MA5 {"高于" if btc_ma5 > btc_ma20 else "低于"} MA20，{"说明短期趋势强于中期，是金叉状态" if btc_ma5 > btc_ma20 else "说明短期趋势弱于中期，是死叉状态"}。
            </p>
        </div>
        
        <div class="teaching-module">
            <h3 style="color: #1E90FF;">模块二：指标解读 - RSI 相对强弱指标</h3>
            <p><strong>RSI（Relative Strength Index）</strong>用于衡量价格的涨跌速度和幅度，判断市场是否超买或超卖。</p>
            <table style="width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13px;">
                <tr style="background-color: #f2f9ff;">
                    <th style="border: 1px solid #e0e0e0; padding: 8px;">RSI 范围</th>
                    <th style="border: 1px solid #e0e0e0; padding: 8px;">市场状态</th>
                    <th style="border: 1px solid #e0e0e0; padding: 8px;">操作建议</th>
                </tr>
                <tr><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">70-100</td><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">超买区</td><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">考虑减仓</td></tr>
                <tr><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">50-70</td><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">偏强区</td><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">可继续持仓</td></tr>
                <tr><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">30-50</td><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">偏弱区</td><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">观望为主</td></tr>
                <tr><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">0-30</td><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">超卖区</td><td style="border: 1px solid #e0e0e0; padding: 8px; text-align: center;">考虑建仓</td></tr>
            </table>
            <p style="background-color: #e7f3ff; padding: 10px; border-radius: 4px; margin-top: 10px;">
                💡 <strong>今日应用：</strong>BTC 的 RSI 为 <strong>{btc_rsi}</strong>，处于{"偏强区，可继续持仓" if 50 <= btc_rsi < 70 else "超买区，注意风险" if btc_rsi >= 70 else "偏弱区，建议观望"}；AAPL 的 RSI 为 <strong>{aapl_rsi}</strong>。
            </p>
        </div>
        
        <div class="teaching-module">
            <h3 style="color: #1E90FF;">模块三：趋势判断 - 如何识别多头/空头趋势？</h3>
            <p>判断趋势是交易决策的核心。以下是识别趋势的三个关键方法：</p>
            <ul>
                <li><strong>高点与低点：</strong>多头趋势 = 高点创新高 + 低点抬高；空头趋势 = 低点创新低 + 高点降低</li>
                <li><strong>均线排列：</strong>多头排列 = 价格&gt;MA5&gt;MA20；空头排列 = 价格&lt;MA5&lt;MA20</li>
                <li><strong>趋势线：</strong>连接两个以上低点形成支撑线，连接两个以上高点形成压力线</li>
            </ul>
            <p style="background-color: #e7f3ff; padding: 10px; border-radius: 4px; margin-top: 10px;">
                💡 <strong>今日应用：</strong>BTC 近 30 天走势{"呈现多头趋势，高点和低点都在抬高" if btc_ma5 > btc_ma20 else "呈现震荡整理，方向不明确"}。建议结合成交量进一步确认。
            </p>
        </div>
        
        <div class="teaching-module">
            <h3 style="color: #1E90FF;">模块四：实战策略 - 支撑位与阻力位的画法</h3>
            <p>支撑位和阻力位是制定交易计划的重要参考：</p>
            <ul>
                <li><strong>支撑位画法：</strong>找到近期至少 2-3 个低点，连接成水平线或斜线</li>
                <li><strong>阻力位画法：</strong>找到近期至少 2-3 个高点，连接成水平线或斜线</li>
                <li><strong>突破确认：</strong>价格突破后需观察 3 天，站稳才能确认有效突破</li>
            </ul>
            <p style="background-color: #e7f3ff; padding: 10px; border-radius: 4px; margin-top: 10px;">
                💡 <strong>今日应用：</strong>根据 BTC 近 30 天数据，{"上方阻力位约在 " + str(round(btc_close * 1.05, 2)) + " 附近，下方支撑位约在 " + str(round(btc_close * 0.95, 2)) + " 附近" if btc_ma5 > btc_ma20 else "当前处于震荡区间，等待方向选择"}。
            </p>
        </div>
        
        <div class="teaching-module">
            <h3 style="color: #1E90FF;">模块五：风险管理 - 仓位控制与止损策略</h3>
            <p>风险管理是长期盈利的关键，以下是核心原则：</p>
            <ul>
                <li><strong>单笔风险：</strong>每笔交易亏损不超过总资金的 2%</li>
                <li><strong>仓位分配：</strong>单一资产不超过总仓位的 20%</li>
                <li><strong>止损设置：</strong>短线止损 5-8%，中线止损 10-15%</li>
                <li><strong>止盈策略：</strong>达到目标位后分批止盈，保留部分仓位博取更大收益</li>
            </ul>
            <p style="background-color: #fff3cd; padding: 10px; border-radius: 4px; margin-top: 10px; border-left: 3px solid #ffc107;">
                ⚠️ <strong>风险提示：</strong>当前 BTC RSI 为{btc_rsi}，{"处于偏高位置，建议控制仓位在 30% 以内" if btc_rsi > 60 else "处于合理区间，仓位可控制在 50% 左右" if btc_rsi > 40 else "处于偏低位置，可分批建仓"}。永远不要满仓操作！
            </p>
        </div>
    </div>
    
    <div class="disclaimer">
        ⚠️ 免责声明：本报告仅为技术分析参考，不构成任何投资建议。市场有风险，投资需谨慎。
    </div>
</body>
</html>"""
    
    # 保存到用户桌面
    desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
    filename = f"金融分析日报_{report_date}.html"
    local_path = os.path.join(desktop_path, filename)
    
    # 写入 HTML 文件（UTF-8 编码）
    os.makedirs(desktop_path, exist_ok=True)
    with open(local_path, "w", encoding="utf-8") as f:
        f.write(html_content)
    
    print(f"✅ 本地 HTML 报告已生成：{local_path}")
    return local_path, report_date, btc_close, btc_ma5, btc_ma20, btc_rsi, aapl_close, aapl_ma5, aapl_ma20, aapl_rsi


# ====================== 步骤 3：生成企业微信推送内容 ======================
def generate_markdown_content(local_path, report_date, btc_close, btc_ma5, btc_ma20, btc_rsi, aapl_close, aapl_ma5, aapl_ma20, aapl_rsi):
    """生成适配企业微信的 Markdown 推送内容（教学导向）"""
    report_time = time.strftime("%Y-%m-%d %H:%M:%S")
    
    # 根据数据生成动态分析
    btc_trend = "震荡上行" if btc_ma5 > btc_ma20 else "震荡下行"
    btc_sentiment = "中性偏多" if btc_rsi > 55 else "中性偏空" if btc_rsi < 45 else "中性"
    aapl_trend = "稳步上涨" if aapl_close > 185 else "震荡整理"
    aapl_signal = "金叉" if aapl_ma5 > aapl_ma20 else "死叉"
    aapl_strength = "偏强" if aapl_ma5 > aapl_ma20 else "偏弱"
    overall_advice = "持仓为主" if btc_rsi < 70 and aapl_rsi < 70 else "谨慎观望"
    
    # 计算支撑阻力位
    btc_resistance = round(btc_close * 1.05, 2)
    btc_support = round(btc_close * 0.95, 2)
    
    # RSI 状态描述
    btc_rsi_status = "偏强区，可继续持仓" if 50 <= btc_rsi < 70 else "超买区，注意风险" if btc_rsi >= 70 else "偏弱区，建议观望"
    position_advice = "建议控制仓位在 30% 以内" if btc_rsi > 60 else "仓位可控制在 50% 左右" if btc_rsi > 40 else "可分批建仓"
    
    markdown_content = f"""# 📚 金融技术分析教学日报 - {report_date}
> 数据周期：近 30 天 | 生成时间：{report_time}

## 📊 今日核心数据
| 资产 | 最新价 | MA5/MA20 | RSI14 | 趋势 |
|------|--------|---------|-------|------|
| BTC/USDT | {btc_close} | {btc_ma5}/{btc_ma20} | {btc_rsi} | {btc_trend} |
| AAPL | {aapl_close} | {aapl_ma5}/{aapl_ma20} | {aapl_rsi} | {aapl_trend} |

---

## 📖 五大教学模块

### 模块一：MA 均线基础
**概念：** MA5 是过去 5 天平均价，MA20 是过去 20 天平均价
**金叉：** MA5 上穿 MA20 → 买入信号 | **死叉：** MA5 下穿 MA20 → 卖出信号
**今日应用：** BTC 的 MA5({btc_ma5}) {"高于" if btc_ma5 > btc_ma20 else "低于"} MA20({btc_ma20})，{"金叉状态，短期趋势强" if btc_ma5 > btc_ma20 else "死叉状态，短期趋势弱"}

### 模块二：RSI 指标解读
**超买区(70-100)：** 考虑减仓 | **偏强区(50-70)：** 可持仓 | **偏弱区(30-50)：** 观望 | **超卖区(0-30)：** 考虑建仓
**今日应用：** BTC 的 RSI={btc_rsi}，处于{btc_rsi_status}

### 模块三：趋势识别方法
**多头趋势：** 高点创新高 + 低点抬高 + 价格>MA5>MA20
**空头趋势：** 低点创新低 + 高点降低 + 价格<MA5<MA20
**今日应用：** BTC 近 30 天{"呈现多头趋势" if btc_ma5 > btc_ma20 else "震荡整理中"}

### 模块四：支撑位与阻力位
**画法：** 连接 2-3 个低点=支撑线 | 连接 2-3 个高点=阻力线
**突破确认：** 突破后观察 3 天，站稳才有效
**今日应用：** BTC 阻力位≈{btc_resistance}，支撑位≈{btc_support}

### 模块五：风险管理核心
⚠️ **单笔风险≤2%** | **单一资产≤20%** | **短线止损 5-8%** | **中线止损 10-15%**
**今日建议：** {position_advice}。永远不要满仓！

---

## 🔗 完整学习报告
详细图表和深入讲解已保存到桌面：`{local_path}`

> ⚠️ 免责声明：本报告仅为教学参考，不构成投资建议。市场有风险，投资需谨慎。""".strip()
    
    return markdown_content


# ====================== 步骤 4：推送至企业微信 ======================
def send_to_wework(report_date, btc_close, btc_ma5, btc_ma20, btc_rsi, aapl_close, aapl_ma5, aapl_ma20, aapl_rsi, local_path, minio_url=None):
    """推送报告到指定企业微信账号（使用 textcard 格式）"""
    
    # 计算趋势和状态
    btc_trend = "震荡上行" if btc_ma5 > btc_ma20 else "震荡下行"
    btc_signal = "金叉" if btc_ma5 > btc_ma20 else "死叉"
    btc_rsi_status = "偏强区" if 50 <= btc_rsi < 70 else "超买区" if btc_rsi >= 70 else "偏弱区"
    aapl_signal = "金叉" if aapl_ma5 > aapl_ma20 else "死叉"
    
    # 生成 HTML 描述内容
    description = f"""<div class="gray">{report_date} {time.strftime('%H:%M')}</div>
<div class="normal">
<strong>BTC/USDT</strong>: {btc_close} | MA5/MA20: {btc_ma5}/{btc_ma20} | RSI: {btc_rsi} | {btc_trend}({btc_signal})<br>
<strong>AAPL</strong>: {aapl_close} | MA5/MA20: {aapl_ma5}/{aapl_ma20} | RSI: {aapl_rsi} | {aapl_signal}<br>
<strong>RSI 状态</strong>: BTC 处于{btc_rsi_status}，建议关注仓位控制
</div>"""
    
    # 确定链接优先级：MinIO > 本地
    if minio_url:
        learn_url = minio_url
        description += """<div style='color: #1E90FF; margin-top: 8px; font-size: 12px;'>☁️ 完整报告已上传云端，点击按钮在线查看</div>"""
    else:
        learn_url = "https://kd.chatedu.jiaxutech.com/container/testuser001"  # 占位链接
        description += """<div style='color: #999; margin-top: 8px; font-size: 12px;'>📁 完整报告已保存到桌面，双击打开即可查看</div>"""
    
    # 构建 textcard 格式 payload
    payload = {
        "departmentId": 1,
        "msgType": "textcard",
        "title": f"【金融技术分析日报】{report_date}",
        "description": description,
        "learnUrl": learn_url,
        "btnText": "查看完整报告"
    }
    
    try:
        print("📤 正在推送报告到企业微信...")
        print(f"📋 推送内容：标题={payload['title']}")
        response = requests.post(
            url=API_URL,
            headers=HEADERS,
            data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
            timeout=30
        )
        response.raise_for_status()
        result = response.json()
        
        # 打印推送结果
        print("\n=== 企业微信推送结果 ===")
        print(json.dumps(result, ensure_ascii=False, indent=2))
        
        if result.get("code") == 200:
            print("\n🎉 报告推送成功！")
            print("📱 查看位置：企业微信 → 消息卡片")
            print("💡 桌面已生成带高清图表的 HTML 报告，双击即可打开")
            return result
        else:
            print(f"\n⚠️ 推送返回非 200 状态码：{result.get('code')}")
            return result
            
    except requests.exceptions.Timeout:
        print("\n❌ 推送超时：请检查网络连接")
        return None
    except requests.exceptions.HTTPError as e:
        print(f"\n❌ HTTP 错误：{e}")
        print(f"  接口返回：{response.text if 'response' in locals() else '无'}")
        return None
    except Exception as e:
        print(f"\n❌ 推送异常：{str(e)}")
        return None


# ====================== 主流程（一键运行） ======================
def main():
    """主流程（一键运行）"""
    # 自动安装依赖包
    required_pkgs = ["requests", "pandas", "matplotlib", "numpy", "minio"]
    for pkg in required_pkgs:
        try:
            __import__(pkg)
        except ImportError:
            print(f"📌 正在安装 {pkg} 库...")
            os.system(f"pip install {pkg} -i https://pypi.tuna.tsinghua.edu.cn/simple")
    
    # 执行全流程
    try:
        # 1. 生成 HTML 报告
        local_path, report_date, btc_close, btc_ma5, btc_ma20, btc_rsi, aapl_close, aapl_ma5, aapl_ma20, aapl_rsi = generate_html_report()
        
        # 2. 上传到 MinIO
        minio_url = upload_to_minio(local_path)
        
        # 3. 推送企业微信（带 MinIO 链接）
        result = send_to_wework(report_date, btc_close, btc_ma5, btc_ma20, btc_rsi, aapl_close, aapl_ma5, aapl_ma20, aapl_rsi, local_path, minio_url)
        
        if result and result.get("code") == 200:
            print("\n✅ 全流程完成！")
            if minio_url:
                print(f"☁️ MinIO 链接：{minio_url}")
            return 0
        else:
            print("\n⚠️ 推送可能失败，请检查企业微信配置")
            return 1
            
    except Exception as e:
        print(f"\n❌ 程序执行失败：{str(e)}")
        print("💡 请检查依赖安装是否完成，或联系技术支持")
        return 1


if __name__ == "__main__":
    exit(main())


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)