Initial public commit.
This commit is contained in:
29
coindesk/lib_test.go
Normal file
29
coindesk/lib_test.go
Normal 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
112
coindesk/model.go
Normal 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
48
coindesk/requests.go
Normal 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")
|
||||
}
|
Reference in New Issue
Block a user