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_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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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} <= ?
|
||||||
|
|||||||
@@ -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
@@ -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 == "止损":
|
||||||
|
|||||||
Reference in New Issue
Block a user