-
Notifications
You must be signed in to change notification settings - Fork 6
/
smarkets.ts
215 lines (187 loc) · 6.37 KB
/
smarkets.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import axios from "axios";
import { QuestionOption } from "../../common/types";
import { average } from "../../utils";
import { FetchedQuestion, Platform } from "./";
/* Definitions */
const platformName = "smarkets";
const apiEndpoint = "https://api.smarkets.com/v3"; // documented at https://docs.smarkets.com/
type Context = {
verbose: boolean;
};
/* Support functions */
async function fetchEvents(ctx: Context) {
let queryString =
"?state=new&state=upcoming&state=live&type_domain=politics&type_scope=single_event&with_new_type=true&sort=id&limit=50";
let events = [];
while (queryString) {
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 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({
url: `${apiEndpoint}/events/${eventId}/markets/`,
method: "GET",
})
.then((res) => res.data)
.then((res) => res.markets);
return response;
}
async function fetchContracts(marketId: string, ctx: Context) {
const response = await axios({
url: `${apiEndpoint}/markets/${marketId}/contracts/?include_hidden=true`,
method: "GET",
}).then((res) => res.data);
ctx.verbose && console.log(response);
if (!(response.contracts instanceof Array)) {
throw new Error("Invalid response while fetching contracts");
}
return response.contracts as any[];
}
async function fetchPrices(marketId: string, ctx: Context) {
const response = await axios({
url: `https://api.smarkets.com/v3/markets/${marketId}/last_executed_prices/`,
method: "GET",
}).then((res) => res.data);
ctx.verbose && console.log(response);
if (!response.last_executed_prices) {
throw new Error("Invalid response while fetching prices");
}
return response.last_executed_prices;
}
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,
qualityindicators: {},
extra: {
contracts,
prices,
},
};
ctx.verbose && console.log(result);
results.push(result);
}
return results;
}
export const smarkets: Platform<"eventId" | "verbose"> = {
name: platformName,
label: "Smarkets",
color: "#6f5b41",
version: "v2",
fetcherArgs: ["eventId", "verbose"],
async fetcher(opts) {
const ctx = {
verbose: Boolean(opts.args?.verbose) || false,
};
let events: any[] = [];
let partial = true;
if (opts.args?.eventId) {
events = [await fetchSingleEvent(opts.args.eventId, ctx)];
} else {
events = await fetchEvents(ctx);
partial = false;
}
let results: FetchedQuestion[] = [];
for (const event of events) {
const eventResults = await processEventMarkets(event, ctx);
results.push(...eventResults);
}
return {
questions: results,
partial,
};
},
calculateStars(data) {
const nuno = () => 2;
const eli = () => null;
const misha = () => null;
const starsDecimal = average([nuno()]); //, eli(), misha()])
const starsInteger = Math.round(starsDecimal);
return starsInteger;
},
};