下单预览实时更新:现货买卖、永续开平仓、互换

This commit is contained in:
ZHU MINCHAO committed 2026-11-05 22:41:00 +08:00
1 parent 465e46c6bb
commit df57c417d9
10 files changed
+121 -20

No files matched your search

+4
View File
@@ -1,5 +1,9 @@
# Changelog
## 未发布
- 下单时的预览改成输入时实时更新(不重建页面,输入框不会失焦):Poinbase 现货每个交易对显示买入、卖出的预估得到数量、平均成交价和滑点;永续开仓显示名义价值、预估平均成交价(含滑点)、手续费和强平价;有仓位时显示平仓的预估成交价、手续费、回到账户的 CRC 和盈亏;Web3 互换的预览也改为实时。
## 1.0.0-rc10
- 浏览器:转出、转入分成两列,按列宽显示完整地址和名称,放不下时先去名称、再用缩写,鼠标悬停显示完整地址和名称。
@@ -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}%
1 # Probably Decentralized localization table: German (de)
464 perp.fund
465 perp.open
466 perp.margin
perp.preview
467 perp.do.open
468 perp.do.add
469 perp.do.close
952 explorer.col.to
953 rig.st.cond
954 asic.st.dead
955 perp.preview
956 perp.sim.close
957 perp.sim.close2
958 trade.sim.buy
959 trade.sim.sell
@@ -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}%
1 # Probably Decentralized localization table: English (en)
464 perp.fund
465 perp.open
466 perp.margin
perp.preview
467 perp.do.open
468 perp.do.add
469 perp.do.close
952 explorer.col.to
953 rig.st.cond
954 asic.st.dead
955 perp.preview
956 perp.sim.close
957 perp.sim.close2
958 trade.sim.buy
959 trade.sim.sell
@@ -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}%
1 # Probably Decentralized localization table: Spanish (es)
464 perp.fund
465 perp.open
466 perp.margin
perp.preview
467 perp.do.open
468 perp.do.add
469 perp.do.close
952 explorer.col.to
953 rig.st.cond
954 asic.st.dead
955 perp.preview
956 perp.sim.close
957 perp.sim.close2
958 trade.sim.buy
959 trade.sim.sell
@@ -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}%
1 # Probably Decentralized localization table: French (fr)
464 perp.fund
465 perp.open
466 perp.margin
perp.preview
467 perp.do.open
468 perp.do.add
469 perp.do.close
952 explorer.col.to
953 rig.st.cond
954 asic.st.dead
955 perp.preview
956 perp.sim.close
957 perp.sim.close2
958 trade.sim.buy
959 trade.sim.sell
@@ -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}%
Can't render this file because it contains an unexpected character in line 487 and column 24.
@@ -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}%
1 # Probably Decentralized localization table: Polish (pl)
464 perp.fund
465 perp.open
466 perp.margin
perp.preview
467 perp.do.open
468 perp.do.add
469 perp.do.close
952 explorer.col.to
953 rig.st.cond
954 asic.st.dead
955 perp.preview
956 perp.sim.close
957 perp.sim.close2
958 trade.sim.buy
959 trade.sim.sell
@@ -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}%
1 # Probably Decentralized localization table: Russian (ru)
464 perp.fund
465 perp.open
466 perp.margin
perp.preview
467 perp.do.open
468 perp.do.add
469 perp.do.close
952 explorer.col.to
953 rig.st.cond
954 asic.st.dead
955 perp.preview
956 perp.sim.close
957 perp.sim.close2
958 trade.sim.buy
959 trade.sim.sell
@@ -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}%
1 # Probably Decentralized 本地化表:简体中文(zh)
463 perp.fund
464 perp.open
465 perp.margin
perp.preview
466 perp.do.open
467 perp.do.add
468 perp.do.close
951 explorer.col.to
952 rig.st.cond
953 asic.st.dead
954 perp.preview
955 perp.sim.close
956 perp.sim.close2
957 trade.sim.buy
958 trade.sim.sell
+77 -12
View File
@@ -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<KeyValuePair<CustomUIElement, Func<string>>> _live = new List<KeyValuePair<CustomUIElement, Func<string>>>();
/// <summary>加一行实时预览:文字由 text() 算,输入框内容变化时刷新;没有内容时是一个空行占位。</summary>
private static void LiveLabel(CustomUIBuilder b, Func<string> 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<CustomUIElement, Func<string>>(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
}
}
/// <summary>现货模拟:按输入数量算买入或卖出能得到多少、平均成交价、相对现价的滑点(不改池子)。买入数量是计价币,卖出数量是标的币。</summary>
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
});
}
/// <summary>开仓模拟:名义价值、预估平均成交价(含滑点)、手续费、强平价。line 1 是价格行,2 是费用和强平价行。</summary>
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));
}
/// <summary>平仓模拟:按现价平掉整个仓位的预估成交价(含滑点)、手续费、回到账户的 CRC 和盈亏。</summary>
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 };