Créez un bot de pronostics Telegram avec les cotes Pinnacle en direct
Ce bot surveille les prix de Pinnacle sur le WebSocket de PinnFeed et publie un message dans un canal Telegram chaque fois qu'un marché fait du steam, c'est-à-dire quand sa cote baisse de 5 % ou plus en deux minutes sur un marché avec une mise maximale raisonnable. C'est un seul fichier d'environ 70 lignes, sans bibliothèque Telegram : il appelle directement la Bot API.
Étape 1 — Créez le bot#
- Dans Telegram, ouvrez @BotFather, envoyez
/newbotet suivez les instructions. Copiez le token. - Créez un canal et ajoutez le bot comme administrateur avec le droit de publier.
- Utilisez le nom public du canal (
@mychannel) comme chat id, ou l'id numérique pour une discussion privée.
Étape 2 — Configurez l'environnement#
export PINNFEED_KEY="ps_live_..." # plan Builder (WebSocket)
export TELEGRAM_TOKEN="123456:ABC..." # fourni par @BotFather
export TELEGRAM_CHAT="@mychannel"
npm install ws
Si TELEGRAM_TOKEN n'est pas défini, le bot fonctionne en mode test : il affiche les messages au lieu de les envoyer.
Étape 3 — Le bot#
Enregistrez-le sous telegram-picks-bot.mjs :
// telegram-picks-bot.mjs : publie les mouvements de steam de Pinnacle dans un canal Telegram sous forme de "pronostics".
// npm install ws Node 18+ (global fetch)
// environnement : PINNFEED_KEY, TELEGRAM_TOKEN (fourni par @BotFather), TELEGRAM_CHAT (ex. @mychannel ou un id de 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); // baisse de 5 %...
const WINDOW_MS = Number(process.env.WINDOW_MS || 120000); // ...en moins de 2 minutes
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" -> heure de la dernière alerte
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) < 100) 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();
Étape 4 — Lancez-le et testez-le#
node telegram-picks-bot.mjs
Un message ressemble à ceci :
📉 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 150
Réglez le volume avec MIN_DROP et WINDOW_MS ; par exemple MIN_DROP=0.08 pour des alertes moins nombreuses mais plus fortes.
Étape 5 — Gardez-le en marche#
- Lancez-le avec
systemd,pm2ou un conteneur qui redémarre en cas d'échec ; le bot se reconnecte seul au WebSocket. - Telegram autorise environ 20 messages par minute par groupe ou canal. Augmentez
MIN_DROPsi vous recevez des réponses429. - Publiez votre historique honnêtement : enregistrez chaque pronostic et mesurez-le avec le closing line value.
Questions fréquentes#
Quel plan PinnFeed faut-il pour le bot ?#
Builder (99 €/mois) ou supérieur, car il utilise le WebSocket. Pour un bot plus lent sur Free ou Hobby, interrogez /v1/drops toutes les minutes.
Puis-je publier les pronostics sur Discord ?#
Oui. Remplacez la fonction post par un POST vers l'URL d'un webhook Discord avec le corps {"content": text}.
Est-il légal de publier des pronostics ?#
Les règles sur les pronostics et la publicité pour les jeux d'argent varient selon les pays. En France, les jeux d'argent en ligne et leur publicité sont encadrés par l'Autorité nationale des jeux (ANJ), et la loi du 9 juin 2023 sur l'influence commerciale restreint la promotion des paris sportifs par les influenceurs. Vérifiez les règles là où vous et votre audience vous trouvez, ne vous adressez qu'aux plus de 18 ans et ne présentez jamais un pronostic comme sûr. Ceci n'est pas un avis juridique.