update
This commit is contained in:
parent
4aed5f4c9a
commit
c7786946af
@ -1,4 +1,4 @@
|
||||
# AlphaX Docker 环境变量示例
|
||||
# AlphaX Agent | Crypto Docker 环境变量示例
|
||||
# 复制为 .env 后再按需填写:cp .env.example .env
|
||||
|
||||
# Web 服务端口由 docker-compose 映射为宿主机 8191 -> 容器 8190。
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# AlphaX Docker 化副本
|
||||
# AlphaX Agent | Crypto Docker 化副本
|
||||
|
||||
这是从当前运行中的 `/home/ubuntu/quant_monitor/altcoin` 复制出来的独立 Docker 化副本,目录:
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
## 重要原则
|
||||
|
||||
- 这个目录是副本,不影响当前正在运行的 AlphaX。
|
||||
- 这个目录是副本,不影响当前正在运行的 AlphaX Agent | Crypto。
|
||||
- 默认 `docker-compose.yml` 将 Web 暴露到宿主机 `8191`,避免占用当前线上 `8190`。
|
||||
- 调度器默认 `ALPHAX_SCHEDULER_DRY_RUN=1`,第一次启动不会真的跑筛选/确认/跟踪任务。
|
||||
- SQLite 数据挂载在 `./data/altcoin_monitor.db`,容器内路径为 `/app/data/altcoin_monitor.db`。
|
||||
|
||||
@ -1 +1 @@
|
||||
"""Application package for AlphaX."""
|
||||
"""Application package for AlphaX Agent | Crypto."""
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""Unified CLI entrypoint for AlphaX jobs."""
|
||||
"""Unified CLI entrypoint for AlphaX Agent | Crypto jobs."""
|
||||
|
||||
import argparse
|
||||
|
||||
@ -6,7 +6,7 @@ from app.services import altcoin_confirm, altcoin_screener, event_driven_screene
|
||||
|
||||
|
||||
def build_parser():
|
||||
parser = argparse.ArgumentParser(description="AlphaX unified CLI")
|
||||
parser = argparse.ArgumentParser(description="AlphaX Agent | Crypto unified CLI")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
screener = subparsers.add_parser("screener", help="运行粗筛/细筛")
|
||||
|
||||
@ -26,7 +26,7 @@ DEFAULT_ENTRY_GATE = {
|
||||
|
||||
|
||||
|
||||
# AlphaX 统一状态机:所有展示/统计/推送都应消费这些派生状态,不再各自解释 status/action_status。
|
||||
# AlphaX Agent | Crypto 统一状态机:所有展示/统计/推送都应消费这些派生状态,不再各自解释 status/action_status。
|
||||
TERMINAL_STATUSES = {"hit_tp1", "hit_tp2", "stopped_out", "expired", "archived", "invalid"}
|
||||
EXIT_ACTIONS = {"止损", "衰减", "反转", "放弃"}
|
||||
PROFIT_ACTIONS = {"止盈1", "止盈2", "跟踪止盈"}
|
||||
|
||||
@ -1024,18 +1024,31 @@ def _pipeline_summary_for_run(run, related):
|
||||
}
|
||||
|
||||
|
||||
def get_pipeline_runs(limit=30, hours=24):
|
||||
def get_pipeline_runs(limit=30, hours=24, offset=0):
|
||||
"""按粗筛任务批次聚合推荐链路日志。"""
|
||||
try:
|
||||
limit = max(1, min(int(limit or 30), 100))
|
||||
except Exception:
|
||||
limit = 30
|
||||
try:
|
||||
offset = max(0, int(offset or 0))
|
||||
except Exception:
|
||||
offset = 0
|
||||
try:
|
||||
hours = max(1, min(int(hours or 24), 24 * 30))
|
||||
except Exception:
|
||||
hours = 24
|
||||
|
||||
conn = get_conn()
|
||||
total_count = conn.execute(
|
||||
"""
|
||||
SELECT COUNT(*)
|
||||
FROM cron_run_log
|
||||
WHERE job_name = '粗筛'
|
||||
AND julianday(?) - julianday(started_at) <= ?
|
||||
""",
|
||||
(datetime.now().isoformat(), hours / 24.0),
|
||||
).fetchone()[0]
|
||||
run_rows = conn.execute(
|
||||
"""
|
||||
SELECT * FROM cron_run_log
|
||||
@ -1043,8 +1056,9 @@ def get_pipeline_runs(limit=30, hours=24):
|
||||
AND julianday(?) - julianday(started_at) <= ?
|
||||
ORDER BY started_at DESC, id DESC
|
||||
LIMIT ?
|
||||
OFFSET ?
|
||||
""",
|
||||
(datetime.now().isoformat(), hours / 24.0, limit),
|
||||
(datetime.now().isoformat(), hours / 24.0, limit, offset),
|
||||
).fetchall()
|
||||
|
||||
runs = []
|
||||
@ -1069,7 +1083,21 @@ def get_pipeline_runs(limit=30, hours=24):
|
||||
}
|
||||
kpi["recommendation_rate"] = round(kpi["recommendations"] / kpi["fine_qualified"] * 100, 1) if kpi["fine_qualified"] else 0
|
||||
kpi["performance_hit_rate"] = round(kpi["perf_success"] / (kpi["perf_success"] + kpi["perf_failed"]) * 100, 1) if (kpi["perf_success"] + kpi["perf_failed"]) else 0
|
||||
return {"kpi": kpi, "runs": runs}
|
||||
total_pages = (total_count + limit - 1) // limit if total_count else 0
|
||||
current_page = (offset // limit) + 1 if total_count else 0
|
||||
return {
|
||||
"kpi": kpi,
|
||||
"runs": runs,
|
||||
"pagination": {
|
||||
"hours": hours,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"total_count": total_count,
|
||||
"total_pages": total_pages,
|
||||
"page": current_page,
|
||||
"has_more": offset + limit < total_count,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_pipeline_run_detail(run_id):
|
||||
|
||||
@ -47,13 +47,13 @@ def send_verification_email(to_email: str, code: str) -> bool:
|
||||
sender = os.getenv("ASTOCK_SMTP_SENDER", username).strip()
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = "AlphaX 邮箱验证码"
|
||||
msg["Subject"] = "AlphaX Agent | Crypto 邮箱验证码"
|
||||
msg["From"] = sender
|
||||
msg["To"] = to_email
|
||||
msg.set_content(
|
||||
f"AlphaX 邮箱验证码:{code}\n\n"
|
||||
f"AlphaX Agent | Crypto 邮箱验证码:{code}\n\n"
|
||||
f"AI Market Intelligence.\n"
|
||||
f"验证码 {VERIFY_CODE_MINUTES} 分钟内有效,用于完成 AlphaX 注册或登录验证。\n"
|
||||
f"验证码 {VERIFY_CODE_MINUTES} 分钟内有效,用于完成 AlphaX Agent | Crypto 注册或登录验证。\n"
|
||||
f"如果不是你本人操作,请忽略本邮件。"
|
||||
)
|
||||
msg.add_alternative(
|
||||
@ -65,12 +65,12 @@ def send_verification_email(to_email: str, code: str) -> bool:
|
||||
<div style="background:#ffffff;border:1px solid #ece7d8;border-radius:22px;overflow:hidden;box-shadow:0 18px 48px rgba(28,28,30,.08);">
|
||||
<div style="padding:26px 28px 18px;background:linear-gradient(135deg,#1c1c1e 0%,#2d2d32 52%,#4262ff 100%);color:#ffffff;">
|
||||
<div style="display:inline-block;width:34px;height:34px;line-height:34px;text-align:center;border-radius:10px;background:#ffd02f;color:#1c1c1e;font-weight:900;font-size:18px;margin-bottom:14px;">A</div>
|
||||
<div style="font-size:24px;font-weight:850;letter-spacing:-.02em;">AlphaX</div>
|
||||
<div style="font-size:24px;font-weight:850;letter-spacing:0;">AlphaX Agent | Crypto</div>
|
||||
<div style="font-size:13px;opacity:.78;margin-top:4px;letter-spacing:.02em;">AI Market Intelligence.</div>
|
||||
</div>
|
||||
<div style="padding:28px;">
|
||||
<div style="font-size:18px;font-weight:800;margin-bottom:8px;">邮箱验证码</div>
|
||||
<p style="margin:0 0 18px;color:#5f626b;font-size:14px;line-height:1.75;">请使用下面的验证码完成 AlphaX 注册或登录验证。验证码 {VERIFY_CODE_MINUTES} 分钟内有效。</p>
|
||||
<p style="margin:0 0 18px;color:#5f626b;font-size:14px;line-height:1.75;">请使用下面的验证码完成 AlphaX Agent | Crypto 注册或登录验证。验证码 {VERIFY_CODE_MINUTES} 分钟内有效。</p>
|
||||
<div style="margin:22px 0;padding:20px 18px;border-radius:18px;background:#fff8d8;border:1px solid #f2de82;text-align:center;">
|
||||
<div style="font-size:13px;color:#6d642b;margin-bottom:8px;">你的验证码</div>
|
||||
<div style="font-size:34px;font-weight:900;letter-spacing:8px;color:#1c1c1e;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;">{code}</div>
|
||||
@ -78,7 +78,7 @@ def send_verification_email(to_email: str, code: str) -> bool:
|
||||
<p style="margin:0;color:#7a7d86;font-size:13px;line-height:1.7;">如果不是你本人操作,请忽略本邮件。为保护账号安全,请不要把验证码转发给任何人。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:center;color:#a0a3ad;font-size:12px;margin-top:18px;">© 2026 AlphaX · AI 驱动的 Crypto 市场情报系统</div>
|
||||
<div style="text-align:center;color:#a0a3ad;font-size:12px;margin-top:18px;">© 2026 AlphaX Agent | Crypto · AI 驱动的 Crypto 市场情报系统</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1362,7 +1362,7 @@ def main(compact: bool = False):
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="AlphaX 爆发确认主流程")
|
||||
parser = argparse.ArgumentParser(description="AlphaX Agent | Crypto 爆发确认主流程")
|
||||
parser.add_argument("--compact", action="store_true", help="输出紧凑 JSON,便于脚本消费")
|
||||
args = parser.parse_args()
|
||||
main(compact=args.compact)
|
||||
|
||||
@ -1365,7 +1365,7 @@ def main(compact: bool = False):
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="AlphaX 粗筛/细筛主流程")
|
||||
parser = argparse.ArgumentParser(description="AlphaX Agent | Crypto 粗筛/细筛主流程")
|
||||
parser.add_argument("--compact", action="store_true", help="输出紧凑 JSON,便于脚本消费")
|
||||
args = parser.parse_args()
|
||||
main(compact=args.compact)
|
||||
|
||||
@ -474,7 +474,7 @@ def main():
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="AlphaX 价格跟踪任务")
|
||||
parser = argparse.ArgumentParser(description="AlphaX Agent | Crypto 价格跟踪任务")
|
||||
parser.add_argument("--once", action="store_true", default=True, help="执行单轮跟踪并输出结果")
|
||||
parser.parse_args()
|
||||
main()
|
||||
|
||||
@ -1586,7 +1586,7 @@ def run_review(push_enabled: bool = True, compact: bool = False):
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="AlphaX 复盘引擎")
|
||||
parser = argparse.ArgumentParser(description="AlphaX Agent | Crypto 复盘引擎")
|
||||
parser.add_argument("--no-push", action="store_true", help="只运行复盘,不发送飞书通知")
|
||||
parser.add_argument("--compact", action="store_true", help="输出紧凑 JSON,便于脚本消费")
|
||||
args = parser.parse_args()
|
||||
|
||||
@ -154,9 +154,9 @@ async def api_cron_summary(hours: int = 24, altcoin_session: str = Cookie(defaul
|
||||
|
||||
|
||||
@router.get("/api/pipeline/runs")
|
||||
async def api_pipeline_runs(limit: int = 30, hours: int = 24, altcoin_session: str = Cookie(default="")):
|
||||
async def api_pipeline_runs(limit: int = 30, hours: int = 24, offset: int = 0, altcoin_session: str = Cookie(default="")):
|
||||
require_api_user_with_subscription(altcoin_session)
|
||||
return get_pipeline_runs(limit=limit, hours=hours)
|
||||
return get_pipeline_runs(limit=limit, hours=hours, offset=offset)
|
||||
|
||||
|
||||
@router.get("/api/pipeline/runs/{run_id}")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
"""
|
||||
FastAPI application assembly for the AlphaX web surface.
|
||||
FastAPI application assembly for the AlphaX Agent | Crypto web surface.
|
||||
"""
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
@ -35,7 +35,7 @@ async def lifespan(app: FastAPI):
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(title="山寨币爆发监控 v11", lifespan=lifespan)
|
||||
app = FastAPI(title="AlphaX Agent | Crypto", lifespan=lifespan)
|
||||
templates = Jinja2Templates(directory=str(REPO_ROOT / "static"))
|
||||
|
||||
app.include_router(auth_router)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}管理看板 · AlphaX{% endblock %}
|
||||
{% block title %}管理看板 · AlphaX Agent | Crypto{% endblock %}
|
||||
|
||||
{% block nav_links %}
|
||||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}AlphaX / Omnix — 看板{% endblock %}
|
||||
{% block title %}AlphaX Agent | Crypto — 看板{% endblock %}
|
||||
<!-- BUILD: 2026-05-09T18:25:00 grid+kline-autoload -->
|
||||
|
||||
{% block extra_head_css %}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>登录 / 注册 — AlphaX</title>
|
||||
<title>登录 / 注册 — AlphaX Agent | Crypto</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
@ -34,7 +34,7 @@ a { color: inherit; text-decoration: none; }
|
||||
.brand { display: flex; align-items: center; gap: 8px; margin-bottom: 40px; }
|
||||
.brand-mark { width: 24px; height: 24px; background: var(--yellow); border-radius: 6px; display: grid; place-items: center; }
|
||||
.brand-mark::after { content: ""; width: 8px; height: 8px; border: 1.5px solid var(--primary); border-radius: 50%; box-shadow: 6px -4px 0 -2px var(--primary); }
|
||||
.brand-name { font-weight: 500; font-size: 16px; letter-spacing: -.2px; }
|
||||
.brand-name { font-weight: 500; font-size: 16px; letter-spacing: 0; white-space: nowrap; }
|
||||
|
||||
/* Tab pills — DESIGN.md pill-tab pattern */
|
||||
.tabs { display: grid; grid-template-columns: 1fr 1fr; padding: 4px; background: var(--surface); border-radius: var(--radius-full); margin-bottom: 32px; }
|
||||
@ -141,11 +141,11 @@ a { color: inherit; text-decoration: none; }
|
||||
<div class="page">
|
||||
<a class="brand" href="/">
|
||||
<span class="brand-mark"></span>
|
||||
<span class="brand-name">AlphaX</span>
|
||||
<span class="brand-name">AlphaX Agent | Crypto</span>
|
||||
</a>
|
||||
|
||||
<div class="notice" style="margin-bottom:16px">
|
||||
<strong>AI Opportunity Radar</strong><br>
|
||||
<strong>AlphaX Agent | Crypto</strong><br>
|
||||
提前发现机会,别在强信号后追高。登录或开启免费体验,创建账号后可前往订阅中心。
|
||||
</div>
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||
<title>{% block title %}AlphaX{% endblock %}</title>
|
||||
<title>{% block title %}AlphaX Agent | Crypto{% endblock %}</title>
|
||||
<style>
|
||||
/* ===== DESIGN.md Miro Tokens ===== */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
@ -44,11 +44,11 @@ a { color: inherit; text-decoration: none; }
|
||||
}
|
||||
.sidebar-brand {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 18px 20px; border-bottom: 1px solid var(--hairline-soft);
|
||||
padding: 18px 14px; border-bottom: 1px solid var(--hairline-soft);
|
||||
}
|
||||
.brand-mark { width: 22px; height: 22px; background: var(--yellow); border-radius: 5px; display: grid; place-items: center; flex-shrink: 0; }
|
||||
.brand-mark::after { content: ""; width: 7px; height: 7px; border: 1.5px solid var(--primary); border-radius: 50%; box-shadow: 5px -3px 0 -1.5px var(--primary); }
|
||||
.brand-name { font-weight: 600; font-size: 14px; letter-spacing: -.2px; }
|
||||
.brand-name { font-weight: 600; font-size: 13px; letter-spacing: 0; min-width: 0; white-space: nowrap; }
|
||||
.beta-badge { display:inline-flex; align-items:center; height:19px; padding:0 7px; border-radius:var(--radius-full); background:var(--surface); border:1px solid var(--hairline); color:var(--steel); font-size:10px; font-weight:700; line-height:1; }
|
||||
|
||||
.sidebar-nav {
|
||||
@ -163,10 +163,9 @@ a { color: inherit; text-decoration: none; }
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<a class="sidebar-brand" href="/" aria-label="返回 AlphaX 首页">
|
||||
<a class="sidebar-brand" href="/" aria-label="返回 AlphaX Agent | Crypto 首页">
|
||||
<span class="brand-mark"></span>
|
||||
<span class="brand-name">AlphaX</span>
|
||||
<span class="beta-badge">Beta</span>
|
||||
<span class="brand-name">AlphaX Agent | Crypto</span>
|
||||
</a>
|
||||
<nav class="sidebar-nav">
|
||||
{% block nav_links %}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>AlphaX</title>
|
||||
<title>AlphaX Agent | Crypto</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
@ -79,7 +79,7 @@ body.auth .nav-user { display: flex; }
|
||||
.nav-left { display: flex; align-items: center; gap: 8px; }
|
||||
.brand-mark { width: 22px; height: 22px; background: var(--yellow); border-radius: 5px; display: grid; place-items: center; }
|
||||
.brand-mark::after { content: ""; width: 7px; height: 7px; border: 1.5px solid var(--primary); border-radius: 50%; box-shadow: 5px -3px 0 -1.5px var(--primary); }
|
||||
.brand-name { font-weight: 500; font-size: 15px; letter-spacing: -.2px; }
|
||||
.brand-name { font-weight: 500; font-size: 15px; letter-spacing: 0; white-space: nowrap; }
|
||||
.beta-badge { display:inline-flex; align-items:center; height:20px; padding:0 7px; border-radius:var(--radius-full); background:var(--surface); border:1px solid var(--hairline); color:var(--steel); font-size:11px; font-weight:600; line-height:1; }
|
||||
.hero-brand .beta-badge { margin-left:2px; }
|
||||
|
||||
@ -110,7 +110,7 @@ body.auth .nav-user { display: flex; }
|
||||
.hero-brand { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 32px; }
|
||||
.hero-mark { width: 36px; height: 36px; background: var(--yellow); border-radius: 8px; display: grid; place-items: center; flex-shrink: 0; }
|
||||
.hero-mark::after { content: ""; width: 12px; height: 12px; border: 2px solid var(--primary); border-radius: 50%; box-shadow: 9px -5px 0 -2.5px var(--primary); }
|
||||
.hero-word { font-weight: 450; font-size: 22px; letter-spacing: -0.3px; color: var(--ink); }
|
||||
.hero-word { font-weight: 450; font-size: 22px; letter-spacing: 0; color: var(--ink); }
|
||||
h1 { font-size: clamp(36px, 5.5vw, 56px); font-weight: 500; line-height: 1.1; letter-spacing: -1px; color: var(--ink-deep); max-width: 600px; margin: 0 auto 20px; }
|
||||
.hero-lead { font-size: 16px; line-height: 1.6; color: var(--slate); max-width: 440px; margin: 0 auto 32px; }
|
||||
.hero-actions { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin-top: 32px; }
|
||||
@ -139,7 +139,7 @@ h1 { font-size: clamp(36px, 5.5vw, 56px); font-weight: 500; line-height: 1.1; le
|
||||
<nav class="nav">
|
||||
<a class="nav-left" href="/">
|
||||
<span class="brand-mark"></span>
|
||||
<span class="brand-name">AlphaX</span>
|
||||
<span class="brand-name">AlphaX Agent | Crypto</span>
|
||||
</a>
|
||||
<div class="nav-guest">
|
||||
<a class="btn btn-secondary" href="/auth?tab=login">登录</a>
|
||||
@ -185,7 +185,7 @@ h1 { font-size: clamp(36px, 5.5vw, 56px); font-weight: 500; line-height: 1.1; le
|
||||
</div>
|
||||
<div class="hero-brand">
|
||||
<span class="hero-mark"></span>
|
||||
<span class="hero-word">AlphaX</span>
|
||||
<span class="hero-word">AlphaX Agent | Crypto</span>
|
||||
<span class="beta-badge">Beta</span>
|
||||
</div>
|
||||
<h1>AI Market Intelligence.</h1>
|
||||
@ -196,7 +196,7 @@ h1 { font-size: clamp(36px, 5.5vw, 56px); font-weight: 500; line-height: 1.1; le
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<span class="footer-copy">© 2026 AlphaX</span>
|
||||
<span class="footer-copy">© 2026 AlphaX Agent | Crypto</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}AlphaX — 策略进化{% endblock %}
|
||||
{% block title %}AlphaX Agent | Crypto — 策略进化{% endblock %}
|
||||
{% block nav_links %}
|
||||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||||
<a class="sidebar-link" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
|
||||
@ -106,7 +106,7 @@ h2 { font-size:26px; font-weight:900; margin:0 0 8px; color:var(--ink); }
|
||||
<div class="hero">
|
||||
<div>
|
||||
<h2>策略进化</h2>
|
||||
<p class="subtitle">这里展示 AlphaX 策略是否真的在变聪明:本轮有没有发布、为什么没发布、哪些规律还在观察、哪些错误正在减少。</p>
|
||||
<p class="subtitle">这里展示 AlphaX Agent | Crypto 策略是否真的在变聪明:本轮有没有发布、为什么没发布、哪些规律还在观察、哪些错误正在减少。</p>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button class="btn" onclick="refreshCandidates()">刷新规则表现</button>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}推荐好友 · AlphaX{% endblock %}
|
||||
{% block title %}推荐好友 · AlphaX Agent | Crypto{% endblock %}
|
||||
|
||||
{% block nav_links %}
|
||||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||||
@ -98,7 +98,7 @@ main { max-width: 680px; margin: 0 auto; width: 100%; padding: 32px 20px; displa
|
||||
<main>
|
||||
<div class="page-header">
|
||||
<h1>🎁 推荐好友</h1>
|
||||
<p class="sub">邀请朋友加入 AlphaX,一起跟踪市场信号</p>
|
||||
<p class="sub">邀请朋友加入 AlphaX Agent | Crypto,一起跟踪市场信号</p>
|
||||
</div>
|
||||
|
||||
<div class="invite-card">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}AlphaX — 舆情雷达{% endblock %}
|
||||
{% block title %}AlphaX Agent | Crypto — 舆情雷达{% endblock %}
|
||||
{% block nav_links %}
|
||||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||||
<a class="sidebar-link active" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}策略 — AlphaX{% endblock %}
|
||||
{% block title %}策略 — AlphaX Agent | Crypto{% endblock %}
|
||||
{% block nav_links %}
|
||||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||||
<a class="sidebar-link" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}订阅中心 — AlphaX{% endblock %}
|
||||
{% block title %}订阅中心 — AlphaX Agent | Crypto{% endblock %}
|
||||
{% block nav_links %}
|
||||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||||
<a class="sidebar-link" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
|
||||
@ -87,7 +87,7 @@
|
||||
<p class="sub">新用户可免费体验 1 个月,后续按需选择方案。</p>
|
||||
|
||||
<div id="guideBox" class="guide-box">
|
||||
<div class="guide-title" id="guideTitle">先开通套餐,开始使用 AlphaX</div>
|
||||
<div class="guide-title" id="guideTitle">先开通套餐,开始使用 AlphaX Agent | Crypto</div>
|
||||
<div class="guide-text" id="guideText">新用户可领取 30 天免费体验。开通后即可进入看板、策略、迭代和舆情页面。</div>
|
||||
</div>
|
||||
|
||||
@ -218,7 +218,7 @@ async function loadMe() {
|
||||
document.getElementById('guideTitle').textContent = '订阅已到期,请先续订';
|
||||
document.getElementById('guideText').textContent = '当前账号没有有效订阅。续订或开通套餐后,才能继续访问看板、策略、迭代和舆情页面。';
|
||||
} else if (!s) {
|
||||
document.getElementById('guideTitle').textContent = '欢迎使用 AlphaX,请先开通套餐';
|
||||
document.getElementById('guideTitle').textContent = '欢迎使用 AlphaX Agent | Crypto,请先开通套餐';
|
||||
document.getElementById('guideText').textContent = '新用户可领取 30 天免费体验。开通后即可进入完整功能页面。';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}关注 — AlphaX{% endblock %}
|
||||
{% block title %}关注 — AlphaX Agent | Crypto{% endblock %}
|
||||
{% block nav_links %}
|
||||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||||
<a class="sidebar-link" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
|
||||
|
||||
@ -211,6 +211,32 @@ def test_pipeline_runs_aggregates_funnel_and_performance(temp_db):
|
||||
assert detail["missed_explosions"][0]["symbol"] == "MISS/USDT"
|
||||
|
||||
|
||||
def test_pipeline_runs_supports_pagination(temp_db):
|
||||
base = datetime.now() - timedelta(minutes=90)
|
||||
for i in range(3):
|
||||
start = (base + timedelta(minutes=i * 5)).isoformat(timespec="seconds")
|
||||
altcoin_db.log_cron_run(
|
||||
"粗筛",
|
||||
"altcoin_screener.py",
|
||||
"success",
|
||||
f"batch_{i}",
|
||||
started_at=start,
|
||||
finished_at=(base + timedelta(minutes=i * 5 + 1)).isoformat(timespec="seconds"),
|
||||
summary={"total_candidates": i + 1, "total_qualified": i},
|
||||
)
|
||||
|
||||
data_page1 = get_pipeline_runs(limit=2, hours=24, offset=0)
|
||||
data_page2 = get_pipeline_runs(limit=2, hours=24, offset=2)
|
||||
|
||||
assert data_page1["pagination"]["total_count"] == 3
|
||||
assert data_page1["pagination"]["total_pages"] == 2
|
||||
assert data_page1["pagination"]["page"] == 1
|
||||
assert len(data_page1["runs"]) == 2
|
||||
assert data_page2["pagination"]["page"] == 2
|
||||
assert len(data_page2["runs"]) == 1
|
||||
assert data_page1["runs"][0]["run_id"] != data_page2["runs"][0]["run_id"]
|
||||
|
||||
|
||||
def test_pipeline_api_keeps_observation_batch_without_recommendations(temp_db):
|
||||
base = datetime.now() - timedelta(minutes=20)
|
||||
altcoin_db.log_cron_run(
|
||||
@ -236,6 +262,30 @@ def test_pipeline_api_keeps_observation_batch_without_recommendations(temp_db):
|
||||
assert detail["screening_items"][0]["stage_label"] == "观察候选"
|
||||
|
||||
|
||||
def test_pipeline_api_returns_pagination_meta(temp_db):
|
||||
base = datetime.now() - timedelta(minutes=15)
|
||||
for i in range(2):
|
||||
altcoin_db.log_cron_run(
|
||||
"粗筛",
|
||||
"altcoin_screener.py",
|
||||
"success",
|
||||
f"paged_{i}",
|
||||
started_at=(base + timedelta(minutes=i)).isoformat(timespec="seconds"),
|
||||
finished_at=(base + timedelta(minutes=i, seconds=20)).isoformat(timespec="seconds"),
|
||||
summary={"total_candidates": 1, "total_qualified": 1},
|
||||
)
|
||||
|
||||
client = TestClient(web_server.app)
|
||||
resp = client.get("/api/pipeline/runs?hours=24&limit=1&offset=1")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["pagination"]["limit"] == 1
|
||||
assert data["pagination"]["offset"] == 1
|
||||
assert data["pagination"]["page"] == 2
|
||||
assert data["pagination"]["total_count"] >= 2
|
||||
assert len(data["runs"]) == 1
|
||||
|
||||
|
||||
def test_pipeline_page_nav_hides_watchlist_entry_and_watchlist_route_survives(temp_db):
|
||||
client = TestClient(web_server.app)
|
||||
|
||||
|
||||
@ -228,7 +228,7 @@ def test_auth_page_hides_internal_requirements_and_has_modern_member_copy(temp_a
|
||||
"创建账号",
|
||||
"会员登录",
|
||||
"前往订阅中心",
|
||||
"AI Opportunity Radar",
|
||||
"AlphaX Agent | Crypto",
|
||||
]:
|
||||
assert expected in html
|
||||
|
||||
@ -256,7 +256,7 @@ def test_app_shell_returns_200_for_all_users(temp_auth_db):
|
||||
client = TestClient(web_server.app)
|
||||
# 未登录也能拿到壳页(JS自己判断跳转)
|
||||
assert client.get("/app").status_code == 200
|
||||
assert "Omnix" in client.get("/app").text
|
||||
assert "AlphaX Agent | Crypto" in client.get("/app").text
|
||||
|
||||
# 登录用户也一样
|
||||
reg = auth_db.register_user("alice@example.com", "StrongPass123")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user