Skip to content

Commit

Permalink
examples refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
calbera committed Oct 25, 2024
1 parent 43400a1 commit 04d717d
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 75 deletions.
46 changes: 11 additions & 35 deletions examples/lib/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,6 @@ import (
"github.com/calbera/go-pyth-client/types"
)

// QueryType is an enum that represents the type of query to be made to the Pythnet Hermes API.
type QueryType string

const (
// LatestSync is a query type that fetches all latest prices together & synchronously.
LatestSync QueryType = "latest-sync"
// LatestAsync is a query type that fetches all latest prices individually, in parallel.
LatestAsync QueryType = "latest-async"
// Stream is a query type that subscribes to price updates and returns the latest cached price.
StreamCached QueryType = "stream"
)

// QuerySettings is a struct that holds the settings for querying Hermes using the Pythnet client.
type QuerySettings struct {
UseEma bool
DesiredPrecision int
RequestType QueryType
SingleUpdateFee uint
}

// Builds a PriceUpdate for a Pyth oracle result.
//
// NOTE: does not set the PythUpdateFee or the FeedType.
Expand Down Expand Up @@ -55,41 +35,37 @@ func GetPriceUpdateFromPythStructsPriceFeed(

return &PriceUpdate{
PairIndex: pairIndex,
Price: Price(
NormalizeToPrecision(psp.Price, int(-psp.Expo), desiredPrecision),
),
Conf: Price(
NormalizeToPrecision(int64(psp.Conf), int(-psp.Expo), desiredPrecision),
),
Price: NormalizeToPrecision(psp.Price, int(-psp.Expo), desiredPrecision),
Conf: NormalizeToPrecision(int64(psp.Conf), int(-psp.Expo), desiredPrecision),
TimeStamp: psp.PublishTime.Uint64(),
}
}

// NormalizeToPrecision normalizes a int64 of decimals to a int64 with the desired precision.
func NormalizeToPrecision(i int64, decimals, precision int) int64 {
func NormalizeToPrecision(i int64, decimals, precision int) Price {
if decimals < precision {
return i * int64(math.Pow10(precision-decimals))
return Price(i * int64(math.Pow10(precision-decimals)))
} else if decimals > precision {
return i / int64(math.Pow10(decimals-precision))
return Price(i / int64(math.Pow10(decimals-precision)))
}
return i
return Price(i)
}

// Returns baseP/quoteP in the desired precision. Assumes baseP/quoteP are in the same precision.
func CalculateTriangularPrice(baseP, quoteP int64, desiredPrecision int) int64 {
return int64(float64(baseP) * math.Pow10(desiredPrecision) / float64(quoteP))
func CalculateTriangularPrice(baseP, quoteP Price, desiredPrecision int) Price {
return Price(float64(baseP) * math.Pow10(desiredPrecision) / float64(quoteP))
}

// Returns the confidence of baseP/quoteP in the desired precision. Assumes baseP/quoteP
// are in the same precision. Logic taken from Pyth Rust SDK.
// (https://github.com/pyth-network/pyth-sdk-rs/blob/main/pyth-sdk/src/price.rs#L424)
func CalculateTriangularConf(
baseP, quoteP, baseC, quoteC int64, desiredPrecision int,
) int64 {
baseP, quoteP, baseC, quoteC Price, desiredPrecision int,
) Price {
triangularPrice := float64(baseP) / float64(quoteP)
confMultiplier := math.Sqrt(
(math.Pow(float64(baseC), 2) / math.Pow(float64(baseP), 2)) +
(math.Pow(float64(quoteC), 2) / math.Pow(float64(quoteP), 2)),
)
return int64(triangularPrice * confMultiplier * math.Pow10(desiredPrecision))
return Price(triangularPrice * confMultiplier * math.Pow10(desiredPrecision))
}
10 changes: 5 additions & 5 deletions examples/lib/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func TestNormalizeToPrecision(t *testing.T) {
i int64
decimals int
precision int
expected int64
expected lib.Price
}{
{100000, 6, 3, 100},
{100, 3, 6, 100000},
Expand All @@ -28,9 +28,9 @@ func TestNormalizeToPrecision(t *testing.T) {

func TestCalculateTriangularPrice(t *testing.T) {
tests := []struct {
baseP, quoteP int64
baseP, quoteP lib.Price
desiredPrecision int
expected int64
expected lib.Price
}{
{3e10, 4e10, 10, 3e10 / 4},
{1000, 10000, 10, 1e10 / 10},
Expand All @@ -51,9 +51,9 @@ func TestCalculateTriangularPrice(t *testing.T) {

func TestCalculateTriangularConf(t *testing.T) {
tests := []struct {
baseP, quoteP, baseC, quoteC int64
baseP, quoteP, baseC, quoteC lib.Price
desiredPrecision int
expected int64
expected lib.Price
}{
{3e10, 4e10, 1e10, 1e10, 10, 5e10 / 16},
{1000, 10000, 1, 5, 10, 1118033},
Expand Down
21 changes: 21 additions & 0 deletions examples/query/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package query

// Type is an enum that represents the type of query to be made to the Pythnet Hermes API.
type Type string

const (
// LatestSync is a query type that fetches all latest prices together & synchronously.
LatestSync Type = "latest-sync"
// LatestAsync is a query type that fetches all latest prices individually, in parallel.
LatestAsync Type = "latest-async"
// Stream is a query type that subscribes to price updates and returns the latest cached price.
StreamCached Type = "stream"
)

// Settings is a struct that holds the settings for querying Hermes using the Pythnet client.
type Settings struct {
UseEma bool
DesiredPrecision int
RequestType Type
SingleUpdateFee uint
}
26 changes: 12 additions & 14 deletions examples/query/historical.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// Requires a Pyth client to fetch prices from Pyth Benchmarks API. Assumes that all required
// feeds to be queried for `oracleFeeds` are in the `uniqueFeeds` slice.
func GetAllPricesAt(
ctx context.Context, timestamp time.Time, pythClient client.Benchmarks, qs *lib.QuerySettings,
ctx context.Context, timestamp time.Time, pythClient client.Benchmarks, qs *Settings,
pairIndexes map[string]uint64, oracleFeeds map[string][]string, uniqueFeeds []string,
) (lib.PriceUpdates, error) {
// Query Pyth for the unique price feeds necessary
Expand Down Expand Up @@ -43,31 +43,29 @@ func GetAllPricesAt(
)

case lib.Feed_TRIANGULAR:
baseP := lib.GetPriceUpdateFromPythStructsPriceFeed(
base := lib.GetPriceUpdateFromPythStructsPriceFeed(
priceData[priceFeeds[0]], pairIndex, qs.UseEma, qs.DesiredPrecision,
)
quoteP := lib.GetPriceUpdateFromPythStructsPriceFeed(
quote := lib.GetPriceUpdateFromPythStructsPriceFeed(
priceData[priceFeeds[1]], pairIndex, qs.UseEma, qs.DesiredPrecision,
)

// Use the older of the two as the timestamp.
var timestamp uint64
if baseP.TimeStamp <= quoteP.TimeStamp {
timestamp = baseP.TimeStamp
if base.TimeStamp <= quote.TimeStamp {
timestamp = base.TimeStamp
} else {
timestamp = quoteP.TimeStamp
timestamp = quote.TimeStamp
}

allPairPrices[pairIndex] = &lib.PriceUpdate{
PairIndex: pairIndex,
Price: lib.Price(lib.CalculateTriangularPrice(
int64(baseP.Price), int64(quoteP.Price), qs.DesiredPrecision,
)),
Conf: lib.Price(lib.CalculateTriangularConf(
int64(baseP.Price), int64(quoteP.Price),
int64(baseP.Conf), int64(quoteP.Conf),
qs.DesiredPrecision,
)),
Price: lib.CalculateTriangularPrice(
base.Price, quote.Price, qs.DesiredPrecision,
),
Conf: lib.CalculateTriangularConf(
base.Price, quote.Price, base.Conf, quote.Conf, qs.DesiredPrecision,
),
TimeStamp: timestamp,
}

Expand Down
40 changes: 19 additions & 21 deletions examples/query/latest.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
// Requires a Pyth client to fetch prices from Pyth Hermes API. Assumes that all required feeds
// to be queried for `oracleFeeds` are in the `uniqueFeeds` slice.
func GetAllLatestPrices(
ctx context.Context, pythClient client.Hermes, qs *lib.QuerySettings,
ctx context.Context, pythClient client.Hermes, qs *Settings,
pairIndexes map[string]uint64, oracleFeeds map[string][]string, uniqueFeeds []string,
) (lib.PriceUpdates, error) {
// Query Pyth for the unique price feeds necessary
Expand All @@ -22,11 +22,11 @@ func GetAllLatestPrices(
err error
)
switch qs.RequestType {
case lib.StreamCached:
case StreamCached:
priceData, err = pythClient.GetCachedLatestPriceUpdates(ctx, uniqueFeeds...)
case lib.LatestSync:
case LatestSync:
priceData, err = pythClient.GetLatestPriceUpdatesSync(ctx, uniqueFeeds...)
case lib.LatestAsync:
case LatestAsync:
fallthrough
default:
priceData, err = pythClient.GetLatestPriceUpdatesAsync(ctx, uniqueFeeds...)
Expand Down Expand Up @@ -57,7 +57,7 @@ func GetAllLatestPrices(
)

// Set the update fee depending on the request type to the API.
if qs.RequestType == lib.LatestAsync {
if qs.RequestType == LatestAsync {
// If latest async, each feed's update data is stored separately.
pu.PythUpdateFee = big.NewInt(int64(qs.SingleUpdateFee))
} else {
Expand All @@ -68,44 +68,42 @@ func GetAllLatestPrices(
allPairPrices[pairIndex] = pu

case lib.Feed_TRIANGULAR:
baseP := lib.GetPriceUpdateFromPythResult(
base := lib.GetPriceUpdateFromPythResult(
priceData[priceFeeds[0]], pairIndex, qs.UseEma, qs.DesiredPrecision,
)
quoteP := lib.GetPriceUpdateFromPythResult(
quote := lib.GetPriceUpdateFromPythResult(
priceData[priceFeeds[1]], pairIndex, qs.UseEma, qs.DesiredPrecision,
)

// Use the older of the two as the timestamp.
var timestamp uint64
if baseP.TimeStamp <= quoteP.TimeStamp {
timestamp = baseP.TimeStamp
if base.TimeStamp <= quote.TimeStamp {
timestamp = base.TimeStamp
} else {
timestamp = quoteP.TimeStamp
timestamp = quote.TimeStamp
}

pu := &lib.PriceUpdate{
PairIndex: pairIndex,
Price: lib.Price(lib.CalculateTriangularPrice(
int64(baseP.Price), int64(quoteP.Price), qs.DesiredPrecision,
)),
Conf: lib.Price(lib.CalculateTriangularConf(
int64(baseP.Price), int64(quoteP.Price),
int64(baseP.Conf), int64(quoteP.Conf),
qs.DesiredPrecision,
)),
Price: lib.CalculateTriangularPrice(
base.Price, quote.Price, qs.DesiredPrecision,
),
Conf: lib.CalculateTriangularConf(
base.Price, quote.Price, base.Conf, quote.Conf, qs.DesiredPrecision,
),
TimeStamp: timestamp,
}

// Set the update data depending on the request type to the API.
if qs.RequestType == lib.LatestAsync {
if qs.RequestType == LatestAsync {
// If latest async, each feed's update data is stored separately.
pu.PythUpdateData = [][]byte{
baseP.PythUpdateData[0], quoteP.PythUpdateData[0],
base.PythUpdateData[0], quote.PythUpdateData[0],
}
pu.PythUpdateFee = big.NewInt(int64(qs.SingleUpdateFee) * 2)
} else {
// For other request types, all feeds' update data is stored in each feed's data.
pu.PythUpdateData = baseP.PythUpdateData
pu.PythUpdateData = base.PythUpdateData
pu.PythUpdateFee = big.NewInt(int64(qs.SingleUpdateFee) * int64(len(uniqueFeeds)))
}

Expand Down

0 comments on commit 04d717d

Please sign in to comment.