From 7b7e6666c20c8a37d3f03c3b157649d734a56df3 Mon Sep 17 00:00:00 2001 From: Felipe Santana Soares Silva <145938001+felipesantanafs@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:45:21 -0300 Subject: [PATCH] =?UTF-8?q?[2025-s2]=20POO=20-=20Avan=C3=A7ado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tipo de arquivo: Jupiter Notebook Trilha: Ciência de Dados Nível de dificuldade: Avançado Tópicos abordados: - O que é Programação Orientada a Objetos (POO) - Conceitos de classe, objeto, atributos e métodos - Exemplos em Python --- .../2025-s2/introducao_poo_fea_dev.md | 1403 +++++++++++++++++ 1 file changed, 1403 insertions(+) create mode 100644 content/03-ciencia-de-dados/2025-s2/introducao_poo_fea_dev.md diff --git a/content/03-ciencia-de-dados/2025-s2/introducao_poo_fea_dev.md b/content/03-ciencia-de-dados/2025-s2/introducao_poo_fea_dev.md new file mode 100644 index 000000000..1488d1547 --- /dev/null +++ b/content/03-ciencia-de-dados/2025-s2/introducao_poo_fea_dev.md @@ -0,0 +1,1403 @@ +# **Módulo 1 - Introdução a Programação Orientada a Objetos** + +## **1.1 - O que é Programação Orientada a Objetos ?** + +A Programação Orientada a Objetos *(POO)* é um estilo de programação que organiza o código em unidades chamadas objetos. Esses objetos representam entidades do mundo real e possuem características *(dados)* e ações *(comportamentos)*. **Esse modelo facilita a organização do código e torna os programas mais intuitivos e modulares**. + +Em suma, esse paradigma visa aproximar a programação da forma como percebemos o mundo real, onde objetos interagem entre si para realizar tarefas. + +#### **Diferença entre Programação Procedural e Programação Orientada a Objetos** + +A programação procedural, que geralmente é a primeira abordagem que aprendemos, organiza o código em funções e estruturas de controle, mantendo os dados e os procedimentos separados. Por outro lado, a Programação Orientada a Objetos encapsula dados e comportamentos dentro de objetos, promovendo uma estrutura mais modular, facilitando a reutilização e a manutenção do código. + +##### *Exemplo de Programação Procedural* + + +```python +# Procedural: Funções manipulam diretamente os dados +def criar_conta(nome, saldo): + return {"nome": nome, "saldo": saldo} + +def depositar(conta, valor): + conta["saldo"] += valor + +def exibir_saldo(conta): + print(f"Saldo de {conta['nome']}: {conta['saldo']}") + +# Uso +diego = criar_conta("Diego", 1000) +depositar(diego, 500) +exibir_saldo(diego) +``` + + Saldo de Diego: 1500 + + +##### *Exemplo de Programação Orientada a Objetos* + + +```python +# Orientado a Objetos: Criamos uma classe Conta +class Conta: + def __init__(self, nome, saldo): + self.nome = nome + self.saldo = saldo + + def depositar(self, valor): + self.saldo += valor + + def exibir_saldo(self): + print(f"Saldo de {self.nome}: {self.saldo}") + +# Uso +diego = Conta("Diego", 1000) +diego.depositar(500) +diego.exibir_saldo() +``` + + Saldo de Diego: 1500 + + +##### **Benefícios da Programação Orientada a Objetos** + +- Modularidade: O código é dividido em objetos independentes, facilitando a manutenção. + +- Reutilização de Código: Classes podem ser reutilizadas em diferentes partes do projeto. + +- Encapsulamento: Protege os dados do objeto e permite melhor controle de acesso. + +- Flexibilidade e Extensibilidade: É fácil adicionar novas funcionalidades. + +- Facilidade na modelagem: Permite criar representações próximas ao mundo real + +## **1.2 - Definições fundamentais** + +### **Classes** + +#### **Definição intuitiva** + +**Uma classe pode ser entendida como um molde ou projeto.** + +Exemplo intuitivo: +Imagine que você quer construir várias casas. Contudo, antes de começar, precisa de um projeto que detalhe: + +- Número de quartos + +- Localização da porta + +- Tamanho das janelas + +- E outros detalhes importantes + +**Esse projeto não é uma casa de verdade, mas define todas as informações necessárias para construir quantas casas você +quiser. Esse projeto é o que chamamos de classe.** + +#### **Classes na programação** + +Em termos de programação, uma classe funciona da mesma forma. Ela é um projeto que define as características (atributos) +e os comportamentos (métodos) de um tipo específico de elemento. + +Por exemplo, se quisermos representar carros no nosso programa, criamos uma classe chamada Carro. Essa classe vai dizer: + +- Quais informações cada carro terá (cor, modelo, ano de fabricação). + +- Quais ações o carro pode realizar (acelerar, frear, buzinar). + +#### **Outra forma de pensar em uma classe** + +**Uma classe também pode ser vista como a criação de um novo tipo de dado.** + +Em Python, já temos tipos de dados como int *(para números inteiros)*, str *(para textos)* e float *(para números decimais)*. +**Quando você cria uma classe, está definindo seu próprio tipo de dado personalizado, com características e comportamentos únicos**. +Por exemplo: + +Enquanto o tipo int serve para representar números inteiros. A classe Carro pode servir para representar carros, com todas +as informações que você quiser incluir (como marca, cor e velocidade). + + +#### **Exemplo prático** + + +```python +# Vamos começar a criar uma classe para representar carros + +# Primeiro, vamos criar a estrutura base da nossa classe. +# Neste momento, ela é apenas um "projeto" vazio, sem características nem comportamentos. + +class Carro: + pass # Usamos a palavra-chave 'pass' para indicar que a classe está vazia por enquanto. + # Isso evita erros de sintaxe e nos permite continuar desenvolvendo o código depois. +``` + +### **Atributos** + +#### **Definição intuitiva** + + +Depois de entender o que é uma classe (um molde ou projeto), **podemos pensar nos atributos como as características ou propriedades que definimos nesse projeto**. + +Exemplo intuitivo: +Imagine novamente o projeto de uma casa. Esse projeto descreve várias características, como: + +- Número de quartos +- Cor das paredes +- Tamanho das janelas +- E outros detalhes importantes + +Esses detalhes são os atributos da casa. Quando você constrói várias casas com base no mesmo projeto, cada uma pode ter atributos diferentes. + +Por exemplo: + +Uma casa pode ter 3 quartos e paredes azuis. Enquanto outra pode ter 4 quartos e paredes brancas. + +**Ou seja, mesmo que o projeto (classe) seja o mesmo, os atributos permitem que cada casa construída tenha suas próprias características únicas.** + +#### **Atributos na programação** + +**Em termos de programação, atributos são as informações que descrevem algo criado a partir de uma classe.** + +Por exemplo, se temos uma classe chamada Carro, os atributos podem ser: + +- cor (ex: vermelho, azul) +- modelo (ex: Toyota, Ford) +- ano de fabricação (ex: 2020, 2023) +- velocidade atual (ex: 0 km/h, 60 km/h) + +Cada carro que criamos a partir da classe Carro pode ter atributos diferentes. **Assim como no exemplo das casas, o molde é o mesmo, mas os detalhes (atributos) mudam de um carro para outro.** + +#### **Exemplo prático** + + +```python +# 2. Adicionando Atributos + +# Agora vamos definir as características (atributos) que cada carro terá. +# Para isso, usamos uma função especial chamada __init__ +# (explicaremos melhor sobre funções/métodos especiais em tópicos futuros). +# O __init__ é automaticamente executado sempre que criamos um novo carro a partir da nossa classe. +# Ele é responsável por inicializar o carro com as informações que fornecemos, +# ou seja, atribui os valores passados como parâmetros (marca, modelo, ano, cor) +# às características específicas do carro, definindo seus atributos iniciais. + +class Carro: + def __init__(self, marca, modelo, ano, cor): + # Observação importante: + # O parâmetro 'self' representa o próprio carro que está sendo criado. + # Ele é usado para acessar e definir as características (atributos) do carro, + # associando os valores que passamos como parâmetros (marca, modelo, ano, cor) + # às propriedades permanentes do carro (como self.marca, self.modelo, etc.). + + self.marca = marca # 'self.marca' é o atributo do carro, ou seja, a marca que será armazenada no carro. + # 'marca' (sem o self) é o valor que passamos quando criamos o carro. + # Exemplo: se criarmos Carro("Toyota", "Corolla", 2020, "Vermelho"), + # o valor "Toyota" será armazenado em self.marca. + + self.modelo = modelo # 'self.modelo' é o atributo que guarda o modelo do carro. + # 'modelo' é o valor passado quando o carro é criado. + # Exemplo: "Corolla" será armazenado em self.modelo. + + self.ano = ano # 'self.ano' é o atributo que representa o ano de fabricação do carro. + # 'ano' é o valor passado na criação do carro. + # Exemplo: 2020 será armazenado em self.ano. + + self.cor = cor # 'self.cor' é o atributo que guarda a cor do carro. + # 'cor' é o valor fornecido ao criar o carro. + # Exemplo: "Vermelho" será armazenado em self.cor. +``` + +### **Métodos** + +#### **Definição intuitiva** + +Agora que sabemos que uma classe é um projeto e que os atributos são as características desse projeto, **podemos pensar nos métodos como as ações ou comportamentos que algo criado a partir da classe pode realizar**. + +Exemplo intuitivo: +Voltando ao exemplo da casa, imagine que o projeto não define apenas as características (como o número de quartos), mas também o que a casa pode fazer, como: + +- Abrir portas +- Acender luzes +- Trancar janelas + +**Essas ações são os métodos da casa. Cada casa construída a partir do projeto pode realizar essas ações.** + + +#### **Métodos na programação** + +**Na programação, métodos são funções associadas a uma classe. Eles definem o que os objetos (que são criados a partir da classe) podem fazer**. + +Se criarmos uma classe Carro, podemos definir métodos como: + +- acelerar() – para aumentar a velocidade do carro. +- frear() – para reduzir a velocidade. +- buzinar() – para emitir um som de buzina. + +**Os métodos usam ou modificam os atributos do carro**. Por exemplo, o método acelerar() pode aumentar o valor do atributo velocidade atual. + +#### **Exemplo prático** + + +```python +# 3. Adicionando Comportamentos (criando métodos) + +# Agora vamos adicionar comportamentos que o carro pode realizar. +# Para isso, criaremos métodos, que são funções definidas dentro da classe. +# Esses métodos descrevem ações específicas que o carro pode executar, como acelerar, frear ou buzinar. +# Com isso, o carro deixa de ser apenas um conjunto de informações (atributos) e passa a interagir com o ambiente, +# simulando comportamentos do mundo real. + +class Carro: + def __init__(self, marca, modelo, ano, cor): + self.marca = marca + self.modelo = modelo + self.ano = ano + self.cor = cor + self.velocidade = 0 # Começamos com o carro parado + + def acelerar(self, incremento): + # O método acelerar aumenta o valor do atributo 'velocidade' do carro. + # O parâmetro 'incremento' define o quanto a velocidade deve aumentar. + # Por exemplo, se o carro estiver a 20 km/h e o incremento for 10, + # a nova velocidade será 30 km/h. + + self.velocidade += incremento + print(f"O carro acelerou para {self.velocidade} km/h.") + + def frear(self, decremento): + # O método frear reduz o valor do atributo 'velocidade' do carro. + # O parâmetro 'decremento' define o quanto a velocidade deve diminuir. + # Por exemplo, se o carro estiver a 30 km/h e o decremento for 10, + # a nova velocidade será 20 km/h. Se tentar reduzir mais do que a velocidade atual, + # a velocidade será ajustada para 0 km/h. + + self.velocidade = max(0, self.velocidade - decremento) # A função max(0, ...) garante que a velocidade nunca será negativa. + print(f"O carro desacelerou para {self.velocidade} km/h.") + + def buzinar(self): + # O método buzinar simula o som da buzina do carro. + # Quando chamado, ele simplesmente imprime "Bii Bii!". + + print("Bii Bii!") +``` + +#### **Métodos especiais** + +##### **Definição intuitiva** + +Agora que sabemos o que são métodos (as ações que um objeto pode realizar), podemos falar sobre os métodos especiais. + +**Os métodos especiais são ações especiais que a linguagem de programação já reconhece automaticamente**. Em Python, esses métodos têm uma nomenclatura distinta: começam e terminam com dois underlines (por exemplo, __init__). Os mais comuns são: + +- __init__ – Método de inicialização (construtor): + + O __init__ é chamado automaticamente quando criamos uma instância da classe. Ele é conhecido como construtor porque sua função essencial é construir o objeto, ou seja, definir seus atributos iniciais e garantir que o objeto (conceito que exploraremos em breve) esteja pronto para uso. + + Em termos simples, construtores configuram o estado inicial do objeto, estabelecendo os valores dos atributos que o definem. Por exemplo, ao criar um carro, o construtor define sua marca, modelo, ano e cor. Sem o construtor, o objeto seria criado, mas não teria características definidas. + +- __str__ – Método de representação textual: + + Define como o objeto será exibido quando usarmos a função print(). Esse método retorna uma descrição legível do objeto, facilitando a visualização de suas informações. + +##### **Exemplo prático** + + +```python +# 3. Adicionando Comportamentos Especiais (criando métodos especiais) + +# Agora vamos adicionar um comportamento especial com o método __str__, que define como o objeto será representado como texto. +# Também vamos destacar novamente o papel do método especial __init__, responsável por inicializar o objeto. + +class Carro: + def __init__(self, marca, modelo, ano, cor): + # O método __init__ é um método especial, também conhecido como construtor, e é chamado automaticamente + # sempre que criamos uma nova instância da classe Carro (iremos explorar o conceito de instância em breve). + # Construtores, como o __init__, têm a função de inicializar o objeto, configurando seus atributos iniciais. + # Neste caso, o carro é inicializado com as informações fornecidas: marca, modelo, ano, cor e velocidade. + + self.marca = marca + self.modelo = modelo + self.ano = ano + self.cor = cor + self.velocidade = 0 + + def acelerar(self, incremento): + self.velocidade += incremento + print(f"O carro acelerou para {self.velocidade} km/h.") + + def frear(self, decremento): + self.velocidade = max(0, self.velocidade - decremento) + print(f"O carro desacelerou para {self.velocidade} km/h.") + + def buzinar(self): + print("Bii Bii!") + + def __str__(self): + # O método __str__ é chamado automaticamente quando usamos a função print() em um objeto Carro. + # Ele retorna uma string que descreve o carro de forma legível, incluindo marca, modelo, ano, cor e velocidade atual. + + return f"{self.marca} {self.modelo} ({self.ano}), cor {self.cor}, velocidade atual: {self.velocidade} km/h" + +``` + +### **Objetos** + +#### **Definição intuitiva** + +Agora que sabemos o que é uma classe (projeto), o que são atributos (características) e o que são métodos (ações), podemos entender o que é um objeto. + +**Um objeto é um elemento concreto criado a partir da classe (projeto)**. Ele tem atributos definidos e é capaz de realizar ações (métodos). + +Exemplo intuitivo: +**Se a classe é o projeto da casa, o objeto é a casa real construída a partir desse projeto.** + +Por exemplo: + +- Casa 1: 3 quartos, paredes azuis, janelas grandes. +- Casa 2: 4 quartos, paredes brancas, janelas pequenas. + +**Cada casa é um objeto que tem suas próprias características (atributos) e pode realizar ações (métodos), como abrir portas ou acender luzes.** + +#### **Instância de uma classe** + + +**Uma instância é um termo técnico usado para descrever um objeto criado a partir de uma classe.** + +Imagine que você tem o projeto de uma casa. Esse projeto descreve como a casa deve ser: quantos quartos, o tamanho da sala, a cor das paredes, etc. Quando você usa esse projeto para construir uma casa real, essa casa é algo concreto. Cada casa construída a partir desse mesmo projeto é única, mas todas seguem a mesma estrutura. + +Na programação, acontece o mesmo: a classe é o projeto que define as características e comportamentos de algo. Quando criamos algo com base nesse projeto, temos um objeto. Cada objeto criado é uma instância única desse projeto, com suas próprias características. + +Por exemplo, com a classe Carro, podemos criar carro1 e carro2, cada um representando uma instância individual (ou seja, um objeto único criado a partir da classe), com sua própria marca, modelo e cor. + + +#### **Objetos na programação** + +**Na programação, um objeto é uma instância da classe, ou seja, um exemplo específico da estrutura que definimos.** + +Exemplo prático: + +Quando criarmos um carro específico no nosso programa, utilizando a classe Carro que estamos desenvolvendo, faremos algo assim: + +```python +meu_carro = Carro("Toyota", "Corolla", 2020, "vermelho") +``` + +Esse meu_carro é um objeto da classe Carro, ou seja, uma instância da classe Carro. Ele tem: + +Atributos: + +- marca = Toyota +- modelo = Corolla +- ano = 2020 +- cor = vermelho + +Métodos que ele pode realizar: + +- meu_carro.acelerar() – aumenta a velocidade. +- meu_carro.frear() – diminui a velocidade. +- meu_carro.buzinar() – toca a buzina. + +Cada objeto criado a partir da classe Carro representa uma instância individual, com atributos próprios e capaz de realizar as ações definidas nos métodos da classe. + +#### **Exemplo prático** + + +```python +# Relembrando como definimos a nossa classe Carro + +class Carro: + def __init__(self, marca, modelo, ano, cor): + self.marca = marca + self.modelo = modelo + self.ano = ano + self.cor = cor + self.velocidade = 0 # Começamos com o carro parado + + def acelerar(self, incremento): + # O método acelerar aumenta o valor do atributo 'velocidade' do carro. + # O parâmetro 'incremento' define o quanto a velocidade deve aumentar. + # Por exemplo, se o carro estiver a 20 km/h e o incremento for 10, + # a nova velocidade será 30 km/h. + + self.velocidade += incremento + print(f"O carro acelerou para {self.velocidade} km/h.") + + def frear(self, decremento): + # O método frear reduz o valor do atributo 'velocidade' do carro. + # O parâmetro 'decremento' define o quanto a velocidade deve diminuir. + # Por exemplo, se o carro estiver a 30 km/h e o decremento for 10, + # a nova velocidade será 20 km/h. Se tentar reduzir mais do que a velocidade atual, + # a velocidade será ajustada para 0 km/h. + + self.velocidade = max(0, self.velocidade - decremento) # A função max(0, ...) garante que a velocidade nunca será negativa. + print(f"O carro desacelerou para {self.velocidade} km/h.") + + def buzinar(self): + # O método buzinar simula o som da buzina do carro. + # Quando chamado, ele simplesmente imprime "Bii Bii!". + + print("Bii Bii!") + + def __str__(self): + # O método __str__ é chamado automaticamente quando usamos a função print() em um objeto Carro. + # Ele retorna uma string que descreve o carro de forma legível, incluindo marca, modelo, ano, cor e velocidade atual. + + return f"{self.marca} {self.modelo} ({self.ano}), cor {self.cor}, velocidade atual: {self.velocidade} km/h" +``` + + +```python +# 4. Criando um Objeto + +# Agora que temos a classe Carro pronta, com seus atributos e métodos definidos, vamos criar um objeto do tipo Carro. +# Esse objeto é uma instância da classe Carro, representando um carro específico com características definidas. + +# Criando uma instância da classe Carro com marca Toyota, modelo Corolla, ano 2020 e cor Vermelho. +meu_carro = Carro("Toyota", "Corolla", 2020, "Vermelho") + +# Agora vamos utilizar os métodos da classe para ver o comportamento desse objeto em ação: +meu_carro.acelerar(30) # Acelera o carro para 30 km/h. +print(f"Velocidade atual do meu_carro: {meu_carro.velocidade} km/h") # Exibe a velocidade atual após acelerar. + +meu_carro.buzinar() # O carro emite o som da buzina: "Bii Bii!". + +meu_carro.frear(10) # Reduz a velocidade do carro para 20 km/h. +print(f"Velocidade atual do meu_carro após frear: {meu_carro.velocidade} km/h") # Exibe a velocidade após frear. + +print("\n------------------------------------\n") + +# Podemos criar várias outras instâncias da classe Carro, cada uma representando um carro diferente: +carro_azul = Carro("Ford", "Focus", 2018, "Azul") +carro_preto = Carro("Honda", "Civic", 2022, "Preto") + +# Cada instância tem seus próprios atributos e pode executar os mesmos métodos de forma independente: +carro_azul.acelerar(50) # O carro azul acelera para 50 km/h. +print(f"Velocidade atual do carro_azul: {carro_azul.velocidade} km/h") # Exibe a velocidade atual do carro azul. + +carro_preto.buzinar() # O carro preto buzina: "Bii Bii!". + +carro_azul.frear(20) # O carro azul reduz a velocidade para 30 km/h. +print(f"Velocidade atual do carro_azul após frear: {carro_azul.velocidade} km/h") # Exibe a velocidade após frear. + +# Para acessar os atributos dos objetos, basta referenciar o atributo desejado: +print(f"O {meu_carro.marca} {meu_carro.modelo} é da cor {meu_carro.cor} e foi fabricado em {meu_carro.ano}.") +print(f"O {carro_azul.marca} {carro_azul.modelo} é da cor {carro_azul.cor} e foi fabricado em {carro_azul.ano}.") +print(f"O {carro_preto.marca} {carro_preto.modelo} é da cor {carro_preto.cor} e foi fabricado em {carro_preto.ano}.") + +``` + + O carro acelerou para 30 km/h. + Velocidade atual do meu_carro: 30 km/h + Bii Bii! + O carro desacelerou para 20 km/h. + Velocidade atual do meu_carro após frear: 20 km/h + + ------------------------------------ + + O carro acelerou para 50 km/h. + Velocidade atual do carro_azul: 50 km/h + Bii Bii! + O carro desacelerou para 30 km/h. + Velocidade atual do carro_azul após frear: 30 km/h + O Toyota Corolla é da cor Vermelho e foi fabricado em 2020. + O Ford Focus é da cor Azul e foi fabricado em 2018. + O Honda Civic é da cor Preto e foi fabricado em 2022. + + +### **Exercício: Simulando uma Carteira de Investimentos** + +Imagine que você é responsável por desenvolver um sistema simples de controle de investimentos para um investidor iniciante. Esse sistema precisa acompanhar diferentes ativos, como ações e títulos de renda fixa, calculando o valor atual de cada ativo e o retorno obtido. + +#### **Parte 1: Criando a Classe AtivoFinanceiro** + +1. Crie uma classe chamada AtivoFinanceiro com os seguintes **atributos**: + + - **nome**: Nome do ativo (ex: "Ação Petrobras"). + - **valor_investido**: Valor inicial investido no ativo. + - **valor_atual**: Valor atual do ativo. + + +```python +# Seu código aqui +``` + +2. Atualize o código acima criando métodos para: + + - Atualizar o valor atual do ativo. + - Calcular o retorno percentual do ativo: + + $Retorno \ (\%) = \dfrac{Valor \ atual - Valor \ Investido}{Valor \ Investido}*100$ + + + +```python +# Seu código aqui +``` + +#### **Parte 2: Criando Objetos e Testando a Classe** + +1. Utilizando a classe AtivoFinanceiro criada anteriormente, crie dois ativos financeiros distintos: + + - Um ativo de renda variável (ex: "Ação Itaú", valor investido R$ 1000, valor atual R$ 1200). + - Um ativo de renda fixa (ex: "Tesouro Direto", valor investido R$ 2000, valor atual R$ 2050). + +2. Atualize o valor de um dos ativos. + +3. Calcule e imprima o retorno percentual de cada ativo. + + +```python +# Seu código aqui +``` + +#### **Exercício Extra 1** + +Personalize a classe AtivoFinanceiro para exibir informações detalhadas sobre o ativo quando o objeto for impresso. + +Ao utilizar o comando print() em um objeto da classe AtivoFinanceiro, ele deve mostrar: + +- Nome do Ativo +- Valor Investido +- Valor Atual +- Retorno Percentual (lucro ou prejuízo em relação ao valor investido) + +**Dica:** +**Utilize o método especial __str__, que permite definir como o objeto será representado em formato de texto. Esse método é automaticamente chamado quando usamos print() no objeto.** + + +```python +# Seu código aqui +``` + +#### **Parte 3: Criando uma Simples Carteira de Investimentos** + +Agora que você já criou ativos individuais, vamos criar uma Carteira de Investimentos. + +1. Crie uma classe chamada CarteiraInvestimentos. + + - Essa classe deve ter um atributo ativos, que será uma lista de objetos do tipo AtivoFinanceiro. + +2. Crie métodos para: + + - Adicionar um novo ativo à carteira. + - Calcular o valor total investido. + - Calcular o valor atual total. + - Calcular o retorno total da carteira. + + +```python +# Seu código aqui +``` + +#### **Exercício Extra 2** + +Personalize a classe CarteiraInvestimentos para exibir informações detalhadas sobre a carteira quando o objeto for impresso. + +Ao utilizar o comando print() em um objeto da classe CarteiraInvestimentos, ele deve mostrar: + +- Lista de todos os ativos financeiros na carteira, com seus respectivos detalhes (nome do ativo, valor investido, valor atual e retorno percentual). +- Valor Total Investido na carteira. +- Valor Atual Total da carteira. +- Retorno Percentual Total (lucro ou prejuízo considerando todos os ativos da carteira). + +**Dica:** +**Utilize o método especial __str__, que permite definir como o objeto será representado em formato de texto. Esse método é automaticamente chamado quando usamos print() no objeto.** + + +```python +# Seu código aqui +``` + +#### **Parte 4: Testando a Carteira** + +1. Crie uma carteira de investimentos utilizando a classe CarteiraInvestimentos desenvolvida no exercício anterior. + + - Pense na carteira como um portfólio real, onde você gerencia diversos ativos financeiros. + + +2. Adicione à sua carteira os dois ativos financeiros que você criou anteriormente. + - Inclua tanto a ação da empresa quanto o ativo de renda fixa para observar como diferentes tipos de investimentos impactam o desempenho da carteira. + + +3. Calcule e imprima os seguintes resultados: + - Valor Total Investido: A soma do valor inicial aplicado em todos os ativos. + - Valor Atual Total: O valor atual combinado de todos os ativos após as atualizações de mercado. + - Retorno Total da Carteira: O percentual de lucro ou prejuízo da carteira como um todo. + +**Dica: Use o método __str__ que você implementou para exibir os detalhes da carteira de forma clara e organizada. Isso simula um relatório financeiro, como aqueles que investidores e gestores recebem periodicamente!** + + +```python +# Seu código aqui +``` + +# **Módulo 2 - Avançando em Programação Orientada a Objetos** + +## **2.1 - Os 4 Conceitos Fundamentais da Programação Orientada a Objetos** + +### **Abstração** + +#### **Definição intuitiva** + +**Abstração é o processo de esconder detalhes desnecessários e mostrar apenas o que é essencial**. + +Imagine um carro. Quando você dirige, não precisa saber exatamente como o motor funciona internamente, quais circuitos elétricos estão ativados ou como o combustível é processado. Você apenas interage com o carro por meio do volante, pedais e painel de controle. Ou seja, você se preocupa apenas com o que é necessário para dirigir. **Isso é abstração: mostrar apenas as informações relevantes e ocultar os detalhes internos**. + +#### **Abstração na programação** + +Na programação, a abstração funciona da mesma forma. Criamos classes que representam conceitos do mundo real, mas **escondemos os detalhes internos de implementação e mostramos apenas o necessário para quem usa a classe**. + +**Por exemplo, podemos ter uma classe OrdemDeCompra que permite um investidor comprar ações, sem precisar expor os detalhes de como a ordem é processada na bolsa de valores**. + + +```python +class OrdemDeCompra: + def __init__(self, ativo, quantidade, preco): + self.ativo = ativo + self.quantidade = quantidade + self.preco = preco + + def executar(self): + print(f"Enviando ordem de compra: {self.quantidade} ações de {self.ativo} a R$ {self.preco:.2f}") + +# Criando uma ordem de compra +ordem = OrdemDeCompra("PETR4", 100, 32.50) +ordem.executar() # Saída esperada: "Enviando ordem de compra: 100 ações de PETR4 a R$ 32.50" +``` + + Enviando ordem de compra: 100 ações de PETR4 a R$ 32.50 + + +**Aqui, a abstração permite que o usuário envie ordens de compra sem precisar saber como a ordem é roteada para o mercado, como os custos são calculados ou como os dados são armazenados. A classe esconde esses detalhes e expõe apenas o necessário para operar.** + +*Vale destacar que, no exemplo acima, o processo de emissão da ordem de compra não foi efetivamente implementado. O objetivo é apenas ilustrar o conceito de abstração. Mesmo que a lógica real de envio de ordens fosse adicionada, a maneira como o usuário interage com a classe permaneceria a mesma — bastaria chamar o método executar(), sem precisar se preocupar com os detalhes internos. Essa é a essência da abstração na POO.* + +### **Encapsulamento** + +#### **Definição intuitiva** + +**Encapsulamento significa proteger os dados de acesso indevido e garantir que eles só possam ser modificados de maneira controlada**. + +Pense em um cofre de banco. Você não pode simplesmente abrir e pegar o dinheiro sem passar pelos procedimentos adequados. Você precisa de uma chave ou senha para acessar o conteúdo. + +**No mundo da programação, o encapsulamento funciona de maneira semelhante: os atributos e métodos de um objeto podem ser protegidos para que só possam ser acessados ou modificados de maneira controlada**. + +#### **Encapsulamento na programação** + +Em programação, usamos modificadores de acesso para restringir o acesso direto a certos atributos e métodos de uma classe. **Em Python, seguimos uma convenção para diferenciar atributos e métodos públicos, protegidos e privados.** + +Métodos e atributos públicos, protegidos e privados em Python: +- Públicos: Podem ser acessados diretamente de fora da classe. + +- Protegidos (_atributo | _método): Devem ser tratados como internos à classe, mas ainda podem ser acessados fora dela (por convenção, não por restrição técnica). + +- Privados (__atributo | __método): Não podem ser acessados diretamente de fora da classe, pois o Python aplica uma modificação de nome (name mangling). + + + + +```python +class ContaBancaria: + def __init__(self, titular, saldo): + self.titular = titular # Atributo público + self._saldo = saldo # Atributo protegido (_atributo) + self.__senha = "1234" # Atributo privado (__atributo) + + def depositar(self, valor): # Método público + self._saldo += valor + + def sacar(self, valor): # Método público + if valor <= self._saldo: + self._saldo -= valor + else: + print("Saldo insuficiente") + + def get_saldo(self): # Método público + return self._saldo + + def __verificar_senha(self, senha): # Método privado (__método) + return senha == self.__senha + +# Criando um objeto da classe ContaBancaria +minha_conta = ContaBancaria("Gabriel", 1000) + +# Acessando métodos públicos +print(minha_conta.get_saldo()) # Saída esperada: 1000 +minha_conta.sacar(200) +print(minha_conta.get_saldo()) # Saída esperada: 800 + +# Tentando acessar um atributo protegido (possível, mas não recomendado) +print(minha_conta._saldo) # Saída esperada: 800 + +# Tentando acessar um atributo privado (não pode ser acessado diretamente) +# print(minha_conta.__senha) # Isso geraria um erro! +# Porém, devido ao name mangling, podemos acessar manualmente (NÃO RECOMENDADO) +print(minha_conta._ContaBancaria__senha) # Saída esperada: 1234 +``` + + 1000 + 800 + 800 + 1234 + + +#### **Observação** + +**Em Python, não existe efetivamente o conceito de atributos privados e protegidos como em linguagens como Java e C++.** + +Isso acontece porque Python segue a filosofia de "somos todos adultos aqui" (We are all consenting adults here). Ou seja, em vez de impor restrições rígidas, Python confia que os desenvolvedores seguirão as convenções estabelecidas. + +Por exemplo: + +- Atributos protegidos (_atributo) **não são realmente protegidos** — é apenas uma convenção para indicar que devem ser tratados como internos à classe. + +- Atributos privados (__atributo) também **não são verdadeiramente inacessíveis**, pois Python apenas renomeia (name mangling) o atributo, tornando-o acessível por _Classe__atributo. + +**Imagine um cofre com um aviso escrito: "Favor não abrir sem permissão." O cofre está trancado? Não. Mas o aviso deixa claro que você não deveria mexer nele. O encapsulamento em Python funciona da mesma forma: o acesso aos dados não é bloqueado, mas há indicações claras de que certos atributos não devem ser modificados diretamente.** + +### **Herança** + +#### **Definição intuitiva** + +**Herança permite criar novas classes a partir de classes existentes**, aproveitando e reutilizando código. + +Imagine que uma empresa tem diferentes tipos de funcionários: estagiários, analistas e gerentes. Todos compartilham algumas características básicas, como nome e salário, mas têm responsabilidades diferentes. **Em vez de criar classes separadas do zero, podemos definir uma classe geral Funcionario e fazer com que Estagiario, Analista e Gerente herdem suas características básicas, adicionando apenas os detalhes específicos de cada um**. + +#### **Herança na programação** + +**Na programação, a herança permite que uma classe filha (subclasse) herde atributos e métodos de uma classe pai (superclasse). Isso evita duplicação de código e facilita a manutenção.** + + +```python +# Definição da classe pai (superclasse) +class Funcionario: + def __init__(self, nome, salario): + self.nome = nome + self.salario = salario + + def exibir_dados(self): + print(f"Nome: {self.nome}, Salário: R$ {self.salario}") + +# Definição da classe filho (subclasse) +class Analista(Funcionario): # A sintaxe "class Analista(Funcionario)" indica que a subclasse Analista + # herda os atributos e métodos da superclasse Funcionario + + def __init__(self, nome, salario, area): + """ + O método __init__ da subclasse chama o __init__ da superclasse usando super(). + Isso evita a necessidade de reescrever a inicialização dos atributos nome e salario. + """ + + super().__init__(nome, salario) # Chama o construtor da superclasse Funcionario + self.area = area # Adiciona um novo atributo exclusivo da subclasse + + def exibir_dados(self): + """ + O método exibir_dados da subclasse reutiliza o método de mesmo nome da superclasse. + Primeiro, chama super().exibir_dados(), que exibe nome e salário. Depois, adiciona + a exibição da área específica do Analista. + """ + + super().exibir_dados() # Chama o método exibir_dados() da superclasse + print(f"Área: {self.area}") # Adiciona a nova informação exclusiva da subclasse + +# Criando um objeto da classe Analista +ana = Analista("Ana", 5000, "TI") + +# Chamando o método exibir_dados() +ana.exibir_dados() + +# Saída esperada: +# Nome: Ana, Salário: R$ 5000 +# Área: TI + +# No exemplo acima, a classe Analista herda os atributos e métodos da classe Funcionario, +# o que permite reutilizar a funcionalidade já definida. No entanto, ela tem a flexibilidade +# de adicionar novos atributos, como o "area", e personalizar comportamentos, como fizemos +# ao sobrescrever o método "exibir_dados()", para adaptar o comportamento da classe filha às necessidades específicas. +``` + + Nome: Ana, Salário: R$ 5000 + Área: TI + + +#### **Em suma, o que significa herdar algo em POO?** + +**Quando uma classe herda de outra, ela automaticamente recebe os atributos e métodos da classe pai. Isso significa que podemos:** + +- **Criar objetos da subclasse e usar métodos da superclasse sem reimplementá-los**. + +- **Adicionar novos atributos e métodos específicos da subclasse**. + +- **Sobrescrever métodos para personalizar o comportamento da subclasse**. + +Por exemplo, Analista herdou exibir_dados() de Funcionario, mas sobrescreveu esse método para exibir também a área de atuação. *(Esse comportamento de sobrescrever métodos é um exemplo clássico de polimorfismo, que veremos com mais detalhes no próximo tópico)* + +### **Polimorfismo** + +#### **Definição intuitiva** + +**Polimorfismo significa 'muitas formas'. Esse conceito permite que classes relacionadas por herança tenham métodos com o mesmo nome, mas comportamentos diferentes.** + +Imagine que uma empresa tem vários tipos de funcionários: estagiários, analistas e gerentes. Todos recebem salário, mas de formas diferentes: + +- Estagiários recebem bolsa-auxílio. + +- Analistas recebem salário fixo. + +- Gerentes podem ter bônus adicionais. + +Se quisermos calcular o pagamento de qualquer funcionário sem nos preocupar com o tipo específico dele, podemos usar o conceito de polimorfismo. + +#### **Polimorfismo na programação** + +Na programação, polimorfismo permite que métodos com o mesmo nome tenham implementações diferentes em classes relacionadas por herança. Isso torna o código mais flexível e reutilizável. + +Vamos ver na prática: + + +```python +# Definição da classe base (superclasse) +class Funcionario: + def __init__(self, nome, salario): + """ + Inicializa os atributos nome e salario. + """ + + self.nome = nome + self.salario = salario + + def calcular_pagamento(self): + """ + Retorna o salário do funcionário. Este método pode ser sobrescrito em subclasses. + """ + + return self.salario + +# Subclasse Estagiario herdando de Funcionario +class Estagiario(Funcionario): + def __init__(self, nome, bolsa_auxilio): + """ + Chama o construtor da superclasse passando a bolsa_auxilio como salário. + """ + + super().__init__(nome, bolsa_auxilio) + + def calcular_pagamento(self): + """ + Sobrescreve o método calcular_pagamento da superclasse. + Para estagiários, o pagamento é apenas a bolsa_auxilio (salario no contexto da superclasse). + """ + + return self.salario + +# Subclasse Gerente herdando de Funcionario +class Gerente(Funcionario): + def __init__(self, nome, salario, bonus): + """ + Chama o construtor da superclasse e adiciona o atributo bonus. + """ + + super().__init__(nome, salario) + self.bonus = bonus + + def calcular_pagamento(self): + """ + Sobrescreve o método calcular_pagamento da superclasse. + Para gerentes, o pagamento inclui o salário e um bônus adicional. + """ + + return self.salario + self.bonus + +# Criando objetos de diferentes classes +funcionario1 = Funcionario("Carlos", 5000) +estagiario1 = Estagiario("Ana", 2000) +gerente1 = Gerente("Mariana", 8000, 2000) + +# Chamando o mesmo método para diferentes tipos de funcionários +print(funcionario1.calcular_pagamento()) # 5000 +print(estagiario1.calcular_pagamento()) # 2000 +print(gerente1.calcular_pagamento()) # 10000 (salário + bônus) + +# O que aconteceu aqui? +# Mesmo que Funcionario, Estagiario e Gerente tenham o método calcular_pagamento(), +# cada um se comporta de forma diferente. Isso é polimorfismo! +``` + + 5000 + 2000 + 10000 + + +#### **Em Resumo** + +- Polimorfismo permite que diferentes classes tenham métodos com o mesmo nome, mas comportamentos diferentes. + +- Ele torna o código mais genérico e reutilizável, pois podemos tratar objetos de diferentes classes de forma uniforme. + +# **Módulo 3 - Projeto Final, Curiosidades e Conclusões** + +## **Projeto Final** + +### **Contexto** + +#### **📊 O que é uma Estratégia Quantitativa?** + +Uma estratégia quantitativa é uma abordagem de investimento baseada em modelos matemáticos, estatísticos e computacionais para tomar decisões no mercado financeiro. Essas estratégias utilizam dados históricos e algoritmos para identificar padrões e executar operações de compra e venda de ativos de forma sistemática, reduzindo a influência de emoções humanas. + +#### **🔍 O que é um Backtest e Por que Ele é Importante?** + + +Um backtest é o processo de testar uma estratégia de investimento utilizando dados históricos para avaliar seu desempenho antes de aplicá-la no mercado real. Esse processo é essencial para entender como uma estratégia teria se comportado em diferentes cenários do passado. + +#### **✅ Por que fazer um backtest?** + +Realizar um backtest oferece diversas vantagens que ajudam a tomar decisões mais informadas e seguras: + +✔ Permite testar uma estratégia sem arriscar dinheiro real. + +✔ Identifica pontos fortes e fracos do modelo antes da implementação. + +✔ Ajuda a ajustar parâmetros e melhorar a robustez da estratégia. + +✔ Evita decisões baseadas apenas em intuição, utilizando dados e evidências. + +Com esses benefícios em mente, é fundamental contar com ferramentas que tornem o processo de backtesting mais eficiente e preciso. É aqui que entra o **Backtrader**. + +#### **🚀 Como o Backtrader Facilita o Backtest?** + +O Backtrader é uma biblioteca em Python projetada para automatizar o processo de backtesting, permitindo testar múltiplas estratégias com diferentes parâmetros de forma rápida e eficiente. Ele simplifica o trabalho de traders e analistas, tornando o desenvolvimento de estratégias quantitativas mais acessível. + +**Vantagens do Backtrader**: + +✔ Simulação realista → Gerencia a execução de ordens e a gestão de capital automaticamente. + +✔ Suporte a múltiplos ativos → Teste estratégias em diversas ações, moedas e commodities. + +✔ Indicadores embutidos → Inclui dezenas de indicadores como RSI, MACD e Médias Móveis. + +✔ Visualização gráfica → Permite gerar gráficos detalhados com o desempenho da estratégia. + +**Como essa biblioteca se relaciona com POO?** + +- Abstração → O usuário apenas interage com a API sem precisar entender os detalhes internos do funcionamento do backtesting. + +- Encapsulamento → Os dados do mercado e as regras de estratégia são protegidos e acessados apenas por métodos específicos. + +- Herança → Criamos novas estratégias herdando da classe base bt.Strategy, evitando repetição de código. + +- Polimorfismo → Diferentes estratégias podem ser implementadas, e todas interagem com o sistema de backtesting da mesma forma. + +Em suma, o Backtrader transforma o processo de criação e teste de estratégias quantitativas em algo mais fácil, rápido e confiável. Com ele, traders e analistas podem validar suas ideias com dados históricos, ajustá-las conforme necessário e aplicá-las no mercado real com muito mais confiança. + +#### **🔧 Sobre o Backtrader** + +##### **1. bt.Strategy: A Classe Base das Estratégias** + +**O que é?** + +bt.Strategy é a classe base no Backtrader que define como criar estratégias de trading. Para implementar uma estratégia, você deve criar uma nova classe que herda dessa classe base. + +**Por que é importante?** + +Isso permite que você defina suas próprias regras de compra e venda, utilizando os métodos e atributos da classe base sem precisar reescrever toda a lógica do backtest. + +##### **2. __init__: Inicializando a Estratégia** + +**O que é?** + +O método __init__ é usado para inicializar os atributos da sua estratégia, como indicadores técnicos (Médias Móveis, RSI, etc.). É aqui que você define o que será usado para tomar decisões. + +**Exemplo:** + +```python + def __init__(self): + # Inicializa dois indicadores de Média Móvel Simples (SMA). + + # Cria uma média móvel simples de curto prazo (10 períodos) usando o preço de fechamento dos dados fornecidos. + self.ma_short = bt.indicators.SimpleMovingAverage(self.data.close, period=10) + + # Cria uma média móvel simples de longo prazo (30 períodos) usando o preço de fechamento dos dados fornecidos. + self.ma_long = bt.indicators.SimpleMovingAverage(self.data.close, period=30) +``` + +##### **3. next: Definindo a Lógica da Estratégia** + +**O que é?** + +O método next é chamado a cada novo dado (cada candle diário, por exemplo). Aqui você define quando comprar ou vender com base nos seus indicadores. + +**Exemplo:** +```python + def next(self): + if self.ma_short[0] > self.ma_long[0]: # Se a média curta cruzar acima da longa + self.buy() # Comprar + elif self.ma_short[0] < self.ma_long[0]: # Se a média curta cruzar abaixo da longa + self.sell() # Vender +``` + +##### **4. Cerebro: O Cérebro do Backtrader** + +**O que é?** +Cerebro é o motor do Backtrader que executa o backtest. Ele gerencia a estratégia, os dados, a alocação de capital e gera os resultados. + +**Funções principais:** + +- Adicionar a estratégia: cerebro.addstrategy(SuaEstrategia) + +- Adicionar dados: cerebro.adddata(dados) + +- Executar o backtest: cerebro.run() + +- Plotar resultados: cerebro.plot() + +##### **5. Dados: Alimentando o Backtest** + +**O que é?** + +O Backtrader aceita diferentes fontes de dados, como arquivos CSV ou dados do Yahoo Finance. + +**Exemplo (Yahoo Finance):** + +```python + from backtrader.feeds import YahooFinanceData + dados = YahooFinanceData(dataname='AAPL', fromdate=datetime(2020, 1, 1), todate=datetime(2023, 1, 1)) +``` + +##### **6. Indicadores Técnicos no Backtrader** + +**O que é?** +O Backtrader já vem com diversos indicadores técnicos, como Médias Móveis, RSI, MACD, entre outros. + +**Exemplo (Média Móvel):** +```python + self.ma = bt.indicators.SimpleMovingAverage(self.data.close, period=20) +``` + +##### **7. Visualização Gráfica** + +**O que é?** + +O Backtrader gera gráficos automáticos mostrando o desempenho da sua estratégia. + +**Exemplo:** + +```python + cerebro.plot() +``` + +### **📌 Projeto Final: Estratégia de Trading Orientada a Objetos com Backtrader** + +Neste projeto final, desenvolveremos um sistema de backtesting de estratégias de trading utilizando a biblioteca Backtrader. O objetivo é integrar de forma prática e aprofundada os 4 pilares da Programação Orientada a Objetos (POO) — Abstração, Encapsulamento, Herança e Polimorfismo — juntamente com os conceitos fundamentais de Classes, Atributos, Métodos e Objetos. + +#### **🛠️ O que vocês terão que fazer** + +##### **1. Configuração do Ambiente** + +**Instalar o Backtrader** + +Execute o seguinte comando no terminal para instalar a biblioteca: + + +```python +!pip install backtrader +``` + + Defaulting to user installation because normal site-packages is not writeable + Requirement already satisfied: backtrader in /home/user/.local/lib/python3.10/site-packages (1.9.78.123) + + +**Obter Dados Históricos** + +Baixe um dataset de ações de empresas como Apple (AAPL) ou Google (GOOGL) utilizando o Yahoo Finance. Porém, antes disso você precisara instalar essa biblioteca. Para tal, utilize o seguinte comando: + + +```python +!pip install yfinance +``` + + Defaulting to user installation because normal site-packages is not writeable + Requirement already satisfied: yfinance in /home/user/.local/lib/python3.10/site-packages (0.2.52) + Requirement already satisfied: frozendict>=2.3.4 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (2.4.4) + Requirement already satisfied: requests>=2.31 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (2.31.0) + Requirement already satisfied: multitasking>=0.0.7 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (0.0.11) + Requirement already satisfied: html5lib>=1.1 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (1.1) + Requirement already satisfied: beautifulsoup4>=4.11.1 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (4.12.3) + Requirement already satisfied: pandas>=1.3.0 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (2.2.3) + Requirement already satisfied: pytz>=2022.5 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (2024.1) + Requirement already satisfied: lxml>=4.9.1 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (5.2.2) + Requirement already satisfied: numpy>=1.16.5 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (1.24.3) + Requirement already satisfied: peewee>=3.16.2 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (3.17.6) + Requirement already satisfied: platformdirs>=2.0.0 in /home/user/.local/lib/python3.10/site-packages (from yfinance) (4.2.0) + Requirement already satisfied: soupsieve>1.2 in /home/user/.local/lib/python3.10/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.5) + Requirement already satisfied: webencodings in /home/user/.local/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (0.5.1) + Requirement already satisfied: six>=1.9 in /usr/lib/python3/dist-packages (from html5lib>=1.1->yfinance) (1.16.0) + Requirement already satisfied: python-dateutil>=2.8.2 in /home/user/.local/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2.9.0.post0) + Requirement already satisfied: tzdata>=2022.7 in /home/user/.local/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2024.1) + Requirement already satisfied: certifi>=2017.4.17 in /home/user/.local/lib/python3.10/site-packages (from requests>=2.31->yfinance) (2024.12.14) + Requirement already satisfied: idna<4,>=2.5 in /usr/lib/python3/dist-packages (from requests>=2.31->yfinance) (3.3) + Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/lib/python3/dist-packages (from requests>=2.31->yfinance) (1.26.5) + Requirement already satisfied: charset-normalizer<4,>=2 in /home/user/.local/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.3.2) + + +**Importação das Bibliotecas Essenciais** + +Na célula abaixo, importem as bibliotecas que considerarem necessárias para o desenvolvimento do projeto. Lembrem que talvez vocês precisem instalar alguma delas. Para começar, já incluímos algumas bibliotecas fundamentais que facilitarão o processo: + + +```python +# Importa o Backtrader, uma biblioteca de backtesting para estratégias de trading +import backtrader as bt + +# Importa o yFinance, utilizado para baixar dados financeiros históricos diretamente do Yahoo Finance +import yfinance as yf + +# Importa a classe datetime para manipulação de datas +from datetime import datetime + +# Importa o pandas, biblioteca essencial para análise e manipulação de dados em formato de DataFrames +import pandas as pd + +# Importa o matplotlib.pyplot, biblioteca para criação de gráficos e visualizações de dados +import matplotlib.pyplot as plt + +``` + +##### **2. Desenvolvimento da Estratégia de Trading com POO** + +**Criar uma Classe para a Estratégia** +Definam uma classe que herde de bt.Strategy, que será o coração do seu projeto. Essa classe deve conter: + +- Atributos: Definam indicadores técnicos, como médias móveis de curto e longo prazo. + +- Métodos: + - __ init __: Inicializa os indicadores e variáveis da estratégia. + - next: Define a lógica de compra e venda baseada nos sinais dos indicadores. + + +**Exemplo:** + + +```python +# Definimos uma classe de estratégia que herda da classe base bt.Strategy do Backtrader. +# Isso é um exemplo de **Herança** em Programação Orientada a Objetos (POO), permitindo que nossa estratégia utilize +# todas as funcionalidades embutidas da classe Strategy, como controle de posições e execução de ordens. +class MovingAverageCrossStrategy(bt.Strategy): + + # O método __init__ é chamado uma vez no início da execução da estratégia. + # Usamos esse método para inicializar os **atributos** da nossa estratégia, como indicadores técnicos. + def __init__(self): + # Criamos um indicador de Média Móvel Simples (SMA) de 10 períodos sobre o preço de fechamento dos dados. + # 'self.ma_short' é um atributo que guarda a média móvel curta, que reage mais rapidamente às mudanças de preço. + self.ma_short = bt.indicators.SimpleMovingAverage(self.data.close, period=10) + + # Criamos outro indicador de Média Móvel Simples (SMA), agora de 30 períodos. + # 'self.ma_long' é um atributo que guarda a média móvel longa, que é mais lenta para reagir às variações do preço. + self.ma_long = bt.indicators.SimpleMovingAverage(self.data.close, period=30) + + # O método next é chamado automaticamente pelo Backtrader a cada novo "candle" ou barra de dados (ex.: diário, horário). + # É aqui que definimos a **lógica de compra e venda** da nossa estratégia. + def next(self): + # Condição para compra: + # Se a média móvel curta (ma_short) cruzar acima da média móvel longa (ma_long), + # e ainda **não temos uma posição aberta** (not self.position), então executamos uma compra. + + # O que é self.position? + # 'self.position' é um atributo do Backtrader que indica se já temos uma posição aberta no ativo. + # Se não houver posição aberta, 'self.position' é avaliado como False (ou 0). + # Se estivermos comprados, 'self.position' é True (ou o número de unidades compradas). + + if self.ma_short[0] > self.ma_long[0] and not self.position: + self.buy() # Método do Backtrader que executa uma ordem de compra. + + # Quanto é comprado? + # Por padrão, o método 'self.buy()' compra 1 unidade do ativo. + # Para comprar uma quantidade específica, podemos passar o argumento 'size', por exemplo: self.buy(size=100). + + # Condição para venda: + # Se a média móvel curta cruzar abaixo da média móvel longa, + # e **já temos uma posição aberta** (self.position), então vendemos. + elif self.ma_short[0] < self.ma_long[0] and self.position: + self.sell() # Método do Backtrader que executa uma ordem de venda. + + # Quanto é vendido? + # Por padrão, 'self.sell()' vende todas as unidades do ativo que foram compradas anteriormente, encerrando a posição. + # Para vender uma quantidade específica (por exemplo, vender parcialmente), podemos usar: self.sell(size=50). + +``` + +##### **3. Executar o Backtest:** + +Com a estratégia pronta, configurem e executem o backtest usando o Cerebro, o motor de execução do Backtrader. + +**Configurar o Cerebro** + +Inicializem o motor Cerebro, adicionem os dados históricos e a estratégia desenvolvida: + + +```python +# Parâmetros relacionados a captura de dados do ticker. +ticker = 'AAPL' +start_date = '2020-01-01' +end_date = '2021-01-01' + +# Baixando os dados do ticker com auto ajuste +data = yf.download(ticker, start=start_date, end=end_date, auto_adjust=True) + +# Verificando e ajustando as colunas para o formato correto +if isinstance(data.columns, pd.MultiIndex): + data.columns = data.columns.get_level_values(0) # Remove o MultiIndex + +# Carregando os dados no Backtrader +data_feed = bt.feeds.PandasData(dataname=data) + +# Inicializando o Cerebro +cerebro = bt.Cerebro() +cerebro.adddata(data_feed) +cerebro.addstrategy(MovingAverageCrossStrategy) +``` + + [*********************100%***********************] 1 of 1 completed + + + + + + 0 + + + +**Executar o Backtest** + +Rodem o backtest e visualizem o desempenho: + + +```python +# Setando o valor inicial a ser investido na estratégia +initial_cash = 15000 + +# Definindo o saldo inicial +cerebro.broker.set_cash(initial_cash) + +# Executando o backtest +cerebro.run() + +# Capturando o saldo final +final_cash = cerebro.broker.getvalue() + +# Exibindo os resultados +print(f'Saldo inicial: ${initial_cash:.2f}') +print(f'Saldo final: ${final_cash:.2f}') +print(f'Lucro/Prejuízo: ${final_cash - initial_cash:.2f}') + +# Plota o gráfico e salva como PNG +fig = cerebro.plot()[0][0] +filename = f'backtest_{ticker}_{start_date}_to_{end_date}.png' +fig.savefig(f'backtest_{ticker}_{start_date}_to_{end_date}.png') +print(f'O gráfico foi salvo como {filename}. \nEsse é um gráfico bastante poluído, mas, se tiver interesse, existem formas de mudá-lo, basta uma rápida pesquisa :)') + +``` + + Saldo inicial: $15000.00 + Saldo final: $15025.05 + Lucro/Prejuízo: $25.05 + + + + + + + +
+ + + O gráfico foi salvo como backtest_AAPL_2020-01-01_to_2021-01-01.png. + Esse é um gráfico bastante poluído, mas, se tiver interesse, existem formas de mudá-lo, basta uma rápida pesquisa :) + + +## **POO na Prática: O Que Você Já Usava Sem Perceber** + +### **1. Pandas e NumPy São Baseados em POO** + +Quando vocês criam um DataFrame no Pandas ou uma matriz no NumPy, estão na verdade instanciando objetos de classes que já foram definidas pelos desenvolvedores dessas bibliotecas! + +🔹 Exemplo: Quando usamos: + +```python + import pandas as pd + + df = pd.DataFrame({"Nome": ["Ana", "Carlos"], "Idade": [25, 30]}) + print(df.shape) # Atributo + print(df.describe()) # Método +``` + +Aqui, df é um objeto da classe DataFrame, e .shape e .describe() são atributos e métodos, exatamente como vocês aprenderam! + +🔹 O mesmo acontece no NumPy: + +```python + import numpy as np + + matriz = np.array([[1, 2], [3, 4]]) + print(matriz.T) # Transposta (método) + print(matriz.shape) # Dimensão (atributo) +``` + +Agora fica claro que tudo isso são apenas classes bem projetadas! + +### **2. Strings, Listas e Dicionários Também São Objetos** + +Vocês já usaram .upper(), .append() e .items() centenas de vezes, certo? Agora vocês sabem que esses são métodos das classes str, list e dict. + +🔹 Exemplo: + +```python + texto = "hello" + print(texto.upper()) # Método da classe str + + lista = [1, 2, 3] + lista.append(4) # Método da classe list + + dicionario = {"chave": "valor"} + print(dicionario.items()) # Método da classe dict +``` + +Cada um desses tipos é uma classe que disponibiliza métodos específicos sem que precisemos nos preocupar com sua implementação interna! + +### **3. Modelos de Machine Learning São Objetos!** + +Se você já usou Scikit-Learn, viu que um modelo precisa ser instanciado, treinado e testado – tudo isso são conceitos de POO. + +🔹 Exemplo: + +```python + from sklearn.linear_model import LinearRegression + + modelo = LinearRegression() # Criando um objeto da classe LinearRegression + modelo.fit(X_train, y_train) # Método para treinar o modelo + y_pred = modelo.predict(X_test) # Método para prever valores +``` + +O modelo é um objeto, e .fit() e .predict() são métodos que manipulam esse objeto! + +### + +Esses são apenas alguns exemplos, mas o mundo da programação está repleto de aplicações da POO. Frameworks como Django para desenvolvimento web, bibliotecas de visualização como Matplotlib e até ferramentas de automação seguem esses mesmos princípios. Agora que você entende a base por trás dessas estruturas, conseguirá identificar a POO em quase todos os códigos que encontrar. + + + + + + + + + +## **Conclusão: POO é uma Nova Forma de Pensar Programação** + +Ao longo dessa jornada, exploramos os conceitos fundamentais da Programação Orientada a Objetos (POO) e como eles se aplicam tanto na teoria quanto na prática. Aprendemos que POO não é apenas um conjunto de regras ou palavras-chave, mas sim uma forma diferente de estruturar e organizar nossos programas. + +Através de abstração, encapsulamento, herança e polimorfismo, vimos como é possível criar sistemas modulares, reutilizáveis e mais fáceis de manter. Mais do que isso, percebemos que essas ideias não estão isoladas do nosso dia a dia: bibliotecas que usamos o tempo todo, como Pandas e NumPy, foram construídas seguindo esses princípios. + +Agora que vocês conhecem POO, conseguem enxergar que quase tudo o que usamos em Python segue esse modelo. O conhecimento que vocês adquiriram não serve apenas para criar classes do zero, mas também para entender como as bibliotecas funcionam internamente e até para desenvolver códigos mais eficientes e organizados. 🚀💡 + +POO também não se limita a uma única área. Seja no mercado financeiro, inteligência artificial, desenvolvimento web ou até mesmo em jogos, essa abordagem pode tornar o código mais intuitivo e escalável. Cada problema pode ser resolvido de diferentes formas, e POO nos dá uma maneira poderosa de estruturar nossas soluções. + +Agora que você compreende esses conceitos, vale a pena revisitar códigos antigos e tentar enxergá-los com essa nova perspectiva. Você vai perceber como muitas das ferramentas que já usa são baseadas nesses mesmos princípios! 🚀