第十阶段 stage 2(一):交易对改为 X/CRC 池,任意币互换经 CRC 两跳路由,币价表改为 CRC 价格
This commit is contained in:
1 parent
75fb6d6b2a
commit
3758049dcb
14 files changed
+196
-48
No files matched your search
@@ -4,6 +4,14 @@ using System.Linq;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>一次互换的报价:得到数量、经过几个池、相对中间价的损失(手续费加滑点)。</summary>
|
||||
public sealed class SwapQuote
|
||||
{
|
||||
public string From, To;
|
||||
public double In, Out, Impact;
|
||||
public int Hops;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 影响行情的游戏事件种类。接入层把游戏的新闻事件与派系行动映射到这里(例如全站停电、电力短缺 → Blackout)。
|
||||
/// </summary>
|
||||
@@ -64,14 +72,43 @@ namespace Decentralized.Domain
|
||||
var m = new Market();
|
||||
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.Dex, Assets.PSC, Assets.CRT, 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.PSC, Assets.CRC, 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.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;
|
||||
}
|
||||
|
||||
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) =>
|
||||
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 quoteCredits = pool.Quote == Assets.PSC ? p : stablePrice(pool.Quote);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Decentralized.Domain
|
||||
/// <summary>外观折价(占价值的比例):叶片积灰 / 缺角,金手指发暗 / 氧化,挡板划痕,封条缺失。</summary>
|
||||
public const double CardFanDust = 0.04, CardFanChipped = 0.12, CardFingersDark = 0.04, CardFingersOxidized = 0.12, CardBracketScratch = 0.02, CardSealMissing = 0.06;
|
||||
/// <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>
|
||||
public const int BmOpenRep = 10, BmMidRep = 40, BmHighRep = 80, BmRefreshDays = 7, BmStockBase = 4;
|
||||
public const double BmStockPerRep = 40, BmPremium = 0.30;
|
||||
|
||||
@@ -324,6 +324,7 @@ namespace Decentralized.Domain
|
||||
["SentimentVol"] = Tuning.SentimentVol,
|
||||
["SideArbRate"] = Tuning.SideArbRate,
|
||||
["SideDepthShare"] = Tuning.SideDepthShare,
|
||||
["SparkDepthShare"] = Tuning.SparkDepthShare,
|
||||
["SparkFee"] = Tuning.SparkFee,
|
||||
["SparkMaxMult"] = Tuning.SparkMaxMult,
|
||||
["SparkMinMult"] = Tuning.SparkMinMult,
|
||||
|
||||
@@ -149,8 +149,6 @@ bank.ops,Ein-/Auszahlung
|
||||
poin.account.h,Poinbase-Konto
|
||||
trade.h,Handel
|
||||
web3.wallet.h,On-Chain-Wallet
|
||||
col.peg,Preis
|
||||
col.status,Status
|
||||
stable.ok,Gekoppelt
|
||||
stable.depeg,Entkoppelt
|
||||
stable.warn,Warnung: Staking-Subventionspool knapp
|
||||
@@ -902,3 +900,9 @@ rig.st.power,Verbrauch
|
||||
rig.st.hash,Hashrate
|
||||
rig.st.kj,{0} kJ/Tag
|
||||
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.
|
||||
|
@@ -149,8 +149,6 @@ bank.ops,Deposit / withdraw
|
||||
poin.account.h,Poinbase account
|
||||
trade.h,Trade
|
||||
web3.wallet.h,On-chain wallet
|
||||
col.peg,Price
|
||||
col.status,Status
|
||||
stable.ok,Pegged
|
||||
stable.depeg,Depegged
|
||||
stable.warn,Warning: staking subsidy pool low
|
||||
@@ -902,3 +900,9 @@ rig.st.power,Power
|
||||
rig.st.hash,Hashrate
|
||||
rig.st.kj,{0} kJ/day
|
||||
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.
|
||||
|
@@ -149,8 +149,6 @@ bank.ops,Depósito / retiro
|
||||
poin.account.h,Cuenta de Poinbase
|
||||
trade.h,Comercio
|
||||
web3.wallet.h,Billetera en cadena
|
||||
col.peg,Precio
|
||||
col.status,Estado
|
||||
stable.ok,Anclado
|
||||
stable.depeg,Desanclado
|
||||
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.kj,{0} kJ/día
|
||||
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.
|
||||
|
@@ -149,8 +149,6 @@ bank.ops,Dépôt / Retrait
|
||||
poin.account.h,Compte Poinbase
|
||||
trade.h,Trading
|
||||
web3.wallet.h,Portefeuille on-chain
|
||||
col.peg,Prix
|
||||
col.status,Statut
|
||||
stable.ok,Arrimé
|
||||
stable.depeg,Désarrimé
|
||||
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.kj,{0} kJ/j
|
||||
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.
|
||||
|
@@ -149,8 +149,6 @@ bank.ops,入出金
|
||||
poin.account.h,Poinbase口座
|
||||
trade.h,取引
|
||||
web3.wallet.h,オンチェーンウォレット
|
||||
col.peg,価格
|
||||
col.status,状態
|
||||
stable.ok,ペグ正常
|
||||
stable.depeg,デペグ
|
||||
stable.warn,警告:ステーキング補助金プール残高低下
|
||||
@@ -902,3 +900,9 @@ rig.st.power,消費電力
|
||||
rig.st.hash,ハッシュレート
|
||||
rig.st.kj,{0} kJ/日
|
||||
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
|
||||
trade.h,Handel
|
||||
web3.wallet.h,Portfel on-chain
|
||||
col.peg,Cena
|
||||
col.status,Status
|
||||
stable.ok,W parytecie
|
||||
stable.depeg,Utrata parytetu
|
||||
stable.warn,Ostrzeżenie: niska pula dotacji stakingu
|
||||
@@ -902,3 +900,9 @@ rig.st.power,Pobór
|
||||
rig.st.hash,Moc obl.
|
||||
rig.st.kj,{0} kJ/dzień
|
||||
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ć.
|
||||
|
@@ -149,8 +149,6 @@ bank.ops,Депозит / вывод
|
||||
poin.account.h,Счёт Poinbase
|
||||
trade.h,Торговля
|
||||
web3.wallet.h,Ончейн-кошелёк
|
||||
col.peg,Цена
|
||||
col.status,Статус
|
||||
stable.ok,Привязан
|
||||
stable.depeg,Отвязан
|
||||
stable.warn,Внимание: пул субсидий стейкинга на исходе
|
||||
@@ -902,3 +900,9 @@ rig.st.power,Расход
|
||||
rig.st.hash,Хешрейт
|
||||
rig.st.kj,{0} кДж/день
|
||||
lbl.point.model,Указать модель
|
||||
col.crcprice,Цена в CRC
|
||||
swap.pay,Отдаю
|
||||
swap.get,Получаю
|
||||
swap.preview,"Около {0} {1} пулов: {2}, потери {3}%"
|
||||
swap.go,Обменять
|
||||
swap.same,Монеты оплаты и получения должны различаться.
|
||||
|
@@ -148,8 +148,6 @@ bank.ops,"存取"
|
||||
poin.account.h,"Poinbase 账户"
|
||||
trade.h,"交易"
|
||||
web3.wallet.h,"链上钱包"
|
||||
col.peg,"价格"
|
||||
col.status,"状态"
|
||||
stable.ok,"锚定正常"
|
||||
stable.depeg,"脱锚"
|
||||
stable.warn,"预兆:质押补贴池告急"
|
||||
@@ -901,3 +899,9 @@ rig.st.power,耗电
|
||||
rig.st.hash,算力
|
||||
rig.st.kj,{0} kJ/天
|
||||
lbl.point.model,指出型号
|
||||
col.crcprice,CRC 价格
|
||||
swap.pay,支付
|
||||
swap.get,获得
|
||||
swap.preview,约得 {0} {1} 经 {2} 个池,损耗 {3}%
|
||||
swap.go,兑换
|
||||
swap.same,支付和获得的币种不能相同。
|
||||
|
@@ -136,7 +136,8 @@ namespace Decentralized
|
||||
}
|
||||
catch (Exception ex) { Log.Error($"读取存档数据失败,重新开始:{ex.Message}"); }
|
||||
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());
|
||||
if (Data.Story == null) Data.Story = new StoryMods();
|
||||
|
||||
@@ -231,9 +231,6 @@ namespace Decentralized
|
||||
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)
|
||||
{
|
||||
double p = st.Price(asset);
|
||||
@@ -242,15 +239,78 @@ namespace Decentralized
|
||||
return p < 0.99 ? L.T("stable.depeg") : L.T("stable.ok");
|
||||
}
|
||||
|
||||
/// <summary>稳定币行情:价格与状态(脱锚、预兆)。</summary>
|
||||
private static void StableTable(CustomUIBuilder b)
|
||||
private static readonly float[] PriceWBase = new[] { 100f, 160f };
|
||||
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;
|
||||
int day = StoreStation.GetDayCounter();
|
||||
var m = Session.Data.Market;
|
||||
b.AddSeparator();
|
||||
TableHeader(b, StableW, L.T("col.asset"), L.T("col.peg"), L.T("col.status"));
|
||||
foreach (var a in new[] { Assets.CRC, Assets.CRT, Assets.CRA })
|
||||
TableRow(b, StableW, a, Fmt.Num(st.Price(a)), StableStatus(st, a, day));
|
||||
TableHeader(b, PriceW, L.T("col.asset"), L.T("col.crcprice"));
|
||||
foreach (var a in SwapCoins.Where(x => x != Assets.CRC))
|
||||
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)
|
||||
@@ -1193,14 +1253,13 @@ namespace Decentralized
|
||||
l.AddSeparator();
|
||||
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");
|
||||
StableTable(l);
|
||||
PriceTable(l);
|
||||
l.AddSeparator();
|
||||
CandleChart.AddPicker(l, "web3", Assets.PSC, Open, _picked);
|
||||
}, r =>
|
||||
{
|
||||
r.AddHeader(L.T("trade.h"));
|
||||
AmountRow(r);
|
||||
TradeRows(r, Venue.Dex);
|
||||
SwapBlock(r);
|
||||
if (Unlock.BlackUnlocked && BlackMarket.DeskOpen(StoreReputation.GetBMReputation())) PoneroBlock(r);
|
||||
});
|
||||
}
|
||||
@@ -2311,18 +2370,14 @@ namespace Decentralized
|
||||
Open();
|
||||
}
|
||||
|
||||
/// <summary>交易:Poinbase 是托管账户,直接成交;DEX 是合约调用,要在硬件钱包上签名。</summary>
|
||||
/// <summary>交易:Poinbase 是托管账户,直接成交。</summary>
|
||||
private static void Trade(Pool p, bool buy)
|
||||
{
|
||||
if (!ReadAmount(out double v)) return;
|
||||
var w = p.Venue == Venue.Dex ? Session.Data.Hot : Session.Data.Poinbase;
|
||||
string pay = buy ? p.Quote : p.Base, get = buy ? p.Base : p.Quote;
|
||||
var w = Session.Data.Poinbase;
|
||||
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 (p.Venue != Venue.Dex) { DoTrade(p, buy, v, w); return; }
|
||||
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) });
|
||||
DoTrade(p, buy, v, w);
|
||||
}
|
||||
|
||||
private static void DoTrade(Pool p, bool buy, double v, Wallet w)
|
||||
|
||||
@@ -318,17 +318,38 @@ internal static class Program
|
||||
Check(n.Anchor(new MarketEvent[0]) > Tuning.PscStartPrice * 1.05, "锚随之上移");
|
||||
});
|
||||
|
||||
Test("市场:其他交易对每晚被套利拉向参考价,三角价差收敛", () =>
|
||||
Test("市场:其他交易对每晚被套利拉向参考价,价差收敛", () =>
|
||||
{
|
||||
var m = Market.CreateDefault(1);
|
||||
var crt = m.Find(Venue.Dex, Assets.PSC, Assets.CRT);
|
||||
crt.SetPrice(130, crt.QuoteReserve);
|
||||
var dexPsc = m.DexPool(Assets.PSC);
|
||||
dexPsc.SetPrice(130, dexPsc.QuoteReserve);
|
||||
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);
|
||||
Check(gap < 0.03, $"五晚后 PSC/CRT 与参考价相差 {gap:P1}");
|
||||
var crtCrc = m.Find(Venue.Dex, Assets.CRT, Assets.CRC);
|
||||
double gap = Math.Abs(dexPsc.Price / m.Price - 1);
|
||||
Check(gap < 0.03, $"五晚后 DEX 的 PSC/CRC 与参考价相差 {gap:P1}");
|
||||
var crtCrc = m.DexPool(Assets.CRT);
|
||||
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}");
|
||||
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 脱锚与冻结概率", () =>
|
||||
|
||||
Reference in new issue
Block a user