Skill: whitertigerwilson/industry-research-cn (version 1.0.0)

--- name: industry-research description: 行业深度研究工具。当用户要求"研究XX行业"、"分析XX商品"、"行业研究报告"时触发。自动化搜集指定商品/行业的价格走势、成因分析、上下游产业链、重大新闻、A股上市公司列表及财务分析（营收/毛利润占比）、价格波动对各公司的影响（受益/受损）、投资机会与风险提示，并附重点公司换手率分析及技术面分析（K线形态+均线排列）。输出10个维度的结构化研究报告。 --- # Industry Research Skill 行业深度研究，10个维度结构化输出。 ## 工作流程 ### Step 0: 预检——多源股价交叉核对 ⚠️ 每次报告发布前必须执行，防止单一数据源错误（如代码错误）导致全报告失真。 **核对流程：** 1. 搜索个股实时价格时，至少用两个不同数据源交叉验证： - 数据源A：腾讯行情 `https://qt.gtimg.cn/q=sz{代码}` - 数据源B：东方财富K线 `https://push2his.eastmoney.com/api/qt/stock/kline/get?secid=0.{代码}` 2. 若两个数据源的价格差异超过5%，需排查代码是否正确 3. 若发现代码错误，立即修正 stock_filter.py 中的预定义股票池 ### Step 1: 确认研究对象 用户输入行业/商品名称，确认为单一商品（如"硫酸"、"铜"、"原油"），非行业整体。 ### Step 2: 价格走势 使用 `web_search` 和 `web_fetch` 搜集： - 近期价格走势（2025年至今） - 当前价格水平及同比/环比变化 - 价格历史区间（近52周、5年、历史高点/低点） 优先数据源：AP News、BBC、NPR（英文主流媒体）→ 中文媒体（界面新闻、彭博中文、东方财富）作为辅助。 搜索关键词模板：`{商品} price trend 2025 2026`、`{商品} 价格上涨 原因` ### Step 3: 成因分析 从搜索结果中提取价格上涨/下跌的核心驱动因素，归类为： - 供给侧（产能、进口、库存、检修） - 需求侧（下游行业景气度） - 政策/地缘政治因素 - 成本端传导 ### Step 4: 上下游产业链 梳理上游原料及占比、下游用途及消费占比。使用表格输出。 ### Step 5: 重大新闻 提取最近3-6条重大新闻，每条附来源。关注：产能变化、政策动向、地缘事件、进出口限制。 ### Step 6: A股上市公司 使用 `scripts/stock_filter.py` 筛选主营业务涉及该商品的上市公司： ```bash python scripts/stock_filter.py <商品关键词> ``` 输出：公司名称、股票代码、主营收入占比（%）、毛利润占比（%）、该商品产能。 若脚本不可用，用 `web_search` 手动搜索"A股 {商品} 上市公司"。 ### Step 7: 受益/受损分析 基于Step 6数据，分析： - **受益**：商品价格上涨 → 利润弹性大的公司（原料自给率高、产能大、营收占比高） - **受损**：商品价格上涨 → 成本压力大的公司（外购原料、营收占比低） 使用弹性估算公式： > 利润弹性 ≈ 价格变动幅度(%) × 产量 × 营收占比 ### Step 8: 投资机会与风险 综合以上分析，给出： - 明确受益标的（高弹性） - 稳健型标的（全产业链/资源自给） - 风险提示（价格高位、政策风险、地缘缓和） ### Step 9: 换手率分析 针对Step 8中明确受益的前3名公司，补充最近30个交易日的换手率数据。 **数据获取**： ```bash python scripts/stock_turnover.py <股票代码> 30 ``` **输出指标**： - 最近30个交易日累计换手率（%） - 超过5%换手率的天数 - 表格格式：公司 | 代码 | 30日累计换手率 | >5%换手天数 | 活跃度 **判断标准**： - 累计换手率>100%：高度活跃 ⚠️ - 累计换手率30-100%：正常活跃 - 累计换手率<30%：相对冷清 - >5%天数>10天：主力频繁活动 ### Step 10: K线形态 + 均线排列分析 针对Step 8中明确受益的前3名公司，补充技术面分析。 **数据获取**： ```bash python scripts/kline_pattern.py <股票代码> 15 ``` **K线形态检测**： - 锤子线/吊颈线、射击之星、吞没（阳包阴/阴包阳）、早晨/黄昏之星、乌云盖顶、大阳/大阴线（放量） - 每种形态标注：日期、类型、看多/看空信号、含义 **均线多头/空头排列判断**（基于5日、10日、20日均线）： - **多头排列**：5日均线>10日均线>20日均线，且价格站于5日均线上方 → 上升趋势 - **空头排列**：5日均线<10日均线<20日均线，且价格处于5日均线下方 → 下降趋势 - **震荡/混合**：均线纠缠、反复交叉 → 趋势不明 **输出格式**： ``` ### {公司名}({代码}) **均线排列**：{多头/空头/震荡} | MA5={价格} MA10={价格} MA20={价格} **K线形态**：共检测到N个信号 - {日期} [{WE/BE}] {形态名称} — {含义} **综合判断**：{基于技术面的短期趋势描述} ``` ## 输出格式 ``` # {商品} 行业深度研究报告 > 数据来源：xxx | 报告日期：2026年x月x日 ## 一、价格走势 ## 二、成因分析 ## 三、上下游产业链 ## 四、重大新闻 ## 五、A股相关上市公司 ## 六、受益/受损分析 ## 七、投资机会与风险 ## 八、重点公司换手率分析（近30交易日） ## 九、技术面分析（前3重点公司） ⚠️ 免责声明 ``` ## 免责声明模板 > ⚠️ 以上内容仅供参考，不构成投资建议。数据来源于公开资料，部分为公司历史披露数据，请以公司公告为准。投资有风险，入市需谨慎。 ## 已知商品研究案例 - 硫酸（2026年4月）：硫磺酸、冶炼酸工艺；铜陵有色/江西铜业/华尔泰受益；钛白粉企业受损 - 镍（2026年4月）：不锈钢+电池双赛道；华友钴业/格林美/容百科技受益；不锈钢厂受损 - 宠物食品（2026年4月）：A股纯度最高标的乖宝宠物(301498)；消费升级+品牌溢价逻辑；换手率正常无异动 - 铜（2026年4月）：紫金矿业/洛阳钼业/铜陵有色受益；铜加工企业受损 ## 升级方向（预留） - ✅ 第九模块已添加：换手率分析 - ✅ 第十模块已添加：K线形态+均线排列分析 - 添加 akshare 实时价格API支持（当前网络数据源不稳定） - 添加财报数据自动抓取（营收/毛利占比精确计算） - 添加期货/现货升贴水分析 - 支持多商品对比研究

path: references/research_manual.md
content: # 行业研究参考手册

## 数据源优先级

### 价格数据
1. 英文主流媒体：AP News、BBC、NPR → 搜 `commodity price 2025 2026`
2. 中文财经：界面新闻、彭博中文、东方财富网、生意社
3. 宏观数据：国家统计局、海关总署

### 新闻
- AP News → BBC → NPR（英文）
- 界面新闻 → 财新 → 澎湃（中文）

### 上市公司财务
- 东方财富网（财务数据页面）
- 新浪财经（个股页面）
- 巨潮资讯网（年报原文）

## 常用搜索关键词模板

### 价格走势
```
"{商品}" price trend 2025 2026
"{商品}" 价格上涨 原因
"{商品}" 市场价格 最新
```

### 产业链
```
"{商品}" 上游 原料
"{商品}" 下游 用途 消费占比
"{商品}" 产业链 上下游
```

### 上市公司
```
A股 "{商品}" 上市公司 龙头
"{商品}" 概念股 有哪些
```

### 投资分析
```
"{商品}" 投资机会 2026
"{商品}" 价格波动 影响 上市公司
```

## 已知商品产能数据（维护更新）

### 硫酸
- 铜陵有色：451万吨/年（冶炼酸，铜冶炼副产品）
- 江西铜业：500万吨+/年
- 云天化：200万吨/年（磷矿配套）
- 华尔泰：60万吨/年（硫磺酸，纯度高）
- 粤桂股份：52万吨/年 + 140万吨硫精矿

### 铜
- 紫金矿业：权益铜产量90万吨+
- 洛阳钼业：权益铜产量40万吨+
- 江西铜业：阴极铜170万吨+
- 铜陵有色：阴极铜160万吨+

## 免责声明模板

```
> ⚠️ 以上内容仅供参考，不构成投资建议。
> 数据来源：公开资料（彭博社、东方财富网、生意社、光大证券研报等）。
> 部分公司数据为历史披露或估算，请以公司公告为准。
> 投资有风险，入市需谨慎。
```

## 7维度输出模板

```markdown
# {商品} 行业深度研究报告
> 数据来源：xxx | 报告日期：2026年x月x日

## 一、价格走势
[数据表格 + 文字描述]

## 二、成因分析
### 供给侧
### 需求侧
### 政策/地缘因素

## 三、上下游产业链
[上游原料表格] | [下游用途表格]

## 四、重大新闻
1. [新闻标题]（来源：xxx）
2. ...

## 五、A股相关上市公司
| 公司 | 代码 | 主营占比 | 毛利占比 | 产能 |

## 六、受益/受损分析
### ✅ 受益
| 公司/行业 | 受益逻辑 |
### ❌ 受损
| 公司/行业 | 受损逻辑 |

## 七、投资机会与风险
### 🔴 明确受益
### 🟡 稳健型
### ⚠️ 风险提示

⚠️ 免责声明
```


path: scripts/kline_pattern.py
content: """
kline_pattern.py - K线形态检测 + 均线排列分析
用法：python kline_pattern.py <股票代码> [天数]
"""
import requests
import sys


def calc_ma(klines_str, period):
    """计算某周期的均线（简单移动平均）"""
    prices = []
    for k_str in klines_str:
        f = k_str.split(',')
        prices.append(float(f[4]))  # 收盘价
    if len(prices) < period:
        return None
    return sum(prices[-period:]) / period


def detect_patterns(klines_str):
    """检测K线形态"""
    results = []

    for i in range(1, len(klines_str)):
        p_str = klines_str[i-1]
        c_str = klines_str[i]

        p = p_str.split(',')
        c = c_str.split(',')

        d = c[0]
        o = float(c[1])
        close = float(c[2])
        h = float(c[3])
        l = float(c[4])
        v = float(c[5])

        po = float(p[1])
        pc_close = float(p[2])
        ph = float(p[3])
        pl = float(p[4])
        pv = float(p[5])

        is_bull = close > o
        body = abs(close - o)
        upper_shadow = h - max(o, close)
        lower_shadow = min(o, close) - l
        full_range = h - l
        body_ratio = body / full_range if full_range > 0 else 0

        patterns = []

        # 锤子线（底部）
        if is_bull and lower_shadow > body * 2.5 and upper_shadow < body * 0.2:
            patterns.append(('锤子线（底部）', '支撑有效，可能反弹', 'WE'))

        # 吊颈线（顶部）
        if not is_bull and lower_shadow > body * 2.5 and upper_shadow < body * 0.2:
            patterns.append(('吊颈线（顶部）', '上攻乏力，可能回落', 'BE'))

        # 射击之星
        if upper_shadow > body * 2.0 and lower_shadow < body * 0.3 and body_ratio < 0.3:
            if is_bull:
                patterns.append(('射击之星（底部反转）', '可能见底回升', 'WE'))
            else:
                patterns.append(('射击之星（顶部射击）', '可能见顶回落', 'BE'))

        # 吞没形态
        body_prev = abs(pc_close - po)
        if is_bull and pc_close < po and close > po and o < pc_close and body > body_prev * 1.1:
            patterns.append(('阳包阴（看涨吞没）', '底部反转信号', 'WE'))
        if not is_bull and pc_close > po and close < po and o > pc_close and body > body_prev * 1.1:
            patterns.append(('阴包阳（看跌吞没）', '顶部反转信号', 'BE'))

        # 乌云盖顶
        if pc_close > po and o > pc_close and close < (o + pc_close) / 2:
            penetration = (o - close) / full_range if full_range > 0 else 0
            if penetration > 0.4:
                patterns.append(('乌云盖顶', '顶部反转信号', 'BE'))

        # 大阳线放量
        if is_bull and body_ratio > 0.75 and v > pv * 1.5:
            patterns.append(('大阳线（放量）', '多头强势，可能延续', 'WE'))

        # 大阴线放量
        if not is_bull and body_ratio > 0.75 and v > pv * 1.5:
            patterns.append(('大阴线（放量）', '空头强势，可能延续', 'BE'))

        # 三日早晨/黄昏星
        if i >= 2:
            pp_str = klines_str[i-2]
            pp = pp_str.split(',')
            ppo, ppc = float(pp[1]), float(pp[2])
            middle_body = abs(float(c[1]) - float(c[2]))
            if ppc < ppo and middle_body < body * 0.5 and close > o and close > (ppo + ppc) / 2:
                patterns.append(('早晨之星（底部反转）', '三日内底部形态', 'WE'))
            if ppc > ppo and middle_body < body * 0.5 and close < o and close < (ppo + ppc) / 2:
                patterns.append(('黄昏之星（顶部反转）', '三日内顶部形态', 'BE'))

        if patterns:
            for name, meaning, sig in patterns:
                results.append((d, close, name, meaning, sig))

    return results


def judge_arrangement(klines_str):
    """
    判断均线排列：多头/空头/震荡
    """
    ma5 = calc_ma(klines_str, 5)
    ma10 = calc_ma(klines_str, 10)
    ma20 = calc_ma(klines_str, 20)

    if None in (ma5, ma10, ma20):
        try:
            latest = float(klines_str[-1].split(',')[4])
        except:
            latest = 0
        return '数据不足', '无法判断', 0, 0, 0, latest

    # 最新收盘价
    latest = float(klines_str[-1].split(',')[4])

    if ma5 > ma10 > ma20 and latest > ma5:
        arr = '多头排列'
        trend = '上升趋势'
    elif ma5 < ma10 < ma20 and latest < ma5:
        arr = '空头排列'
        trend = '下降趋势'
    else:
        arr = '震荡/混合'
        trend = '趋势不明'

    return arr, trend, round(ma5, 2), round(ma10, 2), round(ma20, 2), round(latest, 2)


def main():
    code = sys.argv[1] if len(sys.argv) > 1 else '000878'
    days = int(sys.argv[2]) if len(sys.argv) > 2 else 15

    market = '1' if code.startswith('6') else '0'
    # 多取一些数据用于计算MA20
    fetch_days = max(days, 25)
    url = (
        f"https://push2his.eastmoney.com/api/qt/stock/kline/get"
        f"?secid={market}.{code}"
        f"&fields1=f1,f2,f3,f4,f5,f6"
        f"&fields2=f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61"
        f"&lmt={fetch_days}&klt=101&fqt=1&end=20260415"
    )
    headers = {'User-Agent': 'Mozilla/5.0', 'Referer': 'https://quote.eastmoney.com/'}
    r = requests.get(url, timeout=10, headers=headers)
    data = r.json()
    klines = data.get('data', {}).get('klines', [])

    sys.stdout.reconfigure(encoding='utf-8')

    # 均线排列判断
    ma_result = judge_arrangement(klines)
    ma_result = judge_arrangement(klines)
    arr, trend, ma5, ma10, ma20, latest_price = ma_result

    print(f"\n{'='*55}")
    print(f"  股票: {code}  |  近{len(klines)}日数据")
    print(f"{'='*55}")

    if arr:
        print(f"  均线排列: {arr}")
        print(f"  MA5={ma5}  MA10={ma10}  MA20={ma20}  |  收盘={latest_price}")
        print(f"  趋势判断: {trend}")
    print()

    # K线形态
    patterns = detect_patterns(klines)
    print(f"--- K线形态（共检测到{len(patterns)}个信号）---")
    if not patterns:
        print("  未检测到明显形态")
    else:
        bullish = [p for p in patterns if p[4] == 'WE']
        bearish = [p for p in patterns if p[4] == 'BE']
        print(f"  看多信号: {len(bullish)}个  |  看空信号: {len(bearish)}个")
        print()
        for d, close, name, meaning, sig in patterns:
            sig_str = '[WE]' if sig == 'WE' else '[BE]'
            print(f"  {d} {sig_str} {name}")
            print(f"         收盘:{close} | {meaning}")

    print(f"{'='*55}\n")


if __name__ == '__main__':
    main()


path: scripts/pet_stocks.py
content: import requests, json

# 搜索宠物食品相关A股个股
# 数据源：东方财富

def search_pet_stocks():
    url = 'https://push2.eastmoney.com/api/qt/clist/get?pn=1&pz=50&po=1&np=1&fltt=2&invt=2&fid=f3&fs=m%3A90+t%3A2+f%3A!50&fields=f12%2Cf14%2Cf3%2Cf62'
    r = requests.get(url, timeout=10, headers={'User-Agent': 'Mozilla/5.0', 'Referer': 'https://quote.eastmoney.com/'})
    data = r.json()
    stocks = data['data']['diff']

    pet_names = ['乖宝', '中宠', '佩蒂', '源飞', '天元', '依依', '朝云', '益方', '华亨', '宠物']
    results = []
    for s in stocks:
        name = s.get('f14', '')
        code = s.get('f12', '')
        for kw in pet_names:
            if kw in name:
                chg = s.get('f3', 0)
                results.append((name, code, chg))
                break

    return results


def get_realtime(code):
    """获取个股实时价格"""
    market = '1' if code.startswith('6') else '0'
    url = f'https://qt.gtimg.cn/q=sz{code}'
    r = requests.get(url, timeout=5)
    import sys
    sys.stdout.reconfigure(encoding='utf-8')
    fields = r.text.split('~')
    try:
        name = fields[1]
        price = fields[3]
        yesterday = fields[4]
        change = fields[31]
        pct = fields[32]
        return f'{name} ({code}) 现价:{price} 昨收:{yesterday} 涨跌:{change} ({pct}%)'
    except:
        return f'{code} 数据解析失败'


if __name__ == '__main__':
    print('=== 宠物食品相关A股 ===\n')
    results = search_pet_stocks()
    for name, code, chg in results:
        print(f'{name} ({code}) 涨幅:{chg}%')

    print('\n=== 实时价格 ===')
    for name, code, _ in results:
        try:
            print(get_realtime(code))
        except Exception as e:
            print(f'{code} error: {e}')


path: scripts/stock_filter.py
content: """
stock_filter.py - 筛选主营特定商品的A股上市公司

用法：
  python stock_filter.py <商品关键词>
  python stock_filter.py 硫酸

输出：公司名称、代码、主营占比、毛利占比、产能

依赖：akshare (pip install akshare)
"""

import sys
import pandas as pd

def filter_stocks(keyword: str) -> pd.DataFrame:
    """根据商品关键词筛选相关上市公司"""
    try:
        import akshare as ak
    except ImportError:
        print("akshare not installed. Run: pip install akshare")
        return pd.DataFrame()

    # 预定义商品-股票映射（A股主营该商品的上市公司）
    # 格式：{商品: [(公司名, 代码, 主营占比%, 毛利占比%, 产能说明)]}
    KNOWN_MAP = {
        "硫酸": [
            ("铜陵有色", "000630", "硫酸营收~15%", "硫酸毛利~20%", "451万吨/年"),
            ("江西铜业", "600362", "硫酸营收~10%", "硫酸毛利~18%", "500万吨+/年"),
            ("云天化", "600096", "磷肥配套硫酸~30%", "磷肥毛利~25%", "200万吨/年"),
            ("湖北宜化", "000422", "化肥配套~35%", "化肥毛利~20%", "200万吨/年"),
            ("华尔泰", "001217", "硫酸营收>80%", "硫酸毛利~30%", "60万吨/年"),
            ("粤桂股份", "000833", "硫酸营收~40%", "硫酸毛利~22%", "52万吨/年"),
            ("司尔特", "002538", "磷化工配套~50%", "磷化工毛利~25%", "115万吨/年"),
            ("川金诺", "300505", "磷化工~60%", "磷化工毛利~20%", "50万吨/年"),
            ("江苏索普", "600746", "硫酸~55%", "硫酸毛利~18%", "80万吨/年"),
        ],
        "铜": [
            ("紫金矿业", "601899", "铜营收~60%", "铜毛利~65%", "权益铜产量90万吨+"),
            ("洛阳钼业", "603993", "铜营收~50%", "铜毛利~55%", "权益铜产量40万吨+"),
            ("江西铜业", "600362", "铜营收~75%", "铜毛利~70%", "阴极铜产量170万吨+"),
            ("铜陵有色", "000630", "铜营收~70%", "铜毛利~65%", "阴极铜产量160万吨+"),
            ("西部矿业", "601168", "铜营收~45%", "铜毛利~50%", "铜精矿含铜15万吨+"),
        ],
        "原油": [
            ("中国石油", "601857", "原油~70%", "油气毛利~60%", "原油产量1亿吨+"),
            ("中国石化", "600028", "炼油~55%", "炼油毛利~20%", "原油加工量2.5亿吨+"),
            ("中国海油", "600938", "原油~80%", "油气毛利~70%", "原油产量5亿桶+"),
        ],
        "黄金": [
            ("山东黄金", "600547", "黄金~85%", "黄金毛利~55%", "矿产金30吨+"),
            ("中金黄金", "600489", "黄金~80%", "黄金毛利~50%", "矿产金25吨+"),
            ("紫金矿业", "601899", "黄金~25%", "黄金毛利~30%", "矿产金55吨+"),
        ],
        "榨菜": [
            ("涪陵榨菜", "002507", "榨菜营收~85%", "榨菜毛利~55%", "榨菜行业龙头"),
        ],
        "锂": [
            ("赣锋锂业", "002460", "锂盐~90%", "锂毛利~50%", "碳酸锂8万吨+"),
            ("天齐锂业", "002466", "锂盐~85%", "锂毛利~55%", "碳酸锂6万吨+"),
            ("融捷股份", "002192", "锂矿~95%", "锂毛利~60%", "锂精矿8万吨+"),
        ],
        "磷": [
            ("云天化", "600096", "磷肥~50%", "磷肥毛利~35%", "磷矿储量亿吨+"),
            ("兴发集团", "600141", "磷化工~60%", "磷化工毛利~30%", "磷矿储量亿吨+"),
            ("湖北宜化", "000422", "磷肥~55%", "磷肥毛利~25%", "磷矿产能"),
            ("川金诺", "300505", "磷化工~70%", "磷化工毛利~22%", "饲料级磷酸盐"),
        ],
        "钛": [
            ("龙佰集团", "002601", "钛白粉~80%", "钛白粉毛利~30%", "钛白粉产能100万吨+"),
            ("中核钛白", "002145", "钛白粉~90%", "钛白粉毛利~25%", "钛白粉产能40万吨+"),
            ("安宁股份", "002978", "钛精矿~95%", "钛毛利~65%", "钛精矿53万吨+"),
        ],
    }

    keyword_lower = keyword.lower()
    for k, stocks in KNOWN_MAP.items():
        if k in keyword or keyword in k:
            df = pd.DataFrame(stocks, columns=["公司名称", "股票代码", "主营收入占比", "毛利润占比", "产能/储量"])
            print(f"\n{'='*60}")
            print(f"  商品：{k}  |  关键词：{keyword}")
            print(f"{'='*60}")
            print(df.to_string(index=False))
            return df

    # 未命中预定义MAP，尝试网络搜索
    print(f"\n未在预定义列表中找到 '{keyword}'，尝试搜索...")
    try:
        # 用akshare搜索A股板块概念股
        df = ak.stock_board_industry_name_em()
        print(df.head(20))
    except Exception as e:
        print(f"网络搜索失败: {e}")

    return pd.DataFrame()


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python stock_filter.py <商品关键词>")
        print("示例: python stock_filter.py 硫酸")
        sys.exit(1)

    keyword = sys.argv[1]
    filter_stocks(keyword)


path: scripts/stock_turnover.py
content: """
stock_turnover.py - 获取A股个股最近30个交易日换手率数据

用法：
  python stock_turnover.py <股票代码> [天数]
  python stock_turnover.py 603799 30

输出：
  - 每日收盘价 + 换手率
  - 30日累计换手率
  - 超过5%换手天数（主力活跃度指标）

数据源：东方财富历史K线接口
"""

import sys
import requests


def get_turnover(stock_code: str, days: int = 30) -> dict:
    """
    从东方财富获取个股历史换手率数据
    返回字段: 日期,开,收,高,低,成交量,成交额,振幅,涨跌幅,涨跌额,换手率
    """
    market = '1' if stock_code.startswith('6') else '0'
    url = (
        f"https://push2his.eastmoney.com/api/qt/stock/kline/get"
        f"?secid={market}.{stock_code}"
        f"&fields1=f1,f2,f3,f4,f5,f6"
        f"&fields2=f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61"
        f"&lmt={days}&klt=101&fqt=1&end=20260415"
    )
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Referer': 'https://quote.eastmoney.com/'
    }
    r = requests.get(url, timeout=10, headers=headers)
    data = r.json()
    klines = data.get('data', {}).get('klines', [])

    total = 0.0
    high_days = 0
    daily = []
    for k in klines:
        f = k.split(',')
        try:
            turnover = float(f[10]) if len(f) > 10 and f[10] else 0.0
        except Exception:
            turnover = 0.0
        total += turnover
        if turnover > 5:
            high_days += 1
        daily.append({'date': f[0], 'close': f[2], 'turnover': turnover})

    # 活跃度判断
    if total > 100:
        activity = 'HIGHLY ACTIVE'
    elif total > 50:
        activity = 'Normal Activity'
    else:
        activity = 'Relatively Quiet'

    return {
        'code': stock_code,
        'days': len(klines),
        'total': round(total, 1),
        'high_days': high_days,
        'activity': activity,
        'daily': daily
    }


def print_report(result: dict):
    print(f"\n{'='*55}")
    print(f"  股票代码: {result['code']}  |  统计天数: {result['days']}")
    print(f"{'='*55}")
    print(f"  {'日期':<12} {'收盘价':>8}  {'换手率':>8}")
    print(f"  {'-'*12} {'-'*8}  {'-'*8}")
    for d in result['daily']:
        marker = ' ★' if d['turnover'] > 5 else ''
        print(f"  {d['date']:<12} {d['close']:>8}  {d['turnover']:>7.2f}%{marker}")
    print(f"{'='*55}")
    print(f"  30日累计换手率: {result['total']}%  |  {result['activity']}")
    print(f"  超5%换手天数:   {result['high_days']}天")
    print(f"{'='*55}\n")


if __name__ == '__main__':
    code = sys.argv[1] if len(sys.argv) > 1 else '603799'
    days = int(sys.argv[2]) if len(sys.argv) > 2 else 30
    result = get_turnover(code, days)
    print_report(result)


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