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:
@@ -193,7 +193,7 @@ from history_window_lib import (
|
||||
utc_window_to_bj_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):
|
||||
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]
|
||||
total = len(records)
|
||||
miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过")
|
||||
win = sum(
|
||||
1
|
||||
for r in records
|
||||
if (r.get("effective_result") or "") in ("止盈", "保本止盈", "移动止盈")
|
||||
)
|
||||
win = count_winning_trades(records)
|
||||
occupied_miss_total = sum(
|
||||
1
|
||||
for r in records
|
||||
|
||||
@@ -193,7 +193,7 @@ from history_window_lib import (
|
||||
utc_window_to_bj_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):
|
||||
@@ -6766,11 +6766,7 @@ def render_main_page(page="trade", embed_mode=None):
|
||||
records = [to_effective_trade_dict(r) for r in raw_records]
|
||||
total = len(records)
|
||||
miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过")
|
||||
win = sum(
|
||||
1
|
||||
for r in records
|
||||
if (r.get("effective_result") or "") in ("止盈", "保本止盈", "移动止盈")
|
||||
)
|
||||
win = count_winning_trades(records)
|
||||
occupied_miss_total = sum(
|
||||
1
|
||||
for r in records
|
||||
|
||||
@@ -193,7 +193,7 @@ from history_window_lib import (
|
||||
utc_window_to_bj_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):
|
||||
@@ -6766,11 +6766,7 @@ def render_main_page(page="trade", embed_mode=None):
|
||||
records = [to_effective_trade_dict(r) for r in raw_records]
|
||||
total = len(records)
|
||||
miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过")
|
||||
win = sum(
|
||||
1
|
||||
for r in records
|
||||
if (r.get("effective_result") or "") in ("止盈", "保本止盈", "移动止盈")
|
||||
)
|
||||
win = count_winning_trades(records)
|
||||
occupied_miss_total = sum(
|
||||
1
|
||||
for r in records
|
||||
|
||||
@@ -192,7 +192,7 @@ from history_window_lib import (
|
||||
utc_window_to_bj_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):
|
||||
@@ -6270,11 +6270,7 @@ def render_main_page(page="trade", embed_mode=None):
|
||||
records = [to_effective_trade_dict(r) for r in raw_records]
|
||||
total = len(records)
|
||||
miss_count = sum(1 for r in records if (r.get("effective_result") or "") == "错过")
|
||||
win = sum(
|
||||
1
|
||||
for r in records
|
||||
if (r.get("effective_result") or "") in ("止盈", "保本止盈", "移动止盈")
|
||||
)
|
||||
win = count_winning_trades(records)
|
||||
occupied_miss_total = sum(
|
||||
1
|
||||
for r in records
|
||||
|
||||
@@ -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]:
|
||||
"""顶栏统计用 COUNT,避免 embed 壳拉 1000 行交易记录。"""
|
||||
from trade_result_lib import sql_effective_pnl_expr
|
||||
|
||||
pnl_sql = sql_effective_pnl_expr()
|
||||
row = conn.execute(
|
||||
f"""
|
||||
SELECT
|
||||
COUNT(*) AS total,
|
||||
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
|
||||
FROM trade_records
|
||||
WHERE {tr_ts} >= ? AND {tr_ts} <= ?
|
||||
|
||||
@@ -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():
|
||||
@@ -15,3 +15,16 @@ def test_stop_loss_with_loss_unchanged():
|
||||
|
||||
def test_take_profit_unchanged():
|
||||
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
@@ -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):
|
||||
"""
|
||||
非手动平仓且实际盈利时,不应记为「止损」。
|
||||
程序触发的止损类平仓若盈亏为正,归类为「移动止盈」。
|
||||
"""
|
||||
res = (result or "").strip()
|
||||
res = normalize_display_result(result)
|
||||
if res == "手动平仓":
|
||||
return res
|
||||
if res == "止损":
|
||||
|
||||
Reference in New Issue
Block a user