Metaculus-Hacks

A collection of bookmarklets to do stuff on Metaculus

View on GitHub

About

I wrote these scripts at various points for my own benefit. Recently it occurred to me that maybe someone else could also use them, so I wrapped them up as bookmarklets. Hence, this site.

How to use these

Bookmarklets are an ancient bit of web technology which embeds Javascript in a URL. The idea is, you copy the bookmarklet to your bookmarks bar, and then click it on a site to execute the javascript embedded in it. In general, bookmarklets are kind of dangerous and so not widely used anymore (Browser plugins are now preferred.) But it’s a really fast and simple way to create custom code that you can kick off easily. I promise that these don’t do anything nefarious! If you’re paranoid and can speak Javascript, I’ve included the code below each bookmarklet, and you can URL-decode the bookmarklet to confirm that they don’t send data anywhere it isn’t supposed to go.

Anyway, to use these:

  1. Show your bookmarks Bar.
  2. Find the bookmarklet that does the thing you want. The actual bookmarklets are the underlined text shown in red below.
  3. Drag that bookmarklet to your bookmarks bar, and drop it there. It should now appear there, even if you surf away from this site.
  4. Go to Metaculus (or the specific subdomain in which you’re interested).
  5. Go to the place where you can see the data you want. If you want a time-series of predictions on a single question, go to that question’s page. If you want data on track records or all questions, you can probably do that most anywhere on the site. Bookmarklets with specific rules are noted in their descriptions.
  6. Click your bookmarklet. Whatever should happen, uh, should happen. If it doesn’t, please let me know.

Disclaimer

For Forecasters

Toggle the Community Estimate

Requested by Michal Dubrawski. Keep yourself epistemically honest! Make predictions before you see the community estimate. Click the bookmarklet to toggle the visibility of the community estimate distribution information.

Toggle Community Estimates

(self => {
  if(typeof metHackData == "undefined") metHackData = {communityDisplay: "flex"};
  if(metHackData.communityDisplay == "flex") metHackData.communityDisplay = "none";
  else metHackData.communityDisplay = "flex";
  [
    'svg.ng-scope',
    '.prediction-toggle',
    'div.ng-scope > svg.metac-graph',
    '[ng-if="question.communityPrediction()"]',
    '.handle.avg-guess',
    '.label.avg-guess',
    '.binary_stats'
  ].forEach(s => {
  	document.querySelectorAll(s).forEach(el => {
  	  el.style.display=metHackData.communityDisplay;
  	});
  });
})(self);

Predict the Community Estimate (binary questions only)

If you don’t predict on everything, you’ll fall off the leaderboard. But let’s be honest, nobody’s interested in every question. Sometimes you just want to peg your prediction to the community estimate and move on. This bookmarklet does it for you! On a Binary question page (only binary questions for now!), click this bookmarklet and you’ll register a prediction equal to the community estimate.

Predict Community Estimate

(() => {
  let p = metacData.question.prediction_timeseries;
  fetch(`/api2/questions/${metacData.question.id}/predict/`, {
    method: 'POST',
    headers: {
      'Accept': 'application/json, text/plain, */*',
      'Content-Type': 'application/json;charset=utf-8',
      'X-CSRFToken': document.cookie.split('=')[1],
      'x-requested-with': 'XMLHttpRequest'
    },
    body: JSON.stringify({prediction: p[p.length-1].community_prediction, void: false})
  });
})();

Load Question in Elicit (Continuous, Linear Scale Questions only for now)

The incredible team at Ought has created an awesome tool called Elicit to help update continuous distributions. As part of their offering, they included a bookmarklet, and agreed to let me host a copy here. Thanks @ought!

Launch in Elicit

Download Your Track Record (binary questions only, as csv)

This makes use of the super-secret official-but-unsupported Metaculus API, which only reports binary questions. If you want your track record across all questions, there’s a bookmarklet for that too (but please note that it’s in the danger zone!)

Download My Track Record

(() => {
  const loadScript = async function(url){
    let script   = document.createElement("script");
    script.type  = "text/javascript";
    await fetch(url).then(r => r.text().then(s => script.innerHTML = s));
    document.body.appendChild(script);
  };

  Promise.all([
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.1.0/papaparse.min.js"),
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js")
  ]).then(() => {
      fetch(`https://www.metaculus.com/accounts/profile/${metacData.user.id}/track-record-export/`).then(data1 => data1.json().then(predictions => {
        saveAs(new Blob([Papa.unparse(predictions)], {type: "text/csv;charset=utf-8"}), "track-record.csv");
      }));
    });
})();

Download My Predictions From a Question (as CSV)

For binary questions, will work about how you expect. For range questions, it’s a little more complex–each prediction includes an array components (one for each peak in a multimodal prediction), normalized to a 0-1 scale (or something close to it). I haven’t yet figured out how to translate the component values onto the question’s actual range, but I’m pretty sure I’ve got the values that correspond to the mean, spread, and weight figured out. Those are reported (though not translated into the actual question scale) as component{i}mean, component{i}weight, and component{i}spread (where {i} is the component’s 0-based ordinal index in the array). (Inspired by this feature request.)

Download My Predictions on this Question

(() => {
  const loadScript = async function(url){
    let script   = document.createElement("script");
    script.type  = "text/javascript";
    await fetch(url).then(r => r.text().then(s => script.innerHTML = s));
    document.body.appendChild(script);
  };

  Promise.all([
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.1.0/papaparse.min.js"),
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js")
  ]).then(() => {
      let myPredictions;
      if(metacData.question.possibilities.type == 'binary'){
        myPredictions = metacData.question.my_predictions.predictions.map(p => ({
          time: new Date(p.t*1000).toISOString(),
          prediction: p.x
        }));
      } else {
        myPredictions = metacData.question.my_predictions.predictions.map(p => {
          let prediction = { time: new Date(p.t*1000).toISOString() };
          p.d.forEach((c, i) => {
            prediction[`component${i}mean`] = c.x0;
            prediction[`component${i}spread`] = c.s;
            prediction[`component${i}weight`] = c.w;
          });
          return prediction;
        });
      }
      saveAs(new Blob([Papa.unparse(myPredictions)], {type: "text/csv;charset=utf-8"}), "my-predictions.csv");
    });
})();

For Question Authors

Show my Pending Questions

Suppose in addition to forecasting, you write a lot of questions. Sometimes, some of those will get stuck in moderation for awhile. In times like these, it would be helpful to be able to set the status filter to “Pending”, but that isn’t an option. Sad! Fortunately, we can do this ourselves.

Pending Only, Please!

document.querySelectorAll('question-table-row').forEach(x => {
  if(x.innerText.indexOf('Pending') == -1) x.remove();
});

For Data Junkies

Download the Metaculus Track Record (binary questions only, as csv)

If you want to analyze Metaculus’ Track record (again, on Binary questions only), you can get that data (also from the super-secret official-but-unsupported Metaculus API) by running this Bookmarklet.

Download Metaculus Track Record

(() => {
  const loadScript = async function(url){
    let script   = document.createElement("script");
    script.type  = "text/javascript";
    await fetch(url).then(r => r.text().then(s => script.innerHTML = s));
    document.body.appendChild(script);
  };

  Promise.all([
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.1.0/papaparse.min.js"),
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js")
  ]).then(() => {
      fetch(`https://www.metaculus.com/questions/track-record/export/`).then(data1 => data1.json().then(predictions => {
        saveAs(new Blob([Papa.unparse(predictions)], {type: "text/csv;charset=utf-8"}), "metaculus-track-record.csv");
      }));
    });
})();

Download a Question’s Prediction Data

Metaculus exposes a time-series of a question’s distribution of predictions in order to render the question’s line plot. Using this bookmarklet, we can extract the raw data from the question page and export it to CSV.

Download Prediction Time-series

(() => {
  const loadScript = async function(url){
    let script   = document.createElement("script");
    script.type  = "text/javascript";
    await fetch(url).then(r => r.text().then(s => script.innerHTML = s));
    document.body.appendChild(script);
  };

  Promise.all([
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.1.0/papaparse.min.js"),
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js")
  ]).then(() => saveAs(new Blob([Papa.unparse(metacData.question.community_prediction.history.map(p => Object.assign(p.x1, {np: p.np, nu: p.nu, time: p.time.toISOString()})))], {type: "text/csv;charset=utf-8"}), "predictions.csv"));
})();

Download a Community Prediction Plot (as PNG)

The graphs on Metaculus are rendered using SVG, which is awesome for two things: 1. the web and 2. print. Not so good for exporting (e.g. into a document). Luckily it can be converted into PNG without too much trouble. This means we can write a hack to download a PNG of a graph.

Things to be aware of:

Export Graph

(() => {
  const loadScript = async function(url){
    let script   = document.createElement("script");
    script.type  = "text/javascript";
    await fetch(url).then(r => r.text().then(s => script.innerHTML = s));
    document.body.appendChild(script);
  };

  loadScript("https://unpkg.com/save-svg-as-png@1.4.17/lib/saveSvgAsPng.js").then(() => {
    saveSvgAsPng(document.querySelectorAll("svg.metac-graph")[0], "diagram.png", {scale: 10});
  });
})();

Danger Zone

Please don’t abuse bookmarklets below this. They rely on repeated calls to the Metaculus API, meaning that hammering them could hurt the Metaculus Server.

Disclaimer Addendum

Load All Questions

Ever get annoyed that you have to click “Load More” a bunch to browse down to older questions? Just click this bookmarklet to automatically click it until all questions are loaded. Loads a maximum of 300 questions per second (plus additional latency), so it won’t happen immediately, but it’s still faster than clicking it yourself.

Load All Questions

(() => setInterval(() => document.querySelector('._load-more').click(), 100)})();

Download Your Track Record (All questions, as csv)

Same idea as above, only this one side-steps the API to get all question types, not just binary questions. Makes a bunch of fetch calls, so it can take a minute. (Inspired by this feature request.)

Download My Full Track Record

(() => {
  const loadScript = async function(url){
    let script   = document.createElement("script");
    script.type  = "text/javascript";
    await fetch(url).then(r => r.text().then(s => script.innerHTML = s));
    document.body.appendChild(script);
  };

  Promise.all([
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.1.0/papaparse.min.js"),
    loadScript("https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js")
  ]).then(() => {
      fetch(`https://www.metaculus.com/api2/questions/?guessed_by=${metacData.user.id}&order_by=-activity&page=1`).then(data1 => data1.json().then(predictions => {
        let n = Math.ceil(predictions.count/20);
        let requests = [];
        for(let i = 2; i <= n; i++){
          requests.push(
            fetch(`https://www.metaculus.com/api2/questions/?guessed_by=${metacData.user.id}&order_by=-activity&page=${i}`).then(data2 => data2.json().then(page => {
              predictions.results = predictions.results.concat(page.results);
            }))
          );
        }
        Promise.all(requests).then(() => saveAs(new Blob([Papa.unparse(predictions.results)], {type: "text/csv;charset=utf-8"}), "track-record.csv"));
      }));
    });
})();

Download all my predictions (as JSON)

Runs from your user profile page. Queries the metaculus server for every question page on which you’ve ever predicted. It will take a few seconds (at least) to assemble all your data. DO NOT ABUSE IT. Run it once and save the output. This one could seriously kill the server. (Inspired by this feature request.)

Make me a big JSON of every prediction I’ve ever made

(() => {
  const loadScript = async function(url){
    let script   = document.createElement("script");
    script.type  = "text/javascript";
    await fetch(url).then(r => r.text().then(s => script.innerHTML = s));
    document.body.appendChild(script);
  };

  loadScript("https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js").then(() => {
    let allRequests = [];
    let allQuestions = [];
    for(let i = 0; i < metacData.trackRecord.length; i++){
      allRequests.push(fetch(metacData.trackRecord[i].url).then(r => r.text().then(t => {
        allQuestions.push(JSON.parse(/window.metacData.question = (.*);/.exec(t)[1]));
      })));
    }
    Promise.all(allRequests).then(() => saveAs(new Blob([JSON.stringify(allQuestions)], {type: "application/json;charset=utf-8"}), "predictions.json"));
  });
})();

Suggest a Hack

Think I might be able to make Metaculus work a little better for you? Suggest a new hack and I’ll look into implementing it!