"""Paper trading job entrypoint.""" from __future__ import annotations import json from datetime import datetime import ccxt from app.db.altcoin_db import init_db, log_cron_run, update_latest_price_cache from app.db.paper_trading import get_paper_trading_summary, sync_recommendation from app.db.recommendation_queries import get_active_recommendations_deduped exchange = ccxt.binance({"enableRateLimit": True}) def run_once(limit: int = 100) -> dict: init_db() recs = get_active_recommendations_deduped(actionable_only=True, limit=limit, with_meta=False) results = [] failed = [] for rec in recs: symbol = rec.get("symbol") try: ticker = exchange.fetch_ticker(symbol) current_price = float(ticker["last"] or 0) event_time = datetime.now().isoformat() update_latest_price_cache(symbol, current_price, updated_at=event_time, source="paper_trader") result = sync_recommendation(rec, current_price, event_time=event_time) result.update({"symbol": symbol, "rec_id": rec.get("id"), "current_price": current_price}) results.append(result) except Exception as exc: failed.append({"symbol": symbol, "error": str(exc)}) output = { "status": "completed", "processed_count": len(results), "failed_count": len(failed), "failed": failed, "results": results, "summary": get_paper_trading_summary(days=30), "run_time": datetime.now().isoformat(), } print(json.dumps(output, ensure_ascii=False, indent=2, default=str)) return output def main(limit: int = 100): started_at = datetime.now() try: output = run_once(limit=limit) except Exception as exc: finished_at = datetime.now() log_cron_run( job_name="模拟交易", script_name="paper_trader.py", run_status="error", result_status="exception", started_at=started_at.isoformat(), finished_at=finished_at.isoformat(), duration_ms=int((finished_at - started_at).total_seconds() * 1000), summary={}, error_message=str(exc), ) raise finished_at = datetime.now() log_cron_run( job_name="模拟交易", script_name="paper_trader.py", run_status="success", result_status=output.get("status", "completed"), started_at=started_at.isoformat(), finished_at=finished_at.isoformat(), duration_ms=int((finished_at - started_at).total_seconds() * 1000), summary={ "processed_count": output.get("processed_count", 0), "failed_count": output.get("failed_count", 0), "open_count": output.get("summary", {}).get("open_count", 0), "closed_count": output.get("summary", {}).get("closed_count", 0), }, error_message="", ) return output