第十阶段 stage 2(一):交易对改为 X/CRC 池,任意币互换经 CRC 两跳路由,币价表改为 CRC 价格

This commit is contained in:
ZHU MINCHAO committed 2026-11-05 07:09:05 +08:00
1 parent 75fb6d6b2a
commit 3758049dcb
14 files changed
+196 -48

No files matched your search

+41 -3
View File
@@ -4,6 +4,14 @@ using System.Linq;
namespace Decentralized.Domain namespace Decentralized.Domain
{ {
/// <summary>一次互换的报价:得到数量、经过几个池、相对中间价的损失(手续费加滑点)。</summary>
public sealed class SwapQuote
{
public string From, To;
public double In, Out, Impact;
public int Hops;
}
/// <summary> /// <summary>
/// 影响行情的游戏事件种类。接入层把游戏的新闻事件与派系行动映射到这里(例如全站停电、电力短缺 → Blackout)。 /// 影响行情的游戏事件种类。接入层把游戏的新闻事件与派系行动映射到这里(例如全站停电、电力短缺 → Blackout)。
/// </summary> /// </summary>
@@ -64,14 +72,43 @@ namespace Decentralized.Domain
var m = new Market(); var m = new Market();
double depth = Tuning.PoolDepth(day), p = Tuning.PscStartPrice; double depth = Tuning.PoolDepth(day), p = Tuning.PscStartPrice;
m.Pools.Add(Pool.Create(Venue.Exchange, Assets.PSC, Assets.CRC, p, depth, Tuning.ExchangeFee)); m.Pools.Add(Pool.Create(Venue.Exchange, Assets.PSC, Assets.CRC, p, depth, Tuning.ExchangeFee));
m.Pools.Add(Pool.Create(Venue.Dex, Assets.PSC, Assets.CRT, p, depth * SideDepthShare, Tuning.DexFee)); m.Pools.Add(Pool.Create(Venue.Dex, Assets.PSC, Assets.CRC, p, depth * SideDepthShare, Tuning.DexFee));
m.Pools.Add(Pool.Create(Venue.Dex, Assets.PSC, Assets.CRA, p, depth * SideDepthShare, Tuning.DexFee));
m.Pools.Add(Pool.Create(Venue.Dex, Assets.CRT, Assets.CRC, 1, depth * SideDepthShare, Tuning.DexFee)); m.Pools.Add(Pool.Create(Venue.Dex, Assets.CRT, Assets.CRC, 1, depth * SideDepthShare, Tuning.DexFee));
m.Pools.Add(Pool.Create(Venue.Dex, Assets.CRA, Assets.CRC, 1, depth * SideDepthShare, Tuning.DexFee));
m.Pools.Add(Pool.Create(Venue.Dex, Assets.SPARK, Assets.CRC, Tuning.SparkStart, depth * Tuning.SparkDepthShare, Tuning.DexFee));
return m; return m;
} }
public Pool Main => Pools[0]; public Pool Main => Pools[0];
/// <summary>DEX 上 X/CRC 的池(所有币都以 CRC 计价)。</summary>
public Pool DexPool(string asset) => Find(Venue.Dex, asset, Assets.CRC);
/// <summary>
/// 任意两币互换的报价:CRC 在一端走一个池,否则 X → CRC → Y 两跳(两次手续费、滑点叠加)。
/// 不改池子;没有路由(同币、没有池)返回 null。
/// </summary>
public SwapQuote Quote(string from, string to, double amount)
{
if (from == to || amount <= 0) return null;
var a = from == Assets.CRC ? null : DexPool(from);
var b = to == Assets.CRC ? null : DexPool(to);
if ((from != Assets.CRC && a == null) || (to != Assets.CRC && b == null)) return null;
double crc = a == null ? amount : a.QuoteOut(amount);
double mid = a == null ? amount : amount * a.Price;
double outAmt = b == null ? crc : b.BaseOut(crc);
double ideal = b == null ? mid : mid / b.Price;
return new SwapQuote { From = from, To = to, In = amount, Out = outAmt, Hops = (a != null ? 1 : 0) + (b != null ? 1 : 0), Impact = ideal > 0 ? Math.Max(0, 1 - outAmt / ideal) : 0 };
}
/// <summary>成交:按 <see cref="Quote"/> 的路由改动池子,返回得到的数量(没有路由返回 0)。</summary>
public double Swap(string from, string to, double amount)
{
if (Quote(from, to, amount) == null) return 0;
double crc = from == Assets.CRC ? amount : DexPool(from).SellBase(amount);
return to == Assets.CRC ? crc : DexPool(to).BuyBase(crc);
}
public Pool Find(Venue venue, string baseAsset, string quote) => public Pool Find(Venue venue, string baseAsset, string quote) =>
Pools.FirstOrDefault(x => x.Venue == venue && x.Base == baseAsset && x.Quote == quote); Pools.FirstOrDefault(x => x.Venue == venue && x.Base == baseAsset && x.Quote == quote);
@@ -152,7 +189,8 @@ namespace Decentralized.Domain
double baseCredits = pool.Base == Assets.PSC ? p : stablePrice(pool.Base); double baseCredits = pool.Base == Assets.PSC ? p : stablePrice(pool.Base);
double quoteCredits = pool.Quote == Assets.PSC ? p : stablePrice(pool.Quote); double quoteCredits = pool.Quote == Assets.PSC ? p : stablePrice(pool.Quote);
if (quoteCredits <= 0) continue; if (quoteCredits <= 0) continue;
pool.Revert(baseCredits / quoteCredits, ArbRate, depth * SideDepthShare / quoteCredits); double share = pool.Base == Assets.SPARK ? Tuning.SparkDepthShare : SideDepthShare;
pool.Revert(baseCredits / quoteCredits, ArbRate, depth * share / quoteCredits);
} }
} }
} }
+1 -1
View File
@@ -97,7 +97,7 @@ namespace Decentralized.Domain
/// <summary>外观折价(占价值的比例):叶片积灰 / 缺角,金手指发暗 / 氧化,挡板划痕,封条缺失。</summary> /// <summary>外观折价(占价值的比例):叶片积灰 / 缺角,金手指发暗 / 氧化,挡板划痕,封条缺失。</summary>
public const double CardFanDust = 0.04, CardFanChipped = 0.12, CardFingersDark = 0.04, CardFingersOxidized = 0.12, CardBracketScratch = 0.02, CardSealMissing = 0.06; public const double CardFanDust = 0.04, CardFanChipped = 0.12, CardFingersDark = 0.04, CardFingersOxidized = 0.12, CardBracketScratch = 0.02, CardSealMissing = 0.06;
/// <summary>非主交易对每晚被拉回参考价的比例,及其深度占主交易对的比例。</summary> /// <summary>非主交易对每晚被拉回参考价的比例,及其深度占主交易对的比例。</summary>
public const double SideArbRate = 0.75, SideDepthShare = 0.3; public const double SideArbRate = 0.75, SideDepthShare = 0.3, SparkDepthShare = 0.15;
/// <summary>Web3 黑市:开放所需黑市声望,中 / 高档货所需声望,现货每周刷新的天数与基础件数(每高 BmStockPerRep 声望多一件),现货溢价。</summary> /// <summary>Web3 黑市:开放所需黑市声望,中 / 高档货所需声望,现货每周刷新的天数与基础件数(每高 BmStockPerRep 声望多一件),现货溢价。</summary>
public const int BmOpenRep = 10, BmMidRep = 40, BmHighRep = 80, BmRefreshDays = 7, BmStockBase = 4; public const int BmOpenRep = 10, BmMidRep = 40, BmHighRep = 80, BmRefreshDays = 7, BmStockBase = 4;
public const double BmStockPerRep = 40, BmPremium = 0.30; public const double BmStockPerRep = 40, BmPremium = 0.30;
+1
View File
@@ -324,6 +324,7 @@ namespace Decentralized.Domain
["SentimentVol"] = Tuning.SentimentVol, ["SentimentVol"] = Tuning.SentimentVol,
["SideArbRate"] = Tuning.SideArbRate, ["SideArbRate"] = Tuning.SideArbRate,
["SideDepthShare"] = Tuning.SideDepthShare, ["SideDepthShare"] = Tuning.SideDepthShare,
["SparkDepthShare"] = Tuning.SparkDepthShare,
["SparkFee"] = Tuning.SparkFee, ["SparkFee"] = Tuning.SparkFee,
["SparkMaxMult"] = Tuning.SparkMaxMult, ["SparkMaxMult"] = Tuning.SparkMaxMult,
["SparkMinMult"] = Tuning.SparkMinMult, ["SparkMinMult"] = Tuning.SparkMinMult,
@@ -149,8 +149,6 @@ bank.ops,Ein-/Auszahlung
poin.account.h,Poinbase-Konto poin.account.h,Poinbase-Konto
trade.h,Handel trade.h,Handel
web3.wallet.h,On-Chain-Wallet web3.wallet.h,On-Chain-Wallet
col.peg,Preis
col.status,Status
stable.ok,Gekoppelt stable.ok,Gekoppelt
stable.depeg,Entkoppelt stable.depeg,Entkoppelt
stable.warn,Warnung: Staking-Subventionspool knapp stable.warn,Warnung: Staking-Subventionspool knapp
@@ -902,3 +900,9 @@ rig.st.power,Verbrauch
rig.st.hash,Hashrate rig.st.hash,Hashrate
rig.st.kj,{0} kJ/Tag rig.st.kj,{0} kJ/Tag
lbl.point.model,Modell beanstanden lbl.point.model,Modell beanstanden
col.crcprice,CRC-Preis
swap.pay,Zahlen
swap.get,Erhalten
swap.preview,"Ca. {0} {1} {2} Pool(s), Verlust {3} %"
swap.go,Tauschen
swap.same,Zahlungs- und Empfangswährung müssen verschieden sein.
1 # Probably Decentralized localization table: German (de)
149 poin.account.h
150 trade.h
151 web3.wallet.h
col.peg
col.status
152 stable.ok
153 stable.depeg
154 stable.warn
900 rig.st.hash
901 rig.st.kj
902 lbl.point.model
903 col.crcprice
904 swap.pay
905 swap.get
906 swap.preview
907 swap.go
908 swap.same
@@ -149,8 +149,6 @@ bank.ops,Deposit / withdraw
poin.account.h,Poinbase account poin.account.h,Poinbase account
trade.h,Trade trade.h,Trade
web3.wallet.h,On-chain wallet web3.wallet.h,On-chain wallet
col.peg,Price
col.status,Status
stable.ok,Pegged stable.ok,Pegged
stable.depeg,Depegged stable.depeg,Depegged
stable.warn,Warning: staking subsidy pool low stable.warn,Warning: staking subsidy pool low
@@ -902,3 +900,9 @@ rig.st.power,Power
rig.st.hash,Hashrate rig.st.hash,Hashrate
rig.st.kj,{0} kJ/day rig.st.kj,{0} kJ/day
lbl.point.model,Point out model lbl.point.model,Point out model
col.crcprice,CRC price
swap.pay,Pay
swap.get,Receive
swap.preview,"Receive about {0} {1} {2} pool(s), loss {3}%"
swap.go,Swap
swap.same,Pay and receive must be different coins.
1 # Probably Decentralized localization table: English (en)
149 poin.account.h
150 trade.h
151 web3.wallet.h
col.peg
col.status
152 stable.ok
153 stable.depeg
154 stable.warn
900 rig.st.hash
901 rig.st.kj
902 lbl.point.model
903 col.crcprice
904 swap.pay
905 swap.get
906 swap.preview
907 swap.go
908 swap.same
@@ -149,8 +149,6 @@ bank.ops,Depósito / retiro
poin.account.h,Cuenta de Poinbase poin.account.h,Cuenta de Poinbase
trade.h,Comercio trade.h,Comercio
web3.wallet.h,Billetera en cadena web3.wallet.h,Billetera en cadena
col.peg,Precio
col.status,Estado
stable.ok,Anclado stable.ok,Anclado
stable.depeg,Desanclado stable.depeg,Desanclado
stable.warn,Advertencia: fondo de subsidio de staking bajo stable.warn,Advertencia: fondo de subsidio de staking bajo
@@ -902,3 +900,9 @@ rig.st.power,Consumo
rig.st.hash,Tasa de hash rig.st.hash,Tasa de hash
rig.st.kj,{0} kJ/día rig.st.kj,{0} kJ/día
lbl.point.model,Señalar modelo lbl.point.model,Señalar modelo
col.crcprice,Precio en CRC
swap.pay,Pagar
swap.get,Recibir
swap.preview,"Aprox. {0} {1} {2} pool(s), pérdida {3} %"
swap.go,Canjear
swap.same,Las monedas a pagar y recibir deben ser distintas.
1 # Probably Decentralized localization table: Spanish (es)
149 poin.account.h
150 trade.h
151 web3.wallet.h
col.peg
col.status
152 stable.ok
153 stable.depeg
154 stable.warn
900 rig.st.hash
901 rig.st.kj
902 lbl.point.model
903 col.crcprice
904 swap.pay
905 swap.get
906 swap.preview
907 swap.go
908 swap.same
@@ -149,8 +149,6 @@ bank.ops,Dépôt / Retrait
poin.account.h,Compte Poinbase poin.account.h,Compte Poinbase
trade.h,Trading trade.h,Trading
web3.wallet.h,Portefeuille on-chain web3.wallet.h,Portefeuille on-chain
col.peg,Prix
col.status,Statut
stable.ok,Arrimé stable.ok,Arrimé
stable.depeg,Désarrimé stable.depeg,Désarrimé
stable.warn,Attention : pool de subvention de staking au plus bas stable.warn,Attention : pool de subvention de staking au plus bas
@@ -902,3 +900,9 @@ rig.st.power,Conso.
rig.st.hash,Hachage rig.st.hash,Hachage
rig.st.kj,{0} kJ/j rig.st.kj,{0} kJ/j
lbl.point.model,Signaler le modèle lbl.point.model,Signaler le modèle
col.crcprice,Prix en CRC
swap.pay,Payer
swap.get,Recevoir
swap.preview,"Environ {0} {1} {2} pool(s), perte {3} %"
swap.go,Échanger
swap.same,Les monnaies payée et reçue doivent différer.
1 # Probably Decentralized localization table: French (fr)
149 poin.account.h
150 trade.h
151 web3.wallet.h
col.peg
col.status
152 stable.ok
153 stable.depeg
154 stable.warn
900 rig.st.hash
901 rig.st.kj
902 lbl.point.model
903 col.crcprice
904 swap.pay
905 swap.get
906 swap.preview
907 swap.go
908 swap.same
@@ -149,8 +149,6 @@ bank.ops,入出金
poin.account.h,Poinbase口座 poin.account.h,Poinbase口座
trade.h,取引 trade.h,取引
web3.wallet.h,オンチェーンウォレット web3.wallet.h,オンチェーンウォレット
col.peg,価格
col.status,状態
stable.ok,ペグ正常 stable.ok,ペグ正常
stable.depeg,デペグ stable.depeg,デペグ
stable.warn,警告:ステーキング補助金プール残高低下 stable.warn,警告:ステーキング補助金プール残高低下
@@ -902,3 +900,9 @@ rig.st.power,消費電力
rig.st.hash,ハッシュレート rig.st.hash,ハッシュレート
rig.st.kj,{0} kJ/日 rig.st.kj,{0} kJ/日
lbl.point.model,型番を指摘する lbl.point.model,型番を指摘する
col.crcprice,CRC 価格
swap.pay,支払い
swap.get,受取
swap.preview,約 {0} {1} プール {2} 個、損失 {3}%
swap.go,スワップ
swap.same,支払いと受取は別の通貨にしてください。
Can't render this file because it contains an unexpected character in line 516 and column 24.
@@ -149,8 +149,6 @@ bank.ops,Wpłata / wypłata
poin.account.h,Konto Poinbase poin.account.h,Konto Poinbase
trade.h,Handel trade.h,Handel
web3.wallet.h,Portfel on-chain web3.wallet.h,Portfel on-chain
col.peg,Cena
col.status,Status
stable.ok,W parytecie stable.ok,W parytecie
stable.depeg,Utrata parytetu stable.depeg,Utrata parytetu
stable.warn,Ostrzeżenie: niska pula dotacji stakingu stable.warn,Ostrzeżenie: niska pula dotacji stakingu
@@ -902,3 +900,9 @@ rig.st.power,Pobór
rig.st.hash,Moc obl. rig.st.hash,Moc obl.
rig.st.kj,{0} kJ/dzień rig.st.kj,{0} kJ/dzień
lbl.point.model,Wskaż model lbl.point.model,Wskaż model
col.crcprice,Cena w CRC
swap.pay,Płacę
swap.get,Otrzymuję
swap.preview,"Około {0} {1} pule: {2}, strata {3}%"
swap.go,Wymień
swap.same,Waluty płatności i odbioru muszą się różnić.
1 # Probably Decentralized localization table: Polish (pl)
149 poin.account.h
150 trade.h
151 web3.wallet.h
col.peg
col.status
152 stable.ok
153 stable.depeg
154 stable.warn
900 rig.st.hash
901 rig.st.kj
902 lbl.point.model
903 col.crcprice
904 swap.pay
905 swap.get
906 swap.preview
907 swap.go
908 swap.same
@@ -149,8 +149,6 @@ bank.ops,Депозит / вывод
poin.account.h,Счёт Poinbase poin.account.h,Счёт Poinbase
trade.h,Торговля trade.h,Торговля
web3.wallet.h,Ончейн-кошелёк web3.wallet.h,Ончейн-кошелёк
col.peg,Цена
col.status,Статус
stable.ok,Привязан stable.ok,Привязан
stable.depeg,Отвязан stable.depeg,Отвязан
stable.warn,Внимание: пул субсидий стейкинга на исходе stable.warn,Внимание: пул субсидий стейкинга на исходе
@@ -902,3 +900,9 @@ rig.st.power,Расход
rig.st.hash,Хешрейт rig.st.hash,Хешрейт
rig.st.kj,{0} кДж/день rig.st.kj,{0} кДж/день
lbl.point.model,Указать модель lbl.point.model,Указать модель
col.crcprice,Цена в CRC
swap.pay,Отдаю
swap.get,Получаю
swap.preview,"Около {0} {1} пулов: {2}, потери {3}%"
swap.go,Обменять
swap.same,Монеты оплаты и получения должны различаться.
1 # Probably Decentralized localization table: Russian (ru)
149 poin.account.h
150 trade.h
151 web3.wallet.h
col.peg
col.status
152 stable.ok
153 stable.depeg
154 stable.warn
900 rig.st.hash
901 rig.st.kj
902 lbl.point.model
903 col.crcprice
904 swap.pay
905 swap.get
906 swap.preview
907 swap.go
908 swap.same
@@ -148,8 +148,6 @@ bank.ops,"存取"
poin.account.h,"Poinbase 账户" poin.account.h,"Poinbase 账户"
trade.h,"交易" trade.h,"交易"
web3.wallet.h,"链上钱包" web3.wallet.h,"链上钱包"
col.peg,"价格"
col.status,"状态"
stable.ok,"锚定正常" stable.ok,"锚定正常"
stable.depeg,"脱锚" stable.depeg,"脱锚"
stable.warn,"预兆:质押补贴池告急" stable.warn,"预兆:质押补贴池告急"
@@ -901,3 +899,9 @@ rig.st.power,耗电
rig.st.hash,算力 rig.st.hash,算力
rig.st.kj,{0} kJ/天 rig.st.kj,{0} kJ/天
lbl.point.model,指出型号 lbl.point.model,指出型号
col.crcprice,CRC 价格
swap.pay,支付
swap.get,获得
swap.preview,约得 {0} {1} 经 {2} 个池,损耗 {3}%
swap.go,兑换
swap.same,支付和获得的币种不能相同。
1 # Probably Decentralized 本地化表:简体中文(zh)
148 poin.account.h
149 trade.h
150 web3.wallet.h
col.peg
col.status
151 stable.ok
152 stable.depeg
153 stable.warn
899 rig.st.hash
900 rig.st.kj
901 lbl.point.model
902 col.crcprice
903 swap.pay
904 swap.get
905 swap.preview
906 swap.go
907 swap.same
+2 -1
View File
@@ -136,7 +136,8 @@ namespace Decentralized
} }
catch (Exception ex) { Log.Error($"读取存档数据失败,重新开始:{ex.Message}"); } catch (Exception ex) { Log.Error($"读取存档数据失败,重新开始:{ex.Message}"); }
if (Data == null) Data = new SaveData(); if (Data == null) Data = new SaveData();
if (Data.Market == null) Data.Market = Market.CreateDefault(StoreStation.GetDayCounter()); // 池子表在第十阶段改过(全部以 CRC 计价);旧测试档里的表缺池子,重建成默认值(价格从起始价重来)
if (Data.Market == null || Data.Market.DexPool(Assets.SPARK) == null) Data.Market = Market.CreateDefault(StoreStation.GetDayCounter());
// 旧存档里的商家地址没有校验位:换成合法地址 // 旧存档里的商家地址没有校验位:换成合法地址
foreach (var s in Data.Black.Sellers) if (!ChainAddress.Valid(s.Name)) s.Name = ChainAddress.Make(Data.Rng.NextUInt()); foreach (var s in Data.Black.Sellers) if (!ChainAddress.Valid(s.Name)) s.Name = ChainAddress.Make(Data.Rng.NextUInt());
if (Data.Story == null) Data.Story = new StoryMods(); if (Data.Story == null) Data.Story = new StoryMods();
+76 -21
View File
@@ -231,9 +231,6 @@ namespace Decentralized
b.AddLabel(L.T("aml.taint", (int)Math.Round(taintRatio * 100)), taintRatio > 0.05 ? "aml_note" : "aml_dim"); b.AddLabel(L.T("aml.taint", (int)Math.Round(taintRatio * 100)), taintRatio > 0.05 ? "aml_note" : "aml_dim");
} }
private static readonly float[] StableWBase = new[] { 80f, 100f, 220f };
private static float[] StableW => Sc(StableWBase);
private static string StableStatus(Stablecoins st, string asset, int day) private static string StableStatus(Stablecoins st, string asset, int day)
{ {
double p = st.Price(asset); double p = st.Price(asset);
@@ -242,15 +239,78 @@ namespace Decentralized
return p < 0.99 ? L.T("stable.depeg") : L.T("stable.ok"); return p < 0.99 ? L.T("stable.depeg") : L.T("stable.ok");
} }
/// <summary>稳定币行情:价格与状态(脱锚、预兆)。</summary> private static readonly float[] PriceWBase = new[] { 100f, 160f };
private static void StableTable(CustomUIBuilder b) private static float[] PriceW => Sc(PriceWBase);
private static readonly string[] SwapCoins = { Assets.PSC, Assets.CRC, Assets.CRT, Assets.CRA, Assets.SPARK };
private static string _swapFrom = Assets.CRC, _swapTo = Assets.PSC;
/// <summary>币价表:各币在 DEX 池里以 CRC 计的价格。</summary>
private static void PriceTable(CustomUIBuilder b)
{ {
var st = Session.Data.Stable; var m = Session.Data.Market;
int day = StoreStation.GetDayCounter();
b.AddSeparator(); b.AddSeparator();
TableHeader(b, StableW, L.T("col.asset"), L.T("col.peg"), L.T("col.status")); TableHeader(b, PriceW, L.T("col.asset"), L.T("col.crcprice"));
foreach (var a in new[] { Assets.CRC, Assets.CRT, Assets.CRA }) foreach (var a in SwapCoins.Where(x => x != Assets.CRC))
TableRow(b, StableW, a, Fmt.Num(st.Price(a)), StableStatus(st, a, day)); TableRow(b, PriceW, a, Fmt.Num(m.DexPool(a).Price));
}
/// <summary>互换:任意币对任意币,实际经 CRC 路由;预览显示得到数量、经过几个池、损耗。</summary>
private static void SwapBlock(CustomUIBuilder b)
{
var m = Session.Data.Market;
Action<string, string, bool> picker = (label, cur, from) =>
{
b.AddLabel(label);
b.BeginRow(4f);
foreach (var c in SwapCoins)
{
var coin = c;
PledgerSkin.Pick(b, coin, coin == cur, () =>
{
if (from) { _swapFrom = coin; if (_swapTo == coin) _swapTo = SwapCoins.First(x => x != coin); }
else { _swapTo = coin; if (_swapFrom == coin) _swapFrom = SwapCoins.First(x => x != coin); }
Open();
}, _picked);
}
b.End();
};
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");
b.AddButton(L.T("swap.go"), () => Swap());
}
/// <summary>互换:DEX 是合约调用,要在硬件钱包上签名。</summary>
private static void Swap()
{
if (!ReadAmount(out double v)) return;
string from = _swapFrom, to = _swapTo;
var q = Session.Data.Market.Quote(from, to, v);
if (q == null) { _message = L.T("swap.same"); Open(); return; }
var w = Session.Data.Hot;
if (w.Avail(from) + 1e-9 < v) { _message = L.T(frozenPay(w, from) ? "msg.frozen" : "msg.balance"); Open(); return; }
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill { Contract = true, Asset = from, Amount = v, Summary = L.T("hw.swap", Fmt.Num(v), from, Fmt.Num(q.Out), to), Execute = pw => DoSwap(from, to, v, pw) });
}
private static void DoSwap(string from, string to, double v, Wallet w)
{
if (w.Avail(from) + 1e-9 < v) { _message = L.T(frozenPay(w, from) ? "msg.frozen" : "msg.balance"); Open(); return; }
double got = Session.Data.Market.Swap(from, to, v);
if (got <= 0) { _message = L.T("swap.same"); Open(); return; }
double spent = v * Session.Price(from);
CandleChart.Trade(from, spent); CandleChart.Trade(to, spent);
w.Add(from, -v);
w.Add(to, got);
w.RecordBuy(to, got, spent);
_message = L.T("msg.trade", Fmt.Num(v), from, Fmt.Num(got), to);
Session.Save();
Open();
} }
private static void TradeRows(CustomUIBuilder b, Venue venue) private static void TradeRows(CustomUIBuilder b, Venue venue)
@@ -1193,14 +1253,13 @@ namespace Decentralized
l.AddSeparator(); l.AddSeparator();
l.AddLabel(L.T("wallet.total", Fmt.Credits(w.Value(Session.Price)))); l.AddLabel(L.T("wallet.total", Fmt.Credits(w.Value(Session.Price))));
if (w.Taint > 0.5) l.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)), "taint_note"); if (w.Taint > 0.5) l.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)), "taint_note");
StableTable(l); PriceTable(l);
l.AddSeparator(); l.AddSeparator();
CandleChart.AddPicker(l, "web3", Assets.PSC, Open, _picked); CandleChart.AddPicker(l, "web3", Assets.PSC, Open, _picked);
}, r => }, r =>
{ {
r.AddHeader(L.T("trade.h")); r.AddHeader(L.T("trade.h"));
AmountRow(r); SwapBlock(r);
TradeRows(r, Venue.Dex);
if (Unlock.BlackUnlocked && BlackMarket.DeskOpen(StoreReputation.GetBMReputation())) PoneroBlock(r); if (Unlock.BlackUnlocked && BlackMarket.DeskOpen(StoreReputation.GetBMReputation())) PoneroBlock(r);
}); });
} }
@@ -2311,18 +2370,14 @@ namespace Decentralized
Open(); Open();
} }
/// <summary>交易:Poinbase 是托管账户,直接成交;DEX 是合约调用,要在硬件钱包上签名。</summary> /// <summary>交易:Poinbase 是托管账户,直接成交。</summary>
private static void Trade(Pool p, bool buy) private static void Trade(Pool p, bool buy)
{ {
if (!ReadAmount(out double v)) return; if (!ReadAmount(out double v)) return;
var w = p.Venue == Venue.Dex ? Session.Data.Hot : Session.Data.Poinbase; var w = Session.Data.Poinbase;
string pay = buy ? p.Quote : p.Base, get = buy ? p.Base : p.Quote; string pay = buy ? p.Quote : p.Base;
if (w.Avail(pay) + 1e-9 < v) { _message = L.T(frozenPay(w, pay) ? "msg.frozen" : "msg.balance"); Open(); return; } if (w.Avail(pay) + 1e-9 < v) { _message = L.T(frozenPay(w, pay) ? "msg.frozen" : "msg.balance"); Open(); return; }
if (p.Venue != Venue.Dex) { DoTrade(p, buy, v, w); return; } DoTrade(p, buy, v, w);
double quote = buy ? v / p.Price : v * p.Price;
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill { Contract = true, Asset = pay, Amount = v, Summary = L.T("hw.swap", Fmt.Num(v), pay, Fmt.Num(quote), get), Execute = pw => DoTrade(p, buy, v, pw) });
} }
private static void DoTrade(Pool p, bool buy, double v, Wallet w) private static void DoTrade(Pool p, bool buy, double v, Wallet w)
+27 -6
View File
@@ -318,17 +318,38 @@ internal static class Program
Check(n.Anchor(new MarketEvent[0]) > Tuning.PscStartPrice * 1.05, "锚随之上移"); Check(n.Anchor(new MarketEvent[0]) > Tuning.PscStartPrice * 1.05, "锚随之上移");
}); });
Test("市场:其他交易对每晚被套利拉向参考价,三角价差收敛", () => Test("市场:其他交易对每晚被套利拉向参考价,价差收敛", () =>
{ {
var m = Market.CreateDefault(1); var m = Market.CreateDefault(1);
var crt = m.Find(Venue.Dex, Assets.PSC, Assets.CRT); var dexPsc = m.DexPool(Assets.PSC);
crt.SetPrice(130, crt.QuoteReserve); dexPsc.SetPrice(130, dexPsc.QuoteReserve);
for (int d = 1; d <= 5; d++) m.Night(new MarketEvent[0], new Rng((ulong)d), 1, a => 1); for (int d = 1; d <= 5; d++) m.Night(new MarketEvent[0], new Rng((ulong)d), 1, a => 1);
double gap = Math.Abs(crt.Price / m.Price - 1); double gap = Math.Abs(dexPsc.Price / m.Price - 1);
Check(gap < 0.03, $"五晚后 PSC/CRT 与参考价相差 {gap:P1}"); Check(gap < 0.03, $"五晚后 DEX 的 PSC/CRC 与参考价相差 {gap:P1}");
var crtCrc = m.Find(Venue.Dex, Assets.CRT, Assets.CRC); var crtCrc = m.DexPool(Assets.CRT);
m.Night(new MarketEvent[0], new Rng(9), 1, a => a == Assets.CRT ? 0.9 : 1); m.Night(new MarketEvent[0], new Rng(9), 1, a => a == Assets.CRT ? 0.9 : 1);
Check(crtCrc.Price < 0.98, $"CRT 脱锚时 CRT/CRC 向 0.9 靠拢:{crtCrc.Price:0.000}"); Check(crtCrc.Price < 0.98, $"CRT 脱锚时 CRT/CRC 向 0.9 靠拢:{crtCrc.Price:0.000}");
var m2 = Market.CreateDefault(1);
var spark = m2.DexPool(Assets.SPARK);
m2.Night(new MarketEvent[0], new Rng(11), 1, a => a == Assets.SPARK ? 20 : 1);
Check(spark.Price > Tuning.SparkStart * 1.3, $"SPARK/CRC 向 SPARK 价格靠拢:{spark.Price:0.00}");
});
Test("市场:任意两币互换经 CRC 路由,两跳付两次手续费", () =>
{
var m = Market.CreateDefault(1);
var oneHop = m.Quote(Assets.CRC, Assets.PSC, 100);
var twoHop = m.Quote(Assets.CRA, Assets.PSC, 100);
Check(oneHop != null && oneHop.Hops == 1 && twoHop != null && twoHop.Hops == 2, "CRC 在一端走一个池,否则两个池");
Check(m.Quote(Assets.PSC, Assets.PSC, 1) == null && m.Quote(Assets.CRC, Assets.CRC, 1) == null && m.Quote(Assets.CRC, Assets.Ponero, 1) == null, "同币、没有池没有路由");
Check(twoHop.Impact > oneHop.Impact && twoHop.Impact > Tuning.DexFee * 1.9, $"两跳损失更大:{oneHop.Impact:P2} 对 {twoHop.Impact:P2}");
double expect = m.DexPool(Assets.PSC).BaseOut(m.DexPool(Assets.CRA).QuoteOut(100));
Check(Near(twoHop.Out, expect, 1e-9), "两跳的数量 = 先卖成 CRC 再买");
double got = m.Swap(Assets.CRA, Assets.PSC, 100);
Check(Near(got, twoHop.Out, 1e-9), "成交数量与报价一致");
Check(m.DexPool(Assets.CRA).Price < 1 && m.DexPool(Assets.PSC).Price > Tuning.PscStartPrice, "成交推动两个池的价格");
var big = Market.CreateDefault(1).Quote(Assets.CRC, Assets.SPARK, 20000);
Check(big.Impact > 0.2, $"SPARK 池比较薄,大单滑点大:{big.Impact:P0}");
}); });
Test("稳定币:CRC 脱锚与减记,CRT 脱锚与冻结概率", () => Test("稳定币:CRC 脱锚与减记,CRT 脱锚与冻结概率", () =>