Fix trades API 500 by supporting sqlite Row in hold timing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-27 15:41:27 +08:00
parent 14c6ba51a8
commit 3d7c453bd1
3 changed files with 56 additions and 13 deletions
+18 -7
View File
@@ -18,10 +18,20 @@ def _ts(v: Any) -> int | None:
return n if n > 0 else None
def first_perp_close_ts_ms(fills: Sequence[Mapping[str, Any]]) -> int | None:
def _as_map(row: Mapping[str, Any] | Any) -> Mapping[str, Any]:
if isinstance(row, Mapping):
return row
try:
return dict(row)
except Exception:
return {}
def first_perp_close_ts_ms(fills: Sequence[Mapping[str, Any] | Any]) -> int | None:
"""永续平仓成交时间(目标只平永续时作为持仓结束时刻)。"""
best: int | None = None
for f in fills:
for raw in fills:
f = _as_map(raw)
if str(f.get("leg") or "") != "perp":
continue
if str(f.get("action") or "") != "close":
@@ -35,7 +45,7 @@ def first_perp_close_ts_ms(fills: Sequence[Mapping[str, Any]]) -> int | None:
def hold_timing(
group: Mapping[str, Any], fills: Sequence[Mapping[str, Any]]
group: Mapping[str, Any] | Any, fills: Sequence[Mapping[str, Any] | Any]
) -> dict[str, Any]:
"""
返回展示用开仓/平仓/持仓时长。
@@ -45,10 +55,11 @@ def hold_timing(
- `target_perp_only` / `option_residual`:永续平仓 fill 时间
- 其它已平:groups.close_at_ms(缺则回退成交)
"""
open_ms = _ts(group.get("open_at_ms"))
status = str(group.get("status") or "")
reason = str(group.get("close_reason") or "")
group_close = _ts(group.get("close_at_ms"))
g = _as_map(group)
open_ms = _ts(g.get("open_at_ms"))
status = str(g.get("status") or "")
reason = str(g.get("close_reason") or "")
group_close = _ts(g.get("close_at_ms"))
perp_close = first_perp_close_ts_ms(fills)
use_perp = reason == "target_perp_only" or status == "option_residual"
+29 -6
View File
@@ -55,9 +55,32 @@ def test_hold_dual_leg_uses_group_close():
assert h["hold_basis"] == "group"
def test_hold_open_no_close():
g = {"open_at_ms": 100, "close_at_ms": None, "status": "open", "close_reason": None}
h = hold_timing(g, [])
assert h["hold_close_at_ms"] is None
assert h["hold_ms"] is None
assert h["hold_basis"] == "open"
def test_hold_sqlite_row_like_without_get():
"""sqlite3.Row 无 .get,需能转 dict。"""
class Row:
def __init__(self, d):
self._d = d
def keys(self):
return self._d.keys()
def __getitem__(self, k):
return self._d[k]
def __iter__(self):
return iter(self._d)
g = Row(
{
"open_at_ms": 100,
"close_at_ms": None,
"status": "option_residual",
"close_reason": "target_perp_only",
}
)
fills = [Row({"leg": "perp", "action": "close", "ts_ms": 400})]
h = hold_timing(g, fills)
assert h["hold_close_at_ms"] == 400
assert h["hold_ms"] == 300
+9
View File
@@ -5,6 +5,15 @@
---
## 2026-07-27 — 修复交易记录 500(记录未丢)
### 变更
1. `/api/trades/groups``sqlite3.Row``.get` 抛错导致「Internal Server Error / 暂无成交组」假象。
2. 持仓周期计算改为兼容 Row;库内成交组仍在。
---
## 2026-07-27 — 交易记录:开仓/平仓时间与持仓周期
### 变更