├── src ├── __init__.py ├── conf │ ├── __init__.py │ ├── .env.sample │ └── settings.py ├── mongodb.py ├── analyse.py ├── core.py └── funcoes.py ├── requirements.txt ├── .gitmodules ├── .gitignore ├── LICENSE └── README.md /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/conf/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgamer58/Telegram_Bot_Bovespa/HEAD/requirements.txt -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "phoemur"] 2 | path = phoemur 3 | url = https://github.com/vitorgamer58/fundamentus.git 4 | -------------------------------------------------------------------------------- /src/conf/.env.sample: -------------------------------------------------------------------------------- 1 | TELEGRAM_TOKEN= 2 | BASE_API_URL=https://mfinance.com.br/api/v1/ 3 | FUNDAMENTUS=http://127.0.0.1:5000/ 4 | COINLIB= 5 | OKANE=https://www.okanebox.com.br/api/analisefundamentalista/ 6 | MONGODB= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/conf/__pycache__/ 2 | .vs/ProjectSettings.json 3 | .vs/slnx.sqlite 4 | .vs/Telegram_Bot_Bovespa/v16/.suo 5 | .vs/VSWorkspaceState.json 6 | .vscode/launch.json 7 | .idea 8 | src/__pycache__/ 9 | tests/__init__.py 10 | src/fechamento.py 11 | src/conf/.env 12 | __pycache__/ 13 | -------------------------------------------------------------------------------- /src/conf/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN") 8 | BASE_API_URL = os.getenv("BASE_API_URL") 9 | BISCOINT = os.getenv("BISCOINT_API_URL") 10 | PHOEMUR = os.getenv("FUNDAMENTUS") 11 | COINLIB = os.getenv("COINLIB") 12 | OKANE = os.getenv("OKANE") 13 | MONGODB = os.getenv("MONGODB") -------------------------------------------------------------------------------- /src/mongodb.py: -------------------------------------------------------------------------------- 1 | from conf.settings import MONGODB 2 | from operator import itemgetter 3 | import pymongo 4 | 5 | """ MongoDB 6 | Database name: clients_database 7 | collection name: clients_collection """ 8 | 9 | 10 | class databaseClient: 11 | mongodburl = MONGODB 12 | 13 | def __init__(self): 14 | self.db = pymongo.MongoClient( 15 | self.mongodburl).clients_database.clients_collection 16 | 17 | def addTelegramClient(self, chat): 18 | id, username, first_name = itemgetter( 19 | 'id', 'username', 'first_name')(chat) 20 | clientAlreadyExist = self.db.find_one({"chat_id": id}) 21 | if(clientAlreadyExist): 22 | return False 23 | self.db.insert_one({ 24 | "first_name": first_name, 25 | "username": username, 26 | "chat_id": id 27 | }) 28 | return True 29 | 30 | def removeTelegramClient(self, chat_id): 31 | self.db.delete_one({"chat_id": chat_id}) 32 | return True 33 | 34 | def getAllClients(self): 35 | clients = self.db.find({}) 36 | return clients 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vitor Araujo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/analyse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Starting June 23rd, 2021, Chatbase will be in maintenance mode and we will no longer accept any new bots. All API calls in Chatbase Analytics will stop working after September 27th, 2021 3 | """ 4 | 5 | """ # coding: utf-8 6 | # vitorgamer58 7 | import requests 8 | import json 9 | 10 | api_key = 'YOUR_API_KEY_HERE' 11 | url = 'https://chatbase-area120.appspot.com/api/message' 12 | 13 | 14 | def send_menssage(funcao, type, mensagem, username): 15 | if type == 'user': 16 | mensagem = funcao + ' ' + mensagem 17 | 18 | headers = {'cache-control': 'no-cache', 19 | 'content-type': 'application/json'} 20 | body = { 21 | "api_key": api_key, 22 | "type": type, 23 | "platform": "telegram", 24 | "message": mensagem, 25 | "intent": "Use", 26 | "version": "1.0", 27 | "user_id": username 28 | } 29 | 30 | resposta = requests.post(url, data = json.dumps(body), headers=headers) 31 | print('Envio ok') 32 | 33 | def not_handled(type, mensagem, username): 34 | headers = {'cache-control': 'no-cache', 35 | 'content-type': 'application/json'} 36 | body = { 37 | "api_key": api_key, 38 | "type": type, 39 | "platform": "telegram", 40 | "message": mensagem, 41 | "not_handled": "true", 42 | "intent": "use", 43 | "version": "1.0", 44 | "user_id": username 45 | } 46 | 47 | resposta = requests.post(url, data = json.dumps(body), headers=headers) 48 | print('Envio OK') 49 | """ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ DEPRECIAÇÃO ⚠️ 2 | 3 | Este projeto foi reescrito em Javascript e agora está sendo mantido no seguinte repositório: [https://github.com/vitorgamer58/telegram-bot-bovespa-herbs](https://github.com/vitorgamer58/telegram-bot-bovespa-herbs). 4 | 5 | # Telegram_Bot_Bovespa 6 | Este é o código Backend escrito em Python de um Bot do Telegram 7 | 8 | ## Instalação 9 | Clone este repositório com o seguintes comando: 10 | 11 | git clone --recurse-submodules https://github.com/vitorgamer58/Telegram_Bot_Bovespa.git 12 | 13 | Isso garante que você fez o clone do repositório principal e do submódulo que é responsável pelos dados da análise fundamentalista. 14 | 15 | abra src/conf/.env.sample e digite o token do seu bot do telegram na linha 1 e o token do [Coinlib](https://coinlib.io/) na linha 4, salve com arquivo com o nome .env em vez de .env.sample e então na pasta raiz, digite os seguintes comandos: 16 | 17 | pip install -r requirements.txt 18 | python src/core.py 19 | 20 | E então rode o programa [phoemur](https://github.com/phoemur/fundamentus) disponibilizado como submódulo deste projeto, na pasta phoemur digite: 21 | 22 | pip install -r phoemur/required.txt 23 | python phoemur/fundamentos.py 24 | python phoemur/server.py 25 | 26 | Várias funções do bot precisam que o arquivo server.py esteja rodando, pois é ele que responde com indicadores fundamentalistas baixados do site [fundamentus](https://fundamentus.com.br/) 27 | 28 | ## Funcionamento em Funções 29 | A função principal do Bot é retornar a cotação de alguma ação listada na Bolsa de Valores B3, de acordo com a solicitação do usuário, a solicitação das cotações se dá através da API [mfinance](https://mfinance.com.br/swagger/index.html), a solicitação dos fundamentos atráves do programa phoemur, e o preço do bitcoin atráves da api da biscoint. 30 | | Funções | Descrição | 31 | |--|--| 32 | | /price + código da ação | retorna a cotação e a variação no dia | 33 | | /fii + código do fundo | retorna a cotação, a variação no dia e o dividend yield dos ultimos 12 meses de acordo com a cotação do dia 34 | | /bitcoin | retorna a cotação do bitcoin | 35 | | /fundamentus + código da ação | retorna indicadores fundamentalistas 36 | | /graham + código da ação | retorna o valor justo de acordo com a fórmula de Graham 37 | | /fechamento | retorna as maiores altas e baixas do ibovespa 38 | 39 | 40 | ## Devidos créditos e direitos autorais de terceiros 41 | O código base para a criação deste bot se deu por um tutorial postado no medium.com de autoria do Mauro de Carvalho, que pode ser encontrado aqui: [https://medium.com/](https://medium.com/@mdcg.dev/desenvolvendo-o-seu-primeiro-chatbot-no-telegram-com-python-a9ad787bdf6) 42 | 43 | O código base encontra-se aqui: [/commit/a64fe47fb1b5f101ea68736c3099d9b7f9a08b67](https://github.com/vitorgamer58/Telegram_Bot_Bovespa/commit/a64fe47fb1b5f101ea68736c3099d9b7f9a08b67) 44 | 45 | Para o preço do bitcoin usa-se a api do [Coinlib](https://coinlib.io/) 46 | 47 | Alguns indicadores e o preço da ação derivam da API [mfinance](https://mfinance.com.br/swagger/index.html) ou [OkaneBox](https://www.okanebox.com.br/) 48 | 49 | ### Fundamentus 50 | Utiliza-se o código [phoemur](https://github.com/phoemur/fundamentus) que puxa os dados do site [Fundamentus](https://fundamentus.com.br/) 51 | 52 | ## Licença 53 | Você é livre para usar, copiar, modificar, distribuir, fazer uso privado ou comercial, **desde que** dê os devidos créditos aos autores, de acordo com a [licença MIT](https://github.com/vitorgamer58/Telegram_Bot_Bovespa/blob/master/LICENSE). 54 | -------------------------------------------------------------------------------- /src/core.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # vitorgamer58 3 | import datetime 4 | import pytz 5 | from telegram.ext import Updater, CommandHandler, Filters, MessageHandler, Updater, CallbackContext, JobQueue 6 | from conf.settings import TELEGRAM_TOKEN 7 | import logging 8 | from funcoes import * 9 | 10 | logger = logging.getLogger() 11 | logger.setLevel(logging.INFO) 12 | 13 | logging.basicConfig(level=logging.INFO, 14 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') 15 | 16 | 17 | def fechamento(update, context): 18 | call_function = get_fechamento() 19 | context.bot.send_message( 20 | chat_id=update.message.chat_id, 21 | text=call_function['message'] 22 | ) 23 | 24 | 25 | def start(update, context): 26 | context.bot.send_message( 27 | chat_id=update.message.chat_id, 28 | text=f"Bem vindo {update.message.from_user['first_name']}, eu sou um robô e meus comandos são:" 29 | "\n" 30 | "/price + Código da ação (Responde com o valor da ação)" 31 | "\n" 32 | "/bitcoin (Responde com a cotação do bitcoin)" 33 | "\n" 34 | "/cripto + Código da cripto (Responde com a cotação da cripto)" 35 | "\n" 36 | "/fundamentus + Código da ação (Responde com o valor da ação)" 37 | "\n" 38 | "/graham + Código da ação (Responde com o preço justo segundo a fórmula de Graham)" 39 | "\n" 40 | "/fechamento (responde com as maiores altas e maiores baixas do ibov" 41 | ) 42 | 43 | 44 | def price(update, context): 45 | call_function = get_price( 46 | context.args, update.message.from_user['username']) 47 | context.bot.send_message( 48 | chat_id=update.message.chat_id, 49 | text=call_function['message'] 50 | ) 51 | 52 | 53 | def bitcoin(update, context): 54 | call_function = get_bitcoin(update.message.from_user['username']) 55 | context.bot.send_message( 56 | chat_id=update.message.chat_id, 57 | text=call_function['message'], 58 | parse_mode='Markdown' 59 | ) 60 | 61 | 62 | def fundamentus(update, context): 63 | call_function = get_fundamentus( 64 | context.args, update.message.from_user['username']) 65 | context.bot.send_message( 66 | chat_id=update.message.chat_id, 67 | text=call_function['message'] 68 | ) 69 | 70 | 71 | def graham(update, context): 72 | call_function = get_graham( 73 | context.args, update.message.from_user['username']) 74 | context.bot.send_message( 75 | chat_id=update.message.chat_id, 76 | text=call_function['message'] 77 | ) 78 | 79 | 80 | def fii(update, context): 81 | call_function = get_fii(context.args, update.message.from_user['username']) 82 | context.bot.send_message( 83 | chat_id=update.message.chat_id, 84 | text=call_function['message'] 85 | ) 86 | 87 | 88 | def unknown(update, context): 89 | context.bot.send_message( 90 | chat_id=update.message.chat_id, 91 | text="Não Entendi" 92 | ) 93 | 94 | 95 | def sobre(update, context): 96 | context.bot.send_message( 97 | chat_id=update.message.chat_id, 98 | text=("Este é um bot Open-Source, cujo código fonte está disponível no [Github](https://github.com/vitorgamer58/Telegram_Bot_Bovespa)" 99 | "\n" 100 | f"Criado por [vitorgamer58](tg://user?id=409733392) e está licenciado sob licença MIT" 101 | "\n" 102 | "Siga-me nas redes sociais: [Youtube](https://www.youtube.com/channel/UCn00U9AApstVzfJpFD9ALEA) e [Instagram](https://www.instagram.com/investimentosdovitor/)" 103 | "\n" 104 | "Em meu [Linktree](https://linktr.ee/investimentosdovitor) você pode encontrar mais links úteis, como cursos de investimento e corretoras de Bitcoin"), 105 | parse_mode="Markdown" 106 | ) 107 | 108 | 109 | def cripto(update, context): 110 | call_function = get_cripto(context.args) 111 | context.bot.send_message( 112 | chat_id=update.message.chat_id, 113 | text=call_function['message'], 114 | parse_mode='Markdown' 115 | ) 116 | 117 | 118 | def cadastrarFechamento(update, context): 119 | call_function = cadastrar_fechamento(update.message.chat) 120 | 121 | context.bot.send_message( 122 | chat_id=update.message.chat_id, 123 | text=call_function, 124 | ) 125 | 126 | 127 | def descadastrarFechamento(update, context): 128 | call_function = descadastrar_fechamento(update.message.chat_id) 129 | 130 | def message(result): 131 | if(result): 132 | return "Descadastrado com sucesso" 133 | else: 134 | return "Algum erro ocorreu" 135 | 136 | context.bot.send_message( 137 | chat_id=update.message.chat_id, 138 | text=message(call_function), 139 | ) 140 | 141 | 142 | def dailyFechamento(context: CallbackContext): 143 | print("DailyFechamento disparado") 144 | fechamentoDoDia = get_fechamento() 145 | clients = get_all_clients() 146 | 147 | for client in clients: 148 | context.bot.send_message( 149 | chat_id=client["chat_id"], 150 | text=fechamentoDoDia["message"] 151 | ) 152 | 153 | 154 | def main(): 155 | updater = Updater(token=TELEGRAM_TOKEN, use_context=True) 156 | dispatcher = updater.dispatcher 157 | dispatcher.add_handler( 158 | CommandHandler('start', start, run_async=True) 159 | ) 160 | dispatcher.add_handler( 161 | CommandHandler('price', price, pass_args=True, run_async=True) 162 | ) 163 | dispatcher.add_handler( 164 | CommandHandler('bitcoin', bitcoin, pass_args=False, run_async=True) 165 | ) 166 | dispatcher.add_handler( 167 | CommandHandler('fundamentus', fundamentus, 168 | pass_args=True, run_async=True) 169 | ) 170 | dispatcher.add_handler( 171 | CommandHandler('graham', graham, pass_args=True, run_async=True) 172 | ) 173 | dispatcher.add_handler( 174 | CommandHandler('fechamento', fechamento, 175 | pass_args=False, run_async=True) 176 | ) 177 | dispatcher.add_handler( 178 | CommandHandler('fii', fii, pass_args=True, run_async=True) 179 | ) 180 | dispatcher.add_handler( 181 | CommandHandler('cripto', cripto, pass_args=True, run_async=True) 182 | ) 183 | dispatcher.add_handler( 184 | CommandHandler('sobre', sobre, pass_args=False, run_async=True) 185 | ) 186 | dispatcher.add_handler( 187 | CommandHandler("cadastrarfechamento", cadastrarFechamento, 188 | pass_args=False, run_async=True) 189 | ) 190 | dispatcher.add_handler( 191 | CommandHandler("descadastrarfechamento", descadastrarFechamento, 192 | pass_args=False, run_async=True) 193 | ) 194 | dispatcher.add_handler( 195 | MessageHandler(Filters.command, unknown) 196 | ) 197 | updater.job_queue.run_daily(dailyFechamento, datetime.time( 198 | hour=18, minute=30, tzinfo=pytz.timezone("America/Sao_Paulo")), days=(0, 1, 2, 3, 4)) 199 | 200 | updater.start_polling() 201 | 202 | updater.idle() 203 | 204 | 205 | if __name__ == '__main__': 206 | print("press CTRL + C to cancel.") 207 | main() 208 | -------------------------------------------------------------------------------- /src/funcoes.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # vitorgamer58 3 | 4 | # from analyse import not_handled, #send_menssage 5 | import requests 6 | import logging 7 | import math 8 | import operator 9 | from datetime import date 10 | 11 | from conf.settings import BASE_API_URL, PHOEMUR, COINLIB, OKANE 12 | import operator 13 | from mongodb import * 14 | 15 | from datetime import date 16 | 17 | logger = logging.getLogger() 18 | logger.setLevel(logging.INFO) 19 | 20 | 21 | logging.basicConfig(level=logging.INFO, 22 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') 23 | 24 | default_headers = {"user-agent": "telegram-bot-bovespa/1.3.0", 25 | "Content-Type": "application/json;charset=UTF-8"} 26 | 27 | database = databaseClient() 28 | 29 | 30 | def dontHaveArguments(ticker): 31 | if len(ticker) == 0: 32 | return True 33 | else: 34 | return False 35 | 36 | 37 | def get_fechamento(): 38 | # Puxa os dados de todas as empresas listadas 39 | data_stocks = requests.get( 40 | 'https://mfinance.com.br/api/v1/stocks', headers=default_headers) 41 | if(data_stocks.status_code != 200): 42 | return {'status': 503, 43 | 'message': "O servidor das cotações está indisponível no momento"} 44 | data_stocks = data_stocks.json() 45 | 46 | list_ibov = ["ABEV3", "ALPA4", "ALSO3", "AMER3", "ARZZ3", "ASAI3", "AZUL4", "B3SA3", "BBAS3", "BBDC3", "BBDC4", "BBSE3", "BEEF3", "BPAC11", "BPAN4", "BRAP4", "BRFS3", "BRKM5", "CASH3", "CCRO3", "CIEL3", "CMIG4", "CMIN3", "COGN3", "CPFE3", "CPLE6", "CRFB3", "CSAN3", "CSNA3", "CVCB3", "CYRE3", "DXCO3", "ECOR3", "EGIE3", "ELET3", "ELET6", "EMBR3", "ENBR3", "ENEV3", "ENGI11", "EQTL3", "EZTC3", "FLRY3", "GGBR4", 47 | "GOAU4", "GOLL4", "HAPV3", "HYPE3", "IGTI11", "ITSA4", "ITUB4", "JBSS3", "KLBN11", "LREN3", "LWSA3", "MGLU3", "MRFG3", "MRVE3", "MULT3", "NTCO3", "PCAR3", "PETR3", "PETR4", "PETZ3", "PRIO3", "QUAL3", "RADL3", "RAIL3", "RAIZ4", "RDOR3", "RENT3", "RRRP3", "SANB11", "SBSP3", "SLCE3", "SMTO3", "SOMA3", "SUZB3", "TAEE11", "TIMS3", "TOTS3", "UGPA3", "USIM5", "VALE3", "VBBR3", "VIIA3", "VIVT3", "WEGE3", "YDUQ3"] 48 | 49 | # obter variação do indice Ibovespa 50 | ibov = [i for i in data_stocks['stocks'] 51 | if (i['symbol'] in 'IBOV')] 52 | ibov = ibov[0]['change'] 53 | 54 | # organiza pela alteração no dia - itemgetter('change') 55 | data_stocks['stocks'].sort(key=operator.itemgetter('change')) 56 | 57 | # Filtra as ações listadas, excluindo todas que não fazem parte do indice Ibovespa 58 | data_stocks = [i for i in data_stocks['stocks'] 59 | if (i['symbol'] in list_ibov)] 60 | quantidade_dados = len(data_stocks) # conta a quantidade de dicts na lista 61 | 62 | # obter maiores altas 63 | quantidade_dados -= 1 64 | maior_alta = [data_stocks[quantidade_dados]['symbol'], 65 | data_stocks[quantidade_dados]['change']] 66 | 67 | x = 5 68 | while x >= 1: 69 | x -= 1 70 | quantidade_dados -= 1 71 | maior_alta.append(data_stocks[quantidade_dados]['symbol']) 72 | maior_alta.append(data_stocks[quantidade_dados]['change']) 73 | # adiciona mais 4 empresas na lista de maiores altas 74 | 75 | # obter maiores baixas 76 | quantidade_dados = 0 77 | maior_baixa = [data_stocks[quantidade_dados]['symbol'], 78 | data_stocks[quantidade_dados]['change']] 79 | 80 | y = 0 81 | while y <= 3: 82 | y += 1 83 | quantidade_dados += 1 84 | maior_baixa.append(data_stocks[quantidade_dados]['symbol']) 85 | maior_baixa.append(data_stocks[quantidade_dados]['change']) 86 | 87 | # obtem o dia de hoje 88 | data_atual = date.today() 89 | data_em_texto = data_atual.strftime('%d/%m/%Y') 90 | 91 | # obtem as mais negociadas 92 | data_stocks.sort(key=operator.itemgetter('volume')) # organiza pelo volume 93 | quantidade_dados = len(data_stocks) 94 | quantidade_dados -= 1 95 | 96 | mais_negociadas = [data_stocks[quantidade_dados]['symbol'], 97 | data_stocks[quantidade_dados]['change']] 98 | 99 | x = 5 100 | while x >= 1: 101 | x -= 1 102 | quantidade_dados -= 1 103 | mais_negociadas.append(data_stocks[quantidade_dados]['symbol']) 104 | mais_negociadas.append(data_stocks[quantidade_dados]['change']) 105 | # adiciona mais 4 empresas na lista de mais negociadas 106 | 107 | string_de_retorno = ('Confira os dados de fechamento do pregão!🦈' 108 | "\n" 109 | "\n" 110 | f' {data_em_texto}' 111 | "\n" 112 | "\n" 113 | f' 🇧🇷 IBOVESPA : {ibov}%' 114 | "\n" 115 | "\n" 116 | '📈 MAIORES ALTAS DO IBOV' 117 | "\n" 118 | f'1️⃣ {maior_alta[0]} {maior_alta[1]}%' 119 | "\n" 120 | f'2️⃣ {maior_alta[2]} {maior_alta[3]}%' 121 | "\n" 122 | f'3️⃣ {maior_alta[4]} {maior_alta[5]}%' 123 | "\n" 124 | f'4️⃣ {maior_alta[6]} {maior_alta[7]}%' 125 | "\n" 126 | f'5️⃣ {maior_alta[8]} {maior_alta[9]}%' 127 | "\n" 128 | "\n" 129 | '📉MAIORES BAIXAS DO IBOV' 130 | "\n" 131 | f'1️⃣ {maior_baixa[0]} {maior_baixa[1]}%' 132 | "\n" 133 | f'2️⃣ {maior_baixa[2]} {maior_baixa[3]}%' 134 | "\n" 135 | f'3️⃣ {maior_baixa[4]} {maior_baixa[5]}%' 136 | "\n" 137 | f'4️⃣ {maior_baixa[6]} {maior_baixa[7]}%' 138 | "\n" 139 | f'5️⃣ {maior_baixa[8]} {maior_baixa[9]}%' 140 | "\n" 141 | "\n" 142 | '💥MAIS NEGOCIADAS DO PREGÃO' 143 | "\n" 144 | f'1️⃣ {mais_negociadas[0]} {mais_negociadas[1]}%' 145 | "\n" 146 | f'2️⃣ {mais_negociadas[2]} {mais_negociadas[3]}%' 147 | "\n" 148 | f'3️⃣ {mais_negociadas[4]} {mais_negociadas[5]}%' 149 | "\n" 150 | f'4️⃣ {mais_negociadas[6]} {mais_negociadas[7]}%' 151 | "\n" 152 | f'5️⃣ {mais_negociadas[8]} {mais_negociadas[9]}%') 153 | 154 | # Imprime no log 155 | string_log = "/Comando fechamento Acionado" 156 | logging.info(string_log) 157 | 158 | return {'status': 200, 159 | 'message': string_de_retorno} 160 | 161 | 162 | def get_price(ticker, username): 163 | if(dontHaveArguments(ticker)): 164 | return {'message': 'Você precisa informar o ticket da ação'} 165 | 166 | ticker = ticker[0].upper() 167 | # send_menssage('/price', 'user', ticker, username) 168 | busca = BASE_API_URL + "stocks/" + ticker 169 | json = requests.get(busca, headers=default_headers) 170 | 171 | if(json.status_code == 200): 172 | json = json.json() 173 | priceaction = json['lastPrice'] 174 | changeaction = json['change'] 175 | symbol = json['symbol'] 176 | string_log = f"Comando /price acionado - {symbol}, {priceaction}" 177 | logging.info(string_log) 178 | 179 | if priceaction == 0: 180 | return {'status': 404, 181 | 'message': f"Código {ticker} não encontrado, tem certeza que está correto?"} 182 | else: 183 | return {'status': 200, 184 | 'message': f"O preço da ação {symbol} é: R$ {priceaction} sendo a variação no dia de {changeaction}%"} 185 | 186 | else: 187 | 188 | if(json.status_code == 404): 189 | return {'status': 404, 190 | 'message': f"Código {ticker} não encontrado, tem certeza que está correto?"} 191 | 192 | else: 193 | return {'status': 503, 194 | 'message': "O servidor das cotações está indisponível no momento"} 195 | 196 | 197 | def get_bitcoin(username): 198 | string_log = "Comando /Bitcoin Acionado" 199 | logging.info(string_log) 200 | 201 | buscabtc = f'https://coinlib.io/api/v1/coin?key={COINLIB}&pref=BRL&symbol=BTC' 202 | jsonbtc = requests.get(buscabtc, headers=default_headers) 203 | if(jsonbtc.status_code == 200): 204 | jsonbtc = jsonbtc.json() 205 | if(jsonbtc['remaining'] > 0): 206 | pricebtc = round(float(jsonbtc['price']), 2) 207 | price_btc_usdt = round(float(jsonbtc['markets'][1]['price']), 2) 208 | # float transforma a string em número de ponto flutuante 209 | # round arredonda para duas casas decimais 210 | return {'status': 200, 211 | 'message': (f"O preço do Bitcoin é R$ {pricebtc}" 212 | "\n" 213 | f"Ou US$ {price_btc_usdt}" 214 | "\n" 215 | "Com dados do Coinlib.io" 216 | "\n" 217 | "Compre Bitcoins pela [Bitpreço](https://bitpreco.com/?r=26758)")} 218 | 219 | else: 220 | return {'status': 200, 221 | 'message': "API do Coinlib chegou ao máximo de solicitações, tente novamente mais tarde."} 222 | 223 | else: 224 | return {'status': 503, 225 | 'message': "Sistema temporariamente indisponível"} 226 | 227 | 228 | def get_fundamentus(ticker, username): 229 | if(dontHaveArguments(ticker)): 230 | return {'message': 'Você precisa informar o ticket da ação'} 231 | 232 | busca = PHOEMUR 233 | ticker = ticker[0].upper() 234 | # send_menssage('/Fundamentus', 'user', ticker, username) 235 | busca1 = requests.get(busca, headers=default_headers) 236 | if (busca1.status_code == 200): 237 | busca1 = busca1.json() 238 | cotacao = busca1[ticker]['Cotacao'] 239 | # função ROUND faz com que o numero só tenha 2 casas decimais 240 | DY = round(((busca1[ticker]['DY'])*100), 2) 241 | div_brut_pat = round(((busca1[ticker]['Div.Brut/Pat.'])*100), 2) 242 | ev_ebit = busca1[ticker]['EV/EBIT'] 243 | ev_ebitda = busca1[ticker]['EV/EBITDA'] 244 | liq_corrente = busca1[ticker]['Liq.Corr.'] 245 | mrg_ebit = round(((busca1[ticker]['Mrg.Ebit'])*100), 2) 246 | mrg_liq = round(((busca1[ticker]['Mrg.Liq.'])*100), 2) 247 | p_acl = busca1[ticker]['P/ACL'] 248 | p_ativos = busca1[ticker]['P/Ativo'] 249 | p_cap_giro = busca1[ticker]['P/Cap.Giro'] 250 | p_ebit = busca1[ticker]['P/EBIT'] 251 | p_l = busca1[ticker]['P/L'] 252 | p_vp = busca1[ticker]['P/VP'] 253 | psr = busca1[ticker]['PSR'] 254 | roe = round(((busca1[ticker]['ROE'])*100), 2) 255 | roic = round(((busca1[ticker]['ROIC'])*100), 2) 256 | 257 | string_de_retorno = (f"FUNDAMENTUS {ticker}" 258 | "\n" 259 | f"Cotação no Fundamentus: {cotacao}" 260 | "\n" 261 | f"Dividend Yield: {DY}%" 262 | "\n" 263 | f"Dívida bruta / Patrimônio Líquido: {div_brut_pat}%" 264 | "\n" 265 | f"Margem EBIT: {mrg_ebit}%" 266 | "\n" 267 | f"Margem líquida: {mrg_liq}%" 268 | "\n" 269 | f"Valor da firma / EBIT: {ev_ebit}" 270 | "\n" 271 | f"Valor da firma / EBITDA: {ev_ebitda}" 272 | "\n" 273 | f"Liquidez corrente: {liq_corrente}" 274 | "\n" 275 | f"Preço / Ativo circulante líquido: {p_acl}" 276 | "\n" 277 | f"Preço / Ativos: {p_ativos}" 278 | "\n" 279 | f"Preço / Capital de giro: {p_cap_giro}" 280 | "\n" 281 | f"Preço / EBIT: {p_ebit}" 282 | "\n" 283 | f"Preço / Lucro: {p_l}" 284 | "\n" 285 | f"Preço / Valor Patrimonial: {p_vp}" 286 | "\n" 287 | f"Price Sales Ratio: {psr}" 288 | "\n" 289 | f"ROE: {roe}%" 290 | "\n" 291 | f"ROIC: {roic}%") 292 | 293 | return {'status': 200, 294 | 'message': string_de_retorno} 295 | else: 296 | return {'status': 503, 297 | 'message': 'O sistema está fora do ar por um motivo desconhecido'} 298 | 299 | 300 | def get_graham(ticker, username): 301 | 302 | def calculaPrecoJusto(vpa, lpa): 303 | return round(math.sqrt(22.5 * lpa * vpa), 2) 304 | 305 | def stock_price(ticker): 306 | busca = BASE_API_URL + "stocks/" + ticker 307 | json = requests.get(busca, headers=default_headers) 308 | if(json.status_code != 200): 309 | return 0 310 | 311 | json = json.json() 312 | price = json['lastPrice'] 313 | return price 314 | 315 | def returnMessage(vpa, lpa, ticker): 316 | graham = calculaPrecoJusto(vpa, lpa) 317 | price = stock_price(ticker) 318 | if (price == 0): 319 | return "Sistema indisponível no momento" 320 | 321 | desconto_agio = round(((price/graham)-1)*100, 2) 322 | 323 | if(desconto_agio <= 0): 324 | resultado = 'desconto' 325 | else: 326 | resultado = 'ágio' 327 | 328 | string_de_retorno = (f"O preço justo da ação {ticker} segundo a fórmula de Graham é: R$ {graham}" 329 | "\n" 330 | f"Com um {resultado} de {abs(desconto_agio)}%" 331 | "\n" 332 | f"Preço: {price} VPA: {vpa} LPA: {lpa}") 333 | return string_de_retorno 334 | 335 | def get_graham_okanebox(ticker): 336 | busca = OKANE + ticker 337 | requisicao = requests.get(busca, headers=default_headers) 338 | if(requisicao.status_code == 200): 339 | json = requisicao.json() 340 | lista = ['LPA', 'VPA'] 341 | vpa_lpa = [i for i in json 342 | if (i['title'] in lista)] 343 | lpa = round(vpa_lpa[0]['value'], 2) 344 | vpa = round(vpa_lpa[1]['value'], 2) 345 | # round arredonda para duas casas decimais 346 | if (vpa > 0 and lpa > 0): 347 | string_de_retorno = returnMessage(vpa, lpa, ticker) 348 | 349 | elif(vpa < 0): 350 | string_de_retorno = ("VPA menor que zero, não é possível calcular!" 351 | "\n" 352 | f"VPA: {vpa} LPA: {lpa}") 353 | 354 | elif(lpa < 0): 355 | string_de_retorno = ("LPA menor que zero, não é possível calcular!" 356 | "\n" 357 | f"VPA: {vpa} LPA: {lpa}") 358 | 359 | elif(requisicao.status_code == 404 or requisicao.status_code == 500): 360 | string_de_retorno = f'O ativo {ticker} não foi encontrado' 361 | 362 | else: 363 | string_de_retorno = 'Os servidores mfinance e okanebox estão fora do ar' 364 | 365 | string_de_retorno += ('\n''Fonte: OkaneBox') 366 | return string_de_retorno 367 | 368 | if(dontHaveArguments(ticker)): 369 | return {'message': 'Você precisa informar o ticket da ação'} 370 | 371 | ticker = ticker[0].upper() 372 | # send_menssage('/graham', 'user', ticker, username) 373 | graham_url = BASE_API_URL + "stocks/indicators/" + ticker 374 | json = requests.get(graham_url, headers=default_headers) 375 | if(json.status_code == 200): 376 | json = json.json() 377 | vpa = json['bookValuePerShare']['value'] 378 | lpa = json['earningsPerShare']['value'] 379 | if (vpa > 0 and lpa > 0): 380 | string_de_retorno = returnMessage(vpa, lpa, ticker) 381 | string_de_retorno += ('\n''Fonte: mfinance') 382 | var_return = {'status': 200, 383 | 'message': string_de_retorno} 384 | 385 | else: 386 | if(vpa < 0): 387 | string_de_retorno = ("VPA menor que zero, não é possível calcular!" 388 | "\n" 389 | f"VPA: {vpa} LPA: {lpa}") 390 | 391 | var_return = {'status': 200, 392 | 'message': string_de_retorno} 393 | 394 | elif(lpa < 0): 395 | string_de_retorno = ("LPA menor que zero, não é possível calcular!" 396 | "\n" 397 | f"VPA: {vpa} LPA: {lpa}") 398 | 399 | var_return = {'status': 200, 400 | 'message': string_de_retorno} 401 | 402 | elif(vpa == 0 and lpa == 0): 403 | # Caso a API mfinance esteja fora do ar... 404 | # Chama a API OkaneBox 405 | string_de_retorno = get_graham_okanebox(ticker) 406 | var_return = {'status': 200, 407 | 'message': string_de_retorno} 408 | 409 | else: 410 | # Caso a API mfinance esteja fora do ar... 411 | # Chama a API OkaneBox 412 | get_graham_okanebox(ticker) 413 | string_de_retorno = get_graham_okanebox(ticker) 414 | var_return = {'status': 200, 415 | 'message': string_de_retorno} 416 | 417 | string_log = f"Comando /graham acionado, {ticker}" 418 | logging.info(string_log) 419 | 420 | # send_menssage('/graham', 'agent', var_return['message'], username) 421 | return var_return 422 | 423 | 424 | def get_fii(ticker, username): 425 | if(dontHaveArguments(ticker)): 426 | return {'message': 'Você precisa informar o ticket da ação'} 427 | 428 | # Faz a requisição dos dados para a API 429 | ticker = ticker[0].upper() 430 | get_fii = requests.get( 431 | 'https://mfinance.com.br/api/v1/fiis/'+ticker, headers=default_headers) 432 | get_dividendos = requests.get( 433 | 'https://mfinance.com.br/api/v1/fiis/dividends/'+ticker, headers=default_headers) 434 | 435 | if get_fii.status_code == 200 and get_dividendos.status_code == 200: 436 | # Transforma em Json e possibilita tratar os dados 437 | get_fii = get_fii.json() 438 | get_dividendos = get_dividendos.json() 439 | 440 | if get_fii['lastPrice'] != 0 and get_dividendos['dividends'] != None: 441 | """ 442 | Verifica se o preço e o dividendo são diferentes de zero, pois a API mfinance 443 | Costuma responder com 200 informando valores zerados quando se solicita um código 444 | que não existe. 445 | """ 446 | # Somar os 12 ultimos dividendos 447 | dividendos = 0 448 | soma_dividendos = 0 449 | 450 | ''' 451 | Alguns fundos não possuem histórico com mais de 12 meses, necessita de uma estrutra if 452 | para evitar o erro list index out of range 453 | ''' 454 | 455 | if len(get_dividendos['dividends']) < 12: 456 | ciclos = len(get_dividendos)-1 457 | # Conta a quantidade e reduz em 1 458 | else: 459 | ciclos = 11 460 | # São 12 meses, começando do zero até o onze, resultando em 12 461 | 462 | while dividendos <= ciclos: 463 | # print(dividendos) 464 | soma_dividendos = soma_dividendos + \ 465 | get_dividendos['dividends'][dividendos]['value'] 466 | # Var dividendos indica a posição do valor no dict 467 | dividendos += 1 468 | 469 | # Arredonda 2 casas decimais 470 | soma_dividendos = round(soma_dividendos, 2) 471 | 472 | # Calcula o dividend yield 473 | dividend_yield = (soma_dividendos / 474 | get_fii['closingPrice'])*100 475 | round(dividend_yield, 2) # Arredonda 476 | 477 | string_de_retorno = (f"O preço do FII {ticker} é: R$ {get_fii['closingPrice']} sendo a variação no dia de {get_fii['change']}%" 478 | '\n' 479 | f'Neste preço, o dividend yield (12m) é de {round(dividend_yield, 2)}% com uma distribuição de R$ {round(soma_dividendos, 2)}') 480 | return {'status': 200, 'message': string_de_retorno} 481 | else: 482 | """ 483 | Caso algum dos valores seja zero, necessita um tratamento especial para identificar o fato. 484 | """ 485 | if get_fii['closingPrice'] == 0 and get_dividendos['dividends'] == None: 486 | # Não encontrado na API, provavelmente este FII não existe 487 | var_return = { 488 | 'status': 404, 'message': f'O fundo {ticker} não foi encontrado na API, tem certeza que digitou corretamente?'} 489 | elif get_fii['closingPrice'] != 0 and get_dividendos['dividends'] == None: 490 | string_de_retorno = (f"O preço do FII {ticker} é: R$ {get_fii['closingPrice']} sendo a variação no dia de {get_fii['change']}%" 491 | '\n' 492 | 'Ainda não foi encontrado um histórico de dividendos para este fundo, ele pode ser muito novo.') 493 | return {'status': 200, 'message': string_de_retorno} 494 | else: 495 | return {'status': 500, 496 | 'message': 'erro desconhecido'} 497 | else: 498 | return { 499 | 'status': 503, 'message': f'A API mfinance está fora do ar por um motivo desconhecido, erro {get_fii.status_code}'} 500 | 501 | 502 | def get_cripto(ticker): 503 | if(dontHaveArguments(ticker)): 504 | return {'message': 'Você precisa informar o ticket da criptomoeda'} 505 | 506 | string_log = f"Comando /Coin {ticker} Acionado" 507 | logging.info(string_log) 508 | 509 | ticker = ticker[0].upper() 510 | buscacripto = f'https://coinlib.io/api/v1/coin?key={COINLIB}&pref=BRL&symbol={ticker}' 511 | jsoncripto = requests.get(buscacripto, headers=default_headers) 512 | if(jsoncripto.status_code == 200): 513 | jsoncripto = jsoncripto.json() 514 | if(jsoncripto['remaining'] > 0): 515 | pricecripto = round(float(jsoncripto['price']), 2) 516 | # price_cripto_usdt = round(float(jsoncripto['markets'][1]['price']), 2) 517 | # float transforma a string em número de ponto flutuante 518 | # round arredonda para duas casas decimais 519 | return {'status': 200, 520 | 'message': (f"O preço é R$ {pricecripto}" 521 | "\n" 522 | "Com dados do Coinlib.io")} 523 | else: 524 | return {'status': 200, 525 | 'message': "API do Coinlib chegou ao máximo de solicitações, tente novamente mais tarde."} 526 | 527 | else: 528 | return {'status': 503, 529 | 'message': "Sistema temporariamente indisponível"} 530 | 531 | 532 | def cadastrar_fechamento(chat): 533 | result = database.addTelegramClient(chat) 534 | 535 | if(result): 536 | return "Cadastrado com sucesso" 537 | else: 538 | return "Provavelmente você já está cadastrado" 539 | 540 | 541 | def descadastrar_fechamento(chat_id): 542 | result = database.removeTelegramClient(chat_id) 543 | if(result): 544 | return "Descadastrado com sucesso" 545 | else: 546 | return "Algum erro ocorreu" 547 | 548 | 549 | def get_all_clients(): 550 | clients = database.getAllClients() 551 | return clients 552 | --------------------------------------------------------------------------------