Skip to content

Commit

Permalink
feat: multi-quote deviation logic (backport #1110) (#1132)
Browse files Browse the repository at this point in the history
* feat: multi-quote deviation logic (#1110)

* fix multi quote deviation logic

* test++

* tests++

* cl++

* add reddit as a provider

* tests++

* cl++

* moar tests

* fix this test just a tiny bit

* add the multi-quote test case

* make tests less lenient

* add another btc pair to test cases for deviation logic

* cleanup

* cleanliness

* cleanup tests

* compute tests

* clear up some comments & change this to a feature

Co-authored-by: Robert Zaremba <robert@zaremba.ch>
(cherry picked from commit a85f6dd)

# Conflicts:
#	price-feeder/CHANGELOG.md
#	price-feeder/oracle/oracle.go

* remove some of this stuff

Co-authored-by: Adam Wozniak <29418299+adamewozniak@users.noreply.github.com>
  • Loading branch information
mergify[bot] and adamewozniak authored Jul 14, 2022
1 parent b666930 commit a373b4b
Show file tree
Hide file tree
Showing 5 changed files with 420 additions and 65 deletions.
18 changes: 18 additions & 0 deletions price-feeder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements

- [#978](https://github.com/umee-network/umee/pull/978) Cleanup the oracle package by moving deviation & conversion logic.

### Features

- [1110](https://github.com/umee-network/umee/pull/1110) Add the ability to detect deviations with multi-quoted prices, ex. using BTC/USD and BTC/ETH at the same time.
- [#1038](https://github.com/umee-network/umee/pull/1038) Adds the option for validators to override API endpoints in our config.
- [#1002](https://github.com/umee-network/umee/pull/1002) Add linting to the price feeder CI.
- [#998](https://github.com/umee-network/umee/pull/998) Make deviation thresholds configurable for stablecoin support.

## [v0.2.3](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.3) - 2022-06-30

### Improvements

- [#1069](https://github.com/umee-network/umee/pull/1069) Subscribe to node event EventNewBlockHeader to have the current chain height.

## [v0.2.2](https://github.com/umee-network/umee/releases/tag/price-feeder%2Fv0.2.2) - 2022-06-27

### Improvements

- [#1050](https://github.com/umee-network/umee/pull/1050) Cache x/oracle params to decrease the number of queries to nodes.
- [#1069](https://github.com/umee-network/umee/pull/1069) Subscribe to node event EventNewBlockHeader to have the current chain height.

Expand Down
39 changes: 35 additions & 4 deletions price-feeder/oracle/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/rs/zerolog"
"github.com/umee-network/umee/price-feeder/config"
"github.com/umee-network/umee/price-feeder/oracle/provider"
"github.com/umee-network/umee/price-feeder/oracle/types"
Expand All @@ -30,10 +31,15 @@ func getUSDBasedProviders(asset string, providerPairs map[string][]types.Currenc
}

// ConvertCandlesToUSD converts any candles which are not quoted in USD
// to USD by other price feeds.
// to USD by other price feeds. It will also filter out any candles not
// within the deviation threshold set by the config.
//
// Ref: https://github.com/umee-network/umee/blob/4348c3e433df8c37dd98a690e96fc275de609bc1/price-feeder/oracle/filter.go#L41
func convertCandlesToUSD(
logger zerolog.Logger,
candles provider.AggregatedProviderCandles,
providerPairs map[string][]types.CurrencyPair,
deviationThresholds map[string]sdk.Dec,
) (provider.AggregatedProviderCandles, error) {
if len(candles) == 0 {
return candles, nil
Expand Down Expand Up @@ -71,7 +77,17 @@ func convertCandlesToUSD(
if len(validCandleList) == 0 {
return nil, fmt.Errorf("there are no valid conversion rates for %s", pair.Quote)
}
tvwap, err := ComputeTVWAP(validCandleList)

filteredCandles, err := FilterCandleDeviations(
logger,
validCandleList,
deviationThresholds,
)
if err != nil {
return nil, err
}

tvwap, err := ComputeTVWAP(filteredCandles)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -99,10 +115,15 @@ func convertCandlesToUSD(
}

// convertTickersToUSD converts any tickers which are not quoted in USD to USD,
// using the conversion rates of other tickers.
// using the conversion rates of other tickers. It will also filter out any tickers
// not within the deviation threshold set by the config.
//
// Ref: https://github.com/umee-network/umee/blob/4348c3e433df8c37dd98a690e96fc275de609bc1/price-feeder/oracle/filter.go#L41
func convertTickersToUSD(
logger zerolog.Logger,
tickers provider.AggregatedProviderPrices,
providerPairs map[string][]types.CurrencyPair,
deviationThresholds map[string]sdk.Dec,
) (provider.AggregatedProviderPrices, error) {
if len(tickers) == 0 {
return tickers, nil
Expand Down Expand Up @@ -141,7 +162,17 @@ func convertTickersToUSD(
if len(validTickerList) == 0 {
return nil, fmt.Errorf("there are no valid conversion rates for %s", pair.Quote)
}
vwap, err := ComputeVWAP(validTickerList)

filteredTickers, err := FilterTickerDeviations(
logger,
validTickerList,
deviationThresholds,
)
if err != nil {
return nil, err
}

vwap, err := ComputeVWAP(filteredTickers)
if err != nil {
return nil, err
}
Expand Down
204 changes: 157 additions & 47 deletions price-feeder/oracle/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@ import (
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
"github.com/umee-network/umee/price-feeder/config"
"github.com/umee-network/umee/price-feeder/oracle/provider"
"github.com/umee-network/umee/price-feeder/oracle/types"
)

var (
atomPrice = sdk.MustNewDecFromStr("29.93")
atomVolume = sdk.MustNewDecFromStr("894123.00")
usdtPrice = sdk.MustNewDecFromStr("0.98")
usdtVolume = sdk.MustNewDecFromStr("894123.00")

atomPair = types.CurrencyPair{
Base: "ATOM",
Quote: "USDT",
}
usdtPair = types.CurrencyPair{
Base: "USDT",
Quote: "USD",
}
)

func TestGetUSDBasedProviders(t *testing.T) {
providerPairs := make(map[string][]types.CurrencyPair, 3)
providerPairs["coinbase"] = []types.CurrencyPair{
Expand Down Expand Up @@ -59,49 +76,97 @@ func TestGetUSDBasedProviders(t *testing.T) {

func TestConvertCandlesToUSD(t *testing.T) {
providerCandles := make(provider.AggregatedProviderCandles, 2)
pairs := []types.CurrencyPair{
{
Base: "ATOM",
Quote: "USDT",
},
{
Base: "USDT",
Quote: "USD",
},

binanceCandles := map[string][]provider.CandlePrice{
"ATOM": {{
Price: atomPrice,
Volume: atomVolume,
TimeStamp: provider.PastUnixTime(1 * time.Minute),
}},
}
providerCandles[config.ProviderBinance] = binanceCandles

atomPrice := sdk.MustNewDecFromStr("29.93")
atomVolume := sdk.MustNewDecFromStr("894123.00")
krakenCandles := map[string][]provider.CandlePrice{
"USDT": {{
Price: usdtPrice,
Volume: usdtVolume,
TimeStamp: provider.PastUnixTime(1 * time.Minute),
}},
}
providerCandles[config.ProviderKraken] = krakenCandles

usdtPrice := sdk.MustNewDecFromStr("0.98")
usdtVolume := sdk.MustNewDecFromStr("894123.00")
providerPairs := map[string][]types.CurrencyPair{
config.ProviderBinance: {atomPair},
config.ProviderKraken: {usdtPair},
}

binanceCandles := make(map[string][]provider.CandlePrice, 1)
binanceCandles["ATOM"] = []provider.CandlePrice{
{
convertedCandles, err := convertCandlesToUSD(
zerolog.Nop(),
providerCandles,
providerPairs,
make(map[string]sdk.Dec),
)
require.NoError(t, err)

require.Equal(
t,
atomPrice.Mul(usdtPrice),
convertedCandles["binance"]["ATOM"][0].Price,
)
}

func TestConvertCandlesToUSDFiltering(t *testing.T) {
providerCandles := make(provider.AggregatedProviderCandles, 2)

binanceCandles := map[string][]provider.CandlePrice{
"ATOM": {{
Price: atomPrice,
Volume: atomVolume,
TimeStamp: provider.PastUnixTime(1 * time.Minute),
},
}},
}
providerCandles[config.ProviderBinance] = binanceCandles

krakenCandles := make(map[string][]provider.CandlePrice, 1)
krakenCandles["USDT"] = []provider.CandlePrice{
{
krakenCandles := map[string][]provider.CandlePrice{
"USDT": {{
Price: usdtPrice,
Volume: usdtVolume,
TimeStamp: provider.PastUnixTime(1 * time.Minute),
},
}},
}
providerCandles[config.ProviderKraken] = krakenCandles

gateCandles := map[string][]provider.CandlePrice{
"USDT": {{
Price: usdtPrice,
Volume: usdtVolume,
TimeStamp: provider.PastUnixTime(1 * time.Minute),
}},
}
providerCandles[config.ProviderGate] = gateCandles

okxCandles := map[string][]provider.CandlePrice{
"USDT": {{
Price: sdk.MustNewDecFromStr("100.0"),
Volume: usdtVolume,
TimeStamp: provider.PastUnixTime(1 * time.Minute),
}},
}
providerCandles[config.ProviderOkx] = okxCandles

providerPairs := map[string][]types.CurrencyPair{
config.ProviderBinance: {pairs[0]},
config.ProviderKraken: {pairs[1]},
config.ProviderBinance: {atomPair},
config.ProviderKraken: {usdtPair},
config.ProviderGate: {usdtPair},
config.ProviderOkx: {usdtPair},
}

convertedCandles, err := convertCandlesToUSD(providerCandles, providerPairs)
convertedCandles, err := convertCandlesToUSD(
zerolog.Nop(),
providerCandles,
providerPairs,
make(map[string]sdk.Dec),
)
require.NoError(t, err)

require.Equal(
Expand All @@ -113,48 +178,93 @@ func TestConvertCandlesToUSD(t *testing.T) {

func TestConvertTickersToUSD(t *testing.T) {
providerPrices := make(provider.AggregatedProviderPrices, 2)
pairs := []types.CurrencyPair{
{
Base: "ATOM",
Quote: "USDT",

binanceTickers := map[string]provider.TickerPrice{
"ATOM": {
Price: atomPrice,
Volume: atomVolume,
},
{
Base: "USDT",
Quote: "USD",
}
providerPrices[config.ProviderBinance] = binanceTickers

krakenTicker := map[string]provider.TickerPrice{
"USDT": {
Price: usdtPrice,
Volume: usdtVolume,
},
}
providerPrices[config.ProviderKraken] = krakenTicker

atomPrice := sdk.MustNewDecFromStr("29.93")
atomVolume := sdk.MustNewDecFromStr("894123.00")
providerPairs := map[string][]types.CurrencyPair{
config.ProviderBinance: {atomPair},
config.ProviderKraken: {usdtPair},
}

usdtPrice := sdk.MustNewDecFromStr("0.98")
usdtVolume := sdk.MustNewDecFromStr("894123.00")
convertedTickers, err := convertTickersToUSD(
zerolog.Nop(),
providerPrices,
providerPairs,
make(map[string]sdk.Dec),
)
require.NoError(t, err)

require.Equal(
t,
atomPrice.Mul(usdtPrice),
convertedTickers["binance"]["ATOM"].Price,
)
}

func TestConvertTickersToUSDFiltering(t *testing.T) {
providerPrices := make(provider.AggregatedProviderPrices, 2)

binanceTickers := make(map[string]provider.TickerPrice, 1)
binanceTickers["ATOM"] = provider.TickerPrice{
Price: atomPrice,
Volume: atomVolume,
binanceTickers := map[string]provider.TickerPrice{
"ATOM": {
Price: atomPrice,
Volume: atomVolume,
},
}
providerPrices[config.ProviderBinance] = binanceTickers

krakenTicker := make(map[string]provider.TickerPrice, 1)
krakenTicker["USDT"] = provider.TickerPrice{
Price: usdtPrice,
Volume: usdtVolume,
krakenTicker := map[string]provider.TickerPrice{
"USDT": {
Price: usdtPrice,
Volume: usdtVolume,
},
}
providerPrices[config.ProviderKraken] = krakenTicker

gateTicker := map[string]provider.TickerPrice{
"USDT": krakenTicker["USDT"],
}
providerPrices[config.ProviderGate] = gateTicker

huobiTicker := map[string]provider.TickerPrice{
"USDT": {
Price: sdk.MustNewDecFromStr("10000"),
Volume: usdtVolume,
},
}
providerPrices[config.ProviderHuobi] = huobiTicker

providerPairs := map[string][]types.CurrencyPair{
config.ProviderBinance: {pairs[0]},
config.ProviderKraken: {pairs[1]},
config.ProviderBinance: {atomPair},
config.ProviderKraken: {usdtPair},
config.ProviderGate: {usdtPair},
config.ProviderHuobi: {usdtPair},
}

convertedTickers, err := convertTickersToUSD(providerPrices, providerPairs)
covertedDeviation, err := convertTickersToUSD(
zerolog.Nop(),
providerPrices,
providerPairs,
make(map[string]sdk.Dec),
)
require.NoError(t, err)

require.Equal(
t,
atomPrice.Mul(usdtPrice),
convertedTickers["binance"]["ATOM"].Price,
covertedDeviation["binance"]["ATOM"].Price,
)
}
Loading

0 comments on commit a373b4b

Please sign in to comment.