mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
Improve portfolio, coverage and tool
This commit is contained in:
@@ -197,19 +197,20 @@ func MakecurrencyPairs(supportedCurrencies string) string {
|
||||
// or vice versa.
|
||||
func ConvertCurrency(amount float64, from, to string) (float64, error) {
|
||||
currency := common.StringToUpper(from + to)
|
||||
if CurrencyStore[currency].Name != currency {
|
||||
|
||||
_, ok := CurrencyStore[currency]
|
||||
if !ok {
|
||||
err := SeedCurrencyData(currency[:len(from)] + "," + currency[len(to):])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
for x, y := range CurrencyStore {
|
||||
if x == currency {
|
||||
return amount * y.Rate, nil
|
||||
}
|
||||
result, ok := CurrencyStore[currency]
|
||||
if !ok {
|
||||
return 0, ErrCurrencyNotFound
|
||||
}
|
||||
return 0, ErrCurrencyNotFound
|
||||
return amount * result.Rate, nil
|
||||
}
|
||||
|
||||
// FetchYahooCurrencyData seeds the variable CurrencyStore; this is a
|
||||
|
||||
@@ -106,24 +106,47 @@ func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase,
|
||||
}
|
||||
|
||||
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Bitfinex exchange
|
||||
func (e *Bitfinex) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
|
||||
func (b *Bitfinex) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
|
||||
var response exchange.AccountInfo
|
||||
response.ExchangeName = e.GetName()
|
||||
accountBalance, err := e.GetAccountBalance()
|
||||
response.ExchangeName = b.GetName()
|
||||
accountBalance, err := b.GetAccountBalance()
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
if !e.Enabled {
|
||||
if !b.Enabled {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(accountBalance); i++ {
|
||||
var exchangeCurrency exchange.AccountCurrencyInfo
|
||||
exchangeCurrency.CurrencyName = common.StringToUpper(accountBalance[i].Currency)
|
||||
exchangeCurrency.TotalValue = accountBalance[i].Amount
|
||||
exchangeCurrency.Hold = accountBalance[i].Available
|
||||
type bfxCoins struct {
|
||||
OnHold float64
|
||||
Available float64
|
||||
}
|
||||
|
||||
accounts := make(map[string]bfxCoins)
|
||||
|
||||
for i := range accountBalance {
|
||||
onHold := accountBalance[i].Amount - accountBalance[i].Available
|
||||
coins := bfxCoins{
|
||||
OnHold: onHold,
|
||||
Available: accountBalance[i].Available,
|
||||
}
|
||||
result, ok := accounts[accountBalance[i].Currency]
|
||||
if !ok {
|
||||
accounts[accountBalance[i].Currency] = coins
|
||||
} else {
|
||||
result.Available += accountBalance[i].Available
|
||||
result.OnHold += onHold
|
||||
accounts[accountBalance[i].Currency] = result
|
||||
}
|
||||
}
|
||||
|
||||
for x, y := range accounts {
|
||||
var exchangeCurrency exchange.AccountCurrencyInfo
|
||||
exchangeCurrency.CurrencyName = common.StringToUpper(x)
|
||||
exchangeCurrency.TotalValue = y.Available + y.OnHold
|
||||
exchangeCurrency.Hold = y.OnHold
|
||||
response.Currencies = append(response.Currencies, exchangeCurrency)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -272,11 +272,10 @@ func (b *BTCMarkets) GetAccountBalance() ([]BTCMarketsAccountBalance, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// All values are returned in Satoshis, even for fiat currencies.
|
||||
for i := range balance {
|
||||
if balance[i].Currency == "LTC" || balance[i].Currency == "BTC" {
|
||||
balance[i].Balance = balance[i].Balance / common.SatoshisPerBTC
|
||||
balance[i].PendingFunds = balance[i].PendingFunds / common.SatoshisPerBTC
|
||||
}
|
||||
balance[i].Balance = balance[i].Balance / common.SatoshisPerBTC
|
||||
balance[i].PendingFunds = balance[i].PendingFunds / common.SatoshisPerBTC
|
||||
}
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
19
main.go
19
main.go
@@ -273,18 +273,27 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) {
|
||||
avail := data[i].Currencies[j].TotalValue
|
||||
total := onHold + avail
|
||||
|
||||
if total <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !port.ExchangeAddressExists(exchangeName, currencyName) {
|
||||
if total <= 0 {
|
||||
continue
|
||||
}
|
||||
log.Printf("Portfolio: Adding new exchange address: %s, %s, %f, %s\n",
|
||||
exchangeName, currencyName, total, portfolio.PortfolioAddressExchange)
|
||||
port.Addresses = append(
|
||||
port.Addresses,
|
||||
portfolio.Address{Address: exchangeName, CoinType: currencyName,
|
||||
Balance: total, Description: portfolio.PortfolioAddressExchange},
|
||||
)
|
||||
} else {
|
||||
port.UpdateExchangeAddressBalance(exchangeName, currencyName, total)
|
||||
if total <= 0 {
|
||||
log.Printf("Portfolio: Removing %s %s entry.\n", exchangeName,
|
||||
currencyName)
|
||||
port.RemoveExchangeAddress(exchangeName, currencyName)
|
||||
} else {
|
||||
log.Printf("Portfolio: Updating %s %s entry with balance %f.\n",
|
||||
exchangeName, currencyName, total)
|
||||
port.UpdateExchangeAddressBalance(exchangeName, currencyName, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@ const (
|
||||
|
||||
etherchainAPIURL = "https://etherchain.org/api"
|
||||
etherchainAccountMultiple = "account/multiple"
|
||||
// PortfolioAddressExchange holds the current portfolio address
|
||||
// PortfolioAddressExchange is a label for an exchange address
|
||||
PortfolioAddressExchange = "Exchange"
|
||||
portfolioAddressPersonal = "Personal"
|
||||
// PortfolioAddressPersonal is a label for a personal/offline address
|
||||
PortfolioAddressPersonal = "Personal"
|
||||
)
|
||||
|
||||
// Portfolio is variable store holding an array of portfolioAddress
|
||||
@@ -172,9 +173,9 @@ func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBa
|
||||
// GetAddressBalance acceses the portfolio base and returns the balance by passed
|
||||
// in address
|
||||
func (p *Base) GetAddressBalance(address string) (float64, bool) {
|
||||
for _, x := range p.Addresses {
|
||||
if x.Address == address {
|
||||
return x.Balance, true
|
||||
for x := range p.Addresses {
|
||||
if p.Addresses[x].Address == address {
|
||||
return p.Addresses[x].Balance, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
@@ -182,8 +183,8 @@ func (p *Base) GetAddressBalance(address string) (float64, bool) {
|
||||
|
||||
// ExchangeExists checks to see if an exchange exists in the portfolio base
|
||||
func (p *Base) ExchangeExists(exchangeName string) bool {
|
||||
for _, x := range p.Addresses {
|
||||
if x.Address == exchangeName {
|
||||
for x := range p.Addresses {
|
||||
if p.Addresses[x].Address == exchangeName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -193,8 +194,8 @@ func (p *Base) ExchangeExists(exchangeName string) bool {
|
||||
// AddressExists checks to see if there is an address associated with the
|
||||
// portfolio base
|
||||
func (p *Base) AddressExists(address string) bool {
|
||||
for _, x := range p.Addresses {
|
||||
if x.Address == address {
|
||||
for x := range p.Addresses {
|
||||
if p.Addresses[x].Address == address {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -204,8 +205,8 @@ func (p *Base) AddressExists(address string) bool {
|
||||
// ExchangeAddressExists checks to see if there is an exchange address
|
||||
// associated with the portfolio base
|
||||
func (p *Base) ExchangeAddressExists(exchangeName, coinType string) bool {
|
||||
for _, x := range p.Addresses {
|
||||
if x.Address == exchangeName && x.CoinType == coinType {
|
||||
for x := range p.Addresses {
|
||||
if p.Addresses[x].Address == exchangeName && p.Addresses[x].CoinType == coinType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -221,6 +222,16 @@ func (p *Base) UpdateAddressBalance(address string, amount float64) {
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveExchangeAddress removes an exchange address from the portfolio.
|
||||
func (p *Base) RemoveExchangeAddress(exchangeName, coinType string) {
|
||||
for x := range p.Addresses {
|
||||
if p.Addresses[x].Address == exchangeName && p.Addresses[x].CoinType == coinType {
|
||||
p.Addresses = append(p.Addresses[:x], p.Addresses[x+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateExchangeAddressBalance updates the portfolio balance when checked
|
||||
// against correct exchangeName and coinType.
|
||||
func (p *Base) UpdateExchangeAddressBalance(exchangeName, coinType string, balance float64) {
|
||||
@@ -239,13 +250,28 @@ func (p *Base) AddAddress(address, coinType, description string, balance float64
|
||||
Balance: balance, Description: description},
|
||||
)
|
||||
} else {
|
||||
p.UpdateAddressBalance(address, balance)
|
||||
if balance <= 0 {
|
||||
p.RemoveAddress(address, coinType, description)
|
||||
} else {
|
||||
p.UpdateAddressBalance(address, balance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAddress removes an address when checked against the correct address and
|
||||
// coinType
|
||||
func (p *Base) RemoveAddress(address, coinType, description string) {
|
||||
for x := range p.Addresses {
|
||||
if p.Addresses[x].Address == address && p.Addresses[x].CoinType == coinType && p.Addresses[x].Description == description {
|
||||
p.Addresses = append(p.Addresses[:x], p.Addresses[x+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdatePortfolio adds to the portfolio addresses by coin type
|
||||
func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool {
|
||||
if common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressExchange) || common.StringContains(common.JoinStrings(addresses, ","), portfolioAddressPersonal) {
|
||||
if common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressExchange) || common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressPersonal) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -256,7 +282,7 @@ func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool {
|
||||
}
|
||||
|
||||
for _, x := range result.Data {
|
||||
p.AddAddress(x.Address, coinType, portfolioAddressPersonal, x.Balance)
|
||||
p.AddAddress(x.Address, coinType, PortfolioAddressPersonal, x.Balance)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -266,7 +292,7 @@ func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool {
|
||||
return false
|
||||
}
|
||||
for _, x := range result.Data {
|
||||
p.AddAddress(x.Address, coinType, portfolioAddressPersonal, x.Balance)
|
||||
p.AddAddress(x.Address, coinType, PortfolioAddressPersonal, x.Balance)
|
||||
}
|
||||
} else {
|
||||
result, err := GetBlockrBalanceSingle(addresses[0], coinType)
|
||||
@@ -274,12 +300,23 @@ func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool {
|
||||
return false
|
||||
}
|
||||
p.AddAddress(
|
||||
addresses[0], coinType, portfolioAddressPersonal, result.Data.Balance,
|
||||
addresses[0], coinType, PortfolioAddressPersonal, result.Data.Balance,
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetPortfolioByExchange returns currency portfolio amount by exchange
|
||||
func (p *Base) GetPortfolioByExchange(exchangeName string) map[string]float64 {
|
||||
result := make(map[string]float64)
|
||||
for x := range p.Addresses {
|
||||
if common.StringContains(p.Addresses[x].Address, exchangeName) {
|
||||
result[p.Addresses[x].CoinType] = p.Addresses[x].Balance
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetExchangePortfolio returns current portfolio base information
|
||||
func (p *Base) GetExchangePortfolio() map[string]float64 {
|
||||
result := make(map[string]float64)
|
||||
@@ -314,28 +351,167 @@ func (p *Base) GetPersonalPortfolio() map[string]float64 {
|
||||
return result
|
||||
}
|
||||
|
||||
// GetPortfolioSummary rpoves a summary for your portfolio base
|
||||
func (p *Base) GetPortfolioSummary(coinFilter string) map[string]float64 {
|
||||
result := make(map[string]float64)
|
||||
for _, x := range p.Addresses {
|
||||
if coinFilter != "" && coinFilter != x.CoinType {
|
||||
continue
|
||||
// getPercentage returns the percentage of the target coin amount against the
|
||||
// total coin amount.
|
||||
func getPercentage(input map[string]float64, target string, totals map[string]float64) float64 {
|
||||
subtotal, _ := input[target]
|
||||
total, _ := totals[target]
|
||||
percentage := (subtotal / total) * 100 / 1
|
||||
return percentage
|
||||
}
|
||||
|
||||
// getPercentage returns the percentage a specific value of a target coin amount
|
||||
// against the total coin amount.
|
||||
func getPercentageSpecific(input float64, target string, totals map[string]float64) float64 {
|
||||
total, _ := totals[target]
|
||||
percentage := (input / total) * 100 / 1
|
||||
return percentage
|
||||
}
|
||||
|
||||
// Coin stores a coin type, balance, address and percentage relative to the total
|
||||
// amount.
|
||||
type Coin struct {
|
||||
Coin string `json:"coin"`
|
||||
Balance float64 `json:"balance"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Percentage float64 `json:"percentage,omitempty"`
|
||||
}
|
||||
|
||||
// OfflineCoinSummary stores a coin types address, balance and percentage
|
||||
// relative to the total amount.
|
||||
type OfflineCoinSummary struct {
|
||||
Address string `json:"address"`
|
||||
Balance float64 `json:"balance"`
|
||||
Percentage float64 `json:"percentage,omitempty"`
|
||||
}
|
||||
|
||||
// OnlineCoinSummary stores a coin types balance and percentage relative to the
|
||||
// total amount.
|
||||
type OnlineCoinSummary struct {
|
||||
Balance float64 `json:"balance"`
|
||||
Percentage float64 `json:"percentage,omitempty"`
|
||||
}
|
||||
|
||||
// Summary Stores the entire portfolio summary
|
||||
type Summary struct {
|
||||
Totals []Coin `json:"coin_totals"`
|
||||
Offline []Coin `json:"coins_offline"`
|
||||
OfflineSummary map[string][]OfflineCoinSummary `json:"offline_summary"`
|
||||
Online []Coin `json:"coins_online"`
|
||||
OnlineSummary map[string]map[string]OnlineCoinSummary `json:"online_summary"`
|
||||
}
|
||||
|
||||
// GetPortfolioSummary returns the complete portfolio summary, showing
|
||||
// coin totals, offline and online summaries with their relative percentages.
|
||||
func (p *Base) GetPortfolioSummary() Summary {
|
||||
personalHoldings := p.GetPersonalPortfolio()
|
||||
exchangeHoldings := p.GetExchangePortfolio()
|
||||
totalCoins := make(map[string]float64)
|
||||
|
||||
for x, y := range personalHoldings {
|
||||
if x == "ETH" {
|
||||
y = y / common.WeiPerEther
|
||||
personalHoldings[x] = y
|
||||
}
|
||||
balance, ok := result[x.CoinType]
|
||||
balance, ok := totalCoins[x]
|
||||
if !ok {
|
||||
result[x.CoinType] = x.Balance
|
||||
totalCoins[x] = y
|
||||
} else {
|
||||
result[x.CoinType] = x.Balance + balance
|
||||
totalCoins[x] = y + balance
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
for x, y := range exchangeHoldings {
|
||||
balance, ok := totalCoins[x]
|
||||
if !ok {
|
||||
totalCoins[x] = y
|
||||
} else {
|
||||
totalCoins[x] = y + balance
|
||||
}
|
||||
}
|
||||
|
||||
var portfolioOutput Summary
|
||||
for x, y := range totalCoins {
|
||||
coins := Coin{Coin: x, Balance: y}
|
||||
portfolioOutput.Totals = append(portfolioOutput.Totals, coins)
|
||||
}
|
||||
|
||||
for x, y := range personalHoldings {
|
||||
coins := Coin{
|
||||
Coin: x,
|
||||
Balance: y,
|
||||
Percentage: getPercentage(personalHoldings, x, totalCoins),
|
||||
}
|
||||
portfolioOutput.Offline = append(portfolioOutput.Offline, coins)
|
||||
}
|
||||
|
||||
for x, y := range exchangeHoldings {
|
||||
coins := Coin{
|
||||
Coin: x,
|
||||
Balance: y,
|
||||
Percentage: getPercentage(exchangeHoldings, x, totalCoins),
|
||||
}
|
||||
portfolioOutput.Online = append(portfolioOutput.Online, coins)
|
||||
}
|
||||
|
||||
var portfolioExchanges []string
|
||||
for _, x := range p.Addresses {
|
||||
if x.Description == PortfolioAddressExchange {
|
||||
if !common.DataContains(portfolioExchanges, x.Address) {
|
||||
portfolioExchanges = append(portfolioExchanges, x.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exchangeSummary := make(map[string]map[string]OnlineCoinSummary)
|
||||
for x := range portfolioExchanges {
|
||||
exchgName := portfolioExchanges[x]
|
||||
result := p.GetPortfolioByExchange(exchgName)
|
||||
|
||||
coinSummary := make(map[string]OnlineCoinSummary)
|
||||
for y, z := range result {
|
||||
coinSum := OnlineCoinSummary{
|
||||
Balance: z,
|
||||
Percentage: getPercentageSpecific(z, y, totalCoins),
|
||||
}
|
||||
coinSummary[y] = coinSum
|
||||
|
||||
}
|
||||
exchangeSummary[exchgName] = coinSummary
|
||||
}
|
||||
portfolioOutput.OnlineSummary = exchangeSummary
|
||||
|
||||
offlineSummary := make(map[string][]OfflineCoinSummary)
|
||||
for _, x := range p.Addresses {
|
||||
if x.Description != PortfolioAddressExchange {
|
||||
if x.CoinType == "ETH" {
|
||||
x.Balance = x.Balance / common.WeiPerEther
|
||||
}
|
||||
coinSummary := OfflineCoinSummary{
|
||||
Address: x.Address,
|
||||
Balance: x.Balance,
|
||||
Percentage: getPercentageSpecific(x.Balance, x.CoinType,
|
||||
totalCoins),
|
||||
}
|
||||
result, ok := offlineSummary[x.CoinType]
|
||||
if !ok {
|
||||
offlineSummary[x.CoinType] = append(offlineSummary[x.CoinType],
|
||||
coinSummary)
|
||||
} else {
|
||||
result = append(result, coinSummary)
|
||||
offlineSummary[x.CoinType] = result
|
||||
}
|
||||
}
|
||||
}
|
||||
portfolioOutput.OfflineSummary = offlineSummary
|
||||
return portfolioOutput
|
||||
}
|
||||
|
||||
// GetPortfolioGroupedCoin returns portfolio base information grouped by coin
|
||||
func (p *Base) GetPortfolioGroupedCoin() map[string][]string {
|
||||
result := make(map[string][]string)
|
||||
for _, x := range p.Addresses {
|
||||
if common.StringContains(x.Description, PortfolioAddressExchange) || common.StringContains(x.Description, portfolioAddressPersonal) {
|
||||
if common.StringContains(x.Description, PortfolioAddressExchange) {
|
||||
continue
|
||||
}
|
||||
result[x.CoinType] = append(result[x.CoinType], x.Address)
|
||||
|
||||
@@ -153,12 +153,43 @@ func TestUpdateAddressBalance(t *testing.T) {
|
||||
newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02)
|
||||
newbase.UpdateAddressBalance("someaddress", 0.03)
|
||||
|
||||
value := newbase.GetPortfolioSummary("LTC")
|
||||
if value["LTC"] != 0.03 {
|
||||
value := newbase.GetPortfolioSummary()
|
||||
if value.Totals[0].Coin != "LTC" && value.Totals[0].Balance != 0.03 {
|
||||
t.Error("Test Failed - portfolio_test.go - UpdateUpdateAddressBalance error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAddress(t *testing.T) {
|
||||
newbase := Base{}
|
||||
newbase.AddAddress("someaddr", "LTC", "LTCWALLETTEST", 420)
|
||||
|
||||
if !newbase.AddressExists("someaddr") {
|
||||
t.Error("Test failed - portfolio_test.go - TestRemoveAddress")
|
||||
}
|
||||
|
||||
newbase.RemoveAddress("someaddr", "LTC", "LTCWALLETTEST")
|
||||
if newbase.AddressExists("someaddr") {
|
||||
t.Error("Test failed - portfolio_test.go - TestRemoveAddress")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveExchangeAddress(t *testing.T) {
|
||||
newbase := Base{}
|
||||
exchangeName := "BallerExchange"
|
||||
coinType := "LTC"
|
||||
|
||||
newbase.AddAddress(exchangeName, coinType, PortfolioAddressExchange, 420)
|
||||
|
||||
if !newbase.ExchangeAddressExists(exchangeName, coinType) {
|
||||
t.Error("Test failed - portfolio_test.go - TestRemoveAddress")
|
||||
}
|
||||
|
||||
newbase.RemoveExchangeAddress(exchangeName, coinType)
|
||||
if newbase.ExchangeAddressExists(exchangeName, coinType) {
|
||||
t.Error("Test failed - portfolio_test.go - TestRemoveAddress")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateExchangeAddressBalance(t *testing.T) {
|
||||
newbase := Base{}
|
||||
newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02)
|
||||
@@ -166,18 +197,25 @@ func TestUpdateExchangeAddressBalance(t *testing.T) {
|
||||
portfolio.SeedPortfolio(newbase)
|
||||
portfolio.UpdateExchangeAddressBalance("someaddress", "LTC", 0.04)
|
||||
|
||||
value := portfolio.GetPortfolioSummary("LTC")
|
||||
if value["LTC"] != 0.04 {
|
||||
value := portfolio.GetPortfolioSummary()
|
||||
if value.Totals[0].Coin != "LTC" && value.Totals[0].Balance != 0.04 {
|
||||
t.Error("Test Failed - portfolio_test.go - UpdateExchangeAddressBalance error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAddress(t *testing.T) {
|
||||
newbase := Base{}
|
||||
newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02)
|
||||
newbase.AddAddress("Gibson", "LTC", "LTCWALLETTEST", 0.02)
|
||||
portfolio := GetPortfolio()
|
||||
portfolio.SeedPortfolio(newbase)
|
||||
if !portfolio.AddressExists("someaddress") {
|
||||
if !portfolio.AddressExists("Gibson") {
|
||||
t.Error("Test Failed - portfolio_test.go - AddAddress error")
|
||||
}
|
||||
|
||||
// Test updating balance to <= 0, expected result is to remove the address.
|
||||
// Fail if address still exists.
|
||||
newbase.AddAddress("Gibson", "LTC", "LTCWALLETTEST", -1)
|
||||
if newbase.AddressExists("Gibson") {
|
||||
t.Error("Test Failed - portfolio_test.go - AddAddress error")
|
||||
}
|
||||
}
|
||||
@@ -224,50 +262,128 @@ func TestUpdatePortfolio(t *testing.T) {
|
||||
if value {
|
||||
t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error")
|
||||
}
|
||||
|
||||
value = portfolio.UpdatePortfolio(
|
||||
[]string{PortfolioAddressExchange, PortfolioAddressPersonal}, "LTC")
|
||||
|
||||
if !value {
|
||||
t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPortfolioByExchange(t *testing.T) {
|
||||
newbase := Base{}
|
||||
newbase.AddAddress("ANX", "LTC", PortfolioAddressExchange, 0.07)
|
||||
newbase.AddAddress("Bitfinex", "LTC", PortfolioAddressExchange, 0.05)
|
||||
newbase.AddAddress("someaddress", "LTC", PortfolioAddressPersonal, 0.03)
|
||||
portfolio := GetPortfolio()
|
||||
portfolio.SeedPortfolio(newbase)
|
||||
value := portfolio.GetPortfolioByExchange("ANX")
|
||||
result, ok := value["LTC"]
|
||||
if !ok {
|
||||
t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange error")
|
||||
}
|
||||
|
||||
if result != 0.07 {
|
||||
t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange result != 0.10")
|
||||
}
|
||||
|
||||
value = portfolio.GetPortfolioByExchange("Bitfinex")
|
||||
result, ok = value["LTC"]
|
||||
if !ok {
|
||||
t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange error")
|
||||
}
|
||||
|
||||
if result != 0.05 {
|
||||
t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange result != 0.05")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExchangePortfolio(t *testing.T) {
|
||||
newbase := Base{}
|
||||
newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02)
|
||||
newbase.AddAddress("ANX", "LTC", PortfolioAddressExchange, 0.03)
|
||||
newbase.AddAddress("Bitfinex", "LTC", PortfolioAddressExchange, 0.05)
|
||||
newbase.AddAddress("someaddress", "LTC", PortfolioAddressPersonal, 0.03)
|
||||
portfolio := GetPortfolio()
|
||||
portfolio.SeedPortfolio(newbase)
|
||||
value := portfolio.GetExchangePortfolio()
|
||||
_, ok := value["ANX"]
|
||||
if ok {
|
||||
|
||||
result, ok := value["LTC"]
|
||||
if !ok {
|
||||
t.Error("Test Failed - portfolio_test.go - GetExchangePortfolio error")
|
||||
}
|
||||
|
||||
if result != 0.08 {
|
||||
t.Error("Test Failed - portfolio_test.go - GetExchangePortfolio result != 0.08")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPersonalPortfolio(t *testing.T) {
|
||||
newbase := Base{}
|
||||
newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02)
|
||||
newbase.AddAddress("anotheraddress", "LTC", "LTCWALLETTEST", 0.03)
|
||||
newbase.AddAddress("Exchange", "LTC", PortfolioAddressExchange, 0.01)
|
||||
portfolio := GetPortfolio()
|
||||
portfolio.SeedPortfolio(newbase)
|
||||
value := portfolio.GetPersonalPortfolio()
|
||||
_, ok := value["LTC"]
|
||||
result, ok := value["LTC"]
|
||||
if !ok {
|
||||
t.Error("Test Failed - portfolio_test.go - GetPersonalPortfolio error")
|
||||
}
|
||||
|
||||
if result != 0.05 {
|
||||
t.Error("Test Failed - portfolio_test.go - GetPersonalPortfolio result != 0.05")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPortfolioSummary(t *testing.T) {
|
||||
newbase := Base{}
|
||||
newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02)
|
||||
// Personal holdings
|
||||
newbase.AddAddress("someaddress", "LTC", PortfolioAddressPersonal, 1)
|
||||
newbase.AddAddress("0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", "ETH",
|
||||
PortfolioAddressPersonal, 865346880000000000)
|
||||
newbase.AddAddress("0x9edc81c813b26165f607a8d1b8db87a02f34307f", "ETH",
|
||||
PortfolioAddressPersonal, 165346880000000000)
|
||||
|
||||
// Exchange holdings
|
||||
newbase.AddAddress("Bitfinex", "LTC", PortfolioAddressExchange, 20)
|
||||
newbase.AddAddress("Bitfinex", "BTC", PortfolioAddressExchange, 100)
|
||||
newbase.AddAddress("ANX", "ETH", PortfolioAddressExchange, 42)
|
||||
|
||||
portfolio := GetPortfolio()
|
||||
portfolio.SeedPortfolio(newbase)
|
||||
value := portfolio.GetPortfolioSummary("LTC")
|
||||
if value["LTC"] != 0.02 {
|
||||
t.Error("Test Failed - portfolio_test.go - GetPortfolioGroupedCoin error")
|
||||
value := portfolio.GetPortfolioSummary()
|
||||
|
||||
getTotalsVal := func(s string) Coin {
|
||||
for x := range value.Totals {
|
||||
if value.Totals[x].Coin == s {
|
||||
return value.Totals[x]
|
||||
}
|
||||
}
|
||||
return Coin{}
|
||||
}
|
||||
|
||||
if getTotalsVal("LTC").Coin != "LTC" {
|
||||
t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error")
|
||||
}
|
||||
|
||||
if getTotalsVal("ETH").Coin != "ETH" {
|
||||
t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error")
|
||||
}
|
||||
|
||||
if getTotalsVal("LTC").Balance != 101 {
|
||||
t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPortfolioGroupedCoin(t *testing.T) {
|
||||
newbase := Base{}
|
||||
newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02)
|
||||
newbase.AddAddress("Exchange", "LTC", PortfolioAddressExchange, 0.05)
|
||||
portfolio := GetPortfolio()
|
||||
portfolio.SeedPortfolio(newbase)
|
||||
value := portfolio.GetPortfolioGroupedCoin()
|
||||
if value["LTC"][0] != "someaddress" {
|
||||
if value["LTC"][0] != "someaddress" && len(value["LTC"][0]) != 1 {
|
||||
t.Error("Test Failed - portfolio_test.go - GetPortfolioGroupedCoin error")
|
||||
}
|
||||
}
|
||||
|
||||
27
portfolio_routes.go
Normal file
27
portfolio_routes.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// RESTGetPortfolio replies to a request with an encoded JSON response of the
|
||||
// portfolio
|
||||
func RESTGetPortfolio(w http.ResponseWriter, r *http.Request) {
|
||||
result := bot.portfolio.GetPortfolioSummary()
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// PortfolioRoutes declares the current routes for config_routes.go
|
||||
var PortfolioRoutes = Routes{
|
||||
Route{
|
||||
"GetPortfolio",
|
||||
"GET",
|
||||
"/portfolio/all",
|
||||
RESTGetPortfolio,
|
||||
},
|
||||
}
|
||||
@@ -13,6 +13,7 @@ func NewRouter(exchanges []exchange.IBotExchange) *mux.Router {
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
allRoutes := append(routes, ExchangeRoutes...)
|
||||
allRoutes = append(allRoutes, ConfigRoutes...)
|
||||
allRoutes = append(allRoutes, PortfolioRoutes...)
|
||||
allRoutes = append(allRoutes, WalletRoutes...)
|
||||
for _, route := range allRoutes {
|
||||
var handler http.Handler
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
@@ -12,89 +13,152 @@ import (
|
||||
"github.com/thrasher-/gocryptotrader/portfolio"
|
||||
)
|
||||
|
||||
var (
|
||||
priceMap map[string]float64
|
||||
)
|
||||
|
||||
func printSummary(msg, from, to string, amount float64) {
|
||||
log.Println()
|
||||
log.Println(fmt.Sprintf("%s in USD: $%.2f", msg, amount))
|
||||
conv, err := currency.ConvertCurrency(amount, "USD", "AUD")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
log.Println(fmt.Sprintf("%s in AUD: $%.2f", msg, conv))
|
||||
}
|
||||
log.Println()
|
||||
}
|
||||
|
||||
func getOnlineOfflinePortfolio(coins []portfolio.Coin, online bool) {
|
||||
var totals float64
|
||||
for _, x := range coins {
|
||||
value := priceMap[x.Coin] * x.Balance
|
||||
totals += value
|
||||
log.Printf("\t%v %v Subtotal: $%.2f Coin percentage: %.2f%%\n", x.Coin,
|
||||
x.Balance, value, x.Percentage)
|
||||
}
|
||||
if !online {
|
||||
printSummary("\tOffline balance", "USD", "AUD", totals)
|
||||
} else {
|
||||
printSummary("\tOnline balance", "USD", "AUD", totals)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var inFile, key string
|
||||
var err error
|
||||
flag.StringVar(&inFile, "infile", "config.dat", "The config input file to process.")
|
||||
flag.StringVar(&key, "key", "", "The key to use for AES encryption.")
|
||||
flag.Parse()
|
||||
|
||||
log.Println("GoCryptoTrader: portfolio tool.")
|
||||
|
||||
var data []byte
|
||||
var cfg config.Config
|
||||
|
||||
data, err = common.ReadFile(inFile)
|
||||
var err = cfg.ReadConfig(inFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to read input file %s. Error: %s.", inFile, err)
|
||||
}
|
||||
|
||||
if config.ConfirmECS(data) {
|
||||
if key == "" {
|
||||
result, errf := config.PromptForConfigKey()
|
||||
if errf != nil {
|
||||
log.Fatal("Unable to obtain encryption/decryption key.")
|
||||
}
|
||||
key = string(result)
|
||||
}
|
||||
data, err = config.DecryptConfigFile(data, []byte(key))
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to decrypt config data. Error: %s.", err)
|
||||
}
|
||||
|
||||
}
|
||||
err = config.ConfirmConfigJSON(data, &cfg)
|
||||
if err != nil {
|
||||
log.Fatal("File isn't in JSON format")
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Loaded config file.")
|
||||
|
||||
port := portfolio.Base{}
|
||||
port.SeedPortfolio(cfg.Portfolio)
|
||||
result := port.GetPortfolioSummary("")
|
||||
result := port.GetPortfolioSummary()
|
||||
|
||||
log.Println("Fetched portfolio data.")
|
||||
|
||||
type PortfolioTemp struct {
|
||||
Balance float64
|
||||
Subtotal float64
|
||||
}
|
||||
|
||||
stuff := make(map[string]PortfolioTemp)
|
||||
cfg.RetrieveConfigCurrencyPairs()
|
||||
portfolioMap := make(map[string]PortfolioTemp)
|
||||
total := float64(0)
|
||||
|
||||
for x, y := range result {
|
||||
if x == "ETH" {
|
||||
y = y / common.WeiPerEther
|
||||
log.Println("Fetching currency data..")
|
||||
var fiatCurrencies []string
|
||||
for _, y := range result.Totals {
|
||||
if currency.IsFiatCurrency(y.Coin) {
|
||||
fiatCurrencies = append(fiatCurrencies, y.Coin)
|
||||
}
|
||||
}
|
||||
err = currency.SeedCurrencyData(common.JoinStrings(fiatCurrencies, ","))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Fetched currency data.")
|
||||
log.Println("Fetching ticker data and calculating totals..")
|
||||
priceMap = make(map[string]float64)
|
||||
priceMap["USD"] = 1
|
||||
|
||||
for _, y := range result.Totals {
|
||||
pf := PortfolioTemp{}
|
||||
pf.Balance = y
|
||||
pf.Balance = y.Balance
|
||||
pf.Subtotal = 0
|
||||
|
||||
bf := bitfinex.Bitfinex{}
|
||||
|
||||
if currency.IsDefaultCurrency(x) {
|
||||
continue
|
||||
}
|
||||
|
||||
ticker, errf := bf.GetTicker(x+"USD", url.Values{})
|
||||
if errf != nil {
|
||||
log.Println(errf)
|
||||
if currency.IsDefaultCurrency(y.Coin) {
|
||||
if y.Coin != "USD" {
|
||||
conv, err := currency.ConvertCurrency(y.Balance, y.Coin, "USD")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
priceMap[y.Coin] = conv / y.Balance
|
||||
pf.Subtotal = conv
|
||||
}
|
||||
} else {
|
||||
pf.Subtotal = y.Balance
|
||||
}
|
||||
} else {
|
||||
pf.Subtotal = ticker.Last * y
|
||||
bf := bitfinex.Bitfinex{}
|
||||
ticker, errf := bf.GetTicker(y.Coin+"USD", url.Values{})
|
||||
if errf != nil {
|
||||
log.Println(errf)
|
||||
} else {
|
||||
priceMap[y.Coin] = ticker.Last
|
||||
pf.Subtotal = ticker.Last * y.Balance
|
||||
}
|
||||
}
|
||||
stuff[x] = pf
|
||||
portfolioMap[y.Coin] = pf
|
||||
total += pf.Subtotal
|
||||
}
|
||||
log.Println("Done.")
|
||||
log.Println()
|
||||
log.Println("PORTFOLIO TOTALS:")
|
||||
for x, y := range portfolioMap {
|
||||
log.Printf("\t%s Amount: %f Subtotal: $%.2f USD (1 %s = $%.2f USD). Percentage of portfolio %.3f%%", x, y.Balance, y.Subtotal, x, y.Subtotal/y.Balance, y.Subtotal/total*100/1)
|
||||
}
|
||||
printSummary("\tTotal balance", "USD", "AUD", total)
|
||||
|
||||
for x, y := range stuff {
|
||||
log.Printf("%s %f subtotal: %f USD (1 %s = %.2f USD). Percentage of portfolio %f", x, y.Balance, y.Subtotal, x, y.Subtotal/y.Balance, y.Subtotal/total*100/1)
|
||||
log.Println("OFFLINE COIN TOTALS:")
|
||||
getOnlineOfflinePortfolio(result.Offline, false)
|
||||
|
||||
log.Println("ONLINE COIN TOTALS:")
|
||||
getOnlineOfflinePortfolio(result.Online, true)
|
||||
|
||||
log.Println("OFFLINE COIN SUMMARY:")
|
||||
var totals float64
|
||||
for x, y := range result.OfflineSummary {
|
||||
log.Printf("\t%s:", x)
|
||||
totals = 0
|
||||
for z := range y {
|
||||
value := priceMap[x] * y[z].Balance
|
||||
totals += value
|
||||
log.Printf("\t %s Amount: %f Subtotal: $%.2f Coin percentage: %.2f%%\n",
|
||||
y[z].Address, y[z].Balance, value, y[z].Percentage)
|
||||
}
|
||||
printSummary(fmt.Sprintf("\t %s balance", x), "USD", "AUD", totals)
|
||||
}
|
||||
|
||||
log.Printf("Total balance in USD: %f.\n", total)
|
||||
|
||||
conv, err := currency.ConvertCurrency(total, "USD", "AUD")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
log.Printf("Total balance in AUD: %f.\n", conv)
|
||||
log.Println("ONLINE COINS SUMMARY:")
|
||||
for x, y := range result.OnlineSummary {
|
||||
log.Printf("\t%s:", x)
|
||||
totals = 0
|
||||
for z, w := range y {
|
||||
value := priceMap[z] * w.Balance
|
||||
totals += value
|
||||
log.Printf("\t %s Amount: %f Subtotal $%.2f Coin percentage: %.2f%%",
|
||||
z, w.Balance, value, w.Percentage)
|
||||
}
|
||||
printSummary("\t Exchange balance", "USD", "AUD", totals)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user