Count instance win rate by positive PnL and show external closes as manual close.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 22:27:52 +08:00
parent 0a20ee7eec
commit 448e88ec55
7 changed files with 54 additions and 27 deletions
+2 -6
View File
@@ -193,7 +193,7 @@ from history_window_lib import (
utc_window_to_bj_sql_strings, utc_window_to_bj_sql_strings,
utc_window_to_utc_sql_strings, utc_window_to_utc_sql_strings,
) )
from trade_result_lib import normalize_result_with_pnl from trade_result_lib import count_winning_trades, normalize_result_with_pnl
def load_env_file(path): def load_env_file(path):
if not os.path.exists(path): if not os.path.exists(path):
@@ -6883,11 +6883,7 @@ def render_main_page(page="trade", embed_mode=None):
records = [to_effective_trade_dict(r) for r in raw_records] records = [to_effective_trade_dict(r) for r in raw_records]
total = len(records) total = len(records)
miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过") miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过")
win = sum( win = count_winning_trades(records)
1
for r in records
if (r.get("effective_result") or "") in ("止盈", "保本止盈", "移动止盈")
)
occupied_miss_total = sum( occupied_miss_total = sum(
1 1
for r in records for r in records
+2 -6
View File
@@ -193,7 +193,7 @@ from history_window_lib import (
utc_window_to_bj_sql_strings, utc_window_to_bj_sql_strings,
utc_window_to_utc_sql_strings, utc_window_to_utc_sql_strings,
) )
from trade_result_lib import normalize_result_with_pnl from trade_result_lib import count_winning_trades, normalize_result_with_pnl
def load_env_file(path): def load_env_file(path):
@@ -6766,11 +6766,7 @@ def render_main_page(page="trade", embed_mode=None):
records = [to_effective_trade_dict(r) for r in raw_records] records = [to_effective_trade_dict(r) for r in raw_records]
total = len(records) total = len(records)
miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过") miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过")
win = sum( win = count_winning_trades(records)
1
for r in records
if (r.get("effective_result") or "") in ("止盈", "保本止盈", "移动止盈")
)
occupied_miss_total = sum( occupied_miss_total = sum(
1 1
for r in records for r in records
+2 -6
View File
@@ -193,7 +193,7 @@ from history_window_lib import (
utc_window_to_bj_sql_strings, utc_window_to_bj_sql_strings,
utc_window_to_utc_sql_strings, utc_window_to_utc_sql_strings,
) )
from trade_result_lib import normalize_result_with_pnl from trade_result_lib import count_winning_trades, normalize_result_with_pnl
def load_env_file(path): def load_env_file(path):
@@ -6766,11 +6766,7 @@ def render_main_page(page="trade", embed_mode=None):
records = [to_effective_trade_dict(r) for r in raw_records] records = [to_effective_trade_dict(r) for r in raw_records]
total = len(records) total = len(records)
miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过") miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过")
win = sum( win = count_winning_trades(records)
1
for r in records
if (r.get("effective_result") or "") in ("止盈", "保本止盈", "移动止盈")
)
occupied_miss_total = sum( occupied_miss_total = sum(
1 1
for r in records for r in records
+2 -6
View File
@@ -192,7 +192,7 @@ from history_window_lib import (
utc_window_to_bj_sql_strings, utc_window_to_bj_sql_strings,
utc_window_to_utc_sql_strings, utc_window_to_utc_sql_strings,
) )
from trade_result_lib import normalize_result_with_pnl from trade_result_lib import count_winning_trades, normalize_result_with_pnl
def load_env_file(path): def load_env_file(path):
@@ -6270,11 +6270,7 @@ def render_main_page(page="trade", embed_mode=None):
records = [to_effective_trade_dict(r) for r in raw_records] records = [to_effective_trade_dict(r) for r in raw_records]
total = len(records) total = len(records)
miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过") miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过")
win = sum( win = count_winning_trades(records)
1
for r in records
if (r.get("effective_result") or "") in ("止盈", "保本止盈", "移动止盈")
)
occupied_miss_total = sum( occupied_miss_total = sum(
1 1
for r in records for r in records
+4 -1
View File
@@ -51,12 +51,15 @@ def embed_render_plan(page: str, embed_mode: str | None) -> EmbedRenderPlan:
def trade_records_summary(conn, start_bj: str, end_bj: str, tr_ts: str) -> dict[str, Any]: def trade_records_summary(conn, start_bj: str, end_bj: str, tr_ts: str) -> dict[str, Any]:
"""顶栏统计用 COUNT,避免 embed 壳拉 1000 行交易记录。""" """顶栏统计用 COUNT,避免 embed 壳拉 1000 行交易记录。"""
from trade_result_lib import sql_effective_pnl_expr
pnl_sql = sql_effective_pnl_expr()
row = conn.execute( row = conn.execute(
f""" f"""
SELECT SELECT
COUNT(*) AS total, COUNT(*) AS total,
SUM(CASE WHEN result = '错过' THEN 1 ELSE 0 END) AS miss_count, SUM(CASE WHEN result = '错过' THEN 1 ELSE 0 END) AS miss_count,
SUM(CASE WHEN result IN ('止盈','保本止盈','移动止盈') THEN 1 ELSE 0 END) AS wins, SUM(CASE WHEN {pnl_sql} > 0 THEN 1 ELSE 0 END) AS wins,
SUM(CASE WHEN result = '错过' AND COALESCE(miss_reason,'') LIKE '%持仓占用%' THEN 1 ELSE 0 END) AS occupied_miss SUM(CASE WHEN result = '错过' AND COALESCE(miss_reason,'') LIKE '%持仓占用%' THEN 1 ELSE 0 END) AS occupied_miss
FROM trade_records FROM trade_records
WHERE {tr_ts} >= ? AND {tr_ts} <= ? WHERE {tr_ts} >= ? AND {tr_ts} <= ?
+14 -1
View File
@@ -1,4 +1,4 @@
from trade_result_lib import normalize_result_with_pnl from trade_result_lib import normalize_result_with_pnl, normalize_display_result, is_winning_pnl
def test_stop_loss_with_profit_becomes_trailing_tp(): def test_stop_loss_with_profit_becomes_trailing_tp():
@@ -15,3 +15,16 @@ def test_stop_loss_with_loss_unchanged():
def test_take_profit_unchanged(): def test_take_profit_unchanged():
assert normalize_result_with_pnl("止盈", 5) == "止盈" assert normalize_result_with_pnl("止盈", 5) == "止盈"
def test_external_close_becomes_manual_close():
assert normalize_display_result("外部平仓") == "手动平仓"
assert normalize_result_with_pnl("外部平仓", 2.5) == "手动平仓"
assert normalize_result_with_pnl("外部平仓(自动同步)", -1) == "手动平仓"
def test_winning_pnl_positive_only():
assert is_winning_pnl(2.96) is True
assert is_winning_pnl(0) is False
assert is_winning_pnl(-1.05) is False
assert is_winning_pnl(None) is False
+28 -1
View File
@@ -1,12 +1,39 @@
"""交易结果展示与入库时的语义归一化。""" """交易结果展示与入库时的语义归一化。"""
_WIN_EPS = 1e-9
def normalize_display_result(result):
"""展示用:外部平仓一律视为手动平仓。"""
res = (result or "").strip()
if res == "外部平仓" or res.startswith("外部平仓"):
return "手动平仓"
return res
def is_winning_pnl(pnl_amount) -> bool:
"""胜率统计:盈亏为正即计为盈利单。"""
try:
return float(pnl_amount or 0) > _WIN_EPS
except (TypeError, ValueError):
return False
def sql_effective_pnl_expr() -> str:
"""与 to_effective_trade_dict / hub_trades_lib 一致的盈亏 SQL 表达式。"""
return "COALESCE(reviewed_pnl_amount, exchange_realized_pnl, pnl_amount, 0)"
def count_winning_trades(trades) -> int:
return sum(1 for r in trades or [] if is_winning_pnl(r.get("effective_pnl_amount")))
def normalize_result_with_pnl(result, pnl_amount): def normalize_result_with_pnl(result, pnl_amount):
""" """
非手动平仓且实际盈利时,不应记为「止损」。 非手动平仓且实际盈利时,不应记为「止损」。
程序触发的止损类平仓若盈亏为正,归类为「移动止盈」。 程序触发的止损类平仓若盈亏为正,归类为「移动止盈」。
""" """
res = (result or "").strip() res = normalize_display_result(result)
if res == "手动平仓": if res == "手动平仓":
return res return res
if res == "止损": if res == "止损":