feat数据统计曲线改为权益采样,现金加现货币权利金与浮盈。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 实例数据统计:复盘 KPI + 资金曲线.
|
* 实例数据统计:复盘 KPI + 权益曲线.
|
||||||
*/
|
*/
|
||||||
(function (global) {
|
(function (global) {
|
||||||
"use strict";
|
"use strict";
|
||||||
@@ -278,7 +278,7 @@
|
|||||||
drawCurve(pts);
|
drawCurve(pts);
|
||||||
if (meta) {
|
if (meta) {
|
||||||
meta.textContent =
|
meta.textContent =
|
||||||
"采样 " +
|
"权益采样 " +
|
||||||
pts.length +
|
pts.length +
|
||||||
" 点 · 最新 " +
|
" 点 · 最新 " +
|
||||||
fmtCash(data.latest != null ? data.latest : pts.length ? pts[pts.length - 1].v : null);
|
fmtCash(data.latest != null ? data.latest : pts.length ? pts[pts.length - 1].v : null);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""实例资金曲线采样:现金余额(不含权利金/浮盈),供数据统计页使用."""
|
"""实例资金曲线采样:权益(现金+现货币+权利金+浮盈),供数据统计页使用."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
@@ -36,6 +36,10 @@ def init_cash_sample_table(conn) -> None:
|
|||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
conn.execute("ALTER TABLE instance_cash_samples ADD COLUMN equity_usdt REAL")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _safe_float(v: Any) -> Optional[float]:
|
def _safe_float(v: Any) -> Optional[float]:
|
||||||
@@ -48,7 +52,7 @@ def _safe_float(v: Any) -> Optional[float]:
|
|||||||
|
|
||||||
|
|
||||||
def cash_usdt_from_balances(bal: dict[str, Any] | None) -> Optional[float]:
|
def cash_usdt_from_balances(bal: dict[str, Any] | None) -> Optional[float]:
|
||||||
"""现金 = 交易账户 + 资金账户(优先 USDT,否则 USDC);不含权利金/浮盈."""
|
"""稳定币现金 = 交易账户 + 资金账户(优先 USDT,否则 USDC)."""
|
||||||
if not isinstance(bal, dict):
|
if not isinstance(bal, dict):
|
||||||
return None
|
return None
|
||||||
parts = [_safe_float(bal.get("trading_usdt")), _safe_float(bal.get("funding_usdt"))]
|
parts = [_safe_float(bal.get("trading_usdt")), _safe_float(bal.get("funding_usdt"))]
|
||||||
@@ -60,16 +64,93 @@ def cash_usdt_from_balances(bal: dict[str, Any] | None) -> Optional[float]:
|
|||||||
return round(sum(known), 4)
|
return round(sum(known), 4)
|
||||||
|
|
||||||
|
|
||||||
def record_cash_sample(conn, cash_usdt: float, *, ts_ms: int | None = None) -> None:
|
def spot_coin_usdt_from_balances(
|
||||||
|
bal: dict[str, Any] | None,
|
||||||
|
*,
|
||||||
|
btc_px: float | None = None,
|
||||||
|
eth_px: float | None = None,
|
||||||
|
) -> float:
|
||||||
|
"""交易+资金账户 BTC/ETH 现货按指数折 U(缓冲买币占用)."""
|
||||||
|
if not isinstance(bal, dict):
|
||||||
|
return 0.0
|
||||||
|
total = 0.0
|
||||||
|
btc = (_safe_float(bal.get("trading_btc")) or 0.0) + (
|
||||||
|
_safe_float(bal.get("funding_btc")) or 0.0
|
||||||
|
)
|
||||||
|
eth = (_safe_float(bal.get("trading_eth")) or 0.0) + (
|
||||||
|
_safe_float(bal.get("funding_eth")) or 0.0
|
||||||
|
)
|
||||||
|
if btc > 0 and btc_px is not None and btc_px > 0:
|
||||||
|
total += btc * float(btc_px)
|
||||||
|
if eth > 0 and eth_px is not None and eth_px > 0:
|
||||||
|
total += eth * float(eth_px)
|
||||||
|
return round(total, 4)
|
||||||
|
|
||||||
|
|
||||||
|
def option_premium_and_upl_usdt(positions: list[dict[str, Any]] | None) -> tuple[float, float]:
|
||||||
|
"""持仓权利金U + 浮盈U(权利金+浮盈≈期权市值)."""
|
||||||
|
prem = 0.0
|
||||||
|
upl = 0.0
|
||||||
|
if not positions:
|
||||||
|
return 0.0, 0.0
|
||||||
|
from lib.options.options_positions_lib import (
|
||||||
|
display_pnl_usdt_from_option_row,
|
||||||
|
premium_paid_usdt_from_option_row,
|
||||||
|
)
|
||||||
|
|
||||||
|
for p in positions:
|
||||||
|
if not isinstance(p, dict):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
pu = premium_paid_usdt_from_option_row(p)
|
||||||
|
except Exception:
|
||||||
|
pu = None
|
||||||
|
if pu is not None:
|
||||||
|
prem += float(pu)
|
||||||
|
try:
|
||||||
|
du = display_pnl_usdt_from_option_row(p)
|
||||||
|
except Exception:
|
||||||
|
du = None
|
||||||
|
if du is not None:
|
||||||
|
upl += float(du)
|
||||||
|
return round(prem, 4), round(upl, 4)
|
||||||
|
|
||||||
|
|
||||||
|
def equity_usdt_from_snapshot(
|
||||||
|
bal: dict[str, Any] | None,
|
||||||
|
positions: list[dict[str, Any]] | None = None,
|
||||||
|
*,
|
||||||
|
btc_px: float | None = None,
|
||||||
|
eth_px: float | None = None,
|
||||||
|
) -> Optional[float]:
|
||||||
|
"""权益 = 稳定币现金 + 现货币折U + 权利金U + 浮盈U."""
|
||||||
|
cash = cash_usdt_from_balances(bal)
|
||||||
|
spot = spot_coin_usdt_from_balances(bal, btc_px=btc_px, eth_px=eth_px)
|
||||||
|
prem, upl = option_premium_and_upl_usdt(positions)
|
||||||
|
if cash is None and spot <= 0 and prem == 0 and upl == 0:
|
||||||
|
return None
|
||||||
|
return round(float(cash or 0.0) + float(spot) + float(prem) + float(upl), 4)
|
||||||
|
|
||||||
|
|
||||||
|
def record_cash_sample(
|
||||||
|
conn,
|
||||||
|
cash_usdt: float,
|
||||||
|
*,
|
||||||
|
equity_usdt: float | None = None,
|
||||||
|
ts_ms: int | None = None,
|
||||||
|
) -> None:
|
||||||
init_cash_sample_table(conn)
|
init_cash_sample_table(conn)
|
||||||
t = int(ts_ms if ts_ms is not None else time.time() * 1000)
|
t = int(ts_ms if ts_ms is not None else time.time() * 1000)
|
||||||
|
eq = float(equity_usdt) if equity_usdt is not None else float(cash_usdt)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO instance_cash_samples(ts_ms, cash_usdt)
|
INSERT INTO instance_cash_samples(ts_ms, cash_usdt, equity_usdt)
|
||||||
VALUES (?, ?)
|
VALUES (?, ?, ?)
|
||||||
ON CONFLICT(ts_ms) DO UPDATE SET cash_usdt=excluded.cash_usdt
|
ON CONFLICT(ts_ms) DO UPDATE SET
|
||||||
|
cash_usdt=excluded.cash_usdt,
|
||||||
|
equity_usdt=excluded.equity_usdt
|
||||||
""",
|
""",
|
||||||
(t, float(cash_usdt)),
|
(t, float(cash_usdt), eq),
|
||||||
)
|
)
|
||||||
keep_ms = cash_sample_keep_days() * 86400 * 1000
|
keep_ms = cash_sample_keep_days() * 86400 * 1000
|
||||||
cutoff = t - keep_ms
|
cutoff = t - keep_ms
|
||||||
@@ -95,17 +176,23 @@ def query_cash_series_with_db(conn, *, range_key: str = "7d") -> dict[str, Any]:
|
|||||||
init_cash_sample_table(conn)
|
init_cash_sample_table(conn)
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"""
|
"""
|
||||||
SELECT ts_ms, cash_usdt
|
SELECT ts_ms, cash_usdt, equity_usdt
|
||||||
FROM instance_cash_samples
|
FROM instance_cash_samples
|
||||||
WHERE ts_ms >= ?
|
WHERE ts_ms >= ?
|
||||||
ORDER BY ts_ms ASC
|
ORDER BY ts_ms ASC
|
||||||
""",
|
""",
|
||||||
(start_ms,),
|
(start_ms,),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
points = [{"t": int(r["ts_ms"]), "v": float(r["cash_usdt"])} for r in rows]
|
points = []
|
||||||
|
for r in rows:
|
||||||
|
cash = float(r["cash_usdt"])
|
||||||
|
eq = r["equity_usdt"]
|
||||||
|
v = float(eq) if eq is not None else cash
|
||||||
|
points.append({"t": int(r["ts_ms"]), "v": v, "cash": cash})
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"range": rk,
|
"range": rk,
|
||||||
|
"metric": "equity",
|
||||||
"points": points,
|
"points": points,
|
||||||
"start_ms": start_ms,
|
"start_ms": start_ms,
|
||||||
"end_ms": now_ms,
|
"end_ms": now_ms,
|
||||||
@@ -113,20 +200,36 @@ def query_cash_series_with_db(conn, *, range_key: str = "7d") -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def sample_once(get_db: Callable, fetch_balances: Callable) -> bool:
|
def sample_once(get_db: Callable, fetch_snapshot: Callable) -> bool:
|
||||||
"""拉取余额并写入采样;成功返回 True."""
|
"""拉取快照并写入采样.
|
||||||
|
|
||||||
|
fetch_snapshot 可返回:
|
||||||
|
- float: 视为权益(兼容旧现金回调)
|
||||||
|
- dict: {cash_usdt, equity_usdt}
|
||||||
|
"""
|
||||||
global _last_sample_at
|
global _last_sample_at
|
||||||
try:
|
try:
|
||||||
bal = fetch_balances()
|
snap = fetch_snapshot()
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
cash = cash_usdt_from_balances(bal if isinstance(bal, dict) else None)
|
cash: float | None
|
||||||
if cash is None:
|
equity: float | None
|
||||||
|
if isinstance(snap, dict):
|
||||||
|
cash = _safe_float(snap.get("cash_usdt"))
|
||||||
|
equity = _safe_float(snap.get("equity_usdt"))
|
||||||
|
else:
|
||||||
|
cash = _safe_float(snap)
|
||||||
|
equity = cash
|
||||||
|
if equity is None and cash is None:
|
||||||
return False
|
return False
|
||||||
|
if cash is None:
|
||||||
|
cash = float(equity or 0.0)
|
||||||
|
if equity is None:
|
||||||
|
equity = float(cash)
|
||||||
with _lock:
|
with _lock:
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
try:
|
try:
|
||||||
record_cash_sample(conn, cash)
|
record_cash_sample(conn, float(cash), equity_usdt=float(equity))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -134,23 +237,22 @@ def sample_once(get_db: Callable, fetch_balances: Callable) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def start_cash_sample_loop(get_db: Callable, fetch_balances: Callable) -> None:
|
def start_cash_sample_loop(get_db: Callable, fetch_snapshot: Callable) -> None:
|
||||||
global _thread
|
global _thread
|
||||||
if _thread and _thread.is_alive():
|
if _thread and _thread.is_alive():
|
||||||
return
|
return
|
||||||
_stop.clear()
|
_stop.clear()
|
||||||
|
|
||||||
def _loop() -> None:
|
def _loop() -> None:
|
||||||
# 启动后立刻采一笔,避免曲线长期空白
|
|
||||||
try:
|
try:
|
||||||
sample_once(get_db, fetch_balances)
|
sample_once(get_db, fetch_snapshot)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
while not _stop.is_set():
|
while not _stop.is_set():
|
||||||
if _stop.wait(cash_sample_interval_sec()):
|
if _stop.wait(cash_sample_interval_sec()):
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
sample_once(get_db, fetch_balances)
|
sample_once(get_db, fetch_snapshot)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""实例数据统计:复盘 KPI + 资金曲线 API,以及后台现金采样."""
|
"""实例数据统计:复盘 KPI + 权益曲线 API,以及后台权益采样."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
@@ -10,13 +10,72 @@ def install_instance_data_stats(app: Flask, app_module: Any) -> None:
|
|||||||
get_db: Callable = app_module.get_db
|
get_db: Callable = app_module.get_db
|
||||||
login_required = app_module.login_required
|
login_required = app_module.login_required
|
||||||
|
|
||||||
def _fetch_balances() -> dict[str, Any]:
|
def _fetch_equity_snapshot() -> dict[str, Any]:
|
||||||
|
from lib.instance.instance_cash_sample_lib import (
|
||||||
|
cash_usdt_from_balances,
|
||||||
|
equity_usdt_from_snapshot,
|
||||||
|
)
|
||||||
|
|
||||||
ex = getattr(app_module, "exchange_options", None)
|
ex = getattr(app_module, "exchange_options", None)
|
||||||
if ex is None:
|
bal: dict[str, Any] = {}
|
||||||
return {}
|
if ex is not None:
|
||||||
|
try:
|
||||||
from lib.exchange.okx_options_lib import fetch_options_balances
|
from lib.exchange.okx_options_lib import fetch_options_balances
|
||||||
|
|
||||||
return fetch_options_balances(ex, force=False) or {}
|
bal = fetch_options_balances(ex, force=False) or {}
|
||||||
|
except Exception:
|
||||||
|
bal = {}
|
||||||
|
cash = cash_usdt_from_balances(bal if isinstance(bal, dict) else None)
|
||||||
|
|
||||||
|
positions: list[dict[str, Any]] = []
|
||||||
|
cfg = None
|
||||||
|
try:
|
||||||
|
cfg = app.extensions.get("options_cfg")
|
||||||
|
except Exception:
|
||||||
|
cfg = None
|
||||||
|
if isinstance(cfg, dict) and cfg.get("enabled"):
|
||||||
|
try:
|
||||||
|
from lib.options.options_dashboard_lib import (
|
||||||
|
fetch_light_option_positions_for_dashboard,
|
||||||
|
)
|
||||||
|
|
||||||
|
positions = fetch_light_option_positions_for_dashboard(cfg) or []
|
||||||
|
except Exception:
|
||||||
|
positions = []
|
||||||
|
|
||||||
|
btc_px = eth_px = None
|
||||||
|
if ex is not None:
|
||||||
|
try:
|
||||||
|
from lib.options.options_stats_lib import _underlying_index_usdt
|
||||||
|
|
||||||
|
btc_px = _underlying_index_usdt(ex, "BTC")
|
||||||
|
eth_px = _underlying_index_usdt(ex, "ETH")
|
||||||
|
except Exception:
|
||||||
|
btc_px = eth_px = None
|
||||||
|
if (btc_px is None or eth_px is None) and positions:
|
||||||
|
for p in positions:
|
||||||
|
if not isinstance(p, dict):
|
||||||
|
continue
|
||||||
|
under = str(p.get("underlying") or "").upper()
|
||||||
|
idx = None
|
||||||
|
try:
|
||||||
|
idx = float(p.get("idx_px") or p.get("index_px") or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
idx = None
|
||||||
|
if idx is None or idx <= 0:
|
||||||
|
continue
|
||||||
|
if under == "BTC" and btc_px is None:
|
||||||
|
btc_px = idx
|
||||||
|
elif under == "ETH" and eth_px is None:
|
||||||
|
eth_px = idx
|
||||||
|
|
||||||
|
equity = equity_usdt_from_snapshot(
|
||||||
|
bal if isinstance(bal, dict) else None,
|
||||||
|
positions,
|
||||||
|
btc_px=btc_px,
|
||||||
|
eth_px=eth_px,
|
||||||
|
)
|
||||||
|
return {"cash_usdt": cash, "equity_usdt": equity}
|
||||||
|
|
||||||
@app.route("/api/instance/cash_curve")
|
@app.route("/api/instance/cash_curve")
|
||||||
@login_required
|
@login_required
|
||||||
@@ -26,9 +85,8 @@ def install_instance_data_stats(app: Flask, app_module: Any) -> None:
|
|||||||
sample_once,
|
sample_once,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 打开页面时尽量补一笔最新点(失败忽略)
|
|
||||||
try:
|
try:
|
||||||
sample_once(get_db, _fetch_balances)
|
sample_once(get_db, _fetch_equity_snapshot)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
range_key = (request.args.get("range") or "7d").strip()
|
range_key = (request.args.get("range") or "7d").strip()
|
||||||
@@ -40,10 +98,9 @@ def install_instance_data_stats(app: Flask, app_module: Any) -> None:
|
|||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
# 后台采样(daemon);无密钥时会空转跳过
|
|
||||||
try:
|
try:
|
||||||
from lib.instance.instance_cash_sample_lib import start_cash_sample_loop
|
from lib.instance.instance_cash_sample_lib import start_cash_sample_loop
|
||||||
|
|
||||||
start_cash_sample_loop(get_db, _fetch_balances)
|
start_cash_sample_loop(get_db, _fetch_equity_snapshot)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[instance_cash_sample] start skipped: {e}", flush=True)
|
print(f"[instance_cash_sample] start skipped: {e}", flush=True)
|
||||||
|
|||||||
@@ -68,8 +68,8 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="ods-section" aria-labelledby="ods-curve-heading">
|
<section class="ods-section" aria-labelledby="ods-curve-heading">
|
||||||
<div class="ods-section-title" id="ods-curve-heading">资金曲线</div>
|
<div class="ods-section-title" id="ods-curve-heading">权益曲线</div>
|
||||||
<p class="ods-section-desc">现金余额(交易账户+资金账户,不含权利金与浮盈);后台定时采样。</p>
|
<p class="ods-section-desc">权益=稳定币现金+现货币折U+持仓权利金U+浮盈U;后台定时采样。</p>
|
||||||
<div class="ods-curve-toolbar">
|
<div class="ods-curve-toolbar">
|
||||||
<button type="button" class="ods-range-btn active" data-range="today">今日</button>
|
<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="7d">7日</button>
|
||||||
@@ -78,8 +78,8 @@
|
|||||||
<span class="ods-curve-meta" id="ods-curve-meta">—</span>
|
<span class="ods-curve-meta" id="ods-curve-meta">—</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="ods-curve-wrap">
|
<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>
|
<svg id="ods-curve-svg" class="ods-curve-svg" viewBox="0 0 640 240" preserveAspectRatio="none" role="img" aria-label="权益曲线"></svg>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_data_stats.js?v=1"></script>
|
<script src="/static/options_data_stats.js?v=2"></script>
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
"""权益曲线采样:现金+现货币+权利金+浮盈."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class EquitySampleTests(unittest.TestCase):
|
||||||
|
def test_equity_fills_open_cliff(self):
|
||||||
|
from lib.instance.instance_cash_sample_lib import equity_usdt_from_snapshot
|
||||||
|
|
||||||
|
# 开仓后:USDT 所剩无几,BTC 缓冲 + 权利金仓位
|
||||||
|
bal = {
|
||||||
|
"trading_usdt": 50.0,
|
||||||
|
"funding_usdt": 0.0,
|
||||||
|
"trading_btc": 0.001,
|
||||||
|
"funding_btc": 0.0,
|
||||||
|
}
|
||||||
|
positions = [
|
||||||
|
{
|
||||||
|
"premium_paid": 0.01,
|
||||||
|
"premium_ccy": "BTC",
|
||||||
|
"open_index_px": 84000.0,
|
||||||
|
"idx_px": 84000.0,
|
||||||
|
"net_pnl": 0.0,
|
||||||
|
"mark": 0.01,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
# cash 50 + spot 84 + premium 840 = 974; 浮盈约 0
|
||||||
|
eq = equity_usdt_from_snapshot(bal, positions, btc_px=84000.0, eth_px=None)
|
||||||
|
self.assertIsNotNone(eq)
|
||||||
|
self.assertGreater(float(eq), 900.0)
|
||||||
|
# 纯现金会只有 50
|
||||||
|
from lib.instance.instance_cash_sample_lib import cash_usdt_from_balances
|
||||||
|
|
||||||
|
self.assertAlmostEqual(float(cash_usdt_from_balances(bal)), 50.0, places=2)
|
||||||
|
|
||||||
|
def test_query_prefers_equity_column(self):
|
||||||
|
from lib.instance.instance_cash_sample_lib import (
|
||||||
|
init_cash_sample_table,
|
||||||
|
query_cash_series_with_db,
|
||||||
|
record_cash_sample,
|
||||||
|
)
|
||||||
|
|
||||||
|
conn = sqlite3.connect(":memory:")
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
init_cash_sample_table(conn)
|
||||||
|
record_cash_sample(conn, 100.0, equity_usdt=500.0, ts_ms=1_700_000_000_000)
|
||||||
|
record_cash_sample(conn, 120.0, equity_usdt=None, ts_ms=1_700_000_060_000)
|
||||||
|
# 旧点无 equity 列时回退 cash:手动插一条仅 cash
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO instance_cash_samples(ts_ms, cash_usdt) VALUES (?, ?)",
|
||||||
|
(1_700_000_120_000, 130.0),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
out = query_cash_series_with_db(conn, range_key="all")
|
||||||
|
self.assertTrue(out["ok"])
|
||||||
|
self.assertEqual(out["metric"], "equity")
|
||||||
|
pts = out["points"]
|
||||||
|
self.assertEqual(len(pts), 3)
|
||||||
|
self.assertAlmostEqual(pts[0]["v"], 500.0, places=2)
|
||||||
|
self.assertAlmostEqual(pts[1]["v"], 120.0, places=2) # equity 默认写成 cash
|
||||||
|
self.assertAlmostEqual(pts[2]["v"], 130.0, places=2) # 历史无 equity → cash
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def test_sample_once_dict_snapshot(self):
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from lib.instance.instance_cash_sample_lib import (
|
||||||
|
query_cash_series_with_db,
|
||||||
|
sample_once,
|
||||||
|
)
|
||||||
|
|
||||||
|
tmp = Path(tempfile.mkdtemp()) / "eq.db"
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
c = sqlite3.connect(str(tmp))
|
||||||
|
c.row_factory = sqlite3.Row
|
||||||
|
return c
|
||||||
|
|
||||||
|
ok = sample_once(
|
||||||
|
get_db,
|
||||||
|
lambda: {"cash_usdt": 200.0, "equity_usdt": 950.0},
|
||||||
|
)
|
||||||
|
self.assertTrue(ok)
|
||||||
|
conn = get_db()
|
||||||
|
try:
|
||||||
|
out = query_cash_series_with_db(conn, range_key="all")
|
||||||
|
self.assertAlmostEqual(float(out["latest"]), 950.0, places=2)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user