Expand portfolio to cover exchange balances

This commit is contained in:
Adrian Gallagher
2017-04-05 15:49:09 +10:00
parent a5d7d26ac9
commit f6efa9ee37
10 changed files with 212 additions and 25 deletions

View File

@@ -88,7 +88,7 @@ func (e *Bitfinex) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.CurrencyName = common.StringToUpper(accountBalance[i].Currency)
exchangeCurrency.TotalValue = accountBalance[i].Amount
exchangeCurrency.Hold = accountBalance[i].Available

View File

@@ -75,17 +75,28 @@ func (e *Bitstamp) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error
return response, err
}
var btcExchangeInfo exchange.ExchangeAccountCurrencyInfo
btcExchangeInfo.CurrencyName = "BTC"
btcExchangeInfo.TotalValue = accountBalance.BTCBalance
btcExchangeInfo.Hold = accountBalance.BTCReserved
response.Currencies = append(response.Currencies, btcExchangeInfo)
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
CurrencyName: "BTC",
TotalValue: accountBalance.BTCAvailable,
Hold: accountBalance.BTCReserved,
})
var usdExchangeInfo exchange.ExchangeAccountCurrencyInfo
usdExchangeInfo.CurrencyName = "USD"
usdExchangeInfo.TotalValue = accountBalance.USDBalance
usdExchangeInfo.Hold = accountBalance.USDReserved
response.Currencies = append(response.Currencies, usdExchangeInfo)
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
CurrencyName: "XRP",
TotalValue: accountBalance.XRPAvailable,
Hold: accountBalance.XRPReserved,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
CurrencyName: "USD",
TotalValue: accountBalance.USDAvailable,
Hold: accountBalance.USDReserved,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
CurrencyName: "EUR",
TotalValue: accountBalance.EURAvailable,
Hold: accountBalance.EURReserved,
})
return response, nil
}

View File

@@ -11,6 +11,7 @@ import (
const (
WarningBase64DecryptSecretKeyFailed = "WARNING -- Exchange %s unable to base64 decode secret key.. Disabling Authenticated API support."
ErrExchangeNotFound = "Exchange not found in dataset."
)
//ExchangeAccountInfo : Generic type to hold each exchange's holdings in all enabled currencies

View File

@@ -70,7 +70,7 @@ func (e *GDAX) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.TotalValue = accountBalance[i].Balance
exchangeCurrency.TotalValue = accountBalance[i].Available
exchangeCurrency.Hold = accountBalance[i].Hold
response.Currencies = append(response.Currencies, exchangeCurrency)

View File

@@ -131,16 +131,19 @@ type OKCoinUserInfo struct {
BTC float64 `json:"btc,string"`
LTC float64 `json:"ltc,string"`
USD float64 `json:"usd,string"`
CNY float64 `json:"cny,string"`
} `json:"borrow"`
Free struct {
BTC float64 `json:"btc,string"`
LTC float64 `json:"ltc,string"`
USD float64 `json:"usd,string"`
CNY float64 `json:"cny,string"`
} `json:"free"`
Freezed struct {
BTC float64 `json:"btc,string"`
LTC float64 `json:"ltc,string"`
USD float64 `json:"usd,string"`
CNY float64 `json:"cny,string"`
} `json:"freezed"`
UnionFund struct {
BTC float64 `json:"btc,string"`

View File

@@ -94,10 +94,37 @@ func (o *OKCoin) GetTickerPrice(currency string) (ticker.TickerPrice, error) {
return tickerPrice, nil
}
//TODO support for retrieving holdings from OKCOIN
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the OKCoin exchange
func (e *OKCoin) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
assets, err := e.GetUserInfo()
if err != nil {
return response, err
}
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
CurrencyName: "BTC",
TotalValue: assets.Info.Funds.Free.BTC,
Hold: assets.Info.Funds.Freezed.BTC,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
CurrencyName: "LTC",
TotalValue: assets.Info.Funds.Free.LTC,
Hold: assets.Info.Funds.Freezed.LTC,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
CurrencyName: "USD",
TotalValue: assets.Info.Funds.Free.USD,
Hold: assets.Info.Funds.Freezed.USD,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
CurrencyName: "CNY",
TotalValue: assets.Info.Funds.Free.CNY,
Hold: assets.Info.Funds.Freezed.CNY,
})
return response, nil
}

View File

@@ -76,11 +76,11 @@ func (e *Poloniex) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error
if err != nil {
return response, err
}
currencies := e.AvailablePairs
for i := 0; i < len(currencies); i++ {
for x, y := range accountBalance.Currency {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
exchangeCurrency.CurrencyName = currencies[i]
exchangeCurrency.TotalValue = accountBalance.Currency[currencies[i]]
exchangeCurrency.CurrencyName = x
exchangeCurrency.TotalValue = y
response.Currencies = append(response.Currencies, exchangeCurrency)
}
return response, nil

30
main.go
View File

@@ -151,6 +151,7 @@ func main() {
bot.portfolio = &portfolio.Portfolio
bot.portfolio.SeedPortfolio(bot.config.Portfolio)
SeedExchangeAccountInfo(GetAllEnabledExchangeAccountInfo().Data)
go portfolio.StartPortfolioWatcher()
if bot.config.Webserver.Enabled {
@@ -217,3 +218,32 @@ func Shutdown() {
log.Println("Exiting.")
os.Exit(1)
}
func SeedExchangeAccountInfo(data []exchange.ExchangeAccountInfo) {
if len(data) == 0 {
return
}
port := portfolio.GetPortfolio()
for i := 0; i < len(data); i++ {
exchangeName := data[i].ExchangeName
for j := 0; j < len(data[i].Currencies); j++ {
currencyName := data[i].Currencies[j].CurrencyName
onHold := data[i].Currencies[j].Hold
avail := data[i].Currencies[j].TotalValue
total := onHold + avail
if total <= 0 {
continue
}
if !port.ExchangeAddressExists(exchangeName, currencyName) {
port.Addresses = append(port.Addresses, portfolio.PortfolioAddress{Address: exchangeName, CoinType: currencyName, Balance: total, Decscription: portfolio.PORTFOLIO_ADDRESS_EXCHANGE})
} else {
port.UpdateExchangeAddressBalance(exchangeName, currencyName, total)
}
}
}
}

View File

@@ -23,9 +23,10 @@ const (
var Portfolio PortfolioBase
type PortfolioAddress struct {
Address string
CoinType string
Balance float64
Address string
CoinType string
Balance float64
Decscription string
}
type PortfolioBase struct {
@@ -65,6 +66,19 @@ type EtherchainBalanceResponse struct {
} `json:"data"`
}
//ExchangeAccountInfo : Generic type to hold each exchange's holdings in all enabled currencies
type ExchangeAccountInfo struct {
ExchangeName string
Currencies []ExchangeAccountCurrencyInfo
}
//ExchangeAccountCurrencyInfo : Sub type to store currency name and value
type ExchangeAccountCurrencyInfo struct {
CurrencyName string
TotalValue float64
Hold float64
}
func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) {
addresses := common.JoinStrings(address, ",")
url := fmt.Sprintf("%s/%s/%s", ETHERCHAIN_API_URL, ETHERCHAIN_ACCOUNT_MULTIPLE, addresses)
@@ -115,6 +129,15 @@ func (p *PortfolioBase) GetAddressBalance(address string) (float64, bool) {
return 0, false
}
func (p *PortfolioBase) ExchangeExists(exchangeName string) bool {
for _, x := range p.Addresses {
if x.Address == exchangeName {
return true
}
}
return false
}
func (p *PortfolioBase) AddressExists(address string) bool {
for _, x := range p.Addresses {
if x.Address == address {
@@ -124,6 +147,15 @@ func (p *PortfolioBase) AddressExists(address string) bool {
return false
}
func (p *PortfolioBase) ExchangeAddressExists(exchangeName, coinType string) bool {
for _, x := range p.Addresses {
if x.Address == exchangeName && x.CoinType == coinType {
return true
}
}
return false
}
func (p *PortfolioBase) UpdateAddressBalance(address string, amount float64) {
for x, _ := range p.Addresses {
if p.Addresses[x].Address == address {
@@ -132,7 +164,19 @@ func (p *PortfolioBase) UpdateAddressBalance(address string, amount float64) {
}
}
func (p *PortfolioBase) UpdateExchangeAddressBalance(exchangeName, coinType string, balance float64) {
for x, _ := range p.Addresses {
if p.Addresses[x].Address == exchangeName && p.Addresses[x].CoinType == coinType {
p.Addresses[x].Balance = balance
}
}
}
func (p *PortfolioBase) UpdatePortfolio(addresses []string, coinType string) bool {
if common.StringContains(common.JoinStrings(addresses, ","), PORTFOLIO_ADDRESS_EXCHANGE) || common.StringContains(common.JoinStrings(addresses, ","), PORTFOLIO_ADDRESS_CUSTOM) {
return true
}
if coinType == "ETH" {
result, err := GetEthereumBalance(addresses)
if err != nil {
@@ -174,6 +218,38 @@ func (p *PortfolioBase) UpdatePortfolio(addresses []string, coinType string) boo
return true
}
func (p *PortfolioBase) GetExchangePortfolio() map[string]float64 {
result := make(map[string]float64)
for _, x := range p.Addresses {
if x.Decscription != PORTFOLIO_ADDRESS_EXCHANGE {
continue
}
balance, ok := result[x.CoinType]
if !ok {
result[x.CoinType] = x.Balance
} else {
result[x.CoinType] = x.Balance + balance
}
}
return result
}
func (p *PortfolioBase) GetPersonalPortfolio() map[string]float64 {
result := make(map[string]float64)
for _, x := range p.Addresses {
if x.Decscription == PORTFOLIO_ADDRESS_EXCHANGE {
continue
}
balance, ok := result[x.CoinType]
if !ok {
result[x.CoinType] = x.Balance
} else {
result[x.CoinType] = x.Balance + balance
}
}
return result
}
func (p *PortfolioBase) GetPortfolioSummary(coinFilter string) map[string]float64 {
result := make(map[string]float64)
for _, x := range p.Addresses {
@@ -193,6 +269,9 @@ func (p *PortfolioBase) GetPortfolioSummary(coinFilter string) map[string]float6
func (p *PortfolioBase) GetPortfolioGroupedCoin() map[string][]string {
result := make(map[string][]string)
for _, x := range p.Addresses {
if common.StringContains(x.Decscription, PORTFOLIO_ADDRESS_EXCHANGE) || common.StringContains(x.Decscription, PORTFOLIO_ADDRESS_CUSTOM) {
continue
}
result[x.CoinType] = append(result[x.CoinType], x.Address)
}
return result
@@ -204,7 +283,7 @@ func (p *PortfolioBase) SeedPortfolio(port PortfolioBase) {
func StartPortfolioWatcher() {
addrCount := len(Portfolio.Addresses)
log.Printf("PortfolioWatcher started: Have %d address(es) in portfolio.\n", addrCount)
log.Printf("PortfolioWatcher started: Have %d entries in portfolio.\n", addrCount)
for {
data := Portfolio.GetPortfolioGroupedCoin()
for key, value := range data {

View File

@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"errors"
"log"
"net/http"
@@ -12,9 +13,39 @@ type AllEnabledExchangeAccounts struct {
Data []exchange.ExchangeAccountInfo `json:"data"`
}
func GetAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) {
var response AllEnabledExchangeAccounts
func GetCollatedExchangeAccountInfoByCoin(accounts []exchange.ExchangeAccountInfo) map[string]exchange.ExchangeAccountCurrencyInfo {
result := make(map[string]exchange.ExchangeAccountCurrencyInfo)
for i := 0; i < len(accounts); i++ {
for j := 0; j < len(accounts[i].Currencies); j++ {
currencyName := accounts[i].Currencies[j].CurrencyName
avail := accounts[i].Currencies[j].TotalValue
onHold := accounts[i].Currencies[j].Hold
info, ok := result[currencyName]
if !ok {
accountInfo := exchange.ExchangeAccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail}
result[currencyName] = accountInfo
} else {
info.Hold += onHold
info.TotalValue += avail
result[currencyName] = info
}
}
}
return result
}
func GetAccountCurrencyInfoByExchangeName(accounts []exchange.ExchangeAccountInfo, exchangeName string) (exchange.ExchangeAccountInfo, error) {
for i := 0; i < len(accounts); i++ {
if accounts[i].ExchangeName == exchangeName {
return accounts[i], nil
}
}
return exchange.ExchangeAccountInfo{}, errors.New(exchange.ErrExchangeNotFound)
}
func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts {
var response AllEnabledExchangeAccounts
for _, individualBot := range bot.exchanges {
if individualBot != nil && individualBot.IsEnabled() {
individualExchange, err := individualBot.GetExchangeAccountInfo()
@@ -24,6 +55,11 @@ func GetAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) {
response.Data = append(response.Data, individualExchange)
}
}
return response
}
func SendAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) {
response := GetAllEnabledExchangeAccountInfo()
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
@@ -36,6 +72,6 @@ var WalletRoutes = Routes{
"AllEnabledAccountInfo",
"GET",
"/exchanges/enabled/accounts/all",
GetAllEnabledAccountInfo,
SendAllEnabledAccountInfo,
},
}