mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
Expand portfolio to cover exchange balances
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
30
main.go
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user