Skill: wy859/quant-stock-list (version 1.0.0)

--- name: quant-stock-list description: 从图片自动识别股票并加入股票池。触发条件：用户发送股票截图/图片并要求加入股票池。功能：(1) 使用RapidOCR识别图片中的股票代码、名称、涨幅、价格 (2) 自动去重 (3) FIFO原则管理30只股票上限 (4) 保存到manual_stock_list.json。 --- # quant-stock-list 从图片识别股票代码并自动加入股票池。 ## 工作流程 1. 读取图片：`/Users/wy/.openclaw/media/inbound/{uuid}.jpg` 2. RapidOCR识别 3. 解析股票代码（6位数字） 4. 去重 + FIFO（上限30只） 5. 保存到 `stone_quant/manual_stock_list.json` ## OCR脚本 ```python from rapidocr_onnxruntime import RapidOCR import cv2 ocr = RapidOCR() img = cv2.imread(img_path) result, _ = ocr(img) stocks = [] for bbox, text, conf in result: if text.isdigit() and len(text) == 6: code = text # 下一条通常是名称 stocks.append(code) ``` ## 自动加入规则 - 读取现有股票列表 - 新股票加入（去重） - 超过30只按FIFO移除最旧的 - 记录加入时间和来源 ## 保存格式 ```json { "stocks": [ {"code": "600010", "name": "包钢股份", "add_time": "22:30", "source": "图片识别"} ] } ```

path: scripts/add_stocks.py
content: #!/usr/bin/env python3
"""
从OCR识别结果自动加入股票池
使用方法: python add_stocks.py [img_path]
"""

import json
import sys
from datetime import datetime
from rapidocr_onnxruntime import RapidOCR
import cv2

STOCK_LIST_PATH = "/Users/wy/.openclaw/workspace-changniu/stone_quant/manual_stock_list.json"
MAX_STOCKS = 30

def ocr_stocks(img_path):
    """从图片识别股票"""
    ocr = RapidOCR()
    img = cv2.imread(img_path)
    result, _ = ocr(img)
    
    if not result:
        return []
    
    # 解析股票列表
    stocks = []
    for i, (bbox, text, conf) in enumerate(result):
        if text.isdigit() and len(text) == 6:
            code = text
            # 尝试获取名称（下一条）
            name = ""
            if i + 1 < len(result):
                next_text = result[i + 1][1]
                # 名称不应该是纯数字
                if not next_text.isdigit():
                    name = next_text
            stocks.append({"code": code, "name": name})
    
    return stocks

def load_stock_list():
    """加载现有股票列表"""
    try:
        with open(STOCK_LIST_PATH, 'r') as f:
            return json.load(f)
    except:
        return {"stocks": []}

def save_stock_list(data):
    """保存股票列表"""
    with open(STOCK_LIST_PATH, 'w') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

def add_stocks(new_stocks, source="图片识别"):
    """添加股票到列表，FIFO去重"""
    data = load_stock_list()
    existing_codes = {s['code'] for s in data['stocks']}
    add_time = datetime.now().strftime("%H:%M")
    
    added = []
    skipped = []
    
    for stock in new_stocks:
        code = stock['code']
        name = stock.get('name', '')
        
        if code not in existing_codes:
            data['stocks'].append({
                "code": code,
                "name": name,
                "add_time": add_time,
                "source": source
            })
            added.append((code, name))
        else:
            skipped.append((code, name))
    
    # FIFO: 超过30只移除最旧的
    removed = []
    if len(data['stocks']) > MAX_STOCKS:
        data['stocks'].sort(key=lambda x: (x.get('add_time', ''), x['code']))
        removed = data['stocks'][:-MAX_STOCKS]
        data['stocks'] = data['stocks'][-MAX_STOCKS:]
    
    save_stock_list(data)
    
    return {
        "added": added,
        "skipped": skipped,
        "removed": removed,
        "total": len(data['stocks'])
    }

def main():
    if len(sys.argv) < 2:
        print("用法: python add_stocks.py <图片路径>")
        sys.exit(1)
    
    img_path = sys.argv[1]
    
    # 识别股票
    stocks = ocr_stocks(img_path)
    if not stocks:
        print("未识别到股票")
        sys.exit(1)
    
    print(f"识别到 {len(stocks)} 只股票")
    
    # 加入列表
    result = add_stocks(stocks)
    
    print(f"\n新增: {len(result['added'])} 只")
    for code, name in result['added']:
        print(f"  ✅ {code} {name}")
    
    print(f"\n已存在: {len(result['skipped'])} 只")
    for code, name in result['skipped']:
        print(f"  ⏭️  {code} {name}")
    
    if result['removed']:
        print(f"\nFIFO移除: {len(result['removed'])} 只")
        for s in result['removed']:
            print(f"  ❌ {s['code']} {s['name']}")
    
    print(f"\n当前股票池: {result['total']} 只")

if __name__ == "__main__":
    main()


path: scripts/update_stock_list.py
content: #!/usr/bin/env python3
"""
更新交易标的清单
用法: python3 update_stock_list.py <uuid> [--dry-run]
"""
import json
import sys
from datetime import datetime
from pathlib import Path

def ocr_stocks(uuid: str) -> list:
    """用RapidOCR识别图片中的股票"""
    try:
        from rapidocr_onnxruntime import RapidOCR
        import cv2
        
        img_path = f"/Users/wy/.openclaw/media/inbound/{uuid}.jpg"
        img = cv2.imread(img_path)
        if img is None:
            print(f"❌ 图片不存在: {img_path}")
            return []
        
        ocr = RapidOCR()
        result, _ = ocr(img)
        
        if not result:
            print("⚠️ 未识别到文字")
            return []
        
        stocks = []
        for line in result:
            bbox, text, confidence = line
            if confidence < 0.9:
                continue
            # 6位数字股票代码
            if len(text) == 6 and text.isdigit():
                stocks.append({"code": text, "name": "", "add_time": datetime.now().strftime("%Y-%m-%d %H:%M")})
            # 排除无关文字
            skip = ['万热度', '天板', '热股', '板块', 'ETF', '热门', '可转债', '新股', '技术交易', 
                    '大家都在看', '快速飙升中', '同花顺', '分钟前', '公告', '公司']
            if any(s in text for s in skip):
                continue
            # 清理后的中文名称
            if len(text) >= 2 and len(text) <= 8 and not any(c in text for c in '+-%>0123456789'):
                # 可能的名字，暂存
                pass
        
        return stocks
    except Exception as e:
        print(f"❌ OCR失败: {e}")
        return []

def load_current_list() -> dict:
    """加载当前清单"""
    path = Path("/Users/wy/.openclaw/workspace-changniu/stone_quant/manual_stock_list.json")
    if path.exists():
        with open(path, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {"stocks": [], "update_date": "", "note": ""}

def save_list(data: dict):
    """保存清单"""
    data['update_date'] = datetime.now().strftime("%Y-%m-%d %H:%M")
    path = Path("/Users/wy/.openclaw/workspace-changniu/stone_quant/manual_stock_list.json")
    with open(path, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    print(f"✅ 已保存到 {path}")

def fifo_limit(stocks: list, limit: int = 30) -> list:
    """FIFO: 超过30只时保留最新的"""
    if len(stocks) > limit:
        print(f"⚠️ 超过30只，执行FIFO，保留前{limit}只")
        return stocks[:limit]
    return stocks

def main():
    if len(sys.argv) < 2:
        print("用法: python3 update_stock_list.py <uuid>")
        sys.exit(1)
    
    uuid = sys.argv[1]
    dry_run = "--dry-run" in sys.argv
    
    print(f"📷 开始识别图片: {uuid}")
    new_stocks = ocr_stocks(uuid)
    
    if not new_stocks:
        print("❌ 未识别到股票")
        sys.exit(1)
    
    print(f"✅ 识别到 {len(new_stocks)} 只股票")
    
    data = load_current_list()
    existing_codes = {s['code'] for s in data['stocks']}
    
    # 追加新股票（去重）
    added = 0
    for stock in new_stocks:
        if stock['code'] not in existing_codes:
            data['stocks'].append(stock)
            existing_codes.add(stock['code'])
            added += 1
    
    print(f"➕ 新增 {added} 只股票")
    
    # FIFO限制
    data['stocks'] = fifo_limit(data['stocks'])
    
    print(f"📋 当前共 {len(data['stocks'])} 只股票")
    
    if not dry_run:
        save_list(data)
    
    # 打印清单
    print("\n📋 交易标的清单:")
    for i, s in enumerate(data['stocks'], 1):
        name = s.get('name', '-') or '-'
        add_time = s.get('add_time', '-')
        print(f"  {i}. {s['code']} {name} ({add_time})")

if __name__ == "__main__":
    main()


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