Domain: market, stablecoins, compliant cash-out with AML
Market: PSCoin reference price with sentiment, anchor shifts from game events (blackout, crackdown), daily volatility, NPC whales, a day-average mark price, and side pairs pulled toward the reference by arbitrage. Stablecoins: CRC depeg and haircut, CRT depeg and freeze chance by black market reputation, CRA staking subsidy, warnings and the three-day spiral. Exchange: CRC cash-out at market price, AML hit chance from size, 7-day total, taint ratio and mixer/flag; light hits confiscate the tainted part, heavy hits also report it as fencing; tax only on gains. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ce3bed1467
commit
efc58888f3
6 files changed
+422
No files matched your search
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
public enum AmlOutcome { Clear, Light, Heavy }
|
||||
|
||||
/// <summary>一次合规出金的结果。罪证由接入层记进游戏的罪证账本(销赃)。</summary>
|
||||
public sealed class CashoutResult
|
||||
{
|
||||
public double Requested;
|
||||
public double Received;
|
||||
public double Confiscated;
|
||||
public double Crime;
|
||||
public double Tax;
|
||||
public double HitChance;
|
||||
public AmlOutcome Outcome;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合规交易所出金:CRC 兑回信用点,过 AML。审查按单笔金额、7 天累计金额、污点比例、是否来自混币器或被标记地址判定。
|
||||
/// 轻度命中(污点 < 30%)没收污点部分、不记罪证;重度命中没收污点部分,并把污点额记为销赃罪证。
|
||||
/// </summary>
|
||||
public sealed class Exchange
|
||||
{
|
||||
/// <summary>最近的出金记录:(天, 金额)。</summary>
|
||||
public List<KeyValuePair<int, double>> History = new List<KeyValuePair<int, double>>();
|
||||
|
||||
public static double HitChance(double amount, double taint, double recentTotal, double rent, bool fromMixerOrFlagged)
|
||||
{
|
||||
if (amount <= 0) return 0;
|
||||
double ratio = taint / amount;
|
||||
double big = Tuning.AmlBigRents * rent, cum = Tuning.AmlCumRents * rent;
|
||||
double p = 0;
|
||||
if (ratio >= Tuning.AmlTaintRatio) p += ratio * 1.5;
|
||||
p += Math.Max(0, (amount - big) / big) * 0.2;
|
||||
p += Math.Max(0, (recentTotal + amount - cum) / cum) * 0.2;
|
||||
if (fromMixerOrFlagged) p += Tuning.AmlMixerBonus;
|
||||
return Math.Min(Tuning.AmlMaxChance, p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从地址里取出 amount 个 CRC 兑成信用点(按 CRC 当前价格,平时为 1;脱锚期间按市价),扣手续费。污点与成本按比例随取出部分计算;
|
||||
/// 只对超出成本的部分计税。
|
||||
/// </summary>
|
||||
public CashoutResult Cashout(Wallet wallet, double amount, Func<string, double> price, int day, double rent, bool fromMixer, Rng rng)
|
||||
{
|
||||
amount = Math.Min(amount, wallet.Get(Assets.CRC));
|
||||
var r = new CashoutResult { Requested = amount };
|
||||
if (amount <= 0) return r;
|
||||
double total = wallet.Value(price);
|
||||
double share = total > 0 ? Math.Min(1, amount * price(Assets.CRC) / total) : 0;
|
||||
double taint = wallet.Taint * share, basis = wallet.Basis * share;
|
||||
History.RemoveAll(h => h.Key <= day - Tuning.AmlWindowDays);
|
||||
double recent = History.Sum(h => h.Value);
|
||||
double value = amount * price(Assets.CRC);
|
||||
r.HitChance = HitChance(value, taint, recent, rent, fromMixer || wallet.Flagged);
|
||||
wallet.Add(Assets.CRC, -amount);
|
||||
wallet.Taint -= taint;
|
||||
wallet.Basis -= basis;
|
||||
History.Add(new KeyValuePair<int, double>(day, value));
|
||||
double got = value * (1 - Tuning.ExchangeFee);
|
||||
if (rng.Chance(r.HitChance))
|
||||
{
|
||||
r.Outcome = taint / value >= Tuning.AmlHeavyRatio ? AmlOutcome.Heavy : AmlOutcome.Light;
|
||||
r.Confiscated = Math.Min(got, taint);
|
||||
got -= r.Confiscated;
|
||||
if (r.Outcome == AmlOutcome.Heavy) r.Crime = taint;
|
||||
}
|
||||
r.Tax = Math.Max(0, got - basis) * Tuning.TaxRate;
|
||||
r.Received = got;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>
|
||||
/// 影响行情的游戏事件种类。接入层把游戏的新闻事件与派系行动映射到这里(例如全站停电、电力短缺 → Blackout)。
|
||||
/// </summary>
|
||||
public enum MarketEvent { Blackout, Crackdown, GangWar, RevRaidUpper, LowerUnrest }
|
||||
|
||||
/// <summary>
|
||||
/// PSCoin 行情与全部交易对。PSCoin 有一个参考价(主交易对:合规交易所 PSC/CRC);
|
||||
/// 夜间参考价向价格锚回归并加噪声,其余交易对由 NPC 套利者拉向参考价。
|
||||
/// </summary>
|
||||
public sealed class Market
|
||||
{
|
||||
public double Sentiment = 1.0;
|
||||
public List<Pool> Pools = new List<Pool>();
|
||||
/// <summary>开门时的参考价,夜间用它和收盘价算当日均价(合约的标记价)。</summary>
|
||||
public double OpenPrice = Tuning.PscStartPrice;
|
||||
|
||||
public const double ArbRate = 0.5; // 非主交易对每晚被拉回参考价的比例
|
||||
public const double SideDepthShare = 0.3; // 非主交易对的深度占主交易对的比例
|
||||
|
||||
public static Market CreateDefault(int day)
|
||||
{
|
||||
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.CRT, Assets.CRC, 1, depth * SideDepthShare, Tuning.DexFee));
|
||||
return m;
|
||||
}
|
||||
|
||||
public Pool Main => Pools[0];
|
||||
|
||||
public Pool Find(Venue venue, string baseAsset, string quote) =>
|
||||
Pools.FirstOrDefault(x => x.Venue == venue && x.Base == baseAsset && x.Quote == quote);
|
||||
|
||||
/// <summary>PSCoin 的参考价(信用点)。</summary>
|
||||
public double Price => Main.Price;
|
||||
|
||||
public double Anchor(ICollection<MarketEvent> active)
|
||||
{
|
||||
double a = Tuning.PscStartPrice * Sentiment;
|
||||
if (active.Contains(MarketEvent.Blackout)) a *= 1.10;
|
||||
if (active.Contains(MarketEvent.Crackdown)) a *= 0.92;
|
||||
return a;
|
||||
}
|
||||
|
||||
public static double Volatility(ICollection<MarketEvent> active)
|
||||
{
|
||||
double v = Tuning.PscVol;
|
||||
if (active.Contains(MarketEvent.GangWar)) v *= 2;
|
||||
else if (active.Contains(MarketEvent.RevRaidUpper)) v *= 1.5;
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>开门:一次日内波动(幅度为日波动率的一半)。</summary>
|
||||
public void Open(ICollection<MarketEvent> active, Rng rng, int day)
|
||||
{
|
||||
double p = Price * Math.Exp(rng.Gauss() * Volatility(active) / 2);
|
||||
Main.SetPrice(p, Tuning.PoolDepth(day));
|
||||
OpenPrice = p;
|
||||
}
|
||||
|
||||
/// <summary>NPC 大户:每周约 2 笔、每笔 2,000–8,000 信用点,随机买或卖。返回成交额(正为买入),没有交易时为 0。</summary>
|
||||
public double Whale(Rng rng)
|
||||
{
|
||||
if (!rng.Chance(2.0 / 7)) return 0;
|
||||
double size = rng.Range(2000.0, 8000.0);
|
||||
if (rng.Chance(0.5)) { Main.BuyBase(size); return size; }
|
||||
Main.SellBase(size / Price);
|
||||
return -size;
|
||||
}
|
||||
|
||||
/// <summary>当日均价:合约强平与借贷估值用它,白天的插针不触发强平。</summary>
|
||||
public double Mark => (OpenPrice + Price) / 2;
|
||||
|
||||
/// <summary>
|
||||
/// 夜间:情绪做均值回归随机游走;参考价向锚回归 15% 并加噪声,限制在锚的 0.3–3 倍;
|
||||
/// 其余交易对按各自计价币的价格拉向参考价。
|
||||
/// </summary>
|
||||
public void Night(ICollection<MarketEvent> active, Rng rng, int day, Func<string, double> stablePrice)
|
||||
{
|
||||
Sentiment *= Math.Exp(Tuning.SentimentKappa * -Math.Log(Sentiment) + rng.Gauss() * Tuning.SentimentVol);
|
||||
double anchor = Anchor(active);
|
||||
double lp = Math.Log(Price);
|
||||
lp += Tuning.Revert * (Math.Log(anchor) - lp) + rng.Gauss() * Volatility(active);
|
||||
double p = Math.Max(Tuning.PscStartPrice * Tuning.PriceFloor, Math.Min(Tuning.PscStartPrice * Tuning.PriceCeiling, Math.Exp(lp)));
|
||||
double depth = Tuning.PoolDepth(day);
|
||||
Main.SetPrice(p, depth);
|
||||
foreach (var pool in Pools.Skip(1))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>
|
||||
/// 三种稳定币的价格与状态:CRC(上层,托管银行出事时脱锚、可能减记),CRT(黑市,镇压或火并时脱锚,
|
||||
/// 镇压期间按黑市声望出卖客户),CRA(革命军,质押付息,补贴池告急或外生触发时进入三天死亡螺旋)。
|
||||
/// </summary>
|
||||
public sealed class Stablecoins
|
||||
{
|
||||
public double Crc = 1, Crt = 1, Cra = 1;
|
||||
public int CrcDepegEnd = -1, CrtDepegEnd = -1;
|
||||
public bool CrcHaircutPending;
|
||||
public double CraSubsidy = Tuning.CraSubsidy0;
|
||||
public int CraWarnDay = -1, CraSpiralDay = -1;
|
||||
public double RevPower = 50;
|
||||
public List<double> RevHistory = new List<double>();
|
||||
|
||||
public double Price(string asset)
|
||||
{
|
||||
switch (asset)
|
||||
{
|
||||
case Assets.CRC: return Crc;
|
||||
case Assets.CRT: return Crt;
|
||||
case Assets.CRA: return Cra;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>减记发生时返回 CRC 持有量要乘的系数(其余情况为 1)。</summary>
|
||||
public double OnEventStart(MarketEvent ev, int day, Rng rng)
|
||||
{
|
||||
if (ev == MarketEvent.RevRaidUpper && CrcDepegEnd < 0 && rng.Chance(Tuning.CrcDepegChance))
|
||||
{
|
||||
Crc = rng.Range(Tuning.CrcDepegMin, Tuning.CrcDepegMax);
|
||||
CrcDepegEnd = day + rng.Range(Tuning.CrcDepegDaysMin, Tuning.CrcDepegDaysMax);
|
||||
CrcHaircutPending = rng.Chance(Tuning.CrcHaircutChance);
|
||||
}
|
||||
if ((ev == MarketEvent.GangWar || ev == MarketEvent.Crackdown) && CrtDepegEnd < 0 && rng.Chance(Tuning.CrtDepegChance))
|
||||
{
|
||||
Crt = rng.Range(Tuning.CrtDepegMin, Tuning.CrtDepegMax);
|
||||
CrtDepegEnd = day + rng.Range(Tuning.CrtDepegDaysMin, Tuning.CrtDepegDaysMax);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>镇压开始时,玩家持有的 CRT 被冻结一半的概率:黑市声望 0 时 40%,50 以上为 0。</summary>
|
||||
public static double CrtFreezeChance(double blackMarketRep) =>
|
||||
Math.Max(0, Tuning.CrtFreezeBase - blackMarketRep * Tuning.CrtFreezePerRep);
|
||||
|
||||
public bool CraSubsidyLow => CraSubsidy <= Tuning.CraSubsidy0 * Tuning.CraSubsidyWarn;
|
||||
|
||||
/// <summary>CRA 的预兆:外生螺旋的新闻已出,或补贴池告急(利率下调)。螺旋开始后不再算预兆。</summary>
|
||||
public bool CraWarning(int day) => (CraWarnDay >= 0 && CraWarnDay <= day || CraSubsidyLow) && Cra >= 0.97;
|
||||
|
||||
public double CraRate => CraSubsidyLow ? Tuning.CraRateLow : Tuning.CraRate;
|
||||
|
||||
/// <summary>
|
||||
/// 夜间结算。返回 CRC 持有量要乘的系数(减记兑现时为 0.95,否则为 1)。
|
||||
/// playerStaked:玩家的 CRA 质押额,用来算补贴支出(玩家的利息由调用方按 <see cref="CraRate"/> 发放)。
|
||||
/// </summary>
|
||||
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 (CraSpiralDay < 0)
|
||||
{
|
||||
CraSubsidy -= (Tuning.CraNpcStake + playerStaked) * CraRate;
|
||||
bool revFalling = RevHistory.Count == 7 && RevPower < RevHistory[0] - 5;
|
||||
double weekly = Tuning.CraSpiralWeekly * (revFalling ? 3 : 1);
|
||||
double daily = 1 - Math.Pow(1 - weekly, 1.0 / 7);
|
||||
if (CraWarnDay < 0 && (rng.Chance(daily) || CraSubsidy <= 0))
|
||||
{
|
||||
CraWarnDay = day;
|
||||
CraSpiralDay = day + rng.Range(Tuning.CraWarnLeadMin, Tuning.CraWarnLeadMax);
|
||||
}
|
||||
}
|
||||
if (CraSpiralDay > 0 && day >= CraSpiralDay)
|
||||
Cra = Tuning.CraSpiralPath[Math.Min(day - CraSpiralDay, Tuning.CraSpiralPath.Length - 1)];
|
||||
return crcFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,30 @@ namespace Decentralized.Domain
|
||||
public const double SentimentKappa = 0.02, SentimentVol = 0.02;
|
||||
public const double PriceFloor = 0.3, PriceCeiling = 3.0;
|
||||
|
||||
// 稳定币
|
||||
public const double CrcDepegChance = 0.30, CrcDepegMin = 0.88, CrcDepegMax = 0.93;
|
||||
public const int CrcDepegDaysMin = 2, CrcDepegDaysMax = 3;
|
||||
public const double CrcHaircutChance = 0.15, CrcHaircut = 0.95;
|
||||
public const double CrtDepegChance = 0.40, CrtDepegMin = 0.92, CrtDepegMax = 0.96;
|
||||
public const int CrtDepegDaysMin = 1, CrtDepegDaysMax = 2;
|
||||
public const double CrtFreezeBase = 0.40, CrtFreezePerRep = 0.008, CrtFreezeShare = 0.5;
|
||||
public const int CrtUnfreezeAfter = 3;
|
||||
public const double CrtSwapFee = 0.02;
|
||||
public const double CraRate = 0.005, CraRateLow = 0.002;
|
||||
public const int CraUnlockDays = 4;
|
||||
public const double CraSubsidy0 = 30000, CraSubsidyWarn = 0.2, CraNpcStake = 20000;
|
||||
public const double CraSpiralWeekly = 0.02;
|
||||
public const int CraWarnLeadMin = 4, CraWarnLeadMax = 7;
|
||||
public static readonly double[] CraSpiralPath = { 0.85, 0.30, 0.02 };
|
||||
|
||||
// 合规出金与 AML(阈值以当前房租为单位)
|
||||
public const double AmlBigRents = 5, AmlCumRents = 15;
|
||||
public const int AmlWindowDays = 7;
|
||||
public const double AmlTaintRatio = 0.10, AmlHeavyRatio = 0.30, AmlMixerBonus = 0.30, AmlMaxChance = 0.9;
|
||||
public const double TaxRate = 0.08;
|
||||
/// <summary>单个金融违规事件记入的罪证上限。</summary>
|
||||
public const double CrimeEventCap = 400;
|
||||
|
||||
/// <summary>主交易对(信用点一侧)的深度随天数增长。</summary>
|
||||
public static double PoolDepth(int day) => PoolDepth0 * (1 + day / 60.0);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user