Tech Buzz Game (Chen-Pennock-Kasturi 2008) 派生・Gensyn Delphi コントラクト確認済の L2-norm share-ratio DPM を、ミライマ(TypeScript/Prisma/MySQL)に実装する詳細仕様。
数式・データモデル・関数シグネチャ・エッジケース・既存LMSRからの移行戦略まで網羅。
正式名称:「Share-ratio Dynamic Pari-Mutuel Market」(Pennock 2004 + Chen-Pennock-Kasturi 2008)。
通称:「sqrt式 DPM」「L2-norm DPM」「Delphi式 DPM」「DPPM (9Lives)」。
| 要件 | 充足 | 根拠 |
|---|---|---|
| 運営loss = 0 | ✅ | Chen-Pennock 2010 surveyで明記。pari-mutuel構造の本質 |
| creator簡単 | ✅ | creator seed 不要、運営seedは任意 |
| 予測市場性 | ✅ (中) | L2-norm の price function = κ · q_i / √(Σq²)、確率推定可能 |
| 売却機能 | ✅ | CDA aftermarket(Pennock 2004 §7)、ミライマは operator-mediated CDA で実装 |
| arbitrage-free | ✅ | Tech Buzz Gameのmoney-ratio版が2週間でarbitrageで死亡 → share-ratio版に変更で解決 |
| payout 確定 | △ | 最終 q^f が確定するまで完全には固定されない(DPM共通の制約) |
| 選択肢 | 却下理由 |
|---|---|
| LMSR(継続) | 運営loss発生(4月 −¥227万実証)。UGC不適 |
| LS-LMSR | α tuning art、translation invariance失う、運営loss非ゼロ |
| Pure parimutuel | 価格発見なし、売却不可、Twitch型 |
| CLOB | matching必須 → UGC低liquidity死 |
| CPMM | Manifold が3メカ目で採用、ただし暗号資産系。fee構造複雑 |
| DPM money-ratio版 | arbitrage-free でない → Tech Buzz Gameで2週間で破綻 |
n 個の outcome(YES/NO のbinary なら n=2、競馬18頭なら n=18)。
性質:
0 ≤ pᵢ ≤ κ の範囲ユーザーが outcome k の shares を Δ 枚追加で買う場合:
展開:
ユーザーが outcome k の shares を Δ 枚売る(市場が買い戻す)場合:
outcome w が当選した場合、share保有者へのpayout単価:
注:初期seedはユーザー向けには「prize pool」として扱う(運営の純粋なlossではなくマーケ費用)。
| 単位 | 換算 | 用途 |
|---|---|---|
yen | 基準 | 表示・出金 |
coin | 1 coin = 0.1 yen | ユーザー残高・ベット入力 |
score | 1 score = 100 coin = ¥10 | 内部数学計算(既存LMSR と統一) |
既存 LMSR の b=61 score をベースに、カテゴリ別に再設定:
| カテゴリ | n(outcomes) | 推奨 κ (score) | 初期 seed | worst loss |
|---|---|---|---|---|
| 公式 binary(W杯等) | 2 | 61 | q⁰ = (1, 1) | 61·√2 ≈ ¥863 |
| UGC binary(学校単位) | 2 | 10〜30 | q⁰ = (1, 1) | ~¥140〜¥420 |
| 競馬18頭 | 18 | 30〜50 | q⁰ = 1 each | 30·√18 ≈ ¥1,270 |
| 多選択肢(5択) | 5 | 20〜40 | q⁰ = 1 each | ~¥447〜¥894 |
κ = 数式の定数(無料)
初期seed = C(q⁰) coin の実際のお金(誰かが拠出)
| カテゴリ | q⁰ | κ | seed額(運営負担) | 備考 |
|---|---|---|---|---|
| UGC 学校単位(小規模) | (1, 1) | 3 | ¥42/市場 | 月100市場でも¥4,200/月 |
| UGC 一般 | (1, 1) | 10 | ¥141/市場 | 中規模UGC |
| 公式 binary | (10, 10) | 61 | ¥863/市場 | 既存LMSR相当流動性 |
| 競馬18頭 | (1) × 18 | 30 | ¥1,272/レース | 多outcomes |
運営が出す(ユーザー必須なし)。creator は任意で上乗せseed 可能(option)。マーケ費用として cap 設定。
合計手数料 ≤ 10% を上限とし、creator/運営で分配
| Phase | 期間 | 合計 | Creator | 運営 |
|---|---|---|---|---|
| Phase 0 | 〜W杯前 | 0% | 0% | 0%(UX優先) |
| Phase 1 | W杯〜年末 | 5% | 4% | 1% |
| Phase 2 | 2027〜 | 7% | 5% | 2% |
| Phase 3 | 成熟期 | 10% | 7% | 3% |
常に Creator 70-80%、運営 20-30% の比率を維持
creator は自分の market の手数料を 0%〜上限% で任意設定可能。
→ Creator 自身が市場原理で最適化、運営は固定比率取り分のみ
| タイミング | 計算 |
|---|---|
| ① 売却時 | 売却額 × (creator% + 運営%) |
| ② 解決時 vigorish | 勝者pool × (creator% + 運営%) |
| プラットフォーム | 合計手数料 | Creator取り分 |
|---|---|---|
| ミライマ Phase 1 | 5% | 4% (80%還元) |
| ミライマ Phase 3 | 10% | 7% (70%還元) |
| Polymarket | 0% (maker) / 2% (taker) | 0% |
| XO Market | 0-1% creator | creator自由設定 |
| JRA(競馬) | 20-25% | n/a |
| Twitch Predictions | 0% (play money) | n/a |
bot対策の sybil round-trip 抑止のため、売却時には別途 5% の commission を取る(既存LMSR踏襲)。
これは「手数料」とは別枠の bot対策fee として運営側に入る。合計の負担感は phase別手数料 + 売却5% になる。
// 既存テーブル
model PredictionMarket {
id PredictionMarketId
title String
status MarketStatus // open / closed / distributed / cancelled
liquidityB Int // 既存LMSRのb値(DPM時はnull)
// 新規追加
mechanismType MechanismType // 'LMSR' | 'DPM_L2'
kappaScore Int? // DPM時のκ値(score単位、ゼロ初期化禁止)
initialSeedSumTerm String? // DPM時の初期 S(q⁰)、DECIMAL(38,0) 文字列
outcomesCount Int // n(2 or 多選択肢)
...
}
enum MechanismType {
LMSR
DPM_L2 // share-ratio L2-norm
}
// outcome毎にshare quantityを保持
model PredictionMarketOutcome {
id String
marketId PredictionMarketId
outcomeIndex Int // 0, 1, 2... (binary なら 0=NO, 1=YES)
label String // 表示用
qShares String // DECIMAL(38,18) として保持、score単位
isWinner Boolean? // 解決後にセット
@@unique([marketId, outcomeIndex])
}
// 既存テーブルそのまま使う(DPM固有のフィールド追加)
model PredictionMarketEntry {
id EntryId
marketId PredictionMarketId
userId UserId
outcomeIndex Int // どのoutcomeか
sharesAcquired String // 取得した shares 数(DECIMAL)
pointsPayIn Int // 支払い coin
txType 'BUY' | 'SELL'
...
}
MySQL DECIMAL(38, 18) を使う。score 単位で 0.000000000000000001 まで精度。
Prisma では String として扱い、計算は BigInt or 専用library(decimal.js / dnum) を使う。
浮動小数点 (Float, Double) は 絶対禁止(rounding error で運営損失リスク)。
// 買いの見積もり
function quoteBuyDPM(args: {
kappa: bigint; // κ × 1e18
qShares: bigint[]; // 各 outcome の現在 q × 1e18
outcomeIndex: number;
coinsIn: bigint; // 入金 coin (整数)
}): {
sharesOut: bigint; // 取得 shares × 1e18
newQShares: bigint[]; // 取引後の q
avgPricePerShare: bigint;
feeCoin: bigint;
};
// 売りの見積もり
function quoteSellDPM(args: {
kappa: bigint;
qShares: bigint[];
outcomeIndex: number;
sharesIn: bigint; // 売却 shares × 1e18
}): {
coinsOut: bigint; // 受取 coin(手数料後)
newQShares: bigint[];
feeCoin: bigint;
};
// 買い実行
async function executeBuyDPM(args: {
marketId: PredictionMarketId;
userId: UserId;
outcomeIndex: number;
coinsIn: bigint;
// 重要: quote時のqSharesスナップショットを渡して
// 実行時に状態が変わってないことを検証
expectedQShares: bigint[];
maxSlippage: bigint; // ユーザー許容するスリッページ
}): Promise<PredictionMarketEntry>;
// 売り実行
async function executeSellDPM(args: {
marketId: PredictionMarketId;
userId: UserId;
outcomeIndex: number;
sharesIn: bigint;
expectedQShares: bigint[];
maxSlippage: bigint;
}): Promise<PredictionMarketEntry>;
// 解決
async function distributeDPM(args: {
marketId: PredictionMarketId;
winnerOutcomeIndex: number;
}): Promise<DistributeResult>;
// sumTerm 計算
function computeSumTerm(qShares: bigint[]): bigint {
return qShares.reduce((acc, q) => acc + (q * q), 0n);
// 注: q × q は overflow しないよう scale 注意
}
// κ × √S の計算
function computeCost(kappa: bigint, sumTerm: bigint): bigint {
return (kappa * sqrtBigInt(sumTerm * SCALE)) / SCALE;
// SCALE = 1e18, sqrtBigInt は Newton's method
}
// 売り時の新 sumTerm
function newSumTermSell(sumTerm: bigint, qK: bigint, delta: bigint): bigint {
return sumTerm - delta * (2n * qK - delta);
// 必須: delta ≤ qK
}
// 買い時の新 sumTerm
function newSumTermBuy(sumTerm: bigint, qK: bigint, delta: bigint): bigint {
return sumTerm + delta * (2n * qK + delta);
}
const SCALE = 10n ** 18n; // 全数値を 1e18 scale で保持
const SQRT_SCALE = 10n ** 9n; // sqrt 用の中間 scale (1e18 = sqrt(1e36))
| 計算 | 方向 | 理由 |
|---|---|---|
| 買い時 sharesOut | 切り下げ (Down) | ユーザーが得しすぎないように |
| 買い時 coinsIn 要求 | 切り上げ (Up) | ユーザーが少なく払いすぎないように |
| 売り時 coinsOut | 切り下げ (Down) | 運営/poolが過払いしないように |
| 手数料計算 | 切り上げ (Up) | 運営側が損しないように |
| 新 sumTerm | そのまま(無丸め) | 誤差伝播防止、整数値で完結 |
// Newton's method、bigint 安全
function sqrtBigInt(value: bigint): bigint {
if (value < 0n) throw new Error('negative sqrt');
if (value < 2n) return value;
let x = value;
let y = (x + 1n) / 2n;
while (y < x) {
x = y;
y = (y + value / y) / 2n;
}
return x;
}
// sqrtUp(user に対して切り上げ、cost 側)
function sqrtUp(value: bigint): bigint {
const s = sqrtBigInt(value);
return s * s === value ? s : s + 1n;
}
// sqrtDown(refund 側)
function sqrtDown(value: bigint): bigint {
return sqrtBigInt(value);
}
JavaScript の Number / Float / Double は丸め誤差が累積し、運営損失や arbitrage を生む。全計算を BigInt または decimal.js で実装する。Prisma 経由のDB値も String として扱う。
┌──────┐
│ open │ ← 初期化完了直後
└──┬───┘
│ closeAt 到達 or 手動
▼
┌────────┐
│ closed │ ← 取引停止、結果待ち
└──┬─────┘
│ outcome 確定
▼
┌──────────────┐ ┌─────────────┐
│ distributed │ │ cancelled │
│ (winnerあり) │ │ (全額refund) │
└──────────────┘ └─────────────┘
async function initMarketDPM(args: {
title: string;
outcomes: { label: string }[]; // n >= 2
kappaScore: number; // κ
closeAt: Date;
initialQ: number; // 各 outcome の初期 q (e.g., 1)
}): Promise<PredictionMarket> {
// 1. 各 outcome に q = initialQ × 1e18 で seed
// 2. S(q⁰) = n * initialQ²
// 3. 運営の prize pool として coin を確保(運営loss上限分)
// 需要: kappa * sqrt(S(q⁰)) coin を運営pool へロック
// 4. marketRecord 作成
}
すべての qᵢ = 0 だと S = 0、p_i = 0/0 で undefined。最低でも各outcome に q⁰ ≥ ε をseedする(実装上は 1 × 1e18 = 1.0 程度)。
async function distributeDPM(marketId, winnerIdx) {
const market = await getMarket(marketId);
const qFinal = market.outcomes.map(o => BigInt(o.qShares));
const sumTermFinal = computeSumTerm(qFinal);
const totalPool = market.kappa * sqrtBigInt(sumTermFinal); // C(q^f)
const winnerQ = qFinal[winnerIdx];
// 勝者の share holder それぞれに proportional に分配
for (const entry of winnerEntries) {
// entry.sharesAcquired / winnerQ × totalPool
const payout = (entry.sharesAcquired * totalPool) / winnerQ;
// - 運営fee(vigorish 5-10%)控除
// - 勝者の participation point → redeemable point に変換
await creditUser(entry.userId, payout);
}
// 負け側のentryは loss 確定
}
Pennock 2004 §7 では sell は CDA aftermarket(他traderへの売却)だが、ミライマでは:
| 仕様 | 選択 | 理由 |
|---|---|---|
| 売却の相手 | 運営 pool(two-sided) | 既存LMSR と統一、UX シンプル |
| 数式 | 同じ L2-norm DPM 数式(売り価格) | buy/sell対称、path-independence |
| commission | 5% | 既存LMSRと同じ。bot 自演対策に必須 |
| 最大売却量 | 自分の保有 shares 以下 | 負の q 禁止 |
| 市場 close 後 | 売却禁止 | 結果待ち中の取引なし |
// 例: q = (5, 1), κ = 3, ユーザーが outcome 0 の 2 shares 売る
// S = 25 + 1 = 26
// S' = 26 - 2·(2·5 - 2) = 26 - 16 = 10
// (= 9 + 1 = 3² + 1²、q' = (3, 1) と一致)
// ΔR = 3·(√26 - √10) = 3·(5.099 - 3.162) = 5.81 score
// 5% commission: ユーザー受取 = 5.81 × 0.95 = 5.52 score
// = 552 coin = ¥55.2
// 1 market、binary、κ=10、参加 100人 × 平均 ¥500 bet
// = 総 buy volume ¥50,000 = 5,000 score
//
// 売却率 30% と仮定:
// 売却 volume ¥15,000 → 売却手数料 = ¥750
//
// 解決時:
// pool = ¥50,000 + 初期seed ¥140 ≈ ¥50,140
// vigorish 10% = ¥5,014(運営収益)
//
// 純利益 = 売却手数料 + vigorish - 初期seed
// = 750 + 5,014 - 140 = ¥5,624 / market
//
// 100 market/月 = 月¥562,400 純利益(運営loss = 0 構造下)
q⁰ = (1, 1) で seed されていれば、1人目から normal に bet可能。価格は p_i = κ/√2 から始まる。
例: q_final = (1, 100) で outcome 0 が当選 → payout per share = κ·√10001 / 1 ≈ 100κ。「正解で大儲け」が成立。
// quote 時の qShares スナップショットを受け取り、
// execute 時に re-fetch して整合性確認
async function executeBuyDPM({ expectedQShares, maxSlippage, ... }) {
await prisma.$transaction(async (tx) => {
const market = await tx.predictionMarket.findUnique(...);
const currentQ = market.outcomes.map(o => BigInt(o.qShares));
// slippage check
const diff = computeSumTermDiff(expectedQShares, currentQ);
if (diff > maxSlippage) throw new SlippageExceededError();
// ここで quote 再計算 → 実行
});
}
市場 cancel 時:
pointsPayIn を返金競馬・スポーツで「引き分け」の場合:
絶対禁止。全計算 BigInt or decimal.js。Prisma 経由のDB値も String として扱う。
| 市場タイプ | 機構 | 理由 |
|---|---|---|
| 公式作成(運営) | 既存LMSR継続 | strict-properで価格発見強、運営lossを受容 |
| UGC(学校・creator) | 新規DPM_L2 | 運営loss=0、creator簡単 |
| 競馬 / 多選択肢 | 新規DPM_L2 一択 | LMSR は b·log(n) で運営loss過大(n=18で月¥250k損失)→ DPM必須 |
// マイグレーションファイル例
ALTER TABLE prediction_market
ADD COLUMN mechanism_type ENUM('LMSR', 'DPM_L2') NOT NULL DEFAULT 'LMSR',
ADD COLUMN kappa_score INT NULL,
ADD COLUMN outcomes_count INT NOT NULL DEFAULT 2;
CREATE TABLE prediction_market_outcome (
id VARCHAR(26) PRIMARY KEY,
market_id VARCHAR(26) NOT NULL,
outcome_index INT NOT NULL,
label VARCHAR(200) NOT NULL,
q_shares DECIMAL(38, 18) NOT NULL,
is_winner BOOLEAN NULL,
UNIQUE (market_id, outcome_index),
FOREIGN KEY (market_id) REFERENCES prediction_market(id)
);
describe('DPM_L2 math properties', () => {
it('path independence: buy + sell back returns to original state', () => {
const q0 = [1e18, 1e18];
const { sharesOut } = quoteBuyDPM({ qShares: q0, outcomeIdx: 0, coinsIn: 100n });
const { coinsOut } = quoteSellDPM({ qShares: [q0[0] + sharesOut, q0[1]], outcomeIdx: 0, sharesIn: sharesOut });
// commission前: coinsOut == coinsIn
expect(coinsOut * 100n / 95n).toBeCloseTo(100n); // 5% commission控除
});
it('payout for winner is at least kappa', () => {
// o_winner = κ · √S / q_winner ≥ κ
});
it('market probability sums to 1', () => {
// Σ π_i = 1
});
it('cost increases monotonically with q', () => {
// ∂C/∂q_i > 0
});
it('worst-case loss bounded by initial seed', () => {
// L_max = C(q^0)
});
});
it('Sybil round-trip is unprofitable with 5% commission', () => {
// A buys, B buys, A sells, B sells
// 期待: 合計 net < 0(commission control)
});
it('rapid alternating bets do not break state', () => {
// 高頻度 buy/sell の concurrency test
});
| Phase | 期間 | 内容 | 担当目安 |
|---|---|---|---|
| P0. 設計確定 | 1週 | 本仕様レビュー、Notion化、エンジニア合意 | 川上 + Shou |
| P1. 数学コア実装 | 1.5週 | pure math関数(BigInt)、単体テスト100% | Shou |
| P2. DBマイグレーション | 3日 | schema追加、既存LMSRとの互換確認 | Shou |
| P3. API実装 | 1週 | quote/execute/distribute、slippage protection | Shou |
| P4. フロント実装 | 1週 | 新DPM market UI、売却UI、結果画面 | 川崎 |
| P5. STG検証 | 1週 | bot自演テスト、規模負荷、CSフロー | Leo |
| P6. W杯前β | 2週 | 限定UGC market 5-10本で動作確認 | 全員 |
合計約 6-7週間。W杯6/13に間に合わせるなら 2026-05 中旬には P1 着手必須。
原典: Pennock 2004 EC'04 / Chen-Pennock-Kasturi 2008 EC'08 / Chen & Pennock 2010 AI Magazine survey
実装参考: Gensyn Delphi smart contract DynamicParimutuelMath.sol (Solidity 0.8.30, Foundry)