powerball-quantum 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +76 -0
- package/dist/data.d.ts +17 -0
- package/dist/data.js +116 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +110 -0
- package/dist/quantum.d.ts +45 -0
- package/dist/quantum.js +239 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +6 -0
- package/package.json +41 -0
- package/powerball_data.csv +1880 -0
- package/src/cli.ts +82 -0
- package/src/data.ts +83 -0
- package/src/index.ts +142 -0
- package/src/quantum.ts +277 -0
- package/src/types.ts +25 -0
- package/tsconfig.json +17 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { predict, updateData, formatPick } from './index';
|
|
5
|
+
|
|
6
|
+
const program = new Command();
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name('powerball-quantum')
|
|
10
|
+
.description('Powerball number predictor using quantum-inspired algorithm')
|
|
11
|
+
.version('1.0.0');
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command('predict')
|
|
15
|
+
.description('Generate predicted Powerball numbers')
|
|
16
|
+
.option('-c, --count <number>', 'Number of picks to generate', '5')
|
|
17
|
+
.option('-a, --analysis', 'Show signal analysis', false)
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log('š± POWERBALL QUANTUM PREDICTOR');
|
|
21
|
+
console.log('ā'.repeat(50));
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const picks = await predict({
|
|
25
|
+
count: parseInt(options.count),
|
|
26
|
+
showAnalysis: options.analysis
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log('\nā RECOMMENDED PICKS:\n');
|
|
30
|
+
|
|
31
|
+
picks.forEach((pick, i) => {
|
|
32
|
+
const score = pick.score?.toFixed(1) || '?';
|
|
33
|
+
console.log(` #${i + 1} ${formatPick(pick)} (score: ${score})`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log('\n' + 'ā'.repeat(50));
|
|
37
|
+
console.log('Good luck! š\n');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error:', error);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
program
|
|
45
|
+
.command('update')
|
|
46
|
+
.description('Update Powerball data from NY Lottery API')
|
|
47
|
+
.action(async () => {
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log('š„ Updating Powerball data...');
|
|
50
|
+
console.log('ā'.repeat(50));
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await updateData();
|
|
54
|
+
console.log('ā
Data updated successfully!\n');
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Error updating data:', error);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
program
|
|
62
|
+
.command('quick')
|
|
63
|
+
.description('Get one quick pick')
|
|
64
|
+
.action(async () => {
|
|
65
|
+
try {
|
|
66
|
+
const picks = await predict({ count: 1 });
|
|
67
|
+
if (picks[0]) {
|
|
68
|
+
console.log('\nš« Your lucky numbers:');
|
|
69
|
+
console.log(`\n ${formatPick(picks[0])}\n`);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Error:', error);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Default action (no command)
|
|
78
|
+
if (process.argv.length === 2) {
|
|
79
|
+
program.parse(['node', 'cli', 'predict']);
|
|
80
|
+
} else {
|
|
81
|
+
program.parse();
|
|
82
|
+
}
|
package/src/data.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { Draw, DATA_URL } from './types';
|
|
5
|
+
|
|
6
|
+
const DATA_FILE = path.join(__dirname, '..', 'powerball_data.csv');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Download latest Powerball data from NY Lottery API
|
|
10
|
+
*/
|
|
11
|
+
export async function downloadData(): Promise<string> {
|
|
12
|
+
console.log('Downloading latest Powerball data...');
|
|
13
|
+
const response = await axios.get(DATA_URL);
|
|
14
|
+
fs.writeFileSync(DATA_FILE, response.data);
|
|
15
|
+
console.log('Download complete!');
|
|
16
|
+
return DATA_FILE;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse CSV data into Draw objects
|
|
21
|
+
*/
|
|
22
|
+
export function parseCSV(csvContent: string): Draw[] {
|
|
23
|
+
const lines = csvContent.trim().split('\n');
|
|
24
|
+
const draws: Draw[] = [];
|
|
25
|
+
|
|
26
|
+
// Skip header
|
|
27
|
+
for (let i = 1; i < lines.length; i++) {
|
|
28
|
+
const line = lines[i].trim();
|
|
29
|
+
if (!line) continue;
|
|
30
|
+
|
|
31
|
+
const parts = line.split(',');
|
|
32
|
+
if (parts.length < 3) continue;
|
|
33
|
+
|
|
34
|
+
const dateStr = parts[0];
|
|
35
|
+
const numbersStr = parts[1];
|
|
36
|
+
const multiplier = parseInt(parts[2]) || 2;
|
|
37
|
+
|
|
38
|
+
const numbers = numbersStr.split(' ').map(n => parseInt(n.trim()));
|
|
39
|
+
if (numbers.length < 6) continue;
|
|
40
|
+
|
|
41
|
+
const date = new Date(dateStr);
|
|
42
|
+
// Filter for current rules (post Oct 7, 2015)
|
|
43
|
+
if (date < new Date('2015-10-07')) continue;
|
|
44
|
+
|
|
45
|
+
draws.push({
|
|
46
|
+
date,
|
|
47
|
+
whiteBalls: numbers.slice(0, 5),
|
|
48
|
+
powerball: numbers[5],
|
|
49
|
+
multiplier
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return draws;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load data from file or download if not exists
|
|
58
|
+
*/
|
|
59
|
+
export async function loadData(): Promise<Draw[]> {
|
|
60
|
+
let csvContent: string;
|
|
61
|
+
|
|
62
|
+
if (fs.existsSync(DATA_FILE)) {
|
|
63
|
+
csvContent = fs.readFileSync(DATA_FILE, 'utf-8');
|
|
64
|
+
} else {
|
|
65
|
+
await downloadData();
|
|
66
|
+
csvContent = fs.readFileSync(DATA_FILE, 'utf-8');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const draws = parseCSV(csvContent);
|
|
70
|
+
console.log(`Loaded ${draws.length} draws (current rules since 2015-10-07)`);
|
|
71
|
+
return draws;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Update data (force re-download)
|
|
76
|
+
*/
|
|
77
|
+
export async function updateData(): Promise<Draw[]> {
|
|
78
|
+
await downloadData();
|
|
79
|
+
const csvContent = fs.readFileSync(DATA_FILE, 'utf-8');
|
|
80
|
+
const draws = parseCSV(csvContent);
|
|
81
|
+
console.log(`Updated! ${draws.length} draws loaded.`);
|
|
82
|
+
return draws;
|
|
83
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Draw, Pick, PredictOptions } from './types';
|
|
2
|
+
import { loadData, updateData } from './data';
|
|
3
|
+
import {
|
|
4
|
+
calculateMomentum,
|
|
5
|
+
calculateZScores,
|
|
6
|
+
calculateRecentMomentum,
|
|
7
|
+
analyzePairFrequency,
|
|
8
|
+
quantumSelect,
|
|
9
|
+
checkSumRange,
|
|
10
|
+
checkOddEvenRatio,
|
|
11
|
+
checkHighLowBalance,
|
|
12
|
+
checkDecadeBalance,
|
|
13
|
+
checkEndingDiversity,
|
|
14
|
+
checkNoTripleConsecutive,
|
|
15
|
+
isInHistory,
|
|
16
|
+
buildHistorySet
|
|
17
|
+
} from './quantum';
|
|
18
|
+
|
|
19
|
+
export { Draw, Pick, PredictOptions } from './types';
|
|
20
|
+
export { loadData, updateData } from './data';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate a single quantum-powered pick
|
|
24
|
+
*/
|
|
25
|
+
export function generatePick(
|
|
26
|
+
draws: Draw[],
|
|
27
|
+
historySet: Set<string>,
|
|
28
|
+
whiteMomentum: Record<number, number>,
|
|
29
|
+
redMomentum: Record<number, number>,
|
|
30
|
+
whiteZ: Record<number, number>,
|
|
31
|
+
redZ: Record<number, number>,
|
|
32
|
+
whiteRecent: Record<number, number>,
|
|
33
|
+
redRecent: Record<number, number>,
|
|
34
|
+
pairBonus: Record<number, number>
|
|
35
|
+
): Pick | null {
|
|
36
|
+
const maxAttempts = 150000;
|
|
37
|
+
|
|
38
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
39
|
+
const whiteBalls = quantumSelect(whiteMomentum, whiteZ, whiteRecent, pairBonus, 5).sort((a, b) => a - b);
|
|
40
|
+
const powerball = quantumSelect(redMomentum, redZ, redRecent, {}, 1)[0];
|
|
41
|
+
|
|
42
|
+
// Apply all filters
|
|
43
|
+
if (isInHistory(whiteBalls, powerball, historySet)) continue;
|
|
44
|
+
if (!checkSumRange(whiteBalls)) continue;
|
|
45
|
+
if (!checkOddEvenRatio(whiteBalls)) continue;
|
|
46
|
+
if (!checkHighLowBalance(whiteBalls)) continue;
|
|
47
|
+
if (!checkDecadeBalance(whiteBalls)) continue;
|
|
48
|
+
if (!checkEndingDiversity(whiteBalls)) continue;
|
|
49
|
+
if (!checkNoTripleConsecutive(whiteBalls)) continue;
|
|
50
|
+
|
|
51
|
+
// Calculate score
|
|
52
|
+
let score = 0;
|
|
53
|
+
whiteBalls.forEach(n => {
|
|
54
|
+
score += (whiteMomentum[n] || 0) * 1.0;
|
|
55
|
+
score += Math.max(0, whiteZ[n] || 0) * 3.0;
|
|
56
|
+
score += (whiteRecent[n] || 0) * 2.0;
|
|
57
|
+
score += (pairBonus[n] || 0) * 0.5;
|
|
58
|
+
});
|
|
59
|
+
score += (redMomentum[powerball] || 0) * 2.0;
|
|
60
|
+
score += Math.max(0, redZ[powerball] || 0) * 4.0;
|
|
61
|
+
|
|
62
|
+
return { whiteBalls, powerball, score };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Main prediction function - call this to get picks!
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* import { predict } from 'powerball-quantum';
|
|
74
|
+
*
|
|
75
|
+
* const picks = await predict({ count: 5 });
|
|
76
|
+
* console.log(picks);
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export async function predict(options: PredictOptions = {}): Promise<Pick[]> {
|
|
80
|
+
const { count = 5, showAnalysis = false } = options;
|
|
81
|
+
|
|
82
|
+
// Load data
|
|
83
|
+
const draws = await loadData();
|
|
84
|
+
const historySet = buildHistorySet(draws);
|
|
85
|
+
|
|
86
|
+
// Calculate all signals
|
|
87
|
+
const { white: wMom, red: rMom } = calculateMomentum(draws);
|
|
88
|
+
const { white: wZ, red: rZ } = calculateZScores(draws);
|
|
89
|
+
const { white: wRecent, red: rRecent } = calculateRecentMomentum(draws, 15);
|
|
90
|
+
const pairBonus = analyzePairFrequency(draws, 50);
|
|
91
|
+
|
|
92
|
+
if (showAnalysis) {
|
|
93
|
+
console.log('\nš Signal Analysis:');
|
|
94
|
+
const topMom = Object.entries(wMom).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
95
|
+
console.log('š„ Hot Numbers:', topMom.map(([n]) => n));
|
|
96
|
+
const topZ = Object.entries(wZ).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
97
|
+
console.log('ā° Overdue Numbers:', topZ.filter(([, z]) => z > 1).map(([n]) => n));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Generate picks
|
|
101
|
+
const picks: Pick[] = [];
|
|
102
|
+
const seen = new Set<string>();
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < count * 10 && picks.length < count; i++) {
|
|
105
|
+
const pick = generatePick(
|
|
106
|
+
draws, historySet,
|
|
107
|
+
wMom, rMom, wZ, rZ, wRecent, rRecent, pairBonus
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (pick) {
|
|
111
|
+
const key = pick.whiteBalls.join(',') + '-' + pick.powerball;
|
|
112
|
+
if (!seen.has(key)) {
|
|
113
|
+
seen.add(key);
|
|
114
|
+
picks.push(pick);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Sort by score
|
|
120
|
+
picks.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
121
|
+
|
|
122
|
+
return picks;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Quick function to get a single best pick
|
|
127
|
+
*/
|
|
128
|
+
export async function quickPick(): Promise<Pick | null> {
|
|
129
|
+
const picks = await predict({ count: 1 });
|
|
130
|
+
return picks[0] || null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format pick for display
|
|
135
|
+
*/
|
|
136
|
+
export function formatPick(pick: Pick): string {
|
|
137
|
+
const whites = pick.whiteBalls.map(n => n.toString().padStart(2, ' ')).join(' - ');
|
|
138
|
+
return `${whites} š“ ${pick.powerball}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Default export
|
|
142
|
+
export default { predict, quickPick, updateData, loadData, formatPick };
|
package/src/quantum.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { Draw, Pick, Scores, WHITE_BALL_RANGE, RED_BALL_RANGE } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculate exponential decay momentum scores
|
|
5
|
+
*/
|
|
6
|
+
export function calculateMomentum(draws: Draw[], decayAlpha = 0.03): { white: Scores; red: Scores } {
|
|
7
|
+
const whiteScores: Scores = {};
|
|
8
|
+
const redScores: Scores = {};
|
|
9
|
+
|
|
10
|
+
// Initialize scores
|
|
11
|
+
for (let n = WHITE_BALL_RANGE.min; n <= WHITE_BALL_RANGE.max; n++) {
|
|
12
|
+
whiteScores[n] = 0.1;
|
|
13
|
+
}
|
|
14
|
+
for (let n = RED_BALL_RANGE.min; n <= RED_BALL_RANGE.max; n++) {
|
|
15
|
+
redScores[n] = 0.1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Sort by date ascending
|
|
19
|
+
const sorted = [...draws].sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
20
|
+
const total = sorted.length;
|
|
21
|
+
|
|
22
|
+
sorted.forEach((draw, idx) => {
|
|
23
|
+
const t = total - 1 - idx; // t=0 for most recent
|
|
24
|
+
const weight = Math.exp(-decayAlpha * t);
|
|
25
|
+
|
|
26
|
+
draw.whiteBalls.forEach(w => {
|
|
27
|
+
if (whiteScores[w] !== undefined) {
|
|
28
|
+
whiteScores[w] += weight;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (redScores[draw.powerball] !== undefined) {
|
|
33
|
+
redScores[draw.powerball] += weight;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return { white: whiteScores, red: redScores };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Calculate Z-scores for gap analysis (mean reversion)
|
|
42
|
+
*/
|
|
43
|
+
export function calculateZScores(draws: Draw[]): { white: Scores; red: Scores } {
|
|
44
|
+
const whiteGaps: { [key: number]: number[] } = {};
|
|
45
|
+
const redGaps: { [key: number]: number[] } = {};
|
|
46
|
+
const lastSeenWhite: { [key: number]: number } = {};
|
|
47
|
+
const lastSeenRed: { [key: number]: number } = {};
|
|
48
|
+
|
|
49
|
+
// Initialize
|
|
50
|
+
for (let n = WHITE_BALL_RANGE.min; n <= WHITE_BALL_RANGE.max; n++) {
|
|
51
|
+
whiteGaps[n] = [];
|
|
52
|
+
lastSeenWhite[n] = -1;
|
|
53
|
+
}
|
|
54
|
+
for (let n = RED_BALL_RANGE.min; n <= RED_BALL_RANGE.max; n++) {
|
|
55
|
+
redGaps[n] = [];
|
|
56
|
+
lastSeenRed[n] = -1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sorted = [...draws].sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
60
|
+
|
|
61
|
+
sorted.forEach((draw, idx) => {
|
|
62
|
+
// White balls
|
|
63
|
+
draw.whiteBalls.forEach(w => {
|
|
64
|
+
if (lastSeenWhite[w] !== -1) {
|
|
65
|
+
whiteGaps[w].push(idx - lastSeenWhite[w]);
|
|
66
|
+
}
|
|
67
|
+
lastSeenWhite[w] = idx;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Red ball
|
|
71
|
+
if (lastSeenRed[draw.powerball] !== -1) {
|
|
72
|
+
redGaps[draw.powerball].push(idx - lastSeenRed[draw.powerball]);
|
|
73
|
+
}
|
|
74
|
+
lastSeenRed[draw.powerball] = idx;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const total = sorted.length;
|
|
78
|
+
const whiteZ: Scores = {};
|
|
79
|
+
const redZ: Scores = {};
|
|
80
|
+
|
|
81
|
+
// Calculate Z-scores
|
|
82
|
+
for (let n = WHITE_BALL_RANGE.min; n <= WHITE_BALL_RANGE.max; n++) {
|
|
83
|
+
const currentGap = total - 1 - lastSeenWhite[n];
|
|
84
|
+
const gaps = whiteGaps[n];
|
|
85
|
+
|
|
86
|
+
if (gaps.length === 0) {
|
|
87
|
+
whiteZ[n] = 0;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const mean = gaps.reduce((a, b) => a + b, 0) / gaps.length;
|
|
92
|
+
const variance = gaps.reduce((sum, g) => sum + Math.pow(g - mean, 2), 0) / gaps.length;
|
|
93
|
+
const stdev = Math.sqrt(variance) || 1;
|
|
94
|
+
|
|
95
|
+
whiteZ[n] = (currentGap - mean) / stdev;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (let n = RED_BALL_RANGE.min; n <= RED_BALL_RANGE.max; n++) {
|
|
99
|
+
const currentGap = total - 1 - lastSeenRed[n];
|
|
100
|
+
const gaps = redGaps[n];
|
|
101
|
+
|
|
102
|
+
if (gaps.length === 0) {
|
|
103
|
+
redZ[n] = 0;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const mean = gaps.reduce((a, b) => a + b, 0) / gaps.length;
|
|
108
|
+
const variance = gaps.reduce((sum, g) => sum + Math.pow(g - mean, 2), 0) / gaps.length;
|
|
109
|
+
const stdev = Math.sqrt(variance) || 1;
|
|
110
|
+
|
|
111
|
+
redZ[n] = (currentGap - mean) / stdev;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { white: whiteZ, red: redZ };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Calculate recent momentum (last N draws)
|
|
119
|
+
*/
|
|
120
|
+
export function calculateRecentMomentum(draws: Draw[], nRecent = 15, decay = 0.15): { white: Scores; red: Scores } {
|
|
121
|
+
const whiteScores: Scores = {};
|
|
122
|
+
const redScores: Scores = {};
|
|
123
|
+
|
|
124
|
+
for (let n = WHITE_BALL_RANGE.min; n <= WHITE_BALL_RANGE.max; n++) {
|
|
125
|
+
whiteScores[n] = 0.1;
|
|
126
|
+
}
|
|
127
|
+
for (let n = RED_BALL_RANGE.min; n <= RED_BALL_RANGE.max; n++) {
|
|
128
|
+
redScores[n] = 0.1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const sorted = [...draws].sort((a, b) => b.date.getTime() - a.date.getTime()).slice(0, nRecent);
|
|
132
|
+
|
|
133
|
+
sorted.forEach((draw, idx) => {
|
|
134
|
+
const weight = Math.exp(-decay * idx) * 2;
|
|
135
|
+
|
|
136
|
+
draw.whiteBalls.forEach(w => {
|
|
137
|
+
if (whiteScores[w] !== undefined) {
|
|
138
|
+
whiteScores[w] += weight;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (redScores[draw.powerball] !== undefined) {
|
|
143
|
+
redScores[draw.powerball] += weight;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return { white: whiteScores, red: redScores };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Analyze pair frequency
|
|
152
|
+
*/
|
|
153
|
+
export function analyzePairFrequency(draws: Draw[], topN = 50): Scores {
|
|
154
|
+
const pairCounts: Map<string, number> = new Map();
|
|
155
|
+
|
|
156
|
+
draws.forEach(draw => {
|
|
157
|
+
const whites = [...draw.whiteBalls].sort((a, b) => a - b);
|
|
158
|
+
for (let i = 0; i < whites.length; i++) {
|
|
159
|
+
for (let j = i + 1; j < whites.length; j++) {
|
|
160
|
+
const key = `${whites[i]}-${whites[j]}`;
|
|
161
|
+
pairCounts.set(key, (pairCounts.get(key) || 0) + 1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const pairBonus: Scores = {};
|
|
167
|
+
const sorted = [...pairCounts.entries()]
|
|
168
|
+
.sort((a, b) => b[1] - a[1])
|
|
169
|
+
.slice(0, topN);
|
|
170
|
+
|
|
171
|
+
sorted.forEach(([pair, count]) => {
|
|
172
|
+
const [a, b] = pair.split('-').map(Number);
|
|
173
|
+
pairBonus[a] = (pairBonus[a] || 0) + count * 0.1;
|
|
174
|
+
pairBonus[b] = (pairBonus[b] || 0) + count * 0.1;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return pairBonus;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Weighted random selection
|
|
182
|
+
*/
|
|
183
|
+
export function weightedSample(candidates: number[], weights: number[], k: number): number[] {
|
|
184
|
+
const selected = new Set<number>();
|
|
185
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
186
|
+
|
|
187
|
+
while (selected.size < k) {
|
|
188
|
+
let r = Math.random() * totalWeight;
|
|
189
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
190
|
+
r -= weights[i];
|
|
191
|
+
if (r <= 0) {
|
|
192
|
+
selected.add(candidates[i]);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return [...selected];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Quantum selector combining all signals
|
|
203
|
+
*/
|
|
204
|
+
export function quantumSelect(
|
|
205
|
+
momentum: Scores,
|
|
206
|
+
zScores: Scores,
|
|
207
|
+
recentMomentum: Scores,
|
|
208
|
+
pairBonus: Scores,
|
|
209
|
+
nPicks: number
|
|
210
|
+
): number[] {
|
|
211
|
+
const candidates = Object.keys(momentum).map(Number);
|
|
212
|
+
const weights = candidates.map(n => {
|
|
213
|
+
const base = momentum[n] || 0;
|
|
214
|
+
const zBoost = Math.max(0, zScores[n] || 0) * 5;
|
|
215
|
+
const recentBoost = (recentMomentum[n] || 0) * 1.5;
|
|
216
|
+
const pairB = (pairBonus[n] || 0) * 0.5;
|
|
217
|
+
return Math.max(0.1, base + zBoost + recentBoost + pairB);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return weightedSample(candidates, weights, nPicks);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// --- FILTERS ---
|
|
224
|
+
|
|
225
|
+
export function checkSumRange(whiteBalls: number[], min = 130, max = 220): boolean {
|
|
226
|
+
const sum = whiteBalls.reduce((a, b) => a + b, 0);
|
|
227
|
+
return sum >= min && sum <= max;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function checkOddEvenRatio(whiteBalls: number[]): boolean {
|
|
231
|
+
const odds = whiteBalls.filter(x => x % 2 !== 0).length;
|
|
232
|
+
const evens = 5 - odds;
|
|
233
|
+
return (odds === 3 && evens === 2) || (odds === 2 && evens === 3);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function checkHighLowBalance(whiteBalls: number[], threshold = 35): boolean {
|
|
237
|
+
const lows = whiteBalls.filter(x => x < threshold).length;
|
|
238
|
+
const highs = 5 - lows;
|
|
239
|
+
return (lows === 3 && highs === 2) || (lows === 2 && highs === 3);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function checkDecadeBalance(whiteBalls: number[]): boolean {
|
|
243
|
+
const decades = new Set(whiteBalls.map(n => Math.floor(n / 10)));
|
|
244
|
+
return decades.size >= 3;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function checkEndingDiversity(whiteBalls: number[]): boolean {
|
|
248
|
+
const endings = new Set(whiteBalls.map(n => n % 10));
|
|
249
|
+
return endings.size >= 4;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function checkNoTripleConsecutive(whiteBalls: number[]): boolean {
|
|
253
|
+
const sorted = [...whiteBalls].sort((a, b) => a - b);
|
|
254
|
+
const diffs = [];
|
|
255
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
256
|
+
diffs.push(sorted[i + 1] - sorted[i]);
|
|
257
|
+
}
|
|
258
|
+
const consecutiveOnes = diffs.filter(d => d === 1).length;
|
|
259
|
+
return consecutiveOnes < 2;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if combination exists in history
|
|
264
|
+
*/
|
|
265
|
+
export function isInHistory(whiteBalls: number[], powerball: number, history: Set<string>): boolean {
|
|
266
|
+
const key = [...whiteBalls].sort((a, b) => a - b).join(',') + '-' + powerball;
|
|
267
|
+
return history.has(key);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function buildHistorySet(draws: Draw[]): Set<string> {
|
|
271
|
+
const history = new Set<string>();
|
|
272
|
+
draws.forEach(draw => {
|
|
273
|
+
const key = [...draw.whiteBalls].sort((a, b) => a - b).join(',') + '-' + draw.powerball;
|
|
274
|
+
history.add(key);
|
|
275
|
+
});
|
|
276
|
+
return history;
|
|
277
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface Draw {
|
|
2
|
+
date: Date;
|
|
3
|
+
whiteBalls: number[];
|
|
4
|
+
powerball: number;
|
|
5
|
+
multiplier: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface Pick {
|
|
9
|
+
whiteBalls: number[];
|
|
10
|
+
powerball: number;
|
|
11
|
+
score?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Scores {
|
|
15
|
+
[key: number]: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PredictOptions {
|
|
19
|
+
count?: number;
|
|
20
|
+
showAnalysis?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const WHITE_BALL_RANGE = { min: 1, max: 69 };
|
|
24
|
+
export const RED_BALL_RANGE = { min: 1, max: 26 };
|
|
25
|
+
export const DATA_URL = "https://data.ny.gov/api/views/d6yy-54nr/rows.csv?accessType=DOWNLOAD";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"resolveJsonModule": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|