修改币种精度

This commit is contained in:
dekun
2026-05-14 11:30:52 +08:00
parent 8290bbc060
commit 7978d40a74
14 changed files with 1254 additions and 263 deletions
+124 -53
View File
@@ -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,