Robô para controlar ações e criptomoedas
Uma das vantagens de ser programador é que quando você não encontra o software
que quer, você cria um. Eu estava a procura de um robô para me ajudar no
controle de ações e criptomoedas, mas não encontrei nenhum gratuito que faz o
que eu quero: monitorar a cotação das ações e criptomoedas que eu tenho e me
alertar quando eu tiver um determinado ganho ou perda. Queria algo simples e
prático.
Como não achei nada com todas as características necessárias, criei
um pra mim. Para isso usei a linguagem Go (Golang). Mas não se assuste, dividi esse post em duas partes: a primeira, uma explicação de como o programa funciona e a segunda, mais técnica.
simbolo: apenas o símbolo do ativo
A ideia é a seguinte: ler um arquivo com a carteira que eu tenho, com
as informações necessárias para me informar quando atingir o valor pré-determinado de ganho
ou perda. A cada intervalo de tempo (configurável), carregar as cotações
atualizadas da carteira e recalcular tudo novamente. Se ultrapassar o limiar
definido de perda ou ganho, informar através de uma mensagem na tela e via
Telegram.
Pode parecer que não, mas ajuda bastante: em menos de dois meses, só com Bitcoin, tive um lucro de mais de R$ 1.200,00, investindo pouco mais de R$ 5.000,00. Ou seja, mais de 20% - nada mal...
Tudo começa com o arquivo de configuração, que no programa tem o nome de smartbot.cfg:
SleepMinutes: informa o tempo em minutos que o programa deve aguardar para realizar as buscas por novas cotações.
telegramID: é o seu ID no Telegram. Para descobrir qual é, veja aqui: How to get an id to use on Telegram Messenger. Se você não quiser utilizar o Telegram, basta colocar 0 (zero) neste campo.
telegramToken: é o token do bot do Telegram que irá receber as mensagens de alerta. Existem diversos tutoriais ensinando a fazer isso, como 10 Passos para se criar um Bot no Telegram.
Em seguida, configuramos o arquivo carteira.cfg com as informações
que formam a nossa carteira de investimentos:
link: link utilizado pelo programa para carregar a cotação*
tipo: tipo do ativo: acao ou criptomoeda
quantidade: quantidade inicial do ativo
inicial: valor inicial pago pelo ativo (unitário)
perda: limite de perda aceitável (em R$)
ganho: limite de ganho desejável (em R$)
* Estou usando o site https://br.investing.com/. No download abaixo, disponibilizo também o meu arquivo carteira.cfg.
Você pode fazer o download do programa executável aqui. Basta descompactar e preencher os arquivos .CFG com as informações necessárias conforme explicado acima. Se tiver alguma dúvida no preenchimento, entre em contato.
A partir daqui vou dar algumas explicações mais técnicas, portanto, se for caso, pode parar de ler.
Com isso definido, podemos iniciar o programa propriamente dito. Inicialmente,
defino um struct para os dados de configuração:
type Config struct {
SleepMinutes int `json:"sleepMinutes"`
TelegramID int64 `json:"telegramID"`
TelegramToken string `json:"telegramToken"`
}
E outro struct para os dados da carteira:
type Quotes struct { Quotes []Quote `json:"quotes"` } type Quote struct { Symbol string `json:"symbol"` Quantity int64 `json:"quantity"` Stop float64 `json:"stop"` } type Carteira struct { Ativos []Ativo `json:"ativos"` } type Ativo struct { Simbolo string `json:"simbolo"` Link string `json:"link"` Tipo string `json:"tipo"` Quantidade float64 `json:"quantidade"` Inicial float64 `json:"inicial"` Perda float64 `json:"perda"` Ganho float64 `json:"ganho"` }
Para ler as configurações dos arquivos CFG, criei uma função:
func readConfig(fileName string, cfg interface{}) error { b, err := ioutil.ReadFile(fileName) if err != nil { return fmt.Errorf("readConfig: %w", err) } reader := strings.NewReader(string(b)) if err := json.NewDecoder(reader).Decode(&cfg); err != nil { return fmt.Errorf("readConfig: %w", err) } return nil }
Entrando no programa principal, carrego o arquivo de configuração:
if err := readConfig("./smartBot.cfg", &config); err != nil { log.Fatal(err) }
E finalmente, temos o laço principal do programa, onde primeiramente carrego as informações da carteira e indico se o mercado está aberto (o horário de funcionamento é das 10:00 às 17:30), então, para cada um dos meus ativos, carrego sua cotação e realizo os cálculos para verificar se o limiar de perda ou ganho foi atingido.
for {
if err := readConfig("./carteira.cfg", &carteira); err != nil { log.Fatal(err) } fmt.Println(time.Now().In(loc).Format("02/01/2006 15:04:05")) aberto := true hm := time.Now().In(loc).Format("15:04") if hm > "17:30" || hm < "10:00" { aberto = false } ultimo := "" for _, ativo := range carteira.Ativos { resp, err := calculo(ativo, config) if err != nil { log.Println(err) continue } if !aberto && ativo.Tipo == "acao" && !(hm >= "17:30" && hm <= "17:34") { resp += " Mercado fechado" } fmt.Println(resp) ultimo += resp + "\n" } ioutil.WriteFile(filename, []byte(ultimo), 0644) fmt.Println() if *runonce { os.Exit(0) } time.Sleep(time.Duration(config.SleepMinutes) * time.Minute) }
A função valor, carrega o link definido no ativo da carteira, lê seus dados, e procura pelo valor da cotação, com a expressão regular definida no programa. Se encontra, remove a formatação da moeda da página e transforma em float64.
var re = regexp.MustCompile(`<span class="arial_26 inlineblock pid-(\d*)-last" id="last_last" dir="ltr">(.*?)</span>`)
func valor(ativo Ativo) (float64, error) { res, err := http.Get(ativo.Link) if err != nil { return 0, fmt.Errorf("valor: %w", err) } b, err := ioutil.ReadAll(res.Body) if err != nil { return 0, fmt.Errorf("valor: %w", err) } doc := string(b) matches := re.FindStringSubmatch(doc) if len(matches) != 3 { return 0, fmt.Errorf("valor: cotação não encontrada: %s", ativo.Simbolo) } s := matches[2] ponto := strings.Index(s, ".") virgula := strings.Index(s, ",") //fmt.Printf("DEBUG: %s %d %d\n", s, ponto, virgula) if ponto < virgula { if strings.Contains(s, ",") { s = strings.ReplaceAll(s, ".", "") } } s = strings.ReplaceAll(s, ",", ".") price, err := strconv.ParseFloat(s, 64) if err != nil { return 0, fmt.Errorf("valor: %w", err) } return price, nil }
De posse dos dados retornados pela função valor, a função calculo, faz os cálculos para verificar se houve o ganho ou perda configurado no ativo.
func calculo(ativo Ativo, cfg Config) (string, error) { price, err := valor(ativo) if err != nil { return "", fmt.Errorf("calculo: %w", err) } base := ativo.Quantidade * ativo.Inicial atual := ativo.Quantidade * price diff := atual - base res := fmt.Sprintf("%-12v %-17v %-17v", ativo.Simbolo, fmt.Sprintf("Preço: %.2f", price), fmt.Sprintf("Saldo: %.2f", diff)) if diff < 0 && math.Abs(diff) > ativo.Perda { msg := fmt.Sprintf(res + "Atingiu o limite de perda!") sendTelegram(cfg, msg) return msg, nil } if diff > 0 && diff > ativo.Ganho { msg := fmt.Sprintf(res + "Atingiu o limite de ganho!") sendTelegram(cfg, msg) return msg, nil } hm := time.Now().In(loc).Format("15:04") if hm >= "10:00" && hm <= "10:04" { msg := fmt.Sprintf(res + "Abertura do mercado") //sendTelegram(cfg, msg) return msg, nil } if hm >= "17:30" && hm <= "17:34" { msg := fmt.Sprintf(res + "Fechamento do mercado") //sendTelegram(cfg, msg) return msg, nil } return res, nil }
O código completo do programa pode ser baixado no Github.
Comentários
Postar um comentário