-
Notifications
You must be signed in to change notification settings - Fork 0
/
wa_lottery_scratch.html
434 lines (355 loc) · 21.4 KB
/
wa_lottery_scratch.html
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
<html>
<head>
<script type="text/javascript">
// From https://gist.github.com/adamnovak/f34e6cf2c08684752a9d
function hyp(successesObserved, sampled, successesAvailable, popSize) {
// The actual hypergeometric CDF. Requires that half or less of the
// population be successes, and that half or less of the population be
// sampled.
// We conceptualize the problem like this: given the population, choose the
// successes, choose the sampled items, and look at the size of the overlap.
// Because of this, it doesn't matter which set is the successes and which
// is the sampled items, so we can swap those around for numerical reasons.
// What are the sizes of the two sets we are intersecting, identifgied by
// relative size?
var smallerSet, largerSet;
// best to have sampled<successesAvailable
if (successesAvailable < sampled) {
// The set of successes is smaller
smallerSet = successesAvailable;
largerSet = sampled
} else {
// The set of sampled items is smaller
smallerSet = sampled;
largerSet = successesAvailable;
}
// This is an intermediate value I don't really understand, which is used in
// the middle of the cumulative CDF calculation.
var h = 1;
// This is the probability of having observed everything we looked at so
// far. Except sometimes it goes above 1 and we have to fix it?
var s = 1;
// This is an index over which item we are at in the smaller set
var k = 0;
// This is an index over which item we are at in the intersection
var i = 0;
while (i < successesObserved) {
// For each item in the intersection
while (s > 1 && k < smallerSet) {
// Sample some items from the smaller set (?)
// Get the probability of, after already grabbing k items for the
// smaller set that weren't in the larger set, grabbing another
// item for the smaller set that wasn't in the larger set.
// Then multiply h and s by this probability
h = h * (1 - largerSet / (popSize - k));
s = s * (1 - largerSet / (popSize - k));
// Advance to the next item in the smaller set.
k = k + 1;
}
// Get the number of things not taken for the intersection, after taking
// this thing: (popSize - smallerSet - largerSet + i + 1)
// Get the number of things in the smaller set not in the intersection,
// and multiply by the number of things in the larger set not in the
// intersection.
// As i goes from 0 to successesObserved, h accumulates:
// factors of smallerSet to smallerSet - successesObserved on top
// factors of largerSet to largerSet - successesObserved on top
// factors of 1 to successesObserved on bottom
// factors of (popSize - smallerSet - largerSet + 1) to
// (popSize - smallerSet - largerSet + successesObserved)
// I'm not entirely sure how this works, but it looks like we're
// cheating a bit to calculate the sum of ratios of factorials without
// needing to re-do lots of the multiplications.
h = h * (smallerSet - i) * (largerSet - i) / (i + 1) / (popSize - smallerSet - largerSet + i + 1);
s = s + h;
// Move on to the next item in the intersection
i = i + 1;
}
while (k < smallerSet) {
// For each remaining item in the smaller set (conceptually the sampled
// ones) that was not part of the intersection(?)
// popSize - k is the number of items remaining to be grabbed for the
// smaller set.
// largerSet / (popSize - k) is the probability that the next item
// grabbed for the smaller set would be in the larger set.
// We take 1 - that because we know the next item grabbed for the
// smaller set will not have been in the larger set, since it wasn't in
// the intersection.
// Then we multiply the probability of everything we've seen so far by
// the probability of having successfully not added some extra thing to
// our intersection.
s = s * (1 - largerSet / (popSize - k));
// Move on to grab the next item in the smaller set.
k = k + 1;
}
return s;
}
function compute(popSize, successesAvailable, sampled, successesObserved) {
// Parse the input out of the form, compute the hypergeometric CDF for
// probability of having observed that many successes or fewer, and display
// the output to the user.
// Get the population size
//var popSize = Math.floor(eval(form.pop1.value));
// Get the number of successes in the population
//var successesAvailable = Math.floor(eval(form.pop2.value));
// Get the number of items sampled
//var sampled = Math.floor(eval(form.sample.value));
// Get the number of successes observed
//var successesObserved = Math.floor(eval(form.argument.value));
// This is the probability of observing this many successes or fewer, given
// the other parameters.
var Prob;
if (sampled <= 0 || successesAvailable <= 0 || popSize <= 0) {
// Nothing sampled or no successes available or nothing in the
// population.
alert("Parameters must be positive integers");
Prob = 0;
} else if (successesAvailable > popSize || sampled > popSize) {
// Successes available or samples taken larger than actual population.
alert("successesAvailable and sampled must be less than popSize");
Prob = 0;
} else if (successesObserved < 0 || successesObserved < sampled + successesAvailable - popSize) {
// We observed negative successes, or we pulled out so much of the
// population that we would absolutely have had to have sampled more
// succeses than this.
Prob = 0;
} else if (successesObserved >= sampled || successesObserved >= successesAvailable) {
// We saw as many successes as we had samples (or even more), or we saw
// all (or more) of the successes available. The probability of seeing
// that many successes *or fewer* is thus 1.
Prob = 1;
} else {
// We can't short-circuit and get the answer already. We have to do real
// math. But first, we have to precondition the input parameters to the
// CDF function.
if (2 * successesAvailable > popSize) {
// More than half of the population is successes.
if (2 * sampled > popSize) {
// More than half the population has been sampled.
// Change to a different problem:
// Out of the same size population, with success and failure
// reversed, sampling everything we didn't sample before, what's
// the probability we sample that many or more of the things
// that were originally unsampled failures?
// The popSize - successesAvailable - sampled +
// successesObserved has the +successesObserved to avoid double-
// counting the successesAvailable that were also sampled in the
// subtraction. So it gets the number of things that were
// originally neither sampled nor observed.
// P(the unsampled part contains some number of failures or
// fewer) = P(the sampled part contains x successes or fewer)
Prob = hyp(popSize - successesAvailable - sampled + successesObserved, popSize - sampled, popSize - successesAvailable, popSize);
} else {
// Half or less of the population has been sampled.
// Change to a different different problem:
// Out of the same size population, with success and failure
// reversed, sampling the same number of things, what's the
// probability of observing the same situation we actually
// observed, but with success and failure reversed? Or something
// with more successes than that?
// Basically we go and get the other (smaller) tail, and do 1
// minus that.
Prob = 1 - hyp(sampled - successesObserved - 1, sampled, popSize - successesAvailable, popSize);
}
} else if (2 * sampled > popSize) {
// Half or less of the population is successes, but we sampled more
// than half of it.
// Try a different problem: sample the part we didn't sample before,
// and look at the probability of finding the corresponding number
// of successes or more in that part, and then flip around to the
// other tail.
Prob = 1 - hyp(successesAvailable - successesObserved - 1, successesAvailable, popSize - sampled, popSize);
} else {
// We're sampling half or less of the population, and half or less
// of it is successes. Just go straight to the actual function.
Prob = hyp(successesObserved, sampled, successesAvailable, popSize)
}
}
Prob = Math.round(Prob * 100000) / 100000;
return Prob;
}
</script>
<script type="text/javascript">
function GetPrizeAmount(prize) {
var prizeAmount = prize.PrizeAmount.replace(/\$|,/g, "");
prizeAmount = prizeAmount.replace("LIFE", "1500000");
var regexAnuity = /(.*)\/yr\/(.*)(years|yrs)/;
if (prizeAmount.search(regexAnuity) == 0) {
var amount = Number(prizeAmount.replace(regexAnuity, "$1"));
var duration = Number(prizeAmount.replace(regexAnuity, "$2"));
prizeAmount = amount * duration;
}
if (prizeAmount >= 250000)
{
prizeAmount = prizeAmount / 2;
}
if (prizeAmount >= 600)
{
prizeAmount = prizeAmount * 0.76; // 24% federal income tax withholding
}
return prizeAmount;
}
function FormatNumber(number) {
return new Intl.NumberFormat('en-US').format(number);
}
function FormatCurrency(number) {
return Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0}).format(number);
}
function FormatPercent(number) {
return new Intl.NumberFormat('en-US', { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number);
}
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
var resultsElement = document.getElementById("results");
if (resultsElement == null)
{
return;
}
if (this.readyState == 4 && this.status == 200) {
var fuckUpFactor = .95;
var data = this.responseText.replace(/\s*\n\s*/g, " ").replace(/.*all: JSON\.parse\('({.*?})'\).*/, "$1");
var gamesArray = [];
var games = JSON.parse(data).Games;
for (game of games) {
var ticketsPrinted = Number(game.TicketsPrinted.replace(/\$|,/g, ""))
var topPrize = 0;
var totalTotalPrizes = 0;
var totalPrizesRemaining = 0;
var totalValueOfPrizesRemaining = 0;
{
var isTopPrize = true;
var totalTopPrizes;
var topPrizesRemaining;
var valueOfTopPrizesRemaining;
var prizes = game.Prizes;
prizes.sort(function (lhs, rhs) { return GetPrizeAmount(rhs) - GetPrizeAmount(lhs); })
for (prize of prizes) {
var prizeAmount = GetPrizeAmount(prize);
var totalPrizes = Number(prize.TotalPrizes.replace(/\$|,/g, ""));
var prizesRemaining = Number(prize.PrizesRemaining.replace(/\$|,/g, ""));
if (isTopPrize && prizesRemaining > 0)
{
//prizesRemaining -= 1;
}
if (!isTopPrize) {
prizesRemaining *= fuckUpFactor;
}
var valueOfPrizesRemaining = prizeAmount * prizesRemaining;
totalTotalPrizes += totalPrizes;
totalPrizesRemaining += prizesRemaining;
totalValueOfPrizesRemaining += valueOfPrizesRemaining;
if (isTopPrize) {
totalTopPrizes = totalPrizes;
topPrizesRemaining = prizesRemaining;
valueOfTopPrizesRemaining = valueOfPrizesRemaining;
topPrize = prizeAmount;
isTopPrize = false;
}
}
}
var overallOdds = totalTotalPrizes / ticketsPrinted;
var ticketsRemaining = (totalPrizesRemaining / overallOdds).toFixed(0);
var costOfTicketsRemaining = game.Cost * ticketsRemaining;
var expectedValue = totalValueOfPrizesRemaining / costOfTicketsRemaining - 1;
var expectedValueWithoutTopPrizes = (totalValueOfPrizesRemaining - valueOfTopPrizesRemaining) / costOfTicketsRemaining - 1;
var ticketsSold = ticketsPrinted - ticketsRemaining;
var percentSold = ticketsSold / ticketsPrinted;
var probabilityOfTopPrizeRemaining = 0;
if (topPrizesRemaining > 0) {
probabilityOfTopPrizeRemaining = compute(ticketsPrinted, totalTopPrizes, ticketsSold, totalTopPrizes - 1);
}
var adjustedExpectedValue = (totalValueOfPrizesRemaining - (valueOfTopPrizesRemaining * (1-probabilityOfTopPrizeRemaining))) / costOfTicketsRemaining - 1;
var adjustedTopPrizeOdds = (ticketsRemaining / topPrizesRemaining / probabilityOfTopPrizeRemaining).toFixed(0);
var test = (adjustedTopPrizeOdds * game.Cost / 100).toFixed(0);
var test2 = (1 + expectedValue) * topPrize / test;
var gameObject = { Id: game.Id, Name: game.GameName, Cost: game.Cost, ExpectedValue: expectedValue, AdjustedExpectedValue: adjustedExpectedValue, ExpectedValueWithoutTopPrizes: expectedValueWithoutTopPrizes, RedeemEndDate: new Date(game.RedeemEndDate), Image: game.UnscratchedImageUrl, TopPrize: topPrize, TopPrizesRemaining: topPrizesRemaining, PercentSold: percentSold, TicketsRemaining: ticketsRemaining, Probability: probabilityOfTopPrizeRemaining, AdjustedTopPrizeOdds: adjustedTopPrizeOdds, Test: test, Test2: test2};
gamesArray.push(gameObject);
}
gamesArray.sort(function (lhs, rhs) { return rhs.Test2 - lhs.Test2 });
var results =
"<table border=\"all\"><tbody><tr>" +
"<th>Game</th>" +
"<th>Cost</th>" +
"<th>EV<br>[Adjusted]<br>(Without Top Prizes)</th>" +
"<th>Top Prize<br>(Remaining)</th>" +
"<th>Probability of<br>Top Prize Remaining</th>" +
"<th>Percent Sold<br>Tickets Remaining<br>Adjusted Top Prize Odds<br>Odds Per $100<br>Bang for Buck Factor</th>" +
"<th>End of Game<br>End of Sales</th>" +
"</tr>";
for (game of gamesArray) {
var isGameEnding = false;
var isGameOver = false;
var endOfGame;
var endOfSales;
if (!isNaN(game.RedeemEndDate)) {
var today = new Date();
var endOfRedeem = new Date(game.RedeemEndDate);
endOfGame = new Date(endOfRedeem.getTime() - (1000 * 60 * 60 * 24 * 180));
endOfSales = new Date(endOfGame.getTime() + (1000 * 60 * 60 * 24 * 60));
var salesHaveEnded = endOfSales.getTime() <= today.getTime();
if (salesHaveEnded) continue;
isGameEnding = true;
isGameOver = endOfGame.getTime() <= today.getTime();
}
results += "<tr>";
{
results += "<td>";
results += "<p align=\"center\">";
results += "<a href=\"https://www.walottery.com/Scratch/Explorer.aspx?id=" + game.Id + "\">";
results += "<img src=\"" + game.Image + "\" width=\"75\" /><br>" + game.Name;
results += "</a>";
results += "</p>";
results += "</td>";
results += "<td>";
results += "<p align=\"center\">$" + game.Cost + "</p>";
results += "</td>";
results += "<td>";
results += "<p align=\"center\">" + FormatPercent(game.ExpectedValue) + "</p>";
if (game.TopPrizesRemaining > 0 && game.TopPrizesRemaining < 10) {
results += "<p align=\"center\">[" + FormatPercent(game.AdjustedExpectedValue) + "]</p>";
results += "<p align=\"center\">(" + FormatPercent(game.ExpectedValueWithoutTopPrizes) + ")</p>";
}
results += "</td>";
results += "<td>";
results += "<p align=\"right\">" + FormatCurrency(game.TopPrize) + "</p>";
results += "<p align=\"center\">(" + FormatNumber(game.TopPrizesRemaining) + ")</p>";
results += "</td>";
results += "<td>";
results += "<p align=\"right\">" + FormatPercent(game.Probability) + "</p>";
results += "</td>";
results += "<td>";
results += "<p align=\"right\">" + FormatPercent(game.PercentSold) + "</p>";
results += "<p align=\"right\">" + FormatNumber(game.TicketsRemaining) + "</p>";
results += "<p align=\"right\">1 in " + FormatNumber(game.AdjustedTopPrizeOdds) + "</p>";
results += "<p align=\"right\">1 in " + FormatNumber(game.Test) + "</p>";
results += "<p align=\"right\">" + FormatNumber(game.Test2) + "</p>";
results += "</td>";
results += "<td>";
if (isGameEnding) {
results += "<p align=\"center\">" + (endOfGame.getMonth() + 1) + "/" + endOfGame.getDate() + "/" + endOfGame.getFullYear() + "</p>";
results += "<p align=\"center\">" + (endOfSales.getMonth() + 1) + "/" + endOfSales.getDate() + "/" + endOfSales.getFullYear() + "</p>";
}
results += "</td>";
}
results += "</tr>";
}
results += "</tbody></table>";
resultsElement.innerHTML = results;
}
else
{
//resultsElement.innerHTML = "<p>readyState: " + FormatNumber(this.readyState) + "</p><p>status: " + FormatNumber(this.status) + "</p>";
}
};
xmlhttp.open("GET", "https://vinnyrom.ddns.net:8081/https://www.walottery.com/Scratch/Explorer.aspx", true);
xmlhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlhttp.setRequestHeader("Cache-Control","no-cache, no-store, must-revalidate");
xmlhttp.setRequestHeader("Pragma","no-cache");
xmlhttp.setRequestHeader("Expires","0");
xmlhttp.send();
</script>
</head>
<body style="font-family: 'Courier New', Courier, monospace;">
<div id="results" />
</body>
</html>