On the wire · 04:52:43 UTC Live 548 · Prematch 14,380 · Markets 56,973 Vol. I · No. 5 · Friday 13 November 2026 wss://pinnfeed.io/v1/stream
PinnFeed
Resposta · Postgres · Armazenamento

Guardar odds da Pinnacle no Postgres

Atualizado em · PinnFeed · 2 min de leitura

Resposta: mantenha uma tabela só de inserção com os ticks de preço, com chave por id do mercado, resultado e horário, e grave nela cada upsert de mercado que chega pelo WebSocket do PinnFeed. Os snapshots dão o estado inicial e os frames delta, cada mudança. Depois, adicione linhas de /v1/closing para o CLV. Funciona com qualquer Postgres (auto-hospedado, Supabase, Neon).

Esquema#

create table if not exists odds_ticks (
  ts          timestamptz not null default now(),
  event_id    bigint      not null,
  market_id   text        not null,
  market      text        not null,   -- moneyline | spread | total | team_total
  period      int,
  line        numeric,
  outcome     text        not null,
  decimal     numeric     not null,
  fair        numeric,
  max_stake   numeric,
  primary key (market_id, outcome, ts)
);
create index if not exists odds_ticks_event on odds_ticks (event_id, ts);

Worker em Node (Builder)#

// npm i ws pg   ·   PINNFEED_KEY=ps_live_... DATABASE_URL=postgres://... node worker.js
import WebSocket from 'ws';
import pg from 'pg';

const db = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const sql = `insert into odds_ticks (event_id, market_id, market, period, line, outcome, decimal, fair, max_stake)
             values ($1,$2,$3,$4,$5,$6,$7,$8,$9) on conflict do nothing`;

async function save(m) {
  for (const o of m.outcomes || []) {
    await db.query(sql, [m.eventId, m.id, m.type, m.period, m.line ?? null, o.name, o.decimal, o.fair ?? null, m.maxStake ?? null]);
  }
}

function connect() {
  const ws = new WebSocket('wss://pinnfeed.io/v1/stream', { headers: { 'x-api-key': process.env.PINNFEED_KEY } });
  ws.on('open', () => ws.send(JSON.stringify({ type: 'subscribe', sports: ['soccer'], markets: ['moneyline', 'total'], main: true })));
  ws.on('message', async (raw) => {
    const msg = JSON.parse(raw);
    if (msg.type === 'ping') return ws.send(JSON.stringify({ type: 'pong' }));
    if (msg.type === 'snapshot') for (const m of msg.markets || []) await save(m);
    if (msg.type === 'delta') for (const ch of msg.changes) if (ch.kind === 'market' && ch.op === 'upsert') await save(ch.market);
  });
  ws.on('close', (code) => { console.log('closed', code); setTimeout(connect, 2000); });
}
connect();

Se as inserções ficarem atrás do stream, o servidor pode fechar com 4008 slow_consumer: agrupe as inserções (ou use uma fila) e assine menos mercados. Reconexão completa: reconexão em Node.

Consultas de exemplo#

-- last price per outcome
select distinct on (market_id, outcome) market_id, outcome, decimal, ts
from odds_ticks order by market_id, outcome, ts desc;

Use a tabela para backtests contra o fechamento e gráficos de movimento de linha.

Planos#

O streaming precisa do Builder (€99/mês) ou do Pro; o histórico de fechamento vem incluído no Builder. No Free ou Hobby, você pode consultar /v1/odds por polling e gravar na mesma tabela. Teste de 3 dias; cartão obrigatório, cobrado após o dia 3 a menos que você cancele.

Conteúdo educativo para maiores de 18 anos. Dados para pesquisa, não conselhos de apostas. No Brasil, aposte apenas em operadores autorizados pela SPA/MF.

Perguntas frequentes#

Devo guardar o snapshot?#

Sim, como a primeira linha de cada resultado. Senão, a tabela só terá os preços que mudaram depois da conexão.

Como evito duplicados depois de reconectar?#

O snapshot após reconectar repete os preços atuais; on conflict do nothing com uma chave de horário guarda esses preços como ticks novos. Se quiser só mudanças, compare com o último preço salvo antes de inserir.

E o TimescaleDB?#

Funciona igual: transforme odds_ticks em hypertable sobre ts.

O plano WebSocket: Builder, €99/mês.

Odds da Pinnacle em tempo real por um único WebSocket: os 13 esportes, todos os mercados, odds justas sem margem e odds de fechamento. 3 dias grátis, cancele com um clique.

Form K-1 · Free API key

Get your free API key.

One field. No password, no card: enter your email, click the link we send, and your key is on screen.

By continuing you agree to the Terms and Privacy Policy.

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