Cálculos financeiros, consultas SQL e o mercado de ações – Parte III

Olá Pessoal,

Dando continuidade aos artigos relacionados ao mercado financeiro, hoje espero diminuir a complexidade gerada no último artigo. Ainda irei abordar conceitos financeiros e hoje serão apresentadas situações de dois ativos muito bem falado nos mercados de ações. Alguns exemplos irão utilizar as cotações dos papéis da Vale do Rio Doce (VALE5) e da Petrobrás (PETR4). Antes de propriamente falar desses ativos, falarei de alguns conceitos financeiros como taxa de juros (simples e compostos) e taxas de juros.

O que são juros ?

Toda vez que existe capital, esse capital pode possuir um fim específico. O capital pode ser utilizado para consumo, poupança, pagamento de impostos ou doações. Como impostos são de fato uma imposição e que doações não fazem parte do cotidiano de muitos, concentrarei-me nas destinações de consumo vs poupança para a destinação do capital. Considerando que o capital é um recurso escasso, pois nunca será possível fazer todas as nossas vontades com o capital disponível, ele torna-se um recurso muito valioso. Quanto maior for a diferença entre o capital disponível e o tamanho de nossas vontades mais precioso ele se torna (para alguns a diferença é pequena enquanto que para outros ela é enorme).

O fato é que toda vez que o capital é emprestado, poupado ou investido, ele não pode ser utilizado para consumo imediato. Ainda que o mesmo possa ser reavido futuramente, ao emprestar, poupar ou investir, é feita a decisão de diminuir o consumo presente na esperança de uma disponibilidade futura. Essa abdicação certamente merece ser premiada. Se hoje decide-se poupar uma quantia de R$ 100,00 por um período de 6 meses, é no mínimo esperado que essa restrição seja recompensada. Não faria sentido deixar de consumir R$ 100,00 hoje para ter apenas R$ 100,00 daqui a seis meses. Abrir mão de R$ 100,00 hoje é esperar que daqui seis meses o valor seja superior a R$ 100,00. Essa diferença, esse prêmio, essa pretenção é conhecida como juros. Os juros nada mais são que uma remuneração pelo uso do capital. Se você se dispõe a emprestá-lo, é natural que seja recompensado por isso.

Taxa de juros

Da mesma forma que o lucro é uma medida absoluta e a margem uma medida relativa, os juros e as taxas de juros obedecem às mesmas relações. O juro é algo absoluto. Pode-se dizer que alguém ganhou R$ 1.000,00 em reais em juros devido a uma operação financeira ou algo do tipo. Se esse valor é alto ou baixo dependende do montante investido. Conseguir R$ 1.000,00 de juros para um montante de R$ 5.000,00 é sem dúvida uma grande conquista. Já conseguir R$ 1.000,00 de juros para um montante de R$ 1 milhão é algo insignificante. No primeiro caso, a taxa de juros corresponde a 20% enquanto que no segundo caso corresponde a 0,1%. Esse cálculo foi feito dividindo-se o valor do juros pelo valor do montante. Ainda que 20% pareça ser um valor elevado, devemos lembrar que para uma taxa ser considerada boa ou ruim não basta levar em conta apenas seu valor. É preciso levar em conta também o tempo necessário para obtê-la. 20% em um mês é excelente, mas em cinco anos é uma péssima taxa (dá menos que o FGTS).

Juros Simples e Juros Compostos

No regime de juros simples, os juros não são incorporados e capitalizados junto ao montante principal. Supondo que uma pessoa invista R$ 500.000,00 a uma taxa de juros de 8% ao mês, os juros serão apresentados conforme a tabela abaixo:

Montante

Taxa de Juros

Juros

Juros Acumulados

Total

R$ 500.000,00

8%

R$ 40.000,00

R$ 40.000,00

R$ 540.000,00

R$ 500.000,00

8%

R$ 40.000,00

R$ 80.000,00

R$ 580.000,00

R$ 500.000,00

8%

R$ 40.000,00

R$ 120.000,00

R$ 620.000,00

R$ 500.000,00

8%

R$ 40.000,00

R$ 160.000,00

R$ 660.000,00

R$ 500.000,00

8%

R$ 40.000,00

R$ 200.000,00

R$ 700.000,00

Na situação de juros simples, o montante investido é de R$ 500.000,00 e com uma taxa de juros de 8% ao mês. o montante rende R$ 40.000,00 por mês. O valor dos juros obtidos não são incorporados ao valor do montante e não irão "render" junto com ele. No mercado financeiro, o regime de juros simples não é o mais dominante, mas existem situações em que ele ocorre. Juros de mora, alguéis, etc são exemplos de aplicações do regime de juros simples.

No regime de juros compostos, os juros são incorporados e capitalizados junto ao montante principal. Supondo que uma pessoa invista R$ 500.000,00 a uma taxa de juros de 8% ao mês, os juros serão apresentados conforme a tabela abaixo:

Montante

Taxa de Juros

Juros

Juros Acumulados

Total

R$ 500.000,00

8%

R$ 40.000,00

R$ 40.000,00

R$ 540.000,00

R$ 540.000,00

8%

R$ 43.200,00

R$ 83.200,00

R$ 583.200,00

R$ 583.200,00

8%

R$ 46.656,00

R$ 129.856,00

R$ 629.856,00

R$ 629.856,00

8%

R$ 50.388,48

R$ 180.244,48

R$ 680.244,48

R$ 680.244,48

8%

R$ 54.419,56

R$ 234.664,04

R$ 734.664,04

No regime de juros compostos, tão logo os juros sejam obtidos, são incorporados ao montante aumentando o rendimento dos juros para os próximos períodos. Ao final do 1º período, tanto no regime de juros simples quanto compostos, os juros obtidos são de R$ 40.000,00. Como no regime de juros compostos os juros são incorporados ao capital, no início do 2º período, o montante considerado não será mais de R$ 500.000,00, mas sim de R$ 540.000,00. À medida que os períodos passam, essa incorporação irá render ainda mais juros, pois, os juros vão se incorporando ao montante para futura capitalização (daí a expressão juros sobre juros).

Cálculos de Juros

Não vou me ater aos detalhes das fórmulas que envolvam os cálculos de juros. Irei abordar também somente o regime de juros compostos, visto que, os cálculos de juros simples são bastante "simples" e fáceis de se expressar através da SQL. Consideremos as seguintes convenções antes de demonstrar os cálculos de juros compostos

  • M – Valor do montante inicial
  • Mn – Valor do montante em um período n
  • Jn – Valor dos juros obtidos em um período n
  • i – Taxa de Juros
  • n – Período

Podemos definir uma fórmula matemática através das seguintes proposições:

  • M1 = M * (1 + i) (Ex: 540.000 = 500.000 * (1 + 8%))
  • M2 = M1 * (1 + i) (Ex: 583.200 = 540.000 * (1 + 8%))
  • M3 = M2 * (1 + i) (Ex: 629.856 = 583.200 * (1 + 8%))
  • Se M1 = M * (1 + i) e M2 = M1 * (1 + i) então M2 = M * (1 + i) * (1 + i) ou M2 = M * (1 + i)2
  • Se M3 = M2 * (1 + i) e M2 = M * (1 + i)2 então M3 = M * (1 + i)3
  • Assim sendo Mn = M * (1 + i)n onde n é o número de períodos

De posse dessa fórmula é possível fazer qualquer cálculo. Supondo a taxa fixa de 8%, podemos montar a mesma consulta em SQL com o mesmo raciocínio

— Declara o valor do montante inicial e a taxa de juros
DECLARE @M DECIMAL(8,2), @i DECIMAL(9,8)
SET @M = 500000.00
SET @i = 0.08

— Cria uma tabela com 5 períodos
DECLARE @N TABLE (N TinyInt)
INSERT INTO @N VALUES (1)
INSERT INTO @N VALUES (2)
INSERT INTO @N VALUES (3)
INSERT INTO @N VALUES (4)
INSERT INTO @N VALUES (5)

— Combina a tabela de períodos com a variável
SELECT @M As Montante, N As Periodo FROM @N

— Calcula o montante final
SELECT
    @M As Montante, @i As Taxa,
    @M * POWER((1 + @i),N) As Total
FROM @N

— Calcula os juros
SELECT
    @M As Montante, @i As Taxa,
    (@M * POWER((1 + @i),N)) – (@M * POWER((1 + @i),N – 1)) As Juros,
    (@M * POWER((1 + @i),N)) As Total
FROM @N

— Calcula os juros acumulados
;WITH Juros As (
SELECT
    @M As Montante, @i As Taxa,
    (@M * POWER((1 + @i),N)) – (@M * POWER((1 + @i),N – 1)) As Juros,
    (@M * POWER((1 + @i),N)) As Total,
    N As Periodo
FROM @N)

SELECT
    Montante, Taxa, Juros,
        (SELECT SUM(Juros) FROM Juros As TInt
        WHERE TInt.Periodo <= TOut.Periodo) As JurosAcumulados,
    Total
FROM Juros As TOut

Mesmo com as deduções apresentadas, alguns trechos do código merecem ser esclarecidos. A função POWER é uma função de exponenciação. As deduções mostram que Mn = M * (1 + i)n. Para que esse comportamento possa ser reproduzido, é necessário utilizar a função POWER.

O cálculo dos juros não foi obtido através da fórumula. A fórmula até poderia ser utilizada, mas ao invés de recorrer à fórmula matemática, achei mais prudente recorrer ao raciocínio lógico. Se os juros são uma recompensa pela disponibilização do capital, podemos afirmar que os juros serão a diferença entre o montante atual e o montante do período anterior já que foi justamente investindo o montante do período anterior é que se obteve juros para chegar-se ao montante atual. Isso mostra uma nova fórmula (J = Mn – Mn-1).

O cálculo dos juros acumulados foi feita acumulando-se a coluna de juros. Para cada período é feita a soma dos juros de todos os períodos iguais ou superiores ao período atual. Demonstrei essa técnica de totalização e subtotal no Webcast – Dicas e Truques sobre consultas complexas no SQL Server. O uso do WITH é para definir uma Common Table Expression que também é explicada em SQL Server 2005 New Features: Common Table Expressions (CTEs).

Entendido os conhecimentos financeiros e os recursos do SQL Server, fica bastante simples efetuar cálculos que envolvam juros compostos. Um simples simulação de empréstimo ou investimento depende apenas do montante inicial, da taxa de juros e do

Ainda que o exemplo acima demonstre situações cotidianas como empréstimos e financiamentos bancários, nem todas as operações financeiras se comportam dessa forma. Nos exemplos acima foi feita a premissa de que a taxa de juros é constante. Nem sempre isso é uma verdade. Vejamos o próximo tópico.

Taxas de juros variáveis

Embora haja diversos investimentos que trabalhem com taxas de juros fixas, há também muitos outros que a taxa não é fixa. Não me refiro a taxas pós-fixadas (taxas desconhecidas não necessariamente deixam de ser fixas), mas a taxas cuja natureza é variável. Investimentos em renda variável quase sempre tem taxas variáveis, pois, dificilmente consegue-se se repetir o mesmo comportamento durante os períodos. Há também outras situações que envolvam taxas variáveis. Olhemos por exemplo a inflação dos últimos anos.

Ano

Taxa de Inflação

2000

5,97%

2001

7,67%

2002

12,53%

2003

9,3%

2004

7,6%

2005

5,69%

2006

3,14%

2007

4,46%

2008

5,9%

A inflação significa perda do poder de compra ou aumento dos preços. Se em 2000 tivemos uma inflação de 5,97% significa que no início do ano, um bem de R$ 100,00 passou a custar em média R$ 105,97 no final do ano. Podemos ver que é praticamente impossível manter uma taxa de inflação constante ao longo dos anos. Haverá anos em que a inflação será maior ou menor, mas por mais que a fórmula seja repetida é praticamente impossível repetí-la. Trata-se de um caso perfeito de uma taxa variável.

Se tívessemos uma taxa de inflação fixa, seria muito fácil calcular a inflação em períodos superiores a um ano. Bastaria aplicar uma típica progressão geométrica dada pela fórmula: In = (1 + i)n onde n representa um dado período.

Como nem sempre (ou boa parte dos casos) a taxa não será fixa, a fórmula acima perde parte do seu valor e não pode-se fazer uma progressão geométrica. Se bem lembrarmos do artigo anterior, esse problema se assemelha bastante ao cálculo da rentabilidade acumulada. Na ocasião pode-se obter uma lógica para calculá-la. Foi utilizado um artífico do TSQL para fazê-lo.

— Cria a tabela
CREATE TABLE Inflacao (Ano SMALLINT, Taxa DECIMAL(5,4))

— Insere os registros
INSERT INTO Inflacao (Ano,Taxa) VALUES (2000,0.0597)
INSERT INTO Inflacao (Ano,Taxa) VALUES (2001,0.0767)
INSERT INTO Inflacao (Ano,Taxa) VALUES (2002,0.0253)
INSERT INTO Inflacao (Ano,Taxa) VALUES (2003,0.0930)
INSERT INTO Inflacao (Ano,Taxa) VALUES (2004,0.0760)
INSERT INTO Inflacao (Ano,Taxa) VALUES (2005,0.0569)
INSERT INTO Inflacao (Ano,Taxa) VALUES (2006,0.0314)
INSERT INTO Inflacao (Ano,Taxa) VALUES (2007,0.0446)
INSERT INTO Inflacao (Ano,Taxa) VALUES (2008,0.0590)

— Cria uma função de multiplicação
CREATE FUNCTION dbo.Multiplica (@Ano SMALLINT)
RETURNS DECIMAL(6,4)
AS
BEGIN

— Declara a variável para cálculo da inflação acumulada
DECLARE @i DECIMAL(6,4)
SET @i = 1

SELECT @i = @i * (1 + Taxa) FROM Inflacao
WHERE Ano <= @Ano

SET @i = @i – 1

RETURN (@i)

END

— Retorna a inflação e a inflação acumulada
SELECT Ano, Taxa, dbo.Multiplica(Ano) As TaxaAcumulada FROM Inflacao

O retorno da consulta é:

Ano

Taxa

Taxa Acumulada

2000

5,97%

5,97%

2001

7,67%

14,10%

2002

12,53%

16,99%

2003

9,3%

27,87%

2004

7,6%

37,59%

2005

5,69%

45,42%

2006

3,14%

49,99%

2007

4,46%

56,68%

2008

5,9%

65,92%

Fora o fato de descobrir que os preços subiram quase 66% nos últimos 8 anos, ou seja, algo que custava R$ 100,00 está custando em média R$ 166,00 há mais um problema. A função "Multiplica" foi feita porque a SQL não possui uma função de agregação nativa para fazer multiplicações. A SQL dispõe do SUM, do COUNT, do AVG, etc mas não há nada do tipo "MULTIPLY" na SQL. A function resolveu o problema, mas seu comportamento é iterativo e procedural o que normalmente leva a perdas de desempenho. No último artigo prometi uma alternativa mais eficiente e ANSI compliance. Antes de demonstrá-la, vamos a um pouco mais de matemática

Agregações, Logaritmos e SQL

Se desejamos produzir uma alternativa mais eficiente e ainda por cima ANSI compliance, não podemos pensar no SQL Server, seus cursores, suas functions e SPs ou ainda no CLR. É preciso encontrar uma forma de suprimir a limitação das funções de agregação e a ausência de algo como "MULTIPLY". Deixando um pouco a SQL de lado, vamos recorrer um pouco ao raciocínio matemático.

Em matemática, o logaritmo é o oposto da exponenciação. Sendo AB = C, dizemos que logAC = B. Essa talvez seja a melhor forma de definir o logaritmo.

  • Se 23 = 8 então log28 = 3
  • Se 103 = 1000 então log101000 = 3 ou simplesmente log 1000 = 3 (a ausência da base é um log de base 10)
  • Se log5625 = 4 então 54 = 625

Os logaritmos compartilham algumas propriedades que merecem atenção (estão todos na base 10)

  • Se C = A * B então logC = logA + logB (Ex: 1.000 = 100 * 10 e log 1.000 = log 100 + log 10, ou seja, 3 = 2 + 1)
  • Se A = C / B então logA = logB – logC (Ex: 100 = 100.000 / 1.000 e log 1.000 = log 100.000 – log 100, ou seja, 3 = 5 – 2)
  • logAN é igual a N * logA
  • Se Y = logX então 10Y = X

Existem outras propriedades logarítimicas, mas por hora basta expor as principais. Para o exemplo são necessárias apenas a primeira e a última. Utilizemos um simples exemplo.

CREATE TABLE Numeros (Numero TINYINT)
INSERT INTO Numeros (Numero) VALUES (2)
INSERT INTO Numeros (Numero) VALUES (3)
INSERT INTO Numeros (Numero) VALUES (6)
INSERT INTO Numeros (Numero) VALUES (7)

Essa tabela possui uma simples lista de números. E se fosse necessário multiplicar esses números ? Sabemos que 2 * 3 * 6 * 7 é igual a 252, mas como fazer isso usando SQL ? As propriedades logarítmicas podem ajudar. Vamos chamar o produto de 2 * 3 * 6 * 7 de P, ou seja, P é igual a 2 * 3 * 6 * 7 que é igual a 252. Chamarei de L o logaritmo de P na base 10.

  • Se L é igual a logP, então L é igual a log(2 * 3 * 6 * 7)
  • Se L é igual a log(2 * 3 * 6 * 7) então L é igual a log2 + log3 + log6 + log7
  • Se 10L é igual a P, então 10log2 + log3 + log6 + log7 é igual P

Façamos uma simples prova real das proposições em TSQL.

DECLARE @L DECIMAL(15,10), @P TINYINT
SET @L = LOG10(2) + LOG10(3) + LOG10(6) + LOG10(7)
SET @P = POWER(10,@L)

SELECT @P

As propriedades são verídicas, pois, o valor de P é de fato 252 que é o produto de 2 * 3 * 6 * 7. Agora vejamos na forma de um SELECT

SELECT POWER(10,SUM(LOG10(Numero))) FROM Numeros

De fato as propriedades logarítimicas ajudam a expandir a SQL, uma vez que é possível somar os logaritmos com a função SUM normalmente e utilizar o resultado em uma exponenciação. Mas e se fosse necessário "acumular" ao invés de simples fornecer o resultado final ? A SQL também pode ser utilizada para tal. Basta combinar o uso do logaritmo com a técnica de saldos ou de subtotal exposta em Webcast – Dicas e Truques sobre consultas complexas no SQL Server.

SELECT Numero,
    (SELECT POWER(10,SUM(LOG10(Numero))) FROM Numeros As TInt
    WHERE TInt.Numero <= TOut.Numero) As ProdutoAcumulado
FROM Numeros As TOut

A consulta acima retorna os valores abaixo (excetuando-se a última coluna):

Numero

ProdutoAcumulado

Cálculo

2

2

2

3

6

2 * 3

6

36

2 * 3 * 6

7

252

2 * 3 * 6 * 7

Agora que foi demonstrado que a SQL pode ser utilizada para emular uma função do tipo "MULTIPLY" ou "PRODUCT" é possível calcular a taxa de juros acumulada com base nas taxas de juros individuais, façamos o cálculo utilizando algumas cotações individuais de dois papéis muito comentados: A Vale do Rio Doce (VALE5) e a Petrobrás (PETR4).

CREATE TABLE Cotacoes (Ativo CHAR(5), Data SMALLDATETIME, Valor DECIMAL(4,2))

INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20080218’,40.88)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20080318’,36.07)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20080418’,41.15)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20080519’,48.25)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20080618’,43.81)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20080718’,36.86)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20080818’,30.57)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20080918’,31.07)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20081020’,24.50)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20081118’,18.57)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20081218’,22.68)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20090119’,23.83)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘PETR4’, ‘20090218’,25.83)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20080218’,48.86)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20080318’,47.70)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20080418’,51.70)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20080519’,58.70)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20080618’,48.90)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20080718’,40.16)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20080818’,34.90)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20080918’,34.60)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20081020’,26.09)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20081118’,23.14)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20081218’,25.31)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20090119’,26.39)
INSERT INTO Cotacoes (Ativo, Data, Valor) VALUES (‘VALE5’, ‘20090218’,29.61)

O primeiro passo é calcular a taxa de juros individual para cada cotação

;WITH CotacoesRef (Ativo, Data, Valor, ValorAnterior)
As (
SELECT Ativo, Data, Valor,
    (SELECT TOP 1 Valor FROM Cotacoes As TInt
    WHERE TInt.Ativo = TOut.Ativo And TInt.Data < TOut.Data
    ORDER BY Data Desc) As ValorAnterior
FROM Cotacoes As TOut)

SELECT Ativo, Data, Valor,
    CASE WHEN ValorAnterior IS NULL THEN 0
    ELSE (Valor / ValorAnterior) – 1 END As Taxa
FROM CotacoesRef
ORDER BY Ativo, Data

A consulta acima já obtem a taxa de juros referente aos investimentos e suas cotações:

Ativo

Data

Valor

Taxa

PETR4

18/02/2008

40.88

0.0000000

PETR4

18/03/2008

36.07

-0.1176615

PETR4

18/04/2008

41.15

0.1408372

PETR4

19/05/2008

42.85

0.1725394

PETR4

18/06/2008

43.81

-0.0920208

PETR4

18/07/2008

36.86

-0.1586396

PETR4

18/08/2008

30.57

-0.1706457

PETR4

18/09/2008

31.07

0.0163559

PETR4

20/10/2008

24.50

-0.2144580

PETR4

18/11/2008

18.57

-0.2420409

PETR4

18/12/2008

22.68

0.2213247

PETR4

19/01/2009

23.83

0.0507054

PETR4

18/02/2009

25.83

0.0839278

VALE5

18/02/2008

48.86

0.000000

VALE5

18/03/2008

47.70

-0.0237414

VALE5

18/04/2008

51.70

0.0838574

VALE5

19/05/2008

58.70

0.1353965

VALE5

18/06/2008

48.90

-0.1669506

VALE5

18/07/2008

40.16

-0.1787322

VALE5

18/08/2008

34.90

-0.1309761

VALE5

18/09/2008

34.60

-0.0085960

VALE5

20/10/2008

26.09

-0.2459538

VALE5

18/11/2008

23.14

-0.1130702

VALE5

18/12/2008

25.31

0.0937770

VALE5

19/01/2009

26.39

0.0426708

VALE5

18/02/2009

29.61

0.1220159

Com uso das funções de logaritmo e exponenciação, é possível obter as taxas de forma acumulada.

;WITH CotacoesRef (Ativo, Data, Valor, ValorAnterior)
As (
SELECT Ativo, Data, Valor,
    (SELECT TOP 1 Valor FROM Cotacoes As TInt
    WHERE TInt.Ativo = TOut.Ativo And TInt.Data < TOut.Data
    ORDER BY Data Desc) As ValorAnterior
FROM Cotacoes As TOut)
,

Taxas (Ativo, Data, Valor, Taxa)
As (
SELECT Ativo, Data, Valor,
    CASE WHEN ValorAnterior IS NULL THEN 0
    ELSE (Valor / ValorAnterior) – 1 END As Taxa
FROM CotacoesRef
)

SELECT Ativo, Data, Valor, Taxa,
    (SELECT POWER(10.0000000,SUM(LOG10(1 + Taxa))) FROM Taxas As TInt
    WHERE TInt.Ativo = TOut.Ativo And TInt.Data <= TOut.Data) As TaxaAcumulada
FROM Taxas As TOut
ORDER BY Ativo, Data

O resultado da consulta é exibido abaixo:

Ativo

Data

Valor

Taxa

Taxa Acumulada

Prop

PETR4

18/02/2008

40.88

0.0000000

0.0000000

1.0000000

PETR4

18/03/2008

36.07

-0.1176615

-0.1176615

0.8823385

PETR4

18/04/2008

41.15

0.1408372

0.0066046

1.0066046

PETR4

19/05/2008

42.85

0.1725394

0.1802835

1.1802835

PETR4

18/06/2008

43.81

-0.0920208

0.0716729

1.0716729

PETR4

18/07/2008

36.86

-0.1586396

-0.0983369

0.9016631

PETR4

18/08/2008

30.57

-0.1706457

-0.2522018

0.7477982

PETR4

18/09/2008

31.07

0.0163559

-0.2399709

0.7600291

PETR4

20/10/2008

24.50

-0.2144580

-0.4006851

0.5993149

PETR4

18/11/2008

18.57

-0.2420409

-0.5457438

0.4542562

PETR4

18/12/2008

22.68

0.2213247

-0.4452057

0.5547943

PETR4

19/01/2009

23.83

0.0507054

-0.4170747

0.5829253

PETR4

18/02/2009

25.83

0.0839278

-0.3681510

0.6318490

VALE5

18/02/2008

48.86

0.000000

0.0000000

1.0000000

VALE5

18/03/2008

47.70

-0.0237414

-0.0237414

0.9762586

VALE5

18/04/2008

51.70

0.0838574

0.0581251

1.0581251

VALE5

19/05/2008

58.70

0.1353965

0.2013915

1.2013915

VALE5

18/06/2008

48.90

-0.1669506

0.0008185

1.0008185

VALE5

18/07/2008

40.16

-0.1787322

-0.1780600

0.8219400

VALE5

18/08/2008

34.90

-0.1309761

-0.2857145

0.7142855

VALE5

18/09/2008

34.60

-0.0085960

-0.2918545

0.7081455

VALE5

20/10/2008

26.09

-0.2459538

-0.4660256

0.5339744

VALE5

18/11/2008

23.14

-0.1130702

-0.5264022

0.4735978

VALE5

18/12/2008

25.31

0.0937770

-0.4819896

0.5180104

VALE5

19/01/2009

26.39

0.0426708

-0.4598857

0.5401143

VALE5

18/02/2009

29.61

0.1220159

-0.3939831

0.6060169

É surpreendente ver que após um ano, as ações preferenciais da Petrobrás e da Vale do Rio Doce desvalorizaram aproximadamente 40% do capital (taxa acumulada) e ficarão restritas a pouco mais de 60% do seu valor original (a coluna Prop é de proporção). Essa é sem dúvida uma trágica constatação para quem adquiriu os papéis dessas duas empresas a um ano atrás e os manteve. São sinais claros da crise mundial que começou com a bolha imobiliária. Dizem que agora é um bom momento para investir, mas por enquanto limito-me a tentar calcular esses percentuais em TSQL. Talvez o melhor investimento até então seja continuar estudando.

Durante esse artigo algumas perguntas relacionadas a precisão devem ter ficado sem resposta

  • Por que a taxa (@i) foi declarada como DECIMAL(9,8) na demonstração dos juros ?
  • Por que a taxa (@i) foi declarada como DECIMAL(6,4) dentro da function ?
  • Por que o logaritmo (@L) foi declarado como DECIMAL(15,10) ?
  • Por que a função POWER utilizou 10.0000000 ao invés de usar simplesmente 10 ?

De fato são perguntas que devem ter ficado sem respostas durante a escrita do artigo. A resposta a todas elas é a mesma. As definições tipos utilizam as precisões mínimas para que os exemplos funcionem. Uma vez que os cálculos logaritmicos, as funções exponenciais, etc exijam alta precisão é necessário "extrapolar".

Estou certo também de que após tantas deduções matemáticas e explicações sobre o mercado financeiro, além de uma boa dose de complexidade nas consultas, alguém certamente refletirá: "É muito complicado e difícil. Eu certamente utilizaria um cursor para fazer esses cálculos ou faria na aplicação". Sim, são considerações e reflexões válidas, mas tanto o cursor quanto a aplicação normalmente utilizarão um comportamento linha a linha e não um comportamento em termos de conjuntos como a SQL realiza e isso tende a denegrir o desempenho. Fiz um comparativo utilizando as cotações diárias dos dois ativos de 18/02/2008 a 18/02/2009 (um ano de cotações) utilizando as fórmulas matemáticas e o uso de cursores. O código abaixo é uma possível utilização do cursor.

— Declara uma tabela para armazenar os resultados
DECLARE @Res TABLE (
    Ativo CHAR(5), Data SMALLDATETIME,
    Valor DECIMAL(4,2), Taxa DECIMAL(8,7),
    TaxaAcumulada DECIMAL(8,7), Prop DECIMAL(8,7))

— Declara as variáveis para acumular os valores necessários
DECLARE @Ativo CHAR(5), @Data SMALLDATETIME,
    @Valor DECIMAL(4,2), @Taxa DECIMAL(8,7),
    @TaxaAcumulada DECIMAL(8,7), @Prop DECIMAL(8,7)

— Declara as variáveis auxiliares
DECLARE
    @AtivoAnterior CHAR(5), @ValorAnterior DECIMAL(4,2),
    @TaxaAnterior DECIMAL(8,7), @ValorBase DECIMAL(4,2)

— Inicializa as variáveis
SET @Taxa = 0
SET @TaxaAcumulada = 0
SET @ValorAnterior = 0
SET @TaxaAnterior = 0
SET @AtivoAnterior =

— Declara um Cursor para percorrer os resultados
DECLARE Taxas CURSOR
FAST_FORWARD
FOR SELECT Ativo, Data, Valor FROM Cotacoes
ORDER BY Ativo, Data

— Abre o Cursor
OPEN Taxas

FETCH NEXT FROM Taxas INTO @Ativo, @Data, @Valor

SET @ValorBase = @Valor
SET @AtivoAnterior = @Ativo
SET @ValorAnterior = @Valor

WHILE @@FETCH_STATUS = 0
BEGIN
    — Verifica se não trocou o papel
    IF @Ativo = @AtivoAnterior
    BEGIN
        SET @Taxa = (@Valor / @ValorAnterior) – 1
        SET @Prop = @Valor / @ValorBase
        SET @TaxaAcumulada = @Prop – 1
        INSERT INTO @Res VALUES (@Ativo, @Data, @Valor, @Taxa, @TaxaAcumulada, @Prop)

        SET @AtivoAnterior = @Ativo
        SET @ValorAnterior = @Valor

        — Avança para a próxima linha
        FETCH NEXT FROM Taxas INTO @Ativo, @Data, @Valor
    END

    ELSE
    BEGIN
        SET @ValorBase = @Valor
        SET @AtivoAnterior = @Ativo
    END
END

CLOSE Taxas
DEALLOCATE Taxas

SELECT Ativo, Data, Valor, Taxa, TaxaAcumulada, Prop FROM @Res

Sinceramente não sei até que ponto substituir a lógica utilizando algoritmos por cursor é mais "fácil". Particularmente demorei quase 40 minutos para pensar, escrever e testar o código usando o cursor. Entretanto o resultado é revelador. Segue uma versão simplificada.

Método

Cotações Mensais

Cotações Diárias

SQL

19ms

5199ms

SQL (com índices)

17ms

696ms

Cursores

208ms

1037ms

Cursores (com índices)

190ms

603ms

Ao contrário do que possa parecer, os cursores e seu comportamento linha a linha superaram a SQL. A utilização de índices ajudou as consultas SQL a diminuirem drasticamente seu tempo de execução, mas ainda assim os cursores foram 40% mais rápidos. Não coloquei nos resultados os custos de I/O das consultas SQL, mas os mesmos foram muito maiores que os custos e I/O dos cursores. Como pode um cursor ser mais rápido do que uma instrução SQL ? Deixarei a explicação para um artigo posterior. Os que desejarem obter os scripts de testes com as cotações diárias cliquem no link abaixo:

http://cid-f4f5c630410b9865.skydrive.live.com/self.aspx/ProjetosSQLServer/20090218%7C_Cotacoes.zip

O objetivo desses artigos foi demonstrar alguns cálculos financeiros comuns e alternativas em TSQL comuns de resolução. De forma nenhuma o assunto está esgotado e ainda voltarei a abordá-los. Embora seja muito tentador utilizar o TSQL (seja com consultas ANSI ou com cursores), o fato de ser possível não quer dizer que é melhor. Há outras alternativas como o CLR e principalmente o Analysis Services. O Analysis Services tem um suporte muito superior para lidar com esse tipo de cálculo além do que possuir os dados pré-calculados ou armazenados lhe confere um desempenho muito superior a consultas TSQL comuns. Não basta apenas escolher uma solução que funcione, é preciso escolher uma que funcione bem. Se isso não for observado, o desempenho pode declinar de forma semelhante às ações da Petrobrás e da Vale.

[ ]s,

Gustavo

2 Respostas para “Cálculos financeiros, consultas SQL e o mercado de ações – Parte III

  1. Excelente material, dificil de encontrar com tamanho detalhamento.
    Parabéns.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s