update
This commit is contained in:
parent
ffd6f73427
commit
0e8cf51f64
@ -140,6 +140,7 @@ AlphaX 是一个以 `Python + FastAPI + PostgreSQL + Docker + 静态 HTML` 组
|
||||
|
||||
- 链上功能当前已下线,不再有 `onchain` CLI、调度任务、Web 页面、API route、NodeReal/Alchemy client 或链上因子评分。
|
||||
- 历史 PostgreSQL migration 中的 `onchain_*` 表可暂时保留,避免破坏已部署库的迁移链;当前业务代码不应读取或写入这些表。
|
||||
- 下线运行模块时不能只删 CLI/route/service,还必须在 `app/db/scheduler_db.py#RETIRED_JOBS` 登记退役任务,初始化时自动禁用旧 `scheduler_job_config`、跳过旧手动触发,并从 scheduler/API/运行大屏过滤,避免线上数据库残留任务继续执行已删除命令。
|
||||
- 后续如需重启链上方向,必须先重新设计数据源、可读事件模型、映射机制、策略接入边界和复盘评价,不能直接恢复旧实现。
|
||||
|
||||
### 4.2 Web/API
|
||||
|
||||
@ -7,6 +7,9 @@ from app.db import altcoin_db
|
||||
from app.db.postgres_connection import connect as pg_connect, ensure_migrations_once
|
||||
|
||||
_SCHEDULER_INIT_DONE = False
|
||||
RETIRED_JOBS = {
|
||||
"onchain": "链上采集/API 模块已下线,当前系统聚焦 CEX 机会捕捉",
|
||||
}
|
||||
|
||||
|
||||
def get_scheduler_conn():
|
||||
@ -165,6 +168,43 @@ def _seed_scheduler_tables(conn):
|
||||
""",
|
||||
(job["job_name"], now),
|
||||
)
|
||||
_retire_scheduler_jobs(conn, now)
|
||||
|
||||
|
||||
def _retire_scheduler_jobs(conn, now: str | None = None):
|
||||
now = now or _now()
|
||||
for job_name, reason in RETIRED_JOBS.items():
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE scheduler_job_config
|
||||
SET enabled=0,
|
||||
description=%s,
|
||||
updated_at=%s
|
||||
WHERE job_name=%s
|
||||
""",
|
||||
(f"已下线:{reason}", now, job_name),
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO scheduler_runtime_status (job_name, status, last_error, updated_at)
|
||||
VALUES (%s, 'disabled', %s, %s)
|
||||
ON CONFLICT(job_name) DO UPDATE SET
|
||||
status='disabled',
|
||||
last_error=excluded.last_error,
|
||||
updated_at=excluded.updated_at
|
||||
""",
|
||||
(job_name, reason, now),
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE scheduler_manual_trigger
|
||||
SET status='skipped',
|
||||
finished_at=%s,
|
||||
error_message=%s
|
||||
WHERE job_name=%s AND status IN ('queued','pending','running')
|
||||
""",
|
||||
(now, reason, job_name),
|
||||
)
|
||||
|
||||
|
||||
def init_scheduler_tables():
|
||||
@ -183,7 +223,7 @@ def init_scheduler_tables():
|
||||
def get_job_configs():
|
||||
init_scheduler_tables()
|
||||
conn = get_scheduler_conn()
|
||||
rows = conn.execute("SELECT * FROM scheduler_job_config ORDER BY sort_order ASC, job_name ASC").fetchall()
|
||||
rows = conn.execute("SELECT * FROM scheduler_job_config WHERE job_name <> ALL(%s) ORDER BY sort_order ASC, job_name ASC", (list(RETIRED_JOBS.keys()),)).fetchall()
|
||||
conn.close()
|
||||
jobs = []
|
||||
for row in rows:
|
||||
@ -201,6 +241,8 @@ def get_job_config(job_name):
|
||||
conn.close()
|
||||
if not row:
|
||||
return None
|
||||
if row["job_name"] in RETIRED_JOBS:
|
||||
return None
|
||||
item = dict(row)
|
||||
item["args"] = _load(item.pop("args_json", "[]"), [])
|
||||
item["enabled"] = bool(item.get("enabled"))
|
||||
@ -211,10 +253,13 @@ def set_job_enabled(job_name, enabled):
|
||||
init_scheduler_tables()
|
||||
now = _now()
|
||||
conn = get_scheduler_conn()
|
||||
cur = conn.execute(
|
||||
"UPDATE scheduler_job_config SET enabled=%s, updated_at=%s WHERE job_name=%s",
|
||||
(1 if enabled else 0, now, job_name),
|
||||
)
|
||||
if job_name in RETIRED_JOBS:
|
||||
cur = conn.execute("UPDATE scheduler_job_config SET enabled=0, updated_at=%s WHERE job_name=%s", (now, job_name))
|
||||
else:
|
||||
cur = conn.execute(
|
||||
"UPDATE scheduler_job_config SET enabled=%s, updated_at=%s WHERE job_name=%s",
|
||||
(1 if enabled else 0, now, job_name),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return cur.rowcount > 0
|
||||
@ -225,10 +270,13 @@ def set_job_interval(job_name, every_seconds):
|
||||
init_scheduler_tables()
|
||||
now = _now()
|
||||
conn = get_scheduler_conn()
|
||||
cur = conn.execute(
|
||||
"UPDATE scheduler_job_config SET every_seconds=%s, updated_at=%s WHERE job_name=%s",
|
||||
(seconds, now, job_name),
|
||||
)
|
||||
if job_name in RETIRED_JOBS:
|
||||
cur = conn.execute("UPDATE scheduler_job_config SET enabled=0, updated_at=%s WHERE job_name=%s", (now, job_name))
|
||||
else:
|
||||
cur = conn.execute(
|
||||
"UPDATE scheduler_job_config SET every_seconds=%s, updated_at=%s WHERE job_name=%s",
|
||||
(seconds, now, job_name),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return cur.rowcount > 0
|
||||
@ -335,7 +383,7 @@ def list_manual_triggers(limit=30):
|
||||
def get_scheduler_overview():
|
||||
init_scheduler_tables()
|
||||
conn = get_scheduler_conn()
|
||||
configs = conn.execute("SELECT * FROM scheduler_job_config ORDER BY sort_order ASC, job_name ASC").fetchall()
|
||||
configs = conn.execute("SELECT * FROM scheduler_job_config WHERE job_name <> ALL(%s) ORDER BY sort_order ASC, job_name ASC", (list(RETIRED_JOBS.keys()),)).fetchall()
|
||||
runtime_rows = conn.execute("SELECT * FROM scheduler_runtime_status").fetchall()
|
||||
conn.close()
|
||||
try:
|
||||
|
||||
@ -47,29 +47,7 @@
|
||||
</div>
|
||||
<div class="note" id="paperNote">策略交易只统计已经进入交易账本的信号。页面用账户余额、持仓价值、累计杠杆和实际盈亏展示策略表现,不再把观察池或推荐归档当作收益。</div>
|
||||
<div class="note" id="reportNote" style="display:none"></div>
|
||||
<section class="strategy-board">
|
||||
<div class="strategy-board-head">
|
||||
<div>
|
||||
<div class="strategy-board-title">运行策略看板</div>
|
||||
<div class="strategy-board-copy">按策略独立看信号、机会、交易、胜率和收益;点击卡片可筛选下方持仓、挂单、完结持仓、取消订单和日志。</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn" type="button" onclick="clearStrategyAndSide()">查看全部</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="strategy-grid" id="strategyCards"><div class="strategy-empty">策略数据加载中...</div></div>
|
||||
</section>
|
||||
<div class="kpis" id="kpis"><div class="kpi"><span>状态</span><b>加载中</b></div></div>
|
||||
<section class="perf-panel">
|
||||
<div class="perf-head">
|
||||
<div>
|
||||
<div class="perf-title">每日收益与权益曲线</div>
|
||||
<div class="panel-note">蓝线展示账户权益变化,绿色/红色柱展示每日实现收益,右侧同步最大回撤。</div>
|
||||
</div>
|
||||
<div class="perf-meta" id="perfMeta"><span class="perf-pill">加载中...</span></div>
|
||||
</div>
|
||||
<div class="perf-chart" id="performanceChart"><div class="loading">加载中...</div></div>
|
||||
</section>
|
||||
<div class="tabs" role="tablist" aria-label="策略交易视图切换">
|
||||
<button class="tab-btn active" id="tab-open" type="button" onclick="setTradeTab('open')" role="tab" aria-selected="true">持仓中</button>
|
||||
<button class="tab-btn" id="tab-orders" type="button" onclick="setTradeTab('orders')" role="tab" aria-selected="false">挂单中</button>
|
||||
@ -142,6 +120,28 @@
|
||||
<div class="pagination" id="eventPager"></div>
|
||||
</section>
|
||||
</div>
|
||||
<section class="perf-panel">
|
||||
<div class="perf-head">
|
||||
<div>
|
||||
<div class="perf-title">每日收益与权益曲线</div>
|
||||
<div class="panel-note">蓝线展示账户权益变化,绿色/红色柱展示每日实现收益,右侧同步最大回撤。</div>
|
||||
</div>
|
||||
<div class="perf-meta" id="perfMeta"><span class="perf-pill">加载中...</span></div>
|
||||
</div>
|
||||
<div class="perf-chart" id="performanceChart"><div class="loading">加载中...</div></div>
|
||||
</section>
|
||||
<section class="strategy-board">
|
||||
<div class="strategy-board-head">
|
||||
<div>
|
||||
<div class="strategy-board-title">运行策略看板</div>
|
||||
<div class="strategy-board-copy">统计每个策略的信号、机会、交易、胜率和收益;点击卡片可筛选上方交易列表。</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn" type="button" onclick="clearStrategyAndSide()">查看全部</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="strategy-grid" id="strategyCards"><div class="strategy-empty">策略数据加载中...</div></div>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block extra_script %}
|
||||
|
||||
@ -32,6 +32,34 @@ def test_scheduler_tables_seed_defaults(monkeypatch, tmp_path):
|
||||
assert "onchain" not in jobs
|
||||
|
||||
|
||||
def test_scheduler_init_retires_legacy_onchain_job(pg_conn):
|
||||
pg_conn.execute(
|
||||
"""
|
||||
INSERT INTO scheduler_job_config (job_name, command, args_json, enabled, every_seconds, initial_delay, lock_group, description, sort_order, created_at, updated_at)
|
||||
VALUES ('onchain', 'onchain', '[]', 1, 60, 0, 'onchain', 'legacy', 99, NOW(), NOW())
|
||||
ON CONFLICT(job_name) DO UPDATE SET enabled=1, command='onchain', updated_at=NOW()
|
||||
"""
|
||||
)
|
||||
pg_conn.execute(
|
||||
"""
|
||||
INSERT INTO scheduler_manual_trigger (job_name, force, status, requested_by, requested_at)
|
||||
VALUES ('onchain', 1, 'queued', 'test', NOW())
|
||||
"""
|
||||
)
|
||||
pg_conn.commit()
|
||||
scheduler_db._SCHEDULER_INIT_DONE = False
|
||||
|
||||
scheduler_db.init_scheduler_tables()
|
||||
|
||||
assert scheduler_db.get_job_config("onchain") is None
|
||||
assert "onchain" not in {item["job_name"] for item in scheduler_db.get_job_configs()}
|
||||
row = pg_conn.execute("SELECT enabled, description FROM scheduler_job_config WHERE job_name='onchain'").fetchone()
|
||||
trigger = pg_conn.execute("SELECT status, error_message FROM scheduler_manual_trigger WHERE job_name='onchain' ORDER BY id DESC LIMIT 1").fetchone()
|
||||
assert row["enabled"] == 0
|
||||
assert "已下线" in row["description"]
|
||||
assert trigger["status"] == "skipped"
|
||||
|
||||
|
||||
def test_scheduler_control_api_and_page(monkeypatch, tmp_path):
|
||||
db_path = tmp_path / "altcoin_monitor.db"
|
||||
sched_path = tmp_path / "scheduler_state.db"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user