From 2d0aa526a08455ffac4cae602319b51cb7494e34 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 19 Apr 2026 10:24:59 -0700 Subject: [PATCH] Fix Tiingo crypto volume unmarshal error - Changed Volume and AdjVolume types to float64 in TiingoEndOfDay to support crypto assets. - Added TestTiingoRepositoryGetFractionalVolume to verify the fix. - Updated documentation with model consistency guidelines. Fixes #352 --- GEMINI.md | 1 + asset/GEMINI.md | 4 +++ asset/tiingo_repository.go | 6 ++--- asset/tiingo_repository_test.go | 44 +++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/GEMINI.md b/GEMINI.md index 67a53a8..ea3c299 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -15,6 +15,7 @@ Indicator is a Golang library for technical analysis, providing a wide range of ## Development Standards +- **Finding Tasks:** Use the `gh issue list` command to identify open issues. Issues labeled `good first issue` are typically self-contained indicator or strategy implementations. - **No External Dependencies:** This project aims to have no external dependencies. Do not add any new dependencies. - **Composition & Reusability:** Build and utilize reusable blocks, particularly those in the `helper/` package. Avoid re-implementing existing logic within a single indicator. For example, if an indicator uses a moving average, it should employ the existing implementation rather than duplicating the logic internally. - **Copyright Header:** Every file must start with the copyright notice: diff --git a/asset/GEMINI.md b/asset/GEMINI.md index 0ad9895..3b522a4 100644 --- a/asset/GEMINI.md +++ b/asset/GEMINI.md @@ -27,6 +27,10 @@ type Repository interface { - `SqlRepository`: Database-backed persistence for large datasets. - `TiingoRepository`: Remote API connector for fetching real-time data. +## Model Consistency + +All price and volume fields in repository models (e.g., `TiingoEndOfDay`) must use `float64`. This ensures compatibility with crypto assets that provide fractional volumes, which would otherwise cause JSON unmarshaling errors if `int64` is used. + ## Testing Pattern Test files use `asset_test` package and verify repository implementations against mock and real-world data sources. diff --git a/asset/tiingo_repository.go b/asset/tiingo_repository.go index 5065f61..9206df6 100644 --- a/asset/tiingo_repository.go +++ b/asset/tiingo_repository.go @@ -55,7 +55,7 @@ type TiingoEndOfDay struct { Close float64 `json:"close"` // Volume is the total volume. - Volume int64 `json:"volume"` + Volume float64 `json:"volume"` // AdjOpen is the adjusted opening price. AdjOpen float64 `json:"adjOpen"` @@ -70,7 +70,7 @@ type TiingoEndOfDay struct { AdjClose float64 `json:"adjClose"` // AdjVolume is the adjusted total volume. - AdjVolume int64 `json:"adjVolume"` + AdjVolume float64 `json:"adjVolume"` // Dividend is the dividend paid out. Dividend float64 `json:"divCash"` @@ -87,7 +87,7 @@ func (e *TiingoEndOfDay) ToSnapshot() *Snapshot { High: e.AdjHigh, Low: e.AdjLow, Close: e.AdjClose, - Volume: float64(e.AdjVolume), + Volume: e.AdjVolume, } } diff --git a/asset/tiingo_repository_test.go b/asset/tiingo_repository_test.go index 7d42356..a882912 100644 --- a/asset/tiingo_repository_test.go +++ b/asset/tiingo_repository_test.go @@ -182,3 +182,47 @@ func TestTiingoRepositoryAppend(t *testing.T) { t.Fatal(err) } } + +func TestTiingoRepositoryGetFractionalVolume(t *testing.T) { + // JSON with fractional volume, which should fail to unmarshal into int64 + jsonResponse := `[ + { + "date": "2024-03-01T00:00:00.000Z", + "open": 61000.5, + "high": 62000.5, + "low": 60000.5, + "close": 61500.5, + "volume": 20297.44489644, + "adjOpen": 61000.5, + "adjHigh": 62000.5, + "adjLow": 60000.5, + "adjClose": 61500.5, + "adjVolume": 20297.44489644, + "divCash": 0.0, + "splitFactor": 1.0 + } + ]` + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = fmt.Fprint(w, jsonResponse) + })) + defer server.Close() + + repository := asset.NewTiingoRepository("1234") + repository.BaseURL = server.URL + + snapshots, err := repository.Get("btcusd") + if err != nil { + t.Fatal(err) + } + + snapshot, ok := <-snapshots + if !ok { + t.Fatal("expected snapshot, but channel closed (probably due to unmarshal error)") + } + + expectedVolume := 20297.44489644 + if snapshot.Volume != expectedVolume { + t.Fatalf("actual volume %v expected %v", snapshot.Volume, expectedVolume) + } +}