机器对比改为模拟在上实盘在下,收益率按已结算盈亏相对初始资金计算。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -90,6 +90,50 @@ def record_cash_samples_from_overview(rows: list[dict[str, Any]]) -> int:
|
|||||||
return len(to_write)
|
return len(to_write)
|
||||||
|
|
||||||
|
|
||||||
|
def first_cash_by_instance(instance_ids: list[Any] | None = None) -> dict[Any, float]:
|
||||||
|
"""各机最早一笔现金采样,作初始资金回退(实盘未配 LIVE_INITIAL 时)."""
|
||||||
|
conn = get_db()
|
||||||
|
try:
|
||||||
|
if instance_ids:
|
||||||
|
ids: list[int] = []
|
||||||
|
for x in instance_ids:
|
||||||
|
try:
|
||||||
|
ids.append(int(x))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
if not ids:
|
||||||
|
return {}
|
||||||
|
placeholders = ",".join("?" * len(ids))
|
||||||
|
rows = conn.execute(
|
||||||
|
f"""
|
||||||
|
SELECT instance_id, cash_usdt
|
||||||
|
FROM hub_cash_samples
|
||||||
|
WHERE instance_id IN ({placeholders})
|
||||||
|
ORDER BY ts_ms ASC
|
||||||
|
""",
|
||||||
|
tuple(ids),
|
||||||
|
).fetchall()
|
||||||
|
else:
|
||||||
|
rows = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT instance_id, cash_usdt
|
||||||
|
FROM hub_cash_samples
|
||||||
|
ORDER BY ts_ms ASC
|
||||||
|
"""
|
||||||
|
).fetchall()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
out: dict[Any, float] = {}
|
||||||
|
for r in rows:
|
||||||
|
iid = r["instance_id"]
|
||||||
|
if iid in out:
|
||||||
|
continue
|
||||||
|
v = _safe_float(r["cash_usdt"])
|
||||||
|
if v is not None and v > 0:
|
||||||
|
out[iid] = round(v, 4)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def query_cash_series(
|
def query_cash_series(
|
||||||
*,
|
*,
|
||||||
range_key: str = "7d",
|
range_key: str = "7d",
|
||||||
|
|||||||
+24
-3
@@ -4,7 +4,11 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lib.bridge_client_lib import pull_all_enabled, pull_all_stats
|
from lib.bridge_client_lib import pull_all_enabled, pull_all_stats
|
||||||
from lib.cash_sample_lib import query_cash_series, record_cash_samples_from_overview
|
from lib.cash_sample_lib import (
|
||||||
|
first_cash_by_instance,
|
||||||
|
query_cash_series,
|
||||||
|
record_cash_samples_from_overview,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _safe_float(v: Any) -> float | None:
|
def _safe_float(v: Any) -> float | None:
|
||||||
@@ -52,6 +56,10 @@ def build_stats_summary(
|
|||||||
pass
|
pass
|
||||||
stats_rows = pull_all_stats(w)
|
stats_rows = pull_all_stats(w)
|
||||||
stats_by_id = {r.get("id"): r for r in stats_rows}
|
stats_by_id = {r.get("id"): r for r in stats_rows}
|
||||||
|
try:
|
||||||
|
initial_fallback = first_cash_by_instance([s.get("id") for s in snaps])
|
||||||
|
except Exception:
|
||||||
|
initial_fallback = {}
|
||||||
|
|
||||||
machines: list[dict[str, Any]] = []
|
machines: list[dict[str, Any]] = []
|
||||||
sum_cash = 0.0
|
sum_cash = 0.0
|
||||||
@@ -92,9 +100,13 @@ def build_stats_summary(
|
|||||||
mode = ((body.get("instance") or {}) if isinstance(body.get("instance"), dict) else {}).get(
|
mode = ((body.get("instance") or {}) if isinstance(body.get("instance"), dict) else {}).get(
|
||||||
"mode"
|
"mode"
|
||||||
)
|
)
|
||||||
|
# 收益率 = 窗口已结算盈亏 / 初始资金(非当前余额)
|
||||||
|
initial = _safe_float(premium.get("initial_cash_usdt"))
|
||||||
|
if initial is None or initial <= 0:
|
||||||
|
initial = _safe_float(initial_fallback.get(iid))
|
||||||
return_pct = None
|
return_pct = None
|
||||||
if online and stats.get("ok") and net is not None and cash is not None and cash > 0:
|
if online and stats.get("ok") and net is not None and initial is not None and initial > 0:
|
||||||
return_pct = round(float(net) / float(cash) * 100.0, 2)
|
return_pct = round(float(net) / float(initial) * 100.0, 2)
|
||||||
if online:
|
if online:
|
||||||
online_n += 1
|
online_n += 1
|
||||||
if cash is not None:
|
if cash is not None:
|
||||||
@@ -127,6 +139,7 @@ def build_stats_summary(
|
|||||||
"premium_usdt": paid,
|
"premium_usdt": paid,
|
||||||
"ratio_pct": ratio,
|
"ratio_pct": ratio,
|
||||||
"unrealized_usdt": unreal,
|
"unrealized_usdt": unreal,
|
||||||
|
"initial_cash_usdt": initial,
|
||||||
"pos_count": len(brief),
|
"pos_count": len(brief),
|
||||||
"total_closed": closed if online and stats.get("ok") else None,
|
"total_closed": closed if online and stats.get("ok") else None,
|
||||||
"win_count": wins if online and stats.get("ok") else None,
|
"win_count": wins if online and stats.get("ok") else None,
|
||||||
@@ -141,6 +154,14 @@ def build_stats_summary(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _mode_rank(m: dict[str, Any]) -> tuple[int, str]:
|
||||||
|
mode_v = str(m.get("mode") or "").strip().lower()
|
||||||
|
# 上方 sim,下方 live
|
||||||
|
rank = 0 if mode_v == "sim" else 1 if mode_v == "live" else 2
|
||||||
|
return (rank, str(m.get("name") or m.get("id") or ""))
|
||||||
|
|
||||||
|
machines.sort(key=_mode_rank)
|
||||||
|
|
||||||
agg_win_rate = round(sum_wins / sum_closed * 100, 2) if sum_closed else 0.0
|
agg_win_rate = round(sum_wins / sum_closed * 100, 2) if sum_closed else 0.0
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
|
|||||||
+15
-1
@@ -116,10 +116,24 @@
|
|||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortMachines(machines) {
|
||||||
|
return (machines || []).slice().sort(function (a, b) {
|
||||||
|
function rank(m) {
|
||||||
|
const mode = String((m && m.mode) || "").toLowerCase();
|
||||||
|
if (mode === "sim") return 0;
|
||||||
|
if (mode === "live") return 1;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
const d = rank(a) - rank(b);
|
||||||
|
if (d !== 0) return d;
|
||||||
|
return String((a && a.name) || "").localeCompare(String((b && b.name) || ""), "zh");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderTable(machines) {
|
function renderTable(machines) {
|
||||||
const body = document.getElementById("stats-table-body");
|
const body = document.getElementById("stats-table-body");
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
const list = machines || [];
|
const list = sortMachines(machines);
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
body.innerHTML = '<tr><td colspan="12" class="muted">暂无启用实例</td></tr>';
|
body.innerHTML = '<tr><td colspan="12" class="muted">暂无启用实例</td></tr>';
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<div class="stats-card-head-inline">
|
<div class="stats-card-head-inline">
|
||||||
<div>
|
<div>
|
||||||
<h2>机器对比</h2>
|
<h2>机器对比</h2>
|
||||||
<p class="muted">实时资金状态 + 当前窗口交易 KPI。收益率 = 窗口已实现 ÷ 当前余额。</p>
|
<p class="muted">实时资金状态 + 当前窗口交易 KPI。排序:模拟在上、实盘在下。收益率 = 窗口已结算盈亏 ÷ 初始资金。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overview-table-wrap">
|
<div class="overview-table-wrap">
|
||||||
@@ -73,5 +73,5 @@
|
|||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='stats.js') }}?v=2"></script>
|
<script src="{{ url_for('static', filename='stats.js') }}?v=3"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user