On the wire · 16:01:37 UTC Live 126 · Prematch 8,738 · Markets 36,348 Vol. I · No. 1 · Monday 9 November 2026 wss://pinnfeed.io/v1/stream
PinnFeed
Guide · Bot Telegram · Node

Créez un bot de pronostics Telegram avec les cotes Pinnacle en direct

Mis à jour le · PinnFeed · 4 min de lecture

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#

  1. Dans Telegram, ouvrez @BotFather, envoyez /newbot et suivez les instructions. Copiez le token.
  2. Créez un canal et ajoutez le bot comme administrateur avec le droit de publier.
  3. 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, pm2 ou 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_DROP si vous recevez des réponses 429.
  • 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}.

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.

Le plan WebSocket : Builder, 99 €/mois.

Les cotes Pinnacle en temps réel sur un seul WebSocket : les 13 sports, tous les marchés, cotes justes sans marge et cotes de clôture. 3 jours gratuits, résiliable en un clic.

Form K-1 · Free API key

Get your free API key.

No password. We email you a link; click it and your key is on screen.

One free key per email. Link valid for 20 minutes.