using System;
using System.Collections.Generic;
using System.Linq;
namespace Decentralized.Domain
{
///
/// 三种稳定币的价格与状态:CRC(上层,托管银行出事时脱锚、可能减记),CRT(黑市,镇压或火并时脱锚,
/// 镇压期间按黑市声望出卖客户),CRA(革命军,部分抵押:流通量 S、储备 R(CRC)、抵押率 c;质押利息以无抵押的新 CRA 付出,
/// 稀释储备覆盖率 R/S;革命军挪用储备时 R/S 低于崩盘线进入三天死亡螺旋,劫掠上层补回储备)。
///
public enum CraOrderKind { Minted, Burned, Lost }
/// 官方一级市场的排队单:铸造(存入 CRC 和 SPARK)或销毁(存入 CRA),到期结算,官方可能把它黑掉。
public sealed class CraOrder
{
public int Id, Due;
public string Wallet;
public bool Mint;
public double Cra, Crc, Spark, C;
/// 付出去的币的脏比例(按价值加权):到期换回的币按这个比例是脏的。
public double DirtyRatio;
}
public sealed class CraOrderEvent
{
public CraOrder Order;
public CraOrderKind Kind;
public double Cra, Crc, Spark;
}
public sealed class Stablecoins
{
public double Crc = 1, Crt = 1, Cra = 1;
public int CrcDepegEnd = -1, CrtDepegEnd = -1;
public bool CrcHaircutPending;
/// CRA 流通量与储备(CRC);c 是现在铸造要求的 CRC 份额(0~1)。
public double Supply = Tuning.CraS0, Reserve = Tuning.CraS0 * Tuning.CraC0, C = Tuning.CraC0;
/// 预兆日(新闻发出的那天)、预定的挪用日、崩盘开始日(-1 = 没有)。
public int CraWarnDay = -1, CraEventDay = -1, CraCrashDay = -1;
public List Orders = new List();
public int OrderSeq;
public double RevPower = 50;
/// 平衡代币 SPARK 的价格(信用点);SparkPre 是螺旋开始前一刻的价格,螺旋三天按它的倍数走。
public double Spark = Tuning.SparkStart, SparkPre = -1;
public List RevHistory = new List();
public double Price(string asset)
{
switch (asset)
{
case Assets.CRC: return Crc;
case Assets.CRT: return Crt;
case Assets.CRA: return Cra;
case Assets.SPARK: return Spark;
default: return 1;
}
}
/// 减记发生时返回 CRC 持有量要乘的系数(其余情况为 1)。
public double OnEventStart(MarketEvent ev, int day, Rng rng)
{
if (ev == MarketEvent.RevRaidUpper && CrcDepegEnd < 0 && rng.Chance(Tuning.CrcDepegChance * StoryMods.Current.CrcDepeg))
{
Crc = rng.Range(Tuning.CrcDepegMin, Tuning.CrcDepegMax);
CrcDepegEnd = day + rng.Range(Tuning.CrcDepegDaysMin, Tuning.CrcDepegDaysMax);
CrcHaircutPending = rng.Chance(Math.Min(1, Tuning.CrcHaircutChance * StoryMods.Current.CrcHaircut));
}
if ((ev == MarketEvent.GangWar || ev == MarketEvent.Crackdown) && CrtDepegEnd < 0 && rng.Chance(Tuning.CrtDepegChance * StoryMods.Current.CrtDepeg))
{
Crt = rng.Range(Tuning.CrtDepegMin, Tuning.CrtDepegMax);
CrtDepegEnd = day + rng.Range(Tuning.CrtDepegDaysMin, Tuning.CrtDepegDaysMax);
}
if (ev == MarketEvent.RevRaidUpper) { Spark *= Tuning.SparkRaidShock; if (CraCrashDay < 0) Reserve += Tuning.CraLoot * Supply; }
if (ev == MarketEvent.LowerUnrest) Spark *= Tuning.SparkUnrestShock;
return 1;
}
/// 镇压开始时,玩家持有的 CRT 被冻结一半的概率:黑市声望 0 时 40%,50 以上为 0。
public static double CrtFreezeChance(double blackMarketRep) =>
Math.Max(0, Tuning.CrtFreezeBase - blackMarketRep * Tuning.CrtFreezePerRep) * StoryMods.Current.CrtFreeze;
/// 镇压开始时是否冻结玩家的 CRT:按黑市声望,声望越低越容易被黑市"出卖"。
public static bool CrtFrozenByCrackdown(double blackMarketRep, Rng rng) => rng.Chance(CrtFreezeChance(blackMarketRep));
/// 储备覆盖率 R/S。
public double Ratio => Supply > 0 ? Reserve / Supply : 1;
/// 崩盘线之上的储备(CRC):吃空即必然崩盘。预测盘和线索用它估预兆。
public double Cushion => Reserve - Tuning.CraCrashRatio * Supply;
public static double Cushion0 => Tuning.CraS0 * (Tuning.CraC0 - Tuning.CraCrashRatio);
/// 每晚的垫子消耗:利息按无抵押的新 CRA 付出,每份把崩盘线抬高 CraCrashRatio。
public static double CushionDrain(double stakedTotal, double rate) => stakedTotal * rate * Tuning.CraCrashRatio;
/// 储备覆盖率对应的价格:高于安全线为 1,线性降到崩盘线的 1 − CraDiscountMax。
public static double PriceOfRatio(double r)
{
if (r >= Tuning.CraSafeRatio) return 1;
double t = Math.Max(0, (r - Tuning.CraCrashRatio) / (Tuning.CraSafeRatio - Tuning.CraCrashRatio));
return 1 - Tuning.CraDiscountMax * (1 - t);
}
/// 铸造 cra 个 CRA 要的 CRC 和 SPARK 数量(SPARK 部分按现价折算)。
public void Collateral(double cra, out double crc, out double spark)
{
crc = cra * C;
spark = Spark > 0 ? cra * (1 - C) / Spark : 0;
}
///
/// 铸造申请:从钱包扣 CRC 和 SPARK,排队 CraMintDaysMin..Max 天,到期铸出 CRA。余额不足返回 null。
///
public CraOrder RequestMint(Wallet w, double cra, int day, Rng rng)
{
if (cra <= 0) return null;
Collateral(cra, out double crc, out double spark);
if (w.Avail(Assets.CRC) + 1e-9 < crc || w.Avail(Assets.SPARK) + 1e-9 < spark) return null;
double sv = spark * Spark, total = crc + sv;
double ratio = total > 0 ? (crc * w.DirtyRatio(Assets.CRC) + sv * w.DirtyRatio(Assets.SPARK)) / total : 0;
w.Add(Assets.CRC, -crc); w.Add(Assets.SPARK, -spark);
var o = new CraOrder { Id = ++OrderSeq, Wallet = w.Address, Mint = true, Cra = cra, Crc = crc, Spark = spark, C = C, DirtyRatio = ratio, Due = day + rng.Range(Tuning.CraMintDaysMin, Tuning.CraMintDaysMax) };
Orders.Add(o);
return o;
}
/// 销毁申请:从钱包扣 CRA,排队 CraUnlockDays 天,到期换回 CRC 和 SPARK。余额不足返回 null。
public CraOrder RequestBurn(Wallet w, double cra, int day)
{
if (cra <= 0 || w.Avail(Assets.CRA) + 1e-9 < cra) return null;
double ratio = w.DirtyRatio(Assets.CRA);
w.Add(Assets.CRA, -cra);
var o = new CraOrder { Id = ++OrderSeq, Wallet = w.Address, Mint = false, Cra = cra, C = C, DirtyRatio = ratio, Due = day + Tuning.CraUnlockDays };
Orders.Add(o);
return o;
}
/// 官方每晚黑掉一笔排队单的概率:系数 × (1 − CRA 价格),价格不低于 1 时为 0。
public double RugChance => Math.Max(0, 1 - Cra) * Tuning.CraRugPerDepeg;
public bool CraSubsidyLow => Cushion <= Cushion0 * Tuning.CraSubsidyWarn;
/// CRA 的预兆:外生螺旋的新闻已出,或补贴池告急(利率下调)。螺旋开始后不再算预兆。
public bool CraWarning(int day) => (CraWarnDay >= 0 && CraWarnDay <= day || CraSubsidyLow) && CraCrashDay < 0 && Cra >= 0.97;
public double CraRate => CraSubsidyLow ? Tuning.CraRateLow : Tuning.CraRate;
///
/// 夜间结算。返回 CRC 持有量要乘的系数(减记兑现时为 0.95,否则为 1)。
/// playerStaked:玩家的 CRA 质押额,用来算利息带来的稀释(玩家的利息由调用方按 发放)。
///
public double Night(int day, Rng rng, double playerStaked)
{
double crcFactor = 1;
if (CrcDepegEnd == day)
{
Crc = 1;
if (CrcHaircutPending) crcFactor = Tuning.CrcHaircut;
CrcDepegEnd = -1;
CrcHaircutPending = false;
}
if (CrtDepegEnd == day)
{
Crt = 1;
CrtDepegEnd = -1;
}
RevHistory.Add(RevPower);
if (RevHistory.Count > 7) RevHistory.RemoveAt(0);
RevPower = Math.Max(0, Math.Min(100, RevPower + rng.Gauss() * 3));
if (CraCrashDay < 0)
{
double interest = (Tuning.CraNpcStake + playerStaked) * CraRate;
Supply += interest;
// 抵押率调整:价格低于 1 上调,回到 1 下调;兑付的 CRC 份额不超过储备均值(见 )
if (Cra < 0.995) C = Math.Min(1, C + Tuning.CraCrStep);
else if (Cra >= 0.999) C = Math.Max(Tuning.CraCrMin, C - Tuning.CraCrStep);
if (CraEventDay < 0)
{
bool revFalling = RevHistory.Count == 7 && RevPower < RevHistory[0] - 5;
double weekly = Tuning.CraSpiralWeekly * (revFalling ? 3 : 1) * StoryMods.Current.CraSpiral(day);
double daily = 1 - Math.Pow(1 - weekly, 1.0 / 7);
if (rng.Chance(daily) || Cushion <= 0)
{
CraWarnDay = day;
CraEventDay = day + rng.Range(Tuning.CraWarnLeadMin, Tuning.CraWarnLeadMax);
}
}
else if (day >= CraEventDay)
{
// 挪用:储备被革命军拿走一部分,R/S 掉到崩盘线以下就是挤兑
Reserve *= 1 - rng.Range(Tuning.CraEmbezzleMin, Tuning.CraEmbezzleMax);
CraEventDay = -1; CraWarnDay = -1;
if (Ratio < Tuning.CraCrashRatio) CraCrashDay = day;
}
}
if (CraCrashDay >= 0)
{
int k = Math.Min(day - CraCrashDay, Tuning.CraSpiralPath.Length - 1);
Cra = Tuning.CraSpiralPath[k];
if (SparkPre < 0) SparkPre = Spark;
Spark = SparkPre * Tuning.SparkSpiralPath[Math.Min(day - CraCrashDay, Tuning.SparkSpiralPath.Length - 1)];
}
else
{
Cra = PriceOfRatio(Ratio);
Spark *= Math.Exp(rng.Gauss() * Tuning.SparkVol) * StoryMods.Current.TakeSparkPush();
Spark += (Tuning.SparkStart - Spark) * Tuning.SparkRevert;
Spark = Math.Max(Tuning.SparkStart * Tuning.SparkMinMult, Math.Min(Tuning.SparkStart * Tuning.SparkMaxMult, Spark));
}
return crcFactor;
}
///
/// 销毁一份 CRA 的兑付:CRC 份额 = min(申请时的 c, 储备均值 R/S),其余按现价用新增发的 SPARK 补。
/// 返回 (CRC 数量, SPARK 数量),并扣减储备和流通量。
///
public void Redeem(double cra, double c, out double crc, out double spark)
{
double share = Math.Min(c, Ratio);
crc = Math.Min(Reserve, cra * share);
spark = Spark > 0 ? cra * (1 - crc / Math.Max(1e-9, cra)) / Spark : 0;
Reserve -= crc; Supply = Math.Max(0, Supply - cra);
}
///
/// 夜间处理排队单:先对每一笔掷官方跑路(整笔丢失),再结算到期的。
/// 铸造到期:CRC 进储备、SPARK 销毁、流通量增加;销毁到期:按 兑付。
///
public List NightOrders(int day, Rng rng)
{
var evs = new List();
foreach (var o in Orders.ToList())
{
if (rng.Chance(RugChance)) { Orders.Remove(o); evs.Add(new CraOrderEvent { Order = o, Kind = CraOrderKind.Lost }); continue; }
if (day < o.Due) continue;
Orders.Remove(o);
var ev = new CraOrderEvent { Order = o };
if (o.Mint)
{
Supply += o.Cra; Reserve += o.Crc;
ev.Kind = CraOrderKind.Minted; ev.Cra = o.Cra;
}
else
{
Redeem(o.Cra, o.C, out ev.Crc, out ev.Spark);
ev.Kind = CraOrderKind.Burned;
}
evs.Add(ev);
}
return evs;
}
}
}