Einen Telegram-Tipp-Bot mit Pinnacle-Live-Quoten bauen
Dieser Bot beobachtet Pinnacles Preise im PinnFeed-WebSocket und postet eine Nachricht in einen Telegram-Kanal, sobald ein Markt Steam zeigt, also wenn seine Quote in zwei Minuten um 5 % oder mehr fällt, in einem Markt mit vernünftigem Maximaleinsatz. Es ist eine einzige Datei mit rund 70 Zeilen, ohne Telegram-Bibliothek: Sie ruft die Bot API direkt auf.
Schritt 1 — Den Bot anlegen#
- Öffne in Telegram @BotFather, sende
/newbotund folge den Anweisungen. Kopiere das Token. - Leg einen Kanal an und füge den Bot als Administrator mit Berechtigung zum Posten hinzu.
- Nutze den öffentlichen Kanalnamen (
@mychannel) als Chat-id oder die numerische id für einen privaten Chat.
Schritt 2 — Die Umgebung einrichten#
export PINNFEED_KEY="pf_live_..." # Builder-Plan (WebSocket)
export TELEGRAM_TOKEN="123456:ABC..." # von @BotFather
export TELEGRAM_CHAT="@mychannel"
npm install ws
Lässt du TELEGRAM_TOKEN weg, läuft der Bot im Testmodus: Er gibt die Nachrichten aus, statt sie zu senden.
Schritt 3 — Der Bot#
Als telegram-picks-bot.mjs speichern:
// telegram-picks-bot.mjs: postet Pinnacle-Steam-Moves als "Picks" in einen Telegram-Kanal.
// npm install ws Node 18+ (global fetch)
// Umgebung: PINNFEED_KEY, TELEGRAM_TOKEN (von @BotFather), TELEGRAM_CHAT (z. B. @mychannel oder eine Chat-id)
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); // 5 % Quotensturz...
const WINDOW_MS = Number(process.env.WINDOW_MS || 120000); // ...innerhalb von 2 Minuten
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" -> Zeit des letzten Alarms
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();
Schritt 4 — Starten und testen#
node telegram-picks-bot.mjs
Eine Nachricht sieht so aus:
📉 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
Die Menge steuerst du mit MIN_DROP und WINDOW_MS, zum Beispiel MIN_DROP=0.08 für weniger, dafür stärkere Alerts.
Schritt 5 — Dauerhaft betreiben#
- Betreibe ihn mit
systemd,pm2oder einem Container, der bei Absturz neu startet; der Bot verbindet sich selbst wieder mit dem WebSocket. - Telegram erlaubt rund 20 Nachrichten pro Minute und Gruppe oder Kanal. Erhöhe
MIN_DROP, wenn du429-Antworten bekommst. - Veröffentliche deine Bilanz ehrlich: Protokolliere jeden Pick und miss ihn am Closing Line Value.
Häufige Fragen#
Welchen PinnFeed-Plan braucht der Bot?#
Builder (99 €/Monat) oder höher, weil er den WebSocket nutzt. Für einen langsameren Bot mit Free oder Hobby pollst du /v1/drops jede Minute.
Kann ich die Picks auch in Discord posten?#
Ja. Ersetze die Funktion post durch einen POST an die URL eines Discord-Webhooks mit dem Body {"content": text}.
Ist es erlaubt, Tipps zu veröffentlichen?#
Die Regeln für Wett-Tipps und Glücksspielwerbung unterscheiden sich von Land zu Land; in Deutschland regelt etwa der Glücksspielstaatsvertrag 2021 auch die Werbung für Sportwetten. Prüfe die Regeln dort, wo du und dein Publikum seid, richte dich nur an Erwachsene ab 18 Jahren und stelle Picks nie als sicher dar. Das ist keine Rechtsberatung.