Stage 7.1: prediction markets (LMSR, event/faction/purity/price markets, insider check)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7d13ae2fe8
commit
e5a6e3e22c
11 files changed
+810
-4
No files matched your search
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
## 未发布
|
## 未发布
|
||||||
|
|
||||||
|
### 第七阶段:预测市场与操纵(7.1 预测市场)
|
||||||
|
|
||||||
|
- Web3 页"预测"子页(游戏第 8 天起,Web3 治理社区已买下):每个盘口是一道是非题,开盘当天到窗口开始前可以买卖,窗口最后一天夜里结算;每天开门后补盘,同时开放 3–6 个,每天最多新开 2 个。资金走 Poinbase 账户的 CRC(托管账户,不用签名),污点与成本按花掉的价值比例随资金进持仓,赢的钱继承押注资金的污点比例,卖回时按比例退。
|
||||||
|
- 盘口类型:新闻事件("出现某常规事件",先验 = 游戏掷骰规则:5 天后每天 50 / 35 / 25% 有事件,再按权重抽)、派系行动("某派系有行动开始",每天开始概率 6%,估计值)、水纯度(明天上层 / 下层水纯度 ≥ 阈值,先验精确)、PSC 价格(窗口最后一天夜间结算前的价格 ≥ 阈值,先验用价格过程的正态近似)。事件和派系行动靠夜间对比"昨晚生效的事件"得到当天新出现的;窗口里缺任何一天的记录(没睡觉结算)盘口作废、退回本金。
|
||||||
|
- 定价:LMSR,初始价 = 先验,单盘系统最大亏损 = 300 + 10 × 天数,流动性参数由它反推;买卖各收 2%;单人单盘份额上限 = 最大亏损 × 3;一个盘口只持有一边。
|
||||||
|
- 内幕:下注时这个事件(或派系行动)已经以"玩家看得到"的形式排进日程(传闻纸条、派系特权),而且落在窗口里,押「是」算内幕单。内幕持仓达到份额上限的 40% 后,结算时 50% 概率被查:只退本金,收益的一半(上限 400)记 FRAUD 罪证。情报顾客没接。
|
||||||
|
- 复核(ops `research/predsim`):不知情随机押期望为负;谨慎的内幕(压在查处线下)在低先验盘口一盘净赚 270–520(第 30 天最大亏损 600),买满上限的期望反而更低。行情先验和真实 `Market` 过程的蒙特卡洛对得上(单元测试,误差 < 4%)。
|
||||||
|
- 游戏内验证:新开盘口列表显示问题、先验、持仓;买入 100 得 319 份并涨价、全部卖出收回 96.08(扣 2%);调试推进几天后纯度盘和价格盘押中,兑付 118 和 199,晨间日志"预测押中";一个真实的革命军行动被观测到并结算为"是";没押的盘口也进"最近结果"。没有在游戏里验证:常规新闻事件盘口的自然结算(本次测试存档里没碰上)、作废、内幕判定与被查、手册文字、英文界面。派系每天 6% 的先验没有真实数据校准。
|
||||||
|
- 单元测试 11590 通过。
|
||||||
|
|
||||||
### 第六阶段:高收益与高风险(进行中)
|
### 第六阶段:高收益与高风险(进行中)
|
||||||
|
|
||||||
- RatDigg 矿机(M1 / M2 / M3,11,000 / 27,000 / 72,000,50 / 120 / 300 H,60 / 110 / 220 kJ/天):独立的 Machinery,窗口里有电池槽、模块槽(和矿架同一套效果与上限)、修理槽,没有显卡槽;夜间和矿架一样先从电池扣电、有电才挖,算入全网算力。损耗每天 0.8,暴毙系数 0.5;没有外观、没有真假。暴毙后成"损坏的矿机"(残值 15%),往修理槽按顺序放电子元件修好(M1:4 普通 + 1 稀有;M2:2 稀有;M3:1 超科技 + 1 稀有,约机价的 4%),修好健康 −20。新画了三张像素图(M1 绿、M2 蓝、M3 金的指示灯与风扇轴心)。
|
- RatDigg 矿机(M1 / M2 / M3,11,000 / 27,000 / 72,000,50 / 120 / 300 H,60 / 110 / 220 kJ/天):独立的 Machinery,窗口里有电池槽、模块槽(和矿架同一套效果与上限)、修理槽,没有显卡槽;夜间和矿架一样先从电池扣电、有电才挖,算入全网算力。损耗每天 0.8,暴毙系数 0.5;没有外观、没有真假。暴毙后成"损坏的矿机"(残值 15%),往修理槽按顺序放电子元件修好(M1:4 普通 + 1 稀有;M2:2 稀有;M3:1 超科技 + 1 稀有,约机价的 4%),修好健康 −20。新画了三张像素图(M1 绿、M2 蓝、M3 金的指示灯与风扇轴心)。
|
||||||
|
|||||||
@@ -0,0 +1,310 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Decentralized.Domain
|
||||||
|
{
|
||||||
|
public enum PredictKind { Event, Faction, Purity, Price }
|
||||||
|
|
||||||
|
public enum PredictResult { Ok, Closed, Amount, Funds, Cap, Opposite, NoPosition }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 一个盘口:问一个是非题,开盘当天可以买卖,窗口(FromDay..ToDay)里的结果由游戏或本 mod 的数值决定,ToDay 夜间结算。
|
||||||
|
/// 定价用 LMSR:初始价等于先验概率 P0,系统在单盘的最大亏损固定(<see cref="MaxLoss"/>),流动性参数 B 由它反推。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PredictMarket
|
||||||
|
{
|
||||||
|
public int Id;
|
||||||
|
public PredictKind Kind;
|
||||||
|
/// <summary>事件 / 派系标识;纯度盘是 "gutter" 或 "rust";行情盘是 "PSC"。</summary>
|
||||||
|
public string Key, Title;
|
||||||
|
/// <summary>阈值(纯度、价格盘)。</summary>
|
||||||
|
public double Param;
|
||||||
|
public int OpenDay, FromDay, ToDay;
|
||||||
|
public double P0, B, QYes, QNo;
|
||||||
|
/// <summary>-1 未结算,0 否,1 是,2 作废(缺观测数据,退还本金)。</summary>
|
||||||
|
public int Outcome = -1;
|
||||||
|
|
||||||
|
public double MaxLoss => B * Math.Log(1 / Math.Min(P0, 1 - P0));
|
||||||
|
public double ShareCap => Tuning.PredictCap * MaxLoss;
|
||||||
|
public bool IsOpen(int day) => Outcome < 0 && day < FromDay;
|
||||||
|
public double PYes => 1 / (1 + Math.Exp((QNo - QYes) / B));
|
||||||
|
|
||||||
|
public static double LossFor(int day) => Tuning.PredictLoss0 + Tuning.PredictLossPerDay * day;
|
||||||
|
|
||||||
|
public static PredictMarket Create(int id, PredictKind kind, string key, string title, double param, int openDay, int fromDay, int toDay, double p0)
|
||||||
|
{
|
||||||
|
p0 = Math.Max(Tuning.PredictPMin, Math.Min(Tuning.PredictPMax, p0));
|
||||||
|
double b = LossFor(openDay) / Math.Log(1 / Math.Min(p0, 1 - p0));
|
||||||
|
return new PredictMarket { Id = id, Kind = kind, Key = key, Title = title, Param = param, OpenDay = openDay, FromDay = fromDay, ToDay = toDay, P0 = p0, B = b, QYes = b * Math.Log(p0 / (1 - p0)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>LMSR 成本函数(数值稳定写法)。</summary>
|
||||||
|
public double C(double qy, double qn)
|
||||||
|
{
|
||||||
|
double m = Math.Max(qy, qn);
|
||||||
|
return m + B * Math.Log(Math.Exp((qy - m) / B) + Math.Exp((qn - m) / B));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>某一边买入 shares 份要付的成本(不含手续费)。</summary>
|
||||||
|
public double BuyCost(bool yes, double shares) => yes ? C(QYes + shares, QNo) - C(QYes, QNo) : C(QYes, QNo + shares) - C(QYes, QNo);
|
||||||
|
|
||||||
|
/// <summary>某一边卖出 shares 份能收回的金额(不含手续费)。</summary>
|
||||||
|
public double SellValue(bool yes, double shares) => yes ? C(QYes, QNo) - C(QYes - shares, QNo) : C(QYes, QNo) - C(QYes, QNo - shares);
|
||||||
|
|
||||||
|
/// <summary>花 cost(不含手续费)能买到的份数:LMSR 成本函数的闭式反解。</summary>
|
||||||
|
public double SharesFor(bool yes, double cost)
|
||||||
|
{
|
||||||
|
double y = yes ? QYes : QNo, o = yes ? QNo : QYes;
|
||||||
|
double m = Math.Max(y, o);
|
||||||
|
double a = Math.Exp((y - m) / B), oo = Math.Exp((o - m) / B);
|
||||||
|
double x = Math.Exp(Math.Min(cost / B, 700)) * (a + oo) - oo;
|
||||||
|
return m + B * Math.Log(x) - y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double PriceOf(bool yes) => yes ? PYes : 1 - PYes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>玩家在某个盘口的持仓(一个盘口只能持有一边)。Insider 是内幕单买入的份数。</summary>
|
||||||
|
public sealed class PredictPos
|
||||||
|
{
|
||||||
|
public int Market;
|
||||||
|
public bool Yes;
|
||||||
|
public double Shares, Cost, Taint, Basis, Insider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>结算记录(盘口列表下面的"最近结果")。</summary>
|
||||||
|
public sealed class PredictRecord
|
||||||
|
{
|
||||||
|
public int Day;
|
||||||
|
public string Title;
|
||||||
|
public int Outcome;
|
||||||
|
public double Pnl;
|
||||||
|
public bool Held;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>夜间结算发生的事。Kind:win / lose / void / caught。</summary>
|
||||||
|
public sealed class PredictEvent
|
||||||
|
{
|
||||||
|
public string Kind, Title;
|
||||||
|
public PredictMarket Market;
|
||||||
|
public double Amount, Fraud;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>候选盘口(接入层按游戏当前状态给出):先验概率按窗口算。</summary>
|
||||||
|
public sealed class PredictCandidate
|
||||||
|
{
|
||||||
|
public PredictKind Kind;
|
||||||
|
public string Key, Title;
|
||||||
|
public double Param;
|
||||||
|
/// <summary>true:只问明天一天(纯度盘)。</summary>
|
||||||
|
public bool Tomorrow;
|
||||||
|
public Func<int, int, double> Prob;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PredictBook
|
||||||
|
{
|
||||||
|
public List<PredictMarket> Markets = new List<PredictMarket>();
|
||||||
|
public List<PredictPos> Positions = new List<PredictPos>();
|
||||||
|
public List<PredictRecord> Recent = new List<PredictRecord>();
|
||||||
|
public int Seq;
|
||||||
|
/// <summary>每晚观测:当天新出现的事件标识(和 "F:派系")。窗口里缺任何一天的观测,盘口作废退款。</summary>
|
||||||
|
public Dictionary<int, List<string>> Started = new Dictionary<int, List<string>>();
|
||||||
|
public List<string> Prev;
|
||||||
|
|
||||||
|
public PredictMarket Find(int id) => Markets.Find(m => m.Id == id);
|
||||||
|
public PredictPos PosOf(int id) => Positions.Find(p => p.Market == id);
|
||||||
|
|
||||||
|
private static double Share(Wallet acct, double amount, Func<string, double> price)
|
||||||
|
{
|
||||||
|
double total = acct.Value(price);
|
||||||
|
return total > 0 ? Math.Min(1, amount * price(Assets.CRC) / total) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 买入:花 spend(含 2% 手续费)的 CRC。污点与成本按花掉的价值比例随资金进持仓。insider:这笔单下单时玩家已经知道答案(传闻纸条、派系特权)。
|
||||||
|
/// </summary>
|
||||||
|
public PredictResult Buy(Wallet acct, PredictMarket m, bool yes, double spend, int day, Func<string, double> pr, bool insider, out double shares)
|
||||||
|
{
|
||||||
|
shares = 0;
|
||||||
|
if (!m.IsOpen(day)) return PredictResult.Closed;
|
||||||
|
if (spend <= 0) return PredictResult.Amount;
|
||||||
|
if (acct.Avail(Assets.CRC) + 1e-9 < spend) return PredictResult.Funds;
|
||||||
|
var pos = PosOf(m.Id);
|
||||||
|
if (pos != null && pos.Yes != yes) return PredictResult.Opposite;
|
||||||
|
double cost = spend / (1 + Tuning.PredictFee);
|
||||||
|
shares = m.SharesFor(yes, cost);
|
||||||
|
if ((pos?.Shares ?? 0) + shares > m.ShareCap + 1e-9) { shares = 0; return PredictResult.Cap; }
|
||||||
|
double share = Share(acct, spend, pr);
|
||||||
|
double taint = acct.Taint * share, basis = acct.Basis * share;
|
||||||
|
acct.Add(Assets.CRC, -spend); acct.Taint -= taint; acct.Basis -= basis;
|
||||||
|
if (yes) m.QYes += shares; else m.QNo += shares;
|
||||||
|
if (pos == null) Positions.Add(pos = new PredictPos { Market = m.Id, Yes = yes });
|
||||||
|
pos.Shares += shares; pos.Cost += cost; pos.Taint += taint; pos.Basis += basis;
|
||||||
|
if (insider) pos.Insider += shares;
|
||||||
|
return PredictResult.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>全部卖出(盘口关闭前):收回 LMSR 的卖价减 2% 手续费,污点按比例退回,赚的部分是干净的。返回收回的 CRC。</summary>
|
||||||
|
public PredictResult SellAll(Wallet acct, PredictMarket m, int day, out double got)
|
||||||
|
{
|
||||||
|
got = 0;
|
||||||
|
var pos = PosOf(m.Id);
|
||||||
|
if (pos == null) return PredictResult.NoPosition;
|
||||||
|
if (!m.IsOpen(day)) return PredictResult.Closed;
|
||||||
|
double value = m.SellValue(pos.Yes, pos.Shares);
|
||||||
|
got = value * (1 - Tuning.PredictFee);
|
||||||
|
if (pos.Yes) m.QYes -= pos.Shares; else m.QNo -= pos.Shares;
|
||||||
|
Positions.Remove(pos);
|
||||||
|
Pay(acct, pos, got);
|
||||||
|
return PredictResult.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Pay(Wallet acct, PredictPos pos, double payout)
|
||||||
|
{
|
||||||
|
double keep = pos.Cost > 0 ? Math.Min(1, payout / pos.Cost) : 0;
|
||||||
|
acct.Add(Assets.CRC, payout); acct.Taint += pos.Taint * keep; acct.Basis += pos.Basis * keep;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 夜间:先登记今晚的观测,再结算 ToDay 到了的盘口。outcome 由接入层给出(事件 / 派系读观测,纯度和行情读游戏数值),null 表示拿不到数据。
|
||||||
|
/// 赢家每份兑付 1 信用点;内幕持仓达到份额上限的 <see cref="Tuning.PredictInsiderShare"/> 后,有概率被查:只退本金,收益的一半记罪证。
|
||||||
|
/// </summary>
|
||||||
|
public List<PredictEvent> Night(int day, IEnumerable<string> activeNow, Func<PredictMarket, bool?> outcome, Wallet acct, Rng rng)
|
||||||
|
{
|
||||||
|
var now = activeNow.Distinct().ToList();
|
||||||
|
if (Prev != null) Started[day] = now.Where(x => !Prev.Contains(x)).ToList();
|
||||||
|
Prev = now;
|
||||||
|
foreach (var k in Started.Keys.Where(k => k < day - 12).ToList()) Started.Remove(k);
|
||||||
|
var evs = new List<PredictEvent>();
|
||||||
|
foreach (var m in Markets.Where(x => x.Outcome < 0 && x.ToDay <= day).ToList())
|
||||||
|
{
|
||||||
|
bool? res = outcome(m);
|
||||||
|
var pos = PosOf(m.Id);
|
||||||
|
if (res == null)
|
||||||
|
{
|
||||||
|
m.Outcome = 2;
|
||||||
|
if (pos != null) { Positions.Remove(pos); Pay(acct, pos, pos.Cost); evs.Add(new PredictEvent { Kind = "void", Title = m.Title, Market = m, Amount = pos.Cost }); }
|
||||||
|
Record(day, m, 2, 0, pos != null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m.Outcome = res.Value ? 1 : 0;
|
||||||
|
if (pos != null)
|
||||||
|
{
|
||||||
|
Positions.Remove(pos);
|
||||||
|
if (pos.Yes != res.Value)
|
||||||
|
{
|
||||||
|
evs.Add(new PredictEvent { Kind = "lose", Title = m.Title, Market = m, Amount = pos.Cost });
|
||||||
|
Record(day, m, m.Outcome, -pos.Cost, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double payout = pos.Shares, profit = payout - pos.Cost;
|
||||||
|
if (pos.Insider >= Tuning.PredictInsiderShare * m.ShareCap && rng.Chance(Tuning.PredictInsiderCatch))
|
||||||
|
{
|
||||||
|
Pay(acct, pos, pos.Cost);
|
||||||
|
evs.Add(new PredictEvent { Kind = "caught", Title = m.Title, Market = m, Amount = profit, Fraud = Math.Min(Tuning.PredictFraudCap, Math.Max(0, profit) * Tuning.PredictFraudShare) });
|
||||||
|
Record(day, m, m.Outcome, 0, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Pay(acct, pos, payout);
|
||||||
|
evs.Add(new PredictEvent { Kind = "win", Title = m.Title, Market = m, Amount = payout });
|
||||||
|
Record(day, m, m.Outcome, profit, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else Record(day, m, m.Outcome, 0, false);
|
||||||
|
}
|
||||||
|
Markets.RemoveAll(m => m.Outcome >= 0);
|
||||||
|
return evs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Record(int day, PredictMarket m, int outcome, double pnl, bool held)
|
||||||
|
{
|
||||||
|
Recent.Add(new PredictRecord { Day = day, Title = m.Title, Outcome = outcome, Pnl = pnl, Held = held });
|
||||||
|
while (Recent.Count > Tuning.PredictHistory) Recent.RemoveAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>读观测:窗口里每一天都有记录才能判断;有任何一天缺就返回 null(作废)。</summary>
|
||||||
|
public bool? Happened(PredictMarket m)
|
||||||
|
{
|
||||||
|
string tag = m.Kind == PredictKind.Faction ? "F:" + m.Key : m.Key;
|
||||||
|
bool any = false;
|
||||||
|
for (int d = m.FromDay; d <= m.ToDay; d++)
|
||||||
|
{
|
||||||
|
if (!Started.TryGetValue(d, out var list)) return null;
|
||||||
|
if (list.Contains(tag)) any = true;
|
||||||
|
}
|
||||||
|
return any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每天开门后补盘:开放的盘口少于下限时补到下限,之后每天再随机新开一两个,总数不超过上限。同类同键的盘口不重复开。
|
||||||
|
/// 返回新开的盘口。
|
||||||
|
/// </summary>
|
||||||
|
public List<PredictMarket> Refill(int day, Rng rng, IList<PredictCandidate> candidates)
|
||||||
|
{
|
||||||
|
var made = new List<PredictMarket>();
|
||||||
|
int open = Markets.Count(m => m.Outcome < 0);
|
||||||
|
int want = open < Tuning.PredictOpenMin ? Tuning.PredictOpenMin - open : 0;
|
||||||
|
want = Math.Max(want, rng.Range(1, Tuning.PredictNewPerDay));
|
||||||
|
want = Math.Min(want, Tuning.PredictOpenMax - open);
|
||||||
|
var pool = candidates.Where(c => !Markets.Any(m => m.Outcome < 0 && m.Kind == c.Kind && m.Key == c.Key)).ToList();
|
||||||
|
while (want-- > 0 && pool.Count > 0)
|
||||||
|
{
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
return made;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>盘口先验概率:事件按游戏的掷骰规则,纯度按均匀分布,行情按本 mod 的价格过程。</summary>
|
||||||
|
public static class PredictMath
|
||||||
|
{
|
||||||
|
/// <summary>某个常规事件在窗口里出现的概率:每天先过"有事件"这一关(chance),再按权重在可选事件里抽中。</summary>
|
||||||
|
public static double EventProb(double dayChance, double odd, double totalOdd, int days)
|
||||||
|
{
|
||||||
|
double p1 = totalOdd > 0 ? dayChance * odd / totalOdd : 0;
|
||||||
|
return 1 - Math.Pow(1 - p1, days);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double FactionProb(int days) => 1 - Math.Pow(1 - Tuning.PredictFactionDaily, days);
|
||||||
|
|
||||||
|
/// <summary>均匀整数 lo..hi 里不低于 threshold 的概率。</summary>
|
||||||
|
public static double PurityAtLeast(double threshold, int lo, int hi)
|
||||||
|
{
|
||||||
|
double n = hi - lo + 1, k = hi - Math.Ceiling(threshold) + 1;
|
||||||
|
return Math.Max(0, Math.Min(1, k / n));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double Phi(double x)
|
||||||
|
{
|
||||||
|
double t = 1 / (1 + 0.2316419 * Math.Abs(x));
|
||||||
|
double d = 0.3989423 * Math.Exp(-x * x / 2);
|
||||||
|
double p = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
|
||||||
|
return x > 0 ? 1 - p : p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 价格在 nights 个夜晚之后(每晚向锚回归 revert、加 vol 的对数噪声,每个早上再加 vol/2 的开门波动)不低于 threshold 的概率。
|
||||||
|
/// 对数价格近似正态;锚按今天的值外推(事件和减半的变化不计)。
|
||||||
|
/// </summary>
|
||||||
|
public static double PriceAtLeast(double price, double anchor, double vol, double revert, int nights, double threshold)
|
||||||
|
{
|
||||||
|
double m = Math.Log(price), la = Math.Log(anchor), v = 0;
|
||||||
|
for (int i = 0; i < nights; i++)
|
||||||
|
{
|
||||||
|
m += revert * (la - m);
|
||||||
|
v = (1 - revert) * (1 - revert) * v + vol * vol + vol * vol / 4;
|
||||||
|
}
|
||||||
|
double sd = Math.Sqrt(Math.Max(v, 1e-12));
|
||||||
|
return 1 - Phi((Math.Log(threshold) - m) / sd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -188,6 +188,16 @@ namespace Decentralized.Domain
|
|||||||
public const int EraMin = 50, EraMax = 90, EraWarnMin = 4, EraWarnMax = 7;
|
public const int EraMin = 50, EraMax = 90, EraWarnMin = 4, EraWarnMax = 7;
|
||||||
public const double EraKjMult = 0.5;
|
public const double EraKjMult = 0.5;
|
||||||
|
|
||||||
|
/// <summary>预测市场:游戏第几天起开放;手续费;每盘系统最大亏损 = 基础 + 每天增量 × 天数;单人单盘份额上限(最大亏损的倍数);
|
||||||
|
/// 同时开放的盘口数量范围、每天最多新开几个;内幕持仓达到上限份额的多少比例后结算被查的概率,查实后收益记罪证的比例与上限;
|
||||||
|
/// 派系行动的日均开始概率(先验);先验概率的夹紧范围;结算记录保留条数。</summary>
|
||||||
|
public const int PredictUnlockDay = 8;
|
||||||
|
public const double PredictFee = 0.02, PredictLoss0 = 300, PredictLossPerDay = 10, PredictCap = 3;
|
||||||
|
public const int PredictOpenMin = 3, PredictOpenMax = 6, PredictNewPerDay = 2;
|
||||||
|
public const double PredictInsiderShare = 0.4, PredictInsiderCatch = 0.5, PredictFraudShare = 0.5, PredictFraudCap = 400;
|
||||||
|
public const double PredictFactionDaily = 0.06, PredictPMin = 0.03, PredictPMax = 0.97;
|
||||||
|
public const int PredictHistory = 8;
|
||||||
|
|
||||||
/// <summary>主交易对(信用点一侧)的深度随天数增长。</summary>
|
/// <summary>主交易对(信用点一侧)的深度随天数增长。</summary>
|
||||||
public static double PoolDepth(int day) => PoolDepth0 * (1 + day / 60.0);
|
public static double PoolDepth(int day) => PoolDepth0 * (1 + day / 60.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,14 @@ namespace Decentralized
|
|||||||
}));
|
}));
|
||||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Give ASIC M1/M2/M3 (test)", () => { foreach (var m in new[] { "M1", "M2", "M3" }) Items.Give(AsicItems.New(m, 100)); for (int i = 0; i < 4; i++) Items.Give(ModHelper.SpawnItem("rare_electronic")); });
|
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Give ASIC M1/M2/M3 (test)", () => { foreach (var m in new[] { "M1", "M2", "M3" }) Items.Give(AsicItems.New(m, 100)); for (int i = 0; i < 4; i++) Items.Give(ModHelper.SpawnItem("rare_electronic")); });
|
||||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Era in 2 days (test)", () => { var n = Session.Data.Network; int day = StoreStation.GetDayCounter(); n.EraDay = day + 2; n.EraWarnDay = day; n.EraWarned = false; n.EraDone = false; Log.Info($"测试:矿机时代第 {n.EraDay} 天,NPC 耗电 {n.NpcKj}"); });
|
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Era in 2 days (test)", () => { var n = Session.Data.Network; int day = StoreStation.GetDayCounter(); n.EraDay = day + 2; n.EraWarnDay = day; n.EraWarned = false; n.EraDone = false; Log.Info($"测试:矿机时代第 {n.EraDay} 天,NPC 耗电 {n.NpcKj}"); });
|
||||||
|
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Predict: markets now (test)", () => Log.Guard("预测测试", () =>
|
||||||
|
{
|
||||||
|
var d = Session.Data; int day = StoreStation.GetDayCounter();
|
||||||
|
d.Predict.Markets.Clear();
|
||||||
|
var made = d.Predict.Refill(day, d.Rng, PredictGame.Candidates(day));
|
||||||
|
Log.Info($"测试:新开 {made.Count} 个预测盘口(不看解锁日)");
|
||||||
|
}));
|
||||||
|
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Predict: window = tomorrow (test)", () => { foreach (var m in Session.Data.Predict.Markets) { m.FromDay = m.ToDay = StoreStation.GetDayCounter() + 1; } Log.Info("测试:所有盘口窗口改成明天"); });
|
||||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Perp fund 0 (test)", () => { Session.Data.Perps.InsuranceFund = 0; Log.Info("测试:保险基金清零"); });
|
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Perp fund 0 (test)", () => { Session.Data.Perps.InsuranceFund = 0; Log.Info("测试:保险基金清零"); });
|
||||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "SPARK x0.3 (test)", () => { var st = Session.Data.Stable; st.Spark *= 0.3; Log.Info($"测试:SPARK 价格 {st.Spark:0.00}"); });
|
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "SPARK x0.3 (test)", () => { var st = Session.Data.Stable; st.Spark *= 0.3; Log.Info($"测试:SPARK 价格 {st.Spark:0.00}"); });
|
||||||
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "CRA + PSC (test)", () => { var w = Session.Data.Hot; w.Add(Assets.CRA, 1000); w.Add(Assets.PSC, 20); w.Basis += 1000; Log.Info($"测试:{WalletWindow.NameOf(w)} CRA {w.Get(Assets.CRA):0}"); });
|
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "CRA + PSC (test)", () => { var w = Session.Data.Hot; w.Add(Assets.CRA, 1000); w.Add(Assets.PSC, 20); w.Basis += 1000; Log.Info($"测试:{WalletWindow.NameOf(w)} CRA {w.Get(Assets.CRA):0}"); });
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ black.kind.Contraband.3,Contraband (high)
|
|||||||
black.kind.Tech,High-tech (markup)
|
black.kind.Tech,High-tech (markup)
|
||||||
nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open."
|
nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open."
|
||||||
item.web3guide.name,Web3 Handbook
|
item.web3guide.name,Web3 Handbook
|
||||||
item.web3guide.desc,"Bank: depositing turns credits into CRC in your Poinbase account; withdrawing turns CRC back into credits and goes through compliance screening, which looks at the Poinbase account's taint. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Hardware wallets: on-chain coins sit in PLEDGER hardware wallets, each with its own balances and taint; open and connect them on the terminal's Wallets page. The Web3 page's trading, black market and mixer use the connected wallet, and coin-paying customers and mining rewards go into it too. Payments (black market, mixer, Ponero desk, listing fees) start as an invoice on the terminal; type the payee address on the wallet and hold ✓ to sign. The last two characters of an address are a checksum, so a one-character typo is usually caught; once in a while it happens to be valid and the coins go to an address nobody uses. DEX swaps also need a signature on the wallet. RatDigg miners (M1 / M2 / M3) are standalone machines with far more hash than a card and a lower power draw per hash; they need a battery, take modules and use no rig slots. Only the black market's miner dealers sell them, gated by black-market standing (M1 40, M2 60, M3 80), sometimes second-hand. When one burns out, repair it by putting electronic components in its repair slot in order; it comes back 20 health lower. Somewhere between day 50 and day 90 a "miner era" arrives, announced in the paper a few days earlier: the big farms halve their power costs, network hash climbs, GPU miners are diluted and low-end cards barely earn, while miners still do fine. Perps: the Perps tab on Poinbase uses your account CRC as margin, up to 5× on PSC and 3× on SPARK, one position per product, never long and short at once. Fills are worse than the quote (more so for bigger orders, like the spot pool) and opening and closing both cost a fee. Liquidation is checked only overnight against the day's average price: equity under 10% of notional wipes the margin. There is also a nightly funding payment; when the price is above its 7-day average, longs pay shorts. If a liquidation loses more than the margin, the insurance fund covers the gap; when it runs dry (a SPARK collapse can do that) winning positions are auto-deleveraged and give up half their profit; anything beyond that becomes your debt. Debt grows 2% a day, is repaid first from CRC in the account every night, blocks new positions and cash-outs, and after seven days items in your shop are seized to cover it; whatever is still short is written off and the perp account is banned for 30 days. Poinbase is a custodial account and can withdraw straight to the connected wallet; depositing from a wallet means sending to Poinbase's deposit address, which takes only CRC and PSC. More hardware wallets are sold by Dr. Jackson; drag one onto the trading terminal to add it, up to 5. The black market has cheaper ones. A wallet you no longer want can be destroyed on the Wallets page once its coins are moved out. Transfers pay a fee: slow, normal or fast, and gas prices rise and fall over roughly a week; when the network is congested fees rise and slow transactions may be dropped from the mempool. Check the full address before sending. Staking: CRA staked in the Revolution pool earns a daily rate that compounds into the stake; withdrawing means requesting first and waiting four days, with no interest while waiting and no way to take it early. The pool pays from a subsidy fund; when it runs low the rate is cut and word of big holders pulling out may spread. Treat that as a warning: once it collapses CRA is worthless within three days and four days of unlocking is too slow. The balance token SPARK can be minted and burned against CRA at the going price (with a fee); its price swings hard and crashes with the collapse. Staking, minting and burning are contract calls and need a signature on the wallet. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; merchants sell by type at their own price factor, 5+ items get 5% off and 10+ get 10% off, and a bulk merchant appears at random with the lowest prices but six days of waiting; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometLine truncated
|
item.web3guide.desc,"Bank: depositing turns credits into CRC in your Poinbase account; withdrawing turns CRC back into credits and goes through compliance screening, which looks at the Poinbase account's taint. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Hardware wallets: on-chain coins sit in PLEDGER hardware wallets, each with its own balances and taint; open and connect them on the terminal's Wallets page. The Web3 page's trading, black market and mixer use the connected wallet, and coin-paying customers and mining rewards go into it too. Payments (black market, mixer, Ponero desk, listing fees) start as an invoice on the terminal; type the payee address on the wallet and hold ✓ to sign. The last two characters of an address are a checksum, so a one-character typo is usually caught; once in a while it happens to be valid and the coins go to an address nobody uses. DEX swaps also need a signature on the wallet. RatDigg miners (M1 / M2 / M3) are standalone machines with far more hash than a card and a lower power draw per hash; they need a battery, take modules and use no rig slots. Only the black market's miner dealers sell them, gated by black-market standing (M1 40, M2 60, M3 80), sometimes second-hand. When one burns out, repair it by putting electronic components in its repair slot in order; it comes back 20 health lower. Somewhere between day 50 and day 90 a "miner era" arrives, announced in the paper a few days earlier: the big farms halve their power costs, network hash climbs, GPU miners are diluted and low-end cards barely earn, while miners still do fine. Perps: the Perps tab on Poinbase uses your account CRC as margin, up to 5× on PSC and 3× on SPARK, one position per product, never long and short at once. Fills are worse than the quote (more so for bigger orders, like the spot pool) and opening and closing both cost a fee. Liquidation is checked only overnight against the day's average price: equity under 10% of notional wipes the margin. There is also a nightly funding payment; when the price is above its 7-day average, longs pay shorts. If a liquidation loses more than the margin, the insurance fund covers the gap; when it runs dry (a SPARK collapse can do that) winning positions are auto-deleveraged and give up half their profit; anything beyond that becomes your debt. Debt grows 2% a day, is repaid first from CRC in the account every night, blocks new positions and cash-outs, and after seven days items in your shop are seized to cover it; whatever is still short is written off and the perp account is banned for 30 days. Poinbase is a custodial account and can withdraw straight to the connected wallet; depositing from a wallet means sending to Poinbase's deposit address, which takes only CRC and PSC. More hardware wallets are sold by Dr. Jackson; drag one onto the trading terminal to add it, up to 5. The black market has cheaper ones. A wallet you no longer want can be destroyed on the Wallets page once its coins are moved out. Transfers pay a fee: slow, normal or fast, and gas prices rise and fall over roughly a week; when the network is congested fees rise and slow transactions may be dropped from the mempool. Check the full address before sending. Staking: CRA staked in the Revolution pool earns a daily rate that compounds into the stake; withdrawing means requesting first and waiting four days, with no interest while waiting and no way to take it early. The pool pays from a subsidy fund; when it runs low the rate is cut and word of big holders pulling out may spread. Treat that as a warning: once it collapses CRA is worthless within three days and four days of unlocking is too slow. The balance token SPARK can be minted and burned against CRA at the going price (with a fee); its price swings hard and crashes with the collapse. Staking, minting and burning are contract calls and need a signature on the wallet. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; merchants sell by type at their own price factor, 5+ items get 5% off and 10+ get 10% off, and a bulk merchant appears at random with the lowest prices but six days of waiting; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometLine truncated
|
||||||
courier.odd1,...Package is here.
|
courier.odd1,...Package is here.
|
||||||
courier.odd2,"(His hand stays in his pocket.) Sign here."
|
courier.odd2,"(His hand stays in his pocket.) Sign here."
|
||||||
courier.sign,Sign
|
courier.sign,Sign
|
||||||
@@ -535,3 +535,48 @@ news.erawarn.title,RatDigg is about to launch a new generation of miners
|
|||||||
news.erawarn.desc,"The dedicated-miner maker has announced new hardware, and the industry expects big farms to swap out en masse before long. Some say the good days of GPU mining are numbered."
|
news.erawarn.desc,"The dedicated-miner maker has announced new hardware, and the industry expects big farms to swap out en masse before long. Some say the good days of GPU mining are numbered."
|
||||||
news.era.title,The miner era
|
news.era.title,The miner era
|
||||||
news.era.desc,A new generation of dedicated miners is in mass production and the big farms have halved their power costs. Network hash will climb and GPU miners' returns will be diluted.
|
news.era.desc,A new generation of dedicated miners is in mass production and the big farms have halved their power costs. Network hash will climb and GPU miners' returns will be diluted.
|
||||||
|
web3.sub.predict,Predict
|
||||||
|
predict.h,Prediction markets
|
||||||
|
predict.acct,Poinbase CRC: {0}
|
||||||
|
predict.none,No open markets today.
|
||||||
|
predict.closed,closed
|
||||||
|
predict.row,Yes {0}% No {1}%
|
||||||
|
predict.held,holding {0} x{1}
|
||||||
|
predict.yes,Yes
|
||||||
|
predict.no,No
|
||||||
|
predict.void,void
|
||||||
|
predict.result,Day {0} {1} result: {2} {3}
|
||||||
|
predict.pick,Trade
|
||||||
|
predict.price,Yes {0}% No {1}%
|
||||||
|
predict.pos,"Position {0} x{1}, cost {2}"
|
||||||
|
predict.pos2,"Selling all now returns {0}"
|
||||||
|
predict.closed.note,"Closed, waiting for settlement."
|
||||||
|
predict.preview,"Buy {0}: about {1} shares, net {2} if right"
|
||||||
|
predict.buy.yes,Buy Yes
|
||||||
|
predict.buy.no,Buy No
|
||||||
|
predict.sell,Sell all
|
||||||
|
predict.bought,Bought {0} x{1} for {2}.
|
||||||
|
predict.sold,"Sold, received {0}."
|
||||||
|
predict.err.Closed,This market is closed.
|
||||||
|
predict.err.Amount,Invalid amount.
|
||||||
|
predict.err.Funds,Not enough CRC in the Poinbase account.
|
||||||
|
predict.err.Cap,Over this market's position limit.
|
||||||
|
predict.err.Opposite,You hold the other side; sell it first.
|
||||||
|
predict.err.NoPosition,No position in this market.
|
||||||
|
predict.win.one,Day {0}
|
||||||
|
predict.win.range,Days {0}-{1}
|
||||||
|
predict.q.event,"{0}: ""{1}"" happens"
|
||||||
|
predict.q.faction,{0}: {1} starts an operation
|
||||||
|
predict.q.purity,{0}: {1} purity >= {2}
|
||||||
|
predict.q.price,{0}: {1} price >= {2}
|
||||||
|
predict.faction.FACTION_SECURITY,Security
|
||||||
|
predict.faction.FACTION_REVOLUTION,Revolution
|
||||||
|
predict.faction.FACTION_LOWER_LEVEL,Lower level
|
||||||
|
predict.faction.FACTION_UPPER_LEVEL,Upper level
|
||||||
|
predict.faction.FACTION_BLACK_MARKET,Black market
|
||||||
|
predict.purity.gutter,Lower water
|
||||||
|
predict.purity.rust,Upper water
|
||||||
|
nightlog.predict.win,"Prediction right: ""{0}"", paid out {1}."
|
||||||
|
nightlog.predict.lose,"Prediction wrong: ""{0}"", stake {1} lost."
|
||||||
|
nightlog.predict.void,"Prediction void: ""{0}"", stake {1} refunded."
|
||||||
|
nightlog.predict.caught,"Insider bet caught: ""{0}"", profit {1} confiscated, fraud on record."
|
||||||
@@ -267,7 +267,7 @@ black.kind.Contraband.3,违禁品(高)
|
|||||||
black.kind.Tech,高科技物品(加价)
|
black.kind.Tech,高科技物品(加价)
|
||||||
nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。"
|
nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。"
|
||||||
item.web3guide.name,Web3 手册
|
item.web3guide.name,Web3 手册
|
||||||
item.web3guide.desc,"银行:存入把信用点换成 Poinbase 账户里的 CRC;提取把 CRC 换回信用点,要过合规审查,审查看 Poinbase 账户的污点。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。硬件钱包:链上的币放在 PLEDGER 硬件钱包里,每个钱包的余额和污点各算各的;在终端的钱包页打开、连接。Web3 页的交易、黑市、混币器用已连接的钱包,用币付款的顾客和挖矿收益也进它。付款(黑市、混币器、Ponero 兑换处、上架费)先在终端开账单,再在钱包里手输收款地址、按住 ✓ 签名;地址末尾两位是校验位,输错一位多半会被拦下,偶尔碰巧合法,币就转进了没人用的地址。DEX 兑换也要在钱包上签名。Poinbase 是托管账户,可以直接提到已连接的钱包;从钱包充值要转到 Poinbase 的充值地址,只收 CRC 和 PSC。更多硬件钱包向杰克逊博士购买,拖到交易终端上添加,最多 5 个;黑市上也有更便宜的。不要的钱包可以在钱包页销毁,要先把币转走。转账要付手续费,分慢、普通、快三档,gas 价格大约一周一个起落;网络拥堵时手续费变贵,慢档可能被挤出内存池。转账前核对完整地址。质押:把 CRA 质押进革命军质押池每天有日息,利息并入本金;取出要先申请,等四天才到账,等待期间没有利息,也不能提前拿。质押池靠补贴池付息,补贴池见底会降息,有时还会传出大户撤出的消息,那是预兆:一旦崩盘,三天内 CRA 就一文不值,四天的解锁期来不及。平衡代币 SPARK 可以和 CRA 按价格互相铸销(收手续费),价格波动很大,崩盘时会跟着暴跌。质押、铸销都是合约调用,要在钱包上签名。RatDigg 矿机(M1 / M2 / M3)是独立的机器,算力比显卡大得多、耗电更省,要放电池才挖,可以放模块,不占矿架槽位;只在黑市的矿机商那里有,要黑市声望(M1 40、M2 60、M3 80),偶尔有二手的。暴毙后要往修理槽里放电子元件按顺序修好,修好健康少 20。第 50 到 90 天之间会有一次"矿机时代",提前几天上报纸预告,之后大矿场的耗电成本砍半,全网算力涨上去,显卡矿工的收益被摊薄,低档显卡几乎不赚钱,矿机还行。永续合约:Poinbase 的"永续"页,用账户里的 CRC 当保证金,PSC 最高 5 倍、SPARK 最高 3 倍,一个品种只有一个仓位,不能同时多空。成交价比现价差一截(单越大差得越多,和现货池一样),开平仓还收手续费。强平只在夜里按当天均价检查,权益不到名义价值的 10% 就被强平,保证金没了;每晚还有资金费,价格比近 7 天均价高时多头付给空头。强平后亏光保证金还不够的缺口,先由保险基金垫,基金见底(SPARK 崩盘这类大行情会抽干它)后盈利的仓位会被自动减仓、少拿一半盈利,再不够就记成你的欠款:欠款每天涨 2%,账户里的 CRC 每晚先拿去还,没还清不能开仓、不能出金,七天不还就没收店里的物品抵债,抵不够的核销并拉黑永续账户 30 天。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;商家按类型卖货,价格系数各不相同,一次订 5 件以上 95 折、10 件以上 9 折,大宗商家随机出现,价格最低但要等六天;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的卖货页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高,近 7 天出售的量越大,污点越重;同时最多上架 10 件,上架收 5% 上架费,上架后可以改价(收 2% 改价费,标价按参考价重算,成交的快慢跟着新倍率走),成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了Line truncated
|
item.web3guide.desc,"银行:存入把信用点换成 Poinbase 账户里的 CRC;提取把 CRC 换回信用点,要过合规审查,审查看 Poinbase 账户的污点。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。硬件钱包:链上的币放在 PLEDGER 硬件钱包里,每个钱包的余额和污点各算各的;在终端的钱包页打开、连接。Web3 页的交易、黑市、混币器用已连接的钱包,用币付款的顾客和挖矿收益也进它。付款(黑市、混币器、Ponero 兑换处、上架费)先在终端开账单,再在钱包里手输收款地址、按住 ✓ 签名;地址末尾两位是校验位,输错一位多半会被拦下,偶尔碰巧合法,币就转进了没人用的地址。DEX 兑换也要在钱包上签名。Poinbase 是托管账户,可以直接提到已连接的钱包;从钱包充值要转到 Poinbase 的充值地址,只收 CRC 和 PSC。更多硬件钱包向杰克逊博士购买,拖到交易终端上添加,最多 5 个;黑市上也有更便宜的。不要的钱包可以在钱包页销毁,要先把币转走。转账要付手续费,分慢、普通、快三档,gas 价格大约一周一个起落;网络拥堵时手续费变贵,慢档可能被挤出内存池。转账前核对完整地址。质押:把 CRA 质押进革命军质押池每天有日息,利息并入本金;取出要先申请,等四天才到账,等待期间没有利息,也不能提前拿。质押池靠补贴池付息,补贴池见底会降息,有时还会传出大户撤出的消息,那是预兆:一旦崩盘,三天内 CRA 就一文不值,四天的解锁期来不及。平衡代币 SPARK 可以和 CRA 按价格互相铸销(收手续费),价格波动很大,崩盘时会跟着暴跌。质押、铸销都是合约调用,要在钱包上签名。RatDigg 矿机(M1 / M2 / M3)是独立的机器,算力比显卡大得多、耗电更省,要放电池才挖,可以放模块,不占矿架槽位;只在黑市的矿机商那里有,要黑市声望(M1 40、M2 60、M3 80),偶尔有二手的。暴毙后要往修理槽里放电子元件按顺序修好,修好健康少 20。第 50 到 90 天之间会有一次"矿机时代",提前几天上报纸预告,之后大矿场的耗电成本砍半,全网算力涨上去,显卡矿工的收益被摊薄,低档显卡几乎不赚钱,矿机还行。永续合约:Poinbase 的"永续"页,用账户里的 CRC 当保证金,PSC 最高 5 倍、SPARK 最高 3 倍,一个品种只有一个仓位,不能同时多空。成交价比现价差一截(单越大差得越多,和现货池一样),开平仓还收手续费。强平只在夜里按当天均价检查,权益不到名义价值的 10% 就被强平,保证金没了;每晚还有资金费,价格比近 7 天均价高时多头付给空头。强平后亏光保证金还不够的缺口,先由保险基金垫,基金见底(SPARK 崩盘这类大行情会抽干它)后盈利的仓位会被自动减仓、少拿一半盈利,再不够就记成你的欠款:欠款每天涨 2%,账户里的 CRC 每晚先拿去还,没还清不能开仓、不能出金,七天不还就没收店里的物品抵债,抵不够的核销并拉黑永续账户 30 天。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;商家按类型卖货,价格系数各不相同,一次订 5 件以上 95 折、10 件以上 9 折,大宗商家随机出现,价格最低但要等六天;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的卖货页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高,近 7 天出售的量越大,污点越重;同时最多上架 10 件,上架收 5% 上架费,上架后可以改价(收 2% 改价费,标价按参考价重算,成交的快慢跟着新倍率走),成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了Line truncated
|
||||||
courier.odd1,……东西到了。
|
courier.odd1,……东西到了。
|
||||||
courier.odd2,"(他的手一直放在口袋里)在这儿签个字。"
|
courier.odd2,"(他的手一直放在口袋里)在这儿签个字。"
|
||||||
courier.sign,签收
|
courier.sign,签收
|
||||||
@@ -534,3 +534,48 @@ news.erawarn.title,RatDigg 新一代矿机即将发布
|
|||||||
news.erawarn.desc,专用矿机厂商宣布新品,业内预计不久后大型矿场会成批换机。有人说显卡挖矿的好日子要到头了。
|
news.erawarn.desc,专用矿机厂商宣布新品,业内预计不久后大型矿场会成批换机。有人说显卡挖矿的好日子要到头了。
|
||||||
news.era.title,矿机时代
|
news.era.title,矿机时代
|
||||||
news.era.desc,新一代专用矿机开始量产,大型矿场的耗电成本砍掉一半。全网算力会涨上去,显卡矿工的收益要被摊薄。
|
news.era.desc,新一代专用矿机开始量产,大型矿场的耗电成本砍掉一半。全网算力会涨上去,显卡矿工的收益要被摊薄。
|
||||||
|
web3.sub.predict,预测
|
||||||
|
predict.h,预测市场
|
||||||
|
predict.acct,Poinbase CRC:{0}
|
||||||
|
predict.none,今天没有开放的盘口。
|
||||||
|
predict.closed,已封盘
|
||||||
|
predict.row,是 {0}% 否 {1}%
|
||||||
|
predict.held,持有「{0}」{1} 份
|
||||||
|
predict.yes,是
|
||||||
|
predict.no,否
|
||||||
|
predict.void,作废
|
||||||
|
predict.result,第 {0} 天 {1} 结果:{2} {3}
|
||||||
|
predict.pick,买卖
|
||||||
|
predict.price,是 {0}% 否 {1}%
|
||||||
|
predict.pos,持仓「{0}」{1} 份,成本 {2}
|
||||||
|
predict.pos2,现在全部卖出可得 {0}
|
||||||
|
predict.closed.note,已封盘,等结算。
|
||||||
|
predict.preview,买「{0}」约 {1} 份,押中净赚 {2}
|
||||||
|
predict.buy.yes,买入「是」
|
||||||
|
predict.buy.no,买入「否」
|
||||||
|
predict.sell,全部卖出
|
||||||
|
predict.bought,买入「{0}」{1} 份,花 {2}。
|
||||||
|
predict.sold,卖出,收回 {0}。
|
||||||
|
predict.err.Closed,这个盘口已经封盘。
|
||||||
|
predict.err.Amount,金额不对。
|
||||||
|
predict.err.Funds,Poinbase 账户里的 CRC 不够。
|
||||||
|
predict.err.Cap,超过这个盘口的持仓上限。
|
||||||
|
predict.err.Opposite,这个盘口已经持有另一边,先全部卖出。
|
||||||
|
predict.err.NoPosition,这个盘口没有持仓。
|
||||||
|
predict.win.one,第 {0} 天
|
||||||
|
predict.win.range,第 {0}–{1} 天
|
||||||
|
predict.q.event,{0}:出现「{1}」
|
||||||
|
predict.q.faction,{0}:{1}有行动开始
|
||||||
|
predict.q.purity,{0}:{1}纯度 ≥ {2}
|
||||||
|
predict.q.price,{0}:{1} 价格 ≥ {2}
|
||||||
|
predict.faction.FACTION_SECURITY,安保派
|
||||||
|
predict.faction.FACTION_REVOLUTION,革命军
|
||||||
|
predict.faction.FACTION_LOWER_LEVEL,下层
|
||||||
|
predict.faction.FACTION_UPPER_LEVEL,上层
|
||||||
|
predict.faction.FACTION_BLACK_MARKET,黑市
|
||||||
|
predict.purity.gutter,下层水
|
||||||
|
predict.purity.rust,上层水
|
||||||
|
nightlog.predict.win,预测押中:「{0}」,兑付 {1}。
|
||||||
|
nightlog.predict.lose,预测押错:「{0}」,本金 {1} 没了。
|
||||||
|
nightlog.predict.void,预测作废:「{0}」,退回本金 {1}。
|
||||||
|
nightlog.predict.caught,内幕下注被查:「{0}」,收益 {1} 没收,记诈骗罪证。
|
||||||
@@ -56,6 +56,7 @@ namespace Decentralized
|
|||||||
int day = StoreStation.GetDayCounter();
|
int day = StoreStation.GetDayCounter();
|
||||||
d.Market.Open(ActiveEvents(), d.Rng, day);
|
d.Market.Open(ActiveEvents(), d.Rng, day);
|
||||||
d.Market.Whale(d.Rng);
|
d.Market.Whale(d.Rng);
|
||||||
|
PredictGame.Morning(d, day);
|
||||||
if (!d.OpeningNewsShown)
|
if (!d.OpeningNewsShown)
|
||||||
{
|
{
|
||||||
News.Show("pd_news_open", L.T("news.open.title"), L.T("news.open.desc"), 2);
|
News.Show("pd_news_open", L.T("news.open.title"), L.T("news.open.desc"), 2);
|
||||||
@@ -181,6 +182,9 @@ namespace Decentralized
|
|||||||
d.Network.Night(day, d.Market.Price, hash);
|
d.Network.Night(day, d.Market.Price, hash);
|
||||||
if (d.Network.Reward < rewardBefore) d.Market.OnHalving(d.Rng);
|
if (d.Network.Reward < rewardBefore) d.Market.OnHalving(d.Rng);
|
||||||
if (d.Network.Reward < rewardBefore) News.Show("pd_news_halving_" + day, L.T("news.halving.title"), L.T("news.halving.desc"), 2);
|
if (d.Network.Reward < rewardBefore) News.Show("pd_news_halving_" + day, L.T("news.halving.title"), L.T("news.halving.desc"), 2);
|
||||||
|
// 预测市场:先登记今晚的观测再结算到期的盘口;行情盘读夜间更新之前的价格
|
||||||
|
var predictEvents = PredictGame.Available(day) || d.Predict.Markets.Count > 0
|
||||||
|
? d.Predict.Night(day, PredictGame.ActiveIds(), m => PredictGame.Outcome(m, d), d.Poinbase, d.Rng) : new List<PredictEvent>();
|
||||||
// 永续:用今天的标记价(当日均价)结算,要在今晚的价格更新之前
|
// 永续:用今天的标记价(当日均价)结算,要在今晚的价格更新之前
|
||||||
var perpEvents = d.Perps.Night(day, new Dictionary<string, double> { ["PSC"] = d.Market.Mark, ["SPARK"] = d.Stable.Spark }, d.Poinbase, Session.Price);
|
var perpEvents = d.Perps.Night(day, new Dictionary<string, double> { ["PSC"] = d.Market.Mark, ["SPARK"] = d.Stable.Spark }, d.Poinbase, Session.Price);
|
||||||
if (perpEvents.Any(e => e.Kind == "seize")) SeizeForDebt(d, day);
|
if (perpEvents.Any(e => e.Kind == "seize")) SeizeForDebt(d, day);
|
||||||
@@ -225,6 +229,17 @@ namespace Decentralized
|
|||||||
case Network.EraStep.Warn: News.Show("pd_news_erawarn_" + day, L.T("news.erawarn.title"), L.T("news.erawarn.desc"), 5); break;
|
case Network.EraStep.Warn: News.Show("pd_news_erawarn_" + day, L.T("news.erawarn.title"), L.T("news.erawarn.desc"), 5); break;
|
||||||
case Network.EraStep.Start: News.Show("pd_news_era_" + day, L.T("news.era.title"), L.T("news.era.desc"), 5); Log.Info("矿机时代到来:NPC 耗电效率翻倍"); break;
|
case Network.EraStep.Start: News.Show("pd_news_era_" + day, L.T("news.era.title"), L.T("news.era.desc"), 5); Log.Info("矿机时代到来:NPC 耗电效率翻倍"); break;
|
||||||
}
|
}
|
||||||
|
foreach (var e in predictEvents)
|
||||||
|
{
|
||||||
|
if (e.Kind == "win") ps.AddNightLog(L.T("nightlog.predict.win", PredictGame.Question(e.Market), Fmt.Credits(e.Amount)));
|
||||||
|
else if (e.Kind == "lose") ps.AddNightLog(L.T("nightlog.predict.lose", PredictGame.Question(e.Market), Fmt.Credits(e.Amount)), "cc7755");
|
||||||
|
else if (e.Kind == "void") ps.AddNightLog(L.T("nightlog.predict.void", PredictGame.Question(e.Market), Fmt.Credits(e.Amount)));
|
||||||
|
else if (e.Kind == "caught")
|
||||||
|
{
|
||||||
|
ps.secData.CommitCrime("FRAUD", (int)Math.Round(e.Fraud));
|
||||||
|
ps.AddNightLog(L.T("nightlog.predict.caught", PredictGame.Question(e.Market), Fmt.Credits(e.Amount)), "cc7755");
|
||||||
|
}
|
||||||
|
}
|
||||||
foreach (var e in perpEvents)
|
foreach (var e in perpEvents)
|
||||||
{
|
{
|
||||||
string who = e.Product + " " + L.T(e.Long ? "perp.long" : "perp.short");
|
string who = e.Product + " " + L.T(e.Long ? "perp.long" : "perp.short");
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Decentralized.Domain;
|
||||||
|
|
||||||
|
namespace Decentralized
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 预测市场的接入层:按游戏当前状态给出候选盘口,夜间读游戏事件、派系行动、水纯度和本 mod 价格来结算,判断下注时玩家是否已经知道答案。
|
||||||
|
/// </summary>
|
||||||
|
public static class PredictGame
|
||||||
|
{
|
||||||
|
public static readonly string[] Factions = { "FACTION_SECURITY", "FACTION_REVOLUTION", "FACTION_LOWER_LEVEL", "FACTION_UPPER_LEVEL", "FACTION_BLACK_MARKET" };
|
||||||
|
private static readonly double[] GutterCuts = { 8200, 8400, 8600, 8800 }, RustCuts = { 9030, 9060, 9090, 9120 };
|
||||||
|
private static readonly double[] PriceMult = { 0.9, 0.95, 1.0, 1.05, 1.1 };
|
||||||
|
|
||||||
|
public static bool Available(int day) => Session.Data != null && Unlock.IsUnlocked && day >= Tuning.PredictUnlockDay;
|
||||||
|
|
||||||
|
/// <summary>夜间观测用的标识:生效中的事件,以及派系行动的 "F:派系"。</summary>
|
||||||
|
public static List<string> ActiveIds()
|
||||||
|
{
|
||||||
|
var list = new List<string>();
|
||||||
|
var mgr = StoreStation.Instance?.storeEventManager;
|
||||||
|
if (mgr == null) return list;
|
||||||
|
foreach (var e in mgr.GetActiveEvents())
|
||||||
|
{
|
||||||
|
list.Add(e.identifier);
|
||||||
|
if (e.isFactionOperation && !string.IsNullOrEmpty(e.sourceFaction)) list.Add("F:" + e.sourceFaction);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PredictCandidate> Candidates(int day)
|
||||||
|
{
|
||||||
|
var d = Session.Data;
|
||||||
|
var list = new List<PredictCandidate>();
|
||||||
|
var mgr = StoreStation.Instance?.storeEventManager;
|
||||||
|
if (mgr == null) return list;
|
||||||
|
// 常规事件:开门时先过"今天有没有事件"(5 天后才开始,有 0/1/2 件在身上时 50/35/25%),再按权重抽
|
||||||
|
int counter = mgr.GetEventCounter();
|
||||||
|
double dayChance = counter == 0 ? 0.50 : counter == 1 ? 0.35 : counter == 2 ? 0.25 : 0;
|
||||||
|
var pool = StoreEventManager.normalEventBlueprints.Where(e => !mgr.IsEventOnCooldown(e.identifier)).ToList();
|
||||||
|
double total = pool.Sum(e => (double)e.odd);
|
||||||
|
foreach (var bp in pool)
|
||||||
|
{
|
||||||
|
if (mgr.IsEventActive(bp.identifier)) continue;
|
||||||
|
string name;
|
||||||
|
try { name = bp.storeEventFunc().GetDisplayName(); } catch (Exception) { continue; }
|
||||||
|
double odd = bp.odd;
|
||||||
|
list.Add(new PredictCandidate { Kind = PredictKind.Event, Key = bp.identifier, Title = name, Prob = (from, to) => PredictMath.EventProb(dayChance, odd, total, to - from + 1) });
|
||||||
|
}
|
||||||
|
foreach (var f in Factions)
|
||||||
|
{
|
||||||
|
if (mgr.GetActiveEvents().Any(e => e.isFactionOperation && e.sourceFaction == f)) continue;
|
||||||
|
list.Add(new PredictCandidate { Kind = PredictKind.Faction, Key = f, Title = L.T("predict.faction." + f), Prob = (from, to) => PredictMath.FactionProb(to - from + 1) });
|
||||||
|
}
|
||||||
|
foreach (var t in GutterCuts)
|
||||||
|
{
|
||||||
|
double thr = t;
|
||||||
|
list.Add(new PredictCandidate { Kind = PredictKind.Purity, Key = "gutter", Title = L.T("predict.purity.gutter"), Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.PurityAtLeast(thr, 8000, 8999) });
|
||||||
|
}
|
||||||
|
foreach (var t in RustCuts)
|
||||||
|
{
|
||||||
|
double thr = t;
|
||||||
|
list.Add(new PredictCandidate { Kind = PredictKind.Purity, Key = "rust", Title = L.T("predict.purity.rust"), Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.PurityAtLeast(thr, 9000, 9150) });
|
||||||
|
}
|
||||||
|
double price = d.Market.Price, anchor = d.Market.Anchor(d.ActiveEvents);
|
||||||
|
foreach (var mult in PriceMult)
|
||||||
|
{
|
||||||
|
double thr = Math.Round(price * mult, price >= 100 ? 0 : 1);
|
||||||
|
list.Add(new PredictCandidate { Kind = PredictKind.Price, Key = "PSC", Title = "PSC", Param = thr, Prob = (from, to) => PredictMath.PriceAtLeast(price, anchor, Tuning.PscVol, Tuning.Revert, to - day, thr) });
|
||||||
|
}
|
||||||
|
// 价格盘一次只留一个(阈值不同的盘口会互相对冲)
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>开门后补盘。</summary>
|
||||||
|
public static void Morning(SaveData d, int day)
|
||||||
|
{
|
||||||
|
if (!Available(day)) return;
|
||||||
|
var made = d.Predict.Refill(day, d.Rng, Candidates(day).Where(c => c.Kind != PredictKind.Price || !d.Predict.Markets.Any(m => m.Outcome < 0 && m.Kind == PredictKind.Price)).ToList());
|
||||||
|
foreach (var m in made) Log.Info($"预测盘口 #{m.Id}:{m.Kind} {m.Key} {m.Title} 阈值 {m.Param} 窗口 {m.FromDay}-{m.ToDay} 先验 {m.P0:0.000}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>夜间结算的结果:事件、派系读观测;纯度读当天的数值;行情读夜间更新之前的价格。</summary>
|
||||||
|
public static bool? Outcome(PredictMarket m, SaveData d)
|
||||||
|
{
|
||||||
|
switch (m.Kind)
|
||||||
|
{
|
||||||
|
case PredictKind.Event: case PredictKind.Faction: return d.Predict.Happened(m);
|
||||||
|
case PredictKind.Purity:
|
||||||
|
var st = StoreStation.Instance;
|
||||||
|
return st == null ? (bool?)null : (m.Key == "gutter" ? st.todayGutterflowPurity : st.todayRustwaterPurity) >= m.Param;
|
||||||
|
case PredictKind.Price: return d.Market.Price >= m.Param;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>下注时玩家是否已经知道答案:这个事件(或派系行动)已经排进日程、玩家看得到(传闻纸条、派系特权),而且落在窗口里。只有押"会发生"才算内幕单。</summary>
|
||||||
|
public static bool Known(PredictMarket m, bool yes)
|
||||||
|
{
|
||||||
|
if (!yes || (m.Kind != PredictKind.Event && m.Kind != PredictKind.Faction)) return false;
|
||||||
|
var mgr = StoreStation.Instance?.storeEventManager;
|
||||||
|
if (mgr == null) return false;
|
||||||
|
int today = StoreStation.GetDayCounter();
|
||||||
|
for (int i = 0; i < mgr.futurEvents.Count; i++)
|
||||||
|
{
|
||||||
|
var e = mgr.futurEvents[i];
|
||||||
|
if (!e.IsKnown) continue;
|
||||||
|
bool match = m.Kind == PredictKind.Event ? e.identifier == m.Key : e.isFactionOperation && e.sourceFaction == m.Key;
|
||||||
|
int start = today + mgr.futurEventsDay[i];
|
||||||
|
if (match && start >= m.FromDay && start <= m.ToDay) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Question(PredictMarket m)
|
||||||
|
{
|
||||||
|
string win = m.FromDay == m.ToDay ? L.T("predict.win.one", m.FromDay) : L.T("predict.win.range", m.FromDay, m.ToDay);
|
||||||
|
switch (m.Kind)
|
||||||
|
{
|
||||||
|
case PredictKind.Event: return L.T("predict.q.event", win, m.Title);
|
||||||
|
case PredictKind.Faction: return L.T("predict.q.faction", win, m.Title);
|
||||||
|
case PredictKind.Purity: return L.T("predict.q.purity", win, m.Title, Fmt.Num(m.Param));
|
||||||
|
default: return L.T("predict.q.price", win, m.Title, Fmt.Num(m.Param));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,8 @@ namespace Decentralized
|
|||||||
public Staking Staking = new Staking();
|
public Staking Staking = new Staking();
|
||||||
/// <summary>Poinbase 永续合约账本(保证金用 Poinbase 账户的 CRC)。</summary>
|
/// <summary>Poinbase 永续合约账本(保证金用 Poinbase 账户的 CRC)。</summary>
|
||||||
public PerpBook Perps = new PerpBook();
|
public PerpBook Perps = new PerpBook();
|
||||||
|
/// <summary>预测市场:盘口、持仓、观测记录(下注资金用 Poinbase 账户的 CRC)。</summary>
|
||||||
|
public PredictBook Predict = new PredictBook();
|
||||||
/// <summary>质押日息下调的新闻已经发过。</summary>
|
/// <summary>质押日息下调的新闻已经发过。</summary>
|
||||||
public bool CraLowNews;
|
public bool CraLowNews;
|
||||||
public Network Network = new Network();
|
public Network Network = new Network();
|
||||||
|
|||||||
@@ -349,6 +349,93 @@ namespace Decentralized
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int _predPick;
|
||||||
|
private static readonly float[] PredW = { 330f, 90f };
|
||||||
|
|
||||||
|
/// <summary>预测页:左边盘口列表和最近结果,右边选中盘口的买卖。资金走 Poinbase 的 CRC 账户,不用签名。</summary>
|
||||||
|
private static void PredictPage(CustomUIBuilder b)
|
||||||
|
{
|
||||||
|
var d = Session.Data;
|
||||||
|
var bk = d.Predict;
|
||||||
|
var acct = d.Poinbase;
|
||||||
|
int day = StoreStation.GetDayCounter();
|
||||||
|
var open = bk.Markets.Where(m => m.Outcome < 0).OrderBy(m => m.FromDay).ThenBy(m => m.Id).ToList();
|
||||||
|
var sel = open.Find(m => m.Id == _predPick) ?? open.FirstOrDefault();
|
||||||
|
_predPick = sel?.Id ?? 0;
|
||||||
|
Columns(b, l =>
|
||||||
|
{
|
||||||
|
l.AddHeader(L.T("predict.h"));
|
||||||
|
l.AddLabel(L.T("predict.acct", Fmt.Num(acct.Avail(Assets.CRC))));
|
||||||
|
l.AddSeparator();
|
||||||
|
if (open.Count == 0) l.AddLabel(L.T("predict.none"));
|
||||||
|
foreach (var m in open)
|
||||||
|
{
|
||||||
|
var mk = m;
|
||||||
|
var pos = bk.PosOf(m.Id);
|
||||||
|
string q = PredictGame.Question(m);
|
||||||
|
l.BeginRow(4f);
|
||||||
|
l.AddButton(m.Id == _predPick ? "[" + q + "]" : q, () => { _predPick = mk.Id; _message = null; Open(); });
|
||||||
|
l.End();
|
||||||
|
string state = m.IsOpen(day) ? "" : " " + L.T("predict.closed");
|
||||||
|
l.AddLabel(L.T("predict.row", (m.PYes * 100).ToString("0"), ((1 - m.PYes) * 100).ToString("0")) + (pos != null ? " " + L.T("predict.held", L.T(pos.Yes ? "predict.yes" : "predict.no"), Fmt.Num(pos.Shares)) : "") + state);
|
||||||
|
}
|
||||||
|
if (bk.Recent.Count > 0)
|
||||||
|
{
|
||||||
|
l.AddSeparator();
|
||||||
|
foreach (var r in Enumerable.Reverse(bk.Recent))
|
||||||
|
l.AddLabel(L.T("predict.result", r.Day, r.Title, r.Outcome == 2 ? L.T("predict.void") : L.T(r.Outcome == 1 ? "predict.yes" : "predict.no"), r.Held ? (r.Pnl >= 0 ? "+" : "") + Fmt.Num(r.Pnl) : "—"));
|
||||||
|
}
|
||||||
|
}, r =>
|
||||||
|
{
|
||||||
|
if (sel == null) { r.AddHeader(L.T("predict.h")); return; }
|
||||||
|
r.AddHeader(L.T("predict.pick"));
|
||||||
|
r.AddLabel(PredictGame.Question(sel));
|
||||||
|
r.AddLabel(L.T("predict.price", (sel.PYes * 100).ToString("0.0"), ((1 - sel.PYes) * 100).ToString("0.0")));
|
||||||
|
var pos = bk.PosOf(sel.Id);
|
||||||
|
if (pos != null)
|
||||||
|
{
|
||||||
|
r.AddLabel(L.T("predict.pos", L.T(pos.Yes ? "predict.yes" : "predict.no"), Fmt.Num(pos.Shares), Fmt.Num(pos.Cost)));
|
||||||
|
r.AddLabel(L.T("predict.pos2", Fmt.Num(sel.SellValue(pos.Yes, pos.Shares) * (1 - Tuning.PredictFee))));
|
||||||
|
}
|
||||||
|
if (!sel.IsOpen(day)) { r.AddLabel(L.T("predict.closed.note")); return; }
|
||||||
|
AmountRow(r);
|
||||||
|
if (double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out double v) && v > 0 && v < 1e7)
|
||||||
|
{
|
||||||
|
foreach (bool yes in new[] { true, false })
|
||||||
|
{
|
||||||
|
double shares = sel.SharesFor(yes, v / (1 + Tuning.PredictFee));
|
||||||
|
r.AddLabel(L.T("predict.preview", L.T(yes ? "predict.yes" : "predict.no"), Fmt.Num(shares), Fmt.Num(shares - v)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.BeginRow();
|
||||||
|
r.AddButton(L.T("predict.buy.yes"), () => PredictBuy(sel, true));
|
||||||
|
r.AddButton(L.T("predict.buy.no"), () => PredictBuy(sel, false));
|
||||||
|
r.End();
|
||||||
|
r.AddButton(L.T("predict.sell"), () => PredictSell(sel));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PredictBuy(PredictMarket m, bool yes)
|
||||||
|
{
|
||||||
|
if (!ReadAmount(out double v)) return;
|
||||||
|
var d = Session.Data;
|
||||||
|
int day = StoreStation.GetDayCounter();
|
||||||
|
bool insider = PredictGame.Known(m, yes);
|
||||||
|
var res = d.Predict.Buy(d.Poinbase, m, yes, v, day, Session.Price, insider, out double shares);
|
||||||
|
_message = res == PredictResult.Ok ? L.T("predict.bought", L.T(yes ? "predict.yes" : "predict.no"), Fmt.Num(shares), Fmt.Num(v)) : L.T("predict.err." + res);
|
||||||
|
if (res == PredictResult.Ok) { Log.Info($"预测下注 #{m.Id} {(yes ? "是" : "否")} {shares:0.0} 份,花 {v:0.##},内幕 {insider}"); Session.Save(); }
|
||||||
|
Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PredictSell(PredictMarket m)
|
||||||
|
{
|
||||||
|
var d = Session.Data;
|
||||||
|
var res = d.Predict.SellAll(d.Poinbase, m, StoreStation.GetDayCounter(), out double got);
|
||||||
|
_message = res == PredictResult.Ok ? L.T("predict.sold", Fmt.Num(got)) : L.T("predict.err." + res);
|
||||||
|
if (res == PredictResult.Ok) Session.Save();
|
||||||
|
Open();
|
||||||
|
}
|
||||||
|
|
||||||
private static string PerpErr(PerpResult res) => L.T("perp.err." + res);
|
private static string PerpErr(PerpResult res) => L.T("perp.err." + res);
|
||||||
|
|
||||||
private static void PerpOpen()
|
private static void PerpOpen()
|
||||||
@@ -494,16 +581,18 @@ namespace Decentralized
|
|||||||
{
|
{
|
||||||
if (_web3Tab == Web3Tab.Mixer && !Unlock.MixerUnlocked) _web3Tab = Web3Tab.Trade;
|
if (_web3Tab == Web3Tab.Mixer && !Unlock.MixerUnlocked) _web3Tab = Web3Tab.Trade;
|
||||||
if (_web3Tab == Web3Tab.Black && !Unlock.BlackUnlocked) _web3Tab = Web3Tab.Trade;
|
if (_web3Tab == Web3Tab.Black && !Unlock.BlackUnlocked) _web3Tab = Web3Tab.Trade;
|
||||||
|
if (_web3Tab == Web3Tab.Predict && !PredictGame.Available(StoreStation.GetDayCounter())) _web3Tab = Web3Tab.Trade;
|
||||||
b.BeginRow();
|
b.BeginRow();
|
||||||
foreach (Web3Tab t in Enum.GetValues(typeof(Web3Tab)))
|
foreach (Web3Tab t in Enum.GetValues(typeof(Web3Tab)))
|
||||||
{
|
{
|
||||||
if ((t == Web3Tab.Mixer && !Unlock.MixerUnlocked) || (t == Web3Tab.Black && !Unlock.BlackUnlocked)) continue;
|
if ((t == Web3Tab.Mixer && !Unlock.MixerUnlocked) || (t == Web3Tab.Black && !Unlock.BlackUnlocked) || (t == Web3Tab.Predict && !PredictGame.Available(StoreStation.GetDayCounter()))) continue;
|
||||||
var tab = t;
|
var tab = t;
|
||||||
string name = L.T("web3.sub." + t.ToString().ToLowerInvariant());
|
string name = L.T("web3.sub." + t.ToString().ToLowerInvariant());
|
||||||
b.AddButton(tab == _web3Tab ? $"[{name}]" : name, () => { _web3Tab = tab; _message = null; _edit = null; Open(); });
|
b.AddButton(tab == _web3Tab ? $"[{name}]" : name, () => { _web3Tab = tab; _message = null; _edit = null; Open(); });
|
||||||
}
|
}
|
||||||
b.End();
|
b.End();
|
||||||
if (_web3Tab == Web3Tab.Stake) { StakePage(b); return; }
|
if (_web3Tab == Web3Tab.Stake) { StakePage(b); return; }
|
||||||
|
if (_web3Tab == Web3Tab.Predict) { PredictPage(b); return; }
|
||||||
if (_web3Tab == Web3Tab.Mining) { ReportPage(b); return; }
|
if (_web3Tab == Web3Tab.Mining) { ReportPage(b); return; }
|
||||||
if (_web3Tab == Web3Tab.Mixer) { MixerPage(b); return; }
|
if (_web3Tab == Web3Tab.Mixer) { MixerPage(b); return; }
|
||||||
if (_web3Tab == Web3Tab.Black) { BlackPage(b); return; }
|
if (_web3Tab == Web3Tab.Black) { BlackPage(b); return; }
|
||||||
@@ -968,7 +1057,7 @@ namespace Decentralized
|
|||||||
Open();
|
Open();
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Web3Tab { Trade, Stake, Mining, Mixer, Black }
|
private enum Web3Tab { Trade, Stake, Predict, Mining, Mixer, Black }
|
||||||
private static Web3Tab _web3Tab = Web3Tab.Trade;
|
private static Web3Tab _web3Tab = Web3Tab.Trade;
|
||||||
private static string _mixAsset = Assets.PSC;
|
private static string _mixAsset = Assets.PSC;
|
||||||
private static int _mixRounds = 2;
|
private static int _mixRounds = 2;
|
||||||
|
|||||||
@@ -1043,6 +1043,149 @@ internal static class Program
|
|||||||
Check(o.State == OrderState.Refunded && Math.Abs(b.Get(Assets.CRT) - bBefore) < 1e-6 && a.Get(Assets.CRT) == aBefore, $"骗子托管退款回 B:{o.State}");
|
Check(o.State == OrderState.Refunded && Math.Abs(b.Get(Assets.CRT) - bBefore) < 1e-6 && a.Get(Assets.CRT) == aBefore, $"骗子托管退款回 B:{o.State}");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Test("预测市场:LMSR 定价、最大亏损、买卖", () =>
|
||||||
|
{
|
||||||
|
foreach (double p0 in new[] { 0.5, 0.08, 0.9 })
|
||||||
|
{
|
||||||
|
var m = PredictMarket.Create(1, PredictKind.Event, "x", "t", 0, 10, 11, 12, p0);
|
||||||
|
Check(Math.Abs(m.PYes - p0) < 1e-9, $"初始价 = 先验 {p0}:{m.PYes:0.0000}");
|
||||||
|
Check(Math.Abs(m.MaxLoss - PredictMarket.LossFor(10)) < 1e-6, "最大亏损等于预设");
|
||||||
|
double cost = 100, sh = m.SharesFor(true, cost);
|
||||||
|
Check(Math.Abs(m.BuyCost(true, sh) - cost) < 1e-6, $"闭式反解和成本函数一致({p0})");
|
||||||
|
Check(Math.Abs(m.SellValue(true, 0)) < 1e-9, "卖 0 份收回 0");
|
||||||
|
// 系统最大亏损:玩家买满一边,赢了系统最多亏 MaxLoss
|
||||||
|
double paid = m.BuyCost(true, 5000);
|
||||||
|
Check(5000 - paid <= m.MaxLoss + 1e-6, $"买得再多系统也亏不过最大亏损:{5000 - paid:0.0} ≤ {m.MaxLoss:0.0}");
|
||||||
|
var m2 = PredictMarket.Create(1, PredictKind.Event, "x", "t", 0, 10, 11, 12, p0);
|
||||||
|
double paidNo = m2.BuyCost(false, 5000);
|
||||||
|
Check(5000 - paidNo <= m2.MaxLoss + 1e-6, "反方向同理");
|
||||||
|
// 买了再卖:只损失手续费之外的价差为 0
|
||||||
|
var m3 = PredictMarket.Create(1, PredictKind.Event, "x", "t", 0, 10, 11, 12, p0);
|
||||||
|
double s3 = m3.SharesFor(true, 50);
|
||||||
|
m3.QYes += s3;
|
||||||
|
Check(Math.Abs(m3.SellValue(true, s3) - 50) < 1e-6, "原路卖回收回成本");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Test("预测市场:买入、卖出、手续费、污点、持仓限制", () =>
|
||||||
|
{
|
||||||
|
Func<string, double> pr = a => 1;
|
||||||
|
var acct = new Wallet { Address = "poinbase" };
|
||||||
|
acct.Add(Assets.CRC, 1000); acct.Taint = 400; acct.Basis = 1000;
|
||||||
|
var bk = new PredictBook();
|
||||||
|
var m = PredictMarket.Create(++bk.Seq, PredictKind.Purity, "gutter", "t", 8500, 5, 6, 6, 0.5);
|
||||||
|
bk.Markets.Add(m);
|
||||||
|
Check(bk.Buy(acct, m, true, 0, 5, pr, false, out _) == PredictResult.Amount, "金额为 0 被拒");
|
||||||
|
Check(bk.Buy(acct, m, true, 2000, 5, pr, false, out _) == PredictResult.Funds, "钱不够被拒");
|
||||||
|
Check(bk.Buy(acct, m, true, 100, 6, pr, false, out _) == PredictResult.Closed, "窗口开始后封盘");
|
||||||
|
var res = bk.Buy(acct, m, true, 102, 5, pr, false, out double sh);
|
||||||
|
Check(res == PredictResult.Ok && sh > 0, "买入成功");
|
||||||
|
var pos = bk.PosOf(m.Id);
|
||||||
|
Check(Math.Abs(pos.Cost - 100) < 1e-6 && Math.Abs(acct.Get(Assets.CRC) - 898) < 1e-6, $"成本 100(手续费 2):{pos.Cost:0.00}");
|
||||||
|
Check(Math.Abs(pos.Taint - 40.8) < 1e-6 && Math.Abs(acct.Taint - 359.2) < 1e-6, $"污点按花掉的价值比例带进持仓:{pos.Taint:0.00}");
|
||||||
|
Check(bk.Buy(acct, m, false, 10, 5, pr, false, out _) == PredictResult.Opposite, "不能同盘反方向");
|
||||||
|
// 卖出:价差为 0,只付 2% 手续费
|
||||||
|
double before = acct.Get(Assets.CRC);
|
||||||
|
Check(bk.SellAll(acct, m, 5, out double got) == PredictResult.Ok && Math.Abs(got - 98) < 1e-6, $"原价卖回只扣手续费:{got:0.00}");
|
||||||
|
Check(Math.Abs(acct.Get(Assets.CRC) - before - got) < 1e-6 && bk.PosOf(m.Id) == null, "回到账户");
|
||||||
|
Check(acct.Taint < 400 && acct.Taint > 359.2, "卖回时污点按退回的比例回来");
|
||||||
|
// 持仓上限
|
||||||
|
var big = new Wallet { Address = "x" }; big.Add(Assets.CRC, 1e7);
|
||||||
|
Check(bk.Buy(big, m, true, 1e6, 5, pr, false, out _) == PredictResult.Cap, "超过份额上限被拒");
|
||||||
|
Check(bk.SellAll(acct, m, 5, out _) == PredictResult.NoPosition, "没持仓不能卖");
|
||||||
|
});
|
||||||
|
|
||||||
|
Test("预测市场:结算、作废、内幕被查、观测", () =>
|
||||||
|
{
|
||||||
|
Func<string, double> pr = a => 1;
|
||||||
|
var rng = new Rng(5);
|
||||||
|
var acct = new Wallet { Address = "poinbase" };
|
||||||
|
acct.Add(Assets.CRC, 100000);
|
||||||
|
var bk = new PredictBook();
|
||||||
|
// 观测:第一晚没有 Prev,不算 Started
|
||||||
|
bk.Night(4, new[] { "oxymoreWave" }, m => null, acct, rng);
|
||||||
|
Check(!bk.Started.ContainsKey(4), "第一晚没有对照,不记新出现的事件");
|
||||||
|
var win = PredictMarket.Create(++bk.Seq, PredictKind.Event, "powerShortage", "停电", 0, 4, 5, 6, 0.2);
|
||||||
|
var lose = PredictMarket.Create(++bk.Seq, PredictKind.Event, "blackout", "x", 0, 4, 5, 6, 0.2);
|
||||||
|
var voided = PredictMarket.Create(++bk.Seq, PredictKind.Faction, "FACTION_SECURITY", "镇压", 0, 4, 5, 8, 0.2);
|
||||||
|
bk.Markets.AddRange(new[] { win, lose, voided });
|
||||||
|
bk.Buy(acct, win, true, 51, 4, pr, false, out double sw);
|
||||||
|
bk.Buy(acct, lose, true, 51, 4, pr, false, out _);
|
||||||
|
bk.Buy(acct, voided, true, 51, 4, pr, false, out _);
|
||||||
|
double cash = acct.Get(Assets.CRC);
|
||||||
|
bk.Night(5, new[] { "oxymoreWave", "powerShortage" }, bk.Happened, acct, rng);
|
||||||
|
Check(win.Outcome < 0 && lose.Outcome < 0, "窗口没结束不结算");
|
||||||
|
var evs = bk.Night(6, new[] { "oxymoreWave", "powerShortage" }, bk.Happened, acct, rng);
|
||||||
|
Check(win.Outcome == -1 || bk.Markets.All(x => x.Id != win.Id), "到期的盘口移出列表");
|
||||||
|
Check(evs.Any(e => e.Kind == "win" && e.Title == "停电" && Math.Abs(e.Amount - sw) < 1e-6), "押中兑付份数");
|
||||||
|
Check(evs.Any(e => e.Kind == "lose" && e.Title == "x"), "押错没收");
|
||||||
|
Check(Math.Abs(acct.Get(Assets.CRC) - cash - sw) < 1e-6, "账户只多了赢的那份");
|
||||||
|
Check(bk.Markets.Count == 1 && bk.Markets[0].Id == voided.Id, "后面的窗口还没到");
|
||||||
|
// 缺观测:8 号之前 7、8 的记录缺,到期作废退本金
|
||||||
|
double c2 = acct.Get(Assets.CRC);
|
||||||
|
var ev2 = bk.Night(9, new string[0], bk.Happened, acct, rng);
|
||||||
|
Check(ev2.Any(e => e.Kind == "void") && Math.Abs(acct.Get(Assets.CRC) - c2 - 50) < 1e-6, $"缺数据作废并退本金:{acct.Get(Assets.CRC) - c2:0.00}");
|
||||||
|
// 内幕被查:持仓达到上限的 40% 就有概率;200 次里大约一半
|
||||||
|
int caught = 0, wins = 0;
|
||||||
|
for (int i = 0; i < 400; i++)
|
||||||
|
{
|
||||||
|
var b2 = new PredictBook(); var a2 = new Wallet { Address = "p" }; a2.Add(Assets.CRC, 1e6);
|
||||||
|
var mk = PredictMarket.Create(++b2.Seq, PredictKind.Event, "e", "t", 0, 4, 5, 5, 0.1); b2.Markets.Add(mk);
|
||||||
|
double target = Tuning.PredictInsiderShare * mk.ShareCap * 1.05, spend = 0;
|
||||||
|
spend = (mk.BuyCost(true, target)) * (1 + Tuning.PredictFee);
|
||||||
|
b2.Buy(a2, mk, true, spend, 4, pr, true, out _);
|
||||||
|
var e = b2.Night(5, new string[0], mm => true, a2, new Rng((ulong)(i + 1)));
|
||||||
|
if (e[0].Kind == "caught") { caught++; Check(e[0].Fraud > 0 && e[0].Fraud <= Tuning.PredictFraudCap, "罪证有上限"); }
|
||||||
|
else wins++;
|
||||||
|
}
|
||||||
|
Check(caught > 140 && caught < 260, $"内幕大单约一半被查:{caught}/400");
|
||||||
|
// 小额内幕不会被查
|
||||||
|
var b3 = new PredictBook(); var a3 = new Wallet { Address = "p" }; a3.Add(Assets.CRC, 1e6);
|
||||||
|
var m3 = PredictMarket.Create(++b3.Seq, PredictKind.Event, "e", "t", 0, 4, 5, 5, 0.1); b3.Markets.Add(m3);
|
||||||
|
b3.Buy(a3, m3, true, m3.BuyCost(true, 0.2 * m3.ShareCap) * 1.02, 4, pr, true, out _);
|
||||||
|
Check(b3.Night(5, new string[0], mm => true, a3, new Rng(1))[0].Kind == "win", "小额内幕安全");
|
||||||
|
// 被查:只退本金
|
||||||
|
var b4 = new PredictBook(); var a4 = new Wallet { Address = "p" }; a4.Add(Assets.CRC, 1e6);
|
||||||
|
var m4 = PredictMarket.Create(++b4.Seq, PredictKind.Event, "e", "t", 0, 4, 5, 5, 0.1); b4.Markets.Add(m4);
|
||||||
|
double s4 = m4.BuyCost(true, 0.5 * m4.ShareCap) * 1.02;
|
||||||
|
b4.Buy(a4, m4, true, s4, 4, pr, true, out _);
|
||||||
|
double c4 = a4.Get(Assets.CRC);
|
||||||
|
for (ulong sd = 1; sd < 50; sd++)
|
||||||
|
{
|
||||||
|
var bb = new PredictBook(); bb.Markets.Add(m4); bb.Positions.AddRange(b4.Positions.Select(x => new PredictPos { Market = x.Market, Yes = x.Yes, Shares = x.Shares, Cost = x.Cost, Insider = x.Insider }));
|
||||||
|
var aa = new Wallet { Address = "p" };
|
||||||
|
m4.Outcome = -1;
|
||||||
|
var ee = bb.Night(5, new string[0], mm => true, aa, new Rng(sd));
|
||||||
|
if (ee[0].Kind == "caught") { Check(Math.Abs(aa.Get(Assets.CRC) - (b4.Positions[0].Cost)) < 1e-6, "被查只退本金"); break; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Test("预测市场:先验概率", () =>
|
||||||
|
{
|
||||||
|
Check(Math.Abs(PredictMath.PurityAtLeast(8500, 8000, 8999) - 0.5) < 1e-9, "纯度 8500 以上一半");
|
||||||
|
Check(PredictMath.PurityAtLeast(7000, 8000, 8999) == 1 && PredictMath.PurityAtLeast(9500, 8000, 8999) == 0, "越界取 0 和 1");
|
||||||
|
double p3 = PredictMath.EventProb(0.4, 15, 150, 3);
|
||||||
|
Check(Math.Abs(p3 - (1 - Math.Pow(1 - 0.04, 3))) < 1e-12, "事件概率:每天的概率连乘");
|
||||||
|
Check(PredictMath.FactionProb(1) > 0 && PredictMath.FactionProb(3) > PredictMath.FactionProb(1), "天数越长概率越大");
|
||||||
|
Check(Math.Abs(PredictMath.Phi(0) - 0.5) < 1e-6 && PredictMath.Phi(1.96) > 0.974 && PredictMath.Phi(1.96) < 0.976, "正态分布函数");
|
||||||
|
// 行情先验和 Market 的真实过程对得上
|
||||||
|
foreach (var (mult, nights) in new[] { (1.0, 1), (1.1, 2), (0.9, 3), (1.2, 4) })
|
||||||
|
{
|
||||||
|
int hit = 0, n = 3000;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
var mk = Market.CreateDefault(20);
|
||||||
|
var rng = new Rng((ulong)(i * 7 + 3));
|
||||||
|
double start = mk.Price, anchor = mk.Anchor(new List<MarketEvent>());
|
||||||
|
double thr = start * mult;
|
||||||
|
for (int k = 0; k < nights; k++) { mk.Night(new List<MarketEvent>(), rng, 20 + k, a => 1); mk.Open(new List<MarketEvent>(), rng, 21 + k); }
|
||||||
|
if (mk.Price >= thr) hit++;
|
||||||
|
}
|
||||||
|
double sim = hit / (double)n, calc = PredictMath.PriceAtLeast(Market.CreateDefault(20).Price, Market.CreateDefault(20).Anchor(new List<MarketEvent>()), Tuning.PscVol, Tuning.Revert, nights, Market.CreateDefault(20).Price * mult);
|
||||||
|
Check(Math.Abs(sim - calc) < 0.04, $"行情先验 ×{mult} {nights} 晚:模拟 {sim:0.000} vs 公式 {calc:0.000}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Console.WriteLine($"\n{_passed} 通过,{_failed} 失败");
|
Console.WriteLine($"\n{_passed} 通过,{_failed} 失败");
|
||||||
return _failed == 0 ? 0 : 1;
|
return _failed == 0 ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user