alphax/app/cli.py
2026-06-07 20:58:35 +08:00

157 lines
7.7 KiB
Python

"""Unified CLI entrypoint for AlphaX Agent jobs."""
import argparse
import sys
from app.services import altcoin_confirm, altcoin_screener, event_driven_screener, live_trading_smoke, market_overview, paper_trader, price_streamer, price_tracker, review_engine, sentiment_monitor
def build_parser():
parser = argparse.ArgumentParser(description="AlphaX Agent unified CLI")
subparsers = parser.add_subparsers(dest="command", required=True)
screener = subparsers.add_parser("screener", help="运行粗筛/细筛")
screener.add_argument("--compact", action="store_true", help="输出紧凑 JSON")
confirm = subparsers.add_parser("confirm", help="运行确认流程")
confirm.add_argument("--compact", action="store_true", help="输出紧凑摘要 JSON")
confirm.add_argument("--verbose", action="store_true", help="输出完整确认上下文,仅排查时使用")
confirm.add_argument("--limit", type=int, default=None, help="本轮最多确认的候选数量")
confirm.add_argument("--max-seconds", type=int, default=None, help="本轮最大运行秒数")
tracker = subparsers.add_parser("tracker", help="运行价格跟踪")
paper = subparsers.add_parser("paper-trader", help="运行模拟交易账本同步")
paper.add_argument("--limit", type=int, default=100, help="本轮最多处理的可执行推荐数量")
subparsers.add_parser("price-streamer", help="运行 websocket 实时价格流")
subparsers.add_parser("market", help="采集全市场快照")
review = subparsers.add_parser("review", help="运行复盘")
review.add_argument("--compact", action="store_true", help="输出紧凑 JSON")
review.add_argument("--no-push", action="store_true", help="只运行复盘,不发飞书")
event = subparsers.add_parser("event", help="运行事件驱动筛选")
event.add_argument("--no-process-existing", action="store_true", help="只处理本轮新采集事件")
event.add_argument("--limit", type=int, default=None, help="单轮最多处理事件数")
event.add_argument("--max-seconds", type=int, default=None, help="单轮最大运行秒数")
sentiment = subparsers.add_parser("sentiment", help="运行舆情任务")
sentiment.add_argument("--collect", action="store_true", help="采集并存储")
sentiment.add_argument("--check", action="store_true", help="输出舆情异动")
sentiment.add_argument("--scores", action="store_true", help="输出评分")
llm = subparsers.add_parser("llm-insights", help="异步生成 LLM 缓存解释")
llm.add_argument("--scope", choices=["recommendations", "sentiment", "sentiment-events", "review"], default="recommendations")
llm.add_argument("--limit", type=int, default=30)
live_smoke = subparsers.add_parser("live-trading-smoke", help="运行 Binance 合约接口 smoke test")
live_smoke.add_argument("--account-id", type=int, required=True, help="live_trade_accounts.id")
live_smoke.add_argument("--symbol", default="BTC/USDT", help="测试交易对")
live_smoke.add_argument("--notional-usdt", type=float, default=10.0, help="测试名义金额,默认 10U")
live_smoke.add_argument("--leverage", type=float, default=1.0, help="测试杠杆,默认 1x")
repair_strategy = subparsers.add_parser("repair-strategy-direction", help="修复策略方向与交易方向不一致的推荐数据")
repair_strategy.add_argument("--limit", type=int, default=500, help="最多扫描的 recommendation 数量")
repair_strategy.add_argument("--dry-run", action="store_true", help="只预览不写库")
reset_strategy = subparsers.add_parser("reset-strategy-samples", help="清理已下线策略的历史样本")
reset_strategy.add_argument("--scope", choices=["legacy-strategies"], required=True)
reset_strategy.add_argument("--confirm", required=True, help="必须传 CLEAN_LEGACY_STRATEGY_SAMPLES")
reset_strategy.add_argument("--no-backup", action="store_true", help="跳过 pg_dump 备份,仅用于测试")
return parser
def main():
parser = build_parser()
args = parser.parse_args()
if args.command == "screener":
return altcoin_screener.main(compact=args.compact)
if args.command == "confirm":
return altcoin_confirm.main(compact=args.compact, verbose=args.verbose, limit=args.limit, max_seconds=args.max_seconds)
if args.command == "tracker":
return price_tracker.main()
if args.command == "paper-trader":
return paper_trader.main(limit=args.limit)
if args.command == "price-streamer":
return price_streamer.main()
if args.command == "market":
result = market_overview.collect_market_snapshot()
print(sentiment_monitor.json.dumps(result, ensure_ascii=False, indent=2))
return result
if args.command == "review":
return review_engine.run_review(push_enabled=not args.no_push, compact=args.compact)
if args.command == "event":
result = event_driven_screener.run_once(
process_existing=not args.no_process_existing,
limit=args.limit,
max_seconds=args.max_seconds,
)
print(event_driven_screener.json.dumps(result, ensure_ascii=False, indent=2, default=str))
return result
if args.command == "sentiment":
if args.collect:
result = sentiment_monitor.collect_and_store()
print(sentiment_monitor.json.dumps(result, ensure_ascii=False))
return result
if args.scores:
result = sentiment_monitor.get_sentiment_scores()
print(sentiment_monitor.json.dumps(result, ensure_ascii=False, indent=2))
return result
holdings = sentiment_monitor.get_active_holdings()
alerts = sentiment_monitor.get_sentiment_alert(holdings=holdings)
if args.check:
print(sentiment_monitor.json.dumps(alerts, ensure_ascii=False, indent=2))
return alerts
result = {
"alerts": alerts,
"sentiment_scores": sentiment_monitor.get_sentiment_scores(),
"holdings_count": len(holdings),
"check_time": sentiment_monitor.datetime.now().isoformat(),
}
print(sentiment_monitor.json.dumps(result, ensure_ascii=False, indent=2))
return result
if args.command == "llm-insights":
from app.services import llm_insights
result = llm_insights.run(scope=args.scope, limit=args.limit)
print(sentiment_monitor.json.dumps(result, ensure_ascii=False, indent=2))
return result
if args.command == "live-trading-smoke":
return live_trading_smoke.main(
account_id=args.account_id,
symbol=args.symbol,
notional_usdt=args.notional_usdt,
leverage=args.leverage,
)
if args.command == "repair-strategy-direction":
from app.db.strategy_direction_repair import repair_strategy_direction_mismatches
result = repair_strategy_direction_mismatches(limit=args.limit, dry_run=args.dry_run)
print(sentiment_monitor.json.dumps(result, ensure_ascii=False, indent=2))
return result
if args.command == "reset-strategy-samples":
from app.db.strategy_sample_cleanup import cleanup_legacy_strategy_samples
result = cleanup_legacy_strategy_samples(confirm=args.confirm, create_backup=not args.no_backup)
print(sentiment_monitor.json.dumps(result, ensure_ascii=False, indent=2))
return result
parser.error(f"unknown command: {args.command}")
if __name__ == "__main__":
try:
main()
except Exception as exc:
try:
from app.db.system_logs import record_exception
command = " ".join(sys.argv[1:]) or "unknown"
record_exception(exc, source="cli", context={"argv": sys.argv, "command": command})
except Exception:
pass
raise