智能荐股系统 v1.0.0:股票池/行情/新闻RAG/机构持仓/多因子评分/AI深度研报

This commit is contained in:
2026-08-19 19:36:32 +08:00
commit 90a9f9b212
29 changed files with 3332 additions and 0 deletions
+474
View File
@@ -0,0 +1,474 @@
# -*- 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="跳过向量索引重建")
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(" (跳过)")
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()