Prediction markets use on-chain wallets (signed contract calls) instead of the Poinbase account
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e5a6e3e22c
commit
723724aa4b
8 files changed
+138
-73
No files matched your search
@@ -69,6 +69,8 @@ namespace Decentralized.Domain
|
||||
public sealed class PredictPos
|
||||
{
|
||||
public int Market;
|
||||
/// <summary>持仓所在的钱包地址(下注签名的钱包,结算和卖出都回到它)。</summary>
|
||||
public string Wallet;
|
||||
public bool Yes;
|
||||
public double Shares, Cost, Taint, Basis, Insider;
|
||||
}
|
||||
@@ -113,7 +115,8 @@ namespace Decentralized.Domain
|
||||
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);
|
||||
public PredictPos PosOf(int id, string wallet) => Positions.Find(p => p.Market == id && p.Wallet == wallet);
|
||||
public List<PredictPos> PosAll(int id) => Positions.Where(p => p.Market == id).ToList();
|
||||
|
||||
private static double Share(Wallet acct, double amount, Func<string, double> price)
|
||||
{
|
||||
@@ -130,26 +133,27 @@ namespace Decentralized.Domain
|
||||
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;
|
||||
var all = PosAll(m.Id);
|
||||
if (all.Any(x => x.Yes != yes)) return PredictResult.Opposite;
|
||||
var pos = PosOf(m.Id, acct.Address);
|
||||
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; }
|
||||
if (all.Sum(x => x.Shares) + 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 });
|
||||
if (pos == null) Positions.Add(pos = new PredictPos { Market = m.Id, Yes = yes, Wallet = acct.Address });
|
||||
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>
|
||||
/// <summary>全部卖出这个钱包在盘口里的持仓(盘口关闭前):收回 LMSR 的卖价减 2% 手续费,污点按比例退回,赚的部分是干净的。返回收回的 CRC。</summary>
|
||||
public PredictResult SellAll(Wallet acct, PredictMarket m, int day, out double got)
|
||||
{
|
||||
got = 0;
|
||||
var pos = PosOf(m.Id);
|
||||
var pos = PosOf(m.Id, acct.Address);
|
||||
if (pos == null) return PredictResult.NoPosition;
|
||||
if (!m.IsOpen(day)) return PredictResult.Closed;
|
||||
double value = m.SellValue(pos.Yes, pos.Shares);
|
||||
@@ -167,10 +171,10 @@ namespace Decentralized.Domain
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 夜间:先登记今晚的观测,再结算 ToDay 到了的盘口。outcome 由接入层给出(事件 / 派系读观测,纯度和行情读游戏数值),null 表示拿不到数据。
|
||||
/// 夜间:先登记今晚的观测,再结算 ToDay 到了的盘口(赢的钱回到下注的钱包,wallet 按地址找)。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)
|
||||
public List<PredictEvent> Night(int day, IEnumerable<string> activeNow, Func<PredictMarket, bool?> outcome, Func<string, Wallet> wallet, Rng rng)
|
||||
{
|
||||
var now = activeNow.Distinct().ToList();
|
||||
if (Prev != null) Started[day] = now.Where(x => !Prev.Contains(x)).ToList();
|
||||
@@ -180,38 +184,39 @@ namespace Decentralized.Domain
|
||||
foreach (var m in Markets.Where(x => x.Outcome < 0 && x.ToDay <= day).ToList())
|
||||
{
|
||||
bool? res = outcome(m);
|
||||
var pos = PosOf(m.Id);
|
||||
var all = PosAll(m.Id);
|
||||
foreach (var pos in all) Positions.Remove(pos);
|
||||
double cost = all.Sum(x => x.Cost);
|
||||
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);
|
||||
foreach (var pos in all) Pay(wallet(pos.Wallet), pos, pos.Cost);
|
||||
if (all.Count > 0) evs.Add(new PredictEvent { Kind = "void", Title = m.Title, Market = m, Amount = cost });
|
||||
Record(day, m, 2, 0, all.Count > 0);
|
||||
continue;
|
||||
}
|
||||
m.Outcome = res.Value ? 1 : 0;
|
||||
if (pos != null)
|
||||
var winners = all.Where(x => x.Yes == res.Value).ToList();
|
||||
if (all.Count > 0 && winners.Count == 0)
|
||||
{
|
||||
Positions.Remove(pos);
|
||||
if (pos.Yes != res.Value)
|
||||
evs.Add(new PredictEvent { Kind = "lose", Title = m.Title, Market = m, Amount = cost });
|
||||
Record(day, m, m.Outcome, -cost, true);
|
||||
}
|
||||
else if (winners.Count > 0)
|
||||
{
|
||||
double payout = winners.Sum(x => x.Shares), profit = payout - cost;
|
||||
// 内幕持仓按盘口合计(拆到几个钱包里也一样)
|
||||
if (all.Sum(x => x.Insider) >= Tuning.PredictInsiderShare * m.ShareCap && rng.Chance(Tuning.PredictInsiderCatch))
|
||||
{
|
||||
evs.Add(new PredictEvent { Kind = "lose", Title = m.Title, Market = m, Amount = pos.Cost });
|
||||
Record(day, m, m.Outcome, -pos.Cost, true);
|
||||
foreach (var pos in winners) Pay(wallet(pos.Wallet), 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
|
||||
{
|
||||
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);
|
||||
}
|
||||
foreach (var pos in winners) Pay(wallet(pos.Wallet), pos, pos.Shares);
|
||||
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);
|
||||
|
||||
Reference in new issue
Block a user