Initial public commit.

This commit is contained in:
2024-03-17 02:10:05 -07:00
commit 23ed509200
20 changed files with 1298 additions and 0 deletions

29
coindesk/lib_test.go Normal file
View File

@@ -0,0 +1,29 @@
package coindesk_test
import (
"context"
"fmt"
"testing"
"time"
"code.humancabbage.net/moonmath/coindesk"
)
func TestXxx(t *testing.T) {
now := time.Now()
then := now.Add(time.Duration(-24) * time.Hour)
values, err := coindesk.GetPriceValues(context.Background(), coindesk.BTC, then, now)
if err != nil {
t.Errorf("test failure: %v", err)
}
_ = values
fmt.Println()
tickers, err := coindesk.GetAssetTickers(context.Background(), coindesk.BTC, coindesk.ETH)
if err != nil {
t.Errorf("test failure: %v", err)
}
_ = tickers
fmt.Println()
}

112
coindesk/model.go Normal file
View File

@@ -0,0 +1,112 @@
package coindesk
import (
"encoding/json"
"strconv"
"strings"
"time"
)
// Asset is a cryptocurrency, like Bitcoin, Ethereum, etc.
type Asset string
const (
// BTC is the Bitcoin asset.
BTC Asset = "BTC"
// ETH is the Ethereum asset.
ETH Asset = "ETH"
)
// Response represents the general top-level format of Coindesk API responses.
type Response[T any] struct {
StatusCode int `json:"statusCode"`
Message string `json:"message"`
Data T `json:"data"`
}
// PriceValues contains a series of timestamped prices for a particular asset.
type PriceValues struct {
ISO Asset `json:"iso"`
Name string `json:"name"`
Slug string `json:"slug"`
IngestionStart Date `json:"ingestionStart"`
Entries []TimestampPrice `json:"entries"`
}
// AssetTickers is a map from an asset to its ticker data.
type AssetTickers map[Asset]AssetTicker
// AssetTicker is a snapshot of pricing data for an asset.
type AssetTicker struct {
ISO Asset `json:"iso"`
Name string `json:"name"`
Slug string `json:"slug"`
Change struct {
Percent float64 `json:"percent"`
Value float64 `json:"value"`
} `json:"change"`
OHLC struct {
Opening Price `json:"o"`
High Price `json:"h"`
Low Price `json:"l"`
Closing Price `json:"c"`
} `json:"ohlc"`
CirculatingSupply float64 `json:"circulatingSupply"`
MarketCap Price `json:"marketCap"`
Timestamp Timestamp `json:"ts"`
}
// TimestampPrice represents a JSON array with two elements: an integer Unix
// timestamp expressed in milliseconds, and a floating-point USD price.
type TimestampPrice struct {
Timestamp Timestamp
Price Price
}
func (t *TimestampPrice) UnmarshalJSON(b []byte) error {
a := []interface{}{&t.Timestamp, &t.Price}
return json.Unmarshal(b, &a)
}
// Timestamp represents an integer Unix timestamp expressed in milliseconds
// which has been converted into a Golang time.Time object.
type Timestamp time.Time
func (t *Timestamp) UnmarshalJSON(b []byte) error {
s := string(b)
n := len(s)
secsStr := s[0 : n-3]
millisStr := s[n-3:]
secs, err := strconv.ParseInt(secsStr, 10, 64)
if err != nil {
return err
}
millis, err := strconv.ParseInt(millisStr, 10, 64)
if err != nil {
return err
}
converted := time.Unix(secs, millis*1e6)
*t = Timestamp(converted)
return nil
}
// Price represents the USD price of an asset.
type Price float64
// Date represents a date-only string which has been converted into a Golang
// time.Time object.
type Date time.Time
func (d *Date) UnmarshalJSON(b []byte) error {
s := string(b)
s, _ = strings.CutPrefix(s, "\"")
s, _ = strings.CutSuffix(s, "\"")
t, err := time.Parse(time.DateOnly, s)
if err != nil {
return err
}
*d = Date(t)
return nil
}

48
coindesk/requests.go Normal file
View File

@@ -0,0 +1,48 @@
package coindesk
import (
"context"
"fmt"
"strings"
"time"
"github.com/carlmjohnson/requests"
)
// GetPriceValues gets timestamped prices for a particular asset.
func GetPriceValues(
ctx context.Context, asset Asset, startDate, endDate time.Time,
) (resp Response[PriceValues], err error) {
const basePath = "v2/tb/price/values"
const timeFormat = "2006-01-02T15:04"
err = requests.New(commonConfig).
Path(fmt.Sprintf("%s/%s", basePath, asset)).
Param("start_date", startDate.Format(timeFormat)).
Param("end_date", endDate.Format(timeFormat)).
ToJSON(&resp).
Fetch(ctx)
return
}
// GetAssetTickers gets tickers for a set of assets.
func GetAssetTickers(ctx context.Context, assets ...Asset) (resp Response[AssetTickers], err error) {
const basePath = "v2/tb/price/ticker"
var strAssets []string
for _, asset := range assets {
strAssets = append(strAssets, string(asset))
}
err = requests.New(commonConfig).
Path(basePath).
Param("assets", strings.Join(strAssets, ",")).
ToJSON(&resp).
Fetch(ctx)
return
}
const baseUrl = "https://production.api.coindesk.com"
func commonConfig(rb *requests.Builder) {
rb.
BaseURL(baseUrl).
Accept("application/json;charset=utf-8")
}