quotes

package
v0.0.86 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2024 License: GPL-3.0 Imports: 40 Imported by: 0

README

Quotes service

The Quotes Service is a centralized system responsible for recording and disseminating real-time trade data from various trading platforms.

Available drivers:

  • Index Price
  • Binance
  • Kraken
  • Opendax
  • Bitfaker
  • Uniswap v3
    • based on Subgraph API
    • based on go-ethereum
  • Syncswap

Interface to connect

type Driver interface {
	Start() error
	Stop() error
	Subscribe(market Market) error
	Unsubscribe(market Market) error
}

Types of price sources

Top Tier CEX Altcoins CEX FIAT DEX
BitFinex Gate UpBit Sushi
OKX MEXC Kraken PancakeSwap
Binance KuCoin Coinbase Uniswap
weight: 20 weight: 5 weight: 15 weight: 50

Last Price

For candle sticks, Recent trade, tickers last price is calculated as follows:

last_price = price

Index Price

Index Price is used mainly in risk management for portfolio evaluation and is aggregated from multiple sources.

Trades from all sources are coming into one queue. IndexAggregator reads trades sequentially, calling calculateIndexPrice() method from a configured strategy.

You can leverage your index price calculation strategy by implementing the priceCalculator interface and passing it as an argument to the IndexAggregator constructor.

type priceCalculator interface {
	calculateIndexPrice(trade TradeEvent) (decimal.Decimal, bool)
}

The default strategy for index price calculation is Volume Weighted Average Price (VWAP), additionally weighted by trade source importance.


sourceMultiplier = (tradeSourceWeight/(activeSourcesWeightsSum(market)))

var totalPriceVolume, totalVolume num
for trade in range(N trades(market)) {
		totalPriceVolume += (Price * Volume * sourceMultiplier)
		totalVolume += (Volume * sourceMultiplier)
}

index_price = totalPriceVolume / totalVolume 

In the VWAP strategy Price cache is used to store a queue containing the last N (default=20) trades for each market.

Drivers may support different markets. That's why the price cache additionally stores active drivers for each market. By default, no drivers are active. When a trade is added, the source of the trade becomes active for a market.

Calculation flow

Drivers weights config example:

driverWeights = [{Binance: 20}, {Uniswap: 10}, {Kraken: 15}]

Trade received:

Trade {
	Source: Binance
	Market: btcusdt
	Volume: 0.5
	Price: 44000
}

The trade is skipped if the trade price or volume is zero.

  1. Calculate sourceMultiplier. Select active drivers for the market. By default, all drivers are not active. When the first trade from a driver is received, a market becomes active. Let's say, we are receiving btcusdt trades only from Binance and Uniswap.
activeSourcesWeightsSum(btcusdt) = {driversWeights[Binance] + driversWeights[Uniswap] = 2 + 1} = 3
tradeSourceWeight = driverWeights[Binance] = 2

sourceMultiplier = (tradeSourceWeight/(activeSourcesWeightsSum(market)))

  1. Add trade data to the price cache.
priceCache.AddTrade(event.Market, event.Price, event.Amount, sourceMultiplier)
  1. Fetch the last n trades from PriceCache.
var totalPriceVolume, totalVolume num
for trade in range(priceCache.trades(btcusdt)) {
		totalPriceVolume += (Price * Volume * sourceMultiplier)
		totalVolume += (Volume * sourceMultiplier)
}
  1. Return Index Price
index_price = totalPriceVolume / totalVolume 

VWA20 Index Price Example Over 20 trades

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Amount 1.0 1.1 2.4 0.2 2.0 3.3 4.0 2.9 1.0 0.1 0.01 0.04 9.0 0.4 4.4 5.0 6.0 0.1 2.0 1.0
Price 40000 42000 41500 44000 43000 40000 41000 42000 43000 42000 45500 41000 41500 42000 44000 46000 47000 46000 44000 42000
VWA20 40000 41047 41288 41404 41880 41260 41185 41325 41418 41422 41424 41423 41448 41457 41808 42377 43024 43031 43074 43051

More examples and test scenarios can be found in index_test.go.

How Uniswap adapter calculates swap price

Method 1: from sqrtPriceX96
Motivation

Uniswap V3 uses Q notation, which is a type of fixed-point arithmetics, to encode swap price.

Q notation allows variables to remain integers, but function similarly to floating point numbers. Additionally piping the price through square root allows to reduce dimentionality of the number. Predictable size of the number encoded this way enables efficient caching and fast retrieval of data from chain.

How to calculate price?

Actually this is a two step process:

  1. Decode price
  2. Convert the price into wei/wei ratio
Step 1

Here's the formula:

sqrtPrice = sqrtPriceX96 / (2^96)
price = sqrtPrice^2
Step 2

ERC20 tokens have built in decimal values. For example, 1 WETH actually represents WETH in the contract whereas USDC is 10^6. USDC has 6 decimals and WETH has 18. So the price calculated on step 1 actually depicts [wei of token0] / [unit token of token1]. Now let's convert that into [wei] / [wei] ratio:

price0 = price / (10^(decimal1-decimal0))
price1 = 1 / price0
Method 2: ticks

This method requires a different set of inputs to calculate.

Motivation

Ticks are related directly to price and enable simpler calculations compared to [[#Method 1]]. This requires just a tick value and a one-shot formula.

How to calculate price?

To convert from tick t, to price, take 1.0001^t to get the corresponding price.

price  = 1.0001^tick
price0 = price / (10^(decimal1-decimal0))
price1 = 1 / price0

It's also possible to convert sqrtPriceX96 to tick:

tick = floor[ log((sqrtPriceX96 / (2^96))^2) / log(1.0001) ]

TODO:

  • add specs or amendments to current interface

Documentation

Overview

Package quotes implements multiple price feed adapters.

Index

Constants

This section is empty.

Variables

View Source
var (
	DriverBinance   = DriverType{"binance"}
	DriverKraken    = DriverType{"kraken"}
	DriverOpendax   = DriverType{"opendax"}
	DriverBitfaker  = DriverType{"bitfaker"}
	DriverUniswapV3 = DriverType{"uniswap_v3"}
	DriverSyncswap  = DriverType{"syncswap"}
	DriverQuickswap = DriverType{"quickswap"}
	DriverSectaV2   = DriverType{"secta_v2"}
	DriverSectaV3   = DriverType{"secta_v3"}
	DriverInternal  = DriverType{"internal"} // Internal trades
)
View Source
var (
	// TakerTypeUnknown represents the trade,
	// for which you can't determine its type,
	// and therefore the taker side cannot be deduced.
	TakerTypeUnknown = TakerType{""}
	// TakerTypeBuy represents a "buy" trade.
	// Its value is set to "sell" because the sell order it
	// was matched with was present in the order book before,
	// therefore the taker is the "sell" side.
	TakerTypeBuy = TakerType{"sell"}
	// TakerTypeSell represents a "sell" trade.
	// It's value is set to "buy" because the buy order it
	// was matched with was present in the order book before,
	// therefore the taker is the "buy" side.
	TakerTypeSell = TakerType{"buy"}
)
View Source
var (
	ErrNotStarted     = errors.New("driver is not started; call `Start()` first or wait for it to finish")
	ErrAlreadyStarted = errors.New("driver is already started")
	ErrAlreadyStopped = errors.New("driver is already stopped")
	ErrInvalidWsUrl   = errors.New("websocket URL must start with ws:// or wss://")
	ErrNotSubbed      = errors.New("market not subscribed")
	ErrAlreadySubbed  = errors.New("market already subscribed")
	ErrFailedSub      = errors.New("failed to subscribe to market")
	ErrFailedUnsub    = errors.New("failed to unsubscribe from market")
	ErrSwapParsing    = errors.New("recovered in from panic during swap parsing")
)
View Source
var MarketSubscriptions = prometheus.NewGaugeVec(
	prometheus.GaugeOpts{
		Name: "price_feed_value",
		Help: "Current trades subscriptions by provider and market.",
	},
	[]string{"provider", "market"},
)

Functions

This section is empty.

Types

type BinanceConfig added in v0.0.23

type BinanceConfig struct {
	USDCtoUSDT         bool          `yaml:"usdc_to_usdt" env:"USDC_TO_USDT" env-default:"true"`
	BatchPeriod        time.Duration `yaml:"batch_period" env:"BATCH_PERIOD" env-default:"5s"`
	AssetsUpdatePeriod time.Duration `yaml:"assets_update_period" env:"ASSETS_UPDATE_PERIOD" env-default:"5m"`
	Filter             FilterConfig  `yaml:"filter" env-prefix:"FILTER_"`
}

type BitfakerConfig added in v0.0.23

type BitfakerConfig struct {
	Period time.Duration `yaml:"period" env:"PERIOD" env-default:"5s"`
	Filter FilterConfig  `yaml:"filter" env-prefix:"FILTER_"`
}

type ConfFunc added in v0.0.76

type ConfFunc func(*indexStrategy)

func WithCustomWeights added in v0.0.76

func WithCustomWeights(driversWeights map[DriverType]decimal.Decimal) ConfFunc

WithCustomWeights configures custom drivers weights. Should be passed as an argument to the NewStrategy() constructor.

type Config

type Config struct {
	Drivers []DriverType `yaml:"drivers" env:"QUOTES_DRIVERS" env-default:"binance,syncswap"`
	Index   IndexConfig  `yaml:"index" env-prefix:"QUOTES_INDEX_"`

	Binance   BinanceConfig   `yaml:"binance" env-prefix:"QUOTES_BINANCE_"`
	Kraken    KrakenConfig    `yaml:"kraken" env-prefix:"QUOTES_KRAKEN_"`
	Opendax   OpendaxConfig   `yaml:"opendax" env-prefix:"QUOTES_OPENDAX_"`
	Bitfaker  BitfakerConfig  `yaml:"bitfaker" env-prefix:"QUOTES_BITFAKER_"`
	UniswapV3 UniswapV3Config `yaml:"uniswap_v3" env-prefix:"QUOTES_UNISWAP_V3_"`
	Syncswap  SyncswapConfig  `yaml:"syncswap" env-prefix:"QUOTES_SYNCSWAP_"`
	Quickswap QuickswapConfig `yaml:"quickswap" env-prefix:"QUOTES_QUICKSWAP_"`
	SectaV2   SectaV2Config   `yaml:"secta_v2" env-prefix:"QUOTES_SECTA_V2_"`
	SectaV3   SectaV3Config   `yaml:"secta_v3" env-prefix:"QUOTES_SECTA_V3_"`
}

func NewConfigFromEnv added in v0.0.22

func NewConfigFromEnv() (Config, error)

func NewConfigFromFile added in v0.0.24

func NewConfigFromFile(path string) (Config, error)

func (Config) GetByDriverType added in v0.0.61

func (config Config) GetByDriverType(driver DriverType) (Config, error)

type DisabledFilter added in v0.0.30

type DisabledFilter struct{}

func (DisabledFilter) Allow added in v0.0.30

func (f DisabledFilter) Allow(trade TradeEvent) bool

type Driver

type Driver interface {
	ActiveDrivers() []DriverType
	ExchangeType() ExchangeType
	Start() error
	Stop() error
	Subscribe(market Market) error
	Unsubscribe(market Market) error
	SetInbox(inbox <-chan TradeEvent)
}

func NewDriver

func NewDriver(config Config, outbox chan<- TradeEvent) (Driver, error)

type DriverType

type DriverType struct {
	// contains filtered or unexported fields
}

DriverType is enum that represents all available quotes providers.

func ToDriverType

func ToDriverType(raw string) (DriverType, error)

func (DriverType) MarshalJSON

func (t DriverType) MarshalJSON() ([]byte, error)

func (DriverType) MarshalYAML

func (t DriverType) MarshalYAML() (any, error)

func (*DriverType) SetValue added in v0.0.61

func (t *DriverType) SetValue(s string) error

func (DriverType) String

func (d DriverType) String() string

func (*DriverType) UnmarshalJSON

func (t *DriverType) UnmarshalJSON(raw []byte) error

func (*DriverType) UnmarshalYAML

func (t *DriverType) UnmarshalYAML(value *yaml.Node) error

type ExchangeType added in v0.0.61

type ExchangeType string

ExchangeType is enum that represents the type of exchange. It is not implemented with struct based enums pattern because it's not used as an input parameter for any function or method.

var (
	ExchangeTypeUnspecified ExchangeType = ""
	ExchangeTypeCEX         ExchangeType = "cex"
	ExchangeTypeDEX         ExchangeType = "dex"
	ExchangeTypeHybrid      ExchangeType = "hybrid"
)

type Filter added in v0.0.30

type Filter interface {
	Allow(trade TradeEvent) bool
}

func NewFilter added in v0.0.41

func NewFilter(conf FilterConfig) Filter

type FilterConfig added in v0.0.30

type FilterConfig struct {
	FilterType FilterType `yaml:"filter_type" env:"TYPE" env-default:"disabled"`

	SamplerFilter   SamplerFilterConfig   `yaml:"sampler" env-prefix:"SAMPLER_"`
	PriceDiffFilter PriceDiffFilterConfig `yaml:"price_diff" env-prefix:"PRICE_DIFF_"`
}

type FilterType added in v0.0.30

type FilterType string
const (
	SamplerFilterType   FilterType = "sampler"
	PriceDiffFilterType FilterType = "price_diff"
	DisabledFilterType  FilterType = "disabled"
)

type IndexConfig added in v0.0.26

type IndexConfig struct {
	TradesCached   int                 `yaml:"trades_cached" env:"TRADES_CACHED" env-default:"20"`
	BufferSeconds  int                 `yaml:"buffer_minutes" env:"BUFFER_MINUTES" env-default:"5"`
	MarketsMapping map[string][]string `yaml:"markets_mapping" env:"MARKETS_MAPPING"`
	// MaxPriceDiff has default of `0.2` because our default leverage is 5x,
	// and so if the user opens order on his full balance, he'll get liquidated on 20% price change.
	MaxPriceDiff decimal.Decimal `yaml:"max_price_diff" env:"MAX_PRICE_DIFF" env-default:"0.2"`
}

type KrakenConfig added in v0.0.23

type KrakenConfig struct {
	URL             string        `yaml:"url" env:"URL" env-default:"wss://ws.kraken.com"`
	ReconnectPeriod time.Duration `yaml:"period" env:"RECONNECT_PERIOD" env-default:"5s"`
	Filter          FilterConfig  `yaml:"filter" env-prefix:"FILTER_"`
}

type Market

type Market struct {
	// contains filtered or unexported fields
}

func NewMarket added in v0.0.26

func NewMarket(base, quote string) Market

func NewMarketDerived added in v0.0.77

func NewMarketDerived(base, quote, convertQuoteTo string) Market

func NewMarketFromString added in v0.0.26

func NewMarketFromString(s string) (Market, bool)

NewMarketFromString returns a new Market from a string "btc/usdt" -> Market{btc, usdt} NOTE: string must contain "/" delimiter

func NewMarketWithMainQuote added in v0.0.63

func NewMarketWithMainQuote(base, quote, mainQuote string) Market

func (Market) ApplyMainQuote added in v0.0.63

func (m Market) ApplyMainQuote() Market

func (Market) Base added in v0.0.26

func (m Market) Base() string

func (Market) IsEmpty added in v0.0.26

func (m Market) IsEmpty() bool

func (Market) LegacyQuote added in v0.0.63

func (m Market) LegacyQuote() string

func (Market) MarshalJSON added in v0.0.46

func (m Market) MarshalJSON() ([]byte, error)

func (Market) Quote added in v0.0.26

func (m Market) Quote() string

func (Market) String added in v0.0.26

func (m Market) String() string

String returns a string representation of the market. Example: `Market{btc, usdt}` -> "btc/usdt"

func (Market) StringWithoutMain added in v0.0.63

func (m Market) StringWithoutMain() string

func (*Market) UnmarshalJSON added in v0.0.46

func (m *Market) UnmarshalJSON(raw []byte) error

type OpendaxConfig added in v0.0.23

type OpendaxConfig struct {
	URL             string        `yaml:"url" env:"URL" env-default:"wss://alpha.yellow.org/api/v1/finex/ws"`
	ReconnectPeriod time.Duration `yaml:"period" env:"RECONNECT_PERIOD" env-default:"5s"`
	Filter          FilterConfig  `yaml:"filter" env-prefix:"FILTER_"`
}

type PriceCache added in v0.0.76

type PriceCache struct {
	// contains filtered or unexported fields
}

func (*PriceCache) AddTrade added in v0.0.76

func (p *PriceCache) AddTrade(market Market, price, volume decimal.Decimal, timestamp time.Time, source DriverType)

AddTrade adds a new trade to the cache for a market.

func (*PriceCache) GetIndexPrice added in v0.0.76

func (p *PriceCache) GetIndexPrice(event *TradeEvent) (decimal.Decimal, bool)

type PriceDiffFilter added in v0.0.30

type PriceDiffFilter struct {
	// contains filtered or unexported fields
}

func (*PriceDiffFilter) Allow added in v0.0.30

func (f *PriceDiffFilter) Allow(trade TradeEvent) bool

type PriceDiffFilterConfig added in v0.0.30

type PriceDiffFilterConfig struct {
	Threshold string `yaml:"threshold" env:"THRESHOLD" env-default:"5"`
}

type QuickswapConfig added in v0.0.40

type QuickswapConfig struct {
	URL        string `yaml:"url" env:"URL"`
	AssetsURL  string `` /* 132-byte string literal not displayed */
	MappingURL string `` /* 137-byte string literal not displayed */
	// PoolFactoryAddress is the address of the factory contract.
	// See docs at https://docs.quickswap.exchange/technical-reference/smart-contracts/v3/factory.
	// Note that the contract used in this lib is compiled from https://github.com/code-423n4/2022-09-quickswap.
	PoolFactoryAddress string       `yaml:"pool_factory_address" env:"POOL_FACTORY_ADDRESS" env-default:"0x411b0fAcC3489691f28ad58c47006AF5E3Ab3A28"`
	Filter             FilterConfig `yaml:"filter" env-prefix:"FILTER_"`
}

type SamplerFilterConfig added in v0.0.30

type SamplerFilterConfig struct {
	Percentage int64 `yaml:"percentage" env:"PERCENTAGE" env-default:"5"`
}

type SectaV2Config added in v0.0.77

type SectaV2Config struct {
	URL            string       `yaml:"url" env:"URL"`
	AssetsURL      string       `` /* 134-byte string literal not displayed */
	MappingURL     string       `` /* 139-byte string literal not displayed */
	FactoryAddress string       `yaml:"factory_address" env:"FACTORY_ADDRESS" env-default:"0x8Ad39bf99765E24012A28bEb0d444DE612903C43"`
	Filter         FilterConfig `yaml:"filter" env-prefix:"FILTER_"`
}

type SectaV3Config added in v0.0.77

type SectaV3Config struct {
	URL            string       `yaml:"url" env:"URL"`
	AssetsURL      string       `` /* 134-byte string literal not displayed */
	MappingURL     string       `` /* 139-byte string literal not displayed */
	FactoryAddress string       `yaml:"factory_address" env:"FACTORY_ADDRESS" env-default:"0x9BD425a416A276C72a13c13bBd8145272680Cf07"`
	Filter         FilterConfig `yaml:"filter" env-prefix:"FILTER_"`
}

type SyncswapConfig added in v0.0.23

type SyncswapConfig struct {
	URL                       string       `yaml:"url" env:"URL"`
	AssetsURL                 string       `` /* 134-byte string literal not displayed */
	MappingURL                string       `` /* 139-byte string literal not displayed */
	ClassicPoolFactoryAddress string       `` /* 127-byte string literal not displayed */
	StablePoolFactoryAddress  string       `yaml:"stable_pool_factory_address" env:"STABLE_POOL_FACTORY_ADDRESS" env-default:"0xE4CF807E351b56720B17A59094179e7Ed9dD3727"`
	StablePoolMarkets         []string     `yaml:"stable_pool_markets" env:"STABLE_POOL_MARKETS" env-default:"usdt/usdc"` // `env-default` tag value is a comma separated list of markets as in `usdt/usdc, usdc/dai`
	Filter                    FilterConfig `yaml:"filter" env-prefix:"FILTER_"`
}

type TakerType

type TakerType struct {
	// contains filtered or unexported fields
}

TakerType is enum that represents the side of taker in a trade.

func ToTakerType

func ToTakerType(raw string) (TakerType, error)

func (TakerType) MarshalJSON

func (t TakerType) MarshalJSON() ([]byte, error)

func (TakerType) MarshalYAML

func (t TakerType) MarshalYAML() (any, error)

func (TakerType) String

func (t TakerType) String() string

func (*TakerType) UnmarshalJSON

func (t *TakerType) UnmarshalJSON(raw []byte) error

func (*TakerType) UnmarshalYAML

func (t *TakerType) UnmarshalYAML(value *yaml.Node) error

type TradeEvent

type TradeEvent struct {
	Source    DriverType
	Market    Market // e.g. `btc/usdt`
	Price     decimal.Decimal
	Amount    decimal.Decimal
	Total     decimal.Decimal
	TakerType TakerType
	CreatedAt time.Time
}

TradeEvent is a generic container for trades received from providers.

type UniswapV3Config added in v0.0.69

type UniswapV3Config struct {
	URL            string       `yaml:"url" env:"URL"`
	AssetsURL      string       `` /* 130-byte string literal not displayed */
	MappingURL     string       `` /* 135-byte string literal not displayed */
	FactoryAddress string       `yaml:"factory_address" env:"FACTORY_ADDRESS" env-default:"0x1F98431c8aD98523631AE4a59f267346ea31F984"`
	Filter         FilterConfig `yaml:"filter" env-prefix:"FILTER_"`
}

Directories

Path Synopsis
Types and helpers for easy formatting and parsing open-finance protocol messages.
Types and helpers for easy formatting and parsing open-finance protocol messages.
App for testing quotes drivers.
App for testing quotes drivers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL