新增实例数据统计页:复盘 KPI 上、资金曲线下,系统设置可关。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
"""实例资金曲线采样:现金余额(不含权利金/浮盈),供数据统计页使用."""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
_lock = threading.Lock()
|
||||
_last_sample_at = 0.0
|
||||
_thread: threading.Thread | None = None
|
||||
_stop = threading.Event()
|
||||
|
||||
|
||||
def cash_sample_interval_sec() -> float:
|
||||
try:
|
||||
return max(30.0, float(os.getenv("INSTANCE_CASH_SAMPLE_SEC") or 60))
|
||||
except (TypeError, ValueError):
|
||||
return 60.0
|
||||
|
||||
|
||||
def cash_sample_keep_days() -> int:
|
||||
try:
|
||||
return max(7, int(float(os.getenv("INSTANCE_CASH_SAMPLE_KEEP_DAYS") or 90)))
|
||||
except (TypeError, ValueError):
|
||||
return 90
|
||||
|
||||
|
||||
def init_cash_sample_table(conn) -> None:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS instance_cash_samples (
|
||||
ts_ms INTEGER PRIMARY KEY,
|
||||
cash_usdt REAL NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _safe_float(v: Any) -> Optional[float]:
|
||||
if v is None or v == "":
|
||||
return None
|
||||
try:
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def cash_usdt_from_balances(bal: dict[str, Any] | None) -> Optional[float]:
|
||||
"""现金 = 交易账户 + 资金账户(优先 USDT,否则 USDC);不含权利金/浮盈."""
|
||||
if not isinstance(bal, dict):
|
||||
return None
|
||||
parts = [_safe_float(bal.get("trading_usdt")), _safe_float(bal.get("funding_usdt"))]
|
||||
if all(x is None for x in parts):
|
||||
parts = [_safe_float(bal.get("trading_usdc")), _safe_float(bal.get("funding_usdc"))]
|
||||
known = [x for x in parts if x is not None]
|
||||
if not known:
|
||||
return None
|
||||
return round(sum(known), 4)
|
||||
|
||||
|
||||
def record_cash_sample(conn, cash_usdt: float, *, ts_ms: int | None = None) -> None:
|
||||
init_cash_sample_table(conn)
|
||||
t = int(ts_ms if ts_ms is not None else time.time() * 1000)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO instance_cash_samples(ts_ms, cash_usdt)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(ts_ms) DO UPDATE SET cash_usdt=excluded.cash_usdt
|
||||
""",
|
||||
(t, float(cash_usdt)),
|
||||
)
|
||||
keep_ms = cash_sample_keep_days() * 86400 * 1000
|
||||
cutoff = t - keep_ms
|
||||
conn.execute("DELETE FROM instance_cash_samples WHERE ts_ms < ?", (cutoff,))
|
||||
|
||||
|
||||
def _range_start_ms(range_key: str) -> tuple[str, int]:
|
||||
rk = (range_key or "7d").strip().lower()
|
||||
now_ms = int(time.time() * 1000)
|
||||
if rk in ("today", "1d"):
|
||||
start = dt.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
return "today", int(start.timestamp() * 1000)
|
||||
if rk in ("30d", "30"):
|
||||
return "30d", now_ms - 30 * 86400 * 1000
|
||||
if rk in ("all", "0"):
|
||||
return "all", 0
|
||||
return "7d", now_ms - 7 * 86400 * 1000
|
||||
|
||||
|
||||
def query_cash_series_with_db(conn, *, range_key: str = "7d") -> dict[str, Any]:
|
||||
rk, start_ms = _range_start_ms(range_key)
|
||||
now_ms = int(time.time() * 1000)
|
||||
init_cash_sample_table(conn)
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT ts_ms, cash_usdt
|
||||
FROM instance_cash_samples
|
||||
WHERE ts_ms >= ?
|
||||
ORDER BY ts_ms ASC
|
||||
""",
|
||||
(start_ms,),
|
||||
).fetchall()
|
||||
points = [{"t": int(r["ts_ms"]), "v": float(r["cash_usdt"])} for r in rows]
|
||||
return {
|
||||
"ok": True,
|
||||
"range": rk,
|
||||
"points": points,
|
||||
"start_ms": start_ms,
|
||||
"end_ms": now_ms,
|
||||
"latest": points[-1]["v"] if points else None,
|
||||
}
|
||||
|
||||
|
||||
def sample_once(get_db: Callable, fetch_balances: Callable) -> bool:
|
||||
"""拉取余额并写入采样;成功返回 True."""
|
||||
global _last_sample_at
|
||||
try:
|
||||
bal = fetch_balances()
|
||||
except Exception:
|
||||
return False
|
||||
cash = cash_usdt_from_balances(bal if isinstance(bal, dict) else None)
|
||||
if cash is None:
|
||||
return False
|
||||
with _lock:
|
||||
conn = get_db()
|
||||
try:
|
||||
record_cash_sample(conn, cash)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
_last_sample_at = time.time()
|
||||
return True
|
||||
|
||||
|
||||
def start_cash_sample_loop(get_db: Callable, fetch_balances: Callable) -> None:
|
||||
global _thread
|
||||
if _thread and _thread.is_alive():
|
||||
return
|
||||
_stop.clear()
|
||||
|
||||
def _loop() -> None:
|
||||
# 启动后立刻采一笔,避免曲线长期空白
|
||||
try:
|
||||
sample_once(get_db, fetch_balances)
|
||||
except Exception:
|
||||
pass
|
||||
while not _stop.is_set():
|
||||
if _stop.wait(cash_sample_interval_sec()):
|
||||
break
|
||||
try:
|
||||
sample_once(get_db, fetch_balances)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_thread = threading.Thread(target=_loop, daemon=True, name="instance-cash-sample")
|
||||
_thread.start()
|
||||
@@ -0,0 +1,49 @@
|
||||
"""实例数据统计:复盘 KPI + 资金曲线 API,以及后台现金采样."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
|
||||
def install_instance_data_stats(app: Flask, app_module: Any) -> None:
|
||||
get_db: Callable = app_module.get_db
|
||||
login_required = app_module.login_required
|
||||
|
||||
def _fetch_balances() -> dict[str, Any]:
|
||||
ex = getattr(app_module, "exchange_options", None)
|
||||
if ex is None:
|
||||
return {}
|
||||
from lib.exchange.okx_options_lib import fetch_options_balances
|
||||
|
||||
return fetch_options_balances(ex, force=False) or {}
|
||||
|
||||
@app.route("/api/instance/cash_curve")
|
||||
@login_required
|
||||
def api_instance_cash_curve():
|
||||
from lib.instance.instance_cash_sample_lib import (
|
||||
query_cash_series_with_db,
|
||||
sample_once,
|
||||
)
|
||||
|
||||
# 打开页面时尽量补一笔最新点(失败忽略)
|
||||
try:
|
||||
sample_once(get_db, _fetch_balances)
|
||||
except Exception:
|
||||
pass
|
||||
range_key = (request.args.get("range") or "7d").strip()
|
||||
conn = get_db()
|
||||
try:
|
||||
return jsonify(query_cash_series_with_db(conn, range_key=range_key))
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "msg": f"加载资金曲线失败: {e}"}), 500
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# 后台采样(daemon);无密钥时会空转跳过
|
||||
try:
|
||||
from lib.instance.instance_cash_sample_lib import start_cash_sample_loop
|
||||
|
||||
start_cash_sample_loop(get_db, _fetch_balances)
|
||||
except Exception as e:
|
||||
print(f"[instance_cash_sample] start skipped: {e}", flush=True)
|
||||
@@ -15,7 +15,7 @@ DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
|
||||
"show_nav_strategy": False,
|
||||
"show_nav_strategy_records": False,
|
||||
"show_nav_records": False, # 永续交易记录与复盘已移除
|
||||
"show_nav_stats": False,
|
||||
"show_nav_stats": True, # 数据统计(复盘 KPI + 资金曲线);可在系统设置关闭
|
||||
"show_nav_risk_policy": True,
|
||||
"show_nav_live_guide": True,
|
||||
"show_nav_system_guide": False,
|
||||
@@ -37,7 +37,7 @@ DISPLAY_LABELS: dict[str, str] = {
|
||||
"show_nav_strategy": "策略交易",
|
||||
"show_nav_strategy_records": "策略交易记录",
|
||||
"show_nav_records": "交易记录与复盘",
|
||||
"show_nav_stats": "统计分析",
|
||||
"show_nav_stats": "数据统计",
|
||||
"show_nav_risk_policy": "风控说明",
|
||||
"show_nav_live_guide": "实盘说明",
|
||||
"show_nav_system_guide": "系统说明",
|
||||
@@ -111,8 +111,8 @@ def display_prefs_template_context(get_db: Callable) -> dict[str, Any]:
|
||||
def tab_allowed(tab: str, display: Optional[dict[str, bool]] = None) -> bool:
|
||||
prefs = normalize_display_prefs(display or {})
|
||||
t = (tab or "").strip()
|
||||
if t in ("trade", "records", "stats", "hedge_plan"):
|
||||
return False # 永续下单/记录/统计已下线;关键位监控保留
|
||||
if t in ("trade", "records", "hedge_plan"):
|
||||
return False # 永续下单/记录已下线;关键位监控保留
|
||||
key = NAV_TAB_ALLOWED.get(t)
|
||||
if not key:
|
||||
return True
|
||||
@@ -124,6 +124,7 @@ def display_meta_for_ui() -> list[dict[str, Any]]:
|
||||
"show_nav_dashboard",
|
||||
"show_nav_account_ledger",
|
||||
"show_nav_key_monitor",
|
||||
"show_nav_stats",
|
||||
"show_nav_risk_policy",
|
||||
"show_nav_live_guide",
|
||||
"show_nav_system_guide",
|
||||
|
||||
@@ -62,7 +62,7 @@ def embed_render_plan(page: str, embed_mode: str | None) -> EmbedRenderPlan:
|
||||
key_history=page == "key_monitor",
|
||||
key_list=page in ("key_monitor", "trade") or is_strategy,
|
||||
orders=page == "trade" or is_strategy,
|
||||
stats_bundle=page == "stats",
|
||||
stats_bundle=False, # 旧永续统计已弃用;数据统计页走复盘 API + 资金曲线
|
||||
strategy=is_strategy,
|
||||
orphan_live=page == "trade" and is_shell,
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ EMBED_TABS: tuple[str, ...] = (
|
||||
"key_monitor",
|
||||
"options",
|
||||
"options_review",
|
||||
"stats",
|
||||
"risk_policy",
|
||||
"live_guide",
|
||||
"system_guide",
|
||||
@@ -27,7 +28,7 @@ PATH_TO_EMBED_TAB: dict[str, str] = {
|
||||
"/": "options",
|
||||
"/trade": "options", # 实盘下单界面已移除
|
||||
"/records": "options_review", # 永续交易记录与复盘已移除
|
||||
"/stats": "options",
|
||||
"/stats": "stats",
|
||||
"/dashboard": "dashboard",
|
||||
"/account_ledger": "account_ledger",
|
||||
"/key_monitor": "key_monitor",
|
||||
|
||||
@@ -42,6 +42,7 @@ _COMMON_STATIC_ASSETS: dict[str, str] = {
|
||||
"options_settings.js": "application/javascript; charset=utf-8",
|
||||
"options_position_cards.js": "application/javascript; charset=utf-8",
|
||||
"options_review.js": "application/javascript; charset=utf-8",
|
||||
"options_data_stats.js": "application/javascript; charset=utf-8",
|
||||
"order_entry_model.js": "application/javascript; charset=utf-8",
|
||||
"focus_chart_page.js": "application/javascript; charset=utf-8",
|
||||
"focus_chart_page.css": "text/css; charset=utf-8",
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
{% include 'options_panel.html' %}
|
||||
{% elif page == 'options_review' %}
|
||||
{% include 'options_review_panel.html' %}
|
||||
{% elif page == 'stats' %}
|
||||
{% include 'options_data_stats_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
{% if options_nav_visible and display.show_nav_options_review %}
|
||||
<a href="/options/review" data-embed-tab="options_review" class="{% if initial_tab == 'options_review' %}active{% endif %}">期权复盘</a>
|
||||
{% endif %}
|
||||
<a href="/stats" data-embed-tab="stats" class="{% if initial_tab == 'stats' %}active{% endif %}"{% if not display.show_nav_stats %} style="display:none"{% endif %}>数据统计</a>
|
||||
{% if display.show_nav_risk_policy %}
|
||||
<a href="/risk_policy" data-embed-tab="risk_policy" class="{% if initial_tab == 'risk_policy' %}active{% endif %}">风控说明</a>
|
||||
{% endif %}
|
||||
@@ -66,7 +67,7 @@
|
||||
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
|
||||
|
||||
{% include 'instance_header_panel.html' %}
|
||||
{% if initial_tab not in ('settings', 'risk_policy', 'live_guide', 'system_guide', 'env_config', 'options_review') and include_transfer_block %}
|
||||
{% if initial_tab not in ('settings', 'risk_policy', 'live_guide', 'system_guide', 'env_config', 'options_review', 'stats') and include_transfer_block %}
|
||||
{% include 'instance_top_bar.html' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -113,6 +114,7 @@
|
||||
{% if options_nav_visible and display.show_nav_options_review %}
|
||||
<a href="/options/review" data-embed-tab="options_review">期权复盘</a>
|
||||
{% endif %}
|
||||
<a href="/stats" data-embed-tab="stats"{% if not display.show_nav_stats %} style="display:none"{% endif %}>数据统计</a>
|
||||
{% if display.show_nav_risk_policy %}
|
||||
<a href="/risk_policy" data-embed-tab="risk_policy">风控说明</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -168,15 +168,13 @@
|
||||
{% if display.show_nav_records %}
|
||||
<a href="/records" data-embed-tab="records" class="{% if page == 'records' %}active{% endif %}">交易记录与复盘</a>
|
||||
{% endif %}
|
||||
{% if display.show_nav_stats %}
|
||||
<a href="/stats" data-embed-tab="stats" class="{% if page == 'stats' %}active{% endif %}">统计分析</a>
|
||||
{% endif %}
|
||||
{% if options_nav_visible and display.show_nav_options %}
|
||||
<a href="/options" data-embed-tab="options" class="{% if page == 'options' %}active{% endif %}">期权</a>
|
||||
{% endif %}
|
||||
{% if options_nav_visible and display.show_nav_options_review %}
|
||||
<a href="/options/review" data-embed-tab="options_review" class="{% if page == 'options_review' %}active{% endif %}">期权复盘</a>
|
||||
{% endif %}
|
||||
<a href="/stats" data-embed-tab="stats" class="{% if page == 'stats' %}active{% endif %}"{% if not display.show_nav_stats %} style="display:none"{% endif %}>数据统计</a>
|
||||
{% if display.show_nav_risk_policy %}
|
||||
<a href="/risk_policy" data-embed-tab="risk_policy" class="{% if page == 'risk_policy' %}active{% endif %}">风控说明</a>
|
||||
{% endif %}
|
||||
@@ -194,7 +192,7 @@
|
||||
{% with msg=get_flashed_messages() %}{% if msg %}<div class="flash">{{ msg[0] }}</div>{% endif %}{% endwith %}
|
||||
|
||||
{% include 'instance_header_panel.html' %}
|
||||
{% if page not in ('settings', 'risk_policy', 'live_guide', 'system_guide', 'env_config', 'options', 'options_review') %}
|
||||
{% if page not in ('settings', 'risk_policy', 'live_guide', 'system_guide', 'env_config', 'options', 'options_review', 'stats') %}
|
||||
{% include 'instance_top_bar.html' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -359,6 +357,8 @@
|
||||
{% include 'options_panel.html' %}
|
||||
{% elif page == 'options_review' %}
|
||||
{% include 'options_review_panel.html' %}
|
||||
{% elif page == 'stats' %}
|
||||
{% include 'options_data_stats_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -387,44 +387,6 @@
|
||||
{% include 'settings_panel.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% if page == 'stats' %}
|
||||
<div class="card stats-card full" id="stats-card">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap">
|
||||
<h2 style="margin-bottom:0">数据统计</h2>
|
||||
<button type="button" class="stats-toggle" id="stats-toggle-btn" onclick="toggleStatsCard()">折叠</button>
|
||||
</div>
|
||||
<div class="stats-content" id="stats-content">
|
||||
<div class="sub" style="margin-bottom:12px;color:#8892b0;font-size:.82rem">
|
||||
统计分析按<strong>北京时间 {{ stats_bundle.stats_reset_hour }}:00</strong>切日计入(与顶栏 UTC 列表窗无关).历史总开仓(累计):
|
||||
<strong style="color:#cfd3ef">{{ stats_bundle.total_opens_all }}</strong> 次
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:14px;align-items:center">
|
||||
<label style="display:flex;align-items:center;gap:8px;font-size:.88rem;color:#cfd3ef">
|
||||
统计品类
|
||||
<select id="stats-segment-select" onchange="switchStatsSegment()" style="min-width:200px">
|
||||
{% for seg in stats_bundle.segments %}
|
||||
<option value="{{ seg.key }}">{{ seg.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{% for seg in stats_bundle.segments %}
|
||||
<div class="stats-segment-block stats-segment-panel" data-stats-segment="{{ seg.key }}"{% if not loop.first %} style="display:none"{% endif %}>
|
||||
<div class="stats-period-tabs" role="tablist" aria-label="统计周期">
|
||||
<button type="button" class="stats-period-tab active" data-stats-period="day" role="tab" aria-selected="true" onclick="switchStatsPeriod('day')">日统计</button>
|
||||
<button type="button" class="stats-period-tab" data-stats-period="week" role="tab" aria-selected="false" onclick="switchStatsPeriod('week')">周统计</button>
|
||||
<button type="button" class="stats-period-tab" data-stats-period="month" role="tab" aria-selected="false" onclick="switchStatsPeriod('month')">月统计</button>
|
||||
<button type="button" class="stats-period-tab" data-stats-period="all" role="tab" aria-selected="false" onclick="switchStatsPeriod('all')">全部统计</button>
|
||||
</div>
|
||||
{{ period_stats_pane("day", seg.day) }}
|
||||
{{ period_stats_pane("week", seg.week) }}
|
||||
{{ period_stats_pane("month", seg.month) }}
|
||||
{{ period_stats_pane("all", seg.all) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="modal" id="imgModal" onclick="closeModal()">
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{# 实例数据统计:上方期权复盘 KPI/分组,下方资金曲线 #}
|
||||
<div class="card ods-page full" id="options-data-stats" data-source-type="option_spot">
|
||||
<style>
|
||||
.ods-page{grid-column:1/-1}
|
||||
.ods-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px}
|
||||
.ods-head h2{margin:0;font-size:1.05rem}
|
||||
.ods-section{margin-bottom:22px}
|
||||
.ods-section-title{font-weight:650;font-size:.9rem;margin-bottom:4px}
|
||||
.ods-section-desc{font-size:.72rem;opacity:.7;margin:0 0 12px}
|
||||
.ods-kpi-row{
|
||||
display:grid;grid-template-columns:repeat(6,minmax(0,1fr));
|
||||
gap:8px;margin-bottom:12px;
|
||||
}
|
||||
.ods-kpi-tile{
|
||||
border:1px solid rgba(127,127,127,.22);border-radius:10px;
|
||||
padding:10px 12px;background:rgba(0,0,0,.2);min-width:0;
|
||||
}
|
||||
.ods-kpi-label{font-size:.7rem;opacity:.7;margin-bottom:4px}
|
||||
.ods-kpi-value{font-size:.95rem;font-weight:650;letter-spacing:.01em;word-break:break-all}
|
||||
.ods-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px}
|
||||
.ods-stat-card{
|
||||
border:1px solid rgba(127,127,127,.22);border-radius:10px;
|
||||
padding:10px 12px;background:rgba(0,0,0,.16);font-size:.76rem;
|
||||
}
|
||||
.ods-stat-card-title{
|
||||
font-weight:650;margin-bottom:8px;font-size:.78rem;
|
||||
padding-bottom:6px;border-bottom:1px solid rgba(127,127,127,.18);
|
||||
}
|
||||
.ods-stat-row{
|
||||
display:flex;justify-content:space-between;align-items:baseline;gap:10px;
|
||||
padding:5px 0;border-bottom:1px solid rgba(127,127,127,.1);
|
||||
}
|
||||
.ods-stat-row:last-child{border-bottom:none;padding-bottom:0}
|
||||
.ods-stat-key{opacity:.9;min-width:0;overflow:hidden;text-overflow:ellipsis}
|
||||
.ods-stat-val{flex-shrink:0;font-variant-numeric:tabular-nums;opacity:.85}
|
||||
.ods-curve-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px}
|
||||
.ods-range-btn{
|
||||
font-size:.76rem;padding:4px 10px;border-radius:6px;cursor:pointer;
|
||||
border:1px solid rgba(127,127,127,.3);background:transparent;color:inherit;
|
||||
}
|
||||
.ods-range-btn.active{border-color:rgba(90,160,255,.7);background:rgba(90,160,255,.12)}
|
||||
.ods-curve-meta{font-size:.72rem;opacity:.75;margin-left:auto}
|
||||
.ods-curve-wrap{
|
||||
border:1px solid rgba(127,127,127,.22);border-radius:10px;
|
||||
padding:12px 10px 8px;background:rgba(0,0,0,.14);
|
||||
}
|
||||
.ods-curve-svg{width:100%;height:240px;display:block}
|
||||
.ods-pnl-pos{color:#3dd68c}
|
||||
.ods-pnl-neg{color:#f07178}
|
||||
@media (max-width:900px){.ods-kpi-row{grid-template-columns:repeat(3,minmax(0,1fr))}}
|
||||
@media (max-width:640px){
|
||||
.ods-kpi-row{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.ods-curve-svg{height:200px}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="ods-head">
|
||||
<h2>数据统计</h2>
|
||||
<span class="muted" id="ods-status" style="font-size:.72rem"></span>
|
||||
<button type="button" class="btn-secondary" id="ods-reload-btn" style="font-size:.76rem;padding:4px 10px">刷新</button>
|
||||
</div>
|
||||
|
||||
<section class="ods-section" aria-labelledby="ods-review-heading">
|
||||
<div class="ods-section-title" id="ods-review-heading">期权复盘统计</div>
|
||||
<p class="ods-section-desc">与「期权复盘」页统计同源;复盘页内统计仍保留。</p>
|
||||
<div id="ods-kpi" class="ods-kpi-row"></div>
|
||||
<div id="ods-stats-groups" class="ods-stats-grid"></div>
|
||||
</section>
|
||||
|
||||
<section class="ods-section" aria-labelledby="ods-curve-heading">
|
||||
<div class="ods-section-title" id="ods-curve-heading">资金曲线</div>
|
||||
<p class="ods-section-desc">现金余额(交易账户+资金账户,不含权利金与浮盈);后台定时采样。</p>
|
||||
<div class="ods-curve-toolbar">
|
||||
<button type="button" class="ods-range-btn active" data-range="today">今日</button>
|
||||
<button type="button" class="ods-range-btn" data-range="7d">7日</button>
|
||||
<button type="button" class="ods-range-btn" data-range="30d">30日</button>
|
||||
<button type="button" class="ods-range-btn" data-range="all">全部</button>
|
||||
<span class="ods-curve-meta" id="ods-curve-meta">—</span>
|
||||
</div>
|
||||
<div class="ods-curve-wrap">
|
||||
<svg id="ods-curve-svg" class="ods-curve-svg" viewBox="0 0 640 240" preserveAspectRatio="none" role="img" aria-label="资金曲线"></svg>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<script src="/static/options_data_stats.js?v=1"></script>
|
||||
Reference in New Issue
Block a user