This commit is contained in:
145
main.go
Normal file
145
main.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"deadbeef.codes/steven/ynab-portfolio-monitor/questrade"
|
||||
"deadbeef.codes/steven/ynab-portfolio-monitor/ynab"
|
||||
)
|
||||
|
||||
var (
|
||||
persistentData *PersistentData
|
||||
questradeClient *questrade.Client
|
||||
ynabClient *ynab.Client
|
||||
questradeAccountIDs []int
|
||||
ynabAccountIDs []string
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Printf("ynab-portfolio-monitor init")
|
||||
|
||||
// Load application configuration from environment variables
|
||||
envVars := make(map[string]string)
|
||||
envVars["questrade_refresh_token"] = os.Getenv("questrade_refresh_token")
|
||||
envVars["ynab_secret"] = os.Getenv("ynab_secret")
|
||||
envVars["ynab_budget_id"] = os.Getenv("ynab_budget_id")
|
||||
|
||||
// Validate that all required environment variables are set
|
||||
for key, value := range envVars {
|
||||
if value == "" {
|
||||
log.Fatalf("shell environment variable %s is not set", key)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; true; i++ {
|
||||
questradeAccountIDString := os.Getenv(fmt.Sprintf("questrade_account_%d", i))
|
||||
ynabAccountID := os.Getenv(fmt.Sprintf("questrade_ynab_account_%d", i))
|
||||
if questradeAccountIDString == "" || ynabAccountID == "" {
|
||||
break
|
||||
}
|
||||
|
||||
questradeAccountID, err := strconv.Atoi(questradeAccountIDString)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to convert environment variable questrade_account_%d with value of '%s' to integer: %v", i, questradeAccountIDString, err)
|
||||
}
|
||||
questradeAccountIDs = append(questradeAccountIDs, questradeAccountID)
|
||||
ynabAccountIDs = append(ynabAccountIDs, ynabAccountID)
|
||||
}
|
||||
|
||||
// Load persistent data
|
||||
var err error
|
||||
persistentData, err = loadPersistentData()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load persistent data: %v", err)
|
||||
}
|
||||
|
||||
// ynab client is static and has no persistent data so is initialized here and not in main program loop
|
||||
ynabClient, err = ynab.NewClient(envVars["ynab_budget_id"], envVars["ynab_secret"])
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create ynab client: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
for {
|
||||
var err error
|
||||
|
||||
// Questrade authentication needs to be refreshed and persistentData written to disk in case app restarts
|
||||
questradeClient, err = questrade.NewClient(persistentData.QuestradeRefreshToken)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create questrade client: %v", err)
|
||||
}
|
||||
|
||||
persistentData.QuestradeRefreshToken = questradeClient.Credentials.RefreshToken
|
||||
|
||||
err = savePersistentData(*persistentData)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to save persistent data: %v", err)
|
||||
}
|
||||
|
||||
// Update Questrade accounts
|
||||
err = syncQuestrade()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to sync questrade to ynab: %v", err)
|
||||
}
|
||||
|
||||
// Update Bitcoin account
|
||||
|
||||
// Update ComputerShare account
|
||||
|
||||
log.Print("Sleeping for 6 hours...")
|
||||
time.Sleep(time.Hour * 6)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func syncQuestrade() error {
|
||||
|
||||
for i, questradeAccountID := range questradeAccountIDs {
|
||||
questradeBalance, err := questradeClient.GetQuestradeAccountBalance(questradeAccountID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get questrade account balance for account ID '%d': %v", questradeAccountID, err)
|
||||
}
|
||||
|
||||
ynabTransactionID, ynabTransactionAmount, err := ynabClient.GetTodayYnabCapitalGainsTransaction(ynabAccountIDs[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get ynab capital gains transaction ID: %v", err)
|
||||
}
|
||||
|
||||
ynabAccount, err := ynabClient.GetAccount(ynabAccountIDs[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get ynab account with id '%s': %v", ynabAccountIDs[i], err)
|
||||
}
|
||||
|
||||
balanceDelta := questradeBalance - ynabAccount.Data.Account.Balance
|
||||
balanceDelta += ynabTransactionAmount // Take into account the existing transaction
|
||||
|
||||
if balanceDelta == 0 {
|
||||
continue // If balanceDelta is 0 do not create a transaction i.e. market is closed today
|
||||
}
|
||||
|
||||
if ynabTransactionID == "" {
|
||||
// there is no transaction - so create a new one
|
||||
err = ynabClient.CreateTodayYNABCapitalGainsTransaction(ynabAccountIDs[i], balanceDelta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create YNAB capital gains transaction for account ID '%s': %v", ynabAccountIDs[i], err)
|
||||
}
|
||||
log.Printf("Creating new capital gains transaction for YNAB account '%s' for amount: %d", ynabAccountIDs[i], balanceDelta)
|
||||
|
||||
} else {
|
||||
// there is an existing transaction - so update the existing one
|
||||
err = ynabClient.UpdateTodayYNABCapitalGainsTransaction(ynabAccountIDs[i], ynabTransactionID, balanceDelta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update YNAB capital gains transaction for account ID '%s': %v", ynabAccountIDs[i], err)
|
||||
}
|
||||
log.Printf("Updating existing capital gains transaction for YNAB account '%s' for amount: %d", ynabAccountIDs[i], balanceDelta)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user