A Quick Disclaimer: I'm a programmer and have very little experience trading. Please do your own research before using the algorithm I create for the cryptocurrency trading bot or any other algorithm. Only trade with money you can afford to lose.
In this guide I will show you how to create a cryptocurrency trading bot with Go. I highly recommend an IDE such as Goland but you can use a simple text editor if you would like. Feel free to take a look at my guide on how to develop with Go on Linux if you don’t have Go installed.
With that out of the way we can move on to the good part. I will be creating my own index, a very simple index, which goes up or down depending on the quantity of sell and buy orders placed. Basically I will only look at orders that are 2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} above or below the latest fill order price and use those to make an index. I will use Bittrex for trading as it offers a lot of trade pairs and will focus on BTC-VTC as I believe in both coins and would like to hold either no matter what my bot does. My index is basically order rate relative to last price multiplied by the number of VTC the order is for. If the latest price is 0.000100 and a buy order for 10000 VTC is placed at 0.000102 my index would go up by 10200 points.
(0.000102/0.000100)*10000 = 10200
This is a really simple index and someone bidding 10000 VTC 2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} higher than latest fill price should maybe have a larger impact? However, to keep things simple I decided to just go with this implementation. If someone was to sell 10000 for 2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} less than latest price my index would decrease by 10200 in the same way.
Ah and did I mention that my index resets every minute? I wanted my bot to make minute by minute decisions so I reset the index to 0 every minute. But enough details, let’s get started with some code.
I used a library called go-bittrex but the current version does not support websockets which will give us real time data. There is a fork of go-bittrex that implements a function for websockets and I have decided to go with that instead. In addition to this library you will also need an API key and secret which you will find on the Settings page under the menu API Keys. I activated everything except Withdraw capabilities just in case my keys get compromised. Once you have your keys we can take a look at the example code on the go-bittrex project page:
package main import ( "fmt" "github.com/toorop/go-bittrex" ) const ( API_KEY = "YOUR_API_KEY" API_SECRET = "YOUR_API_SECRET" ) func main() { // Bittrex client bittrex := bittrex.New(API_KEY, API_SECRET) // Get markets markets, err := bittrex.GetMarkets() fmt.Println(err, markets) }
If you run the code above you should see a long list of all the key-pairs.
[{LTC BTC Litecoin Bitcoin 0.01435906 BTC-LTC true false https://bittrexblobstorage.blob.core.windows.net/public/6defbc41-582d-47a6-bb2e-d0fa88663524.png} {DOGE...
Now, it might look a bit weird and that’s because we are simply using print on our object called markets. If we actually took the time to loop through our markets we could take the pieces we want and format it in a better, more readable way. Let’s try to get our account balances and format it a bit better. Make sure you have replaced your api keys with YOUR_API_KEY
and YOUR_API_SECRET
in the script above. We will replace the code under //Get markets
with our own code. My changed code looks like this:
// Get markets // Get balances balances, err := bittrex.GetBalances() if err == nil { for _, b := range balances { fmt.Println(b.Currency, b.Available, "/", b.Balance, " Deposit:", b.CryptoAddress) } } else { fmt.Println(err) }
This code will loop through all the balances in our account and print the cryptocurrency, how much we have available for trading, how much we have in total and our deposit address if there is one. If you don’t see any balances it could be because you don’t have anything in your account or you might have the wrong api keys. The output for my account looks like the following:
ARK 0 / 0 Deposit: BTC 0.01160229 / 0.01160229 Deposit: 1KqNRbFyi9b2Ej248BMnskCcR82Vdenk7a BTG 0 / 0 Deposit: GYi1VdAJ892Dwkx5JgRHeXUyMk9PYhBLWW CVC 0 / 0 Deposit: LTC 0 / 0 Deposit: LKPnskMDSsKyPMyPibGDVcFgQiqJme1igR NAV 0 / 0 Deposit: QRL 0 / 0 Deposit: RISE 0 / 0 Deposit: SC 0 / 0 Deposit: SNT 0 / 0 Deposit: SYS 0 / 0 Deposit: UBQ 0 / 0 Deposit: VTC 522.26812636 / 522.26812636 Deposit: XVG 0 / 0 Deposit:
You can take a look at the bittrex object to see what other methods are available, I doubt I have had a chance to explore them all.
The Code of a Cryptocurrency Trading Bot
When I set up my trading bot I added a few constants that work with my index to trigger sells or buys and also limits how much I need to gain or lose before selling, buying or cancelling an order. Here are my constants:
const ( API_KEY = "" API_SECRET = "" BUY_STRING = "BTC" SELL_STRING = "VTC" MARKET_STRING = BUY_STRING + "-" + SELL_STRING MIN_GAIN = 0.02 MAX_LOSS = 0.02 ORDER_RANGE = 0.02 BUY_TRIGGER = 10000.0 SELL_TRIGGER = -10000.0 ORDER_VARIANCE = 0.02 )
I also need some variables that will keep track of balances, orders, prices, my index, if I have an open order and so on. I also added a bool called readyToRun
which is set to false for the first 60 seconds as my script gathers data and it also disables all trading if I lose connection to Bittrex. I also added a highIndex
and a lowIndex
to get an idea for what the max and min are in one minute for my index.
var ( balances []bittrex.Balance orders []bittrex.Order ticker = bittrex.Ticker{} lastPrice float64 lastBuyPrice = 0.00 buySellIndex = 0.00 openOrder = false readyToRun = false highIndex = 0.00 lowIndex = 0.00 )
The main function is what is initially called when you start running my program. This function will call all other functions.
func main() { // Bittrex client bittrexClient := bittrex.New(API_KEY, API_SECRET) go updateStats(bittrexClient) // A Simple Trading Strategy // We create our own buy/sell index, this resets with every buy and sell // If we buy and incur a 2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} loss we sell at current ask // If we buy and make at least a 2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} profit and our index is sell we sell // If we place an order and it does not clear and market moves -+2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} we cancel // Every trade has a 0.25{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} fee ch := make(chan bittrex.ExchangeState, 16) go subscribeMarket(bittrexClient, ch) for st := range ch { // Order placed for _, b := range st.Buys { //log.Println("Buy: ", b.Quantity, " for ", b.Rate, " as ", b.Type) quantity, _ := b.Quantity.Float64() rate, _ := b.Rate.Float64() calculateIndex(true, quantity, rate) } for _, s := range st.Sells { //log.Println("Sell: ", s.Quantity, " for ", s.Rate, " as ", s.Type) quantity, _ := s.Quantity.Float64() rate, _ := s.Rate.Float64() calculateIndex(false, quantity, rate) } // Order actually fills for _, f := range st.Fills { //log.Println("Fill: ", f.Quantity, " for ", f.Rate, " as ", f.OrderType) // We could say that lastPrice is technically the fill price lastPrice, _ = f.Rate.Float64() } log.Printf("BuySellIndex: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", buySellIndex) decideBuySell(bittrexClient) } }
Here we create a new bittrex client with our api keys and then use this to perform further actions. We call updateStats
as a goroutine, this means it will run asynchronously with the rest of our program. To learn more about goroutines I would recommend checking out Go By Example. The rest of the code will be executed no matter what happens with updateStats. The next thing we do is to make a channel to handle messages received from Bittrex over our websocket connection. The subscribeMarket
function will set up a websocket connection that uses the channel that was created and our for loop at the end of the script will handle any incoming messages. The messages I care about are buys, sells and fills. Buys and sells are just orders, a fill is when one of these orders goes through making it “official” so to speak. I use the buys and sells to calculate my index using the calculateIndex
function. Any fill is used to update or lastPrice so that we are always acting with the most recent price when deciding to buy or sell. Finally we decide if we should buy or sell anything.
func subscribeMarket(b *bittrex.Bittrex, ch chan bittrex.ExchangeState) { log.Println("Connecting to:", MARKET_STRING) err := b.SubscribeExchangeUpdate(MARKET_STRING, ch, nil) if err != nil { log.Println("Error:", err) } log.Println("Reconnecting....") go subscribeMarket(b, ch) }
The above code handles our websocket connection via the bittrex-go library. If it should end or error for some reason we try reconnecting. This function is poorly written but works. I would sometimes lose the websocket connection and might need to come back and update this function if I figure out how to reconnect in a better way.
To buy or to Sell?
func decideBuySell(b *bittrex.Bittrex) { if openOrder { // Should we close the open order? for _, o := range orders { ppu, _ := o.PricePerUnit.Float64() log.Printf("Order percent: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", ppu/lastPrice) if ppu/lastPrice > (1.00+ORDER_VARIANCE) || ppu/lastPrice < (1.00-ORDER_VARIANCE) { log.Println("Canceled order: ", o.OrderUuid) b.CancelOrder(o.OrderUuid) // We assume we only have one order at a time } } } // If we have no open order should we buy or sell? if !openOrder { if buySellIndex > BUY_TRIGGER { log.Println("BUY TRIGGER ACTIVE!") for _, bals := range balances { bal, _ := bals.Balance.Float64() if BUY_STRING == bals.Currency { //log.Printf("Bal: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s == {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s\n", bal/lastPrice, SELL_STRING, bals.Currency) } if bal > 0.01 && BUY_STRING == bals.Currency && lastPrice > 0.00 { // Place buy order log.Printf("Placed buy order of {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s at {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.8f\n=================================================\n", (bal/lastPrice)-5, BUY_STRING, lastPrice) order, err := b.BuyLimit(MARKET_STRING, decimal.NewFromFloat((bal/lastPrice)-5), decimal.NewFromFloat(lastPrice)) if err != nil { log.Println("ERROR ", err) } else { log.Println("Confirmed: ", order) } lastBuyPrice = lastPrice openOrder = true } } } else if buySellIndex < SELL_TRIGGER { log.Println("SELL TRIGGER ACTIVE!") for _, bals := range balances { bal, _ := bals.Balance.Float64() if SELL_STRING == bals.Currency { //allow := "false" //if allowSell() { // allow = "true" //} //log.Printf("Bal: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s == {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s && {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s\n", bal, BUY_STRING, bals.Currency, allow) } if bal > 0.01 && SELL_STRING == bals.Currency && lastPrice > 0.00 && allowSell() { // Place sell order log.Printf("Placed sell order of {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s at {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.8f\n=================================================\n", bal, BUY_STRING, lastPrice) order, err := b.SellLimit(MARKET_STRING, decimal.NewFromFloat(bal), decimal.NewFromFloat(lastPrice)) if err != nil { log.Println("ERROR ", err) } else { log.Println("Confirmed: ", order) } openOrder = true } } } } } func allowSell() bool { if lastBuyPrice > 0 { gain := lastPrice / lastBuyPrice if gain < (1.00 - MAX_LOSS) { return true } if gain < (1.00 + MIN_GAIN) { return false } } return true }
The functions above is a big part of the simple algorithm I created. The first part of the function checks if we have an open order. I decided that if I place an order and the market moves +- 2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} then I want to cancel any order because it will most likely never fill. The 2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} might be a bit high but it works well as a safety measure as most of my orders tend to fill quickly. Buying or selling is only triggered if my index hits 10000 or -10000 and then some additional checks are run. I implemented the allowSell
function to make sure that my bot only sells if there is a gain or loss of more than the Bittrex trading fee of 0.25{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}. In my code I set the margin to be at least +-2{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.
func calculateIndex(buy bool, q float64, r float64) { // q is quantity VTC // r is the rate percent := 0.00 // Calculate percentage of rate if r > 0 && q > 0 && lastPrice > 0 && readyToRun { percent = lastPrice / r if buy { //log.Printf("Buy percent: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", percent) //log.Printf("Buy quantity: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", q) if percent > (1.00 - ORDER_RANGE) && percent < (1.00 + ORDER_RANGE) { buySellIndex = buySellIndex + (percent * q) } } else { //log.Printf("Sell percent: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", percent) //log.Printf("Sell quantity: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", q) if percent > (1.00 - ORDER_RANGE) && percent < (1.00 + ORDER_RANGE) { percent = percent - 2.00 // Reverse percent, lower is higher buySellIndex = buySellIndex + (percent * q) } } } if buySellIndex > highIndex { highIndex = buySellIndex } if buySellIndex < lowIndex { lowIndex = buySellIndex } // Reset really high or low numbers due to startup if highIndex > 5000000.00 || lowIndex < -5000000.00 { highIndex = 0.00 lowIndex = 0.00 buySellIndex = 0.00 } }
Here is the calculateIndex
function which does exactly what it says. It is what my bot uses to decide to buy or sell. At the bottom I implemented a reset for my indexes as I would sometimes see very high or low values. This happend when launching my program as a lot of orders come through the websocket right away. This is also why my program waits 60 seconds to start. My index is calculated every time there is a message via the websocket. This can happen several times per second and on each update my program will decide to buy/sell. This allows the bot to be very responsive and act instantly.
func updateStats(b *bittrex.Bittrex) { var err error = nil for { go func(b *bittrex.Bittrex) { balances, err = b.GetBalances() orders, err = b.GetOpenOrders(MARKET_STRING) ticker, err = b.GetTicker(MARKET_STRING) if err != nil { log.Println("Error:", err) // Pause calculations in case of error readyToRun = false } log.Printf("====================================\n") log.Printf("Last price: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}v\n", ticker.Last) log.Printf("Index: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", buySellIndex) log.Printf("High Index: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", highIndex) log.Printf("Low Index: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}.4f\n", lowIndex) lastPrice, _ = ticker.Last.Float64() buySellIndex = 0.00 log.Printf("Bid: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}v\n", ticker.Bid) log.Printf("Ask: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}v\n", ticker.Ask) // Do we have an open order? openOrder = len(orders) > 0 for _, o := range orders { log.Println("Pending order: ", o.OrderType, " Quanitity: ", o.QuantityRemaining, "/", o.Quantity, " Price: ", o.PricePerUnit) } // Where do we have balances for _, b := range balances { bal, _ := b.Balance.Float64() if bal > 0.00 { log.Printf("{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s: {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}v {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}s {25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a}v\n", b.Currency, b.Available, "/", b.Balance) } } log.Printf("====================================\n") }(b) <-time.After(60 * time.Second) // Wait 60 to init and collect data readyToRun = true } }
Finally we have the updateStats
function. This function loops every 60 seconds and updates the orders, balances, last price and resets my index to 0. I also configured it to print out useful data for tweaking my variables or debugging the application.
All of the code I have shared with you above is available on Github and is MIT licensed so that you are free to take the code and modify it for your own needs. I also recommend testing some of the calculations in a scratch file, I made an example scratch file that I used to make sure my calculations did what I thought they would.
This seemed like a good topic to write about. I hope my example will help someone else learn. I would love to hear your feedback, criticism and hopefully improvements to the code in the comments below.
Thanks for reading!
Update: Turns out the first cryptocurrency trading bot I ever made was not profitable at all. Initially it made some good trades but then it kept losing money. I tried to tweak my current algorithm and ran it for a week loosing 30{25926e1ae733c694be1fca7aad2c08379e63fd61e8d02131996d4c61b85a2b0a} or about 300 euro. At the moment I shut the bot down and have plans to make a new, better trading bot in the future.
Great article! I found it to be very useful. I found your article because I’m looking to accomplish the same task, using the same language. The code itself is pretty straightforward, however, I’m curious as to what resources you used to create your trading algorithm? In my opinion, by better understanding those concepts at a fundamental level, I’ll be more prepared at fine tuning it, based on my results. I’m just not sure where to begin. Also, did you ever update the code, as you mentioned in your update? Thanks for taking the time out to write this article. It was very helpful.
I would advise the bot I made which supports multiple exchanges
https://github.com/saniales/golang-crypto-trading-bot
🙂 Good work here mate.