From df57c417d90355b0e7c70cdbb992729cd6db0f05 Mon Sep 17 00:00:00 2001 From: ZHU MINCHAO <48154958+MinchaoZhu@users.noreply.github.com> Date: Thu, 5 Nov 2026 22:41:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8B=E5=8D=95=E9=A2=84=E8=A7=88=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E6=9B=B4=E6=96=B0=EF=BC=9A=E7=8E=B0=E8=B4=A7=E4=B9=B0?= =?UTF-8?q?=E5=8D=96=E3=80=81=E6=B0=B8=E7=BB=AD=E5=BC=80=E5=B9=B3=E4=BB=93?= =?UTF-8?q?=E3=80=81=E4=BA=92=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 + .../ModFiles/Localization/de.csv | 6 +- .../ModFiles/Localization/en.csv | 6 +- .../ModFiles/Localization/es.csv | 6 +- .../ModFiles/Localization/fr.csv | 6 +- .../ModFiles/Localization/ja.csv | 6 +- .../ModFiles/Localization/pl.csv | 6 +- .../ModFiles/Localization/ru.csv | 6 +- .../ModFiles/Localization/zh.csv | 6 +- src/Decentralized/TerminalWindow.cs | 89 ++++++++++++++++--- 10 files changed, 121 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b56ff..0f252af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 未发布 + +- 下单时的预览改成输入时实时更新(不重建页面,输入框不会失焦):Poinbase 现货每个交易对显示买入、卖出的预估得到数量、平均成交价和滑点;永续开仓显示名义价值、预估平均成交价(含滑点)、手续费和强平价;有仓位时显示平仓的预估成交价、手续费、回到账户的 CRC 和盈亏;Web3 互换的预览也改为实时。 + ## 1.0.0-rc10 - 浏览器:转出、转入分成两列,按列宽显示完整地址和名称,放不下时先去名称、再用缩写,鼠标悬停显示完整地址和名称。 diff --git a/src/Decentralized/ModFiles/Localization/de.csv b/src/Decentralized/ModFiles/Localization/de.csv index fd1c1fa..837b580 100644 --- a/src/Decentralized/ModFiles/Localization/de.csv +++ b/src/Decentralized/ModFiles/Localization/de.csv @@ -464,7 +464,6 @@ perp.rate,{0} Preis {1} Finanzierungsgebühr {2}%/Nacht perp.fund,Versicherungsfonds {0} perp.open,Eröffnen perp.margin,Margin-Betrag (CRC) -perp.preview,Nominalwert {0} Ausführung {1} perp.do.open,Eröffnen perp.do.add,+ Margin perp.do.close,Schließen @@ -953,3 +952,8 @@ explorer.col.from,Von explorer.col.to,An rig.st.cond,Zustand asic.st.dead,Durchgebrannt +perp.preview,Nominalwert {0} gesch. Ø-Preis {1} (Slippage {2}%) +perp.sim.close,Schließen Nominalwert {0} gesch. Ø-Preis {1} (Slippage {2}%) +perp.sim.close2,Gebühr {0} ca. {1} CRC zurück (G/V {2}) +trade.sim.buy,Kauf ergibt ca. {0} {1} Ø {2} Slippage {3}% +trade.sim.sell,Verkauf ergibt ca. {0} {1} Ø {2} Slippage {3}% diff --git a/src/Decentralized/ModFiles/Localization/en.csv b/src/Decentralized/ModFiles/Localization/en.csv index d02d50e..112599e 100644 --- a/src/Decentralized/ModFiles/Localization/en.csv +++ b/src/Decentralized/ModFiles/Localization/en.csv @@ -464,7 +464,6 @@ perp.rate,{0} price {1} funding {2}%/night perp.fund,Insurance fund {0} perp.open,Open perp.margin,Margin amount (CRC) -perp.preview,Notional Value {0} fill {1} perp.do.open,Open perp.do.add,Add margin perp.do.close,Close @@ -953,3 +952,8 @@ explorer.col.from,From explorer.col.to,To rig.st.cond,Condition asic.st.dead,Burnt out +perp.preview,Notional {0} est. avg price {1} (slippage {2}%) +perp.sim.close,Close notional {0} est. avg price {1} (slippage {2}%) +perp.sim.close2,Fee {0} about {1} CRC back (P/L {2}) +trade.sim.buy,Buy gets about {0} {1} avg {2} slippage {3}% +trade.sim.sell,Sell gets about {0} {1} avg {2} slippage {3}% diff --git a/src/Decentralized/ModFiles/Localization/es.csv b/src/Decentralized/ModFiles/Localization/es.csv index 1c488ec..f482977 100644 --- a/src/Decentralized/ModFiles/Localization/es.csv +++ b/src/Decentralized/ModFiles/Localization/es.csv @@ -464,7 +464,6 @@ perp.rate,{0} precio {1} financiación {2}%/noche perp.fund,Fondo de seguro {0} perp.open,Abrir perp.margin,Monto de margen (CRC) -perp.preview,Valor nocional {0} ejecución {1} perp.do.open,Abrir perp.do.add,+ Margen perp.do.close,Cerrar @@ -953,3 +952,8 @@ explorer.col.from,De explorer.col.to,A rig.st.cond,Condición asic.st.dead,Quemado +perp.preview,Valor nominal {0} precio medio est. {1} (deslizamiento {2}%) +perp.sim.close,Cierre valor nominal {0} precio medio est. {1} (deslizamiento {2}%) +perp.sim.close2,Comisión {0} se recuperan unos {1} CRC (G/P {2}) +trade.sim.buy,La compra da unos {0} {1} medio {2} deslizamiento {3}% +trade.sim.sell,La venta da unos {0} {1} medio {2} deslizamiento {3}% diff --git a/src/Decentralized/ModFiles/Localization/fr.csv b/src/Decentralized/ModFiles/Localization/fr.csv index c7bdca3..13bfa78 100644 --- a/src/Decentralized/ModFiles/Localization/fr.csv +++ b/src/Decentralized/ModFiles/Localization/fr.csv @@ -464,7 +464,6 @@ perp.rate,{0} prix {1} financement {2} %/nuit perp.fund,Fonds d'assurance {0} perp.open,Ouvrir perp.margin,Montant de la marge (CRC) -perp.preview,Valeur notionnelle {0} exécution {1} perp.do.open,Ouvrir perp.do.add,+ Marge perp.do.close,Fermer @@ -953,3 +952,8 @@ explorer.col.from,De explorer.col.to,Vers rig.st.cond,Condition asic.st.dead,Grillé +perp.preview,Valeur notionnelle {0} prix moyen est. {1} (glissement {2}%) +perp.sim.close,Clôture valeur notionnelle {0} prix moyen est. {1} (glissement {2}%) +perp.sim.close2,Frais {0} environ {1} CRC récupérés (P/L {2}) +trade.sim.buy,L'achat donne environ {0} {1} moy. {2} glissement {3}% +trade.sim.sell,La vente donne environ {0} {1} moy. {2} glissement {3}% diff --git a/src/Decentralized/ModFiles/Localization/ja.csv b/src/Decentralized/ModFiles/Localization/ja.csv index c01a05c..d717b3f 100644 --- a/src/Decentralized/ModFiles/Localization/ja.csv +++ b/src/Decentralized/ModFiles/Localization/ja.csv @@ -464,7 +464,6 @@ perp.rate,{0} 価格 {1} 資金調達料 {2}%/夜 perp.fund,保険基金 {0} perp.open,新規建て perp.margin,証拠金額(CRC) -perp.preview,想定元本 {0} 約定価格 {1} perp.do.open,新規建て perp.do.add,証拠金追加 perp.do.close,決済 @@ -953,3 +952,8 @@ explorer.col.from,送信元 explorer.col.to,送信先 rig.st.cond,コンディション asic.st.dead,焼損 +perp.preview,想定元本 {0} 推定平均価格 {1}(スリッページ {2}%) +perp.sim.close,決済 想定元本 {0} 推定平均価格 {1}(スリッページ {2}%) +perp.sim.close2,手数料 {0} 約 {1} CRC が戻る(損益 {2}) +trade.sim.buy,購入で約 {0} {1} 平均 {2} スリッページ {3}% +trade.sim.sell,売却で約 {0} {1} 平均 {2} スリッページ {3}% diff --git a/src/Decentralized/ModFiles/Localization/pl.csv b/src/Decentralized/ModFiles/Localization/pl.csv index 9ecd258..665d126 100644 --- a/src/Decentralized/ModFiles/Localization/pl.csv +++ b/src/Decentralized/ModFiles/Localization/pl.csv @@ -464,7 +464,6 @@ perp.rate,{0} cena {1} finansowanie {2}%/noc perp.fund,Fundusz ubezpieczeniowy {0} perp.open,Otwórz perp.margin,Kwota depozytu (CRC) -perp.preview,Wartość nominalna {0} realizacja {1} perp.do.open,Otwórz perp.do.add,+ Depozyt perp.do.close,Zamknij @@ -953,3 +952,8 @@ explorer.col.from,Od explorer.col.to,Do rig.st.cond,Kondycja asic.st.dead,Spalony +perp.preview,Wartość nominalna {0} szac. śr. cena {1} (poślizg {2}%) +perp.sim.close,Zamknięcie wartość nominalna {0} szac. śr. cena {1} (poślizg {2}%) +perp.sim.close2,Opłata {0} zwrot ok. {1} CRC (zysk/strata {2}) +trade.sim.buy,Zakup da ok. {0} {1} śr. {2} poślizg {3}% +trade.sim.sell,Sprzedaż da ok. {0} {1} śr. {2} poślizg {3}% diff --git a/src/Decentralized/ModFiles/Localization/ru.csv b/src/Decentralized/ModFiles/Localization/ru.csv index 5bbb960..7ad67f3 100644 --- a/src/Decentralized/ModFiles/Localization/ru.csv +++ b/src/Decentralized/ModFiles/Localization/ru.csv @@ -464,7 +464,6 @@ perp.rate,{0} цена {1} ставка финансирования {2}%/но perp.fund,Страховой фонд {0} perp.open,Открыть perp.margin,Сумма маржи (CRC) -perp.preview,Номинальная стоимость {0} цена исполнения {1} perp.do.open,Открыть perp.do.add,+ Маржа perp.do.close,Закрыть @@ -953,3 +952,8 @@ explorer.col.from,От explorer.col.to,Кому rig.st.cond,Состояние asic.st.dead,Сгорел +perp.preview,Номинал {0} ожид. ср. цена {1} (проскальзывание {2}%) +perp.sim.close,Закрытие номинал {0} ожид. ср. цена {1} (проскальзывание {2}%) +perp.sim.close2,Комиссия {0} вернётся около {1} CRC (П/У {2}) +trade.sim.buy,Покупка даст около {0} {1} ср. {2} проскальзывание {3}% +trade.sim.sell,Продажа даст около {0} {1} ср. {2} проскальзывание {3}% diff --git a/src/Decentralized/ModFiles/Localization/zh.csv b/src/Decentralized/ModFiles/Localization/zh.csv index 25e3009..84065ea 100644 --- a/src/Decentralized/ModFiles/Localization/zh.csv +++ b/src/Decentralized/ModFiles/Localization/zh.csv @@ -463,7 +463,6 @@ perp.rate,"{0} 现价 {1} 资金费 {2}%/晚" perp.fund,"保险基金 {0}" perp.open,开仓 perp.margin,保证金金额(CRC) -perp.preview,名义价值 {0} 成交价 {1} perp.do.open,开仓 perp.do.add,追加保证金 perp.do.close,平仓 @@ -952,3 +951,8 @@ explorer.col.from,转出 explorer.col.to,转入 rig.st.cond,成色 asic.st.dead,已烧毁 +perp.preview,名义价值 {0} 预估均价 {1}(滑点 {2}%) +perp.sim.close,平仓 名义价值 {0} 预估均价 {1}(滑点 {2}%) +perp.sim.close2,手续费 {0} 约回 {1} CRC(盈亏 {2}) +trade.sim.buy,买入约得 {0} {1} 均价 {2} 滑点 {3}% +trade.sim.sell,卖出约得 {0} {1} 均价 {2} 滑点 {3}% diff --git a/src/Decentralized/TerminalWindow.cs b/src/Decentralized/TerminalWindow.cs index ab97b9f..8d429a9 100644 --- a/src/Decentralized/TerminalWindow.cs +++ b/src/Decentralized/TerminalWindow.cs @@ -110,7 +110,7 @@ namespace Decentralized { var b = CustomUIManager.Window(Id, L.T("term.title")); if (b == null) return; - _picked.Clear(); _clear.Clear(); _rows.Clear(); + _picked.Clear(); _clear.Clear(); _rows.Clear(); _live.Clear(); b.SetSize(WinW, WinH).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle(); // 黑色外边框包裹墨水屏 b.BeginPanel(ScreenW + Bezel * 2, ScreenH + Bezel * 2, TextAnchor.MiddleCenter, Bezel + 3f, 0f); @@ -148,7 +148,7 @@ namespace Decentralized private static void Rebuild() { var m = CustomUIManager.Instance; - _picked.Clear(); _clear.Clear(); _rows.Clear(); + _picked.Clear(); _clear.Clear(); _rows.Clear(); _live.Clear(); CustomUIBuilder.ClearContainer(_topRoot); CustomUIBuilder.ClearContainer(_bodyRoot); CustomUIBuilder.ClearContainer(_noteRoot); @@ -285,12 +285,36 @@ namespace Decentralized b.End(); } + // 输入时实时更新的预览文字:输入框每次输入只改这些文字,不重建页面(重建会让输入框失焦)。 + private static readonly List>> _live = new List>>(); + + /// 加一行实时预览:文字由 text() 算,输入框内容变化时刷新;没有内容时是一个空行占位。 + private static void LiveLabel(CustomUIBuilder b, Func text, string style) + { + string t = text(); + b.AddLabel(string.IsNullOrEmpty(t) ? " " : t, style); + if (b.Last != null && b.Last.text != null) _live.Add(new KeyValuePair>(b.Last, text)); + } + + private static void RefreshLive() + { + foreach (var kv in _live) + { + if (kv.Key == null || kv.Key.text == null) continue; + string t = kv.Value(); + kv.Key.text.text = string.IsNullOrEmpty(t) ? " " : t; + } + } + + private static bool ParseAmount(out double v) => + double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out v) && v > 0; + private static void AmountRow(CustomUIBuilder b, bool label = true) { if (label) b.AddLabel(L.T("trade.amount")); b.BeginPanel(RightW - 30f, -1f, TextAnchor.MiddleCenter, 0f, 0f); _clear.Add(b.Last.image); - b.AddInput("0", _amount, s => _amount = s, "amount", s => _amount = s); + b.AddInput("0", _amount, s => { _amount = s; RefreshLive(); }, "amount", s => { _amount = s; RefreshLive(); }); b.End(); } @@ -361,10 +385,11 @@ namespace Decentralized AmountRow(b); picker(L.T("swap.pay"), _swapFrom, true); picker(L.T("swap.get"), _swapTo, false); - double v; - bool ok = double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out v) && v > 0; - var q = ok ? m.Quote(_swapFrom, _swapTo, v) : null; - if (q != null) b.AddLabel(L.T("swap.preview", Fmt.Num(q.Out), _swapTo, q.Hops, (q.Impact * 100).ToString("0.0", CultureInfo.InvariantCulture)), "swap_preview_dim"); + LiveLabel(b, () => + { + var q = ParseAmount(out double v) ? m.Quote(_swapFrom, _swapTo, v) : null; + return q == null ? "" : L.T("swap.preview", Fmt.Num(q.Out), _swapTo, q.Hops, (q.Impact * 100).ToString("0.0", CultureInfo.InvariantCulture)); + }, "swap_preview_dim"); b.AddButton(L.T("swap.go"), () => Swap()); } @@ -403,6 +428,8 @@ namespace Decentralized { var p = pool; b.AddLabel(L.T("trade.pair", p.Base, p.Quote, L.T("venue." + p.Venue)) + " " + L.T("trade.price", Fmt.Num(p.Price))); + LiveLabel(b, () => SpotPreview(p, true), "trade_preview_dim"); + LiveLabel(b, () => SpotPreview(p, false), "trade_preview_dim"); b.BeginRow(4f); b.AddButton(L.T("trade.buy"), () => Trade(p, buy: true)); b.AddButton(L.T("trade.sell"), () => Trade(p, buy: false)); @@ -410,6 +437,17 @@ namespace Decentralized } } + /// 现货模拟:按输入数量算买入或卖出能得到多少、平均成交价、相对现价的滑点(不改池子)。买入数量是计价币,卖出数量是标的币。 + private static string SpotPreview(Pool p, bool buy) + { + if (!ParseAmount(out double v)) return ""; + double got = buy ? p.BaseOut(v) : p.QuoteOut(v); + if (got <= 0) return ""; + double avg = buy ? v / got : got / v; + double slip = buy ? avg / p.Price - 1 : 1 - avg / p.Price; + return L.T(buy ? "trade.sim.buy" : "trade.sim.sell", Fmt.Num(got), buy ? p.Base : p.Quote, Fmt.Num(avg), (Math.Max(0, slip) * 100).ToString("0.0", CultureInfo.InvariantCulture)); + } + private static readonly float[] HoldWBase = new[] { 80f, 100f, 110f, 110f }; private static float[] HoldW => Sc(HoldWBase); private static readonly TextAnchor[] HoldAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight }; @@ -641,12 +679,15 @@ namespace Decentralized } r.AddLabel(L.T("perp.margin")); AmountRow(r, false); - if (held == null && double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out double m) && m > 0) + if (held == null) { - double notional = m * _perpLev, spot = PerpSpot(_perpProduct), exec = bk.Exec(spot, notional, _perpLong, day); - var ghost = new PerpPosition { Long = _perpLong, Entry = exec, Size = notional / exec, Margin = m }; - r.AddLabel(L.T("perp.preview", Fmt.Credits(notional), Fmt.Num(exec)), "perp_preview_dim"); - r.AddLabel(L.T("perp.preview2", Fmt.Num(notional * Tuning.PerpFee), Fmt.Num(ghost.LiqPrice)), "perp_preview_dim"); + LiveLabel(r, () => PerpOpenPreview(1), "perp_preview_dim"); + LiveLabel(r, () => PerpOpenPreview(2), "perp_preview_dim"); + } + else + { + LiveLabel(r, () => PerpClosePreview(held, 1), "perp_preview_dim"); + LiveLabel(r, () => PerpClosePreview(held, 2), "perp_preview_dim"); } r.BeginRow(6f); if (held == null) r.AddButton(L.T("perp.do.open"), PerpOpen); @@ -656,6 +697,30 @@ namespace Decentralized }); } + /// 开仓模拟:名义价值、预估平均成交价(含滑点)、手续费、强平价。line 1 是价格行,2 是费用和强平价行。 + private static string PerpOpenPreview(int line) + { + if (!ParseAmount(out double m)) return ""; + var bk = Session.Data.Perps; + int day = StoreStation.GetDayCounter(); + double notional = m * _perpLev, spot = PerpSpot(_perpProduct), exec = bk.Exec(spot, notional, _perpLong, day); + if (line == 1) return L.T("perp.preview", Fmt.Credits(notional), Fmt.Num(exec), ((exec / spot - 1) * 100).ToString("+0.0;-0.0;0.0", CultureInfo.InvariantCulture)); + var ghost = new PerpPosition { Long = _perpLong, Entry = exec, Size = notional / exec, Margin = m }; + return L.T("perp.preview2", Fmt.Num(notional * Tuning.PerpFee), Fmt.Num(ghost.LiqPrice)); + } + + /// 平仓模拟:按现价平掉整个仓位的预估成交价(含滑点)、手续费、回到账户的 CRC 和盈亏。 + private static string PerpClosePreview(PerpPosition pos, int line) + { + var bk = Session.Data.Perps; + double spot = PerpSpot(pos.Product); + double exec = bk.Exec(spot, pos.Notional(spot), !pos.Long, StoreStation.GetDayCounter()); + double fee = pos.Notional(exec) * Tuning.PerpFee; + double payout = Math.Max(0, pos.Equity(exec) - fee); + if (line == 1) return L.T("perp.sim.close", Fmt.Credits(pos.Notional(spot)), Fmt.Num(exec), ((exec / spot - 1) * 100).ToString("+0.0;-0.0;0.0", CultureInfo.InvariantCulture)); + return L.T("perp.sim.close2", Fmt.Num(fee), Fmt.Num(payout), (payout - pos.Margin >= 0 ? "+" : "") + Fmt.Num(payout - pos.Margin)); + } + private static readonly float[] PoolWBase = new[] { 110f, 50f, 64f, 64f, 50f, 78f }; private static float[] PoolW => Sc(PoolWBase); private static readonly TextAnchor[] PoolAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight };