1

Como calcular o Preço Médio Ponderado de ativos com vendas parciais e taxas (lógica e implementação)

Ao desenvolver sistemas de portfólio financeiro ou ferramentas pessoais para controle de investimentos na B3, o primeiro desafio contábil real é implementar o algoritmo de Preço Médio (PM).

A Receita Federal do Brasil exige o método da média ponderada móvel. Em termos de código, o cálculo parece trivial até você lidar com compras sucessivas, taxas de corretagem/emolumentos e vendas parciais.

As regras fundamentais

  1. Compras somam custos: As taxas operacionais (emolumentos B3, corretagem, liquidação) devem ser incorporadas ao custo de aquisição. Isso aumenta o PM.
  2. Vendas não alteram o PM: A venda diminui a quantidade de ativos em custódia, mas o preço médio unitário permanece inalterado. As taxas da venda não alteram o PM das cotas remanescentes; elas reduzem o valor líquido da venda para apuração do lucro.
  3. Zerar a posição: Se a quantidade chegar a zero, o PM é resetado.

O modelo matemático passo a passo

Considere a seguinte sequência de eventos:

  1. Compra 1: 100 cotas a R$ 20,00. Custos operacionais = R$ 10,00.
  • Custo total = (100 * 20,00) + 10,00 = R$ 2.010,00
  • Quantidade acumulada = 100
  • Preço Médio = R$ 2.010,00 / 100 = R$ 20,10
  1. Compra 2: 50 cotas a R$ 26,00. Custos operacionais = R$ 5,00.
  • Custo desta operação = (50 * 26,00) + 5,00 = R$ 1.305,00
  • Custo total acumulado = 2.010,00 + 1.305,00 = R$ 3.315,00
  • Quantidade acumulada = 100 + 50 = 150
  • Novo Preço Médio = R$ 3.315,00 / 150 = R$ 22,10
  1. Venda parcial: Venda de 70 cotas a R$ 30,00. Custos operacionais = R$ 8,00.
  • A venda NÃO altera o PM unitário anterior. Ele continua sendo R$ 22,10.
  • Quantidade restante em custódia = 150 - 70 = 80 cotas.
  • Novo custo total em custódia = 80 * R$ 22,10 = R$ 1.768,00.

Para calcular o resultado tributável dessa venda:

  • Receita líquida = (70 * R$ 30,00) - R$ 8,00 = R$ 2.092,00
  • Custo de aquisição proporcional = 70 * R$ 22,10 = R$ 1.547,00
  • Ganho líquido = R$ 2.092,00 - R$ 1.547,00 = R$ 545,00

Exemplo simples em TypeScript

type Operacao = {
  tipo: 'COMPRA' | 'VENDA';
  quantidade: number;
  precoUnitario: number;
  taxas: number;
};

type Posicao = {
  quantidade: number;
  precoMedio: number;
};

function processarOperacoes(operacoes: Operacao[]): Posicao {
  let quantidade = 0;
  let custoTotal = 0;

  for (const op of operacoes) {
    if (op.tipo === 'COMPRA') {
      const custoOperacao = (op.quantidade * op.precoUnitario) + op.taxas;
      custoTotal += custoOperacao;
      quantidade += op.quantidade;
    } else if (op.tipo === 'VENDA') {
      if (op.quantidade > quantidade) {
        throw new Error('Tentativa de vender mais do que a custódia disponível');
      }
      const pmAtual = custoTotal / quantidade;
      quantidade -= op.quantidade;
      custoTotal = quantidade * pmAtual;
    }

    if (quantidade === 0) {
      custoTotal = 0;
    }
  }

  return {
    quantidade,
    precoMedio: quantidade > 0 ? custoTotal / quantidade : 0
  };
}

O maior problema em sistemas reais surge quando há operações de desdobramentos (splits), grupamentos e bonificações no meio do log de transações. Nesses casos, o ajuste de quantidade precisa ocorrer mantendo a paridade do custo acumulado.

Carregando publicação patrocinada...
1

Dois detalhes que costumam aparecer quando isso sai do exemplo e vai pra nota de corretagem de verdade.

O primeiro é day trade. Compra e venda do mesmo ativo no mesmo dia são apuradas separado das operações comuns, então a parte casada no dia não deveria entrar no PM da carteira. Se o log mistura tudo, o PM sai errado justo nos dias em que a pessoa operou dos dois lados.

O outro é usar number pra dinheiro. No exemplo as contas fecham, mas com emolumento em centavos quebrados e muita operação o float começa a acumular erro. Guardar em centavos inteiros (ou usar algo tipo decimal.js) e arredondar só na exibição evita aquela diferença de 1 centavo que ninguém consegue explicar depois.