← 仕様書TOPへ戻る
IMPLEMENTATION SPEC · エンジニア向け実装要件

Share-ratio L2-norm DPM
実装要件

Tech Buzz Game (Chen-Pennock-Kasturi 2008) 派生・Gensyn Delphi コントラクト確認済の L2-norm share-ratio DPM を、ミライマ(TypeScript/Prisma/MySQL)に実装する詳細仕様。
数式・データモデル・関数シグネチャ・エッジケース・既存LMSRからの移行戦略まで網羅。

目次

  1. 1. メカニズム選定根拠
  2. 2. 数学的仕様
  3. 3. ミライマ固有パラメータ
  4. 4. データモデル
  5. 5. 関数・APIシグネチャ
  6. 6. 整数化・精度・丸め
  7. 7. 状態遷移・初期化
  8. 8. 売却機構
  9. 9. 手数料・運営収益
  10. 10. エッジケース
  11. 11. 既存LMSRからの移行
  12. 12. テスト戦略
  13. 13. 実装ロードマップ

1. メカニズム選定根拠

採用:L2-norm share-ratio DPM

正式名称:「Share-ratio Dynamic Pari-Mutuel Market」(Pennock 2004 + Chen-Pennock-Kasturi 2008)
通称:「sqrt式 DPM」「L2-norm DPM」「Delphi式 DPM」「DPPM (9Lives)」。

3要件の充足

要件充足根拠
運営loss = 0Chen-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-freeTech Buzz Gameのmoney-ratio版が2週間でarbitrageで死亡 → share-ratio版に変更で解決
payout 確定最終 q^f が確定するまで完全には固定されない(DPM共通の制約)

他mechanism却下理由

選択肢却下理由
LMSR(継続)運営loss発生(4月 −¥227万実証)。UGC不適
LS-LMSRα tuning art、translation invariance失う、運営loss非ゼロ
Pure parimutuel価格発見なし、売却不可、Twitch型
CLOBmatching必須 → UGC低liquidity死
CPMMManifold が3メカ目で採用、ただし暗号資産系。fee構造複雑
DPM money-ratio版arbitrage-free でない → Tech Buzz Gameで2週間で破綻

2. 数学的仕様

2.1 状態空間

n 個の outcome(YES/NO のbinary なら n=2、競馬18頭なら n=18)。

share vector q = (q₁, q₂, ..., qₙ), qᵢ ≥ 0

sumTerm(コア量) S(q) = Σᵢ qᵢ²

2.2 コスト関数

cost function C(q) = κ · √(S(q)) = κ · √(Σ qᵢ²)

瞬時価格 pᵢ(q) = ∂C/∂qᵢ = κ · qᵢ / √(Σ qⱼ²)

市場確率(推定) πᵢ(q) = qᵢ² / Σ qⱼ² = (pᵢ / κ)² 正規化後 = pᵢ² / Σ pⱼ²

性質:

2.3 取引コスト(買い)

ユーザーが outcome k の shares を Δ 枚追加で買う場合:

買いコスト(net、手数料前) ΔC = C(q') − C(q) = κ · (√S(q') − √S(q))
ただし S(q') = S(q) − qₖ² + (qₖ + Δ)² = S(q) + Δ·(2qₖ + Δ)

展開:

展開形(実装で使う) S' = S + Δ(2qₖ + Δ)
ΔC = κ · (√S' − √S)

2.4 取引リファンド(売り)

ユーザーが outcome k の shares を Δ 枚売る(市場が買い戻す)場合:

売り時のリファンド(net、手数料前) ΔR = C(q) − C(q'') = κ · (√S(q) − √S(q''))
ただし S(q'') = S(q) − qₖ² + (qₖ − Δ)² = S(q) − Δ·(2qₖ − Δ)
必要条件: Δ ≤ qₖ(自分のshares以下に限定、かつ Δ < 2qₖ で S(q'') ≥ 0)

2.5 解決時のpayout

outcome w が当選した場合、share保有者へのpayout単価:

勝者1株あたりpayout oₐ(qᶠ) = C(qᶠ) / qₐᶠ = κ · √(Σ qⱼᶠ²) / qₐᶠ

破産的安全性 oₐ ≥ κ ≥ pᵢ(買い時の単価以上は保証される、※κ で seed した場合)

2.6 運営最大損失

worst-case loss L_max = C(q⁰) = κ · √(Σ (qᵢ⁰)²)

例:binary、q⁰ = (1, 1)、κ = 3 L_max = 3 · √2 ≈ 4.24 score = 424 coin = ¥42.4

注:初期seedはユーザー向けには「prize pool」として扱う(運営の純粋なlossではなくマーケ費用)。

3. ミライマ固有パラメータ

3.1 単位系

単位換算用途
yen基準表示・出金
coin1 coin = 0.1 yenユーザー残高・ベット入力
score1 score = 100 coin = ¥10内部数学計算(既存LMSR と統一)

3.2 κ(liquidity parameter)

既存 LMSR の b=61 score をベースに、カテゴリ別に再設定:

カテゴリn(outcomes)推奨 κ (score)初期 seedworst loss
公式 binary(W杯等)261q⁰ = (1, 1)61·√2 ≈ ¥863
UGC binary(学校単位)210〜30q⁰ = (1, 1)~¥140〜¥420
競馬18頭1830〜50q⁰ = 1 each30·√18 ≈ ¥1,270
多選択肢(5択)520〜40q⁰ = 1 each~¥447〜¥894

3.3 初期seed(誰が出すか)

📌 重要:κ と初期seed は別物

κ = 数式の定数(無料)
初期seed = C(q⁰) coin の実際のお金(誰かが拠出)

カテゴリ別自動seed設計

カテゴリq⁰κseed額(運営負担)備考
UGC 学校単位(小規模)(1, 1)3¥42/市場月100市場でも¥4,200/月
UGC 一般(1, 1)10¥141/市場中規模UGC
公式 binary(10, 10)61¥863/市場既存LMSR相当流動性
競馬18頭(1) × 1830¥1,272/レース多outcomes

運営が出す(ユーザー必須なし)。creator は任意で上乗せseed 可能(option)。マーケ費用として cap 設定。

3.4 手数料(Creator還元型)

🎯 方針:Creator 70-80% 還元、運営最小限

合計手数料 ≤ 10% を上限とし、creator/運営で分配

Phase別の段階導入

Phase期間合計Creator運営
Phase 0〜W杯前0%0%0%(UX優先)
Phase 1W杯〜年末5%4%1%
Phase 22027〜7%5%2%
Phase 3成熟期10%7%3%

常に Creator 70-80%、運営 20-30% の比率を維持

Creator自由度(XO Market参考)

creator は自分の market の手数料を 0%〜上限% で任意設定可能。

→ Creator 自身が市場原理で最適化、運営は固定比率取り分のみ

徴収タイミング

タイミング計算
① 売却時売却額 × (creator% + 運営%)
② 解決時 vigorish勝者pool × (creator% + 運営%)

競合比較

プラットフォーム合計手数料Creator取り分
ミライマ Phase 15%4% (80%還元)
ミライマ Phase 310%7% (70%還元)
Polymarket0% (maker) / 2% (taker)0%
XO Market0-1% creatorcreator自由設定
JRA(競馬)20-25%n/a
Twitch Predictions0% (play money)n/a

⚠️ 売却手数料は別途設計

bot対策の sybil round-trip 抑止のため、売却時には別途 5% の commission を取る(既存LMSR踏襲)。
これは「手数料」とは別枠の bot対策fee として運営側に入る。合計の負担感は phase別手数料 + 売却5% になる。

4. データモデル

4.1 prediction_market テーブル拡張

// 既存テーブル
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
}

4.2 prediction_market_outcome テーブル新設

// 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])
}

4.3 既存 prediction_market_entry の利用

// 既存テーブルそのまま使う(DPM固有のフィールド追加)
model PredictionMarketEntry {
  id              EntryId
  marketId        PredictionMarketId
  userId          UserId
  outcomeIndex    Int             // どのoutcomeか
  sharesAcquired  String          // 取得した shares 数(DECIMAL)
  pointsPayIn     Int             // 支払い coin
  txType          'BUY' | 'SELL'
  ...
}

⚠️ DECIMAL precision

MySQL DECIMAL(38, 18) を使う。score 単位で 0.000000000000000001 まで精度。
Prisma では String として扱い、計算は BigInt or 専用library(decimal.js / dnum) を使う。
浮動小数点 (Float, Double) は 絶対禁止(rounding error で運営損失リスク)。

5. 関数・APIシグネチャ

5.1 quote 関数(読み取り専用)

// 買いの見積もり
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;
};

5.2 実行 関数(DB トランザクション)

// 買い実行
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>;

5.3 数学関数(pure)

// 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);
}

6. 整数化・精度・丸め 最重要

6.1 スケール定数

const SCALE = 10n ** 18n;       // 全数値を 1e18 scale で保持
const SQRT_SCALE = 10n ** 9n;   // sqrt 用の中間 scale (1e18 = sqrt(1e36))

6.2 丸めルール(Gensyn Delphi 参照)

計算方向理由
買い時 sharesOut切り下げ (Down)ユーザーが得しすぎないように
買い時 coinsIn 要求切り上げ (Up)ユーザーが少なく払いすぎないように
売り時 coinsOut切り下げ (Down)運営/poolが過払いしないように
手数料計算切り上げ (Up)運営側が損しないように
新 sumTermそのまま(無丸め)誤差伝播防止、整数値で完結

6.3 sqrt 実装

// 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 として扱う。

7. 状態遷移・初期化

7.1 状態遷移

      ┌──────┐
      │ open │ ← 初期化完了直後
      └──┬───┘
         │ closeAt 到達 or 手動
         ▼
      ┌────────┐
      │ closed │ ← 取引停止、結果待ち
      └──┬─────┘
         │ outcome 確定
         ▼
  ┌──────────────┐         ┌─────────────┐
  │ distributed  │         │  cancelled  │
  │  (winnerあり) │         │ (全額refund) │
  └──────────────┘         └─────────────┘

7.2 初期化処理

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 にすると数式破綻

すべての qᵢ = 0 だと S = 0、p_i = 0/0 で undefined。最低でも各outcome に q⁰ ≥ ε をseedする(実装上は 1 × 1e18 = 1.0 程度)。

7.3 解決処理

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 確定
}

8. 売却機構

8.1 仕様

Pennock 2004 §7 では sell は CDA aftermarket(他traderへの売却)だが、ミライマでは:

仕様選択理由
売却の相手運営 pool(two-sided)既存LMSR と統一、UX シンプル
数式同じ L2-norm DPM 数式(売り価格)buy/sell対称、path-independence
commission5%既存LMSRと同じ。bot 自演対策に必須
最大売却量自分の保有 shares 以下負の q 禁止
市場 close 後売却禁止結果待ち中の取引なし

8.2 売却計算例

// 例: 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

9. 手数料・運営収益

9.1 収益源

  1. 売却手数料 5% — 売却 amount に対する控除
  2. 勝者pool からの vigorish 5-10% — distribute時に勝者pool を 5-10% 削減
  3. creator fee 0-1% — UGC marketの creator が受け取る(運営収益ではない、creator経済)
  4. 買い手数料 0-1%(オプション、初期は0)

9.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 構造下)

10. エッジケース

10.1 1人目の取引

q⁰ = (1, 1) で seed されていれば、1人目から normal に bet可能。価格は p_i = κ/√2 から始まる。

10.2 解決時に q_winner が極端に小さい

例: q_final = (1, 100) で outcome 0 が当選 → payout per share = κ·√10001 / 1 ≈ 100κ。「正解で大儲け」が成立。

10.3 取引中の concurrency

必須:DB トランザクション + 楽観的ロック

// 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 再計算 → 実行
  });
}

10.4 cancel/refund

市場 cancel 時:

  1. 全 entry に対し pointsPayIn を返金
  2. 運営pool の seed を運営に返却
  3. creator fee は徴収しない

10.5 引き分け(tie / draw)

競馬・スポーツで「引き分け」の場合:

10.6 浮動小数点誤差

絶対禁止。全計算 BigInt or decimal.js。Prisma 経由のDB値も String として扱う。

11. 既存LMSRからの移行

11.1 並走戦略

市場タイプ機構理由
公式作成(運営)既存LMSR継続strict-properで価格発見強、運営lossを受容
UGC(学校・creator)新規DPM_L2運営loss=0、creator簡単
競馬 / 多選択肢新規DPM_L2 一択LMSR は b·log(n) で運営loss過大(n=18で月¥250k損失)→ DPM必須

11.2 段階移行

  1. Phase 0(W杯6/13まで):DPM_L2実装+テスト、UGC1機能のみ閉鎖ベータ
  2. Phase 1(7-9月):T2/T3 trusted user の UGC β(学校単位)
  3. Phase 2(10-12月):T1 全開放 + 競馬 launch
  4. Phase 3(2027〜):公式marketも段階的にDPM_L2へ移行検討(運営loss問題が深刻化した場合)

11.3 DBマイグレーション

// マイグレーションファイル例
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)
);

12. テスト戦略

12.1 単体テスト(数学的property)

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)
  });
});

12.2 bot攻撃テスト

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
});

12.3 統合テスト

13. 実装ロードマップ

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)