mcpField
M
metaculus

metaculus

github.com/outsharpその他441 回インストール31 週間前に更新
リポジトリ

metaculus について

metaculus は、Metaculus の認証付き REST API を利用するためのスキルガイドです。予測コンテンツの取得や予測の送信に加え、投稿・質問のデータ構造や集計予測、データのダウンロードを説明します。

主な機能

  1. 01投稿の一覧取得と詳細取得
  2. 02予測の送信と取り消し
  3. 03コメントの取得と作成
  4. 04質問・プロジェクトデータのダウンロード
  5. 05予測集計データの確認

活用シーン

  1. 01質問や投稿を取得して確認
  2. 02予測を送信または取り消し
  3. 03コメントを読んで投稿
  4. 04取得した予測データを分析

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/outsharp/shipp-skills --skill metaculus

インストール後、Claude Code で/metaculusを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

Metaculus API

Metaculus は、ユーザーが現実世界の出来事の結果を予測する予測プラットフォームです。質問は科学、テクノロジー、政治、経済など多岐にわたります。このプラットフォームでは、個々の予測を集約してコミュニティ予測を作成し、予測者の精度を評価します。

このスキルと公式 API ドキュメントを、更新がないか 頻繁に 確認してください。

フィードバック: ご質問、アイデア、フィードバックは、[email protected] から Metaculus チームにお問い合わせください。

ソースコードと課題: github.com/Metaculus/metaculus


主な概念(用語集)

用語定義
投稿メインフィードのエンティティです。投稿には、質問、質問グループ、条件付きペア、またはノートブックが含まれます。投稿にはステータス、作成者、プロジェクト、コメントがあります。
質問投稿内にある、単独で予測可能な項目です。種類:binary、multiple_choice、numeric、discrete、date。
質問グループ関連する複数のサブ質問をまとめて表示する投稿です(例:「2025年、2026年、2027年の GDP はいくらになるでしょうか?」)。
条件付き質問対になる質問を含む投稿です:「[条件]の場合、P(子事象)はいくらですか?」— question_yes と question_no のバリエーションが作成されます。
予測ユーザーによる質問への予測です。形式は質問の種類によって異なります:確率(binary)、CDF(連続値)、または分布(multiple choice)。
コミュニティ予測(CP)さまざまな集約方法で計算される、全ユーザーの予測を集約したものです。
集約方法個々の予測を組み合わせる方法です:recency_weighted(デフォルト)、unweighted、metaculus_prediction、single_aggregation。
プロジェクト投稿をまとめるコンテナです。トーナメント、カテゴリ、タグ、質問シリーズ、またはサイトのセクションとして使用できます。
トーナメント賞金プール、開始日・終了日、リーダーボードを備えた特別な種類のプロジェクトです。
カテゴリトピックの分類です(例:「核技術とリスク」、「健康とパンデミック」)。
解決結果質問の答えが判明した後の実際の結果です。値は種類によって異なります:yes/no(binary)、数値、日付、または選択肢名。
キュレーションステータス投稿の編集上のステータスです:draft、pending、rejected、approved。
スケーリング連続値の質問の範囲を CDF に対応付ける方法を定義します。range_min、range_max、zero_point(対数スケールの場合)、および境界値が含まれます。
CDF累積分布関数(Cumulative Distribution Function)— 連続値の予測に使用する形式です。201 個の浮動小数点数のリストです(離散値の場合は inbound_outcome_count + 1 個)。
範囲内の結果数質問の範囲内で取りうる結果の数です(範囲外の結果を除く)。連続値の場合のデフォルトは 200 で、離散値の場合はそれより少なくなります。

ベース URL

すべてのエンドポイントは次の場所から提供されます:

https://www.metaculus.com

以下のパスはすべて、このベース URL からの相対パスです(例:GET /api/posts/ は GET https://www.metaculus.com/api/posts/ を意味します)。


認証

すべての API リクエストには認証が必要です。 認証されていないリクエストは拒否されます。

トークンの取得
  1. Metaculus にログインします。
  2. アカウント設定 → API アクセスに移動します。
  3. API トークンをコピー(または生成)します。
トークンの使用

すべてのリクエストに Authorization ヘッダーを追加します。トークンの前には、文字列 Token と半角スペースを付ける必要があります:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
例:curl
curl -s "https://www.metaculus.com/api/posts/?limit=5&order_by=-published_at" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .
例:Python
import os, requests

TOKEN = os.environ["METACULUS_API_TOKEN"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
BASE = "https://www.metaculus.com"

resp = requests.get(f"{BASE}/api/posts/", headers=HEADERS, params={"limit": 5})
resp.raise_for_status()
data = resp.json()
環境変数

トークンをコードに直接記述しないでください。環境変数または .env ファイルに保存してください:

METACULUS_API_TOKEN=your-token-here

レート制限

Metaculus は、不正使用を防ぐためにリクエストを制限します。429 Too Many Requests レスポンスを受け取った場合は、再試行する前に指数バックオフを実装してください。


REST API エンドポイントの概要

フィード(投稿)
メソッドエンドポイント説明認証
GET/api/posts/フィルターを指定して、ページ分割された投稿フィードを取得します必須
GET/api/posts/{postId}/詳細をすべて含む単一の投稿を取得します必須
質問と予測
メソッドエンドポイント説明認証
POST/api/questions/forecast/1 つ以上の質問の予測を送信します必須
POST/api/questions/withdraw/有効な予測を取り下げます必須
コメント
メソッドエンドポイント説明認証
GET/api/comments/コメントを取得します(投稿または投稿者で絞り込み可能)必須
POST/api/comments/create/投稿に新しいコメントを作成します必須
ユーティリティとデータ
メソッドエンドポイント説明認証
GET/api/posts/{postId}/download-data/質問データを CSV ファイルの ZIP としてダウンロードします必須
GET/api/projects/{projectId}/download-data/プロジェクトの全データを CSV ファイルの ZIP としてダウンロードします必須(管理者またはホワイトリスト登録済み)

データモデル

投稿 → 質問の階層

投稿は、フィード内の最上位のエンティティです。各投稿には、次のうち 1 つだけが含まれます。

  • question — 単一の質問(バイナリ、数値、日付、複数選択、または離散型)
  • group_of_questions — 関連する複数のサブ質問
  • conditional — 条件付きペア(条件+子。question_yes と question_no を生成)
  • ノートブック(質問コンテンツなし)

それ以外のフィールドは null になります。たとえば、単一のバイナリ質問を含む投稿では、question に値が設定され、conditional/group_of_questions は null になります。

投稿
フィールド型説明
idinteger一意の投稿 ID
titlestring完全なタイトル
short_titlestringURL 用の短いタイトル
slugstringURL スラッグ
author_idinteger投稿者のユーザー ID
author_usernamestring投稿者のユーザー名
projectsobject関連プロジェクト(下記を参照)
created_atdatetime投稿の作成日時
published_atdatetime?投稿の公開日時
open_timedatetime?質問の予測受付開始日時
edited_atdatetime最終編集日時
curation_statusstringdraft、pending、rejected、または approved
comment_countintegerコメント数
statusstringopen、upcoming、closed、resolved、draft、pending、rejected
nr_forecastersinteger予測者のユニーク数
questionQuestion?単一の質問(該当する場合)
conditionalConditional?条件付きペア(該当する場合)
group_of_questionsGroupOfQuestions?質問グループ(該当する場合)
user_permissionstringforecaster または viewer
voteobject{ score: int, user_vote: string? }
forecasts_countinteger予測の合計数
投稿プロジェクトオブジェクト
フィールド型説明
site_mainProject[]サイトレベルのプロジェクト関連付け
tournamentProject[]この投稿が属するトーナメント
categoryCategory[]カテゴリー
tagTag[]タグ
question_seriesProject[]質問シリーズ
default_projectProject投稿の主要/デフォルトプロジェクト
プロジェクト
フィールド型説明
idintegerプロジェクト ID
typestringsite_main、tournament など
namestring表示名
slugstring?URL スラッグ
prize_poolstring賞金総額(例:"0.00")
start_datedatetime?開始日
close_datedatetime?終了日
is_ongoingboolean?プロジェクトが進行中かどうか
default_permissionstringデフォルトのユーザー権限(例:"forecaster")
visibilitystringnormal、not_in_main_feed、unlisted
Question
フィールド型説明
idinteger一意の質問 ID(予測の提出で使用されます。投稿 ID ではありません)
titlestring質問のタイトル
descriptionstring全文の説明(include_descriptions=true を指定しない場合は省略されることがあります)
typestringbinary、multiple_choice、numeric、discrete、date
statusstringupcoming、open、closed、resolved
resolutionstring?判定値(未判定の場合は null)
resolution_criteriastring質問の判定方法
fine_printstring判定に関する追加の詳細
created_atdatetime作成日時
open_timedatetime予測の受付開始日時
scheduled_close_timedatetime予測の受付終了予定日時
actual_close_timedatetime予測の受付終了日時
scheduled_resolve_timedatetime判定予定日時
actual_resolve_timedatetime?実際に判定された日時
optionsstring[]現在の選択肢(複数選択式のみ)
all_options_everstring[]これまでに存在したすべての選択肢(複数選択式のみ)
open_upper_boundboolean上限が開区間かどうか
open_lower_boundboolean下限が開区間かどうか
inbound_outcome_countinteger?範囲内の離散的な結果の数(連続値の場合のデフォルトは 200)
unitstring表示単位(例:"$"、"°C")
labelstring?小問のラベル
scalingQuestionScaling範囲とスケーリングのパラメーター
aggregationsobjectコミュニティ予測の集計(以下を参照)
QuestionScaling
フィールド型説明
range_minfloat?入力範囲の下限
range_maxfloat?入力範囲の上限
zero_pointfloat?対数スケールのゼロ点(null = 線形スケーリング)
open_upper_boundboolean?上限が開区間かどうか
open_lower_boundboolean?下限が開区間かどうか
inbound_outcome_countinteger?範囲内の結果の数
continuous_rangestring[]?CDF の評価対象となる実数値の位置
Aggregations

各質問には、最大 4 つの方式を含む aggregations オブジェクトがあります。

  • recency_weighted — デフォルト。最近の予測をより重く扱います
  • unweighted — すべての予測を均等に扱います
  • metaculus_prediction — Metaculus 独自の予測
  • single_aggregation — ベータ版。管理者のみ使用可能

各方式には次の項目があります。

フィールド型説明
historyobject[]集計予測の時系列
latestobject?最新の集計予測
score_dataobject?スコアリング情報

各 history/latest エントリーには次の項目があります。

フィールド型説明
start_timedatetimeこの集計期間の開始日時
end_timedatetime?この集計期間の終了日時
forecast_valuesfloat[]集計された予測(バイナリの場合は確率、連続値の場合は CDF)
forecaster_countinteger予測に寄与した予測者の数
centersfloat[]中央値/中心値
interval_lower_boundsfloat[]信頼区間の下限
interval_upper_boundsfloat[]信頼区間の上限
meansfloat?平均値
Conditional
フィールド型説明
idinteger条件 ID
conditionQuestion条件となる質問
condition_childQuestion子質問
question_yesQuestion「条件 = はい」の場合のバリエーション
question_noQuestion「条件 = いいえ」の場合のバリエーション
GroupOfQuestions
FieldTypeDescription
idintegerグループ ID
descriptionstringグループの説明
resolution_criteriastring解決基準
fine_printstring追加の詳細
group_variablestringサブ質問間で異なる変数
graph_typestringmultiple_choice_graph または fan_graph
questionsQuestion[]サブ質問
Comment
FieldTypeDescription
idintegerコメント ID
authorobject{ id, username, is_bot, is_staff }
parent_idinteger?親コメント ID(返信の場合)
root_idinteger?スレッド内のルートコメント ID
created_atdatetime作成日時
textstringコメント内容
on_postintegerコメントが属する投稿の ID
included_forecastbooleanユーザーの直近の予測が含まれているか
is_privatebooleanコメントが非公開か
vote_scoreinteger投票スコアの合計
user_voteinteger現在のユーザーの投票(-1、0、1)

エンドポイント:詳細リファレンス

GET /api/posts/ — 投稿フィードの取得

詳細なフィルタリングと並べ替え機能を使用して、ページ分割された投稿のリストを返します。

クエリパラメーター:

ParameterTypeDescription
limitintegerページサイズ(デフォルトは変動)
offsetintegerページネーションのオフセット
tournamentsstring[]トーナメントのスラッグで絞り込み(例:metaculus-cup、aibq3)
statusesstring[]ステータスで絞り込み:upcoming、closed、resolved、open
forecast_typestring[]種類で絞り込み:binary、multiple_choice、numeric、discrete、date、conditional、group_of_questions、notebook
categoriesstring[]カテゴリーのスラッグで絞り込み(例:nuclear、health-pandemics)
forecaster_idintegerこのユーザーが予測した投稿
not_forecaster_idintegerこのユーザーがまだ予測していない投稿
for_main_feedbooleanメインフィードへの適合性で絞り込み
with_cpbooleanコミュニティ予測を含めます(デフォルト:false)。グループの場合、上位 3 件のサブ質問の CP のみを返します。
include_cp_historyboolean集計方法ごとの CP 履歴全体を含めます(デフォルト:false)
include_descriptionsbooleandescription、fine_print、resolution_criteria フィールドを含めます
order_bystring並べ替えフィールド。降順にするには先頭に - を付けます。以下の選択肢を参照してください。
open_time__gtdatetime開始時刻が指定値より後(__gte、__lt、__lte もサポート)
published_at__gtdatetime公開時刻が指定値より後(__gte、__lt、__lte もサポート)
scheduled_resolve_time__gtdatetime解決予定時刻が指定値より後(__gte、__lt、__lte もサポート)

order_by の選択肢:

ValueDescription
published_at公開時刻
open_time予測受付の開始時刻
vote_scoreコミュニティの投票スコア
comment_countコメント数
forecasts_count予測数
scheduled_close_time終了予定時刻
scheduled_resolve_time解決予定時刻
user_last_forecasts_dateユーザーが最後に予測した日時
unread_comment_count未読コメント数
weekly_movement週間の確率変動
divergence乖離指標
hotness複合トレンドスコア(3.5 日後から低下)
scoreユーザーの予測パフォーマンス(forecaster_id が必須)

レスポンス:

{
  "next": "https://www.metaculus.com/api/posts/?limit=20&offset=20",
  "previous": null,
  "results": [ /* array of Post objects */ ]
}

例:最新順で、受付中の二択質問を取得する:

curl -s "https://www.metaculus.com/api/posts/?statuses=open&forecast_type=binary&order_by=-published_at&limit=10&with_cp=true" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title, status}'

例:トーナメントの質問を取得する:

curl -s "https://www.metaculus.com/api/posts/?tournaments=metaculus-cup&with_cp=true&limit=20" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

例:まだ予測していない質問を取得する:

# Replace YOUR_USER_ID with your actual Metaculus user ID
curl -s "https://www.metaculus.com/api/posts/?not_forecaster_id=YOUR_USER_ID&statuses=open&limit=10" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

GET /api/posts/{postId}/ — 投稿の詳細を取得

単一の投稿の詳細をすべて返します。サブ質問と集計も含まれます。

パスパラメーター:

パラメーター型説明
postIdinteger投稿 ID

例:

curl -s "https://www.metaculus.com/api/posts/12345/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

POST /api/questions/forecast/ — 予測を送信

1 件以上の予測を送信します。リクエストボディは予測オブジェクトの JSON 配列 です。

重要: question フィールドには、投稿 ID ではなく質問 ID を指定します。質問 ID は post.question.id、post.group_of_questions.questions[].id、または post.conditional.question_yes.id / post.conditional.question_no.id から取得してください。

二択予測
[
  {
    "question": 12345,
    "probability_yes": 0.63
  }
]
フィールド型必須説明
questionintegerはい質問 ID
probability_yesfloatはい0 から 1 までの確率
end_timedatetimeいいえ自動取り下げのタイムスタンプ
選択肢予測
[
  {
    "question": 12345,
    "probability_yes_per_category": {
      "Futurama": 0.5,
      "Paperclipalypse": 0.3,
      "Singularia": 0.2
    }
  }
]
フィールド型必須説明
questionintegerはい質問 ID
probability_yes_per_categoryobjectはい選択肢名 → 確率のマップ。合計は 1.0 でなければなりません。
end_timedatetimeいいえ自動取り下げのタイムスタンプ
連続値予測(数値/日付/離散値)
[
  {
    "question": 12345,
    "continuous_cdf": [0.0, 0.00005, 0.00010, "... 201 values total ...", 1.0]
  }
]
フィールド型必須説明
questionintegerはい質問 ID
continuous_cdffloat[]はいCDF 配列(下記の CDF 生成セクションを参照)
distribution_inputobjectいいえフロントエンド表示用のスライダー値
end_timedatetimeいいえ自動取り下げのタイムスタンプ
条件付き予測

「はいの場合」と「いいえの場合」の両方の質問について予測を送信します。

[
  { "question": 111, "probability_yes": 0.499 },
  { "question": 222, "probability_yes": 0.501 }
]

ここで、111 は conditional.question_yes.id、222 は conditional.question_no.id です。

グループ予測

1 回のリクエストで複数のサブ質問の予測を送信します。

[
  { "question": 1, "probability_yes": 0.11 },
  { "question": 2, "probability_yes": 0.22 },
  { "question": 3, "probability_yes": 0.33 }
]

例:curl で二択予測を送信:

curl -s -X POST "https://www.metaculus.com/api/questions/forecast/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"question": 12345, "probability_yes": 0.75}]'

レスポンス:

ステータス説明
201予測が正常に送信されました
400リクエスト形式が無効です

POST /api/questions/withdraw/ — 予測を取り下げ

有効な予測を取り下げます。リクエストボディは取り下げオブジェクトの JSON 配列です。

[
  { "question": 12345 },
  { "question": 12346 }
]

例:

curl -s -X POST "https://www.metaculus.com/api/questions/withdraw/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"question": 12345}]'

GET /api/comments/ — コメントを取得

フィルターを指定してコメントを取得します。post または author のいずれかが必須です。

クエリパラメーター:

パラメーター型必須説明
postintegerpost/author のいずれか絞り込み対象の投稿 ID
authorintegerpost/author のいずれか絞り込み対象の投稿者のユーザー ID
limitintegerいいえ取得するコメント数
offsetintegerいいえページネーションのオフセット
is_privatebooleanいいえ非公開/公開で絞り込みます(デフォルト:false)
use_root_comments_paginationbooleanいいえtrue の場合、ページネーションはルートコメントのみに適用されます。すべての子コメントが含まれます
sortstringいいえcreated_at(昇順)または -created_at(降順)
focus_comment_idintegerいいえこのコメントを結果の先頭に配置します

レスポンス:

{
  "total_count": 42,
  "count": 15,
  "next": "https://www.metaculus.com/api/comments/?post=123&limit=10&offset=10",
  "previous": null,
  "results": [ /* array of Comment objects */ ]
}
  • total_count — ルートコメントと子コメントの合計数
  • count — ルートコメントのみの合計数

例:

curl -s "https://www.metaculus.com/api/comments/?post=12345&sort=-created_at&limit=20" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq .

POST /api/comments/create/ — コメントを作成

リクエストボディ:

フィールド型必須説明
on_postintegerはいコメント対象の投稿 ID
textstringはいコメント内容
included_forecastbooleanはいユーザーの直近の予測を含めるかどうか
is_privatebooleanはいコメントを非公開にするかどうか
parentintegerいいえ親コメント ID(返信の場合)

例:

curl -s -X POST "https://www.metaculus.com/api/comments/create/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "on_post": 12345,
    "text": "I updated my forecast based on the latest data.",
    "included_forecast": false,
    "is_private": false
  }'

レスポンス:

ステータス説明
201コメントが正常に作成されました(Comment オブジェクトを返します)
400リクエスト形式が無効です

GET /api/posts/{postId}/download-data/ — 質問データのダウンロード

CSV ファイルを含む ZIP ファイルとして予測データをダウンロードします。

パスパラメーター:

パラメーター型説明
postIdinteger投稿 ID(URL の /questions/ の後に続く番号)

クエリパラメーター:

パラメーター型必須説明
sub_questionintegerいいえグループ質問または条件付き質問のサブ質問 ID
aggregation_methodsstring[]いいえ含める手法:recency_weighted、unweighted、metaculus_prediction、single_aggregation。デフォルト:recency_weighted のみ
include_botsbooleanいいえ集約の再計算にボットの予測を含めます
user_idsstring[]いいえ特定のユーザー ID(許可リストに登録されたユーザーのみ)。aggregation_methods が必要です。
minimizebooleanいいえデフォルト:true。false の場合、すべてのデータポイントを含めます(ファイルサイズが大きくなる場合があります)
include_commentsbooleanいいえデフォルト:false。true の場合、comment_data.csv を追加します
include_scoresbooleanいいえデフォルト:false。true の場合、score_data.csv を追加します

ZIP の内容:

ファイル説明
question_data.csv質問のメタデータ、スケーリング、解決結果
forecast_data.csv個別および集約された予測
comment_data.csvコメント(include_comments=true の場合)
score_data.csvスコア(include_scores=true の場合)

例:

curl -s "https://www.metaculus.com/api/posts/12345/download-data/?include_comments=true" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -o question_data.zip

GET /api/projects/{projectId}/download-data/ — プロジェクトデータのダウンロード

プロジェクト全体のデータを ZIP ファイルとしてダウンロードします。サイト管理者および許可リストに登録されたユーザーのみが利用できます。

パスパラメーター:

パラメーター型説明
projectIdintegerプロジェクト ID

クエリパラメーター:

パラメーター型必須説明
include_commentsbooleanいいえコメントの CSV を含めます
include_scoresbooleanいいえスコアの CSV を含めます

連続型 CDF の生成

連続型、数値型、日付型、および離散型の質問では、CDF(累積分布関数)として予測する必要があります。このセクションでは、有効な CDF の生成方法を説明します。

CDF のルール
  1. 長さ: CDF は正確に inbound_outcome_count + 1 個の値で構成する必要があります。ほとんどの連続型質問では、値の数は 201 です。離散型質問の場合は、質問によって異なります。

  2. 境界:

    • open_lower_bound == false(閉じている)の場合:最初の値は 0.0 でなければなりません
    • open_lower_bound == true(開いている)の場合:最初の値は ≥ 0.001 でなければなりません(下限より下の確率質量が少なくとも 0.1%)
    • open_upper_bound == false(閉じている)の場合:最後の値は 1.0 でなければなりません
    • open_upper_bound == true(開いている)の場合:最後の値は ≤ 0.999 でなければなりません(上限より上の確率質量が少なくとも 0.1%)
  3. 単調性: CDF は各ステップで少なくとも 0.01 / inbound_outcome_count(標準の 200 の場合、各ステップで 0.00005)ずつ厳密に増加する必要があります。

  4. 最大ステップ幅: 隣接する値の差は、いずれも 0.2 * (200 / inbound_outcome_count) を超えてはなりません。

スケーリングについて

CDF の値は、内部の [0, 1] の範囲に等間隔で配置された点に対応します。実世界の値を CDF の位置に対応付ける方法は次のとおりです。

  • 線形スケーリング(zero_point が null):internal = (value - range_min) / (range_max - range_min)
  • 対数スケーリング(zero_point が設定されている):対数変換が必要です(以下の関数を参照)
  • 日付の質問:まず ISO 日付を UNIX タイムスタンプに変換してから、スケーリングを適用します
Python:名目値から CDF の位置への変換
import datetime
import numpy as np

def nominal_location_to_cdf_location(
    nominal_location: str | float,
    question_data: dict,
) -> float:
    """Takes a location in nominal format (e.g. 123, "123",
    or datetime in iso format) and scales it to metaculus's
    "internal representation" range [0,1] incorporating question scaling"""
    if question_data["type"] == "date":
        scaled_location = datetime.datetime.fromisoformat(
            nominal_location
        ).timestamp()
    else:
        scaled_location = float(nominal_location)
    scaling = question_data["scaling"]
    range_min = scaling.get("range_min")
    range_max = scaling.get("range_max")
    zero_point = scaling.get("zero_point")
    if zero_point is not None:
        # logarithmically scaled question
        deriv_ratio = (range_max - zero_point) / (range_min - zero_point)
        unscaled_location = (
            np.log(
                (scaled_location - range_min) * (deriv_ratio - 1)
                + (range_max - range_min)
            )
            - np.log(range_max - range_min)
        ) / np.log(deriv_ratio)
    else:
        # linearly scaled question
        unscaled_location = (scaled_location - range_min) / (
            range_max - range_min
        )
    return unscaled_location
Python:パーセンタイルから CDF を生成
def generate_continuous_cdf(
    percentiles: dict,
    question_data: dict,
    below_lower_bound: float = None,
    above_upper_bound: float = None,
) -> list[float]:
    """
    Takes a set of percentiles and returns a corresponding CDF with
    inbound_outcome_count + 1 values (typically 201).

    percentiles: dict mapping percentile keys to nominal values
      Keys must end in a number interpretable as a float in (0, 100).
      Values are in the question's real-world scale.
      Example:
        {
          "percentile_05": 25,
          "percentile_25": 500,
          "percentile_50": 650,
          "percentile_75": 700,
          "percentile_95": 990,
        }

    below_lower_bound: probability mass below range_min (for open lower bound)
    above_upper_bound: probability mass above range_max (for open upper bound)
    """
    percentile_locations = []
    if below_lower_bound is not None:
        percentile_locations.append((0.0, below_lower_bound))
    if above_upper_bound is not None:
        percentile_locations.append((1.0, 1 - above_upper_bound))
    for percentile, nominal_location in percentiles.items():
        height = float(str(percentile).split("_")[-1]) / 100
        location = nominal_location_to_cdf_location(
            nominal_location, question_data
        )
        percentile_locations.append((location, height))
    percentile_locations.sort()
    first_point, last_point = percentile_locations[0], percentile_locations[-1]
    if (first_point[0] > 0.0) or (last_point[0] < 1.0):
        raise ValueError(
            "Percentiles must encompass bounds of the question"
        )

    def get_cdf_at(location):
        previous = percentile_locations[0]
        for i in range(1, len(percentile_locations)):
            current = percentile_locations[i]
            if previous[0] <= location <= current[0]:
                return previous[1] + (current[1] - previous[1]) * (
                    location - previous[0]
                ) / (current[0] - previous[0])
            previous = current

    n_points = (question_data.get("inbound_outcome_count") or 200) + 1
    continuous_cdf = [get_cdf_at(i / (n_points - 1)) for i in range(n_points)]
    return continuous_cdf
Python:送信用の CDF の標準化

この関数は、CDF がすべての検証ルールを満たすようにします。単調性を保証する小さな線形成分を追加し、境界の制約を守ります。

def standardize_cdf(cdf, question_data: dict):
    """
    Standardize a CDF for submission:
    - No mass outside closed bounds
    - Minimum mass outside open bounds
    - Strictly increasing by at least the minimum step
    - Caps maximum step size
    """
    lower_open = question_data["open_lower_bound"]
    upper_open = question_data["open_upper_bound"]
    inbound_outcome_count = question_data.get("inbound_outcome_count") or 200
    default_inbound_outcome_count = 200

    cdf = np.asarray(cdf, dtype=float)
    if not cdf.size:
        return []

    scale_lower_to = 0 if lower_open else cdf[0]
    scale_upper_to = 1.0 if upper_open else cdf[-1]
    rescaled_inbound_mass = scale_upper_to - scale_lower_to

    def standardize(F: float, location: float) -> float:
        rescaled_F = (F - scale_lower_to) / rescaled_inbound_mass
        if lower_open and upper_open:
            return 0.988 * rescaled_F + 0.01 * location + 0.001
        elif lower_open:
            return 0.989 * rescaled_F + 0.01 * location + 0.001
        elif upper_open:
            return 0.989 * rescaled_F + 0.01 * location
        return 0.99 * rescaled_F + 0.01 * location

    for i, value in enumerate(cdf):
        cdf[i] = standardize(value, i / (len(cdf) - 1))

    pmf = np.diff(cdf, prepend=0, append=1)
    cap = 0.2 * (default_inbound_outcome_count / inbound_outcome_count)

    def cap_pmf(scale: float) -> np.ndarray:
        return np.concatenate(
            [pmf[:1], np.minimum(cap, scale * pmf[1:-1]), pmf[-1:]]
        )

    def capped_sum(scale: float) -> float:
        return float(cap_pmf(scale).sum())

    lo = hi = scale = 1.0
    while capped_sum(hi) < 1.0:
        hi *= 1.2
    for _ in range(100):
        scale = 0.5 * (lo + hi)
        s = capped_sum(scale)
        if s < 1.0:
            lo = scale
        else:
            hi = scale
        if s == 1.0 or (hi - lo) < 2e-5:
            break

    pmf = cap_pmf(scale)
    pmf[1:-1] *= (cdf[-1] - cdf[0]) / pmf[1:-1].sum()
    cdf = np.cumsum(pmf)[:-1]
    cdf = np.round(cdf, 10)
    return cdf.tolist()
エンドツーエンドの例:連続型予測の送信
import os, requests, numpy as np

TOKEN = os.environ["METACULUS_API_TOKEN"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
BASE = "https://www.metaculus.com"

# 1. Fetch the question
post_id = 12345
resp = requests.get(f"{BASE}/api/posts/{post_id}/", headers=HEADERS)
resp.raise_for_status()
post = resp.json()
question = post["question"]

# 2. Define your percentile beliefs (in real-world units)
percentiles = {
    "percentile_05": 25,
    "percentile_25": 40,
    "percentile_50": 55,
    "percentile_75": 70,
    "percentile_95": 90,
}

# 3. Generate and standardize the CDF
raw_cdf = generate_continuous_cdf(
    percentiles,
    question,
    below_lower_bound=0.01,
    above_upper_bound=0.01,
)
final_cdf = standardize_cdf(raw_cdf, question)

# 4. Submit
resp = requests.post(
    f"{BASE}/api/questions/forecast/",
    headers={**HEADERS, "Content-Type": "application/json"},
    json=[{"question": question["id"], "continuous_cdf": final_cdf}],
)
resp.raise_for_status()
print("Forecast submitted!")

一般的なパターン

フィードを閲覧する
# Newest open questions
curl -s "https://www.metaculus.com/api/posts/?statuses=open&order_by=-published_at&limit=10&with_cp=true" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title, status}'
投稿のコミュニティ予測を取得する
curl -s "https://www.metaculus.com/api/posts/12345/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  | jq '.question.aggregations.recency_weighted.latest'
トレンドの質問を見つける
curl -s "https://www.metaculus.com/api/posts/?order_by=-hotness&statuses=open&limit=10&with_cp=true" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title}'
カテゴリー別に質問を検索する
curl -s "https://www.metaculus.com/api/posts/?categories=nuclear&statuses=open&with_cp=true&limit=10" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title}'
トーナメント内の質問を検索する
curl -s "https://www.metaculus.com/api/posts/?tournaments=aibq3&with_cp=true&limit=50" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {id, title, status}'
二択予測を投稿する
curl -s -X POST "https://www.metaculus.com/api/questions/forecast/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"question": 12345, "probability_yes": 0.75}]'
多肢選択予測を投稿する
curl -s -X POST "https://www.metaculus.com/api/questions/forecast/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{
    "question": 12345,
    "probability_yes_per_category": {
      "Option A": 0.4,
      "Option B": 0.35,
      "Option C": 0.25
    }
  }]'
予測を取り下げる
curl -s -X POST "https://www.metaculus.com/api/questions/withdraw/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"question": 12345}]'
投稿のコメントを読む
curl -s "https://www.metaculus.com/api/comments/?post=12345&sort=-created_at&limit=20" \
  -H "Authorization: Token $METACULUS_API_TOKEN" | jq '.results[] | {author: .author.username, text: .text}'
コメントを投稿する
curl -s -X POST "https://www.metaculus.com/api/comments/create/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "on_post": 12345,
    "text": "My analysis suggests a higher probability because...",
    "included_forecast": true,
    "is_private": false
  }'
コメントに返信する
curl -s -X POST "https://www.metaculus.com/api/comments/create/" \
  -H "Authorization: Token $METACULUS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "on_post": 12345,
    "parent": 67890,
    "text": "Good point — I updated my forecast accordingly.",
    "included_forecast": false,
    "is_private": false
  }'
Python:未解決の質問をすべてページネーションで取得する
import os, requests

TOKEN = os.environ["METACULUS_API_TOKEN"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
BASE = "https://www.metaculus.com"

all_posts = []
offset = 0
limit = 100

while True:
    resp = requests.get(
        f"{BASE}/api/posts/",
        headers=HEADERS,
        params={
            "statuses": "open",
            "limit": limit,
            "offset": offset,
            "with_cp": True,
        },
    )
    resp.raise_for_status()
    data = resp.json()
    results = data["results"]
    all_posts.extend(results)
    if data["next"] is None:
        break
    offset += limit

print(f"Fetched {len(all_posts)} open posts")
Python:大きく変動した質問を検索する
import os, requests

TOKEN = os.environ["METACULUS_API_TOKEN"]
HEADERS = {"Authorization": f"Token {TOKEN}"}
BASE = "https://www.metaculus.com"

resp = requests.get(
    f"{BASE}/api/posts/",
    headers=HEADERS,
    params={
        "statuses": "open",
        "order_by": "-weekly_movement",
        "forecast_type": "binary",
        "with_cp": True,
        "limit": 10,
    },
)
resp.raise_for_status()
for post in resp.json()["results"]:
    q = post.get("question")
    if q and q.get("aggregations"):
        latest = q["aggregations"].get("recency_weighted", {}).get("latest")
        if latest:
            prob = latest.get("centers", [None])[0]
            print(f"[{post['id']}] {post['title']} — CP: {prob}")

質問タイプの早見表

タイプ予測形式主なフィールド
binaryprobability_yes: float(0~1)—
multiple_choiceprobability_yes_per_category: {option: float}(合計 = 1.0)options、all_options_ever
numericcontinuous_cdf: float[](201 個の値)scaling、open_lower_bound、open_upper_bound、unit
datecontinuous_cdf: float[](201 個の値)scaling(range_min/max は unix タイムスタンプ)
discretecontinuous_cdf: float[](inbound_outcome_count + 1 個の値)inbound_outcome_count、scaling

投稿ステータスのライフサイクル

draft → pending → approved → open → closed → resolved
                → rejected
ステータス説明
draft作成者が編集中です
pendingキュレーターによる審査に提出されています
rejectedキュレーターによって却下されています
open承認済みで、予測を受け付けています
upcoming承認済みですが、予測受付開始前です
closed予測の受付を終了し、結果の確定を待っています
resolved最終結果が確定しています

エラー処理

ステータスコード意味対処方法
200成功—
201作成済み—
400不正なリクエストリクエスト形式、CDF の有効性、必須フィールドを確認してください
401認証されていませんAuthorization: Token ... ヘッダーを確認してください
403禁止されています権限がありません(例:プロジェクトデータのダウンロード)
404見つかりません投稿/質問/プロジェクトの ID を確認してください
429レート制限を超えています指数バックオフを実装して再試行してください
500サーバーエラー少し待ってから再試行してください。解決しない場合は Metaculus にお問い合わせください

使用上のヒント

  • コミュニティ予測が必要な場合は、投稿の一覧取得時に必ず with_cp=true を指定してください。デフォルトでは集計結果は空です。
  • include_cp_history=true は、過去の CP データが必要な場合に限って使用してください。レスポンスサイズが大幅に増加します。
  • include_descriptions=true は、必要な場合に限って使用してください。説明文は大きくなることがあります。
  • 質問 ID ≠ 投稿 ID — 予測には post.id ではなく question.id を使用します。必ず先に投稿を取得して質問 ID を確認してください。
  • グループ投稿の場合、一覧エンドポイントで with_cp=true を指定しても、上位 3 件のサブ質問の CP のみが返されます。すべてのサブ質問については、詳細エンドポイント(/api/posts/{postId}/)を使用してください。
  • CDF の生成が最も難しい部分です — 用意されている Python 関数を使うか、適宜変更してください。送信前に必ず standardize_cdf() を実行してください。
  • フィルターを組み合わせて対象を絞り込んでください。たとえば、statuses=open&forecast_type=binary&categories=nuclear とすると、効率的に結果を絞り込めます。
  • 適切にページネーションを行ってください — limit と offset を使用してください。投稿を一度にすべて取得しないでください。
  • order_by=score の並べ替えには forecaster_id が必要です — 特定のユーザーの成績に基づいて質問を順位付けします。
  • 日付の質問では scaling.range_min と scaling.range_max に unix タイムスタンプを使用します — CDF の位置に対応させる前に、ISO 日付をタイムスタンプに変換してください。
  • レート制限を守ってください — 429 レスポンスを受け取った場合はバックオフを実装してください。

リソース

リソースURL
Metaculus プラットフォームmetaculus.com
API ドキュメントmetaculus.com/api
アカウント設定(API トークン)metaculus.com/accounts/settings
GitHub の課題github.com/Metaculus/metaculus
お問い合わせ先[email protected]

コメント