Golang : Oanda bot with Telegram and RSI example




Putting this down for my own reference in case I need it again in future. Basically, the code below is a "skeleton" program for my own trading bot using Oanda.com trading platform.

It has a Telegram interface and able to calculate Relative-Strength Index of a given cross such as AUD/JPY. It also uses the excellent Ta-lib Golang port to calculate RSI( which matches the RSI values found in TradingView.com).

The code itself is pretty trivial and does nothing that will cause a person to make or lose money yet....other than providing a base to work on. To get this code to work, first...replace the values found in the application.cfg file first.

You will need to setup a Telegram bot and get your chat id. After that, setup a demo account with Oanda.com

application.cfg


 # Telegram settings
 telegramBotToken = <your telegram bot token>
 telegramChatID = <your telegram chat id>

 # Oanda settings
 # Where to find the account ID? 
 # See https://www.oanda.com/demo-account/funding or LIVE https://www.oanda.com/account/funding
 # Use the [v20 Account Number] 
 oandaAccountID = <xxx-xxx-xxxxxxx-xxx>
 oandaAPIKey = <your oanda api key>

Here you go!


 package main

 import (
  "bufio"
  "fmt"
  "io"
  "log"
  "os"
  "strconv"
  "strings"
  "time"

  "github.com/awoldes/goanda"
  "github.com/markcheno/go-talib"

  //"github.com/davecgh/go-spew/spew"

  tgbotapi "gopkg.in/telegram-bot-api.v4"
 )

 // Config file to set our bot default behaviours
 var applicationConfigFileName = "application.cfg"

 type Config map[string]string

 func main() {

  // always read the application configuration first
  applicationConfig, err := ReadConfig(applicationConfigFileName)
  if err != nil {
 fmt.Println("Unable to read application.cfg file!")
 os.Exit(1)
  }

  telegramBotToken := applicationConfig["telegramBotToken"]
  telegramChatID := applicationConfig["telegramChatID"]
  oandaAPIKey := applicationConfig["oandaAPIKey"]
  oandaAccountID := applicationConfig["oandaAccountID"]

  // set the NewConnection 3rd parameter to [false] to use DEMO account.
  // [true] for LIVE account

  oanda := goanda.NewConnection(oandaAccountID, oandaAPIKey, false) // set false to use https://api-fxpractice.oanda.com

  //go PollingPrice(oanda, "AUD_JPY")

  // go routine to wait for updates from user
  // type /help in Telegran to get started
  go TelegramHandler(telegramBotToken, telegramChatID, oanda)

  fmt.Println("Starting Oanda Trading bot....")

  select {} // this will cause the program to run forever until termination

 }

 //--------------------------------------------------------------------------------------------------------

 func GetOneHourRSI(oanda *goanda.OandaConnection, cross string) string {

  length := 14

  // See http://developer.oanda.com/rest-live-v20/instrument-ep/#collapse_2_example_curl_1
  // and for the candlestick granularity ... see
  // http://developer.oanda.com/rest-live-v20/instrument-df/#CandlestickGranularity

  // Get 100 candles with 1 hour granularity
  data := oanda.GetCandles(cross, "100", "H1") // H1 = 1 hour
  closeSlice := make([]float64, len(data.Candles))

  // calculate RSI
  for i := 0; i < len(data.Candles); i++ {
 closeSlice[i] = data.Candles[i].Mid.Close
  }

  rsiSlice := talib.Rsi(closeSlice, length)
  rsi := rsiSlice[len(rsiSlice)-1] // match tradingview.com RSI
  rsiStr := strconv.FormatFloat(rsi, 'f', 6, 64) // convert float64 to string
  result := "RSI for " + cross + " : " + rsiStr
  return result

 }

 func GetPrice(oanda *goanda.OandaConnection, cross string) string {
  instrument := cross
  priceResponse := oanda.GetInstrumentPrice(instrument)

  askPrice := strconv.FormatFloat(priceResponse.Prices[0].Asks[0].Price, 'f', 6, 64)
  bidPrice := strconv.FormatFloat(priceResponse.Prices[0].Bids[0].Price, 'f', 6, 64)
  queryTime := priceResponse.Prices[0].Time

  result := "Asking price at [" + queryTime.String() + "] is " + askPrice + ".Bidding price is " + bidPrice + "."
  return result
 }

 func PollingPrice(oanda *goanda.OandaConnection, cross string) {
  counter := time.Tick(time.Duration(15) * time.Second) // poll every 15 seconds
  for range counter {
 instrument := cross
 priceResponse := oanda.GetInstrumentPrice(instrument)

 askPrice := priceResponse.Prices[0].Asks[0].Price
 bidPrice := priceResponse.Prices[0].Bids[0].Price
 time := priceResponse.Prices[0].Time
 pair := priceResponse.Prices[0].Instrument

 fmt.Println("Polling ", pair)
 fmt.Println("Asking price at [", time, "] is ", askPrice)
 fmt.Println("Bidding price at [", time, "] is ", bidPrice)
  }
 }

 func TelegramHandler(telegramBotToken, telegramChatID string, oanda *goanda.OandaConnection) {
  fmt.Println("Telegram configurations")
  fmt.Println("BotToken : ", telegramBotToken)
  fmt.Println("ChatID : ", telegramChatID)

  bot, err := tgbotapi.NewBotAPI(telegramBotToken)

  if err != nil {
 log.Println(err)
  } else {
 log.Printf("Telegram authorized on account %s with chat ID %s\n", bot.Self.UserName, telegramChatID)
 log.Println("Type /help in your Telegram app to see the available commands.", bot.Self.UserName, telegramChatID)
  }
  // convert chatID to type int64
  telegramChatID64, _ := strconv.ParseInt(telegramChatID, 10, 64) // use base 10 for sanity

  msg := tgbotapi.NewMessage(telegramChatID64, "OandaBot started! To see available commands type /help")
  msg.ParseMode = "markdown"
  bot.Send(msg)

  u := tgbotapi.NewUpdate(0)
  u.Timeout = 60

  updates, err := bot.GetUpdatesChan(u)

  if err != nil {
 log.Println(err)
  }

  for update := range updates {
 if update.Message == nil {
 continue
 }

 log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)

 if update.Message.IsCommand() {
 msg := tgbotapi.NewMessage(update.Message.Chat.ID, "")
 switch update.Message.Command() {
 case "help":
 msg.Text = "type /sayhi or /status or /recommend or /market or /entry or /audjpy."
 case "sayhi":
 msg.Text = "Hi :)"
 case "status":
 msg.Text = "I'm ok."
 case "recommend":
 msg.Text = "I recommend that you buy AUD/JPY 30000 units at 82.00 at exit at 83.20"
 case "audjpy":
 price := GetPrice(oanda, "AUD_JPY")
 rsi := GetOneHourRSI(oanda, "AUD_JPY")
 msg.Text = price + rsi
 case "entry":
 msg.Text = "Executing entry order."
 default:
 msg.Text = "I don't know that command"
 }
 bot.Send(msg)
 }

  }
 }

 func ReadConfig(filename string) (Config, error) {
  // init with empty data
  config := Config{}
  if len(filename) == 0 {
 return config, nil
  }
  file, err := os.Open(filename)
  if err != nil {
 return nil, err
  }
  defer file.Close()

  reader := bufio.NewReader(file)

  for {
 line, err := reader.ReadString('\n')

 // skip all line starting with #
 if hash := strings.Index(line, "#"); hash < 0 {

 // check if the line has = sign
 // and process the line. Ignore the rest.
 if equal := strings.Index(line, "="); equal >= 0 {
 if key := strings.TrimSpace(line[:equal]); len(key) > 0 {
 value := ""
 if len(line) > equal {
 value = strings.TrimSpace(line[equal+1:])
 }
 // assign the config map
 config[key] = value
 }
 }
 }
 if err == io.EOF {
 break
 }
 if err != nil {
 return nil, err
 }
  }
  return config, nil
 }

References:

https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id

https://www.reddit.com/r/algotrading/comments/7xrw3c/tradingviewsrsidifferentfrommyowncalculation/

  See also : Golang : Calculate Relative Strength Index(RSI) example





By Adam Ng(黃武俊)

IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.


Advertisement