214 lines
7.3 KiB
Python
Executable File
214 lines
7.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""热点股追踪数据导出/导入脚本
|
||
|
||
用法:
|
||
导出8月20日数据:
|
||
python scripts/export_hotspot_data.py export --date 2026-08-20
|
||
|
||
导入数据:
|
||
python scripts/export_hotspot_data.py import --file hotspot_2026-08-20.json
|
||
|
||
查看数据库中有哪些日期的数据:
|
||
python scripts/export_hotspot_data.py list
|
||
"""
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import sys
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
# 添加项目路径
|
||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
|
||
|
||
from database import get_connection, dict_from_row
|
||
|
||
|
||
def export_data(date: str, output_file: Optional[str] = None) -> dict:
|
||
"""导出指定日期的热点股追踪数据"""
|
||
conn = get_connection()
|
||
try:
|
||
# 1. 导出每日题材前20
|
||
themes = conn.execute(
|
||
"SELECT * FROM daily_top_themes WHERE trade_date = ? ORDER BY rank ASC",
|
||
(date,),
|
||
).fetchall()
|
||
|
||
# 2. 导出每日核心股票
|
||
core_stocks = conn.execute(
|
||
"SELECT * FROM daily_core_stocks WHERE trade_date = ? ORDER BY rank ASC",
|
||
(date,),
|
||
).fetchall()
|
||
|
||
# 3. 导出每日核心股票与题材的关联
|
||
stock_themes = conn.execute(
|
||
"SELECT * FROM daily_core_stock_themes WHERE trade_date = ?",
|
||
(date,),
|
||
).fetchall()
|
||
|
||
data = {
|
||
"version": "1.0",
|
||
"export_time": datetime.now().isoformat(),
|
||
"trade_date": date,
|
||
"themes": [dict_from_row(r) for r in themes],
|
||
"core_stocks": [dict_from_row(r) for r in core_stocks],
|
||
"stock_themes": [dict_from_row(r) for r in stock_themes],
|
||
}
|
||
|
||
# 统计信息
|
||
data["stats"] = {
|
||
"theme_count": len(data["themes"]),
|
||
"core_stock_count": len(data["core_stocks"]),
|
||
"stock_theme_count": len(data["stock_themes"]),
|
||
}
|
||
|
||
# 保存到文件
|
||
if output_file is None:
|
||
output_file = f"hotspot_{date}.json"
|
||
|
||
with open(output_file, "w", encoding="utf-8") as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
|
||
print(f"✅ 导出成功:{output_file}")
|
||
print(f" 题材数量:{data['stats']['theme_count']}")
|
||
print(f" 核心股票:{data['stats']['core_stock_count']}")
|
||
print(f" 股票-题材关联:{data['stats']['stock_theme_count']}")
|
||
|
||
return data
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
def import_data(input_file: str, dry_run: bool = False) -> bool:
|
||
"""导入热点股追踪数据"""
|
||
if not os.path.exists(input_file):
|
||
print(f"❌ 文件不存在:{input_file}")
|
||
return False
|
||
|
||
with open(input_file, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
|
||
# 验证数据格式
|
||
required_keys = ["version", "trade_date", "themes", "core_stocks", "stock_themes"]
|
||
if not all(k in data for k in required_keys):
|
||
print("❌ 数据格式错误:缺少必要字段")
|
||
return False
|
||
|
||
date = data["trade_date"]
|
||
print(f"📅 导入日期:{date}")
|
||
print(f" 题材数量:{len(data['themes'])}")
|
||
print(f" 核心股票:{len(data['core_stocks'])}")
|
||
print(f" 股票-题材关联:{len(data['stock_themes'])}")
|
||
|
||
if dry_run:
|
||
print("\n🔍 试运行模式,不写入数据库")
|
||
return True
|
||
|
||
# 检查是否已存在
|
||
conn = get_connection()
|
||
try:
|
||
existing = conn.execute(
|
||
"SELECT 1 FROM daily_core_stocks WHERE trade_date = ? LIMIT 1",
|
||
(date,),
|
||
).fetchone()
|
||
|
||
if existing:
|
||
print(f"\n⚠️ {date} 的数据已存在,将跳过重复数据(使用 INSERT OR IGNORE)")
|
||
finally:
|
||
conn.close()
|
||
|
||
# 写入数据库
|
||
conn = get_connection()
|
||
try:
|
||
# 导入每日题材前20
|
||
for item in data["themes"]:
|
||
# 移除自动生成的id和created_at
|
||
item.pop("id", None)
|
||
item.pop("created_at", None)
|
||
conn.execute(
|
||
"INSERT OR IGNORE INTO daily_top_themes (trade_date, theme_code, theme_name, bf3, hot_rank, rank) VALUES (?,?,?,?,?,?)",
|
||
(item["trade_date"], item["theme_code"], item["theme_name"], item["bf3"], item["hot_rank"], item["rank"]),
|
||
)
|
||
|
||
# 导入每日核心股票
|
||
for item in data["core_stocks"]:
|
||
# 移除自动生成的id和created_at
|
||
item.pop("id", None)
|
||
item.pop("created_at", None)
|
||
conn.execute(
|
||
"INSERT OR IGNORE INTO daily_core_stocks (trade_date, stock_code, stock_name, f3, cover_count, rank) VALUES (?,?,?,?,?,?)",
|
||
(item["trade_date"], item["stock_code"], item["stock_name"], item["f3"], item["cover_count"], item["rank"]),
|
||
)
|
||
|
||
# 导入每日核心股票与题材的关联
|
||
for item in data["stock_themes"]:
|
||
# 移除自动生成的id和created_at
|
||
item.pop("id", None)
|
||
item.pop("created_at", None)
|
||
conn.execute(
|
||
"INSERT OR IGNORE INTO daily_core_stock_themes (trade_date, stock_code, theme_code, theme_name) VALUES (?,?,?,?)",
|
||
(item["trade_date"], item["stock_code"], item["theme_code"], item["theme_name"]),
|
||
)
|
||
|
||
conn.commit()
|
||
print(f"\n✅ 导入成功:{date}")
|
||
return True
|
||
except Exception as e:
|
||
conn.rollback()
|
||
print(f"\n❌ 导入失败:{e}")
|
||
return False
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
def list_dates() -> list[str]:
|
||
"""列出数据库中所有有数据的日期"""
|
||
conn = get_connection()
|
||
try:
|
||
rows = conn.execute(
|
||
"SELECT DISTINCT trade_date FROM daily_core_stocks ORDER BY trade_date DESC"
|
||
).fetchall()
|
||
return [row["trade_date"] for row in rows]
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="热点股追踪数据导出/导入工具")
|
||
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
||
|
||
# 导出命令
|
||
export_parser = subparsers.add_parser("export", help="导出数据")
|
||
export_parser.add_argument("--date", required=True, help="交易日期 (YYYY-MM-DD)")
|
||
export_parser.add_argument("--output", "-o", help="输出文件路径")
|
||
|
||
# 导入命令
|
||
import_parser = subparsers.add_parser("import", help="导入数据")
|
||
import_parser.add_argument("--file", "-f", required=True, help="输入文件路径")
|
||
import_parser.add_argument("--dry-run", action="store_true", help="试运行,不写入数据库")
|
||
|
||
# 列表命令
|
||
subparsers.add_parser("list", help="列出所有有数据的日期")
|
||
|
||
args = parser.parse_args()
|
||
|
||
if args.command == "export":
|
||
export_data(args.date, args.output)
|
||
elif args.command == "import":
|
||
import_data(args.file, args.dry_run)
|
||
elif args.command == "list":
|
||
dates = list_dates()
|
||
if dates:
|
||
print("📅 数据库中的日期:")
|
||
for d in dates:
|
||
print(f" {d}")
|
||
else:
|
||
print("📭 数据库中暂无数据")
|
||
else:
|
||
parser.print_help()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|