Skip to content
Merged
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: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,8 @@ Raw trades, top-of-book quotes and derivative ticks for Binance, OKX and Hyperli
| `mark_price` | Mark price, one row per change | yes | yes | yes |
| `index_price` | Index price, one row per change | yes | yes | yes |
| `funding_rate` | Funding rate and next funding time | yes | yes | yes |
| `open_interest` | Open interest, one row per change | yes | yes | yes |

Hyperliquid's derivative feed carries no exchange time, so in its `mark_price`, `index_price`, `funding_rate` and `open_interest` files `exchange_timestamp` is modelled, and an `exchange_timestamp_kind` column (`"modelled"`) follows it. Raw L2 order books are not offered.
Hyperliquid's derivative feed carries no exchange time, so in its `mark_price`, `index_price` and `funding_rate` files `exchange_timestamp` is modelled, and an `exchange_timestamp_kind` column (`"modelled"`) follows it. Raw L2 order books are not offered.

Every file starts with `exchange_timestamp` (the venue's time) and `local_timestamp` (when the event reached our capture machine). Timestamps are UTC.

Expand Down
11 changes: 5 additions & 6 deletions cli_raw.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ import (
)

var rawDatasetDescriptions = map[RawDataset]string{
RawTrades: "Every trade: id, taker side, price, amount",
RawQuotes: "Top of book: best bid/ask price and amount",
RawMarkPrice: "Mark price, one row per change",
RawIndexPrice: "Index price, one row per change",
RawFundingRate: "Funding rate and next funding time",
RawOpenInterest: "Open interest, one row per change",
RawTrades: "Every trade: id, taker side, price, amount",
RawQuotes: "Top of book: best bid/ask price and amount",
RawMarkPrice: "Mark price, one row per change",
RawIndexPrice: "Index price, one row per change",
RawFundingRate: "Funding rate and next funding time",
}

type rawDownloadFlags struct {
Expand Down
4 changes: 2 additions & 2 deletions raw.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const (

var (
// RawDatasets lists every raw dataset in catalog order.
RawDatasets = []RawDataset{RawTrades, RawQuotes, RawMarkPrice, RawIndexPrice, RawFundingRate, RawOpenInterest}
RawDatasets = []RawDataset{RawTrades, RawQuotes, RawMarkPrice, RawIndexPrice, RawFundingRate}

// Exchanges lists every supported exchange.
Exchanges = []Exchange{ExchangeBinanceFutures, ExchangeOkxPerps, ExchangeHyperliquidPerps}
Expand All @@ -69,7 +69,7 @@ var (
)

// RawExchanges returns the exchanges a raw dataset is served for: every one.
// On Hyperliquid, whose feed carries no exchange time, the four derivative
// On Hyperliquid, whose feed carries no exchange time, the three derivative
// datasets have a modelled exchange_timestamp and an exchange_timestamp_kind
// column.
func RawExchanges(RawDataset) []Exchange {
Expand Down
11 changes: 11 additions & 0 deletions raw_live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ func TestCLI_Raw_Live_PreviewNeedsNoKey(t *testing.T) {
if code != 0 {
t.Fatalf("expected exit code 0, got %d; stderr: %s", code, stderr)
}
// Raw previews may currently have no files, which is a successful response.
if strings.Contains(stdout, "No data found for the given criteria") {
entries, err := os.ReadDir(outputDir)
if err != nil {
t.Fatal(err)
}
if len(entries) != 0 {
t.Errorf("expected no files for an empty preview, got %v", entries)
}
return
}
if !strings.Contains(stdout, "Successfully downloaded 1 files") {
t.Errorf("expected one file downloaded, got: %s", stdout)
}
Expand Down
48 changes: 40 additions & 8 deletions raw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,14 @@ func TestRawFilePath(t *testing.T) {
}

func TestValidateRawQuery(t *testing.T) {
want := []RawDataset{RawTrades, RawQuotes, RawMarkPrice, RawIndexPrice, RawFundingRate}
if !slices.Equal(RawDatasets, want) {
t.Fatalf("expected five raw datasets, got %v", RawDatasets)
}
if err := ValidateRawQuery("open_interest", ExchangeBinanceFutures); err == nil {
t.Error("expected raw open interest to be rejected")
}

// The API serves every raw dataset on every exchange, Hyperliquid's
// derivative datasets included (with a modelled exchange_timestamp).
for _, dataset := range RawDatasets {
Expand All @@ -323,7 +331,7 @@ func TestValidateRawQuery(t *testing.T) {
}
}

if err := ValidateRawQuery("l2_book", ExchangeBinanceFutures); err == nil || !strings.Contains(err.Error(), "open_interest") {
if err := ValidateRawQuery("l2_book", ExchangeBinanceFutures); err == nil || !strings.Contains(err.Error(), "funding_rate") {
t.Errorf("expected an unknown dataset to list the valid ones, got %v", err)
}
if err := ValidateRawQuery(RawTrades, "bybit"); err == nil || !strings.Contains(err.Error(), "okx-perps") {
Expand Down Expand Up @@ -684,14 +692,31 @@ func TestCLI_Raw_ReportsMissingPeriods(t *testing.T) {
}

func TestCLI_Raw_NoFiles(t *testing.T) {
newRawStub(t, staticListing())
for _, preview := range []bool{false, true} {
t.Run(fmt.Sprintf("preview=%t", preview), func(t *testing.T) {
stub := newRawStub(t, staticListing())
args := rawArgs(t.TempDir())
if preview {
t.Setenv("APERIODIC_API_KEY", "")
args = append(args, "--preview")
} else {
args = append(args, "--start-date", "2025-06-01", "--end-date", "2025-06-30")
}

stdout, stderr, code := runCLI(rawArgs(t.TempDir(), "--start-date", "2025-06-01", "--end-date", "2025-06-30")...)
if code != 0 {
t.Fatalf("expected exit code 0, got %d; stderr: %s", code, stderr)
}
if !strings.Contains(stdout, "No data found") {
t.Errorf("expected a no-data message, got: %s", stdout)
stdout, stderr, code := runCLI(args...)
if code != 0 {
t.Fatalf("expected exit code 0, got %d; stderr: %s", code, stderr)
}
if !strings.Contains(stdout, "No data found") {
t.Errorf("expected a no-data message, got: %s", stdout)
}
if preview {
calls := stub.calls()
if len(calls) != 1 || calls[0].path != "/data/raw/preview/trades" || calls[0].key != DemoAPIKey {
t.Errorf("expected one preview request with the demo key, got %v", calls)
}
}
})
}
}

Expand All @@ -706,6 +731,7 @@ func TestCLI_Raw_ValidatesBeforeCallingTheAPI(t *testing.T) {
want string
}{
{"no dataset", []string{"raw", "--symbol", rawTestSymbol, "--output-dir", outputDir}, "raw dataset is required"},
{"raw open interest", append([]string{"raw", "open_interest", "--symbol", rawTestSymbol, "--output-dir", outputDir}, dates...), `unknown raw dataset "open_interest"`},
{"unknown dataset", append([]string{"raw", "book", "--symbol", rawTestSymbol, "--output-dir", outputDir}, dates...), `unknown raw dataset "book"`},
{"unknown exchange", append([]string{"raw", "trades", "--exchange", "bybit", "--symbol", rawTestSymbol, "--output-dir", outputDir}, dates...), `unknown exchange "bybit"`},
{"missing symbol", append([]string{"raw", "trades", "--output-dir", outputDir}, dates...), "--symbol is required"},
Expand Down Expand Up @@ -750,6 +776,9 @@ func TestCLI_Raw_RequiresAKeyWithoutPreview(t *testing.T) {
func TestCLI_Raw_Help(t *testing.T) {
for _, args := range [][]string{{"raw"}, {"raw", "help"}, {"raw", "--help"}, {"raw", "trades", "-h"}} {
stdout, _, code := runCLI(args...)
if strings.Contains(stdout, "open_interest") {
t.Errorf("raw help must not list open interest: %s", stdout)
}
if code != 0 {
t.Errorf("%v: expected exit code 0, got %d", args, code)
}
Expand All @@ -761,6 +790,9 @@ func TestCLI_Raw_Help(t *testing.T) {
}

stdout, _, _ := runCLI("help")
if !strings.Contains(stdout, string(MetricOpenInterest)) {
t.Error("expected open-interest metrics to remain in top-level help")
}
if !strings.Contains(stdout, "aperiodic raw <dataset>") {
t.Error("expected the top-level help to list the raw command")
}
Expand Down
11 changes: 5 additions & 6 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,11 @@ type SymbolsResponse struct {
type RawDataset string

const (
RawTrades RawDataset = "trades"
RawQuotes RawDataset = "quotes"
RawMarkPrice RawDataset = "mark_price"
RawIndexPrice RawDataset = "index_price"
RawFundingRate RawDataset = "funding_rate"
RawOpenInterest RawDataset = "open_interest"
RawTrades RawDataset = "trades"
RawQuotes RawDataset = "quotes"
RawMarkPrice RawDataset = "mark_price"
RawIndexPrice RawDataset = "index_price"
RawFundingRate RawDataset = "funding_rate"
)

type RawFileInfo struct {
Expand Down
Loading