diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py
index 616077f..313e4b7 100644
--- a/crypto_monitor_binance/app.py
+++ b/crypto_monitor_binance/app.py
@@ -239,10 +239,10 @@ def _wechat_trading_capital_text(fallback=None):
except Exception:
trading_capital = None
if trading_capital is not None:
- return f"{round(float(trading_capital), 4)}U"
+ return f"{round(float(trading_capital), FUNDS_DECIMALS)}U"
if fallback is not None:
try:
- return f"{round(float(fallback), 4)}U"
+ return f"{round(float(fallback), FUNDS_DECIMALS)}U"
except Exception:
pass
return "-"
@@ -271,7 +271,7 @@ def build_wechat_close_message(
try:
if pnl_amount is not None:
pv = float(pnl_amount)
- pnl_disp = f"{'+' if pv > 0 else ''}{round(pv, 4)} U"
+ pnl_disp = f"{'+' if pv > 0 else ''}{round(pv, FUNDS_DECIMALS)} U"
else:
pnl_disp = "-"
except (TypeError, ValueError):
@@ -1352,19 +1352,19 @@ def _compute_period_metrics(trades):
closed = len(trades)
wins = sum(1 for p, _, _ in trades if p > 0)
losses = sum(1 for p, _, _ in trades if p < 0)
- net = round(sum(p for p, _, _ in trades), 4)
+ net = round(sum(p for p, _, _ in trades), FUNDS_DECIMALS)
loss_sum_raw = sum(p for p, _, _ in trades if p < 0)
- loss_sum_u = round(abs(loss_sum_raw), 4) if loss_sum_raw < 0 else 0.0
+ loss_sum_u = round(abs(loss_sum_raw), FUNDS_DECIMALS) if loss_sum_raw < 0 else 0.0
neg_pnls = [p for p, _, _ in trades if p < 0]
pos_pnls = [p for p, _, _ in trades if p > 0]
- max_single_loss = round(min(neg_pnls), 4) if neg_pnls else None
- max_single_profit = round(max(pos_pnls), 4) if pos_pnls else None
+ max_single_loss = round(min(neg_pnls), FUNDS_DECIMALS) if neg_pnls else None
+ max_single_profit = round(max(pos_pnls), FUNDS_DECIMALS) if pos_pnls else None
cum = peak = max_dd = 0.0
for p, _, _ in trades:
cum += p
peak = max(peak, cum)
max_dd = max(max_dd, peak - cum)
- max_dd = round(max_dd, 4)
+ max_dd = round(max_dd, FUNDS_DECIMALS)
streak = 0
for p, _, _ in reversed(trades):
if p < 0:
@@ -1388,7 +1388,7 @@ def _compute_period_metrics(trades):
else:
run = 0
worst_day = min(daily.keys(), key=lambda x: daily[x])
- worst_day_pnl = round(daily[worst_day], 4)
+ worst_day_pnl = round(daily[worst_day], FUNDS_DECIMALS)
win_rate_pct = round(wins / (wins + losses) * 100, 2) if (wins + losses) else None
return {
"closed_count": closed,
@@ -1619,10 +1619,10 @@ def update_session_capital(conn, session_date, pnl_amount):
new_capital = float(session_row["current_capital"]) + float(pnl_amount)
conn.execute(
"UPDATE trading_sessions SET current_capital = ?, updated_at = CURRENT_TIMESTAMP WHERE session_date = ?",
- (round(new_capital, 4), session_date)
+ (round(new_capital, FUNDS_DECIMALS), session_date)
)
conn.commit()
- return round(new_capital, 4)
+ return round(new_capital, FUNDS_DECIMALS)
def calc_hold_seconds(opened_at_str, closed_at_dt):
@@ -1684,17 +1684,66 @@ def to_effective_trade_dict(row):
return item
+# USDT 等资金类:展示与入库舍入统一为 2 位小数(与交易所常见口径一致)
+FUNDS_DECIMALS = 2
+
+
+def format_funds_u(value):
+ if value in (None, ""):
+ return "-"
+ try:
+ return f"{float(value):.{FUNDS_DECIMALS}f}"
+ except (TypeError, ValueError):
+ return str(value)
+
+
+def round_funds(value):
+ try:
+ return round(float(value), FUNDS_DECIMALS)
+ except (TypeError, ValueError):
+ return None
+
+
+def _ccxt_swap_symbol_for_precision(symbol):
+ """解析为 ccxt markets 中的永续 symbol,供 price_to_precision 使用。"""
+ raw = (symbol or "").strip()
+ if not raw:
+ return None
+ try:
+ ensure_markets_loaded()
+ markets = getattr(exchange, "markets", {}) or {}
+ except Exception:
+ return None
+ upper = raw.upper().replace(" ", "")
+ candidates = []
+ candidates.append(normalize_exchange_symbol(raw))
+ if upper.endswith("USDT") and len(upper) > 4 and "/" not in raw and ":" not in raw:
+ candidates.append(f"{upper[:-4]}/USDT:USDT")
+ if "/" not in raw and ":" not in raw and upper.isalnum() and not upper.endswith("USDT"):
+ candidates.append(f"{upper}/USDT:USDT")
+ for c in candidates:
+ if c and c in markets:
+ return c
+ return None
+
+
def format_price_for_symbol(symbol, value):
if value in (None, ""):
return "-"
try:
v = float(value)
- except Exception:
+ except (TypeError, ValueError):
return str(value)
if v == 0:
return "0"
+ try:
+ ex_sym = _ccxt_swap_symbol_for_precision(symbol)
+ if ex_sym:
+ return str(exchange.price_to_precision(ex_sym, v))
+ except Exception:
+ pass
av = abs(v)
- # 根据币价量级动态精度:低价币保留更多小数,高价币减少噪音位数
+ # 无法加载市场或无该合约时:按价格量级回退(尽量不阻断页面)
if av >= 10000:
d = 2
elif av >= 100:
@@ -1734,7 +1783,7 @@ def calc_pnl(direction, trigger_price, exit_price, margin_capital, leverage):
pnl_ratio = (trigger - exit_p) / trigger
else:
pnl_ratio = (exit_p - trigger) / trigger
- return round(margin * lev * pnl_ratio, 4)
+ return round(margin * lev * pnl_ratio, FUNDS_DECIMALS)
except Exception:
return 0.0
@@ -1784,7 +1833,7 @@ def calc_risk_amount_from_plan(direction, entry_price, stop_loss, margin_capital
notional = float(margin_capital) * float(leverage)
if notional <= 0:
return None
- return round(notional * rf, 6)
+ return round(notional * rf, FUNDS_DECIMALS)
except Exception:
return None
@@ -1910,7 +1959,7 @@ def enrich_order_item(raw_item, current_capital):
notional = item.get("notional_value")
ratio = item.get("position_ratio")
if notional is None:
- notional = round(margin * lev, 4) if margin and lev else 0
+ notional = round(margin * lev, FUNDS_DECIMALS) if margin and lev else 0
if ratio is None:
ratio = round(margin / current_capital * 100, 2) if current_capital else 0
item["notional_value"] = notional
@@ -2083,7 +2132,7 @@ def friendly_exchange_error(err, available_usdt=None):
or "margin" in low and ("not enough" in low or "不足" in msg)
or "balance" in low and "insufficient" in low
):
- tail = f"(当前交易账户可用约 {round(available_usdt, 4)}U)" if available_usdt is not None else ""
+ tail = f"(当前交易账户可用约 {round(available_usdt, FUNDS_DECIMALS)}U)" if available_usdt is not None else ""
return f"交易所下单失败:保证金不足 {tail}。请降低保证金/杠杆,或先划转USDT到合约账户。"
clean = re.sub(r"\s+", " ", msg).strip()
return f"交易所下单失败:{clean}"
@@ -2173,7 +2222,7 @@ def auto_transfer_once_per_day():
conn.commit()
conn.close()
return
- needed = round(max(target_amount - float(to_balance), 0), 4)
+ needed = round(max(target_amount - float(to_balance), 0), FUNDS_DECIMALS)
if needed <= 0:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
@@ -2185,12 +2234,12 @@ def auto_transfer_once_per_day():
if from_balance is not None and from_balance < needed:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
- ("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{needed}U,当前{round(from_balance,4)}U")
+ ("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{needed}U,当前{round(from_balance, FUNDS_DECIMALS)}U")
)
conn.commit()
conn.close()
send_wechat_msg(
- f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{needed}U,当前{round(from_balance,4)}U\n"
+ f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{needed}U,当前{round(from_balance, FUNDS_DECIMALS)}U\n"
f"账簿日(UTC):{transfer_day}|触发时刻(北京):{app_now_str()}"
)
return
@@ -2688,13 +2737,21 @@ def parse_ccxt_position_metrics(position, order_leverage=None):
mark = _coerce_float(p.get("markPrice"), p.get("mark_price"), info.get("mark_price"), info.get("markPrice"))
out = {}
if initial is not None and initial > 0:
- out["initial_margin"] = round(initial, 4)
+ out["initial_margin"] = round(initial, FUNDS_DECIMALS)
if notional is not None and notional > 0:
- out["notional"] = round(notional, 4)
+ out["notional"] = round(notional, FUNDS_DECIMALS)
if unrealized is not None:
- out["unrealized_pnl"] = round(unrealized, 6)
+ out["unrealized_pnl"] = round(unrealized, FUNDS_DECIMALS)
if mark is not None and mark > 0:
- out["mark_price"] = round(mark, 8)
+ ps = p.get("symbol")
+ try:
+ ex_sym = _ccxt_swap_symbol_for_precision(ps or "")
+ if ex_sym:
+ out["mark_price"] = float(exchange.price_to_precision(ex_sym, mark))
+ else:
+ out["mark_price"] = round(mark, 8)
+ except Exception:
+ out["mark_price"] = round(mark, 8)
return out or None
@@ -3817,8 +3874,8 @@ def render_main_page(page="trade"):
local_current_capital = float(session_row["current_capital"])
funding_capital, trading_capital = get_exchange_capitals()
# 资金账户:仅展示交易所读取结果(含 0)。不可用 TOTAL_CAPITAL 兜底,否则会与实盘不符。
- funding_usdt = round(funding_capital, 4) if funding_capital is not None else None
- current_capital = round(trading_capital, 4) if trading_capital is not None else round(local_current_capital, 4)
+ funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
+ current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
recommended_capital = get_recommended_capital(current_capital)
key_list = conn.execute("SELECT * FROM key_monitors").fetchall()
key_history = conn.execute("SELECT * FROM key_monitor_history ORDER BY id DESC LIMIT 80").fetchall()
@@ -3880,6 +3937,7 @@ def render_main_page(page="trade"):
breakeven_offset_pct=BREAKEVEN_OFFSET_PCT,
occupied_miss_total=occupied_miss_total,
price_fmt=format_price_for_symbol,
+ funds_fmt=format_funds_u,
entry_reason_options=list(ENTRY_REASON_OPTIONS),
entry_reason_other_value=ENTRY_REASON_OTHER,
exchange_display=EXCHANGE_DISPLAY_NAME,
@@ -3919,8 +3977,8 @@ def api_account_snapshot():
session_row = ensure_session(conn, trading_day)
local_current_capital = float(session_row["current_capital"])
funding_capital, trading_capital = get_exchange_capitals(force=True)
- funding_usdt = round(funding_capital, 4) if funding_capital is not None else None
- current_capital = round(trading_capital, 4) if trading_capital is not None else round(local_current_capital, 4)
+ funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
+ current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
recommended_capital = get_recommended_capital(current_capital)
active_count = conn.execute("SELECT COUNT(*) FROM order_monitors WHERE status='active'").fetchone()[0]
conn.close()
@@ -3929,7 +3987,7 @@ def api_account_snapshot():
return jsonify({
"funding_usdt": funding_usdt,
"current_capital": current_capital,
- "available_trading_usdt": round(available_trading_usdt, 4) 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,
"recommended_capital": recommended_capital,
"active_count": active_count,
"can_trade": can_trade,
@@ -3995,19 +4053,21 @@ def api_price_snapshot():
vol_now = round(float(gate.get("vol_break") or 0), 4)
vol_avg = round(float(gate.get("avg20") or 0), 4)
amp_pct = round(float(gate.get("amp_pct") or 0), 4)
- cfm_close = round(float(gate.get("confirm_close") or 0), 8)
- edge = round(float(gate.get("edge_price") or 0), 8)
+ cfm_close = float(gate.get("confirm_close") or 0)
+ edge = float(gate.get("edge_price") or 0)
gate_metrics = (
f"量值:{vol_now}/{vol_avg} "
f"幅值:{amp_pct}% "
- f"二确值:{cfm_close}@{edge}"
+ f"二确值:{format_price_for_symbol(r['symbol'], cfm_close)}@{format_price_for_symbol(r['symbol'], edge)}"
)
except Exception:
gate_metrics = ""
+ sym_k = r["symbol"]
key_prices.append({
"id": r["id"],
- "symbol": r["symbol"],
+ "symbol": sym_k,
"price": round(price, 6),
+ "price_display": format_price_for_symbol(sym_k, price),
"upper_diff": upper_diff,
"upper_pct": upper_pct,
"lower_diff": lower_diff,
@@ -4026,7 +4086,7 @@ def api_price_snapshot():
leverage = float(r["leverage"] or 0)
entry = float(r["trigger_price"] or 0)
pnl = calc_pnl(r["direction"], entry, price, margin, leverage) if entry > 0 else 0
- pnl_pct = round((pnl / margin * 100), 4) if margin > 0 else 0
+ pnl_pct = round((pnl / margin * 100), 2) if margin > 0 else 0
rr_ratio = calc_rr_ratio(r["direction"], entry, r["initial_stop_loss"] or r["stop_loss"], r["take_profit"])
ex_sym = resolve_monitor_exchange_symbol(r)
prow = _select_live_position_row(all_swap_positions, ex_sym, r["direction"])
@@ -4036,13 +4096,15 @@ def api_price_snapshot():
"id": r["id"],
"symbol": r["symbol"],
"price": round(price, 6),
- "float_pnl": round(pnl, 6),
+ "price_display": format_price_for_symbol(ex_sym, price),
+ "float_pnl": round(pnl, FUNDS_DECIMALS),
"float_pct": pnl_pct,
"rr_ratio": rr_ratio,
- "plan_margin": round(margin, 4) if margin else None,
+ "plan_margin": round(margin, FUNDS_DECIMALS) if margin else None,
"exchange_initial_margin": None,
"exchange_notional": None,
"exchange_mark_price": None,
+ "exchange_mark_price_display": None,
"pnl_source": "plan",
}
if ex_metrics:
@@ -4051,13 +4113,15 @@ def api_price_snapshot():
if ex_metrics.get("notional") is not None:
payload["exchange_notional"] = ex_metrics["notional"]
if ex_metrics.get("mark_price") is not None:
- payload["exchange_mark_price"] = ex_metrics["mark_price"]
+ mp = ex_metrics["mark_price"]
+ payload["exchange_mark_price"] = mp
+ payload["exchange_mark_price_display"] = format_price_for_symbol(ex_sym, mp)
if ex_metrics.get("unrealized_pnl") is not None:
- payload["float_pnl"] = round(float(ex_metrics["unrealized_pnl"]), 6)
+ payload["float_pnl"] = round(float(ex_metrics["unrealized_pnl"]), FUNDS_DECIMALS)
payload["pnl_source"] = "exchange"
denom = ex_metrics.get("initial_margin") or margin
payload["float_pct"] = (
- round((payload["float_pnl"] / float(denom)) * 100, 4) if denom and float(denom) > 0 else pnl_pct
+ round((payload["float_pnl"] / float(denom)) * 100, 2) if denom and float(denom) > 0 else pnl_pct
)
order_prices.append(payload)
@@ -4109,7 +4173,7 @@ def api_order_defaults():
"exchange_symbol": exchange_symbol,
"direction": direction,
"leverage": leverage,
- "available_trading_usdt": round(available, 4) if available is not None else None
+ "available_trading_usdt": round(available, FUNDS_DECIMALS) if available is not None else None
})
@@ -4122,7 +4186,7 @@ def order_focus():
session_row = ensure_session(conn, trading_day)
local_current_capital = float(session_row["current_capital"])
_, trading_capital_live = get_exchange_capitals()
- current_capital = round(trading_capital_live, 4) if trading_capital_live is not None else round(local_current_capital, 4)
+ current_capital = round(trading_capital_live, FUNDS_DECIMALS) if trading_capital_live is not None else round(local_current_capital, FUNDS_DECIMALS)
raw_orders = conn.execute("SELECT * FROM order_monitors WHERE status='active' ORDER BY id DESC").fetchall()
conn.close()
orders = [enrich_order_item(row_to_dict(r), current_capital) for r in raw_orders]
@@ -4161,7 +4225,7 @@ def api_order_kline():
session_row = ensure_session(conn, trading_day)
local_current_capital = float(session_row["current_capital"])
_, trading_capital_live = get_exchange_capitals()
- current_capital = round(trading_capital_live, 4) if trading_capital_live is not None else round(local_current_capital, 4)
+ current_capital = round(trading_capital_live, FUNDS_DECIMALS) if trading_capital_live is not None else round(local_current_capital, FUNDS_DECIMALS)
row = conn.execute("SELECT * FROM order_monitors WHERE id=? AND status='active'", (order_id,)).fetchone()
conn.close()
if not row:
@@ -4194,7 +4258,7 @@ def api_order_kline():
leverage = float(order_item.get("leverage") or 0)
entry = float(order_item.get("trigger_price") or 0)
float_pnl = calc_pnl(order_item.get("direction") or "long", entry, current_price, margin, leverage) if current_price else 0
- float_pct = round((float_pnl / margin * 100), 4) if margin > 0 else 0
+ float_pct = round((float_pnl / margin * 100), 2) if margin > 0 else 0
return jsonify({
"ok": True,
@@ -4207,13 +4271,17 @@ def api_order_kline():
"trigger_price": order_item.get("trigger_price"),
"stop_loss": order_item.get("stop_loss"),
"take_profit": order_item.get("take_profit"),
+ "trigger_price_display": format_price_for_symbol(exchange_symbol, order_item.get("trigger_price")),
+ "stop_loss_display": format_price_for_symbol(exchange_symbol, order_item.get("stop_loss")),
+ "take_profit_display": format_price_for_symbol(exchange_symbol, order_item.get("take_profit")),
"margin_capital": order_item.get("margin_capital"),
"leverage": order_item.get("leverage"),
"position_ratio": order_item.get("position_ratio"),
"rr_ratio": order_item.get("rr_ratio"),
"breakeven_enabled": bool(int(order_item.get("breakeven_enabled") or 0)),
"current_price": round(float(current_price), 8) if current_price else None,
- "float_pnl": round(float(float_pnl), 6),
+ "current_price_display": format_price_for_symbol(exchange_symbol, current_price) if current_price else None,
+ "float_pnl": round(float(float_pnl), FUNDS_DECIMALS),
"float_pct": float_pct,
},
"candles": candles,
@@ -4311,6 +4379,8 @@ def api_key_kline():
"direction": key_row["direction"] or "long",
"upper": upper,
"lower": lower,
+ "upper_display": format_price_for_symbol(exchange_symbol, upper) if upper is not None else None,
+ "lower_display": format_price_for_symbol(exchange_symbol, lower) if lower is not None else None,
"notification_count": int(key_row["notification_count"] or 0),
"upper_diff": upper_diff,
"upper_pct": upper_pct,
@@ -4324,6 +4394,7 @@ def api_key_kline():
"timeframe": timeframe,
"limit": limit,
"current_price": round(float(current_price), 8) if current_price is not None else None,
+ "current_price_display": format_price_for_symbol(exchange_symbol, current_price) if current_price is not None else None,
"key_monitor": key_info,
"candles": candles,
"updated_at": app_now_str(),
@@ -4466,18 +4537,18 @@ def add_order():
flash("止损方向不合法:请检查入场方向与止损价格关系")
return redirect("/")
risk_percent = max(0.01, float(RISK_PERCENT))
- risk_amount = round(capital_base * risk_percent / 100.0, 4)
- notional_value = round(risk_amount / risk_fraction, 4)
- margin_capital = round(notional_value / leverage, 4)
+ risk_amount = round(capital_base * risk_percent / 100.0, FUNDS_DECIMALS)
+ notional_value = round(risk_amount / risk_fraction, FUNDS_DECIMALS)
+ margin_capital = round(notional_value / leverage, FUNDS_DECIMALS)
if capital_base and margin_capital > capital_base:
conn.close()
flash("以损定仓后保证金超过当前交易资金,请放宽止损或降低风险比例")
return redirect("/")
if available_usdt is not None:
- max_margin = round(max(available_usdt * FULL_MARGIN_BUFFER_RATIO, 0), 4)
+ max_margin = round(max(available_usdt * FULL_MARGIN_BUFFER_RATIO, 0), FUNDS_DECIMALS)
if margin_capital > max_margin:
conn.close()
- flash(f"保证金不足:交易账户可用约 {round(available_usdt,4)}U,当前最多建议 {max_margin}U")
+ flash(f"保证金不足:交易账户可用约 {round(available_usdt, FUNDS_DECIMALS)}U,当前最多建议 {max_margin}U")
return redirect("/")
position_ratio = round(margin_capital / capital_base * 100, 2) if capital_base else 0
try:
@@ -4592,9 +4663,9 @@ def add_order():
_, trading_capital_after = get_exchange_capitals(force=True)
account_base_display = (
- round(float(trading_capital_after), 4)
+ round(float(trading_capital_after), FUNDS_DECIMALS)
if trading_capital_after is not None
- else round(float(capital_base), 4)
+ else round(float(capital_base), FUNDS_DECIMALS)
)
account_name = (os.getenv("BINANCE_ACCOUNT_LABEL") or "binance实盘账户").strip()
dir_text = "多头(long)" if direction == "long" else "空头(short)"
@@ -4620,7 +4691,7 @@ def add_order():
"🧾 订单基础信息",
f"🔖 交易所订单 ID:{open_order_id}",
f"📈 交易风格:{style_zh}",
- f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), 4)} U",
+ f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), FUNDS_DECIMALS)} U",
"📊 仓位配置详情",
f"账户基数:{account_base_display} USDT",
f"合约杠杆:{leverage} 倍",
@@ -5350,7 +5421,7 @@ def api_trade_record_review_update():
reviewed_closed_at,
reviewed_stop_loss,
reviewed_take_profit,
- round(reviewed_pnl_amount, 4),
+ round(reviewed_pnl_amount, FUNDS_DECIMALS),
reviewed_result or None,
reviewed_miss_reason or None,
hold_seconds,
diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html
index 7d59cb9..2d8f9a1 100644
--- a/crypto_monitor_binance/templates/index.html
+++ b/crypto_monitor_binance/templates/index.html
@@ -130,14 +130,14 @@
{{ o.symbol }} | {{ '做多' if o.direction == 'long' else '做空' }}
- 风格:{{ o.trade_style or 'trend' }} | 风险:{{ o.risk_percent or '-' }}%≈{{ o.risk_amount or '-' }}U
- | {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ o.breakeven_price or '-' }}{% else %}移动保本:关{% endif %}
+ 风格:{{ o.trade_style or 'trend' }} | 风险:{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U
+ | {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
- 成交:{{ o.trigger_price }} 止损:{{ o.stop_loss }} 止盈:{{ o.take_profit }}
+ 成交:{{ price_fmt(o.symbol, o.trigger_price) }} 止损:{{ price_fmt(o.symbol, o.stop_loss) }} 止盈:{{ price_fmt(o.symbol, o.take_profit) }}
| 盈亏比:{% if o.rr_ratio is not none %}1:{{ '%.2f'|format(o.rr_ratio) }}{% else %}-{% endif %}
| 现价:-
| 浮盈亏:-
- | 计划基数:{{ o.margin_capital }}U | 所保证金:-
+ | 计划基数:{{ funds_fmt(o.margin_capital) if o.margin_capital is not none else '-' }}U | 所保证金:-
| 杠杆:{{ o.leverage }}x | 仓位占比:{{ o.position_ratio }}%
平仓
@@ -335,18 +335,18 @@
{{ r.symbol }} |
{{ r.monitor_type }} |
{{ '做多' if r.direction == 'long' else '做空' }} |
-
{{ r.trigger_price }} |
+
{{ price_fmt(r.symbol, r.trigger_price) }} |
{% set stop_show = r.effective_stop_loss or r.initial_stop_loss or r.stop_loss %}
{% set tp_show = r.effective_take_profit or r.take_profit %}
{{ price_fmt(r.symbol, stop_show) }} |
{{ price_fmt(r.symbol, tp_show) }} |
-
{{ r.margin_capital or '-' }} |
+
{% if r.margin_capital is not none and r.margin_capital != '' %}{{ funds_fmt(r.margin_capital) }}{% else %}-{% endif %} |
{{ r.leverage or '-' }} |
{{ r.effective_hold_minutes or 0 }} |
{{ (r.effective_opened_at or '-')[:16] }} |
{{ (r.effective_closed_at or r.created_at or '-')[:16] }} |
{% set pnl_val = (r.effective_pnl_amount or 0)|float %}
-
{{ r.effective_pnl_amount or 0 }} |
+
{{ funds_fmt(r.effective_pnl_amount or 0) }} |
{% set effective_result = r.effective_result %}
{% if effective_result in ["止盈","保本止盈","移动止盈"] %}{{ effective_result }}
@@ -1091,7 +1091,7 @@ setTimeout(() => {
let latestAvailableUsdt = null;
const lastPriceMap = {};
-function formatSigned(v, digits=4){
+function formatSigned(v, digits=2){
if(v === null || typeof v === "undefined" || Number.isNaN(Number(v))) return "-";
const n = Number(v);
const sign = n > 0 ? "+" : "";
@@ -1121,7 +1121,7 @@ function refreshPriceSnapshot(){
(data.key_prices || []).forEach(k=>{
const pEl = document.getElementById(`key-price-${k.id}`);
if(pEl){
- pEl.innerText = Number(k.price).toFixed(6);
+ pEl.innerText = k.price_display || (Number.isFinite(Number(k.price)) ? Number(k.price).toFixed(6) : "-");
paintPriceTrend(pEl, `k-${k.id}`, Number(k.price));
}
const upEl = document.getElementById(`key-up-diff-${k.id}`);
@@ -1146,17 +1146,25 @@ function refreshPriceSnapshot(){
const pEl = document.getElementById(`order-price-${o.id}`);
if(pEl){
const hasMark = (()=>{ const x = o.exchange_mark_price; if(x===null||x===undefined||x==="")return false; const n=Number(x); return !Number.isNaN(n); })();
- const px = hasMark ? Number(o.exchange_mark_price) : Number(o.price);
- const decimals = hasMark ? 8 : 6;
- pEl.innerText = px.toFixed(decimals);
- paintPriceTrend(pEl, `o-${o.id}`, px);
+ let disp = "";
+ if(hasMark && o.exchange_mark_price_display){
+ disp = o.exchange_mark_price_display;
+ } else if(o.price_display){
+ disp = o.price_display;
+ } else {
+ const px = hasMark ? Number(o.exchange_mark_price) : Number(o.price);
+ disp = Number.isFinite(px) ? px.toFixed(6) : "-";
+ }
+ pEl.innerText = disp;
+ const pxNum = hasMark ? Number(o.exchange_mark_price) : Number(o.price);
+ paintPriceTrend(pEl, `o-${o.id}`, Number.isFinite(pxNum) ? pxNum : px);
}
const exM = document.getElementById(`order-ex-margin-${o.id}`);
if(exM){
const mv = o.exchange_initial_margin;
const mn = (mv === null || mv === undefined || mv === "") ? NaN : Number(mv);
if(!Number.isNaN(mn)){
- exM.innerText = `${mn.toFixed(4)}U`;
+ exM.innerText = `${mn.toFixed(2)}U`;
} else {
const prc = (typeof data.positions_raw_count === "number") ? data.positions_raw_count : null;
exM.innerText = (prc === 0) ? "无仓数据" : "-";
@@ -1164,7 +1172,7 @@ function refreshPriceSnapshot(){
}
const pnlEl = document.getElementById(`order-pnl-${o.id}`);
if(pnlEl){
- pnlEl.innerText = `${formatSigned(o.float_pnl, 4)}U (${formatSigned(o.float_pct, 2)}%)`;
+ pnlEl.innerText = `${formatSigned(o.float_pnl, 2)}U (${formatSigned(o.float_pct, 2)}%)`;
pnlEl.classList.remove("price-up","price-down","price-flat");
if(Number(o.float_pnl) > 0) pnlEl.classList.add("price-up");
else if(Number(o.float_pnl) < 0) pnlEl.classList.add("price-down");
@@ -1198,7 +1206,7 @@ function refreshOrderDefaults(){
const fullEl = document.getElementById("use-full-margin");
const marginEl = document.getElementById("order-margin");
if(fullEl && marginEl && fullEl.checked){
- const m = Math.max(latestAvailableUsdt * {{ full_margin_buffer_ratio }}, 0).toFixed(4);
+ const m = Math.max(latestAvailableUsdt * {{ full_margin_buffer_ratio }}, 0).toFixed(2);
marginEl.value = m;
}
}
@@ -1209,18 +1217,18 @@ function refreshAccountSnapshot(){
fetch("/api/account_snapshot").then(r=>r.json()).then(data=>{
if (typeof data.funding_usdt !== "undefined") {
const el = document.getElementById("total-capital");
- if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${data.funding_usdt}U`;
+ if(el) el.innerText = (data.funding_usdt === null || data.funding_usdt === undefined) ? "—" : `${Number(data.funding_usdt).toFixed(2)}U`;
}
if (typeof data.current_capital !== "undefined") {
const el = document.getElementById("current-capital");
- if(el) el.innerText = `${data.current_capital}U`;
+ if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`;
}
if (typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null) {
latestAvailableUsdt = Number(data.available_trading_usdt);
}
const canTradeText = data.can_trade ? "可开仓" : "不可开仓(有持仓或未到北京时间 {{ reset_hour }}:00)";
const tip = document.getElementById("order-rule-tip");
- const avail = (latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)) ? `;交易账户可用约${latestAvailableUsdt}U` : "";
+ const avail = (latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)) ? `;交易账户可用约${latestAvailableUsdt.toFixed(2)}U` : "";
if(tip){
tip.innerText = `规则:单仓;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x;${canTradeText}${avail}`;
}
@@ -1236,7 +1244,7 @@ if(fullMarginEl){
fullMarginEl.addEventListener("change", function(){
const marginEl = document.getElementById("order-margin");
if(marginEl && this.checked && latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)){
- marginEl.value = Math.max(latestAvailableUsdt * {{ full_margin_buffer_ratio }}, 0).toFixed(4);
+ marginEl.value = Math.max(latestAvailableUsdt * {{ full_margin_buffer_ratio }}, 0).toFixed(2);
}
});
}
diff --git a/crypto_monitor_binance/templates/key_focus_v2.html b/crypto_monitor_binance/templates/key_focus_v2.html
index d4b3492..26af46d 100644
--- a/crypto_monitor_binance/templates/key_focus_v2.html
+++ b/crypto_monitor_binance/templates/key_focus_v2.html
@@ -166,7 +166,7 @@ function addLine(price, title, color){
function paintMeta(data){
const key = data.key_monitor || null;
document.getElementById("m-symbol").innerText = data.symbol || "-";
- document.getElementById("m-price").innerText = fmt(data.current_price,8);
+ document.getElementById("m-price").innerText = data.current_price_display || fmt(data.current_price,8);
if(!key){
document.getElementById("m-type").innerText = "未匹配到关键位";
@@ -180,8 +180,8 @@ function paintMeta(data){
document.getElementById("m-type").innerText = key.monitor_type || "-";
document.getElementById("m-direction").innerText = key.direction === "short" ? "做空" : "做多";
- document.getElementById("m-upper").innerText = fmt(key.upper,8);
- document.getElementById("m-lower").innerText = fmt(key.lower,8);
+ document.getElementById("m-upper").innerText = key.upper_display || fmt(key.upper,8);
+ document.getElementById("m-lower").innerText = key.lower_display || fmt(key.lower,8);
document.getElementById("m-updiff").innerText = `${fmtSigned(key.upper_diff,4)} (${fmtSigned(key.upper_pct,2)}%)`;
document.getElementById("m-lowdiff").innerText = `${fmtSigned(key.lower_diff,4)} (${fmtSigned(key.lower_pct,2)}%)`;
}
diff --git a/crypto_monitor_binance/templates/order_focus.html b/crypto_monitor_binance/templates/order_focus.html
index 0811e93..c0992d4 100644
--- a/crypto_monitor_binance/templates/order_focus.html
+++ b/crypto_monitor_binance/templates/order_focus.html
@@ -140,13 +140,13 @@ function addLine(price, title, color){
function paintOrder(order){
document.getElementById("m-symbol").innerText = order.symbol || "-";
document.getElementById("m-direction").innerText = (order.direction === "short") ? "做空" : "做多";
- document.getElementById("m-entry").innerText = fmt(order.trigger_price, 8);
- document.getElementById("m-sl").innerText = fmt(order.stop_loss, 8);
- document.getElementById("m-tp").innerText = fmt(order.take_profit, 8);
+ document.getElementById("m-entry").innerText = order.trigger_price_display || fmt(order.trigger_price, 8);
+ document.getElementById("m-sl").innerText = order.stop_loss_display || fmt(order.stop_loss, 8);
+ document.getElementById("m-tp").innerText = order.take_profit_display || fmt(order.take_profit, 8);
document.getElementById("m-rr").innerText = (order.rr_ratio === null || typeof order.rr_ratio === "undefined") ? "-" : `1:${Number(order.rr_ratio).toFixed(2)}`;
- document.getElementById("m-price").innerText = fmt(order.current_price, 8);
+ document.getElementById("m-price").innerText = order.current_price_display || fmt(order.current_price, 8);
const pnlEl = document.getElementById("m-pnl");
- pnlEl.innerText = `${fmt(order.float_pnl, 4)}U (${fmt(order.float_pct, 2)}%)`;
+ pnlEl.innerText = `${fmt(order.float_pnl, 2)}U (${fmt(order.float_pct, 2)}%)`;
pnlEl.style.color = Number(order.float_pnl || 0) > 0 ? "#4cd97f" : (Number(order.float_pnl || 0) < 0 ? "#ff6666" : "#d6deff");
}
diff --git a/crypto_monitor_binance/templates/order_focus_v2.html b/crypto_monitor_binance/templates/order_focus_v2.html
index 9c9add3..f9bceab 100644
--- a/crypto_monitor_binance/templates/order_focus_v2.html
+++ b/crypto_monitor_binance/templates/order_focus_v2.html
@@ -142,15 +142,15 @@ function addLine(price, title, color){
function paintOrder(order){
document.getElementById("m-symbol").innerText = order.symbol || "-";
document.getElementById("m-direction").innerText = (order.direction === "short") ? "做空" : "做多";
- document.getElementById("m-entry").innerText = fmt(order.trigger_price, 8);
- document.getElementById("m-sl").innerText = fmt(order.stop_loss, 8);
- document.getElementById("m-tp").innerText = fmt(order.take_profit, 8);
+ document.getElementById("m-entry").innerText = order.trigger_price_display || fmt(order.trigger_price, 8);
+ document.getElementById("m-sl").innerText = order.stop_loss_display || fmt(order.stop_loss, 8);
+ document.getElementById("m-tp").innerText = order.take_profit_display || fmt(order.take_profit, 8);
document.getElementById("m-rr").innerText = (order.rr_ratio === null || typeof order.rr_ratio === "undefined") ? "-" : `1:${Number(order.rr_ratio).toFixed(2)}`;
document.getElementById("m-breakeven").innerText =
(order.breakeven_enabled === false || order.breakeven_enabled === 0) ? "关闭" : "开启";
- document.getElementById("m-price").innerText = fmt(order.current_price, 8);
+ document.getElementById("m-price").innerText = order.current_price_display || fmt(order.current_price, 8);
const pnlEl = document.getElementById("m-pnl");
- pnlEl.innerText = `${fmt(order.float_pnl, 4)}U (${fmt(order.float_pct, 2)}%)`;
+ pnlEl.innerText = `${fmt(order.float_pnl, 2)}U (${fmt(order.float_pct, 2)}%)`;
pnlEl.style.color = Number(order.float_pnl || 0) > 0 ? "#4cd97f" : (Number(order.float_pnl || 0) < 0 ? "#ff6666" : "#d6deff");
}
diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py
index be80b84..c562f58 100644
--- a/crypto_monitor_gate/app.py
+++ b/crypto_monitor_gate/app.py
@@ -238,10 +238,10 @@ def _wechat_trading_capital_text(fallback=None):
except Exception:
trading_capital = None
if trading_capital is not None:
- return f"{round(float(trading_capital), 4)}U"
+ return f"{round(float(trading_capital), 2)}U"
if fallback is not None:
try:
- return f"{round(float(fallback), 4)}U"
+ return f"{round(float(fallback), 2)}U"
except Exception:
pass
return "-"
@@ -265,12 +265,12 @@ def build_wechat_close_message(
ep = format_price_for_symbol(symbol, trigger_price)
cp = format_price_for_symbol(symbol, current_price)
tp = format_price_for_symbol(symbol, take_profit)
- sl = format_price_for_symbol(symbol, stop_loss)
+ sl = format_wechat_scalar_2dp(stop_loss)
cap_txt = _wechat_trading_capital_text(session_capital_fallback)
try:
if pnl_amount is not None:
pv = float(pnl_amount)
- pnl_disp = f"{'+' if pv > 0 else ''}{round(pv, 4)} U"
+ pnl_disp = f"{'+' if pv > 0 else ''}{round(pv, 2)} U"
else:
pnl_disp = "-"
except (TypeError, ValueError):
@@ -300,7 +300,7 @@ def build_wechat_close_message(
def build_wechat_breakeven_message(symbol, direction, arm_txt, now_rr, locked_r, new_sl):
- sl_fmt = format_price_for_symbol(symbol, new_sl)
+ sl_fmt = format_wechat_scalar_2dp(new_sl)
return "\n".join(
[
f"# 🛡️ {symbol} 保护位更新",
@@ -975,6 +975,7 @@ def init_db():
breakeven_armed INTEGER DEFAULT 0, breakeven_price REAL,
notional_value REAL, position_ratio REAL, base_amount REAL,
order_amount REAL, exchange_order_id TEXT, exchange_close_order_id TEXT,
+ exchange_margin_usdt REAL,
opened_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, opened_at_ms INTEGER, session_date TEXT,
status TEXT DEFAULT "active")''')
@@ -1088,6 +1089,10 @@ def init_db():
c.execute("ALTER TABLE order_monitors ADD COLUMN breakeven_enabled INTEGER DEFAULT 1")
except Exception:
pass
+ try:
+ c.execute("ALTER TABLE order_monitors ADD COLUMN exchange_margin_usdt REAL")
+ except Exception:
+ pass
try:
c.execute("UPDATE order_monitors SET opened_at = datetime('now') WHERE opened_at IS NULL OR opened_at = ''")
except: pass
@@ -1351,19 +1356,19 @@ def _compute_period_metrics(trades):
closed = len(trades)
wins = sum(1 for p, _, _ in trades if p > 0)
losses = sum(1 for p, _, _ in trades if p < 0)
- net = round(sum(p for p, _, _ in trades), 4)
+ net = round(sum(p for p, _, _ in trades), 2)
loss_sum_raw = sum(p for p, _, _ in trades if p < 0)
- loss_sum_u = round(abs(loss_sum_raw), 4) if loss_sum_raw < 0 else 0.0
+ loss_sum_u = round(abs(loss_sum_raw), 2) if loss_sum_raw < 0 else 0.0
neg_pnls = [p for p, _, _ in trades if p < 0]
pos_pnls = [p for p, _, _ in trades if p > 0]
- max_single_loss = round(min(neg_pnls), 4) if neg_pnls else None
- max_single_profit = round(max(pos_pnls), 4) if pos_pnls else None
+ max_single_loss = round(min(neg_pnls), 2) if neg_pnls else None
+ max_single_profit = round(max(pos_pnls), 2) if pos_pnls else None
cum = peak = max_dd = 0.0
for p, _, _ in trades:
cum += p
peak = max(peak, cum)
max_dd = max(max_dd, peak - cum)
- max_dd = round(max_dd, 4)
+ max_dd = round(max_dd, 2)
streak = 0
for p, _, _ in reversed(trades):
if p < 0:
@@ -1387,7 +1392,7 @@ def _compute_period_metrics(trades):
else:
run = 0
worst_day = min(daily.keys(), key=lambda x: daily[x])
- worst_day_pnl = round(daily[worst_day], 4)
+ worst_day_pnl = round(daily[worst_day], 2)
win_rate_pct = round(wins / (wins + losses) * 100, 2) if (wins + losses) else None
return {
"closed_count": closed,
@@ -1640,9 +1645,8 @@ def to_effective_trade_dict(row):
return item
-def format_price_for_symbol(symbol, value):
- if value in (None, ""):
- return "-"
+def format_price_magnitude_fallback(value):
+ """无 markets 或解析失败时的价格展示兜底(按量级)。"""
try:
v = float(value)
except Exception:
@@ -1650,7 +1654,6 @@ def format_price_for_symbol(symbol, value):
if v == 0:
return "0"
av = abs(v)
- # 根据币价量级动态精度:低价币保留更多小数,高价币减少噪音位数
if av >= 10000:
d = 2
elif av >= 100:
@@ -1667,6 +1670,88 @@ def format_price_for_symbol(symbol, value):
return text.rstrip("0").rstrip(".") if "." in text else text
+def resolve_ccxt_price_symbol(symbol):
+ """将界面/库中的品种名转为 ccxt 永续合约 id(如 BTC/USDT -> BTC/USDT:USDT)。"""
+ s = (symbol or "").strip()
+ if not s:
+ return ""
+ if "/" not in s and ":" not in s:
+ s = f"{s.upper()}/USDT"
+ else:
+ s = s.upper()
+ return normalize_exchange_symbol(s)
+
+
+def round_price_to_exchange(exchange_symbol, price):
+ """与交易所 tick 对齐后的 float,供入库与计算;失败时退回 float(price)。"""
+ if price in (None, ""):
+ return None
+ try:
+ v = float(price)
+ except (TypeError, ValueError):
+ return None
+ if not exchange_symbol:
+ return v
+ try:
+ ensure_markets_loaded()
+ s = exchange.price_to_precision(exchange_symbol, v)
+ return float(s)
+ except Exception:
+ return v
+
+
+def format_price_for_symbol(symbol, value):
+ """价格展示:与交易所 price_to_precision 一致(与入库 round_price_to_exchange 对齐)。"""
+ if value in (None, ""):
+ return "-"
+ try:
+ v = float(value)
+ except Exception:
+ return str(value)
+ ex = resolve_ccxt_price_symbol(symbol)
+ if not ex:
+ return format_price_magnitude_fallback(v)
+ try:
+ ensure_markets_loaded()
+ return exchange.price_to_precision(ex, v)
+ except Exception:
+ return format_price_magnitude_fallback(v)
+
+
+def format_usdt(value):
+ """USDT 资金类展示:固定两位小数。"""
+ if value in (None, ""):
+ return "-"
+ try:
+ return f"{float(value):.2f}"
+ except (TypeError, ValueError):
+ return str(value)
+
+
+def format_signed_usdt(value):
+ """USDT 盈亏等可正可负:+1.23 / -0.50 / 0.00"""
+ if value in (None, ""):
+ return "-"
+ try:
+ v = float(value)
+ except (TypeError, ValueError):
+ return str(value)
+ if v == 0:
+ return "0.00"
+ sign = "+" if v > 0 else ""
+ return f"{sign}{v:.2f}"
+
+
+def format_wechat_scalar_2dp(value):
+ """企业微信推送:数值统一两位小数(与交易所 tick 无关)。"""
+ if value in (None, ""):
+ return "-"
+ try:
+ return f"{float(value):.2f}"
+ except (TypeError, ValueError):
+ return str(value)
+
+
def format_hold_minutes(minutes):
if not minutes:
return "0分钟"
@@ -1866,7 +1951,7 @@ def enrich_order_item(raw_item, current_capital):
notional = item.get("notional_value")
ratio = item.get("position_ratio")
if notional is None:
- notional = round(margin * lev, 4) if margin and lev else 0
+ notional = round(margin * lev, 2) if margin and lev else 0
if ratio is None:
ratio = round(margin / current_capital * 100, 2) if current_capital else 0
item["notional_value"] = notional
@@ -2140,7 +2225,7 @@ def friendly_exchange_error(err, available_usdt=None):
or "margin" in low and ("not enough" in low or "不足" in msg)
or "balance" in low and "insufficient" in low
):
- tail = f"(当前交易账户可用约 {round(available_usdt, 4)}U)" if available_usdt is not None else ""
+ tail = f"(当前交易账户可用约 {round(available_usdt, 2)}U)" if available_usdt is not None else ""
return f"交易所下单失败:保证金不足 {tail}。请降低保证金/杠杆,或先划转USDT到合约账户。"
clean = re.sub(r"\s+", " ", msg).strip()
return f"交易所下单失败:{clean}"
@@ -2236,7 +2321,7 @@ def auto_transfer_once_per_day():
if needed <= 0:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
- ("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "skipped", f"{AUTO_TRANSFER_TO}账户已达到目标{target_amount}U")
+ ("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "skipped", f"{AUTO_TRANSFER_TO}账户已达到目标{round(float(target_amount), 2)}U")
)
conn.commit()
conn.close()
@@ -2244,12 +2329,12 @@ def auto_transfer_once_per_day():
if from_balance is not None and from_balance < needed:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
- ("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{needed}U,当前{round(from_balance,4)}U")
+ ("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{round(needed, 2)}U,当前{round(from_balance, 2)}U")
)
conn.commit()
conn.close()
send_wechat_msg(
- f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{needed}U,当前{round(from_balance,4)}U\n"
+ f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{round(needed, 2)}U,当前{round(from_balance, 2)}U\n"
f"账簿日(UTC):{transfer_day}|触发时刻(北京):{app_now_str()}"
)
return
@@ -2263,13 +2348,13 @@ def auto_transfer_once_per_day():
conn.close()
if ok:
send_wechat_msg(
- f"自动划转成功:补足到{target_amount}U,实际划转{needed}U "
+ f"自动划转成功:补足到{round(float(target_amount), 2)}U,实际划转{round(needed, 2)}U "
f"{AUTO_TRANSFER_FROM}->{AUTO_TRANSFER_TO}\n"
f"账簿日(UTC):{transfer_day}|触发时刻(北京):{app_now_str()}"
)
else:
send_wechat_msg(
- f"自动划转失败:计划补足到{target_amount}U,需划转{needed}U\n原因:{msg}\n"
+ f"自动划转失败:计划补足到{round(float(target_amount), 2)}U,需划转{round(needed, 2)}U\n原因:{msg}\n"
f"账簿日(UTC):{transfer_day}|触发时刻(北京):{app_now_str()}"
)
@@ -2724,17 +2809,17 @@ def parse_ccxt_position_metrics(position, order_leverage=None):
mark = _coerce_float(p.get("markPrice"), p.get("mark_price"), info.get("mark_price"), info.get("markPrice"))
out = {}
if initial is not None and initial > 0:
- out["initial_margin"] = round(initial, 4)
+ out["initial_margin"] = round(initial, 2)
if notional is not None and notional > 0:
- out["notional"] = round(notional, 4)
+ out["notional"] = round(notional, 2)
if unrealized is not None:
- out["unrealized_pnl"] = round(unrealized, 6)
+ out["unrealized_pnl"] = round(unrealized, 2)
if mark is not None and mark > 0:
out["mark_price"] = round(mark, 8)
return out or None
-def get_live_position_exchange_metrics(exchange_symbol, direction):
+def get_live_position_exchange_metrics(exchange_symbol, direction, order_leverage=None):
ensure_markets_loaded()
if not exchange_private_api_configured() or not exchange_symbol:
return None
@@ -2746,7 +2831,72 @@ def get_live_position_exchange_metrics(exchange_symbol, direction):
except Exception:
return None
p = _select_live_position_row(rows, exchange_symbol, direction)
- return parse_ccxt_position_metrics(p)
+ return parse_ccxt_position_metrics(p, order_leverage=order_leverage)
+
+
+def _order_row_exchange_margin_usdt(row):
+ if not row:
+ return None
+ try:
+ keys = row.keys()
+ except Exception:
+ return None
+ if "exchange_margin_usdt" not in keys:
+ return None
+ v = row["exchange_margin_usdt"]
+ if v is None:
+ return None
+ try:
+ x = float(v)
+ except (TypeError, ValueError):
+ return None
+ return x if x > 0 else None
+
+
+def margin_capital_for_trade_record(order_row):
+ """trade_records.基数:优先交易所持仓保证金快照,旧数据无快照时回退计划保证金。"""
+ ex = _order_row_exchange_margin_usdt(order_row)
+ if ex is not None:
+ return round(ex, 2)
+ if not order_row:
+ return None
+ try:
+ v = order_row["margin_capital"]
+ except (TypeError, KeyError, IndexError):
+ return None
+ if v is None:
+ return None
+ try:
+ return float(v)
+ except (TypeError, ValueError):
+ return None
+
+
+def try_persist_exchange_margin_for_order(conn, order_id, exchange_symbol, direction, order_leverage=None, max_attempts=6, sleep_s=0.45):
+ """开仓成功后持仓可见时拉取交易所保证金并写入 order_monitors(平仓后无法再取)。"""
+ if not conn or not order_id or not exchange_private_api_configured():
+ return False
+ direction = (direction or "long").lower()
+ ex_sym = (exchange_symbol or "").strip()
+ if not ex_sym:
+ return False
+ n = max(1, int(max_attempts))
+ delay = max(0.05, float(sleep_s))
+ for _ in range(n):
+ pm = get_live_position_exchange_metrics(ex_sym, direction, order_leverage=order_leverage)
+ if pm and pm.get("initial_margin") is not None:
+ try:
+ v = float(pm["initial_margin"])
+ except (TypeError, ValueError):
+ v = 0.0
+ if v > 0:
+ conn.execute(
+ "UPDATE order_monitors SET exchange_margin_usdt=? WHERE id=?",
+ (round(v, 4), int(order_id)),
+ )
+ return True
+ time.sleep(delay)
+ return False
def opened_at_str_to_ms(opened_at_str):
@@ -3055,7 +3205,7 @@ def reconcile_external_closes(conn, days=None):
stop_loss=r["stop_loss"],
initial_stop_loss=r["initial_stop_loss"] or r["stop_loss"],
take_profit=r["take_profit"],
- margin_capital=r["margin_capital"],
+ margin_capital=margin_capital_for_trade_record(r),
leverage=r["leverage"],
pnl_amount=pnl_amount,
hold_seconds=hold_seconds,
@@ -3429,6 +3579,21 @@ def check_order_monitors():
pid, sym, direction, trigger_price, stop_loss, take_profit = r["id"], r["symbol"], r["direction"], r["trigger_price"], r["stop_loss"], r["take_profit"]
margin_capital = r["margin_capital"] or DAILY_START_CAPITAL
leverage = r["leverage"] or infer_leverage(sym)
+ trade_basis_row = row_to_dict(r)
+ ex_sym = r["exchange_symbol"] or normalize_exchange_symbol(sym)
+ if _order_row_exchange_margin_usdt(r) is None and exchange_private_api_configured():
+ pm = get_live_position_exchange_metrics(ex_sym, direction, order_leverage=leverage)
+ if pm and pm.get("initial_margin") is not None:
+ try:
+ mv = float(pm["initial_margin"])
+ if mv > 0:
+ conn.execute(
+ "UPDATE order_monitors SET exchange_margin_usdt=? WHERE id=?",
+ (round(mv, 4), pid),
+ )
+ trade_basis_row["exchange_margin_usdt"] = round(mv, 4)
+ except (TypeError, ValueError):
+ pass
session_date = r["session_date"] or get_trading_day()
p = get_price(sym)
if not p: continue
@@ -3466,6 +3631,7 @@ def check_order_monitors():
direction == "long" and new_sl > float(stop_loss)
)
if should_move:
+ new_sl = round_price_to_exchange(resolve_monitor_exchange_symbol(r), new_sl)
conn.execute(
"UPDATE order_monitors SET stop_loss=?, breakeven_armed=1, breakeven_price=? WHERE id=?",
(new_sl, new_sl, pid),
@@ -3585,7 +3751,7 @@ def check_order_monitors():
stop_loss=stop_loss,
initial_stop_loss=r["initial_stop_loss"] or stop_loss,
take_profit=take_profit,
- margin_capital=margin_capital,
+ margin_capital=margin_capital_for_trade_record(trade_basis_row),
leverage=leverage,
pnl_amount=pnl_amount,
hold_seconds=hold_seconds,
@@ -3655,7 +3821,7 @@ def check_order_monitors():
stop_loss=stop_loss,
initial_stop_loss=r["initial_stop_loss"] or stop_loss,
take_profit=take_profit,
- margin_capital=margin_capital,
+ margin_capital=margin_capital_for_trade_record(trade_basis_row),
leverage=leverage,
pnl_amount=pnl_amount,
hold_seconds=hold_seconds,
@@ -3720,7 +3886,7 @@ def force_close_before_reset():
stop_loss=r["stop_loss"],
initial_stop_loss=r["initial_stop_loss"] or r["stop_loss"],
take_profit=r["take_profit"],
- margin_capital=margin_capital,
+ margin_capital=margin_capital_for_trade_record(r),
leverage=leverage,
pnl_amount=pnl_amount,
hold_seconds=hold_seconds,
@@ -3853,9 +4019,9 @@ def render_main_page(page="trade"):
local_current_capital = float(session_row["current_capital"])
funding_capital, trading_capital = get_exchange_capitals()
# 资金账户:仅展示交易所读取结果(含 0)。不可用 TOTAL_CAPITAL 兜底,否则会与实盘不符。
- funding_usdt = round(funding_capital, 4) if funding_capital is not None else None
- current_capital = round(trading_capital, 4) if trading_capital is not None else round(local_current_capital, 4)
- recommended_capital = get_recommended_capital(current_capital)
+ funding_usdt = round(funding_capital, 2) if funding_capital is not None else None
+ current_capital = round(trading_capital, 2) if trading_capital is not None else round(local_current_capital, 2)
+ recommended_capital = round(float(get_recommended_capital(current_capital)), 2)
key_list = conn.execute("SELECT * FROM key_monitors").fetchall()
key_history = conn.execute("SELECT * FROM key_monitor_history ORDER BY id DESC LIMIT 80").fetchall()
stats_bundle = compute_stats_bundle(conn, trading_day, now)
@@ -3916,6 +4082,8 @@ def render_main_page(page="trade"):
breakeven_offset_pct=BREAKEVEN_OFFSET_PCT,
occupied_miss_total=occupied_miss_total,
price_fmt=format_price_for_symbol,
+ usdt_fmt=format_usdt,
+ signed_usdt_fmt=format_signed_usdt,
entry_reason_options=list(ENTRY_REASON_OPTIONS),
entry_reason_other_value=ENTRY_REASON_OTHER,
exchange_display=EXCHANGE_DISPLAY_NAME,
@@ -3955,9 +4123,9 @@ def api_account_snapshot():
session_row = ensure_session(conn, trading_day)
local_current_capital = float(session_row["current_capital"])
funding_capital, trading_capital = get_exchange_capitals(force=True)
- funding_usdt = round(funding_capital, 4) if funding_capital is not None else None
- current_capital = round(trading_capital, 4) if trading_capital is not None else round(local_current_capital, 4)
- recommended_capital = get_recommended_capital(current_capital)
+ funding_usdt = round(funding_capital, 2) if funding_capital is not None else None
+ current_capital = round(trading_capital, 2) if trading_capital is not None else round(local_current_capital, 2)
+ recommended_capital = round(float(get_recommended_capital(current_capital)), 2)
active_count = conn.execute("SELECT COUNT(*) FROM order_monitors WHERE status='active'").fetchone()[0]
conn.close()
can_trade = trading_day_reset_allows_new_open(now) and active_count == 0
@@ -3965,7 +4133,7 @@ def api_account_snapshot():
return jsonify({
"funding_usdt": funding_usdt,
"current_capital": current_capital,
- "available_trading_usdt": round(available_trading_usdt, 4) 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,
"recommended_capital": recommended_capital,
"active_count": active_count,
"can_trade": can_trade,
@@ -3983,6 +4151,11 @@ def api_price_snapshot():
).fetchall()
conn.close()
+ try:
+ ensure_markets_loaded()
+ except Exception:
+ pass
+
symbol_set = set()
for r in key_rows:
symbol_set.add(r["symbol"])
@@ -4044,10 +4217,16 @@ def api_price_snapshot():
)
except Exception:
gate_metrics = ""
+ px_disp = format_price_for_symbol(r["symbol"], price)
+ try:
+ price_num = float(px_disp) if px_disp != "-" else float(price)
+ except Exception:
+ price_num = float(price)
key_prices.append({
"id": r["id"],
"symbol": r["symbol"],
- "price": round(price, 6),
+ "price": price_num,
+ "price_display": px_disp,
"upper_diff": upper_diff,
"upper_pct": upper_pct,
"lower_diff": lower_diff,
@@ -4075,11 +4254,10 @@ def api_price_snapshot():
payload = {
"id": r["id"],
"symbol": r["symbol"],
- "price": round(price, 6),
- "float_pnl": round(pnl, 6),
+ "float_pnl": round(pnl, 2),
"float_pct": pnl_pct,
"rr_ratio": rr_ratio,
- "plan_margin": round(margin, 4) if margin else None,
+ "plan_margin": round(margin, 2) if margin else None,
"exchange_initial_margin": None,
"exchange_notional": None,
"exchange_mark_price": None,
@@ -4093,12 +4271,24 @@ def api_price_snapshot():
if ex_metrics.get("mark_price") is not None:
payload["exchange_mark_price"] = ex_metrics["mark_price"]
if ex_metrics.get("unrealized_pnl") is not None:
- payload["float_pnl"] = round(float(ex_metrics["unrealized_pnl"]), 6)
+ payload["float_pnl"] = round(float(ex_metrics["unrealized_pnl"]), 2)
payload["pnl_source"] = "exchange"
denom = ex_metrics.get("initial_margin") or margin
payload["float_pct"] = (
round((payload["float_pnl"] / float(denom)) * 100, 4) if denom and float(denom) > 0 else pnl_pct
)
+ px_for_fmt = float(price)
+ if ex_metrics and ex_metrics.get("mark_price") is not None:
+ try:
+ px_for_fmt = float(ex_metrics["mark_price"])
+ except (TypeError, ValueError):
+ pass
+ px_disp = format_price_for_symbol(r["symbol"], px_for_fmt)
+ try:
+ payload["price"] = float(px_disp) if px_disp != "-" else px_for_fmt
+ except Exception:
+ payload["price"] = px_for_fmt
+ payload["price_display"] = px_disp
order_prices.append(payload)
return jsonify({
@@ -4149,7 +4339,7 @@ def api_order_defaults():
"exchange_symbol": exchange_symbol,
"direction": direction,
"leverage": leverage,
- "available_trading_usdt": round(available, 4) if available is not None else None
+ "available_trading_usdt": round(available, 2) if available is not None else None
})
@@ -4162,7 +4352,7 @@ def order_focus():
session_row = ensure_session(conn, trading_day)
local_current_capital = float(session_row["current_capital"])
_, trading_capital_live = get_exchange_capitals()
- current_capital = round(trading_capital_live, 4) if trading_capital_live is not None else round(local_current_capital, 4)
+ current_capital = round(trading_capital_live, 2) if trading_capital_live is not None else round(local_current_capital, 2)
raw_orders = conn.execute("SELECT * FROM order_monitors WHERE status='active' ORDER BY id DESC").fetchall()
conn.close()
orders = [enrich_order_item(row_to_dict(r), current_capital) for r in raw_orders]
@@ -4201,7 +4391,7 @@ def api_order_kline():
session_row = ensure_session(conn, trading_day)
local_current_capital = float(session_row["current_capital"])
_, trading_capital_live = get_exchange_capitals()
- current_capital = round(trading_capital_live, 4) if trading_capital_live is not None else round(local_current_capital, 4)
+ current_capital = round(trading_capital_live, 2) if trading_capital_live is not None else round(local_current_capital, 2)
row = conn.execute("SELECT * FROM order_monitors WHERE id=? AND status='active'", (order_id,)).fetchone()
conn.close()
if not row:
@@ -4236,24 +4426,29 @@ def api_order_kline():
float_pnl = calc_pnl(order_item.get("direction") or "long", entry, current_price, margin, leverage) if current_price else 0
float_pct = round((float_pnl / margin * 100), 4) if margin > 0 else 0
+ sym = order_item["symbol"]
return jsonify({
"ok": True,
"timeframe": timeframe,
"limit": limit,
"order": {
"id": order_item["id"],
- "symbol": order_item["symbol"],
+ "symbol": sym,
"direction": order_item.get("direction") or "long",
"trigger_price": order_item.get("trigger_price"),
"stop_loss": order_item.get("stop_loss"),
"take_profit": order_item.get("take_profit"),
+ "trigger_price_display": format_price_for_symbol(sym, order_item.get("trigger_price")),
+ "stop_loss_display": format_price_for_symbol(sym, order_item.get("stop_loss")),
+ "take_profit_display": format_price_for_symbol(sym, order_item.get("take_profit")),
"margin_capital": order_item.get("margin_capital"),
"leverage": order_item.get("leverage"),
"position_ratio": order_item.get("position_ratio"),
"rr_ratio": order_item.get("rr_ratio"),
"breakeven_enabled": bool(int(order_item.get("breakeven_enabled") or 0)),
"current_price": round(float(current_price), 8) if current_price else None,
- "float_pnl": round(float(float_pnl), 6),
+ "current_price_display": format_price_for_symbol(sym, current_price) if current_price else None,
+ "float_pnl": round(float(float_pnl), 2),
"float_pct": float_pct,
},
"candles": candles,
@@ -4386,8 +4581,15 @@ def add_key():
flash(f"{symbol} 当前日成交量排名为 {rank}/{total},不在前30,已拒绝添加关键位")
return redirect("/")
conn = get_db()
+ ex_sym_key = normalize_exchange_symbol(symbol)
+ try:
+ ensure_markets_loaded()
+ except Exception:
+ pass
+ upper_px = round_price_to_exchange(ex_sym_key, float(d["upper"]))
+ lower_px = round_price_to_exchange(ex_sym_key, float(d["lower"]))
conn.execute("INSERT INTO key_monitors (symbol,monitor_type,direction,upper,lower) VALUES (?,?,?,?,?)",
- (symbol, d["type"], d.get("direction", "long"), d["upper"], d["lower"]))
+ (symbol, d["type"], d.get("direction", "long"), upper_px, lower_px))
conn.commit()
conn.close()
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total})")
@@ -4414,14 +4616,19 @@ def add_order():
tgt_raw = parse_positive_float(d.get("tgt"))
except Exception:
tp_raw = sl_raw = tgt_raw = None
+ ex_miss = normalize_exchange_symbol(symbol)
+ try:
+ ensure_markets_loaded()
+ except Exception:
+ pass
insert_trade_record(
conn,
symbol=symbol,
monitor_type="下单监控",
direction=direction if direction in ("long", "short") else "long",
- trigger_price=tp_raw or 0,
- stop_loss=sl_raw or 0,
- take_profit=tgt_raw or 0,
+ trigger_price=round_price_to_exchange(ex_miss, tp_raw) if tp_raw else 0,
+ stop_loss=round_price_to_exchange(ex_miss, sl_raw) if sl_raw else 0,
+ take_profit=round_price_to_exchange(ex_miss, tgt_raw) if tgt_raw else 0,
result="错过",
miss_reason="持仓占用:一次只能持有一个仓位",
opened_at=app_now_str(),
@@ -4467,6 +4674,13 @@ def add_order():
conn.close()
flash("获取交易所实时价格失败,请稍后重试")
return redirect("/")
+ try:
+ ensure_markets_loaded()
+ except Exception:
+ pass
+ lp_r = round_price_to_exchange(exchange_symbol, live_price)
+ if lp_r is not None:
+ live_price = lp_r
sltp_mode = (d.get("sltp_mode") or "price").strip().lower()
if sltp_mode not in ("price", "pct"):
sltp_mode = "price"
@@ -4500,6 +4714,12 @@ def add_order():
conn.close()
flash("价格参数必须大于0")
return redirect("/")
+ sl_adj = round_price_to_exchange(exchange_symbol, stop_loss)
+ tp_adj = round_price_to_exchange(exchange_symbol, take_profit)
+ if sl_adj is not None:
+ stop_loss = sl_adj
+ if tp_adj is not None:
+ take_profit = tp_adj
risk_fraction = calc_risk_fraction(direction, live_price, stop_loss)
if risk_fraction is None:
conn.close()
@@ -4517,7 +4737,7 @@ def add_order():
max_margin = round(max(available_usdt * FULL_MARGIN_BUFFER_RATIO, 0), 4)
if margin_capital > max_margin:
conn.close()
- flash(f"保证金不足:交易账户可用约 {round(available_usdt,4)}U,当前最多建议 {max_margin}U")
+ flash(f"保证金不足:交易账户可用约 {round(available_usdt, 2)}U,当前最多建议 {round(max_margin, 2)}U")
return redirect("/")
position_ratio = round(margin_capital / capital_base * 100, 2) if capital_base else 0
try:
@@ -4533,6 +4753,10 @@ def add_order():
flash(friendly_exchange_error(e, available_usdt=available_usdt))
return redirect("/")
+ trigger_price = round_price_to_exchange(exchange_symbol, trigger_price)
+ stop_loss = round_price_to_exchange(exchange_symbol, stop_loss)
+ take_profit = round_price_to_exchange(exchange_symbol, take_profit)
+
make_order_chart = d.get("order_chart", "").lower() in ("1", "true", "on", "yes")
opened_at_bj = app_now_str()
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
@@ -4542,9 +4766,10 @@ def add_order():
breakeven_step_r = float(BREAKEVEN_STEP_R) if float(BREAKEVEN_STEP_R) > 0 else 1.0
risk_amount_final = calc_risk_amount_from_plan(direction, trigger_price, stop_loss, margin_capital, leverage) or risk_amount
if direction == "short":
- breakeven_price = round(float(trigger_price) * (1 - breakeven_offset_pct / 100.0), 8)
+ breakeven_raw = float(trigger_price) * (1 - breakeven_offset_pct / 100.0)
else:
- breakeven_price = round(float(trigger_price) * (1 + breakeven_offset_pct / 100.0), 8)
+ breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
+ breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
conn.execute(
"INSERT INTO order_monitors (symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, margin_capital, leverage, trade_style, risk_percent, risk_amount, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
@@ -4557,6 +4782,8 @@ def add_order():
)
conn.commit()
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
+ try_persist_exchange_margin_for_order(conn, new_order_id, exchange_symbol, direction, order_leverage=leverage)
+ conn.commit()
opens_today_after = conn.execute(
"SELECT COUNT(*) FROM order_monitors WHERE session_date=?",
(trading_day,),
@@ -4632,9 +4859,9 @@ def add_order():
_, trading_capital_after = get_exchange_capitals(force=True)
account_base_display = (
- round(float(trading_capital_after), 4)
+ round(float(trading_capital_after), 2)
if trading_capital_after is not None
- else round(float(capital_base), 4)
+ else round(float(capital_base), 2)
)
account_name = (os.getenv("GATE_ACCOUNT_LABEL") or "gate实盘账户").strip()
dir_text = "多头(long)" if direction == "long" else "空头(short)"
@@ -4645,12 +4872,12 @@ def add_order():
)
rr_show = planned_rr if planned_rr is not None else "-"
try:
- rr_show_fmt = round(float(planned_rr), 4) if planned_rr is not None else None
+ rr_show_fmt = f"{float(planned_rr):.2f}" if planned_rr is not None else None
except (TypeError, ValueError):
rr_show_fmt = None
rr_line = f"RR {rr_show_fmt} : 1" if rr_show_fmt is not None else f"RR {rr_show} : 1"
ep_wx = format_price_for_symbol(symbol, trigger_price)
- sl_wx = format_price_for_symbol(symbol, stop_loss)
+ sl_wx = format_wechat_scalar_2dp(stop_loss)
tp_wx = format_price_for_symbol(symbol, take_profit)
be_wx = format_price_for_symbol(symbol, breakeven_price)
style_zh = "Swing 波段" if trade_style == "swing" else "Trend 趋势"
@@ -4660,13 +4887,13 @@ def add_order():
"🧾 订单基础信息",
f"🔖 交易所订单 ID:{open_order_id}",
f"📈 交易风格:{style_zh}",
- f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), 4)} U",
+ f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), 2)} U",
"📊 仓位配置详情",
f"账户基数:{account_base_display} USDT",
f"合约杠杆:{leverage} 倍",
- f"名义仓位:{notional_value} USDT",
+ f"名义仓位:{format_wechat_scalar_2dp(notional_value)} USDT",
f"仓位占比:{position_ratio}%",
- f"合约张数:{amount} 张",
+ f"合约张数:{format_wechat_scalar_2dp(amount)} 张",
f"折算标的:{base_amount} {journal_coin_from_symbol(symbol)}",
"🎯 价位 & 盈亏比",
f"开仓成交价:{ep_wx}",
@@ -4683,8 +4910,8 @@ def add_order():
send_wechat_msg("\n".join(wx_lines))
flash_lines = [
- f"实盘开单成功:风格 {trade_style};风险 {risk_percent}%≈{risk_amount_final}U;基数 {margin_capital}U,杠杆 {leverage}x,名义仓位 {notional_value}U,仓位占比 {position_ratio}%,合约张数 {amount}(折算标的 {base_amount}),"
- f"计划RR {planned_rr if planned_rr is not None else '-'};已在交易所挂条件止盈/止损委托(非仓位绑定型)",
+ f"实盘开单成功:风格 {trade_style};风险 {risk_percent}%≈{round(float(risk_amount_final), 2)}U;基数 {round(float(margin_capital), 2)}U,杠杆 {leverage}x,名义仓位 {format_wechat_scalar_2dp(notional_value)}U,仓位占比 {position_ratio}%,合约张数 {format_wechat_scalar_2dp(amount)}(折算标的 {base_amount}),"
+ f"计划RR {format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else '-'};已在交易所挂条件止盈/止损委托(非仓位绑定型)",
f"本交易日累计开仓:{opens_today_after}",
]
if chart_url:
@@ -4694,7 +4921,7 @@ def add_order():
if opens_today_before < DAILY_OPEN_ALERT_THRESHOLD <= opens_today_after:
advice = ai_short_advice(
f"用户在北京时间交易日 {trading_day} 已累计开仓 {opens_today_after} 次(阈值 {DAILY_OPEN_ALERT_THRESHOLD})。"
- f"最新一笔:{symbol} {direction},杠杆{leverage}x,基数{margin_capital}U。"
+ f"最新一笔:{symbol} {direction},杠杆{leverage}x,基数{round(float(margin_capital), 2)}U。"
f"用户自述“上头了”。请给克制提醒。"
)
if advice:
@@ -4931,6 +5158,7 @@ def del_order(id):
cancel_gate_swap_trigger_orders(row["exchange_symbol"] or normalize_exchange_symbol(row["symbol"]))
session_date = row["session_date"] or get_trading_day()
session_capital = update_session_capital(conn, session_date, pnl_amount)
+ row_snap = conn.execute("SELECT * FROM order_monitors WHERE id=?", (id,)).fetchone() or row
insert_trade_record(
conn,
symbol=row["symbol"],
@@ -4940,7 +5168,7 @@ def del_order(id):
stop_loss=row["stop_loss"],
initial_stop_loss=row["initial_stop_loss"] or row["stop_loss"],
take_profit=row["take_profit"],
- margin_capital=row["margin_capital"],
+ margin_capital=margin_capital_for_trade_record(row_snap),
leverage=row["leverage"],
pnl_amount=pnl_amount,
hold_seconds=hold_seconds,
@@ -4985,6 +5213,7 @@ def del_order(id):
hold_seconds = calc_hold_seconds(opened_at, closed_at_dt)
session_date = row["session_date"] or get_trading_day(closed_at_dt)
update_session_capital(conn, session_date, pnl_amount)
+ row_snap = conn.execute("SELECT * FROM order_monitors WHERE id=?", (id,)).fetchone() or row
insert_trade_record(
conn,
symbol=row["symbol"],
@@ -4994,7 +5223,7 @@ def del_order(id):
stop_loss=row["stop_loss"],
initial_stop_loss=row["initial_stop_loss"] or row["stop_loss"],
take_profit=row["take_profit"],
- margin_capital=row["margin_capital"],
+ margin_capital=margin_capital_for_trade_record(row_snap),
leverage=row["leverage"],
pnl_amount=pnl_amount,
hold_seconds=hold_seconds,
@@ -5025,15 +5254,28 @@ def del_order(id):
def add_miss():
d = request.form
direction = d.get("direction", "long")
+ sym_in = normalize_symbol_input(d.get("symbol"))
+ ex_sym = normalize_exchange_symbol(sym_in)
+ try:
+ ensure_markets_loaded()
+ except Exception:
+ pass
+ try:
+ tp_px = round_price_to_exchange(ex_sym, float(d["tp"]))
+ sl_px = round_price_to_exchange(ex_sym, float(d["sl"]))
+ tgt_px = round_price_to_exchange(ex_sym, float(d["tgt"]))
+ except Exception:
+ flash("价格格式错误")
+ return redirect("/records")
conn = get_db()
insert_trade_record(
conn,
- symbol=d["symbol"],
+ symbol=sym_in,
monitor_type=d["type"],
direction=direction,
- trigger_price=d["tp"],
- stop_loss=d["sl"],
- take_profit=d["tgt"],
+ trigger_price=tp_px,
+ stop_loss=sl_px,
+ take_profit=tgt_px,
result="错过",
miss_reason=d["reason"],
opened_at=app_now_str(),
@@ -5379,11 +5621,20 @@ def api_trade_record_review_update():
reviewed_entry_reason_update = s or None
conn = get_db()
- row = conn.execute("SELECT risk_amount FROM trade_records WHERE id=?", (rec_id,)).fetchone()
+ row = conn.execute("SELECT risk_amount, symbol FROM trade_records WHERE id=?", (rec_id,)).fetchone()
if not row:
conn.close()
return jsonify({"ok": False, "msg": "记录不存在"}), 404
risk_amount = row["risk_amount"]
+ ex_review = resolve_ccxt_price_symbol(row["symbol"])
+ try:
+ ensure_markets_loaded()
+ except Exception:
+ pass
+ if reviewed_stop_loss is not None:
+ reviewed_stop_loss = round_price_to_exchange(ex_review, reviewed_stop_loss)
+ if reviewed_take_profit is not None:
+ reviewed_take_profit = round_price_to_exchange(ex_review, reviewed_take_profit)
actual_rr = calc_actual_rr(reviewed_pnl_amount, risk_amount)
base_params = [
reviewed_opened_at,
diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html
index 7d59cb9..41e968c 100644
--- a/crypto_monitor_gate/templates/index.html
+++ b/crypto_monitor_gate/templates/index.html
@@ -130,14 +130,14 @@
胜率 {% if s.win_rate_pct is not none %}{{ s.win_rate_pct }}%{% else %}-{% endif %}
-
- 亏损额合计(U) {{ s.loss_sum_u }}
- 单笔最大亏损(U) {% if s.max_single_loss is not none %}{{ s.max_single_loss }}{% else %}-{% endif %}
- 单笔最大盈利(U) {% if s.max_single_profit is not none %}{{ s.max_single_profit }}{% else %}-{% endif %}
- 最大回撤(U) {{ s.max_drawdown_u }}
+ 净盈亏(U) {{ signed_usdt_fmt(s.net_pnl_u) }}
+ 亏损额合计(U) {{ usdt_fmt(s.loss_sum_u) }}
+ 单笔最大亏损(U) {% if s.max_single_loss is not none %}{{ signed_usdt_fmt(s.max_single_loss) }}{% else %}-{% endif %}
+ 单笔最大盈利(U) {% if s.max_single_profit is not none %}{{ usdt_fmt(s.max_single_profit) }}{% else %}-{% endif %}
+ 最大回撤(U) {{ usdt_fmt(s.max_drawdown_u) }}
当前连续亏损笔数 {{ s.consecutive_losses }}
最长连续亏损(交易日) {{ s.max_loss_streak_days }} 天
- 期内最大亏损日 {% if s.worst_day %}{{ s.worst_day }}({{ s.worst_day_pnl }}U){% else %}-{% endif %}
+ 期内最大亏损日 {% if s.worst_day %}{{ s.worst_day }}({{ signed_usdt_fmt(s.worst_day_pnl) }}U){% else %}-{% endif %}
{% endmacro %}
@@ -165,9 +165,9 @@
- 资金账户(USDT) {% if funding_usdt is not none %}{{ funding_usdt }}U{% else %}—{% endif %}
+ 资金账户(USDT) {% if funding_usdt is not none %}{{ usdt_fmt(funding_usdt) }}U{% else %}—{% endif %}
- 当日资金(交易账户) {{ current_capital }}U
+ 当日资金(交易账户) {{ usdt_fmt(current_capital) }}U
实时价格更新时间:--(北京时间 UTC+8)
@@ -202,7 +202,7 @@
{{ k.symbol }} | {{ k.monitor_type }} | {{ '做多' if k.direction == 'long' else '做空' }}
- 上:{{ k.upper }} 下:{{ k.lower }}
+ 上:{{ price_fmt(k.symbol, k.upper) }} 下:{{ price_fmt(k.symbol, k.lower) }}
| 已提醒:{{ k.notification_count or 0 }}/{{ k.max_notify or 3 }}
| 现价:-
| 距上沿:-
@@ -224,7 +224,7 @@
{{ h.symbol }} | {{ h.monitor_type }} | {{ '做多' if h.direction == 'long' else '做空' }} | {{ h.close_reason }}
- 上:{{ h.upper }} 下:{{ h.lower }} | 提醒次数:{{ h.notification_count }} | {{ (h.closed_at or '-')[:16] }}
+ 上:{{ price_fmt(h.symbol, h.upper) }} 下:{{ price_fmt(h.symbol, h.lower) }} | 提醒次数:{{ h.notification_count }} | {{ (h.closed_at or '-')[:16] }}
{% if h.last_alert_message %} {{ h.last_alert_message[:200] }}{% if h.last_alert_message|length > 200 %}…{% endif %} {% endif %}
{% else %}
@@ -252,7 +252,7 @@
以损定仓:风险 {{ risk_percent }}% |移动保本:下单可勾选关闭;开启时 {{ breakeven_rr_trigger }}R 触发(每 1R 阶梯上移),偏移 {{ breakeven_offset_pct }}%
- 划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天北京时间 {{ auto_transfer_bj_hour }}:00起该整点小时内尝试;账簿按 UTC 自然日去重;界面时间为北京;将 {{ auto_transfer_to }} 补足到 {{ auto_transfer_amount }}U,来自 {{ auto_transfer_from }})
+ 划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天北京时间 {{ auto_transfer_bj_hour }}:00起该整点小时内尝试;账簿按 UTC 自然日去重;界面时间为北京;将 {{ auto_transfer_to }} 补足到 {{ usdt_fmt(auto_transfer_amount) }}U,来自 {{ auto_transfer_from }})
|