Files
probably-decentralized/src/Decentralized.Domain/Market.cs
T

124 lines
6.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace Decentralized.Domain
{
/// <summary>
/// 影响行情的游戏事件种类。接入层把游戏的新闻事件与派系行动映射到这里(例如全站停电、电力短缺 → Blackout)。
/// </summary>
public enum MarketEvent { Blackout, Crackdown, GangWar, RevRaidUpper, LowerUnrest }
/// <summary>
/// PSC 行情与全部交易对。PSC 有一个参考价(主交易对:合规交易所 PSC/CRC);
/// 夜间参考价向价格锚回归并加噪声,其余交易对由 NPC 套利者拉向参考价。
/// </summary>
public sealed class Market
{
public double Sentiment = 1.0;
/// <summary>供给因子:出块奖励减半抬高挖矿成本,价格锚随之上移;减半前一周有预期行情。每晚向目标靠拢。</summary>
public double Supply = 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>PSC 的参考价(信用点)。</summary>
public double Price => Main.Price;
/// <summary>历次减半抽到的传导指数之和:价格锚的供给因子目标为 2^此值。</summary>
public double HalvingLog;
/// <summary>减半当晚:抽本次的传导指数。</summary>
public void OnHalving(Rng rng) => HalvingLog += Tuning.HalvingPassMin + rng.NextDouble() * (Tuning.HalvingPassMax - Tuning.HalvingPassMin);
/// <summary>供给因子的目标:已发生的减半各自抬高锚;减半前 7 天内再逐日加预期。</summary>
public double SupplyTarget(int day)
{
double t = Math.Pow(2, HalvingLog);
int toHalving = Tuning.HalvingDays - day % Tuning.HalvingDays; // 减半当晚 day % HalvingDays == 0,那天之后又是一个完整周期
if (toHalving <= Tuning.HalvingRampDays) t *= 1 + Tuning.HalvingRamp * (Tuning.HalvingRampDays - toHalving + 1) / Tuning.HalvingRampDays;
return t;
}
public void UpdateSupply(int day) => Supply += (SupplyTarget(day) - Supply) * Tuning.SupplyAdjust;
public double Anchor(ICollection<MarketEvent> active)
{
double a = Tuning.PscStartPrice * Sentiment * Supply;
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);
}
}
}
}