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,9 +278,10 @@
|
|||||||
drawCurve(pts);
|
drawCurve(pts);
|
||||||
if (meta) {
|
if (meta) {
|
||||||
meta.textContent =
|
meta.textContent =
|
||||||
"权益采样 " +
|
"已实现 " +
|
||||||
|
(data.trade_count != null ? data.trade_count + " 笔 · " : "") +
|
||||||
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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -170,36 +170,106 @@ def _range_start_ms(range_key: str) -> tuple[str, int]:
|
|||||||
return "7d", now_ms - 7 * 86400 * 1000
|
return "7d", now_ms - 7 * 86400 * 1000
|
||||||
|
|
||||||
|
|
||||||
def query_cash_series_with_db(conn, *, range_key: str = "7d") -> dict[str, Any]:
|
def _closed_at_to_ms(raw: Any) -> int | None:
|
||||||
|
if raw is None or raw == "":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
from lib.options.options_stats_lib import _parse_ts
|
||||||
|
|
||||||
|
d = _parse_ts(raw)
|
||||||
|
if d is None:
|
||||||
|
return None
|
||||||
|
return int(d.timestamp() * 1000)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _trade_realized_usdt(row: Any) -> float | None:
|
||||||
|
u = _safe_float(row["realized_pnl_usdt"] if hasattr(row, "keys") else None)
|
||||||
|
if u is not None:
|
||||||
|
return round(float(u), 4)
|
||||||
|
# 兼容未冻结 U:币盈亏×平仓价
|
||||||
|
try:
|
||||||
|
coin = _safe_float(row["realized_pnl"])
|
||||||
|
px = _safe_float(row["close_index_px"])
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
if coin is None or px is None or px <= 0:
|
||||||
|
return None
|
||||||
|
return round(float(coin) * float(px), 4)
|
||||||
|
|
||||||
|
|
||||||
|
def query_realized_pnl_series_with_db(conn, *, range_key: str = "7d") -> dict[str, Any]:
|
||||||
|
"""累计已实现盈亏曲线(只随平仓变化,划转/提现不影响)."""
|
||||||
rk, start_ms = _range_start_ms(range_key)
|
rk, start_ms = _range_start_ms(range_key)
|
||||||
now_ms = int(time.time() * 1000)
|
now_ms = int(time.time() * 1000)
|
||||||
init_cash_sample_table(conn)
|
try:
|
||||||
|
from lib.options.options_db import init_options_tables
|
||||||
|
|
||||||
|
init_options_tables(conn)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"""
|
"""
|
||||||
SELECT ts_ms, cash_usdt, equity_usdt
|
SELECT id, closed_at, created_at, realized_pnl_usdt, realized_pnl, close_index_px
|
||||||
FROM instance_cash_samples
|
FROM options_trades
|
||||||
WHERE ts_ms >= ?
|
WHERE status = 'closed'
|
||||||
ORDER BY ts_ms ASC
|
ORDER BY COALESCE(closed_at, created_at) ASC, id ASC
|
||||||
""",
|
"""
|
||||||
(start_ms,),
|
|
||||||
).fetchall()
|
).fetchall()
|
||||||
points = []
|
events: list[tuple[int, float]] = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
cash = float(r["cash_usdt"])
|
pnl_u = _trade_realized_usdt(r)
|
||||||
eq = r["equity_usdt"]
|
if pnl_u is None:
|
||||||
v = float(eq) if eq is not None else cash
|
continue
|
||||||
points.append({"t": int(r["ts_ms"]), "v": v, "cash": cash})
|
ts = _closed_at_to_ms(r["closed_at"]) or _closed_at_to_ms(r["created_at"])
|
||||||
|
if ts is None:
|
||||||
|
continue
|
||||||
|
events.append((ts, float(pnl_u)))
|
||||||
|
|
||||||
|
cum = 0.0
|
||||||
|
all_points: list[dict[str, Any]] = []
|
||||||
|
for ts, pnl in events:
|
||||||
|
cum = round(cum + pnl, 4)
|
||||||
|
all_points.append({"t": int(ts), "v": cum})
|
||||||
|
|
||||||
|
baseline = 0.0
|
||||||
|
series: list[dict[str, Any]] = []
|
||||||
|
for p in all_points:
|
||||||
|
if int(p["t"]) < start_ms:
|
||||||
|
baseline = float(p["v"])
|
||||||
|
continue
|
||||||
|
if not series:
|
||||||
|
series.append({"t": int(start_ms), "v": baseline})
|
||||||
|
series.append({"t": int(p["t"]), "v": float(p["v"])})
|
||||||
|
if not series:
|
||||||
|
# 窗口内无新平仓:画水平线(历史累计或 0)
|
||||||
|
series = [
|
||||||
|
{"t": int(start_ms), "v": baseline},
|
||||||
|
{"t": int(now_ms), "v": baseline},
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
last_v = float(series[-1]["v"])
|
||||||
|
if int(series[-1]["t"]) < now_ms:
|
||||||
|
series.append({"t": int(now_ms), "v": last_v})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"range": rk,
|
"range": rk,
|
||||||
"metric": "equity",
|
"metric": "realized_pnl",
|
||||||
"points": points,
|
"points": series,
|
||||||
"start_ms": start_ms,
|
"start_ms": start_ms,
|
||||||
"end_ms": now_ms,
|
"end_ms": now_ms,
|
||||||
"latest": points[-1]["v"] if points else None,
|
"latest": series[-1]["v"] if series else None,
|
||||||
|
"trade_count": len(events),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def query_cash_series_with_db(conn, *, range_key: str = "7d") -> dict[str, Any]:
|
||||||
|
"""兼容旧名:数据统计曲线改为累计已实现盈亏."""
|
||||||
|
return query_realized_pnl_series_with_db(conn, range_key=range_key)
|
||||||
|
|
||||||
|
|
||||||
def sample_once(get_db: Callable, fetch_snapshot: Callable) -> bool:
|
def sample_once(get_db: Callable, fetch_snapshot: Callable) -> bool:
|
||||||
"""拉取快照并写入采样.
|
"""拉取快照并写入采样.
|
||||||
|
|
||||||
|
|||||||
@@ -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,97 +10,16 @@ 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_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)
|
|
||||||
bal: dict[str, Any] = {}
|
|
||||||
if ex is not None:
|
|
||||||
try:
|
|
||||||
from lib.exchange.okx_options_lib import fetch_options_balances
|
|
||||||
|
|
||||||
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
|
||||||
def api_instance_cash_curve():
|
def api_instance_cash_curve():
|
||||||
from lib.instance.instance_cash_sample_lib import (
|
from lib.instance.instance_cash_sample_lib import query_realized_pnl_series_with_db
|
||||||
query_cash_series_with_db,
|
|
||||||
sample_once,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
sample_once(get_db, _fetch_equity_snapshot)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
range_key = (request.args.get("range") or "7d").strip()
|
range_key = (request.args.get("range") or "7d").strip()
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
try:
|
try:
|
||||||
return jsonify(query_cash_series_with_db(conn, range_key=range_key))
|
return jsonify(query_realized_pnl_series_with_db(conn, range_key=range_key))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"ok": False, "msg": f"加载资金曲线失败: {e}"}), 500
|
return jsonify({"ok": False, "msg": f"加载盈亏曲线失败: {e}"}), 500
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
try:
|
|
||||||
from lib.instance.instance_cash_sample_lib import start_cash_sample_loop
|
|
||||||
|
|
||||||
start_cash_sample_loop(get_db, _fetch_equity_snapshot)
|
|
||||||
except Exception as e:
|
|
||||||
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">权益=稳定币现金+现货币折U+持仓权利金U+浮盈U;后台定时采样。</p>
|
<p class="ods-section-desc">累计已实现盈亏(期权平仓真盈亏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=2"></script>
|
<script src="/static/options_data_stats.js?v=3"></script>
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
"""权益曲线采样:现金+现货币+权利金+浮盈."""
|
"""权益采样辅助(曲线已改为已实现盈亏,此处仅覆盖权益计算工具)."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
class EquitySampleTests(unittest.TestCase):
|
class EquitySampleTests(unittest.TestCase):
|
||||||
def test_equity_fills_open_cliff(self):
|
def test_equity_fills_open_cliff(self):
|
||||||
from lib.instance.instance_cash_sample_lib import equity_usdt_from_snapshot
|
from lib.instance.instance_cash_sample_lib import (
|
||||||
|
cash_usdt_from_balances,
|
||||||
|
equity_usdt_from_snapshot,
|
||||||
|
)
|
||||||
|
|
||||||
# 开仓后:USDT 所剩无几,BTC 缓冲 + 权利金仓位
|
|
||||||
bal = {
|
bal = {
|
||||||
"trading_usdt": 50.0,
|
"trading_usdt": 50.0,
|
||||||
"funding_usdt": 0.0,
|
"funding_usdt": 0.0,
|
||||||
@@ -26,49 +30,15 @@ class EquitySampleTests(unittest.TestCase):
|
|||||||
"mark": 0.01,
|
"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)
|
eq = equity_usdt_from_snapshot(bal, positions, btc_px=84000.0, eth_px=None)
|
||||||
self.assertIsNotNone(eq)
|
self.assertIsNotNone(eq)
|
||||||
self.assertGreater(float(eq), 900.0)
|
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)
|
self.assertAlmostEqual(float(cash_usdt_from_balances(bal)), 50.0, places=2)
|
||||||
|
|
||||||
def test_query_prefers_equity_column(self):
|
def test_record_writes_equity_column(self):
|
||||||
from lib.instance.instance_cash_sample_lib import (
|
from lib.instance.instance_cash_sample_lib import (
|
||||||
init_cash_sample_table,
|
init_cash_sample_table,
|
||||||
query_cash_series_with_db,
|
|
||||||
record_cash_sample,
|
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,
|
sample_once,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,8 +56,19 @@ class EquitySampleTests(unittest.TestCase):
|
|||||||
self.assertTrue(ok)
|
self.assertTrue(ok)
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
try:
|
try:
|
||||||
out = query_cash_series_with_db(conn, range_key="all")
|
init_cash_sample_table(conn)
|
||||||
self.assertAlmostEqual(float(out["latest"]), 950.0, places=2)
|
row = conn.execute(
|
||||||
|
"SELECT cash_usdt, equity_usdt FROM instance_cash_samples ORDER BY ts_ms DESC LIMIT 1"
|
||||||
|
).fetchone()
|
||||||
|
self.assertAlmostEqual(float(row["cash_usdt"]), 200.0, places=2)
|
||||||
|
self.assertAlmostEqual(float(row["equity_usdt"]), 950.0, places=2)
|
||||||
|
record_cash_sample(conn, 100.0, equity_usdt=500.0, ts_ms=1_700_000_000_000)
|
||||||
|
conn.commit()
|
||||||
|
row2 = conn.execute(
|
||||||
|
"SELECT equity_usdt FROM instance_cash_samples WHERE ts_ms=?",
|
||||||
|
(1_700_000_000_000,),
|
||||||
|
).fetchone()
|
||||||
|
self.assertAlmostEqual(float(row2["equity_usdt"]), 500.0, places=2)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""已实现盈亏曲线:累计平仓真盈亏,不受划转影响."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class RealizedPnlCurveTests(unittest.TestCase):
|
||||||
|
def _init(self):
|
||||||
|
from lib.options.options_db import init_options_tables
|
||||||
|
|
||||||
|
conn = sqlite3.connect(":memory:")
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
init_options_tables(conn)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def test_cumulative_steps(self):
|
||||||
|
from lib.instance.instance_cash_sample_lib import query_realized_pnl_series_with_db
|
||||||
|
|
||||||
|
conn = self._init()
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO options_trades(
|
||||||
|
inst_id, underlying, opt_type, sheets, eth_amount,
|
||||||
|
premium_paid, premium_received, realized_pnl, premium_ccy,
|
||||||
|
status, realized_pnl_usdt, close_index_px,
|
||||||
|
created_at, closed_at
|
||||||
|
) VALUES
|
||||||
|
('BTC-A','BTC','P',1,0.01,0.01,0.02,0.01,'BTC','closed',100.0,80000,
|
||||||
|
'2026-10-01 10:00:00','2026-10-01 12:00:00'),
|
||||||
|
('BTC-B','BTC','P',1,0.01,0.01,0.02,0.01,'BTC','closed',50.0,80000,
|
||||||
|
'2026-10-02 10:00:00','2026-10-02 12:00:00'),
|
||||||
|
('BTC-C','BTC','P',1,0.01,0.01,0.02,-0.01,'BTC','closed',-20.0,80000,
|
||||||
|
'2026-10-03 10:00:00','2026-10-03 12:00:00')
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
out = query_realized_pnl_series_with_db(conn, range_key="all")
|
||||||
|
self.assertTrue(out["ok"])
|
||||||
|
self.assertEqual(out["metric"], "realized_pnl")
|
||||||
|
self.assertEqual(out["trade_count"], 3)
|
||||||
|
# 点含区间起点/各笔台阶/延伸到 now;累计最终 130
|
||||||
|
self.assertAlmostEqual(float(out["latest"]), 130.0, places=2)
|
||||||
|
vals = [p["v"] for p in out["points"]]
|
||||||
|
self.assertIn(100.0, vals)
|
||||||
|
self.assertIn(150.0, vals)
|
||||||
|
self.assertIn(130.0, vals)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def test_range_carries_baseline(self):
|
||||||
|
from lib.instance.instance_cash_sample_lib import query_realized_pnl_series_with_db
|
||||||
|
|
||||||
|
conn = self._init()
|
||||||
|
# 一笔很早的盈利,窗口内无新单 → 水平线在 80
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO options_trades(
|
||||||
|
inst_id, underlying, opt_type, sheets, eth_amount,
|
||||||
|
premium_paid, premium_received, realized_pnl, premium_ccy,
|
||||||
|
status, realized_pnl_usdt, created_at, closed_at
|
||||||
|
) VALUES (
|
||||||
|
'BTC-OLD','BTC','P',1,0.01,0.01,0.02,0.01,'BTC',
|
||||||
|
'closed', 80.0, '2020-01-01 10:00:00', '2020-01-01 12:00:00'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
out = query_realized_pnl_series_with_db(conn, range_key="7d")
|
||||||
|
self.assertAlmostEqual(float(out["latest"]), 80.0, places=2)
|
||||||
|
self.assertTrue(all(abs(float(p["v"]) - 80.0) < 1e-6 for p in out["points"]))
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user