This content originally appeared on Level Up Coding - Medium and was authored by Adam Szpilewicz
Building a Bitcoin Price Tracker
In this post, we will walk you through the process of building a Bitcoin price tracker using Golang and the github.com/robfig/cron/v3 package. This application will fetch the latest Bitcoin price from the Coindesk API every minute and save it to a CSV file.
Meet cron and github.com/robfig/cron/v3
Cron is a time-based job scheduler widely used in Unix-based operating systems, allowing users to automate repetitive tasks by running commands or scripts at specified intervals. The name “cron” comes from the Greek word “chronos,” which means “time,” and it is an essential tool for system administrators and developers alike.
Cron in General
Cron is built around the idea of time-based scheduling, allowing tasks to be executed at regular intervals, such as every minute, hour, or day. The core component of cron is the crontab (short for “cron table”), a configuration file that specifies the commands or scripts to run and the schedule for each task.
A typical crontab entry consists of six fields: the minute, hour, day of the month, month, day of the week, and the command or script to execute. For example, a crontab entry to run a script every day at midnight would look like this:
0 0 * * * /path/to/your/script.sh
The github.com/robfig/cron/v3
The github.com/robfig/cron/v3 package is a powerful and flexible Golang library that simplifies the process of scheduling and managing cron jobs within your Golang applications. It provides an easy-to-use API for adding, removing, and modifying cron jobs and supports various scheduling options, including fixed intervals, specific dates, and more complex schedules using cron expressions.
Some key features of the github.com/robfig/cron/v3 package include:
- Familiar cron syntax: The library uses the traditional cron syntax for defining schedules, making it easy for developers familiar with cron to get started quickly.
- Extensible: The package is designed with extensibility in mind, allowing developers to customize job execution, error handling, and job persistence.
- Flexibility: The library supports a wide range of scheduling options, from simple fixed intervals to more complex schedules with cron expressions.
- Lightweight: The package has minimal dependencies and is designed to have a small memory footprint, making it an excellent choice for resource-constrained environments.
Application Overview
The application that we will build consists of several components:
- CronManager: A struct that encapsulates the cron functionality and provides methods to start, stop, and add tasks to the scheduler.
- Fetching Bitcoin Price: Functions to fetch the latest Bitcoin price from the Coindesk API, parse the JSON response, and return the price.
- Saving Data to CSV: A function to save the timestamp and Bitcoin price to a CSV file.
Application Implementation
CronManager:
The CronManager struct is created to manage the cron jobs. It has a Start() and Stop() method to start and stop the cron scheduler. The AddBitcoinPriceJob() method adds the Bitcoin price fetching task to the scheduler.
type CronManager struct {
cron *cron.Cron
}
func NewCronManager() *CronManager {
return &CronManager{
cron: cron.New(cron.WithSeconds()),
}
}
// AddBitcoinPriceJob adds a cron task to fetch the Bitcoin price every minute
func (cm *CronManager) AddBitcoinPriceJob() error {
_, err := cm.cron.AddFunc(cronExpression, func() {
price, err := fetchBitcoinPrice()
if err != nil {
fmt.Println("Error fetching Bitcoin price:", err)
return
}
...
}
cm.cron.AddFunc method is used to add a new cron job to the scheduler. The method takes two arguments: a cron expression and a function to be executed when the job is scheduled to run.
The cronExpression parameter is a string that represents the schedule for the job using cron syntax. In this case, the expression "0 */1 * * * *" specifies that the job should run every minute, as it is set to trigger when the seconds field is 0 and the minutes field is a multiple of 1 (e.g., 0, 1, 2, ...).
The second argument is an anonymous function, which is the actual task that the scheduler will execute at the specified interval. In this example, the function does the following:
- Calls the fetchBitcoinPrice() function to fetch the current Bitcoin price.
- Checks if there was an error while fetching the price. If so, it prints the error message and returns immediately, skipping the rest of the function.
- If there was no error, it prints the fetched Bitcoin price to the console.
- Calls the saveToCSVFile() function to save the timestamp and fetched Bitcoin price to a CSV file.
- If there was an error while saving the data to the CSV file, it prints the error message and returns immediately. Otherwise, it prints a message indicating that the price was saved to the file.
The cm.cron.AddFunc method returns two values: the first value is a unique ID for the cron job, which can be used later to remove or modify the job. The second value is an error, which will be non-nil if there was a problem adding the job to the scheduler. In this example, the job ID is ignored (with _) and only the error is checked for further processing.
Fetching Bitcoin Price:
The fetchBitcoinPrice() function sends an HTTP GET request to the Coindesk API to fetch the current Bitcoin price. The response body is read using io.ReadAll() and passed to the parsePriceData() function.
The parsePriceData() function unmarshals the JSON data and extracts the Bitcoin price from the nested structure. It then returns the price as a float64 value.
// fetchBitcoinPrice fetches the current Bitcoin price from the CoinDesk API
func fetchBitcoinPrice() (float64, error) {
}
// parsePriceData parses the JSON response from the CoinDesk API
func parsePriceData(data []byte) (float64, error) {
}
Saving Data to CSV:
The saveToCSVFile() function takes a filename, timestamp, and price as arguments. It first checks if the file exists; if not, it creates the file and writes the header. The function then appends a new row with the timestamp and price to the CSV file.
// saveToCSVFile saves the Bitcoin price to a CSV file
func saveToCSVFile(filename string, timestamp time.Time, price float64) error {
}
Running the Application
The main function initializes a new CronManager instance, adds the Bitcoin price fetching task, and starts the cron scheduler. The application then waits for a termination signal (such as Ctrl+C) from the user before stopping the cron scheduler gracefully.
func main() {
// Create a new CronManager instance
cm := NewCronManager()
// Add the Bitcoin price fetching task
err := cm.AddBitcoinPriceJob()
if err != nil {
fmt.Println("Error adding cron task:", err)
return
}
}
Finally we can build app, ship and run and then check that we fetch from API BTC price every minute and save into csv file:
And the whole code snippet
package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
"io"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
const (
priceAPIURL = "https://api.coindesk.com/v1/bpi/currentprice/BTC.json"
outputFile = "bitcoin_price.csv"
cronExpression = "0 */1 * * * *"
)
func main() {
// Create a new CronManager instance
cm := NewCronManager()
// Add the Bitcoin price fetching task
err := cm.AddBitcoinPriceJob()
if err != nil {
fmt.Println("Error adding cron task:", err)
return
}
// Start the cron scheduler
cm.Start()
// Wait for a termination signal from the user
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
// Stop the cron scheduler gracefully
cm.Stop()
fmt.Println("Cron scheduler stopped")
}
// CronManager manages the cron scheduler
type CronManager struct {
cron *cron.Cron
}
// NewCronManager creates a new CronManager instance
func NewCronManager() *CronManager {
return &CronManager{
cron: cron.New(cron.WithSeconds()),
}
}
// Start starts the cron scheduler
func (cm *CronManager) Start() {
cm.cron.Start()
}
// Stop stops the cron scheduler gracefully
func (cm *CronManager) Stop() {
cm.cron.Stop()
}
// AddBitcoinPriceJob adds a cron task to fetch the Bitcoin price every minute
func (cm *CronManager) AddBitcoinPriceJob() error {
_, err := cm.cron.AddFunc(cronExpression, func() {
price, err := fetchBitcoinPrice()
if err != nil {
fmt.Println("Error fetching Bitcoin price:", err)
return
}
fmt.Printf("Fetched Bitcoin price: %f\n", price)
err = saveToCSVFile(outputFile, time.Now(), price)
if err != nil {
fmt.Println("Error saving to CSV file:", err)
return
}
fmt.Println("Saved Bitcoin price to CSV file")
})
return err
}
// fetchBitcoinPrice fetches the current Bitcoin price from the CoinDesk API
func fetchBitcoinPrice() (float64, error) {
resp, err := http.Get(priceAPIURL)
if err != nil {
return 0, errors.Wrap(err, "failed to fetch Bitcoin price")
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, errors.Wrap(err, "failed to read response body")
}
price, err := parsePriceData(body)
if err != nil {
return 0, errors.Wrap(err, "failed to parse price data")
}
return price, nil
}
// parsePriceData parses the JSON response from the CoinDesk API
func parsePriceData(data []byte) (float64, error) {
var priceData struct {
Bpi struct {
USD struct {
RateFloat float64 `json:"rate_float"`
} `json:"USD"`
} `json:"bpi"`
}
err := json.Unmarshal(data, &priceData)
if err != nil {
return 0, errors.Wrap(err, "failed to unmarshal JSON")
}
return priceData.Bpi.USD.RateFloat, nil
}
// saveToCSVFile saves the Bitcoin price to a CSV file
func saveToCSVFile(filename string, timestamp time.Time, price float64) error {
fileExists := false
if _, err := os.Stat(filename); err == nil {
fileExists = true
}
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return errors.Wrap(err, "failed to open CSV file")
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
if !fileExists {
// Write header if the file is newly created
err = writer.Write([]string{"timestamp", "price"})
if err != nil {
return errors.Wrap(err, "failed to write header to CSV file")
}
}
// Write the timestamp and price to the CSV file
err = writer.Write([]string{timestamp.Format(time.RFC3339), fmt.Sprintf("%.2f", price)})
if err != nil {
return errors.Wrap(err, "failed to write data to CSV file")
}
return nil
}
Conclusion
This example demonstrates how to build a simple Bitcoin price tracker using Golang and the robfig/cron/v3 package. The application fetches the latest Bitcoin price from the Coindesk API and saves the data to a CSV file at regular intervals using a cron job. This project can be extended to track other cryptocurrencies or fetch data from different APIs.
Level Up Coding
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 💰 Free coding interview course ⇒ View Course
- 🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Join the Level Up talent collective and find an amazing job
Golang and Cron was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Adam Szpilewicz
Adam Szpilewicz | Sciencx (2023-04-18T20:20:32+00:00) Golang and Cron. Retrieved from https://www.scien.cx/2023/04/18/golang-and-cron/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.