Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/holdings ignored #275

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ func updateHoldingWeights(assets []c.Asset, holdingSummary HoldingSummary) []c.A

func getHoldingFromAssetQuote(assetQuote c.AssetQuote, lotsBySymbol map[string]AggregatedLot, currencyRateByUse currency.CurrencyRateByUse) c.Holding {

if aggregatedLot, ok := lotsBySymbol[assetQuote.Symbol]; ok {
// fix #240: Use Asset Id in place of Symbol as unique id
if aggregatedLot, ok := lotsBySymbol[assetQuote.ID]; ok {
value := aggregatedLot.Quantity * assetQuote.QuotePrice.Price * currencyRateByUse.QuotePrice
cost := aggregatedLot.Cost * currencyRateByUse.PositionCost
totalChangeAmount := value - cost
Expand Down
3 changes: 3 additions & 0 deletions internal/asset/asset_fixture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var fixtureAssetGroupQuote = c.AssetGroupQuote{
},
AssetQuotes: []c.AssetQuote{
{
ID: "TWKS",
Name: "ThoughtWorks",
Symbol: "TWKS",
Class: c.AssetClassStock,
Expand All @@ -37,6 +38,7 @@ var fixtureAssetGroupQuote = c.AssetGroupQuote{
},
},
{
ID: "MSFT",
Name: "Microsoft Inc",
Symbol: "MSFT",
Class: c.AssetClassStock,
Expand All @@ -52,6 +54,7 @@ var fixtureAssetGroupQuote = c.AssetGroupQuote{
},
},
{
ID: "SOL1-USD",
Name: "Solana USD",
Symbol: "SOL1-USD",
Class: c.AssetClassCryptocurrency,
Expand Down
2 changes: 2 additions & 0 deletions internal/asset/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ var _ = Describe("Asset", func() {
inputAssetGroupQuote := fixtureAssetGroupQuote
inputAssetGroupQuote.AssetQuotes = []c.AssetQuote{
{
ID: "TWKS",
Name: "ThoughtWorks",
Symbol: "TWKS",
Class: c.AssetClassStock,
Expand All @@ -110,6 +111,7 @@ var _ = Describe("Asset", func() {
QuoteExtended: c.QuoteExtended{FiftyTwoWeekHigh: 150, FiftyTwoWeekLow: 50, MarketCap: 1000000},
},
{
ID: "MSFT",
Name: "Microsoft Inc",
Symbol: "MSFT",
Class: c.AssetClassStock,
Expand Down
17 changes: 15 additions & 2 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Options struct {
type symbolSource struct {
symbol string
source c.QuoteSource
id string // fix #245 : Keep id as reference to Yaml config
}

// Run starts the ticker UI
Expand Down Expand Up @@ -309,13 +310,15 @@ func getSymbolAndSource(symbol string, tickerSymbolToSourceSymbol symbol.TickerS

if strings.HasSuffix(symbolUppercase, ".CG") {
return symbolSource{
id: symbolUppercase,
source: c.QuoteSourceCoingecko,
symbol: strings.ToLower(symbol)[:len(symbol)-3],
}
}

if strings.HasSuffix(symbolUppercase, ".CC") {
return symbolSource{
id: symbolUppercase,
source: c.QuoteSourceCoinCap,
symbol: strings.ToLower(symbol)[:len(symbol)-3],
}
Expand All @@ -326,6 +329,7 @@ func getSymbolAndSource(symbol string, tickerSymbolToSourceSymbol symbol.TickerS
if tickerSymbolToSource, exists := tickerSymbolToSourceSymbol[symbolUppercase]; exists {

return symbolSource{
id: symbolUppercase,
source: tickerSymbolToSource.Source,
symbol: tickerSymbolToSource.SourceSymbol,
}
Expand All @@ -335,6 +339,7 @@ func getSymbolAndSource(symbol string, tickerSymbolToSourceSymbol symbol.TickerS
}

return symbolSource{
id: symbolUppercase,
source: c.QuoteSourceYahoo,
symbol: symbolUppercase,
}
Expand All @@ -343,18 +348,26 @@ func getSymbolAndSource(symbol string, tickerSymbolToSourceSymbol symbol.TickerS

func appendSymbol(symbolsUnique map[c.QuoteSource]c.AssetGroupSymbolsBySource, symbolAndSource symbolSource) map[c.QuoteSource]c.AssetGroupSymbolsBySource {

newSymbol := c.Symbol{
ID: symbolAndSource.id,
Name: symbolAndSource.symbol,
}

if symbolsBySource, ok := symbolsUnique[symbolAndSource.source]; ok {

symbolsBySource.Symbols = append(symbolsBySource.Symbols, symbolAndSource.symbol)
symbolsBySource.Symbols = append(symbolsBySource.Symbols, newSymbol)

symbolsUnique[symbolAndSource.source] = symbolsBySource

return symbolsUnique
}

newSymbols := make([]c.Symbol, 0)
newSymbols = append(newSymbols, newSymbol)

symbolsUnique[symbolAndSource.source] = c.AssetGroupSymbolsBySource{
Source: symbolAndSource.source,
Symbols: []string{symbolAndSource.symbol},
Symbols: newSymbols,
}

return symbolsUnique
Expand Down
8 changes: 4 additions & 4 deletions internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,20 +266,20 @@ var _ = Describe("Cli", func() {
}, g.Elements{
"0": g.MatchFields(g.IgnoreExtras, g.Fields{
"Symbols": g.MatchAllElementsWithIndex(g.IndexIdentity, g.Elements{
"0": Equal("TSLA"),
"0": Equal(c.Symbol{ID: "TSLA", Name: "TSLA"}),
}),
"Source": Equal(c.QuoteSourceYahoo),
}),
"2": g.MatchFields(g.IgnoreExtras, g.Fields{
"Symbols": g.MatchAllElementsWithIndex(g.IndexIdentity, g.Elements{
"0": Equal("ethereum"),
"1": Equal("solana"),
"0": Equal(c.Symbol{ID: "ETHEREUM.CG", Name: "ethereum"}),
"1": Equal(c.Symbol{ID: "SOL.X", Name: "solana"}),
}),
"Source": Equal(c.QuoteSourceCoingecko),
}),
"4": g.MatchFields(g.IgnoreExtras, g.Fields{
"Symbols": g.MatchAllElementsWithIndex(g.IndexIdentity, g.Elements{
"0": Equal("bitcoin"),
"0": Equal(c.Symbol{ID: "BITCOIN.CC", Name: "bitcoin"}),
}),
"Source": Equal(c.QuoteSourceCoinCap),
}),
Expand Down
9 changes: 8 additions & 1 deletion internal/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type AssetGroup struct {
}

type AssetGroupSymbolsBySource struct {
Symbols []string
Symbols []Symbol
Source QuoteSource
}

Expand Down Expand Up @@ -218,6 +218,7 @@ const (

// AssetQuote represents a price quote and related attributes for a single security
type AssetQuote struct {
ID string
Name string
Symbol string
Class AssetClass
Expand All @@ -228,3 +229,9 @@ type AssetQuote struct {
Exchange Exchange
Meta Meta
}

// Symbol represents a symbol but keep id not only name
type Symbol struct {
ID string
Name string
}
14 changes: 14 additions & 0 deletions internal/common/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package common

import "strings"

// Concatenates symbol names
func JoinSymbolName(symbols []Symbol, separator string) string {
symbolsStr := make([]string, len(symbols))

for i, symbol := range symbols {
symbolsStr[i] = symbol.Name
}

return strings.Join(symbolsStr, separator)
}
12 changes: 9 additions & 3 deletions internal/print/print_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ var _ = Describe("Print", func() {
SymbolsBySource: []c.AssetGroupSymbolsBySource{
{
Source: c.QuoteSourceYahoo,
Symbols: []string{
"GOOG",
"RBLX",
Symbols: []c.Symbol{
{
ID: "GOOG",
Name: "GOOG",
},
{
ID: "RBLX",
Name: "RBLX",
},
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion internal/quote/coincap/coincap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var _ = Describe("CoinCap Quote", func() {

MockResponseCoincapQuotes()

output := GetAssetQuotes(*client, []string{"elrond"})
output := GetAssetQuotes(*client, []c.Symbol{{ID: "ELROND.CC", Name: "elrond"}})
Expect(output).To(g.MatchAllElementsWithIndex(g.IndexIdentity, g.Elements{
"0": g.MatchFields(g.IgnoreExtras, g.Fields{
"QuotePrice": g.MatchFields(g.IgnoreExtras, g.Fields{
Expand Down
27 changes: 24 additions & 3 deletions internal/quote/coincap/quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

// Quote represents a quote of a single security from the API response
type Quote struct {
ID string `json:"id"`
ShortName string `json:"name"`
Symbol string `json:"symbol"`
RegularMarketChangePercent string `json:"changePercent24Hr"`
Expand All @@ -32,6 +33,7 @@ func transformQuote(responseQuote Quote) c.AssetQuote {
volume, _ := strconv.ParseFloat(responseQuote.RegularMarketVolume, 64)

assetQuote := c.AssetQuote{
ID: responseQuote.ID,
Name: responseQuote.ShortName,
Symbol: responseQuote.Symbol,
Class: c.AssetClassCryptocurrency,
Expand Down Expand Up @@ -82,14 +84,33 @@ func transformQuotes(responseQuotes []Quote) []c.AssetQuote {
}

// GetAssetQuotes issues a HTTP request to retrieve quotes from the API and process the response
func GetAssetQuotes(client resty.Client, symbols []string) []c.AssetQuote {
symbolsString := strings.Join(symbols, ",")
func GetAssetQuotes(client resty.Client, symbols []c.Symbol) []c.AssetQuote {

symbolsDict := map[string]c.Symbol{}
for _, symbol := range symbols {
symbolsDict[strings.ToUpper(symbol.Name)] = symbol
}

symbolsString := c.JoinSymbolName(symbols, ",")

res, _ := client.R().
SetResult(Response{}).
SetQueryParam("ids", strings.ToLower(symbolsString)).
Get("https://api.coincap.io/v2/assets")

return transformQuotes((res.Result().(*Response)).Data) //nolint:forcetypeassert
response, ok := res.Result().(*Response)

if !ok {
return []c.AssetQuote{}
}

data := response.Data

// fix #245 : force Id using Symbol declaration
for idx, quote := range data {
data[idx].ID = symbolsDict[strings.ToUpper(quote.ID)].ID
}

return transformQuotes(data) //nolint:forcetypeassert

}
17 changes: 15 additions & 2 deletions internal/quote/coingecko/coingecko.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func transformResponseToAssetQuotes(responseQuotes *ResponseQuotes) []c.AssetQuo
for _, responseQuote := range *responseQuotes {

assetQuote := c.AssetQuote{
ID: responseQuote.Id,
Name: responseQuote.Name,
Symbol: strings.ToUpper(responseQuote.Symbol),
Class: c.AssetClassCryptocurrency,
Expand Down Expand Up @@ -87,15 +88,27 @@ func transformResponseToAssetQuotes(responseQuotes *ResponseQuotes) []c.AssetQuo

}

func GetAssetQuotes(client resty.Client, symbols []string) []c.AssetQuote {
symbolsString := strings.Join(symbols, ",")
func GetAssetQuotes(client resty.Client, symbols []c.Symbol) []c.AssetQuote {

var symbolsDict = map[string]c.Symbol{}
for _, symbol := range symbols {
symbolsDict[strings.ToUpper(symbol.Name)] = symbol
}

symbolsString := c.JoinSymbolName(symbols, ",")

url := fmt.Sprintf("https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=%s&order=market_cap_desc&per_page=250&page=1&sparkline=false", symbolsString)
res, _ := client.R().
SetResult(ResponseQuotes{}).
Get(url)

out := (res.Result().(*ResponseQuotes)) //nolint:forcetypeassert

// fix #245 : force Id using Symbol declaration
for idx, quote := range *out {
(*out)[idx].Id = symbolsDict[strings.ToUpper(quote.Id)].ID
}

assetQuotes := transformResponseToAssetQuotes(out)

return assetQuotes
Expand Down
2 changes: 1 addition & 1 deletion internal/quote/coingecko/coingecko_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var _ = Describe("Coingecko", func() {
It("should make a request to get crypto quotes and transform the response", func() {
MockResponseCoingeckoQuotes()

output := GetAssetQuotes(*client, []string{"bitcoin"})
output := GetAssetQuotes(*client, []c.Symbol{{ID: "BITCOIN.CG", Name: "bitcoin"}})
Expect(output).To(g.MatchAllElementsWithIndex(g.IndexIdentity, g.Elements{
"0": g.MatchFields(g.IgnoreExtras, g.Fields{
"QuotePrice": g.MatchFields(g.IgnoreExtras, g.Fields{
Expand Down
6 changes: 3 additions & 3 deletions internal/quote/quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func GetAssetGroupQuote(dep c.Dependencies) func(c.AssetGroup) c.AssetGroupQuote
func getUniqueSymbolsBySource(assetGroups []c.AssetGroup) []c.AssetGroupSymbolsBySource {

symbols := make(map[c.QuoteSource]map[string]bool)
symbolsUnique := make(map[c.QuoteSource][]string)
symbolsUnique := make(map[c.QuoteSource][]c.Symbol)
assetGroupSymbolsBySource := make([]c.AssetGroupSymbolsBySource, 0)
for _, assetGroup := range assetGroups {

Expand All @@ -63,8 +63,8 @@ func getUniqueSymbolsBySource(assetGroups []c.AssetGroup) []c.AssetGroupSymbolsB
symbols[source] = map[string]bool{}
}

if !symbols[source][symbol] {
symbols[source][symbol] = true
if !symbols[source][symbol.Name] {
symbols[source][symbol.Name] = true
symbolsUnique[source] = append(symbolsUnique[source], symbol)
}
}
Expand Down
24 changes: 12 additions & 12 deletions internal/quote/quote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,28 +89,28 @@ var _ = Describe("Quote", func() {
SymbolsBySource: []c.AssetGroupSymbolsBySource{
{
Source: c.QuoteSourceYahoo,
Symbols: []string{
"GOOG",
"RBLX",
Symbols: []c.Symbol{
{ID: "GOOG", Name: "GOOG"},
{ID: "RBLX", Name: "RBLX"},
},
},
{
Source: c.QuoteSourceCoingecko,
Symbols: []string{
"bitcoin",
Symbols: []c.Symbol{
{ID: "BITCOIN.CG", Name: "bitcoin"},
},
},
{
Source: c.QuoteSourceCoinCap,
Symbols: []string{
"elrond",
Symbols: []c.Symbol{
{ID: "ELROND.CC", Name: "elrond"},
},
},
{
Source: c.QuoteSourceUserDefined,
Symbols: []string{
"CASH",
"PRIVATESHARES",
Symbols: []c.Symbol{
{ID: "CASH", Name: "CASH"},
{ID: "PRIVATESHARES", Name: "PRIVATESHARES"},
},
},
},
Expand Down Expand Up @@ -148,8 +148,8 @@ var _ = Describe("Quote", func() {
SymbolsBySource: []c.AssetGroupSymbolsBySource{
{
Source: c.QuoteSourceYahoo,
Symbols: []string{
"GOOG",
Symbols: []c.Symbol{
{ID: "GOOG", Name: "GOOG"},
},
},
},
Expand Down
Loading
Loading