feat: expand header stats with total funds and profit-loss ratio
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7134,6 +7134,8 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
from lib.instance.instance_embed_context_lib import (
|
from lib.instance.instance_embed_context_lib import (
|
||||||
embed_render_plan,
|
embed_render_plan,
|
||||||
minimal_stats_bundle,
|
minimal_stats_bundle,
|
||||||
|
profit_loss_ratio_from_trades,
|
||||||
|
total_funds_usdt,
|
||||||
trade_records_summary,
|
trade_records_summary,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -7185,14 +7187,17 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
total = len(records)
|
total = len(records)
|
||||||
win = count_winning_trades(records)
|
win = count_winning_trades(records)
|
||||||
rate = round(win / total * 100, 2) if total else 0
|
rate = round(win / total * 100, 2) if total else 0
|
||||||
|
profit_loss_ratio = profit_loss_ratio_from_trades(records)
|
||||||
elif plan.records_summary:
|
elif plan.records_summary:
|
||||||
summary = trade_records_summary(conn, start_bj, end_bj, tr_ts)
|
summary = trade_records_summary(conn, start_bj, end_bj, tr_ts)
|
||||||
records = summary["records"]
|
records = summary["records"]
|
||||||
total = summary["total"]
|
total = summary["total"]
|
||||||
rate = summary["rate"]
|
rate = summary["rate"]
|
||||||
|
profit_loss_ratio = summary.get("profit_loss_ratio")
|
||||||
else:
|
else:
|
||||||
records = []
|
records = []
|
||||||
total = rate = 0
|
total = rate = 0
|
||||||
|
profit_loss_ratio = None
|
||||||
active_count = len(order_list)
|
active_count = len(order_list)
|
||||||
from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors
|
from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
@@ -7251,6 +7256,8 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
record=records,
|
record=records,
|
||||||
total=total,
|
total=total,
|
||||||
rate=rate,
|
rate=rate,
|
||||||
|
profit_loss_ratio=profit_loss_ratio,
|
||||||
|
total_funds=total_funds_usdt(funding_usdt, current_capital),
|
||||||
trading_day=trading_day,
|
trading_day=trading_day,
|
||||||
funding_usdt=funding_usdt,
|
funding_usdt=funding_usdt,
|
||||||
daily_start_capital=DAILY_START_CAPITAL,
|
daily_start_capital=DAILY_START_CAPITAL,
|
||||||
@@ -7403,6 +7410,8 @@ def api_account_snapshot():
|
|||||||
extra_blocks=not risk_status.get("can_trade", True),
|
extra_blocks=not risk_status.get("can_trade", True),
|
||||||
)
|
)
|
||||||
available_trading_usdt = get_available_trading_usdt()
|
available_trading_usdt = get_available_trading_usdt()
|
||||||
|
from lib.instance.instance_embed_context_lib import total_funds_usdt
|
||||||
|
|
||||||
unrealized_pnl = None
|
unrealized_pnl = None
|
||||||
if exchange_private_api_configured():
|
if exchange_private_api_configured():
|
||||||
from lib.instance.instance_live_pnl_lib import resolve_instance_unrealized_pnl
|
from lib.instance.instance_live_pnl_lib import resolve_instance_unrealized_pnl
|
||||||
@@ -7419,6 +7428,7 @@ def api_account_snapshot():
|
|||||||
return jsonify({
|
return jsonify({
|
||||||
"funding_usdt": funding_usdt,
|
"funding_usdt": funding_usdt,
|
||||||
"current_capital": current_capital,
|
"current_capital": current_capital,
|
||||||
|
"total_funds": total_funds_usdt(funding_usdt, current_capital),
|
||||||
"available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None,
|
"available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None,
|
||||||
"unrealized_pnl": unrealized_pnl,
|
"unrealized_pnl": unrealized_pnl,
|
||||||
"recommended_capital": recommended_capital,
|
"recommended_capital": recommended_capital,
|
||||||
|
|||||||
@@ -6922,6 +6922,8 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
from lib.instance.instance_embed_context_lib import (
|
from lib.instance.instance_embed_context_lib import (
|
||||||
embed_render_plan,
|
embed_render_plan,
|
||||||
minimal_stats_bundle,
|
minimal_stats_bundle,
|
||||||
|
profit_loss_ratio_from_trades,
|
||||||
|
total_funds_usdt,
|
||||||
trade_records_summary,
|
trade_records_summary,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -6982,14 +6984,17 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
total = len(records)
|
total = len(records)
|
||||||
win = count_winning_trades(records)
|
win = count_winning_trades(records)
|
||||||
rate = round(win / total * 100, 2) if total else 0
|
rate = round(win / total * 100, 2) if total else 0
|
||||||
|
profit_loss_ratio = profit_loss_ratio_from_trades(records)
|
||||||
elif plan.records_summary:
|
elif plan.records_summary:
|
||||||
summary = trade_records_summary(conn, start_bj, end_bj, tr_ts)
|
summary = trade_records_summary(conn, start_bj, end_bj, tr_ts)
|
||||||
records = summary["records"]
|
records = summary["records"]
|
||||||
total = summary["total"]
|
total = summary["total"]
|
||||||
rate = summary["rate"]
|
rate = summary["rate"]
|
||||||
|
profit_loss_ratio = summary.get("profit_loss_ratio")
|
||||||
else:
|
else:
|
||||||
records = []
|
records = []
|
||||||
total = rate = 0
|
total = rate = 0
|
||||||
|
profit_loss_ratio = None
|
||||||
active_count = len(order_list)
|
active_count = len(order_list)
|
||||||
from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors
|
from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
@@ -7044,6 +7049,8 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
record=records,
|
record=records,
|
||||||
total=total,
|
total=total,
|
||||||
rate=rate,
|
rate=rate,
|
||||||
|
profit_loss_ratio=profit_loss_ratio,
|
||||||
|
total_funds=total_funds_usdt(funding_usdt, current_capital),
|
||||||
trading_day=trading_day,
|
trading_day=trading_day,
|
||||||
funding_usdt=funding_usdt,
|
funding_usdt=funding_usdt,
|
||||||
daily_start_capital=DAILY_START_CAPITAL,
|
daily_start_capital=DAILY_START_CAPITAL,
|
||||||
@@ -7218,6 +7225,8 @@ def api_account_snapshot():
|
|||||||
extra_blocks=not risk_status.get("can_trade", True),
|
extra_blocks=not risk_status.get("can_trade", True),
|
||||||
)
|
)
|
||||||
available_trading_usdt = get_available_trading_usdt()
|
available_trading_usdt = get_available_trading_usdt()
|
||||||
|
from lib.instance.instance_embed_context_lib import total_funds_usdt
|
||||||
|
|
||||||
unrealized_pnl = None
|
unrealized_pnl = None
|
||||||
if exchange_private_api_configured():
|
if exchange_private_api_configured():
|
||||||
from lib.instance.instance_live_pnl_lib import resolve_instance_unrealized_pnl
|
from lib.instance.instance_live_pnl_lib import resolve_instance_unrealized_pnl
|
||||||
@@ -7237,6 +7246,7 @@ def api_account_snapshot():
|
|||||||
return jsonify({
|
return jsonify({
|
||||||
"funding_usdt": funding_usdt,
|
"funding_usdt": funding_usdt,
|
||||||
"current_capital": current_capital,
|
"current_capital": current_capital,
|
||||||
|
"total_funds": total_funds_usdt(funding_usdt, current_capital),
|
||||||
"available_trading_usdt": round(available_trading_usdt, 2) if available_trading_usdt is not None else None,
|
"available_trading_usdt": round(available_trading_usdt, 2) if available_trading_usdt is not None else None,
|
||||||
"unrealized_pnl": unrealized_pnl,
|
"unrealized_pnl": unrealized_pnl,
|
||||||
"recommended_capital": recommended_capital,
|
"recommended_capital": recommended_capital,
|
||||||
|
|||||||
@@ -6471,6 +6471,8 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
from lib.instance.instance_embed_context_lib import (
|
from lib.instance.instance_embed_context_lib import (
|
||||||
embed_render_plan,
|
embed_render_plan,
|
||||||
minimal_stats_bundle,
|
minimal_stats_bundle,
|
||||||
|
profit_loss_ratio_from_trades,
|
||||||
|
total_funds_usdt,
|
||||||
trade_records_summary,
|
trade_records_summary,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -6530,14 +6532,17 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
total = len(records)
|
total = len(records)
|
||||||
win = count_winning_trades(records)
|
win = count_winning_trades(records)
|
||||||
rate = round(win / total * 100, 2) if total else 0
|
rate = round(win / total * 100, 2) if total else 0
|
||||||
|
profit_loss_ratio = profit_loss_ratio_from_trades(records)
|
||||||
elif plan.records_summary:
|
elif plan.records_summary:
|
||||||
summary = trade_records_summary(conn, start_bj, end_bj, tr_ts)
|
summary = trade_records_summary(conn, start_bj, end_bj, tr_ts)
|
||||||
records = summary["records"]
|
records = summary["records"]
|
||||||
total = summary["total"]
|
total = summary["total"]
|
||||||
rate = summary["rate"]
|
rate = summary["rate"]
|
||||||
|
profit_loss_ratio = summary.get("profit_loss_ratio")
|
||||||
else:
|
else:
|
||||||
records = []
|
records = []
|
||||||
total = rate = 0
|
total = rate = 0
|
||||||
|
profit_loss_ratio = None
|
||||||
active_count = len(order_list)
|
active_count = len(order_list)
|
||||||
from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors
|
from lib.strategy.strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
@@ -6594,6 +6599,8 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
record=records,
|
record=records,
|
||||||
total=total,
|
total=total,
|
||||||
rate=rate,
|
rate=rate,
|
||||||
|
profit_loss_ratio=profit_loss_ratio,
|
||||||
|
total_funds=total_funds_usdt(funding_usdt, current_capital),
|
||||||
trading_day=trading_day,
|
trading_day=trading_day,
|
||||||
daily_start_capital=DAILY_START_CAPITAL,
|
daily_start_capital=DAILY_START_CAPITAL,
|
||||||
current_capital=current_capital,
|
current_capital=current_capital,
|
||||||
@@ -6764,6 +6771,8 @@ def api_account_snapshot():
|
|||||||
extra_blocks=not risk_status.get("can_trade", True),
|
extra_blocks=not risk_status.get("can_trade", True),
|
||||||
)
|
)
|
||||||
available_trading_usdt = get_available_trading_usdt()
|
available_trading_usdt = get_available_trading_usdt()
|
||||||
|
from lib.instance.instance_embed_context_lib import total_funds_usdt
|
||||||
|
|
||||||
unrealized_pnl = None
|
unrealized_pnl = None
|
||||||
if exchange_private_api_configured():
|
if exchange_private_api_configured():
|
||||||
from lib.instance.instance_live_pnl_lib import resolve_instance_unrealized_pnl
|
from lib.instance.instance_live_pnl_lib import resolve_instance_unrealized_pnl
|
||||||
@@ -6783,6 +6792,7 @@ def api_account_snapshot():
|
|||||||
return jsonify({
|
return jsonify({
|
||||||
"funding_usdt": funding_usdt,
|
"funding_usdt": funding_usdt,
|
||||||
"current_capital": current_capital,
|
"current_capital": current_capital,
|
||||||
|
"total_funds": total_funds_usdt(funding_usdt, current_capital),
|
||||||
"available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None,
|
"available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None,
|
||||||
"unrealized_pnl": unrealized_pnl,
|
"unrealized_pnl": unrealized_pnl,
|
||||||
"recommended_capital": recommended_capital,
|
"recommended_capital": recommended_capital,
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ html[data-theme="light"] .theme-toggle-btn.is-active {
|
|||||||
|
|
||||||
.instance-header-stats {
|
.instance-header-stats {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
grid-template-columns: repeat(9, minmax(0, 1fr));
|
||||||
gap: 0;
|
gap: 0;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
padding: 14px 0 12px;
|
padding: 14px 0 12px;
|
||||||
@@ -667,7 +667,7 @@ html[data-theme="light"] .theme-toggle-btn.is-active {
|
|||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.instance-header-stats {
|
.instance-header-stats {
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
row-gap: 10px;
|
row-gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from typing import Any
|
|||||||
|
|
||||||
EMBED_STRATEGY_PAGES = frozenset({"strategy", "strategy_trend", "strategy_roll", "strategy_records"})
|
EMBED_STRATEGY_PAGES = frozenset({"strategy", "strategy_trend", "strategy_roll", "strategy_records"})
|
||||||
|
|
||||||
|
_WIN_EPS = 1e-9
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class EmbedRenderPlan:
|
class EmbedRenderPlan:
|
||||||
@@ -50,6 +52,48 @@ def embed_render_plan(page: str, embed_mode: str | None) -> EmbedRenderPlan:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def profit_loss_ratio_from_averages(avg_win: float | None, avg_loss: float | None) -> float | None:
|
||||||
|
"""盈亏比 = 平均盈利 / |平均亏损|。"""
|
||||||
|
if avg_win is None or avg_loss is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
aw = float(avg_win)
|
||||||
|
al = float(avg_loss)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
if al == 0:
|
||||||
|
return None
|
||||||
|
return round(aw / abs(al), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def profit_loss_ratio_from_trades(trades: list[dict[str, Any]] | None) -> float | None:
|
||||||
|
wins: list[float] = []
|
||||||
|
losses: list[float] = []
|
||||||
|
for row in trades or []:
|
||||||
|
if not isinstance(row, dict):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
pnl = float(row.get("effective_pnl_amount") or row.get("pnl_amount") or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
if pnl > _WIN_EPS:
|
||||||
|
wins.append(pnl)
|
||||||
|
elif pnl < -_WIN_EPS:
|
||||||
|
losses.append(pnl)
|
||||||
|
avg_win = sum(wins) / len(wins) if wins else None
|
||||||
|
avg_loss = sum(losses) / len(losses) if losses else None
|
||||||
|
return profit_loss_ratio_from_averages(avg_win, avg_loss)
|
||||||
|
|
||||||
|
|
||||||
|
def total_funds_usdt(funding_usdt: float | None, trading_usdt: float | None) -> float | None:
|
||||||
|
if funding_usdt is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return round(float(funding_usdt) + float(trading_usdt or 0), 2)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
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 lib.trade.trade_result_lib import sql_effective_pnl_expr
|
from lib.trade.trade_result_lib import sql_effective_pnl_expr
|
||||||
@@ -59,7 +103,9 @@ def trade_records_summary(conn, start_bj: str, end_bj: str, tr_ts: str) -> dict[
|
|||||||
f"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) AS total,
|
COUNT(*) AS total,
|
||||||
SUM(CASE WHEN {pnl_sql} > 0 THEN 1 ELSE 0 END) AS wins
|
SUM(CASE WHEN {pnl_sql} > 0 THEN 1 ELSE 0 END) AS wins,
|
||||||
|
AVG(CASE WHEN {pnl_sql} > 0 THEN {pnl_sql} END) AS avg_win,
|
||||||
|
AVG(CASE WHEN {pnl_sql} < 0 THEN {pnl_sql} END) AS avg_loss
|
||||||
FROM trade_records
|
FROM trade_records
|
||||||
WHERE {tr_ts} >= ? AND {tr_ts} <= ?
|
WHERE {tr_ts} >= ? AND {tr_ts} <= ?
|
||||||
AND COALESCE(result, '') != '错过'
|
AND COALESCE(result, '') != '错过'
|
||||||
@@ -70,10 +116,13 @@ def trade_records_summary(conn, start_bj: str, end_bj: str, tr_ts: str) -> dict[
|
|||||||
total = int(row["total"] or 0) if row else 0
|
total = int(row["total"] or 0) if row else 0
|
||||||
wins = int(row["wins"] or 0) if row else 0
|
wins = int(row["wins"] or 0) if row else 0
|
||||||
rate = round(wins / total * 100, 2) if total else 0
|
rate = round(wins / total * 100, 2) if total else 0
|
||||||
|
avg_win = float(row["avg_win"]) if row and row["avg_win"] is not None else None
|
||||||
|
avg_loss = float(row["avg_loss"]) if row and row["avg_loss"] is not None else None
|
||||||
return {
|
return {
|
||||||
"records": [],
|
"records": [],
|
||||||
"total": total,
|
"total": total,
|
||||||
"rate": rate,
|
"rate": rate,
|
||||||
|
"profit_loss_ratio": profit_loss_ratio_from_averages(avg_win, avg_loss),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1053,6 +1053,10 @@ function refreshAccountSnapshot(){
|
|||||||
const el = document.getElementById("total-capital");
|
const el = document.getElementById("total-capital");
|
||||||
if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${Number(data.funding_usdt).toFixed(2)}U`;
|
if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${Number(data.funding_usdt).toFixed(2)}U`;
|
||||||
}
|
}
|
||||||
|
if (typeof data.total_funds !== "undefined") {
|
||||||
|
const el = document.getElementById("total-funds");
|
||||||
|
if(el) el.innerText = (data.total_funds === null || data.total_funds === undefined) ? "—" : `${Number(data.total_funds).toFixed(2)}U`;
|
||||||
|
}
|
||||||
if (typeof data.current_capital !== "undefined") {
|
if (typeof data.current_capital !== "undefined") {
|
||||||
const el = document.getElementById("current-capital");
|
const el = document.getElementById("current-capital");
|
||||||
if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`;
|
if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=3">
|
<link rel="stylesheet" href="/static/instance_page.css?v=3">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=59">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=60">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=1">
|
<link rel="stylesheet" href="/static/instance_page.css?v=1">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=59">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=60">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
@@ -1599,6 +1599,10 @@ function refreshAccountSnapshot(){
|
|||||||
const el = document.getElementById("total-capital");
|
const el = document.getElementById("total-capital");
|
||||||
if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${Number(data.funding_usdt).toFixed(2)}U`;
|
if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${Number(data.funding_usdt).toFixed(2)}U`;
|
||||||
}
|
}
|
||||||
|
if (typeof data.total_funds !== "undefined") {
|
||||||
|
const el = document.getElementById("total-funds");
|
||||||
|
if(el) el.innerText = (data.total_funds === null || data.total_funds === undefined) ? "—" : `${Number(data.total_funds).toFixed(2)}U`;
|
||||||
|
}
|
||||||
if (typeof data.current_capital !== "undefined") {
|
if (typeof data.current_capital !== "undefined") {
|
||||||
const el = document.getElementById("current-capital");
|
const el = document.getElementById("current-capital");
|
||||||
if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`;
|
if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`;
|
||||||
|
|||||||
@@ -46,6 +46,10 @@
|
|||||||
<div class="label">交易所</div>
|
<div class="label">交易所</div>
|
||||||
<div class="value">{{ exchange_display }}</div>
|
<div class="value">{{ exchange_display }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-strip-item">
|
||||||
|
<div class="label">交易日</div>
|
||||||
|
<div class="value">{{ trading_day }}</div>
|
||||||
|
</div>
|
||||||
<div class="stat-strip-item stat-strip-item--primary">
|
<div class="stat-strip-item stat-strip-item--primary">
|
||||||
<div class="label">总交易</div>
|
<div class="label">总交易</div>
|
||||||
<div class="value" id="stat-total">{{ total }}</div>
|
<div class="value" id="stat-total">{{ total }}</div>
|
||||||
@@ -54,16 +58,20 @@
|
|||||||
<div class="label">胜率</div>
|
<div class="label">胜率</div>
|
||||||
<div class="value" id="stat-rate">{{ rate }}%</div>
|
<div class="value" id="stat-rate">{{ rate }}%</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-strip-item" title="平均盈利 ÷ 平均亏损(当前列表窗口)">
|
||||||
|
<div class="label">盈亏比</div>
|
||||||
|
<div class="value" id="stat-pl-ratio">{% if profit_loss_ratio is not none %}{{ profit_loss_ratio }}{% else %}—{% endif %}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-strip-item">
|
||||||
|
<div class="label">总资金</div>
|
||||||
|
<div class="value" id="total-funds">{% if total_funds is not none %}{{ funds_fmt(total_funds) }}U{% else %}—{% endif %}</div>
|
||||||
|
</div>
|
||||||
<div class="stat-strip-item">
|
<div class="stat-strip-item">
|
||||||
<div class="label">资金账户</div>
|
<div class="label">资金账户</div>
|
||||||
<div class="value" id="total-capital">{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}</div>
|
<div class="value" id="total-capital">{% if funding_usdt is not none %}{{ funds_fmt(funding_usdt) }}U{% else %}—{% endif %}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-strip-item">
|
<div class="stat-strip-item">
|
||||||
<div class="label">交易日</div>
|
<div class="label">交易账户</div>
|
||||||
<div class="value">{{ trading_day }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-strip-item">
|
|
||||||
<div class="label">当日资金</div>
|
|
||||||
<div class="value" id="current-capital">{{ funds_fmt(current_capital) }}U</div>
|
<div class="value" id="current-capital">{{ funds_fmt(current_capital) }}U</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-strip-item stat-strip-item--pnl">
|
<div class="stat-strip-item stat-strip-item--pnl">
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""instance_embed_context_lib 顶栏统计。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from lib.instance.instance_embed_context_lib import (
|
||||||
|
profit_loss_ratio_from_averages,
|
||||||
|
profit_loss_ratio_from_trades,
|
||||||
|
total_funds_usdt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestHeaderStatsLib(unittest.TestCase):
|
||||||
|
def test_profit_loss_ratio_from_averages(self):
|
||||||
|
self.assertEqual(profit_loss_ratio_from_averages(9.0, -3.0), 3.0)
|
||||||
|
self.assertIsNone(profit_loss_ratio_from_averages(9.0, 0))
|
||||||
|
|
||||||
|
def test_profit_loss_ratio_from_trades(self):
|
||||||
|
trades = [
|
||||||
|
{"effective_pnl_amount": 10},
|
||||||
|
{"effective_pnl_amount": 8},
|
||||||
|
{"effective_pnl_amount": -4},
|
||||||
|
{"effective_pnl_amount": -2},
|
||||||
|
]
|
||||||
|
self.assertEqual(profit_loss_ratio_from_trades(trades), 3.0)
|
||||||
|
|
||||||
|
def test_total_funds_usdt(self):
|
||||||
|
self.assertEqual(total_funds_usdt(100.5, 59.27), 159.77)
|
||||||
|
self.assertIsNone(total_funds_usdt(None, 10))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user