feat: improve smarkets; cli args and partial fetchers

This commit is contained in:
Vyacheslav Matyukhin 2022-05-12 17:58:56 +04:00
parent f37a49e398
commit fcbc627d1d
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
21 changed files with 311 additions and 199 deletions

View File

@ -4,18 +4,20 @@ import { platforms, processPlatform } from "../platforms";
import { rebuildAlgoliaDatabase } from "../utils/algolia"; import { rebuildAlgoliaDatabase } from "../utils/algolia";
import { sleep } from "../utils/sleep"; import { sleep } from "../utils/sleep";
interface Job { interface Job<ArgNames extends string = ""> {
name: string; name: string;
message: string; message: string;
run: () => Promise<void>; args?: ArgNames[];
run: (args?: { [k in ArgNames]: string }) => Promise<void>;
separate?: boolean; separate?: boolean;
} }
export const jobs: Job[] = [ export const jobs: Job<string>[] = [
...platforms.map((platform) => ({ ...platforms.map((platform) => ({
name: platform.name, name: platform.name,
message: `Download predictions from ${platform.name}`, message: `Download predictions from ${platform.name}`,
run: () => processPlatform(platform), ...(platform.version === "v2" ? { args: platform.fetcherArgs } : {}),
run: (args: any) => processPlatform(platform, args),
})), })),
{ {
name: "algolia", name: "algolia",
@ -35,27 +37,39 @@ export const jobs: Job[] = [
}, },
]; ];
async function tryCatchTryAgain(fun: () => Promise<void>) { async function tryCatchTryAgain<T extends object = never>(
fun: (args: T) => Promise<void>,
args: T
) {
try { try {
console.log("Initial try"); console.log("Initial try");
await fun(); await fun(args);
} catch (error) { } catch (error) {
sleep(10000); sleep(10000);
console.log("Second try"); console.log("Second try");
console.log(error); console.log(error);
try { try {
await fun(); await fun(args);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
} }
export const executeJobByName = async (option: string) => { export const executeJobByName = async (
const job = jobs.find((job) => job.name === option); jobName: string,
jobArgs: { [k: string]: string } = {}
) => {
const job = jobs.find((job) => job.name === jobName);
if (!job) { if (!job) {
console.log(`Error, job ${option} not found`); console.log(`Error, job ${jobName} not found`);
} else { return;
await tryCatchTryAgain(job.run);
} }
for (const key of Object.keys(jobArgs)) {
if (!job.args || job.args.indexOf(key) < 0) {
throw new Error(`Job ${jobName} doesn't accept ${key} argument`);
}
}
await tryCatchTryAgain(job.run, jobArgs);
}; };

View File

@ -24,31 +24,54 @@ const generateWhatToDoMessage = () => {
const whattodoMessage = generateWhatToDoMessage(); const whattodoMessage = generateWhatToDoMessage();
/* BODY */ const askForJobName = async () => {
const commandLineUtility = async () => { const rl = readline.createInterface({
const pickOption = async () => { input: process.stdin,
if (process.argv.length === 3) { output: process.stdout,
return process.argv[2]; // e.g., npm run cli polymarket });
}
const rl = readline.createInterface({ const question = (query: string) => {
input: process.stdin, return new Promise((resolve: (s: string) => void) => {
output: process.stdout, rl.question(query, resolve);
}); });
const question = (query: string) => {
return new Promise((resolve: (s: string) => void) => {
rl.question(query, resolve);
});
};
const answer = await question(whattodoMessage);
rl.close();
return answer;
}; };
await executeJobByName(await pickOption()); const answer = await question(whattodoMessage);
rl.close();
return answer;
};
const pickJob = async (): Promise<[string, { [k: string]: string }]> => {
if (process.argv.length < 3) {
const jobName = await askForJobName();
return [jobName, {}]; // e.g., npm run cli polymarket
}
const jobName = process.argv[2];
if ((process.argv.length - 3) % 2) {
throw new Error("Number of extra arguments must be even");
}
const args: { [k: string]: string } = {};
for (let i = 3; i < process.argv.length; i += 2) {
let argName = process.argv[i];
const argValue = process.argv[i + 1];
if (argName.slice(0, 2) !== "--") {
throw new Error(`${argName} should start with --`);
}
argName = argName.slice(2);
args[argName] = argValue;
}
return [jobName, args];
};
/* BODY */
const commandLineUtility = async () => {
const [jobName, jobArgs] = await pickJob();
await executeJobByName(jobName, jobArgs);
process.exit(); process.exit();
}; };

View File

@ -59,6 +59,7 @@ export const example: Platform = {
name: platformName, name: platformName,
label: "Example platform", label: "Example platform",
color: "#ff0000", color: "#ff0000",
version: "v1",
async fetcher() { async fetcher() {
let data = await fetchData(); let data = await fetchData();
let results = await processPredictions(data); // somehow needed let results = await processPredictions(data); // somehow needed

View File

@ -141,6 +141,7 @@ export const betfair: Platform = {
name: platformName, name: platformName,
label: "Betfair", label: "Betfair",
color: "#3d674a", color: "#3d674a",
version: "v1",
async fetcher() { async fetcher() {
const data = await fetchPredictions(); const data = await fetchPredictions();
const results = await processPredictions(data); const results = await processPredictions(data);

View File

@ -113,6 +113,7 @@ export const fantasyscotus: Platform = {
name: platformName, name: platformName,
label: "FantasySCOTUS", label: "FantasySCOTUS",
color: "#231149", color: "#231149",
version: "v1",
async fetcher() { async fetcher() {
let rawData = await fetchData(); let rawData = await fetchData();
let results = await processData(rawData); let results = await processData(rawData);

View File

@ -62,6 +62,7 @@ export const foretold: Platform = {
name: platformName, name: platformName,
label: "Foretold", label: "Foretold",
color: "#62520b", color: "#62520b",
version: "v1",
async fetcher() { async fetcher() {
let results: FetchedQuestion[] = []; let results: FetchedQuestion[] = [];
for (let community of highQualityCommunities) { for (let community of highQualityCommunities) {

View File

@ -68,6 +68,7 @@ export const givewellopenphil: Platform = {
name: platformName, name: platformName,
label: "GiveWell/OpenPhilanthropy", label: "GiveWell/OpenPhilanthropy",
color: "#32407e", color: "#32407e",
version: "v1",
async fetcher() { async fetcher() {
// main1() // main1()
return; // not necessary to refill the DB every time return; // not necessary to refill the DB every time

View File

@ -15,6 +15,7 @@ export const goodjudgment: Platform = {
name: platformName, name: platformName,
label: "Good Judgment", label: "Good Judgment",
color: "#7d4f1b", color: "#7d4f1b",
version: "v1",
async fetcher() { async fetcher() {
// Proxy fuckery // Proxy fuckery
// let proxy; // let proxy;

View File

@ -231,6 +231,7 @@ export const goodjudgmentopen: Platform = {
name: platformName, name: platformName,
label: "Good Judgment Open", label: "Good Judgment Open",
color: "#002455", color: "#002455",
version: "v1",
async fetcher() { async fetcher() {
let cookie = process.env.GOODJUDGMENTOPENCOOKIE; let cookie = process.env.GOODJUDGMENTOPENCOOKIE;
return (await applyIfSecretExists(cookie, goodjudgmentopen_inner)) || null; return (await applyIfSecretExists(cookie, goodjudgmentopen_inner)) || null;

View File

@ -92,6 +92,7 @@ export const guesstimate: Platform & {
label: "Guesstimate", label: "Guesstimate",
color: "#223900", color: "#223900",
search, search,
version: "v1",
fetchQuestion, fetchQuestion,
calculateStars: (q) => (q.description.length > 250 ? 2 : 1), calculateStars: (q) => (q.description.length > 250 ? 2 : 1),
}; };

View File

@ -51,25 +51,42 @@ export type FetchedQuestion = Omit<
}; };
// fetcher should return null if platform failed to fetch questions for some reason // fetcher should return null if platform failed to fetch questions for some reason
export type PlatformFetcher = () => Promise<FetchedQuestion[] | null>; type PlatformFetcherV1 = () => Promise<FetchedQuestion[] | null>;
export interface Platform { type PlatformFetcherV2Result = {
questions: FetchedQuestion[];
// if partial is true then we won't cleanup old questions from the database; this is useful when manually invoking a fetcher with arguments for updating a single question
partial: boolean;
} | null;
type PlatformFetcherV2<ArgNames extends string> = (opts: {
args?: { [k in ArgNames]: string };
}) => Promise<PlatformFetcherV2Result>;
export type PlatformFetcher<ArgNames extends string> =
| PlatformFetcherV1
| PlatformFetcherV2<ArgNames>;
// using "" as ArgNames default is technically incorrect, but shouldn't cause any real issues
// (I couldn't find a better solution for signifying an empty value, though there probably is one)
export type Platform<ArgNames extends string = ""> = {
name: string; // short name for ids and `platform` db column, e.g. "xrisk" name: string; // short name for ids and `platform` db column, e.g. "xrisk"
label: string; // longer name for displaying on frontend etc., e.g. "X-risk estimates" label: string; // longer name for displaying on frontend etc., e.g. "X-risk estimates"
color: string; // used on frontend color: string; // used on frontend
fetcher?: PlatformFetcher;
calculateStars: (question: FetchedQuestion) => number; calculateStars: (question: FetchedQuestion) => number;
} } & (
| {
version: "v1";
fetcher?: PlatformFetcherV1;
}
| {
version: "v2";
fetcherArgs?: ArgNames[];
fetcher?: PlatformFetcherV2<ArgNames>;
}
);
// draft for the future callback-based streaming/chunking API: export const platforms: Platform<string>[] = [
// interface FetchOptions {
// since?: string; // some kind of cursor, Date object or opaque string?
// save: (questions: Question[]) => Promise<void>;
// }
// export type PlatformFetcher = (options: FetchOptions) => Promise<void>;
export const platforms: Platform[] = [
betfair, betfair,
fantasyscotus, fantasyscotus,
foretold, foretold,
@ -104,7 +121,7 @@ type PreparedQuestion = Omit<
export const prepareQuestion = ( export const prepareQuestion = (
q: FetchedQuestion, q: FetchedQuestion,
platform: Platform platform: Platform<any>
): PreparedQuestion => { ): PreparedQuestion => {
return { return {
extra: {}, extra: {},
@ -118,12 +135,26 @@ export const prepareQuestion = (
}; };
}; };
export const processPlatform = async (platform: Platform) => { export const processPlatform = async <T extends string = "">(
platform: Platform<T>,
args?: { [k in T]: string }
) => {
if (!platform.fetcher) { if (!platform.fetcher) {
console.log(`Platform ${platform.name} doesn't have a fetcher, skipping`); console.log(`Platform ${platform.name} doesn't have a fetcher, skipping`);
return; return;
} }
const fetchedQuestions = await platform.fetcher(); const result =
platform.version === "v1"
? { questions: await platform.fetcher(), partial: false } // this is not exactly PlatformFetcherV2Result, since `questions` can be null
: await platform.fetcher({ args });
if (!result) {
console.log(`Platform ${platform.name} didn't return any results`);
return;
}
const { questions: fetchedQuestions, partial } = result;
if (!fetchedQuestions || !fetchedQuestions.length) { if (!fetchedQuestions || !fetchedQuestions.length) {
console.log(`Platform ${platform.name} didn't return any results`); console.log(`Platform ${platform.name} didn't return any results`);
return; return;
@ -154,24 +185,32 @@ export const processPlatform = async (platform: Platform) => {
} }
} }
const stats: { created?: number; updated?: number; deleted?: number } = {};
await prisma.question.createMany({ await prisma.question.createMany({
data: createdQuestions, data: createdQuestions,
}); });
stats.created = createdQuestions.length;
for (const q of updatedQuestions) { for (const q of updatedQuestions) {
await prisma.question.update({ await prisma.question.update({
where: { id: q.id }, where: { id: q.id },
data: q, data: q,
}); });
stats.updated ??= 0;
stats.updated++;
} }
await prisma.question.deleteMany({ if (!partial) {
where: { await prisma.question.deleteMany({
id: { where: {
in: deletedIds, id: {
in: deletedIds,
},
}, },
}, });
}); stats.deleted = deletedIds.length;
}
await prisma.history.createMany({ await prisma.history.createMany({
data: [...createdQuestions, ...updatedQuestions].map((q) => ({ data: [...createdQuestions, ...updatedQuestions].map((q) => ({
@ -181,7 +220,10 @@ export const processPlatform = async (platform: Platform) => {
}); });
console.log( console.log(
`Done, ${deletedIds.length} deleted, ${updatedQuestions.length} updated, ${createdQuestions.length} created` "Done, " +
Object.entries(stats)
.map(([k, v]) => `${v} ${k}`)
.join(", ")
); );
}; };

View File

@ -230,6 +230,7 @@ export const infer: Platform = {
name: platformName, name: platformName,
label: "Infer", label: "Infer",
color: "#223900", color: "#223900",
version: "v1",
async fetcher() { async fetcher() {
let cookie = process.env.INFER_COOKIE; let cookie = process.env.INFER_COOKIE;
return (await applyIfSecretExists(cookie, infer_inner)) || null; return (await applyIfSecretExists(cookie, infer_inner)) || null;

View File

@ -68,6 +68,7 @@ export const kalshi: Platform = {
name: platformName, name: platformName,
label: "Kalshi", label: "Kalshi",
color: "#615691", color: "#615691",
version: "v1",
fetcher: async function () { fetcher: async function () {
let markets = await fetchAllMarkets(); let markets = await fetchAllMarkets();
return await processMarkets(markets); return await processMarkets(markets);

View File

@ -88,6 +88,7 @@ export const manifold: Platform = {
name: platformName, name: platformName,
label: "Manifold Markets", label: "Manifold Markets",
color: "#793466", color: "#793466",
version: "v1",
async fetcher() { async fetcher() {
let data = await fetchData(); let data = await fetchData();
let results = processPredictions(data); // somehow needed let results = processPredictions(data); // somehow needed

View File

@ -98,6 +98,7 @@ export const metaculus: Platform = {
name: platformName, name: platformName,
label: "Metaculus", label: "Metaculus",
color: "#006669", color: "#006669",
version: "v1",
async fetcher() { async fetcher() {
// let metaculusQuestionsInit = await fetchMetaculusQuestions(1) // let metaculusQuestionsInit = await fetchMetaculusQuestions(1)
// let numQueries = Math.round(Number(metaculusQuestionsInit.count) / 20) // let numQueries = Math.round(Number(metaculusQuestionsInit.count) / 20)

View File

@ -67,6 +67,7 @@ export const polymarket: Platform = {
name: platformName, name: platformName,
label: "PolyMarket", label: "PolyMarket",
color: "#00314e", color: "#00314e",
version: "v1",
async fetcher() { async fetcher() {
let results: FetchedQuestion[] = []; let results: FetchedQuestion[] = [];
let webpageEndpointData = await fetchAllContractInfo(); let webpageEndpointData = await fetchAllContractInfo();

View File

@ -40,6 +40,7 @@ export const predictit: Platform = {
name: platformName, name: platformName,
label: "PredictIt", label: "PredictIt",
color: "#460c00", color: "#460c00",
version: "v1",
async fetcher() { async fetcher() {
let markets = await fetchmarkets(); let markets = await fetchmarkets();
let marketVolumes = await fetchmarketvolumes(); let marketVolumes = await fetchmarketvolumes();

View File

@ -48,6 +48,7 @@ export const rootclaim: Platform = {
name: platformName, name: platformName,
label: "Rootclaim", label: "Rootclaim",
color: "#0d1624", color: "#0d1624",
version: "v1",
async fetcher() { async fetcher() {
const claims = await fetchAllRootclaims(); const claims = await fetchAllRootclaims();
const results: FetchedQuestion[] = []; const results: FetchedQuestion[] = [];

View File

@ -6,23 +6,45 @@ import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "smarkets"; const platformName = "smarkets";
let htmlEndPointEntrance = "https://api.smarkets.com/v3/events/"; const apiEndpoint = "https://api.smarkets.com/v3"; // documented at https://docs.smarkets.com/
let VERBOSE = false;
type Context = {
verbose: boolean;
};
/* Support functions */ /* Support functions */
async function fetchEvents(url: string) { async function fetchEvents(ctx: Context) {
const response = await axios({ let queryString =
url: htmlEndPointEntrance + url, "?state=new&state=upcoming&state=live&type_domain=politics&type_scope=single_event&with_new_type=true&sort=id&limit=50";
method: "GET",
}).then((res) => res.data); let events = [];
VERBOSE && console.log(response); while (queryString) {
return response; const data = await axios({
url: `${apiEndpoint}/events/${queryString}`,
method: "GET",
}).then((res) => res.data);
events.push(...data.events);
queryString = data.pagination.next_page;
}
ctx.verbose && console.log(events);
return events;
} }
async function fetchMarkets(eventid: string) { async function fetchSingleEvent(id: string, ctx: Context) {
const events = await fetchEvents(ctx);
const event = events.find((event) => event.id === id);
if (!event) {
throw new Error(`Event ${id} not found`);
}
return event;
}
async function fetchMarkets(eventId: string) {
const response = await axios({ const response = await axios({
url: `https://api.smarkets.com/v3/events/${eventid}/markets/`, url: `${apiEndpoint}/events/${eventId}/markets/`,
method: "GET", method: "GET",
}) })
.then((res) => res.data) .then((res) => res.data)
@ -30,12 +52,12 @@ async function fetchMarkets(eventid: string) {
return response; return response;
} }
async function fetchContracts(marketid: string) { async function fetchContracts(marketId: string, ctx: Context) {
const response = await axios({ const response = await axios({
url: `https://api.smarkets.com/v3/markets/${marketid}/contracts/`, url: `${apiEndpoint}/markets/${marketId}/contracts/?include_hidden=true`,
method: "GET", method: "GET",
}).then((res) => res.data); }).then((res) => res.data);
VERBOSE && console.log(response); ctx.verbose && console.log(response);
if (!(response.contracts instanceof Array)) { if (!(response.contracts instanceof Array)) {
throw new Error("Invalid response while fetching contracts"); throw new Error("Invalid response while fetching contracts");
@ -43,154 +65,148 @@ async function fetchContracts(marketid: string) {
return response.contracts as any[]; return response.contracts as any[];
} }
async function fetchPrices(marketid: string) { async function fetchPrices(marketId: string, ctx: Context) {
const response = await axios({ const response = await axios({
url: `https://api.smarkets.com/v3/markets/${marketid}/last_executed_prices/`, url: `https://api.smarkets.com/v3/markets/${marketId}/last_executed_prices/`,
method: "GET", method: "GET",
}).then((res) => res.data); }).then((res) => res.data);
VERBOSE && console.log(response); ctx.verbose && console.log(response);
if (!response.last_executed_prices) { if (!response.last_executed_prices) {
throw new Error("Invalid response while fetching prices"); throw new Error("Invalid response while fetching prices");
} }
return response.last_executed_prices; return response.last_executed_prices;
} }
export const smarkets: Platform = { async function processEventMarkets(event: any, ctx: Context) {
ctx.verbose && console.log(Date.now());
ctx.verbose && console.log(event.name);
let markets = await fetchMarkets(event.id);
markets = markets.map((market: any) => ({
...market,
// smarkets doesn't have separate urls for different markets in a single event
// we could use anchors (e.g. https://smarkets.com/event/886716/politics/uk/uk-party-leaders/next-conservative-leader#contract-collapse-9815728-control), but it's unclear if they aren't going to change
slug: event.full_slug,
}));
ctx.verbose && console.log(`Markets for ${event.id} fetched`);
ctx.verbose && console.log(markets);
let results: FetchedQuestion[] = [];
for (const market of markets) {
ctx.verbose && console.log("================");
ctx.verbose && console.log("Market:", market);
const contracts = await fetchContracts(market.id, ctx);
ctx.verbose && console.log("Contracts:", contracts);
const prices = await fetchPrices(market.id, ctx);
ctx.verbose && console.log("Prices:", prices[market.id]);
let optionsObj: {
[k: string]: QuestionOption;
} = {};
const contractsById = Object.fromEntries(
contracts.map((c) => [c.id as string, c])
);
for (const price of prices[market.id]) {
const contract = contractsById[price.contract_id];
if (!contract) {
console.warn(
`Couldn't find contract ${price.contract_id} in contracts data for ${market.id}, event ${market.event_id}, skipping`
);
continue;
}
optionsObj[price.contract_id] = {
name: contract.name,
probability: contract.hidden ? 0 : Number(price.last_executed_price),
type: "PROBABILITY",
};
}
let options: QuestionOption[] = Object.values(optionsObj);
ctx.verbose && console.log("Options before patching:", options);
// monkey patch the case where there are only two options and only one has traded.
if (
options.length === 2 &&
options.map((option) => option.probability).includes(0)
) {
const nonNullPrice = options[0].probability || options[1].probability;
if (nonNullPrice) {
options = options.map((option) => {
return {
...option,
probability: option.probability || 100 - nonNullPrice,
// yes, 100, because prices are not yet normalized.
};
});
}
}
ctx.verbose && console.log("Options after patching:", options);
// Normalize normally
const totalValue = options
.map((element) => Number(element.probability))
.reduce((a, b) => a + b, 0);
options = options.map((element) => ({
...element,
probability: Number(element.probability) / totalValue,
}));
ctx.verbose && console.log("Normalized options:", options);
const result: FetchedQuestion = {
id: `${platformName}-${market.id}`,
title: market.name,
url: "https://smarkets.com/event/" + market.event_id + market.slug,
description: market.description,
options,
timestamp: new Date(),
qualityindicators: {},
};
ctx.verbose && console.log(result);
results.push(result);
}
return results;
}
export const smarkets: Platform<"eventId" | "verbose"> = {
name: platformName, name: platformName,
label: "Smarkets", label: "Smarkets",
color: "#6f5b41", color: "#6f5b41",
async fetcher() { version: "v2",
let htmlPath = fetcherArgs: ["eventId", "verbose"],
"?state=new&state=upcoming&state=live&type_domain=politics&type_scope=single_event&with_new_type=true&sort=id&limit=50"; async fetcher(opts) {
const ctx = {
verbose: Boolean(opts.args?.verbose) || false,
};
let events = []; let events: any[] = [];
while (htmlPath) { let partial = true;
const data = await fetchEvents(htmlPath); if (opts.args?.eventId) {
events.push(...data.events); events = [await fetchSingleEvent(opts.args.eventId, ctx)];
htmlPath = data.pagination.next_page; } else {
events = await fetchEvents(ctx);
partial = false;
} }
VERBOSE && console.log(events);
let markets = []; let results: FetchedQuestion[] = [];
for (const event of events) { for (const event of events) {
VERBOSE && console.log(Date.now()); const eventResults = await processEventMarkets(event, ctx);
VERBOSE && console.log(event.name); results.push(...eventResults);
let eventMarkets = await fetchMarkets(event.id);
eventMarkets = eventMarkets.map((market: any) => ({
...market,
// smarkets doesn't have separate urls for different markets in a single event
// we could use anchors (e.g. https://smarkets.com/event/886716/politics/uk/uk-party-leaders/next-conservative-leader#contract-collapse-9815728-control), but it's unclear if they aren't going to change
slug: event.full_slug,
}));
VERBOSE && console.log("Markets fetched");
VERBOSE && console.log(event.id);
VERBOSE && console.log(eventMarkets);
markets.push(...eventMarkets);
} }
VERBOSE && console.log(markets); return {
questions: results,
let results = []; partial,
for (let market of markets) { };
VERBOSE && console.log("================");
VERBOSE && console.log("Market: ", market);
let contracts = await fetchContracts(market.id);
VERBOSE && console.log("Contracts: ", contracts);
let prices = await fetchPrices(market.id);
VERBOSE && console.log("Prices: ", prices[market.id]);
let optionsObj: {
[k: string]: QuestionOption;
} = {};
const contractIdToName = Object.fromEntries(
contracts.map((c) => [c.id as string, c.name as string])
);
for (const price of prices[market.id]) {
const contractName = contractIdToName[price.contract_id];
if (!contractName) {
console.warn(
`Couldn't find contract ${price.contract_id} in contracts data, skipping`
);
continue;
}
optionsObj[price.contract_id] = {
name: contractName,
probability: price.last_executed_price
? Number(price.last_executed_price)
: undefined,
type: "PROBABILITY",
};
}
let options: QuestionOption[] = Object.values(optionsObj);
// monkey patch the case where there are only two options and only one has traded.
if (
options.length == 2 &&
options.map((option) => option.probability).includes(undefined)
) {
const nonNullPrice =
options[0].probability == null
? options[1].probability
: options[0].probability;
if (nonNullPrice != null) {
options = options.map((option) => {
let probability = option.probability;
return {
...option,
probability:
probability == null ? 100 - nonNullPrice : probability,
// yes, 100, because prices are not yet normalized.
};
});
}
}
// Normalize normally
const totalValue = options
.map((element) => Number(element.probability))
.reduce((a, b) => a + b, 0);
options = options.map((element) => ({
...element,
probability: Number(element.probability) / totalValue,
}));
VERBOSE && console.log(options);
/*
if(contracts.length == 2){
isBinary = true
percentage = ( Number(prices[market.id][0].last_executed_price) + (100 - Number(prices[market.id][1].last_executed_price)) ) / 2
percentage = Math.round(percentage)+"%"
let contractName = contracts[0].name
name = name+ (contractName=="Yes"?'':` (${contracts[0].name})`)
}
*/
const id = `${platformName}-${market.id}`;
const title = market.name;
const result: FetchedQuestion = {
id,
title,
url: "https://smarkets.com/event/" + market.event_id + market.slug,
description: market.description,
options,
timestamp: new Date(),
qualityindicators: {},
};
VERBOSE && console.log(result);
results.push(result);
}
VERBOSE && console.log(results);
return results;
}, },
calculateStars(data) { calculateStars(data) {
let nuno = () => 2; const nuno = () => 2;
let eli = () => null; const eli = () => null;
let misha = () => null; const misha = () => null;
let starsDecimal = average([nuno()]); //, eli(), misha()]) const starsDecimal = average([nuno()]); //, eli(), misha()])
let starsInteger = Math.round(starsDecimal); const starsInteger = Math.round(starsDecimal);
return starsInteger; return starsInteger;
}, },
}; };

View File

@ -121,6 +121,7 @@ export const wildeford: Platform = {
name: platformName, name: platformName,
label: "Peter Wildeford", label: "Peter Wildeford",
color: "#984158", color: "#984158",
version: "v1",
async fetcher() { async fetcher() {
const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY; // See: https://developers.google.com/sheets/api/guides/authorizing#APIKey const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY; // See: https://developers.google.com/sheets/api/guides/authorizing#APIKey
return (await applyIfSecretExists(GOOGLE_API_KEY, wildeford_inner)) || null; return (await applyIfSecretExists(GOOGLE_API_KEY, wildeford_inner)) || null;

View File

@ -9,6 +9,7 @@ export const xrisk: Platform = {
name: "xrisk", name: "xrisk",
label: "X-risk estimates", label: "X-risk estimates",
color: "#272600", color: "#272600",
version: "v1",
async fetcher() { async fetcher() {
// return; // not necessary to refill the DB every time // return; // not necessary to refill the DB every time
let fileRaw = fs.readFileSync("./input/xrisk-questions.json", { let fileRaw = fs.readFileSync("./input/xrisk-questions.json", {