Improve portfolio, coverage and tool

This commit is contained in:
Adrian Gallagher
2017-08-17 11:56:54 +10:00
parent 0f55715987
commit 4f34b58d55
9 changed files with 532 additions and 116 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
View File

@@ -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)
}
}
}
}

View File

@@ -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)

View File

@@ -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
View 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,
},
}

View File

@@ -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

View File

@@ -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)
}
}