Files
zeekay 8aa4b5f780 maker: live Kraken CEX oracle + composite price feed
Add a Kraken public-market-data price source (provider_kraken.go), the same
ProviderSource shape as provider_oma.go / provider_v3.go, exposing the live CEX
mid the seeder tracks via UseFeed. NewKrakenFeed wraps it; CompositeMid (feed.go)
composes the CEX feed (listed majors) with the native V3 AMM feed (LUX/*) behind
the single feed the seeder consumes. Binary opt-in via MAKER_KRAKEN_PAIRS.

Read-only, unauthenticated, market-data only — the maker never trades on Kraken.
Tests hermetic (canned frames) + KRAKEN_LIVE gate; live-proven (real BTC/USD mid).

go.sum: refresh luxfi/[email protected] checksum (origin re-tag).
2026-06-28 17:13:31 -07:00

53 lines
2.0 KiB
Go

package maker
import (
"context"
"fmt"
)
// feed.go composes heterogeneous price references behind the SINGLE MidProvider the
// seeder tracks. The seeder is reference-agnostic — it tracks one feed's mid per
// symbol (seeder.go currentMid). Different markets need different references: a
// CEX-listed major (ETH/USD) is priced by Kraken; a LUX/* pair is priced by the
// native V2/V3 AMM. CompositeMid lets both coexist as ONE feed, each pricing the
// symbols it knows, so there is still exactly one "what to track" seam.
// CompositeMid composes several MidProviders into one. GetMidPrice returns the
// first sub-provider that yields a positive mid for the symbol (order = priority),
// so a CEX feed and the native-AMM feed live behind a single seeder feed: the CEX
// prices its listed majors, the AMM prices LUX/*, and a symbol no sub-provider
// knows falls through to the seeder's own DEX-book / configured-mid fallback.
type CompositeMid struct{ feeds []MidProvider }
// NewCompositeMid builds a CompositeMid from the non-nil feeds, in priority order.
// It returns nil when none are given (no feed → seeder keeps prior behavior) and
// the sole feed unwrapped when exactly one is given (no needless indirection).
func NewCompositeMid(feeds ...MidProvider) MidProvider {
live := make([]MidProvider, 0, len(feeds))
for _, f := range feeds {
if f != nil {
live = append(live, f)
}
}
switch len(live) {
case 0:
return nil
case 1:
return live[0]
default:
return &CompositeMid{feeds: live}
}
}
// GetMidPrice returns the first sub-provider's positive mid for symbol. A symbol no
// sub-provider prices yields an error, so the seeder's currentMid falls through to
// the DEX book mid then the configured reference — never a fabricated price.
func (c *CompositeMid) GetMidPrice(ctx context.Context, symbol string) (float64, error) {
for _, f := range c.feeds {
if mid, err := f.GetMidPrice(ctx, symbol); err == nil && mid > 0 {
return mid, nil
}
}
return 0, fmt.Errorf("composite: no live mid for %s across %d feeds", symbol, len(c.feeds))
}