chore: initialize lhbfx project and documentation

This commit is contained in:
wanghep
2026-04-17 21:20:26 +08:00
commit 5a5dd3c9fd
54 changed files with 11185 additions and 0 deletions

View File

@ -0,0 +1,11 @@
from __future__ import annotations
import sys
from pathlib import Path
def add_src_to_path() -> None:
backend_dir = Path(__file__).resolve().parents[1]
src_dir = backend_dir / "src"
if str(src_dir) not in sys.path:
sys.path.insert(0, str(src_dir))

View File

@ -0,0 +1,21 @@
from __future__ import annotations
from _bootstrap import add_src_to_path
add_src_to_path()
from lhbfx.pipeline import generate_warnings
def main() -> None:
result = generate_warnings()
print(
"预警生成完成:"
f"sell_alert={result['sell_alert']}, "
f"slow_exit_watch={result['slow_exit_watch']}, "
f"total={result['total']}"
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,25 @@
from __future__ import annotations
import argparse
from datetime import datetime
from _bootstrap import add_src_to_path
add_src_to_path()
from lhbfx.pipeline import import_daily
def main() -> None:
parser = argparse.ArgumentParser(description="导入同花顺某日全量龙虎榜数据")
parser.add_argument("--date", default=datetime.now().strftime("%Y-%m-%d"), help="交易日期,格式 YYYY-MM-DD")
args = parser.parse_args()
result = import_daily(args.date)
print(f"导入日期: {args.date}")
print(f"概览记录数: {result.overview_count}")
print(f"营业部明细数: {result.detail_count}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,56 @@
from __future__ import annotations
import argparse
from datetime import datetime
from pathlib import Path
from _bootstrap import add_src_to_path
add_src_to_path()
from lhbfx.pipeline import import_range
def log_line(log_file: Path, message: str) -> None:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{timestamp}] {message}"
print(line, flush=True)
log_file.parent.mkdir(parents=True, exist_ok=True)
with log_file.open("a", encoding="utf-8") as f:
f.write(line + "\n")
def main() -> None:
parser = argparse.ArgumentParser(description="按工作日批量导入某年同花顺龙虎榜数据")
parser.add_argument("--year", type=int, default=2026, help="年份,默认 2026")
parser.add_argument("--start", default=None, help="起始日期 YYYY-MM-DD")
parser.add_argument("--end", default=None, help="结束日期 YYYY-MM-DD")
parser.add_argument("--limit", type=int, default=None, help="限制导入的交易日数量")
parser.add_argument("--log-file", default=None, help="日志文件路径")
args = parser.parse_args()
default_log = Path(__file__).resolve().parents[1] / "output" / "logs" / f"import_{args.year}.log"
log_file = Path(args.log_file) if args.log_file else default_log
log_line(
log_file,
f"开始导入 year={args.year}, start={args.start}, end={args.end}, limit={args.limit}",
)
result = import_range(year=args.year, start=args.start, end=args.end, limit=args.limit)
for item in result["success"]:
log_line(
log_file,
f"{item['trade_date']} -> 成功, 概览 {item['overview_count']} 条, 明细 {item['detail_count']}",
)
for item in result["failed"]:
log_line(log_file, f"{item['trade_date']} -> 失败, error={item['error']}")
log_line(
log_file,
f"导入完成 success={result['success_count']}, failed={result['failed_count']}, total={result['requested_days']}",
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,75 @@
from pathlib import Path
from _bootstrap import add_src_to_path
add_src_to_path()
from lhbfx.config import AppConfig, load_config
from lhbfx.db import db_cursor, execute_schema
from lhbfx.seed import seed_traders
def _column_exists(cursor, schema_name: str, table_name: str, column_name: str) -> bool:
cursor.execute(
"""
SELECT 1
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = %s
LIMIT 1
""",
(schema_name, table_name, column_name),
)
return cursor.fetchone() is not None
def _index_exists(cursor, schema_name: str, table_name: str, index_name: str) -> bool:
cursor.execute(
"""
SELECT 1
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND INDEX_NAME = %s
LIMIT 1
""",
(schema_name, table_name, index_name),
)
return cursor.fetchone() is not None
def apply_incremental_alters(config: AppConfig) -> None:
schema_name = config.database.database
with db_cursor(config=config) as (_, cursor):
if not _column_exists(cursor, schema_name, "lhb_overview", "rid"):
cursor.execute("ALTER TABLE lhb_overview ADD COLUMN rid VARCHAR(32) NULL AFTER flag")
if not _column_exists(cursor, schema_name, "lhb_overview", "detail_url"):
cursor.execute("ALTER TABLE lhb_overview ADD COLUMN detail_url VARCHAR(255) NULL AFTER net_buy")
if not _index_exists(cursor, schema_name, "lhb_overview", "uniq_lhb_overview_record"):
cursor.execute("ALTER TABLE lhb_overview ADD UNIQUE KEY uniq_lhb_overview_record (trade_date, stock_code, rid)")
if not _column_exists(cursor, schema_name, "lhb_detail_seats", "rid"):
cursor.execute("ALTER TABLE lhb_detail_seats ADD COLUMN rid VARCHAR(32) NULL AFTER stock_name")
if not _column_exists(cursor, schema_name, "lhb_detail_seats", "section_title"):
cursor.execute("ALTER TABLE lhb_detail_seats ADD COLUMN section_title VARCHAR(255) NULL AFTER rid")
if not _column_exists(cursor, schema_name, "lhb_detail_seats", "buy_ratio"):
cursor.execute("ALTER TABLE lhb_detail_seats ADD COLUMN buy_ratio VARCHAR(32) NULL AFTER buy_amount_wan")
if not _column_exists(cursor, schema_name, "lhb_detail_seats", "sell_ratio"):
cursor.execute("ALTER TABLE lhb_detail_seats ADD COLUMN sell_ratio VARCHAR(32) NULL AFTER sell_amount_wan")
if not _column_exists(cursor, schema_name, "lhb_detail_seats", "detail_url"):
cursor.execute("ALTER TABLE lhb_detail_seats ADD COLUMN detail_url VARCHAR(255) NULL AFTER matched_seat")
if not _index_exists(cursor, schema_name, "lhb_detail_seats", "uniq_lhb_detail_record"):
cursor.execute(
"ALTER TABLE lhb_detail_seats ADD UNIQUE KEY uniq_lhb_detail_record (trade_date, stock_code, rid, table_title, seat_name)"
)
def main() -> None:
root = Path(__file__).resolve().parents[1]
schema_path = root / "src" / "lhbfx" / "schema.sql"
config = load_config()
execute_schema(schema_path=schema_path, config=config)
apply_incremental_alters(config)
seed_traders(config=config)
print("数据库建表完成,游资配置已写入数据库。")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,16 @@
from __future__ import annotations
from _bootstrap import add_src_to_path
add_src_to_path()
from lhbfx.pipeline import rematch_traders
def main() -> None:
result = rematch_traders()
print(f"重新匹配完成,处理记录数: {result['updated']}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,14 @@
from __future__ import annotations
import uvicorn
from _bootstrap import add_src_to_path
def main() -> None:
add_src_to_path()
uvicorn.run("lhbfx.app:app", host="127.0.0.1", port=8000, reload=False)
if __name__ == "__main__":
main()