2026-08-19 19:36:32 +08:00
# -*- 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 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 ]
base_vol = float_shares * 10000 * random . uniform ( 0.8 , 2.2 ) # 基准成交量(万股)
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 + 3 * 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
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 = "跳过向量索引重建" )
2026-08-19 21:09:16 +08:00
parser . add_argument ( "--no-strategies" , action = "store_true" , help = "跳过量化策略回测" )
2026-08-19 19:36:32 +08:00
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 ()])
# 回填市值
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 ( " (跳过)" )
2026-08-19 21:09:16 +08:00
print ( ">>> 量化策略全市场回测 ..." )
if not args . no_strategies :
from engine.strategies import build_all , STRATEGIES
cnt = build_all ()
print ( f " 回测记录 { cnt } 条( { len ( STRATEGIES ) } 策略 × 全市场)" )
else :
print ( " (跳过)" )
2026-08-19 19:36:32 +08:00
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 ()