Prediction markets: news reaction, clues, gas/CRA/era markets, intel broker, bigger stakes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 07:53:12 +08:00
1 parent ca3230e7d2
commit 002606056d
12 files changed
+637 -52

No files matched your search

+85 -2
View File
@@ -4,7 +4,7 @@ using System.Linq;
namespace Decentralized.Domain
{
public enum PredictKind { Event, Faction, Purity, Price }
public enum PredictKind { Event, Faction, Purity, Price, Gas, Cra, Era }
public enum PredictResult { Ok, Closed, Amount, Funds, Cap, Opposite, NoPosition }
@@ -75,6 +75,14 @@ namespace Decentralized.Domain
public double Shares, Cost, Taint, Basis, Insider;
}
/// <summary>情报贩子卖的内幕消息:对某个盘口给出"是 / 否"的说法,三档准确率,可能是假的(与真相相反)。</summary>
public sealed class PredictTip
{
public int Market, Tier;
public bool Claim;
public double Paid;
}
/// <summary>结算记录(盘口列表下面的"最近结果")。</summary>
public sealed class PredictRecord
{
@@ -109,6 +117,7 @@ namespace Decentralized.Domain
public List<PredictMarket> Markets = new List<PredictMarket>();
public List<PredictPos> Positions = new List<PredictPos>();
public List<PredictRecord> Recent = new List<PredictRecord>();
public List<PredictTip> Tips = new List<PredictTip>();
public int Seq;
/// <summary>每晚观测:当天新出现的事件标识(和 "F:派系")。窗口里缺任何一天的观测,盘口作废退款。</summary>
public Dictionary<int, List<string>> Started = new Dictionary<int, List<string>>();
@@ -222,6 +231,7 @@ namespace Decentralized.Domain
else Record(day, m, m.Outcome, 0, false);
}
Markets.RemoveAll(m => m.Outcome >= 0);
Tips.RemoveAll(t => Find(t.Market) == null);
return evs;
}
@@ -231,6 +241,34 @@ namespace Decentralized.Domain
while (Recent.Count > Tuning.PredictHistory) Recent.RemoveAt(0);
}
public PredictTip TipFor(int market) => Tips.Find(t => t.Market == market);
/// <summary>造一条内幕消息:假情报(概率 IntelFake)说的与真相相反,否则按这一档的准确率说对或说错。</summary>
public static PredictTip MakeTip(PredictMarket m, bool truth, int tier, Rng rng)
{
bool claim;
if (rng.Chance(Tuning.IntelFake)) claim = !truth;
else claim = rng.Chance(Tuning.IntelTipAcc[tier]) ? truth : !truth;
return new PredictTip { Market = m.Id, Tier = tier, Claim = claim, Paid = Tuning.IntelTipPrice[tier] * m.MaxLoss };
}
/// <summary>
/// 夜间的消息反应:还能交易的盘口(明天仍开着)向"公开信息下的合理概率"靠拢 PredictDrift,再加 logit 噪声(NPC 流量)。
/// 价格在夜里才反应,所以白天先看到报纸、传闻的人可以抢在前面买、等涨价再卖。target 返回 null 表示这个盘口没有新信息。
/// </summary>
public void Drift(int day, Func<PredictMarket, double?> target, Rng rng)
{
foreach (var m in Markets.Where(x => x.Outcome < 0 && x.FromDay > day + 1))
{
double p = m.PYes, t = target(m) ?? p;
t = Math.Max(Tuning.PredictPMin, Math.Min(Tuning.PredictPMax, t));
double pt = p + Tuning.PredictDrift * (t - p);
double lp = Math.Log(pt / (1 - pt)) + Tuning.PredictNoise * rng.Gauss();
pt = Math.Max(Tuning.PredictPMin, Math.Min(Tuning.PredictPMax, 1 / (1 + Math.Exp(-lp))));
m.QYes = m.QNo + m.B * Math.Log(pt / (1 - pt));
}
}
/// <summary>读观测:窗口里每一天都有记录才能判断;有任何一天缺就返回 null(作废)。</summary>
public bool? Happened(PredictMarket m)
{
@@ -260,7 +298,7 @@ namespace Decentralized.Domain
{
var c = pool[rng.Range(0, pool.Count - 1)];
pool.RemoveAll(x => x.Kind == c.Kind && x.Key == c.Key);
int from = c.Tomorrow ? day + 1 : day + 1 + rng.Range(0, 1);
int from = c.Tomorrow ? day + 1 : day + 1 + rng.Range(0, 2);
int to = c.Tomorrow ? from : from + rng.Range(0, 2);
var m = PredictMarket.Create(++Seq, c.Kind, c.Key, c.Title, c.Param, day, from, to, c.Prob(from, to));
Markets.Add(m); made.Add(m);
@@ -279,6 +317,51 @@ namespace Decentralized.Domain
return 1 - Math.Pow(1 - p1, days);
}
/// <summary>某天 gas 不低于阈值的概率(已知相位):周期值 × (1 ± 噪声) 均匀分布。</summary>
public static double GasAtLeast(int day, int phase, double threshold)
{
double c = TxRules.GasCycle(day, phase), hi = c * (1 + Tuning.TxGasNoise), lo = c * (1 - Tuning.TxGasNoise);
return Math.Max(0, Math.Min(1, (hi - threshold) / (hi - lo)));
}
/// <summary>不知道相位时的先验:七个相位等可能。</summary>
public static double GasFlat(int day, double threshold)
{
double s = 0;
for (int ph = 0; ph < Tuning.TxGasPeriod; ph++) s += GasAtLeast(day, ph, threshold);
return s / Tuning.TxGasPeriod;
}
/// <summary>
/// CRA 预警在 [from, to] 夜里发出的概率:每晚补贴池先被消耗,归零那晚必发,否则按日概率;
/// 窗口之前已经发出的不算(那就是"否")。subsidy / drain 是今天的补贴池和每晚消耗,today 是今晚。
/// </summary>
public static double CraWarnProb(double subsidy, double drain, int today, int from, int to, double dailyHazard)
{
double noWarn = 1, inWindow = 0;
for (int d = today; d <= to; d++)
{
subsidy -= drain;
double h = subsidy <= 0 ? 1 : dailyHazard;
if (d >= from) inWindow += noWarn * h;
noWarn *= 1 - h;
}
return inWindow;
}
/// <summary>
/// 矿机时代在 [from, to] 开始的概率:没出预警时在 [eraMin, eraMax] 里均匀;预警已出(warnDay >= 0)时落在预警后 EraWarnMin..EraWarnMax 天。
/// 只算还没过去的日子(today 之后)。
/// </summary>
public static double EraProb(int today, int from, int to, int warnDay)
{
int lo = warnDay >= 0 ? warnDay + Tuning.EraWarnMin : Tuning.EraMin, hi = warnDay >= 0 ? warnDay + Tuning.EraWarnMax : Tuning.EraMax;
lo = Math.Max(lo, today + 1);
if (hi < lo) return 0;
int a = Math.Max(lo, from), b = Math.Min(hi, to);
return b < a ? 0 : (b - a + 1) / (double)(hi - lo + 1);
}
public static double FactionProb(int days) => 1 - Math.Pow(1 - Tuning.PredictFactionDaily, days);
/// <summary>均匀整数 lo..hi 里不低于 threshold 的概率。</summary>