feat: automate after-market update and email delivery
This commit is contained in:
12
README.md
12
README.md
@ -48,6 +48,18 @@ python backend/scripts/init_db.py
|
|||||||
python backend/scripts/run_api.py
|
python backend/scripts/run_api.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
4. 收盘后更新并发送邮件:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python backend/scripts/after_market_update.py --trade-date 2026-04-17 --send-email
|
||||||
|
```
|
||||||
|
|
||||||
|
默认约定:
|
||||||
|
|
||||||
|
- 按 `Asia/Shanghai` 取当天日期
|
||||||
|
- 预期定时为 A 股交易日 `17:00`
|
||||||
|
- 只有当日成功拉到新的龙虎榜数据,才会继续生成预警、PDF 并发邮件;否则自动跳过
|
||||||
|
|
||||||
默认地址:
|
默认地址:
|
||||||
|
|
||||||
- `http://127.0.0.1:8000`
|
- `http://127.0.0.1:8000`
|
||||||
|
|||||||
116
backend/scripts/after_market_update.py
Normal file
116
backend/scripts/after_market_update.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from datetime import datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
from _bootstrap import add_src_to_path
|
||||||
|
|
||||||
|
add_src_to_path()
|
||||||
|
|
||||||
|
from lhbfx.config import load_config
|
||||||
|
from lhbfx.mailer import send_email
|
||||||
|
from lhbfx.pdf_export import generate_daily_report_pdf
|
||||||
|
from lhbfx.pipeline import generate_warnings, import_daily, rematch_traders
|
||||||
|
from lhbfx.reporting import build_daily_report, build_email_body, default_report_output_path
|
||||||
|
|
||||||
|
|
||||||
|
SHANGHAI_TZ = ZoneInfo("Asia/Shanghai")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Run lhbfx after-market update workflow")
|
||||||
|
parser.add_argument("--trade-date", help="Trade date in YYYY-MM-DD format, defaults to Asia/Shanghai today")
|
||||||
|
parser.add_argument("--send-email", action="store_true", help="Send completion email after report generation")
|
||||||
|
parser.add_argument("--force", action="store_true", help="Run even if the target date is not a weekday")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def default_trade_date() -> str:
|
||||||
|
return datetime.now(SHANGHAI_TZ).date().isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def is_weekday(trade_date: str) -> bool:
|
||||||
|
return datetime.fromisoformat(trade_date).weekday() < 5
|
||||||
|
|
||||||
|
|
||||||
|
def build_update_email_body(
|
||||||
|
*,
|
||||||
|
trade_date: str,
|
||||||
|
overview_count: int,
|
||||||
|
detail_count: int,
|
||||||
|
rematch_updated: int,
|
||||||
|
warning_total: int,
|
||||||
|
report_body: str,
|
||||||
|
) -> str:
|
||||||
|
lines = [
|
||||||
|
f"lhbfx 收盘后更新完成 - {trade_date}",
|
||||||
|
"",
|
||||||
|
f"龙虎榜概览更新数: {overview_count}",
|
||||||
|
f"席位明细更新数: {detail_count}",
|
||||||
|
f"游资重新匹配数: {rematch_updated}",
|
||||||
|
f"预警总数: {warning_total}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
lines.append(report_body)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
config = load_config()
|
||||||
|
trade_date = args.trade_date or default_trade_date()
|
||||||
|
|
||||||
|
if not args.force and not is_weekday(trade_date):
|
||||||
|
print(f"Skip {trade_date}: not a weekday, assumed non-trading day.")
|
||||||
|
return
|
||||||
|
|
||||||
|
import_result = import_daily(trade_date, config=config)
|
||||||
|
if import_result.overview_count <= 0:
|
||||||
|
print(f"Skip {trade_date}: no overview rows imported, source data may not be ready or market may be closed.")
|
||||||
|
return
|
||||||
|
|
||||||
|
rematch_result = rematch_traders(config=config)
|
||||||
|
warning_result = generate_warnings(config=config)
|
||||||
|
|
||||||
|
report = build_daily_report(config=config, trade_date=trade_date)
|
||||||
|
pdf_path = default_report_output_path(trade_date)
|
||||||
|
generate_daily_report_pdf(report, pdf_path)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"After-market update finished:",
|
||||||
|
{
|
||||||
|
"trade_date": trade_date,
|
||||||
|
"overview_count": import_result.overview_count,
|
||||||
|
"detail_count": import_result.detail_count,
|
||||||
|
"rematch_updated": rematch_result["updated"],
|
||||||
|
"warning_total": warning_result["total"],
|
||||||
|
"pdf_path": str(pdf_path),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.send_email:
|
||||||
|
if config.mail is None:
|
||||||
|
raise RuntimeError("Mail config is missing")
|
||||||
|
|
||||||
|
report_body = build_email_body(report)
|
||||||
|
body_text = build_update_email_body(
|
||||||
|
trade_date=trade_date,
|
||||||
|
overview_count=import_result.overview_count,
|
||||||
|
detail_count=import_result.detail_count,
|
||||||
|
rematch_updated=rematch_result["updated"],
|
||||||
|
warning_total=warning_result["total"],
|
||||||
|
report_body=report_body,
|
||||||
|
)
|
||||||
|
subject = f"lhbfx 收盘后更新完成 - {trade_date}"
|
||||||
|
send_email(
|
||||||
|
mail_config=config.mail,
|
||||||
|
subject=subject,
|
||||||
|
body_text=body_text,
|
||||||
|
attachments=[pdf_path],
|
||||||
|
)
|
||||||
|
print(f"Email sent to: {', '.join(config.mail.recipients)}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user