Crea un bot di pronostici per Telegram con le quote Pinnacle live
Questo bot osserva i prezzi di Pinnacle sul WebSocket di PinnFeed e pubblica un messaggio in un canale Telegram ogni volta che un mercato va in steam, cioè quando la sua quota scende del 5% o più in due minuti in un mercato con una puntata massima ragionevole. È un solo file di circa 70 righe, senza librerie Telegram: chiama direttamente la Bot API.
Passo 1 — Crea il bot#
- Su Telegram apri @BotFather, invia
/newbote segui le istruzioni. Copia il token. - Crea un canale e aggiungi il bot come amministratore con il permesso di pubblicare.
- Usa il nome pubblico del canale (
@mychannel) come chat id, oppure l'id numerico per una chat privata.
Passo 2 — Configura l'ambiente#
export PINNFEED_KEY="pf_live_..." # piano Builder (WebSocket)
export TELEGRAM_TOKEN="123456:ABC..." # da @BotFather
export TELEGRAM_CHAT="@mychannel"
npm install ws
Se lasci TELEGRAM_TOKEN vuoto, il bot gira in modalità di prova: stampa i messaggi invece di inviarli.
Passo 3 — Il bot#
Salvalo come telegram-picks-bot.mjs:
// telegram-picks-bot.mjs: pubblica gli steam move di Pinnacle in un canale Telegram come "pronostici".
// npm install ws Node 18+ (global fetch)
// ambiente: PINNFEED_KEY, TELEGRAM_TOKEN (da @BotFather), TELEGRAM_CHAT (es. @mychannel o un id chat)
import WebSocket from 'ws';
const WS_URL = process.env.PINNFEED_WS || 'wss://pinnfeed.io/v1/stream';
const { PINNFEED_KEY, TELEGRAM_TOKEN, TELEGRAM_CHAT } = process.env;
const MIN_DROP = Number(process.env.MIN_DROP || 0.05); // calo del 5%...
const WINDOW_MS = Number(process.env.WINDOW_MS || 120000); // ...entro 2 minuti
const SIDES = new Set(['home', 'away', 'draw', 'over', 'under']);
const events = new Map();
const highs = new Map(); // "marketId|outcome" -> [{t, p}]
const sent = new Map(); // "marketId|outcome" -> ora dell'ultimo alert
async function post(text) {
if (!TELEGRAM_TOKEN) return console.log('[dry run]', text);
const r = await fetch(`https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage`, {
method: 'POST', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ chat_id: TELEGRAM_CHAT, text, disable_web_page_preview: true }),
});
if (!r.ok) console.error('telegram', r.status, await r.text());
}
function check(m) {
const ev = events.get(m.eventId);
if (!ev || (m.maxStake ?? 0) < 500) return;
const now = Date.now();
for (const o of m.outcomes) {
if (!SIDES.has(o.name)) continue;
const k = `${m.id}|${o.name}`;
const h = (highs.get(k) || []).filter((x) => now - x.t < WINDOW_MS);
h.push({ t: now, p: o.decimal });
highs.set(k, h);
const high = Math.max(...h.map((x) => x.p));
const drop = 1 - o.decimal / high;
if (drop < MIN_DROP || now - (sent.get(k) || 0) < WINDOW_MS) continue;
sent.set(k, now);
const pick = o.name === 'home' ? ev.home : o.name === 'away' ? ev.away : o.name;
const line = o.points == null ? '' : m.type === 'total' ? ` ${o.points}` : ` ${o.points > 0 ? '+' : ''}${o.points}`;
post(`📉 ${ev.sport.toUpperCase()} · ${ev.league?.name ?? ''}\n${ev.home} v ${ev.away}\n` +
`Pick: ${pick} (${m.type}${line})\nPinnacle ${high.toFixed(2)} → ${o.decimal.toFixed(2)} (−${(drop * 100).toFixed(1)}%)\n` +
`Fair now ${o.fair.toFixed(2)} · max stake ${m.maxStake}`);
}
}
function connect(delay = 1000) {
const ws = new WebSocket(WS_URL, { headers: { 'x-api-key': PINNFEED_KEY }, perMessageDeflate: true });
ws.on('open', () => {
delay = 1000;
ws.send(JSON.stringify({ type: 'subscribe', sports: ['soccer', 'tennis', 'basketball'], markets: ['moneyline', 'spread', 'total'], main: true, specials: false }));
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
if (msg.type === 'ping') return ws.send('{"type":"pong"}');
if (msg.type === 'snapshot') msg.events.forEach((e) => events.set(e.id, e));
if (msg.type === 'delta') for (const c of msg.changes) {
if (c.kind === 'event') c.op === 'upsert' ? events.set(c.event.id, c.event) : events.delete(c.id);
if (c.kind === 'market' && c.op === 'upsert') check(c.market);
}
});
ws.on('close', () => setTimeout(() => connect(Math.min(delay * 2, 30000)), delay));
ws.on('error', (e) => console.error('ws error:', e.message));
}
connect();
Passo 4 — Avvialo e provalo#
node telegram-picks-bot.mjs
Un messaggio si presenta così:
📉 SOCCER · Italy - Serie A
Udinese v Fiorentina
Pick: Udinese (spread +0.25)
Pinnacle 1.88 → 1.70 (−9.5%)
Fair now 1.83 · max stake 500
Regola il volume con MIN_DROP e WINDOW_MS; per esempio MIN_DROP=0.08 per meno alert ma più forti.
Passo 5 — Tienilo acceso#
- Eseguilo con
systemd,pm2o un container che si riavvia in caso di errore; il bot si riconnette da solo al WebSocket. - Telegram consente circa 20 messaggi al minuto per gruppo o canale. Alza
MIN_DROPse ricevi risposte429. - Pubblica il tuo storico con onestà: registra ogni pronostico e misuralo con il closing line value.
Domande frequenti#
Quale piano di PinnFeed serve al bot?#
Builder (99 €/mese) o superiore, perché usa il WebSocket. Per un bot più lento su Free o Hobby, fai polling su /v1/drops ogni minuto.
Posso pubblicare i pronostici su Discord?#
Sì. Sostituisci la funzione post con un POST all'URL di un webhook Discord con il body {"content": text}.
È consentito pubblicare pronostici?#
Le regole su pronostici e pubblicità del gioco cambiano da paese a paese; in Italia, per esempio, il Decreto Dignità (2018) vieta la pubblicità del gioco con vincite in denaro. Verifica le regole dove ti trovi tu e dove si trova il tuo pubblico, rivolgiti solo a maggiori di 18 anni e non presentare mai i pronostici come sicuri. Questa non è una consulenza legale.