508 lines
22 KiB
Python
508 lines
22 KiB
Python
"""
|
||
山寨币爆发监控系统 v11.7.9 — 价格跟踪+三级分级跟踪止盈+趋势反转(纯前瞻版)
|
||
趋势反转检测:1H连续阴动K、量价背离、空头加速(替代MACD/RSI)
|
||
v1.7.9: 跟踪止盈按盈利分三级 — 防震(3×ATR) → 锁利(2×ATR) → 紧贴(1.2×ATR)
|
||
止盈止损跟踪、动态买入/卖出指引
|
||
"""
|
||
|
||
import sys, os, shutil
|
||
|
||
# ⚠️ 安全机制:启动时强制清__pycache__,防止旧版字节码残留
|
||
for cache_dir in [
|
||
os.path.join(os.path.dirname(__file__), "__pycache__"),
|
||
os.path.join(os.path.dirname(__file__), "..", "__pycache__"),
|
||
]:
|
||
if os.path.exists(cache_dir):
|
||
shutil.rmtree(cache_dir, ignore_errors=True)
|
||
|
||
import ccxt
|
||
import pandas as pd
|
||
import json
|
||
import sys
|
||
import os
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, os.path.dirname(__file__))
|
||
from app.db.altcoin_db import (
|
||
init_db, get_active_recommendations, update_recommendation_tracking,
|
||
expire_old_recommendations, get_stats, update_recommendation_action_status,
|
||
apply_recommendation_state_transition, log_cron_run,
|
||
update_latest_price_cache,
|
||
)
|
||
from app.core.pa_engine import (
|
||
calc_atr, full_pa_analysis, detect_trend_exhaustion,
|
||
analyze_entry_point,
|
||
)
|
||
from app.integrations.push_orchestrator import push_trade_action_update
|
||
from app.config.config_loader import load_rules
|
||
from app.core.opportunity_lifecycle import apply_entry_quality_gate
|
||
|
||
exchange = ccxt.binance({"enableRateLimit": True})
|
||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||
|
||
PROVISIONAL_BUY_SIGNAL_MARKERS = (
|
||
"可即刻入场",
|
||
"当前价接近回踩目标",
|
||
"回踩确认完毕",
|
||
)
|
||
|
||
|
||
def fetch_klines(symbol, timeframe, limit=200):
|
||
try:
|
||
ohlcv = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
|
||
df = pd.DataFrame(ohlcv, columns=["timestamp", "open", "high", "low", "close", "volume"])
|
||
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
|
||
return df
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def _format_tracking_price(price):
|
||
try:
|
||
price = float(price)
|
||
except Exception:
|
||
return ""
|
||
if price <= 0:
|
||
return ""
|
||
if price >= 1:
|
||
return f"${price:.3f}"
|
||
if price >= 0.01:
|
||
return f"${price:.4f}"
|
||
if price >= 0.0001:
|
||
return f"${price:.6f}"
|
||
return f"${price:.8f}"
|
||
|
||
|
||
def reconcile_buy_signals_after_gate(buy_signals, final_action, gated_plan, gate_reasons):
|
||
"""买点质量闸门降级后,移除临时买入文案,保留最终终端指引。"""
|
||
if final_action == "可即刻买入" or not gate_reasons:
|
||
return list(buy_signals or [])
|
||
|
||
filtered = [
|
||
str(signal)
|
||
for signal in (buy_signals or [])
|
||
if not any(marker in str(signal) for marker in PROVISIONAL_BUY_SIGNAL_MARKERS)
|
||
]
|
||
first_reason = str(gate_reasons[0])
|
||
if final_action == "等回踩":
|
||
target_price = (
|
||
(gated_plan or {}).get("rr_target_entry")
|
||
or (gated_plan or {}).get("entry_price")
|
||
or (gated_plan or {}).get("wait_price")
|
||
)
|
||
price_text = _format_tracking_price(target_price)
|
||
if price_text:
|
||
filtered.append(f"🟡 现价不买,等待回踩至{price_text}附近;{first_reason}")
|
||
else:
|
||
filtered.append(f"🟡 现价不买,继续等待回踩;{first_reason}")
|
||
elif final_action == "观察":
|
||
filtered.append(f"🟡 买点未达标,保持观察;{first_reason}")
|
||
return filtered
|
||
|
||
|
||
def analyze_tracking_signals(symbol, rec, current_price):
|
||
"""
|
||
对active推荐做动态跟踪分析
|
||
返回: {"action_status": str, "sell_signals": [...], "buy_signals": [...],
|
||
"exhaustion": {...}, "entry_update": {...}}
|
||
"""
|
||
sell_signals = []
|
||
buy_signals = []
|
||
action_status = "持有" # 默认状态
|
||
|
||
entry_price = rec["entry_price"]
|
||
stop_loss = rec.get("stop_loss", entry_price * 0.95)
|
||
tp1 = rec.get("tp1", entry_price * 1.03)
|
||
tp2 = rec.get("tp2", entry_price * 1.05)
|
||
entry_plan = rec.get("entry_plan") or {}
|
||
|
||
# ---- 拉取1H数据做趋势分析 ----
|
||
h1_df = fetch_klines(symbol, "1h", limit=100)
|
||
m15_df = fetch_klines(symbol, "15m", limit=100)
|
||
h4_df = fetch_klines(symbol, "4h", limit=100)
|
||
|
||
atr_1h = calc_atr(h1_df, 14) if h1_df is not None else 0
|
||
|
||
# ---- 趋势衰减检测 ----
|
||
exhaustion = {}
|
||
if h1_df is not None and atr_1h > 0:
|
||
exhaustion = detect_trend_exhaustion(h1_df, atr_1h)
|
||
if exhaustion.get("exhausted"):
|
||
for es in exhaustion.get("signals", []):
|
||
sell_signals.append(es)
|
||
if exhaustion["severity"] == "high":
|
||
action_status = "衰减"
|
||
elif exhaustion["severity"] == "medium":
|
||
# 中度衰减,还持有但需关注
|
||
sell_signals.append("趋势中度衰减,关注止盈")
|
||
|
||
# ---- 止盈信号检测 ----
|
||
pnl_pct = ((current_price / entry_price) - 1) * 100 if entry_price > 0 else 0
|
||
|
||
# 到达TP1(v1.7.8:TP1保留作为提醒目标)
|
||
if tp1 > 0 and current_price >= tp1:
|
||
sell_signals.append(f"✅ 到达TP1(${tp1:.4f}), 建议止盈50%仓位")
|
||
action_status = "止盈1" # 无条件:TP1到了就是止盈,无论之前什么状态
|
||
|
||
# === v1.7.8 跟踪止盈全面升级 ===
|
||
# 核心改动:
|
||
# ① 激活门槛 5%→3% (抓更多利润)
|
||
# ② ATR乘数 2.0→1.5 (更紧贴行情)
|
||
# ③ 每次tracker运行都重新计算并只升不降 (动态跟随行情逐步抬高)
|
||
# ④ 跟踪止盈触发时无条件覆盖所有状态 (利润保护优先级最高)
|
||
# ⑤ TP2已废除(历史0命中),跟踪止盈是唯一动态止盈方式
|
||
rules = load_rules()
|
||
trail_cfg = rules.get("tracker", {}).get("trailing_stop", {})
|
||
trailing_stop_level = entry_plan.get("trailing_stop_level", 0)
|
||
trailing_stop_activated = False
|
||
trailing_stop_moved = False
|
||
|
||
if trail_cfg.get("enabled", True) and atr_1h > 0 and entry_price > 0:
|
||
activate_pct = trail_cfg.get("activate_pnl_pct", 3)
|
||
|
||
# === v1.7.9 三级分级乘数: 按盈利切换 ===
|
||
tiers = trail_cfg.get("tiers", [])
|
||
trail_atr_mult = 1.5 # 兜底
|
||
tier_label = ""
|
||
if tiers:
|
||
for t in sorted(tiers, key=lambda x: x.get("min_pnl_pct", 0), reverse=True):
|
||
if pnl_pct >= t.get("min_pnl_pct", 0):
|
||
trail_atr_mult = t.get("atr_mult", 1.5)
|
||
tier_label = t.get("label", "")
|
||
break
|
||
|
||
# === 动态跟随:每次运行都重新计算跟踪止盈位 ===
|
||
# 算法: trailing_stop = current_price - atr_mult × ATR_1h
|
||
# 规则: 只升不降 (max(old_level, new_level))
|
||
# 激活条件: pnl_pct ≥ activate_pct (3%)
|
||
if pnl_pct >= activate_pct:
|
||
new_trail = current_price - trail_atr_mult * atr_1h
|
||
min_lock_pct = float(trail_cfg.get("min_lock_profit_pct", 0.5))
|
||
breakeven_buffer_pct = float(trail_cfg.get("breakeven_buffer_pct", min_lock_pct))
|
||
# ATR 对高波动山寨币会很宽。利润保护一旦激活,止盈线至少要高于入场价,
|
||
# 且不能低于原硬止损;否则“移动止盈已激活”实际没有任何保护效果。
|
||
protection_floor = max(
|
||
stop_loss or 0,
|
||
entry_price * (1 + max(min_lock_pct, breakeven_buffer_pct) / 100),
|
||
)
|
||
new_trail = max(new_trail, protection_floor)
|
||
|
||
if trailing_stop_level > 0:
|
||
# 已有跟踪位 → 只上移不下移
|
||
old_trail = trailing_stop_level
|
||
trailing_stop_level = max(trailing_stop_level, new_trail)
|
||
trailing_stop_moved = trailing_stop_level > old_trail + 1e-12
|
||
else:
|
||
# 首次激活
|
||
trailing_stop_level = new_trail
|
||
trailing_stop_activated = True
|
||
tier_info = f" [{tier_label}·{trail_atr_mult}×ATR]" if tier_label else ""
|
||
sell_signals.append(f"🎯 跟踪止盈激活(盈+{pnl_pct:.1f}%≥{activate_pct}%{tier_info}, 保护位${trailing_stop_level:.4f})")
|
||
|
||
# === 触发检查:当前价跌破跟踪止盈位 → 止盈 ===
|
||
# 🔴 v1.7.8: 跟踪止盈触发时无条件覆盖(利润保护优先级最高)
|
||
if trailing_stop_level > 0 and current_price <= trailing_stop_level:
|
||
drop_from_trail = trailing_stop_level - current_price
|
||
sell_signals.append(f"🎯 跟踪止盈触发! 从高位回撤${drop_from_trail:.4f}({drop_from_trail/trailing_stop_level*100:.1f}%)")
|
||
action_status = "跟踪止盈"
|
||
|
||
# 定期报告(即使没触发也显示跟踪位)
|
||
if trailing_stop_level > 0 and current_price > trailing_stop_level and pnl_pct >= activate_pct * 2:
|
||
cushion = (current_price - trailing_stop_level) / current_price * 100
|
||
sell_signals.append(f"📊 跟踪止盈中: 止盈位${trailing_stop_level:.4f}(距现价{cushion:.1f}%)")
|
||
elif pnl_pct >= 1 and trailing_stop_level > 0 and current_price <= trailing_stop_level:
|
||
# 利润回落到1%以内但跟踪位已激活且被击穿 → 保本出
|
||
sell_signals.append(f"🔒 保本止盈: 利润缩至{pnl_pct:.1f}%,跟踪位击穿")
|
||
action_status = "跟踪止盈"
|
||
|
||
# ---- 无TP保护的推荐,涨超15%自动止盈(孤儿推荐保护)----
|
||
# 加速推荐(粗筛/细筛层)没有 TP/SL,price_tracker 无法判断出场点。
|
||
# 涨超15%仍无结论 → 认怂落袋,避免永续浮盈不兑现。
|
||
if tp1 == 0 and pnl_pct >= 15:
|
||
sell_signals.append(f"✅ 无TP保护自动止盈(涨+{pnl_pct:.1f}%≥15%,落袋为安)")
|
||
action_status = "止盈1"
|
||
|
||
# ---- 止损接近警告 ----
|
||
if stop_loss > 0:
|
||
loss_pct = ((current_price / stop_loss) - 1) * 100
|
||
if loss_pct < 3: # 当前价离止损不到3%
|
||
sell_signals.append(f"⚠️ 接近止损!当前${current_price:.4f}离止损${stop_loss:.4f}仅{loss_pct:.1f}%")
|
||
if current_price <= stop_loss:
|
||
sell_signals.append(f"🔴 已触发止损!${current_price:.4f}≤${stop_loss:.4f}")
|
||
action_status = "止损"
|
||
|
||
# ---- 趋势反转信号(PA行为检测,替代MACD) ----
|
||
if h1_df is not None and len(h1_df) >= 30 and atr_1h > 0:
|
||
pa_1h = full_pa_analysis(h1_df, "1h")
|
||
pa_1h_candles = pa_1h.get("candles_class", [])
|
||
recent_candles = pa_1h_candles[-6:] if len(pa_1h_candles) >= 6 else pa_1h_candles
|
||
|
||
# 1H连续阴动K → 趋势反转
|
||
dy_bear_count = sum(1 for c in recent_candles if c["type"] == "dynamic" and c["direction"] == -1)
|
||
if dy_bear_count >= 3:
|
||
sell_signals.append(f"🔴 1H连续{dy_bear_count}根阴动K(趋势反转)")
|
||
if action_status == "持有":
|
||
action_status = "反转"
|
||
|
||
# 1H量价背离:放量但阴线(多头出货)
|
||
avg_vol = float(h1_df["volume"].rolling(20).mean().iloc[-1])
|
||
recent_3 = h1_df.tail(3)
|
||
high_vol_bear = 0
|
||
for _, row in recent_3.iterrows():
|
||
vol_r = row["volume"] / avg_vol if avg_vol > 0 else 0
|
||
if vol_r >= 2 and row["close"] < row["open"]:
|
||
high_vol_bear += 1
|
||
if high_vol_bear >= 2:
|
||
sell_signals.append("🔴 1H放量阴线×2(多头出货)")
|
||
|
||
# 1H连续K空头加速
|
||
cont_k = pa_1h.get("continuous_k", [])
|
||
for ck in cont_k:
|
||
if ck["type"] == "bearish_continue" and ck["length"] >= 3:
|
||
sell_signals.append(f"🔴 1H连续{ck['length']}K空头加速")
|
||
if action_status == "持有":
|
||
action_status = "反转"
|
||
|
||
# === 时间衰减 (v1.6.9) ===
|
||
# 持仓>24h仍无盈利→降级衰减
|
||
decay_cfg = rules.get("tracker", {}).get("time_decay", {})
|
||
if decay_cfg.get("enabled", True):
|
||
decay_hours = decay_cfg.get("decay_hours", 24)
|
||
rec_time = rec.get("rec_time", "")
|
||
if rec_time:
|
||
try:
|
||
t_rec = datetime.fromisoformat(rec_time)
|
||
hours_held = (datetime.now() - t_rec).total_seconds() / 3600
|
||
if hours_held > decay_hours and pnl_pct <= 0 and action_status == "持有":
|
||
sell_signals.append(f"⏰ 持仓{hours_held:.0f}h无盈利,降级衰减")
|
||
action_status = "衰减"
|
||
except Exception:
|
||
pass
|
||
|
||
# ---- 动态买入指引(只允许在未触发任何止盈/止损/退出信号时执行) ----
|
||
entry_update = {}
|
||
current_action = entry_plan.get("entry_action", "")
|
||
|
||
# 如果推荐状态是"等回踩",检查是否到了回踩价位。
|
||
# 注意:一旦本轮 action_status 已经是止盈/止损/跟踪止盈/反转/衰减,就绝不能再覆盖成“可即刻买入”。
|
||
if action_status == "持有" and current_action in ("等回踩", "🟡等回踩") and h4_df is not None and atr_1h > 0:
|
||
pa_4h = full_pa_analysis(h4_df, "4h")
|
||
h4_zones = pa_4h.get("zones", [])
|
||
direction = 1 # 做多方向
|
||
|
||
# 重新做15min入场点分析
|
||
if m15_df is not None and len(m15_df) >= 20:
|
||
entry_result = analyze_entry_point(
|
||
h1_df=h1_df, m15_df=m15_df, atr_1h=atr_1h,
|
||
zones_4h=h4_zones, direction=direction,
|
||
)
|
||
new_action = entry_result.get("action", "等回踩")
|
||
|
||
if new_action == "即刻买入":
|
||
buy_signals.append(f"🟢 回踩确认完毕!可即刻入场(15min动K确认)")
|
||
action_status = "可即刻买入"
|
||
elif new_action == "等回踩":
|
||
wait_price = entry_result.get("wait_price", 0)
|
||
if wait_price > 0:
|
||
# 检查当前价是否接近回踩目标
|
||
dist_pct = ((current_price / wait_price) - 1) * 100
|
||
if abs(dist_pct) < 2:
|
||
buy_signals.append(f"🟢 当前价接近回踩目标!${current_price:.4f}≈${wait_price:.4f}")
|
||
action_status = "可即刻买入"
|
||
|
||
entry_update = {
|
||
"new_action": new_action,
|
||
"reason": entry_result.get("reason", ""),
|
||
"wait_price": entry_result.get("wait_price", 0),
|
||
}
|
||
|
||
action_status, gated_plan, gate_reasons = apply_entry_quality_gate(
|
||
action_status=action_status,
|
||
entry_plan=entry_plan,
|
||
signals=rec.get("signals"),
|
||
current_price=current_price,
|
||
market_context=rec.get("market_context") or {},
|
||
derivatives_context=rec.get("derivatives_context") or {},
|
||
sector_context=rec.get("sector_context") or {},
|
||
)
|
||
if gate_reasons:
|
||
buy_signals = reconcile_buy_signals_after_gate(
|
||
buy_signals,
|
||
action_status,
|
||
gated_plan,
|
||
gate_reasons,
|
||
)
|
||
buy_signals.append("⚠️ 买点质量闸门: " + ";".join(gate_reasons[:3]))
|
||
entry_plan.update(gated_plan)
|
||
|
||
return {
|
||
"action_status": action_status,
|
||
"sell_signals": sell_signals,
|
||
"buy_signals": buy_signals,
|
||
"exhaustion": exhaustion,
|
||
"entry_update": entry_update,
|
||
"pnl_pct": round(pnl_pct, 2),
|
||
"trailing_stop_level": trailing_stop_level,
|
||
"trailing_stop_activated": trailing_stop_activated,
|
||
"trailing_stop_moved": trailing_stop_moved,
|
||
}
|
||
|
||
|
||
def track_prices():
|
||
"""拉取所有active推荐币的实时价格,更新盈亏 + 动态跟踪信号"""
|
||
recs = get_active_recommendations()
|
||
if not recs:
|
||
output = {
|
||
"status": "no_active",
|
||
"message": "无active推荐需要跟踪",
|
||
"stats": get_stats(),
|
||
"track_time": datetime.now().isoformat(),
|
||
}
|
||
print(json.dumps(output, ensure_ascii=False))
|
||
return output
|
||
|
||
results = []
|
||
failed_symbols = []
|
||
for rec in recs:
|
||
symbol = rec["symbol"]
|
||
try:
|
||
ticker = exchange.fetch_ticker(symbol)
|
||
current_price = ticker["last"]
|
||
|
||
# 最新价格缓存:看板读取小表 latest_price_cache,不再依赖 price_tracking 高频流水表
|
||
update_latest_price_cache(symbol, current_price, source="tracker")
|
||
|
||
# 基础盈亏跟踪
|
||
track_result = update_recommendation_tracking(rec["id"], current_price)
|
||
|
||
# PA增强:动态跟踪信号分析
|
||
tracking_signals = analyze_tracking_signals(symbol, rec, current_price)
|
||
|
||
# === v1.7.8 跟踪止盈DB回写 ===
|
||
# 每次tracker运行都写DB,支持动态跟随行情逐步抬高跟踪位
|
||
trail_level = tracking_signals.get("trailing_stop_level", 0)
|
||
if trail_level > 0:
|
||
entry_plan = rec.get("entry_plan") or {}
|
||
old_trail = entry_plan.get("trailing_stop_level", 0)
|
||
# v1.7.8: 只要跟踪位有变化就写DB(上移时必变;首次激活也写)
|
||
if abs(trail_level - old_trail) > 0.000001:
|
||
entry_plan["trailing_stop_level"] = trail_level
|
||
from app.db.schema import get_conn as _get_conn
|
||
_c2 = _get_conn()
|
||
_c2.execute("UPDATE recommendation SET entry_plan_json=%s WHERE id=%s",
|
||
(json.dumps(entry_plan, ensure_ascii=False), rec["id"]))
|
||
_c2.commit()
|
||
_c2.close()
|
||
|
||
# 主链路状态迁移:tracker 只提交“候选状态 + 当前价”,最终状态由 DB 主链路统一落库。
|
||
# 飞书推送只能消费主链路返回的最终状态,不能再自行判断。
|
||
terminal_action = {
|
||
"hit_tp2": "止盈2",
|
||
"stopped_out": "止损",
|
||
}.get(track_result.get("status"))
|
||
requested_action = terminal_action or tracking_signals["action_status"]
|
||
state_decision = apply_recommendation_state_transition(
|
||
rec["id"],
|
||
requested_action=requested_action,
|
||
current_price=current_price,
|
||
event_time=datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
|
||
signals=tracking_signals.get("sell_signals", []) + tracking_signals.get("buy_signals", []),
|
||
)
|
||
final_action = state_decision.get("action_status", requested_action)
|
||
push_trade_action_update(symbol, rec["id"], state_decision, final_action, push_type="entry")
|
||
if tracking_signals.get("trailing_stop_activated"):
|
||
activation_decision = dict(state_decision)
|
||
activation_decision["push_required"] = True
|
||
activation_decision["push_signals"] = tracking_signals.get("sell_signals", [])
|
||
push_trade_action_update(
|
||
symbol,
|
||
rec["id"],
|
||
activation_decision,
|
||
"移动止盈保护",
|
||
push_type="profit_protection",
|
||
)
|
||
|
||
results.append({
|
||
"symbol": symbol,
|
||
"rec_id": rec["id"],
|
||
"entry_price": rec["entry_price"],
|
||
"current_price": current_price,
|
||
"pnl_pct": tracking_signals["pnl_pct"],
|
||
"status": track_result["status"],
|
||
"action_status": tracking_signals["action_status"],
|
||
"sell_signals": tracking_signals["sell_signals"],
|
||
"buy_signals": tracking_signals["buy_signals"],
|
||
"exhaustion_severity": tracking_signals.get("exhaustion", {}).get("severity", "low"),
|
||
})
|
||
print(f" {symbol}: 入场${rec['entry_price']} → 现在${current_price} "
|
||
f"盈亏{tracking_signals['pnl_pct']}% 状态={track_result['status']} "
|
||
f"操作={tracking_signals['action_status']}")
|
||
|
||
except Exception as e:
|
||
failed_symbols.append({"symbol": symbol, "error": str(e)})
|
||
print(f" {symbol}: 获取价格失败 - {e}")
|
||
|
||
# 过期检查
|
||
expire_old_recommendations()
|
||
|
||
output = {
|
||
"status": "tracked",
|
||
"tracked_count": len(results),
|
||
"failed_count": len(failed_symbols),
|
||
"failed_symbols": failed_symbols,
|
||
"results": results,
|
||
"stats": get_stats(),
|
||
"track_time": datetime.now().isoformat(),
|
||
}
|
||
print(json.dumps(output, ensure_ascii=False, indent=2))
|
||
return output
|
||
|
||
|
||
def main():
|
||
started_at = datetime.now()
|
||
try:
|
||
init_db()
|
||
output = track_prices()
|
||
except Exception as e:
|
||
finished_at = datetime.now()
|
||
log_cron_run(
|
||
job_name="跟踪",
|
||
script_name="price_tracker.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(e),
|
||
)
|
||
raise
|
||
else:
|
||
finished_at = datetime.now()
|
||
summary = {
|
||
"tracked_count": output.get("tracked_count", 0),
|
||
"failed_count": output.get("failed_count", 0),
|
||
"active_count": output.get("stats", {}).get("active_count", 0),
|
||
}
|
||
log_cron_run(
|
||
job_name="跟踪",
|
||
script_name="price_tracker.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=summary,
|
||
error_message="",
|
||
)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description="AlphaX Agent | Crypto 价格跟踪任务")
|
||
parser.add_argument("--once", action="store_true", default=True, help="执行单轮跟踪并输出结果")
|
||
parser.parse_args()
|
||
main()
|