chore: initialize lhbfx project and documentation
This commit is contained in:
11
backend/scripts/_bootstrap.py
Normal file
11
backend/scripts/_bootstrap.py
Normal 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))
|
||||
21
backend/scripts/generate_warnings.py
Normal file
21
backend/scripts/generate_warnings.py
Normal 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()
|
||||
25
backend/scripts/import_ths_daily.py
Normal file
25
backend/scripts/import_ths_daily.py
Normal 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()
|
||||
56
backend/scripts/import_ths_year.py
Normal file
56
backend/scripts/import_ths_year.py
Normal 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()
|
||||
75
backend/scripts/init_db.py
Normal file
75
backend/scripts/init_db.py
Normal 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()
|
||||
16
backend/scripts/rematch_traders.py
Normal file
16
backend/scripts/rematch_traders.py
Normal 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()
|
||||
14
backend/scripts/run_api.py
Normal file
14
backend/scripts/run_api.py
Normal 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()
|
||||
Reference in New Issue
Block a user