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