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. 

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...

Exemplo (real) de mensagem enviada pelo programa.

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:

simbolo: apenas o símbolo do ativo
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

Postagens mais visitadas deste blog

Como aprender a programar

Netflix não mostra ícone de streaming

Google Hacking