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
Tutorials
+32.6k Golang : How to check if a date is within certain range?
+13.9k Golang : Simple word wrap or line breaking example
+14.7k Golang : Save(pipe) HTTP response into a file
+16.2k Golang : Send email and SMTP configuration example
+9.1k Golang : How to get ECDSA curve and parameters data?
+41.2k Golang : Convert string to array/slice
+9k Golang : How to get username from email address
+9.5k Golang : List available AWS regions
+11.9k Golang : convert(cast) string to integer value
+4.9k Golang : Customize scanner.Scanner to treat dash as part of identifier
+47.6k Golang : How to convert JSON string to map and slice
+14.9k Golang : Get HTTP protocol version example