# -*- coding: utf-8 -*- """ 模拟数据生成器(当前为演示数据,后期可替换为真实数据源) 生成内容: 1. stocks 股票基础信息(60 只,覆盖 20+ 行业) 2. stock_daily 180 个交易日 OHLCV 行情(随机游走 + 趋势/题材分化) 3. market_index 上证/深证/创业板 三大指数序列 4. news 财经新闻(280+ 条,含情感标签/关联个股,作为 RAG 语料) 5. institutions 机构实体(公募/券商/保险资管/外资/私募) 6. inst_ratings 机构评级记录 7. fund_holdings 基金季度持仓 8. 向量索引:stock_news_v1(新闻正文)+ stock_profiles_v1(公司概况) 用法:python seed_data.py [--skip-vector] """ import argparse import datetime as dt import json import math import random import sys from config import (CHROMA_NEWS_COLLECTION, CHROMA_PROFILE_COLLECTION, LOG_DIR) from database import init_db, executemany, query_one, wipe_all from rag import vector_store as vs random.seed(42) # ===================================================================== 股票池 # (code, name, industry, board, base_price, float_shares(亿股), trend(0多/1平/2空), vol, biz) STOCKS = [ ("600519", "贵州茅台", "白酒", "主板", 1680, 12.56, 1, 0.020, "高端白酒龙头"), ("000858", "五粮液", "白酒", "主板", 128, 38.8, 1, 0.021, "浓香型白酒头部企业"), ("600809", "山西汾酒", "白酒", "主板", 198, 12.2, 0, 0.024, "清香型白酒代表"), ("000568", "泸州老窖", "白酒", "主板", 118, 14.7, 1, 0.022, "国窖1573高端白酒"), ("300750", "宁德时代", "动力电池", "创业板", 248, 43.9, 0, 0.026, "全球动力电池龙头"), ("002594", "比亚迪", "新能源汽车", "主板", 235, 11.6, 0, 0.025, "新能源整车+电池一体化"), ("601012", "隆基绿能", "光伏", "主板", 16.8, 75.8, 2, 0.028, "单晶硅片与组件龙头"), ("600438", "通威股份", "光伏", "主板", 20.5, 45.0, 2, 0.027, "硅料+电池片双龙头"), ("300274", "阳光电源", "光伏储能", "创业板", 78, 14.9, 0, 0.026, "光伏逆变器与储能龙头"), ("002460", "赣锋锂业", "锂电材料", "主板", 36, 20.2, 2, 0.030, "锂盐龙头"), ("688981", "中芯国际", "半导体", "科创板", 88, 26.5, 0, 0.025, "晶圆代工龙头"), ("688012", "中微公司", "半导体设备", "科创板", 178, 6.2, 0, 0.029, "刻蚀设备龙头"), ("002371", "北方华创", "半导体设备", "主板", 352, 5.3, 0, 0.028, "半导体设备平台型龙头"), ("603986", "兆易创新", "半导体", "主板", 118, 6.7, 1, 0.027, "存储芯片+MCU"), ("603501", "韦尔股份", "半导体", "主板", 96, 12.2, 1, 0.028, "CIS图像传感器龙头"), ("688041", "海光信息", "芯片设计", "科创板", 128, 23.2, 0, 0.028, "国产CPU/DCU"), ("600276", "恒瑞医药", "创新药", "主板", 46, 63.7, 1, 0.022, "创新药龙头"), ("603259", "药明康德", "CXO", "主板", 56, 29.6, 1, 0.023, "医药研发外包龙头"), ("300760", "迈瑞医疗", "医疗器械", "创业板", 268, 12.1, 1, 0.021, "医疗器械平台龙头"), ("600085", "同仁堂", "中药", "主板", 42, 13.7, 1, 0.019, "老字号中药"), ("000538", "云南白药", "中药", "主板", 52, 17.8, 1, 0.020, "中药+健康消费品"), ("300015", "爱尔眼科", "医疗服务", "创业板", 13.5, 93.2, 1, 0.026, "连锁眼科医疗"), ("600036", "招商银行", "银行", "主板", 36, 206.3, 1, 0.018, "零售银行龙头"), ("601398", "工商银行", "银行", "主板", 6.1, 2696.0, 1, 0.015, "国有大行"), ("601166", "兴业银行", "银行", "主板", 19.8, 207.7, 1, 0.017, "股份行"), ("600000", "浦发银行", "银行", "主板", 8.9, 293.5, 2, 0.016, "股份行"), ("600030", "中信证券", "券商", "主板", 28, 148.2, 1, 0.022, "券商龙头"), ("601688", "华泰证券", "券商", "主板", 16.5, 90.8, 1, 0.021, "互联网券商"), ("300059", "东方财富", "互联网金融", "创业板", 13.8, 158.0, 0, 0.027, "互联网财富管理"), ("600999", "招商证券", "券商", "主板", 15.2, 86.9, 1, 0.020, "综合券商"), ("601633", "长城汽车", "汽车", "主板", 24, 85.4, 1, 0.024, "SUV与新能源整车"), ("000625", "长安汽车", "汽车", "主板", 14.5, 99.2, 1, 0.023, "自主品牌整车"), ("601238", "广汽集团", "汽车", "主板", 8.6, 104.7, 2, 0.022, "整车集团"), ("600104", "上汽集团", "汽车", "主板", 14.2, 115.7, 2, 0.020, "整车集团"), ("002475", "立讯精密", "消费电子", "主板", 32, 72.1, 0, 0.026, "消费电子+汽车连接器"), ("002241", "歌尔股份", "消费电子", "主板", 24, 34.2, 1, 0.026, "声学与VR整机"), ("000725", "京东方A", "面板", "主板", 4.2, 376.0, 1, 0.024, "显示面板龙头"), ("000100", "TCL科技", "面板", "主板", 4.8, 187.7, 1, 0.024, "面板+光伏材料"), ("000333", "美的集团", "家电", "主板", 68, 68.3, 1, 0.019, "白电龙头"), ("000651", "格力电器", "家电", "主板", 40, 56.3, 1, 0.020, "空调龙头"), ("600690", "海尔智家", "家电", "主板", 28, 94.5, 1, 0.020, "白电+智慧家庭"), ("600309", "万华化学", "化工", "主板", 78, 31.4, 1, 0.022, "MDI与新材料龙头"), ("601899", "紫金矿业", "有色", "主板", 18, 263.0, 0, 0.023, "黄金铜矿龙头"), ("002648", "卫星化学", "化工", "主板", 16.5, 33.7, 1, 0.024, "轻烃一体化化工"), ("600031", "三一重工", "工程机械", "主板", 16.8, 84.9, 1, 0.023, "挖掘机龙头"), ("000425", "徐工机械", "工程机械", "主板", 7.2, 118.3, 1, 0.022, "工程机械"), ("600760", "中航沈飞", "军工", "主板", 52, 27.6, 1, 0.024, "战斗机主机厂"), ("603288", "海天味业", "食品饮料", "主板", 42, 55.6, 1, 0.020, "调味品龙头"), ("600887", "伊利股份", "食品饮料", "主板", 27, 63.7, 1, 0.019, "乳制品龙头"), ("000895", "双汇发展", "食品饮料", "主板", 25, 34.6, 2, 0.019, "肉制品龙头"), ("002415", "海康威视", "安防", "主板", 32, 92.3, 1, 0.022, "智能物联龙头"), ("002230", "科大讯飞", "人工智能", "主板", 46, 23.1, 0, 0.028, "AI语音与大模型"), ("688111", "金山办公", "软件", "科创板", 268, 4.6, 0, 0.027, "办公软件WPS"), ("300033", "同花顺", "金融科技", "创业板", 158, 5.4, 0, 0.028, "金融信息服务"), ("600570", "恒生电子", "软件", "主板", 26, 19.0, 1, 0.023, "金融IT龙头"), ("601888", "中国中免", "免税", "主板", 68, 20.7, 1, 0.023, "免税龙头"), ("600900", "长江电力", "电力", "主板", 28, 244.7, 1, 0.015, "水电龙头"), ("601088", "中国神华", "煤炭", "主板", 42, 198.7, 1, 0.017, "煤电一体化"), ("000002", "万科A", "房地产", "主板", 6.8, 119.3, 2, 0.026, "地产开发"), ("600048", "保利发展", "房地产", "主板", 8.4, 119.7, 2, 0.025, "地产开发"), ("600941", "中国移动", "通信运营", "主板", 102, 95.7, 1, 0.016, "通信运营龙头"), ("000063", "中兴通讯", "通信设备", "主板", 28, 47.8, 1, 0.024, "通信设备"), ("002714", "牧原股份", "养殖", "主板", 42, 54.6, 1, 0.026, "生猪养殖龙头"), ("300498", "温氏股份", "养殖", "创业板", 18, 66.5, 1, 0.025, "生猪+禽养殖"), ("601318", "中国平安", "保险", "主板", 48, 181.7, 1, 0.019, "综合金融"), ("601628", "中国人寿", "保险", "主板", 30, 282.6, 1, 0.018, "寿险龙头"), ("600111", "北方稀土", "稀土", "主板", 21, 36.2, 1, 0.027, "稀土龙头"), ("601600", "中国铝业", "有色", "主板", 7.5, 171.6, 1, 0.022, "电解铝龙头"), ] # ===================================================================== 机构 INSTITUTIONS = [ # (name, type, desc, 重点行业) ("易方达基金", "公募基金", "国内头部公募基金,管理规模超1.6万亿,深耕消费与科技赛道。", ["白酒", "消费电子", "医药"]), ("华夏基金", "公募基金", "老牌公募,主动权益与ETF双轮驱动。", ["半导体", "新能源", "医药"]), ("嘉实基金", "公募基金", "全牌照公募,均衡配置大盘蓝筹与成长。", ["银行", "食品饮料", "家电"]), ("广发基金", "公募基金", "权益投资见长,聚焦科技成长。", ["半导体", "人工智能", "光伏"]), ("南方基金", "公募基金", "老十家公募之一,稳健价值风格。", ["银行", "券商", "白酒"]), ("富国基金", "公募基金", "主动权益明星基金较多,偏好行业景气。", ["医药", "半导体", "机械"]), ("汇添富基金", "公募基金", "价值成长均衡,重仓消费与医药。", ["白酒", "医药", "消费电子"]), ("中欧基金", "公募基金", "权益投资口碑公募,聚焦成长赛道。", ["新能源", "人工智能", "软件"]), ("中信证券研究部", "券商研究", "国内综合实力领先的券商研究所,覆盖全行业。", ["券商", "半导体", "军工"]), ("华泰证券研究所", "券商研究", "科技+金融双主线研究见长。", ["消费电子", "金融科技", "半导体"]), ("国泰君安研究所", "券商研究", "老牌研究所,宏观策略与大金融研究扎实。", ["银行", "券商", "保险"]), ("招商证券研究所", "券商研究", "以行业景气跟踪见长,深度覆盖成长板块。", ["汽车", "消费电子", "医药"]), ("广发证券研究所", "券商研究", "策略+行业结合,偏成长挖掘。", ["人工智能", "软件", "新能源"]), ("兴业证券研究所", "券商研究", "产业链研究见长,覆盖周期成长。", ["化工", "有色", "银行"]), ("东吴证券研究所", "券商研究", "新经济研究活跃,覆盖TMT。", ["半导体", "软件", "通信"]), ("浙商证券研究所", "券商研究", "后起之秀,新能源与制造研究领先。", ["光伏", "新能源", "机械"]), ("中国人寿资管", "保险资管", "险资巨头,偏好高股息蓝筹与稳定现金流。", ["银行", "电力", "保险"]), ("平安资管", "保险资管", "综合金融背景,长期资金代表。", ["银行", "食品饮料", "保险"]), ("泰康资产", "保险资管", "保险资管头部,注重绝对收益。", ["医药", "家电", "白酒"]), ("高盛", "外资机构", "全球投行,A股研究覆盖核心资产。", ["白酒", "新能源", "消费电子"]), ("摩根士丹利", "外资机构", "全球投行,偏好大盘龙头与全球化公司。", ["消费电子", "新能源", "汽车"]), ("瑞银证券", "外资机构", "外资券商,深入研究A股核心蓝筹。", ["银行", "白酒", "医药"]), ("贝莱德", "外资机构", "全球最大资管,长期配置中国核心资产。", ["银行", "食品饮料", "电力"]), ("高毅资产", "私募游资", "头部私募,逆向投资与深度研究。", ["医药", "消费", "科技"]), ("淡水泉投资", "私募游资", "老牌私募,擅长困境反转与逆向布局。", ["化工", "地产", "养殖"]), ("景林资产", "私募游资", "价值投资私募,重仓优质成长。", ["白酒", "互联网", "家电"]), ("幻方量化", "私募游资", "头部量化私募,捕捉市场波动机会。", ["金融", "周期", "TMT"]), ] # ===================================================================== 新闻 NEWS_SOURCES = ["证券时报", "上海证券报", "中国证券报", "财联社", "界面新闻", "每日经济新闻", "澎湃新闻", "21世纪经济报道", "第一财经"] NEWS_TEMPLATES = [ # (category, positive, title_tpl, content_tpls) ("业绩", True, "{name}发布业绩预告:前三季度净利润同比增长{pct}%", ["公司公告显示,受益于{industry}行业景气度提升,{name}核心业务收入实现较快增长,业绩超市场一致预期。", "多家券商点评认为,{name}盈利质量改善明显,费用管控有效,全年业绩有望延续高增。", "业内人士表示,行业需求回暖背景下,{name}作为{industry}领域头部公司,市占率有望进一步提升。"]), ("业绩", True, "{name}半年度净利大增{pct}%,创上市以来新高", ["{name}中报披露,报告期内实现营业收入同比增长{num}%,净利润同比增长{pct}%,均超市场预期。", "公司称,新产能释放叠加产品结构优化,带动毛利率显著提升。", "多家机构预计,随着行业景气延续,{name}未来业绩增长确定性较强。"]), ("业绩", False, "{name}业绩不及预期:第三季度营收同比下滑{pct}%", ["{name}三季报显示,受行业需求疲软影响,公司营业收入同比下滑{pct}%,净利润降幅扩大。", "公司解释称,原材料成本上行及产品价格承压是主要原因。", "市场人士认为,短期{name}基本面仍面临压力,需观察行业拐点信号。"]), ("行业", True, "政策加码!{industry}行业迎来{num}亿产业基金支持", ["相关部门发文明确,将设立{num}亿元产业投资基金,重点支持{industry}产业链关键环节技术攻关与产能建设。", "分析人士指出,政策红利有望带动{industry}板块整体估值修复,相关龙头企业将直接受益。", "板块内多只个股盘中异动,资金关注度明显提升。"]), ("行业", False, "原材料价格波动,{industry}行业盈利承压", ["近期上游原材料价格波动加大,{industry}行业部分企业毛利率受到侵蚀。", "业内调研显示,中小企业已出现减产观望情绪,头部公司凭借成本优势影响相对可控。", "机构提示,短期需关注库存去化进度与价格企稳信号。"]), ("公司", True, "{name}拟回购{num}亿元股份,彰显发展信心", ["{name}公告,拟以自有资金{num}亿元回购公司股份,用于员工持股计划或股权激励,回购价格不超过{price}元/股。", "公司表示,回购基于对未来发展前景的信心及对公司价值的认可。", "分析人士称,回购计划落地有望对股价形成支撑,彰显管理层信心。"]), ("公司", True, "{name}中标{num}亿元重大项目,订单持续饱满", ["{name}公告,近日中标{industry}领域重大工程项目,中标金额合计约{num}亿元。", "公司称,该项目是公司在核心客户与重点市场的重要突破,有利于巩固行业地位。", "机构预计,在手订单充足将支撑{name}未来收入增长确定性。"]), ("公司", False, "{name}股东拟减持不超过{pct}%股份", ["{name}公告,持股{num}%的股东计划在未来6个月内减持不超过公司总股本{pct}%的股份。", "公司称,减持系股东自身资金安排,不影响公司正常经营。", "市场对此反应谨慎,分析人士提醒关注减持节奏对股价的短期压制。"]), ("机构观点", True, "{inst}上调{name}评级至「买入」,目标价{price}元", ["{inst}发布研报认为,{name}受益于{industry}行业景气回升,业绩进入加速释放期,将评级由「增持」上调至「买入」。", "研报给出目标价{price}元,较当前股价存在一定上行空间。", "研报强调,{name}核心竞争力稳固,估值具备吸引力,建议积极配置。"]), ("机构观点", False, "{inst}下调{name}评级至「中性」,提示估值风险", ["{inst}研报指出,{name}短期涨幅较大,当前估值已透支部分预期,将评级由「买入」下调至「中性」。", "研报认为,行业景气虽有支撑,但股价上行空间收窄,建议等待更好的介入时点。", "市场人士表示,机构评级下调或引发短期情绪扰动。"]), ("市场", True, "沪指放量上涨{pct}%,两市成交额突破{num}万亿", ["A股市场情绪回暖,沪指放量上行{pct}%,深成指、创业板指同步走强。", "盘面上,{industry}等板块领涨,赚钱效应明显,两市成交额突破{num}万亿元。", "分析人士认为,市场风险偏好回升,中期趋势向好,可关注业绩确定性方向。"]), ("市场", False, "大盘缩量回调{pct}%,市场观望情绪升温", ["A股缩量调整,沪指收跌{pct}%,两市成交额较前期明显萎缩。", "盘面上热点轮动加快,缺乏持续性主线,资金观望情绪浓厚。", "机构提示,短期指数或以震荡为主,建议控制仓位、关注结构机会。"]), ] FILLERS = [ "相关消息发布后,市场反应总体平稳。", "多位市场人士对此进行了讨论。", "后续进展值得持续跟踪。", "公司方面暂未就此事进一步置评。", "受此影响,相关产业链公司受到市场关注。", "整体来看,基本面对股价中期走势具有决定性影响。", ] def _gen_trading_dates(n=180): """生成最近 n 个交易日(跳过周末),终止于最近工作日""" dates = [] d = dt.date.today() # 回退到最近的非周末 while d.weekday() >= 5: d -= dt.timedelta(days=1) while len(dates) < n: if d.weekday() < 5: dates.append(d.isoformat()) d -= dt.timedelta(days=1) return list(reversed(dates)) # ===================================================================== 生成 def gen_stocks(): rows = [] for code, name, industry, board, base, float_shares, trend, vol, biz in STOCKS: total = round(float_shares * random.uniform(1.0, 1.6), 2) pe = round(random.uniform(15, 60), 1) pb = round(random.uniform(1.5, 8), 2) desc = (f"{name}是{industry}领域{biz}。" f"公司主营产品广泛应用于核心客户,行业地位稳固,近年来持续加大研发投入," f"积极拓展新增长曲线。当前总股本约{total}亿股,流通市值位居行业前列。") rows.append((code, name, industry, board, total, float_shares, f"200{random.randint(0, 9)}-{random.randint(1, 12):02d}-{random.randint(1, 28):02d}", pe, pb, 0.0, desc)) return rows def gen_daily(dates): """生成个股日线 + 指数序列""" daily = [] index = {} sh, sz, cy = 3245.0, 10580.0, 2120.0 price = {} for code, name, industry, board, base, float_shares, trend, vol, biz in STOCKS: p = base drift = {0: 0.0011, 1: 0.00025, 2: -0.00085}[trend] # 最近30天加速(制造趋势分化,让荐股有区分度) recent_drift = {0: 0.0045, 1: 0.0001, 2: -0.0045}[trend] # 基准成交量(万股)= 流通盘 × 0.4%~1.5% 日换手(贴近真实市场) base_vol = float_shares * 10000 * random.uniform(0.004, 0.015) for i, d in enumerate(dates): phase = max(0, i - (len(dates) - 30)) dr = drift + (recent_drift if phase > 0 else 0) r = random.gauss(dr, vol) if random.random() < 0.02: # 偶发跳空 r += random.gauss(0, vol * 1.6) prev = p p = max(0.8, p * (1 + r)) open_p = prev * (1 + random.gauss(0, vol * 0.5)) high = max(open_p, p) * (1 + abs(random.gauss(0, vol * 0.35))) low = min(open_p, p) * (1 - abs(random.gauss(0, vol * 0.35))) volume = base_vol * (1 + 1.5 * abs(r) / vol) * random.uniform(0.6, 1.4) amount = volume * (open_p + p) / 2 # 万元 chg = (p - prev) / prev * 100 daily.append((code, d, round(open_p, 2), round(high, 2), round(low, 2), round(p, 2), round(volume, 0), round(amount, 0), round(chg, 2))) price[code] = p # 指数序列(独立随机游走) for i, d in enumerate(dates): if i == 0: index[d] = {"sh": round(sh, 2), "sz": round(sz, 2), "cy": round(cy, 2)} continue sh_r = sum(random.gauss(0.0004, 0.008) for _ in range(6)) / 6 sz_r = sh_r + random.gauss(0, 0.004) cy_r = sh_r + random.gauss(0, 0.006) sh *= (1 + sh_r); sz *= (1 + sz_r); cy *= (1 + cy_r) index[d] = {"sh": round(sh, 2), "sz": round(sz, 2), "cy": round(cy, 2)} return daily, index, price def gen_news(dates, price): """生成新闻(关联最近90天,偏近分布)""" news = [] recent_dates = dates[-95:] stock_map = {s[0]: s for s in STOCKS} for _ in range(300): tpl = random.choice(NEWS_TEMPLATES) category, positive, title_tpl, contents = tpl s = random.choice(STOCKS) code, name, industry = s[0], s[1], s[2] pct = random.randint(5, 85) if positive else random.randint(5, 60) num = random.choice([5, 10, 20, 30, 50, 80, 100, 120, 150, 200]) price_t = round(price.get(code, 10) * random.uniform(1.02, 1.35), 2) inst = random.choice(INSTITUTIONS)[0] title = title_tpl.format(name=name, pct=pct, num=num, price=price_t, inst=inst, industry=industry) content = " ".join(c.format(name=name, pct=pct, num=num, price=price_t, inst=inst, industry=industry) for c in contents) content += " " + " ".join(random.sample(FILLERS, random.randint(1, 3))) date = random.choice(recent_dates) source = random.choice(NEWS_SOURCES) sentiment = round(random.uniform(0.25, 0.85) if positive else random.uniform(-0.85, -0.25), 2) # 关联股票:主股 + 同行业0~2只 related = [code] same = [x[0] for x in STOCKS if x[2] == industry and x[0] != code] random.shuffle(same) related += same[:random.randint(0, 2)] news.append({ "title": title, "content": content, "source": source, "category": category, "publish_date": date, "related": ",".join(related), "sentiment": sentiment, "is_positive": 1 if positive else 0, }) news.sort(key=lambda x: x["publish_date"]) return news def gen_institutions(): rows = [] for name, typ, desc, _focus in INSTITUTIONS: rows.append((name, typ, desc)) return rows def gen_ratings(price): """机构评级:每家机构关注重点行业内的股票""" rows = [] for name, typ, desc, focus in INSTITUTIONS: pool = [s for s in STOCKS if s[2] in focus] if not pool: pool = STOCKS picks = random.sample(pool, min(8, len(pool))) for s in picks: code = s[0] r = random.choices(["买入", "增持", "中性", "减持"], weights=[5, 4, 2, 1])[0] up = {"买入": random.uniform(0.15, 0.35), "增持": random.uniform(0.05, 0.18), "中性": random.uniform(-0.05, 0.06), "减持": random.uniform(-0.18, -0.08)}[r] tp = round(price.get(code, 10) * (1 + up), 2) # 用近期交易日作为评级日 days = _recent_days(45) rows.append((0, name, code, r, tp, random.choice(days), random.choice(["中性", "增持", "买入", "增持", "买入"]))) return rows def _recent_days(n): dates = _gen_trading_dates(n) return dates GLOBAL_INDICES = [ ("dji", "道琼斯", 34000), ("nasdaq", "纳斯达克", 12800), ("sp500", "标普500", 4400), ("hsi", "恒生指数", 17500), ("nikkei", "日经225", 33000), ("kospi", "韩国KOSPI", 2500), ("dax", "德国DAX", 16000), ("cac", "法国CAC40", 7000), ("ftse", "英国FTSE100", 7500), ] def _gen_global(dates): """生成全球主要指数模拟序列(随机游走,chg 基于前一交易日)""" vals = {k: v for k, _, v in GLOBAL_INDICES} prev = dict(vals) out = {} for d in dates: row = {} for k, label, _v in GLOBAL_INDICES: vals[k] *= (1 + random.gauss(0.0002, 0.009)) row[k] = {"label": label, "value": round(vals[k], 2), "chg": round((vals[k] - prev[k]) / prev[k] * 100, 2)} for k in prev: prev[k] = vals[k] out[d] = row return out def gen_holdings(price, dates): """基金季度持仓:2025Q4 / 2026Q1 / 2026Q2""" rows = [] quarters = ["2025Q4", "2026Q1", "2026Q2"] funds = [i for i in INSTITUTIONS if i[1] == "公募基金"] + \ [i for i in INSTITUTIONS if i[1] == "保险资管"] + \ [i for i in INSTITUTIONS if i[1] in ("外资机构", "私募游资")] for name, typ, desc, focus in funds: pool = [s for s in STOCKS if s[2] in focus] or STOCKS picks = random.sample(pool, min(random.randint(6, 12), len(pool))) for s in picks: code = s[0] last_hold = random.uniform(2000, 40000) # 万股 prev_hold = None for qi, q in enumerate(quarters): if qi == 0: hold = last_hold * random.uniform(0.5, 1.1) else: hold = last_hold chg = 0.0 if qi == 2: # 最新季度给出增减方向,与趋势挂钩 trend = s[6] delta = {0: random.uniform(5, 30), 1: random.uniform(-12, 12), 2: random.uniform(-25, -3)}[trend] chg = hold * delta / 100 hold = max(500, hold + chg) value = hold * price.get(code, 10) rows.append((0, name, code, q, round(hold, 0), round(value, 0), round(chg, 0), round(chg / max(hold - chg, 1) * 100, 2))) last_hold = hold return rows # ===================================================================== 入库 def build_vectors(news, stocks): """构建 Chroma 向量索引:新闻 + 公司概况""" print(">>> 构建新闻向量索引 ...") vs.delete_collection(CHROMA_NEWS_COLLECTION) ids, docs, metas = [], [], [] for n in news: for code in n["related"].split(","): ids.append(f"news-{n['title']}-{code}") docs.append(f"{n['title']}\n{n['content']}") metas.append({"code": code, "title": n["title"], "date": n["publish_date"], "category": n["category"], "sentiment": n["sentiment"], "news_id": 0}) for i in range(0, len(ids), 16): vs.add_documents(ids[i:i + 16], docs[i:i + 16], metas[i:i + 16], CHROMA_NEWS_COLLECTION) print(f" news {min(i+16, len(ids))}/{len(ids)}") print(">>> 构建公司概况向量索引 ...") vs.delete_collection(CHROMA_PROFILE_COLLECTION) ids, docs, metas = [], [], [] for s in stocks: ids.append(f"profile-{s[0]}") docs.append(f"{s[1]}({s[0]}),所属行业:{s[2]}。{s[10]}") metas.append({"code": s[0], "name": s[1], "industry": s[2]}) vs.add_documents(ids, docs, metas, CHROMA_PROFILE_COLLECTION) print(f" profiles {len(ids)}") print(f" 新闻索引条数: {vs.collection_count(CHROMA_NEWS_COLLECTION)}") print(f" 概况索引条数: {vs.collection_count(CHROMA_PROFILE_COLLECTION)}") def main(): parser = argparse.ArgumentParser() parser.add_argument("--skip-vector", action="store_true", help="跳过向量索引重建") parser.add_argument("--no-strategies", action="store_true", help="跳过量化策略回测") args = parser.parse_args() print(">>> 初始化数据库 ...") init_db() wipe_all() dates = _gen_trading_dates() print(f">>> 生成 {len(STOCKS)} 只股票日线行情({dates[0]} ~ {dates[-1]})...") stocks = gen_stocks() executemany( "INSERT INTO stocks(code,name,industry,board,total_shares,float_shares,list_date,pe,pb,market_cap,description) " "VALUES(?,?,?,?,?,?,?,?,?,?,?)", [(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], 0.0, s[10]) for s in stocks]) daily, index, price = gen_daily(dates) executemany( "INSERT OR REPLACE INTO stock_daily(code,date,open,high,low,close,volume,amount,change_pct) " "VALUES(?,?,?,?,?,?,?,?,?)", daily) executemany("INSERT OR REPLACE INTO market_index(date,sh,sz,cy) VALUES(?,?,?,?)", [(d, v["sh"], v["sz"], v["cy"]) for d, v in index.items()]) # 全球市场指数(模拟) gm = _gen_global(dates) executemany("INSERT OR REPLACE INTO global_markets(date,data) VALUES(?,?)", [(d, json.dumps(v, ensure_ascii=False)) for d, v in gm.items()]) # 回填市值 for code, name, industry, board, base, fs, trend, vol, biz in STOCKS: from database import execute as ex ex("UPDATE stocks SET market_cap=ROUND((SELECT close FROM stock_daily WHERE code=? ORDER BY date DESC LIMIT 1)*total_shares,2) WHERE code=?", (code, code)) print(">>> 生成财经新闻 ...") news = gen_news(dates, price) executemany( "INSERT INTO news(title,content,source,category,publish_date,related_stocks,sentiment,is_positive) " "VALUES(?,?,?,?,?,?,?,?)", [(n["title"], n["content"], n["source"], n["category"], n["publish_date"], n["related"], n["sentiment"], n["is_positive"]) for n in news]) print(">>> 生成机构与评级 ...") insts = gen_institutions() executemany("INSERT INTO institutions(name,type,description) VALUES(?,?,?)", insts) inst_map = {} for i, (name, typ, desc) in enumerate(insts, start=1): inst_map[name] = i ratings = gen_ratings(price) executemany( "INSERT INTO inst_ratings(inst_id,inst_name,stock_code,rating,target_price,rating_date,prev_rating) " "VALUES(?,?,?,?,?,?,?)", [(inst_map[r[1]], r[1], r[2], r[3], r[4], r[5], r[6]) for r in ratings]) print(">>> 生成基金持仓 ...") holdings = gen_holdings(price, dates) executemany( "INSERT INTO fund_holdings(inst_id,inst_name,stock_code,quarter,hold_shares,hold_value,change_shares,change_pct) " "VALUES(?,?,?,?,?,?,?,?)", [(inst_map.get(h[1], 0), h[1], h[2], h[3], h[4], h[5], h[6], h[7]) for h in holdings]) print(">>> 向量索引构建 ...") if not args.skip_vector: build_vectors(news, stocks) else: print(" (跳过)") print(">>> 量化策略全市场回测 ...") if not args.no_strategies: from engine.strategies import build_all, STRATEGIES cnt = build_all() print(f" 回测记录 {cnt} 条({len(STRATEGIES)} 策略 × 全市场)") else: print(" (跳过)") from database import table_count print("=" * 50) print("数据库统计:") for t in ("stocks", "stock_daily", "news", "institutions", "inst_ratings", "fund_holdings", "watchlist", "analysis_cache", "market_index"): print(f" {t:16s} {table_count(t)} 条") print("✅ 数据生成完成") if __name__ == "__main__": main()