1
This commit is contained in:
parent
fe5c8a7c3f
commit
e43760e006
@ -1138,6 +1138,39 @@ def _report_order_line(item: dict) -> str:
|
|||||||
return f"- **{symbol}** · {side} · 目标 {_fmt_price(item.get('target_price'))} · 距目标 {_fmt_pct(distance)}"
|
return f"- **{symbol}** · {side} · 目标 {_fmt_price(item.get('target_price'))} · 距目标 {_fmt_pct(distance)}"
|
||||||
|
|
||||||
|
|
||||||
|
def _paper_running_days() -> float:
|
||||||
|
conn = get_conn()
|
||||||
|
try:
|
||||||
|
row = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT MIN(first_at) AS first_at
|
||||||
|
FROM (
|
||||||
|
SELECT MIN(opened_at) AS first_at FROM paper_trades
|
||||||
|
UNION ALL
|
||||||
|
SELECT MIN(created_at) AS first_at FROM paper_orders
|
||||||
|
) x
|
||||||
|
WHERE first_at IS NOT NULL AND first_at <> ''
|
||||||
|
"""
|
||||||
|
).fetchone()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
first_at = _parse_time(row["first_at"] if row else "")
|
||||||
|
if not first_at:
|
||||||
|
return 0.0
|
||||||
|
now = datetime.now(first_at.tzinfo) if first_at.tzinfo else datetime.now()
|
||||||
|
seconds = max(0.0, (now - first_at).total_seconds())
|
||||||
|
return max(1.0, seconds / 86400)
|
||||||
|
|
||||||
|
|
||||||
|
def _periodized_return_pct(total_return_pct: float, running_days: float, period_days: float) -> float:
|
||||||
|
if running_days <= 0:
|
||||||
|
return 0.0
|
||||||
|
growth = 1 + total_return_pct / 100
|
||||||
|
if growth <= 0:
|
||||||
|
return -100.0
|
||||||
|
return round((growth ** (period_days / running_days) - 1) * 100, 4)
|
||||||
|
|
||||||
|
|
||||||
def send_paper_trading_report(days: int = 30) -> dict:
|
def send_paper_trading_report(days: int = 30) -> dict:
|
||||||
days = max(1, min(_safe_int(days, 30), 365))
|
days = max(1, min(_safe_int(days, 30), 365))
|
||||||
summary = get_paper_trading_summary(days=days)
|
summary = get_paper_trading_summary(days=days)
|
||||||
@ -1152,6 +1185,9 @@ def send_paper_trading_report(days: int = 30) -> dict:
|
|||||||
current_balance = _safe_float(summary.get("current_balance_usdt"))
|
current_balance = _safe_float(summary.get("current_balance_usdt"))
|
||||||
return_pct = _safe_float(summary.get("account_total_return_pct"))
|
return_pct = _safe_float(summary.get("account_total_return_pct"))
|
||||||
win_rate = _safe_float(summary.get("win_rate"))
|
win_rate = _safe_float(summary.get("win_rate"))
|
||||||
|
running_days = _paper_running_days()
|
||||||
|
monthly_return_pct = _periodized_return_pct(return_pct, running_days, 30)
|
||||||
|
annualized_return_pct = _periodized_return_pct(return_pct, running_days, 365)
|
||||||
template = "green" if total_pnl >= 0 else "red"
|
template = "green" if total_pnl >= 0 else "red"
|
||||||
elements = [
|
elements = [
|
||||||
{
|
{
|
||||||
@ -1161,7 +1197,17 @@ def send_paper_trading_report(days: int = 30) -> dict:
|
|||||||
"columns": [
|
"columns": [
|
||||||
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("初始资金", f"{initial_equity:.2f} USDT")]},
|
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("初始资金", f"{initial_equity:.2f} USDT")]},
|
||||||
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("当前资金", f"{current_balance:.2f} USDT")]},
|
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("当前资金", f"{current_balance:.2f} USDT")]},
|
||||||
|
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("运行天数", f"{running_days:.1f} 天")]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "column_set",
|
||||||
|
"flex_mode": "none",
|
||||||
|
"background_style": "default",
|
||||||
|
"columns": [
|
||||||
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("账户收益率", _fmt_pct(return_pct))]},
|
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("账户收益率", _fmt_pct(return_pct))]},
|
||||||
|
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("月化收益率", _fmt_pct(monthly_return_pct))]},
|
||||||
|
{"tag": "column", "width": "weighted", "weight": 1, "elements": [_card_field("年化收益率", _fmt_pct(annualized_return_pct))]},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1201,6 +1247,9 @@ def send_paper_trading_report(days: int = 30) -> dict:
|
|||||||
return {
|
return {
|
||||||
"ok": bool(ok),
|
"ok": bool(ok),
|
||||||
"days": days,
|
"days": days,
|
||||||
|
"running_days": running_days,
|
||||||
|
"monthly_return_pct": monthly_return_pct,
|
||||||
|
"annualized_return_pct": annualized_return_pct,
|
||||||
"summary": summary,
|
"summary": summary,
|
||||||
"sent_at": _now(),
|
"sent_at": _now(),
|
||||||
"push_result": push_result,
|
"push_result": push_result,
|
||||||
|
|||||||
@ -427,12 +427,18 @@ def test_send_paper_trading_report_pushes_performance_summary(monkeypatch, buy_n
|
|||||||
result = send_paper_trading_report(days=30)
|
result = send_paper_trading_report(days=30)
|
||||||
|
|
||||||
assert result["ok"] is True
|
assert result["ok"] is True
|
||||||
|
assert result["running_days"] >= 1
|
||||||
|
assert "monthly_return_pct" in result
|
||||||
|
assert "annualized_return_pct" in result
|
||||||
assert pushed[-1]["metadata"]["event_type"] == "trade_report"
|
assert pushed[-1]["metadata"]["event_type"] == "trade_report"
|
||||||
text = _visible_card_text(pushed[-1])
|
text = _visible_card_text(pushed[-1])
|
||||||
assert "交易报告" in text
|
assert "交易报告" in text
|
||||||
assert "初始资金" in text
|
assert "初始资金" in text
|
||||||
assert "当前资金" in text
|
assert "当前资金" in text
|
||||||
|
assert "运行天数" in text
|
||||||
assert "账户收益率" in text
|
assert "账户收益率" in text
|
||||||
|
assert "月化收益率" in text
|
||||||
|
assert "年化收益率" in text
|
||||||
assert "成功率" in text
|
assert "成功率" in text
|
||||||
assert "战绩概览" in text
|
assert "战绩概览" in text
|
||||||
_assert_no_paper_trading_copy(pushed[-1])
|
_assert_no_paper_trading_copy(pushed[-1])
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user