Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Kelp UI: show botInfo when bot is not running, instead of ? values (#203
Browse files Browse the repository at this point in the history
)

addresses part of issue #67

* 1 - extract call to getBotInfo via IPC in a separate function

* 2 - started fetching getBotInfo directly with temp data

* 3 - rename gui/model to gui/model2 to avoid conflicts with model pacakge

* 4 - fill in balances in getBotInfo

* 5 - fill in numBids and numAsks in getBotInfo

* 6 - fill in logic for spreads

* 7 - getBotInfo should wait when bot is initializing
  • Loading branch information
nikhilsaraf authored Jul 23, 2019
1 parent a574558 commit d22bcfe
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 29 deletions.
8 changes: 8 additions & 0 deletions gui/backend/api_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"path/filepath"

"github.com/stellar/go/clients/horizonclient"
"github.com/stellar/kelp/support/kelpos"
)

Expand All @@ -19,6 +20,8 @@ type APIServer struct {
configsDir string
logsDir string
kos *kelpos.KelpOS
apiTestNet *horizonclient.Client
apiPubNet *horizonclient.Client
}

// MakeAPIServer is a factory method
Expand All @@ -32,12 +35,17 @@ func MakeAPIServer(kos *kelpos.KelpOS) (*APIServer, error) {
configsDir := dirPath + "/ops/configs"
logsDir := dirPath + "/ops/logs"

apiTestNet := horizonclient.DefaultTestNetClient
apiPubNet := horizonclient.DefaultPublicNetClient

return &APIServer{
dirPath: dirPath,
binPath: binPath,
configsDir: configsDir,
logsDir: logsDir,
kos: kos,
apiTestNet: apiTestNet,
apiPubNet: apiPubNet,
}, nil
}

Expand Down
4 changes: 2 additions & 2 deletions gui/backend/autogenerate_bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/stellar/go/clients/horizon"
"github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/keypair"
"github.com/stellar/kelp/gui/model"
"github.com/stellar/kelp/gui/model2"
"github.com/stellar/kelp/plugins"
"github.com/stellar/kelp/support/kelpos"
"github.com/stellar/kelp/support/networking"
Expand All @@ -34,7 +34,7 @@ func (s *APIServer) autogenerateBot(w http.ResponseWriter, r *http.Request) {
}

// make and register bot, which places it in the initial bot state
bot := model.MakeAutogeneratedBot()
bot := model2.MakeAutogeneratedBot()
e = s.kos.RegisterBot(bot)
if e != nil {
s.writeError(w, fmt.Sprintf("error registering bot: %s\n", e))
Expand Down
4 changes: 2 additions & 2 deletions gui/backend/delete_bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"net/http"
"time"

"github.com/stellar/kelp/gui/model"
"github.com/stellar/kelp/gui/model2"
"github.com/stellar/kelp/support/kelpos"
)

Expand Down Expand Up @@ -51,7 +51,7 @@ func (s *APIServer) deleteBot(w http.ResponseWriter, r *http.Request) {
s.kos.SafeUnregisterBot(botName)

// delete configs
botPrefix := model.GetPrefix(botName)
botPrefix := model2.GetPrefix(botName)
_, e = s.kos.Blocking("rm", fmt.Sprintf("rm %s/%s*", s.configsDir, botPrefix))
if e != nil {
s.writeError(w, fmt.Sprintf("error running rm command for bot configs: %s\n", e))
Expand Down
7 changes: 3 additions & 4 deletions gui/backend/get_bot_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import (
"log"
"net/http"

"github.com/stellar/kelp/plugins"

"github.com/stellar/go/support/config"
"github.com/stellar/kelp/gui/model"
"github.com/stellar/kelp/gui/model2"
"github.com/stellar/kelp/plugins"
"github.com/stellar/kelp/trader"
)

Expand All @@ -27,7 +26,7 @@ func (s *APIServer) getBotConfig(w http.ResponseWriter, r *http.Request) {
return
}

filenamePair := model.GetBotFilenames(botName, "buysell")
filenamePair := model2.GetBotFilenames(botName, "buysell")
traderFilePath := fmt.Sprintf("%s/%s", s.configsDir, filenamePair.Trader)
var botConfig trader.BotConfig
e = config.Read(traderFilePath, &botConfig)
Expand Down
172 changes: 171 additions & 1 deletion gui/backend/get_bot_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,35 @@ import (
"fmt"
"log"
"net/http"
"strconv"
"strings"

"github.com/stellar/go/clients/horizon"
"github.com/stellar/go/clients/horizonclient"
hProtocol "github.com/stellar/go/protocols/horizon"
"github.com/stellar/go/support/config"
"github.com/stellar/kelp/gui/model2"
"github.com/stellar/kelp/model"
"github.com/stellar/kelp/query"
"github.com/stellar/kelp/support/kelpos"
"github.com/stellar/kelp/support/utils"
"github.com/stellar/kelp/trader"
)

const buysell = "buysell"

func (s *APIServer) getBotInfo(w http.ResponseWriter, r *http.Request) {
botName, e := s.parseBotName(r)
if e != nil {
s.writeError(w, fmt.Sprintf("error parsing bot name in getBotInfo: %s\n", e))
return
}

// s.runGetBotInfoViaIPC(w, botName)
s.runGetBotInfoDirect(w, botName)
}

func (s *APIServer) runGetBotInfoViaIPC(w http.ResponseWriter, botName string) {
p, exists := s.kos.GetProcess(botName)
if !exists {
log.Printf("kelp bot process with name '%s' does not exist; processes available: %v\n", botName, s.kos.RegisteredProcesses())
Expand All @@ -39,7 +56,7 @@ func (s *APIServer) getBotInfo(w http.ResponseWriter, r *http.Request) {
output += text
}
var buf bytes.Buffer
e = json.Indent(&buf, []byte(output), "", " ")
e := json.Indent(&buf, []byte(output), "", " ")
if e != nil {
log.Printf("cannot indent json response (error=%s), json_response: %s\n", e, output)
w.WriteHeader(http.StatusInternalServerError)
Expand All @@ -51,3 +68,156 @@ func (s *APIServer) getBotInfo(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write(buf.Bytes())
}

func (s *APIServer) runGetBotInfoDirect(w http.ResponseWriter, botName string) {
log.Printf("getBotInfo is invoking logic directly for botName: %s\n", botName)

botState, e := s.doGetBotState(botName)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("cannot read bot state for bot '%s': %s\n", botName, e))
return
}
if botState == kelpos.BotStateInitializing {
log.Printf("bot state is initializing for bot '%s'\n", botName)
w.WriteHeader(http.StatusOK)
w.Write([]byte("{}"))
return
}

filenamePair := model2.GetBotFilenames(botName, buysell)
traderFilePath := fmt.Sprintf("%s/%s", s.configsDir, filenamePair.Trader)
var botConfig trader.BotConfig
e = config.Read(traderFilePath, &botConfig)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("cannot read bot config at path '%s': %s\n", traderFilePath, e))
return
}
e = botConfig.Init()
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("cannot init bot config at path '%s': %s\n", traderFilePath, e))
return
}

assetBase := botConfig.AssetBase()
assetQuote := botConfig.AssetQuote()
tradingPair := &model.TradingPair{
Base: model.Asset(utils.Asset2CodeString(assetBase)),
Quote: model.Asset(utils.Asset2CodeString(assetQuote)),
}
account, e := s.apiTestNet.AccountDetail(horizonclient.AccountRequest{AccountID: botConfig.TradingAccount()})
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("cannot get account data for account '%s' for botName '%s': %s\n", botConfig.TradingAccount(), botName, e))
return
}
var balanceBase float64
if assetBase == utils.NativeAsset {
balanceBase, e = getNativeBalance(account)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error getting native balanceBase for account '%s' for botName '%s': %s\n", botConfig.TradingAccount(), botName, e))
return
}
} else {
balanceBase, e = getCreditBalance(account, assetBase)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error getting credit balanceBase for account '%s' for botName '%s': %s\n", botConfig.TradingAccount(), botName, e))
return
}
}
var balanceQuote float64
if assetQuote == utils.NativeAsset {
balanceQuote, e = getNativeBalance(account)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error getting native balanceQuote for account '%s' for botName '%s': %s\n", botConfig.TradingAccount(), botName, e))
return
}
} else {
balanceQuote, e = getCreditBalance(account, assetQuote)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error getting credit balanceQuote for account '%s' for botName '%s': %s\n", botConfig.TradingAccount(), botName, e))
return
}
}

offers, e := utils.LoadAllOffers(account.AccountID, horizon.DefaultTestNetClient)
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error getting offers for account '%s' for botName '%s': %s\n", botConfig.TradingAccount(), botName, e))
return
}
sellingAOffers, buyingAOffers := utils.FilterOffers(offers, assetBase, assetQuote)
numBids := len(buyingAOffers)
numAsks := len(sellingAOffers)

obs, e := s.apiTestNet.OrderBook(horizonclient.OrderBookRequest{
SellingAssetType: horizonclient.AssetType(assetBase.Type),
SellingAssetCode: assetBase.Code,
SellingAssetIssuer: assetBase.Issuer,
BuyingAssetType: horizonclient.AssetType(assetQuote.Type),
BuyingAssetCode: assetQuote.Code,
BuyingAssetIssuer: assetQuote.Issuer,
Limit: 1,
})
if e != nil {
s.writeErrorJson(w, fmt.Sprintf("error getting orderbook for assets (base=%v, quote=%v) for botName '%s': %s\n", assetBase, assetQuote, botName, e))
return
}
spread := -1.0
spreadPct := -1.0
if len(obs.Asks) > 0 && len(obs.Bids) > 0 {
topAsk := float64(obs.Asks[0].PriceR.N) / float64(obs.Asks[0].PriceR.D)
topBid := float64(obs.Bids[0].PriceR.N) / float64(obs.Bids[0].PriceR.D)

spread = topAsk - topBid
midPrice := (topAsk + topBid) / 2
spreadPct = spread / midPrice
}

bi := query.BotInfo{
Strategy: buysell,
TradingPair: tradingPair,
AssetBase: assetBase,
AssetQuote: assetQuote,
BalanceBase: balanceBase,
BalanceQuote: balanceQuote,
NumBids: numBids,
NumAsks: numAsks,
SpreadValue: model.NumberFromFloat(spread, 8).AsFloat(),
SpreadPercent: model.NumberFromFloat(spreadPct, 8).AsFloat(),
}

marshalledJson, e := json.MarshalIndent(bi, "", " ")
if e != nil {
log.Printf("cannot marshall to json response (error=%s), BotInfo: %+v\n", e, bi)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("{}"))
return
}
marshalledJsonString := string(marshalledJson)
log.Printf("getBotInfo returned direct response for botName '%s': %s\n", botName, marshalledJsonString)

w.WriteHeader(http.StatusOK)
w.Write(marshalledJson)
}

func getNativeBalance(account hProtocol.Account) (float64, error) {
balanceString, e := account.GetNativeBalance()
if e != nil {
return 0.0, fmt.Errorf("cannot get native balance: %s\n", e)
}

balance, e := strconv.ParseFloat(balanceString, 64)
if e != nil {
return 0.0, fmt.Errorf("cannot parse native balance: %s (string value = %s)\n", e, balanceString)
}

return balance, nil
}

func getCreditBalance(account hProtocol.Account, asset horizon.Asset) (float64, error) {
balanceString := account.GetCreditBalance(asset.Code, asset.Issuer)
balance, e := strconv.ParseFloat(balanceString, 64)
if e != nil {
return 0.0, fmt.Errorf("cannot parse credit asset balance (%s:%s): %s (string value = %s)\n", asset.Code, asset.Issuer, e, balanceString)
}

return balance, nil
}
6 changes: 3 additions & 3 deletions gui/backend/list_bots.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"net/http"
"strings"

"github.com/stellar/kelp/gui/model"
"github.com/stellar/kelp/gui/model2"
"github.com/stellar/kelp/support/kelpos"
)

Expand All @@ -21,10 +21,10 @@ func (s *APIServer) listBots(w http.ResponseWriter, r *http.Request) {
configFiles := string(resultBytes)
files := strings.Split(configFiles, "\n")

bots := []model.Bot{}
bots := []model2.Bot{}
// run till one less than length of files because the last name will end in a newline
for i := 0; i < len(files)-1; i += 2 {
bot := model.FromFilenames(files[i+1], files[i])
bot := model2.FromFilenames(files[i+1], files[i])
bots = append(bots, *bot)
}
log.Printf("bots available: %v", bots)
Expand Down
6 changes: 3 additions & 3 deletions gui/backend/start_bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"os/exec"
"strings"

"github.com/stellar/kelp/gui/model"
"github.com/stellar/kelp/gui/model2"
"github.com/stellar/kelp/support/kelpos"
)

Expand Down Expand Up @@ -36,8 +36,8 @@ func (s *APIServer) startBot(w http.ResponseWriter, r *http.Request) {
}

func (s *APIServer) doStartBot(botName string, strategy string, iterations *uint8, maybeFinishCallback func()) error {
filenamePair := model.GetBotFilenames(botName, strategy)
logPrefix := model.GetLogPrefix(botName, strategy)
filenamePair := model2.GetBotFilenames(botName, strategy)
logPrefix := model2.GetLogPrefix(botName, strategy)
command := fmt.Sprintf("trade -c %s/%s -s %s -f %s/%s -l %s/%s --with-ipc", s.configsDir, filenamePair.Trader, strategy, s.configsDir, filenamePair.Strategy, s.logsDir, logPrefix)
if iterations != nil {
command = fmt.Sprintf("%s --iter %d", command, *iterations)
Expand Down
6 changes: 3 additions & 3 deletions gui/backend/upsert_bot_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/stellar/go/clients/horizon"
"github.com/stellar/go/keypair"
"github.com/stellar/go/strkey"
"github.com/stellar/kelp/gui/model"
"github.com/stellar/kelp/gui/model2"
"github.com/stellar/kelp/plugins"
"github.com/stellar/kelp/support/kelpos"
"github.com/stellar/kelp/support/toml"
Expand Down Expand Up @@ -78,7 +78,7 @@ func (s *APIServer) upsertBotConfig(w http.ResponseWriter, r *http.Request) {
return
}

filenamePair := model.GetBotFilenames(req.Name, req.Strategy)
filenamePair := model2.GetBotFilenames(req.Name, req.Strategy)
traderFilePath := fmt.Sprintf("%s/%s", s.configsDir, filenamePair.Trader)
botConfig := req.TraderConfig
log.Printf("upsert bot config to file: %s\n", traderFilePath)
Expand Down Expand Up @@ -160,7 +160,7 @@ func hasNewLevel(levels []plugins.StaticLevel) bool {

func (s *APIServer) reinitBotCheck(req upsertBotConfigRequest) {
isTestnet := strings.Contains(req.TraderConfig.HorizonURL, "test")
bot := &model.Bot{
bot := &model2.Bot{
Name: req.Name,
Strategy: req.Strategy,
Running: false,
Expand Down
2 changes: 1 addition & 1 deletion gui/model/bot.go → gui/model2/bot.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package model
package model2

import (
"fmt"
Expand Down
7 changes: 4 additions & 3 deletions query/botInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (
"github.com/stellar/kelp/support/utils"
)

type botInfo struct {
// BotInfo is the response from the getBotInfo IPC request
type BotInfo struct {
Strategy string `json:"strategy"`
TradingPair *model.TradingPair `json:"trading_pair"`
AssetBase horizon.Asset `json:"asset_base"`
Expand All @@ -21,7 +22,7 @@ type botInfo struct {
SpreadPercent float64 `json:"spread_pct"`
}

func (s *Server) getBotInfo() (*botInfo, error) {
func (s *Server) getBotInfo() (*BotInfo, error) {
assetBase, assetQuote, e := s.sdex.Assets()
if e != nil {
return nil, fmt.Errorf("error getting assets from sdex: %s", e)
Expand Down Expand Up @@ -60,7 +61,7 @@ func (s *Server) getBotInfo() (*botInfo, error) {
spreadPct = spreadValue.Divide(*midPrice)
}

return &botInfo{
return &BotInfo{
Strategy: s.strategyName,
TradingPair: s.tradingPair,
AssetBase: assetBase,
Expand Down
Loading

0 comments on commit d22bcfe

Please sign in to comment.