From 61a0f9a4adab27563f8e47396addbedd79de1923 Mon Sep 17 00:00:00 2001 From: cryptohazard Date: Fri, 25 Aug 2023 17:54:38 +0200 Subject: [PATCH 1/3] innit thalex --- cmd/cli/flags.go | 2 +- pkg/provider/deribit/deribit.go | 12 +-- pkg/provider/providers.go | 4 +- pkg/provider/thalex/thalex.go | 128 ++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 pkg/provider/thalex/thalex.go diff --git a/cmd/cli/flags.go b/cmd/cli/flags.go index 6145618c..e382ada3 100644 --- a/cmd/cli/flags.go +++ b/cmd/cli/flags.go @@ -46,7 +46,7 @@ func listProviderNames() []string { *providers = "" } if *cex { - *providers += ",deribit,delta" + *providers += ",deribit,delta,thalex" } if *dex { *providers += ",lyra,synquote,zeta" diff --git a/pkg/provider/deribit/deribit.go b/pkg/provider/deribit/deribit.go index 4bc31795..a61361bb 100644 --- a/pkg/provider/deribit/deribit.go +++ b/pkg/provider/deribit/deribit.go @@ -16,7 +16,9 @@ import ( "github.com/teal-finance/rainbow/pkg/rainbow" ) -var log = emo.NewZone("Deribit") +const name = "Deribit" + +var log = emo.NewZone(name) const baseURL = "https://www.deribit.com/options/" @@ -25,7 +27,7 @@ type Provider struct { } func (Provider) Name() string { - return "Deribit" + return name } // adaptiveMinSleepTime to rate limit the Deribit API. @@ -41,7 +43,7 @@ const maxBytesToRead = 2_000_000 func (p *Provider) Options() ([]rainbow.Option, error) { if p.ar.Name == "" { - p.ar = garcon.NewAdaptiveRate("Deribit", adaptiveMinSleepTime) + p.ar = garcon.NewAdaptiveRate(name, adaptiveMinSleepTime) } instruments, err := p.query("BTC") @@ -88,7 +90,7 @@ func (p *Provider) query(coin string) ([]instrument, error) { const api = "https://deribit.com/api/v2/public/get_instruments?currency=" const opts = "&expired=false&kind=option" url := api + coin + opts - log.Info("Deribit " + url) + log.Info(name + url) var result instrumentsResult err := p.ar.Get(coin, url, &result, maxBytesToRead) @@ -168,7 +170,7 @@ func (p *Provider) fillOptions(instruments []instrument, depth uint32) ([]rainbo apiurl := api + instruments[i].InstrumentName if err := p.ar.Get(instruments[i].InstrumentName, apiurl, &result); err != nil { lastError = err - log.Warn("Deribit book " + err.Error()) + log.Warn(name + " book " + err.Error()) } // API doc: https://docs.deribit.com/#public-get_index_price_names diff --git a/pkg/provider/providers.go b/pkg/provider/providers.go index 640ab8d2..079debc9 100644 --- a/pkg/provider/providers.go +++ b/pkg/provider/providers.go @@ -16,6 +16,7 @@ import ( "github.com/teal-finance/rainbow/pkg/provider/lyra" "github.com/teal-finance/rainbow/pkg/provider/synquote" "github.com/teal-finance/rainbow/pkg/provider/thales" + "github.com/teal-finance/rainbow/pkg/provider/thalex" "github.com/teal-finance/rainbow/pkg/rainbow" ) @@ -26,7 +27,8 @@ func AllProviders() []rainbow.Provider { return []rainbow.Provider{ &deribit.Provider{}, lyra.Provider{}, - synquote.Provider{}, // paused cause they changed layer and I didn't get the new API link + synquote.Provider{}, + &thalex.Provider{}, // zetamarkets.Provider{}, // paused platform due to current market conditions thales.Provider{}, // Thales = exotic options -> https://teal.finance/rainbow/exotic deltaexchange.Provider{}, // last because slow (rate limit) diff --git a/pkg/provider/thalex/thalex.go b/pkg/provider/thalex/thalex.go new file mode 100644 index 00000000..523c6c2a --- /dev/null +++ b/pkg/provider/thalex/thalex.go @@ -0,0 +1,128 @@ +// Copyright 2023 Teal.Finance/Rainbow contributors +// This file is part of Teal.Finance/Rainbow, +// a screener for DeFi options under the MIT License. +// SPDX-License-Identifier: MIT + +package thalex + +import ( + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/teal-finance/emo" + "github.com/teal-finance/garcon" + "github.com/teal-finance/rainbow/pkg/rainbow" +) + +var log = emo.NewZone(name) + +const baseURL = "https://www." +const name = "Thalex" + +type Provider struct { + ar garcon.AdaptiveRate +} + +func (Provider) Name() string { + return name +} + +// adaptiveMinSleepTime to rate limit the Thalex API. +// https://thalex.com/docs/#section/API-description/Message-rates +// The maximum number of matching engine messages (buy, sell, amend, delete, etc.) per connection per second is 10. +// When the connection is set to non-persistent (private/set_cancel_on_disconnect), this limit is raised to 50. +const adaptiveMinSleepTime = 25 * time.Millisecond + +// Hour at which the options expires = 8:00 UTC. +const Hour = 8 + +// maxBytesToRead prevents wasting memory/CPU when receiving an abnormally huge response from Thalex API. +// we put the same param as Deribit +const maxBytesToRead = 2_000_000 + +func (p *Provider) Options() ([]rainbow.Option, error) { + if p.ar.Name == "" { + p.ar = garcon.NewAdaptiveRate(name, adaptiveMinSleepTime) + } + + instruments, err := p.query() + if err != nil { + return nil, err + } + j := 9 + spew.Dump(instruments[j : j+1]) + i := instruments[j] + var result tickers + apiurl := "https://thalex.com/api/v2/public/ticker" + "?instrument_name=" + i.InstrumentName + if err := p.ar.Get(i.InstrumentName, apiurl, &result); err != nil { + log.Warn(name + " book " + err.Error()) + } + + spew.Dump(result) + + return []rainbow.Option{}, nil +} + +func (p *Provider) query() ([]Instruments, error) { + const api = "https://thalex.com/api/v2/public/instruments" + url := api + log.Info(name + url) + + var result instrumentsResult + err := p.ar.Get("", url, &result, maxBytesToRead) + if err != nil { + return nil, err + } + + return result.Result, nil +} + +type tickers struct { + Result Ticker `json:"result"` +} + +type Ticker struct { + MarkPrice float64 `json:"mark_price"` + BestBidPrice int `json:"best_bid_price"` + BestBidAmount float64 `json:"best_bid_amount"` + BestAskPrice int `json:"best_ask_price"` + BestAskAmount float64 `json:"best_ask_amount"` + LastPrice int `json:"last_price"` + Iv float64 `json:"iv"` + Delta float64 `json:"delta"` + Volume24H float64 `json:"volume_24h"` + Value24H float64 `json:"value_24h"` + LowPrice24H float64 `json:"low_price_24h"` + HighPrice24H float64 `json:"high_price_24h"` + Change24H float64 `json:"change_24h"` + Index float64 `json:"index"` + Forward float64 `json:"forward"` + Funding_mark float64 `json:"funding_mark"` + Funding_rate float64 `json:"funding_rate"` + CollarLow float64 `json:"collar_low"` + CollarHigh float64 `json:"collar_high"` + OpenInterest float64 `json:"open_interest"` +} + +type instrumentsResult struct { + Result []Instruments `json:"result"` +} + +type Instruments struct { + InstrumentName string `json:"instrument_name"` + Product string `json:"product"` + TickSize float64 `json:"tick_size"` + VolumeTickSize float64 `json:"volume_tick_size"` + Underlying string `json:"underlying"` + Type string `json:"type"` + OptionType string `json:"option_type,omitempty"` + ExpiryDate string `json:"expiry_date"` + ExpirationTimestamp int `json:"expiration_timestamp"` + StrikePrice float64 `json:"strike_price,omitempty"` + BaseCurrency string `json:"base_currency"` + CreateTime float64 `json:"create_time"` + Legs []struct { + InstrumentName string `json:"instrument_name"` + Quantity int `json:"quantity"` + } `json:"legs,omitempty"` +} From 024b498a4e127376c1a9ed817d2062934b591dc4 Mon Sep 17 00:00:00 2001 From: cryptohazard Date: Sat, 26 Aug 2023 00:27:03 +0200 Subject: [PATCH 2/3] thalex done --- pkg/provider/thalex/thalex.go | 98 ++++++++++++++++++++++++++++------- pkg/rainbow/option.go | 11 ++-- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/pkg/provider/thalex/thalex.go b/pkg/provider/thalex/thalex.go index 523c6c2a..d266247d 100644 --- a/pkg/provider/thalex/thalex.go +++ b/pkg/provider/thalex/thalex.go @@ -6,9 +6,9 @@ package thalex import ( + "strings" "time" - "github.com/davecgh/go-spew/spew" "github.com/teal-finance/emo" "github.com/teal-finance/garcon" "github.com/teal-finance/rainbow/pkg/rainbow" @@ -16,7 +16,7 @@ import ( var log = emo.NewZone(name) -const baseURL = "https://www." +const baseURL = "https://thalex.com/api/v2/public/" const name = "Thalex" type Provider struct { @@ -49,24 +49,18 @@ func (p *Provider) Options() ([]rainbow.Option, error) { if err != nil { return nil, err } - j := 9 - spew.Dump(instruments[j : j+1]) - i := instruments[j] - var result tickers - apiurl := "https://thalex.com/api/v2/public/ticker" + "?instrument_name=" + i.InstrumentName - if err := p.ar.Get(i.InstrumentName, apiurl, &result); err != nil { - log.Warn(name + " book " + err.Error()) - } - spew.Dump(result) - - return []rainbow.Option{}, nil + options, err := p.fillOptions(instruments) + if err != nil { + return nil, err + } + return options, nil } -func (p *Provider) query() ([]Instruments, error) { - const api = "https://thalex.com/api/v2/public/instruments" +func (p *Provider) query() ([]Instrument, error) { + const api = baseURL + "instruments" url := api - log.Info(name + url) + log.Info(name + " " + url) var result instrumentsResult err := p.ar.Get("", url, &result, maxBytesToRead) @@ -77,15 +71,79 @@ func (p *Provider) query() ([]Instruments, error) { return result.Result, nil } +func (p *Provider) fillOptions(instruments []Instrument) ([]rainbow.Option, error) { + options := make([]rainbow.Option, 0, len(instruments)) + var err error + + var result tickers + + for _, i := range instruments { + if i.Type != "option" { + continue + } + apiurl := baseURL + "ticker?instrument_name=" + i.InstrumentName + if err := p.ar.Get(i.InstrumentName, apiurl, &result); err != nil { + log.Warn(name + " book " + err.Error()) + } + + asset := getUnderlying(i.Underlying) + + o := rainbow.Option{ + Name: i.InstrumentName, + Type: strings.ToUpper(i.OptionType), + UnderlyingAsset: asset, + Asset: asset, + Strike: i.StrikePrice, + Expiry: i.ExpiryDate + " 08:00:00", + ExchangeType: "CEX", + Chain: "-", + Layer: "-", + LayerName: "-", + Provider: name, + UnderlyingQuote: i.BaseCurrency, + QuoteCurrency: "USD", + URL: "https://thalex.com/exchange/options?underlying=" + i.Product + "&expiration=" + i.ExpiryDate, + OpenInterest: result.Result.OpenInterest * result.Result.Index, + MarketIV: 100 * result.Result.Iv, + } + o.Bid = append(o.Bid, rainbow.Order{ + Price: result.Result.BestBidPrice, + Size: result.Result.BestBidAmount, + }) + o.Ask = append(o.Ask, rainbow.Order{ + Price: result.Result.BestAskPrice, + Size: result.Result.BestAskAmount, + }) + + options = append(options, o) + } + + return options, err + +} +func getUnderlying(u string) string { + coin := "" + switch u { + case "ETHUSD": + coin = "ETH" + case "BTCUSD": + coin = "BTC" + default: + log.Warn(name + " unknow underlying instrument " + u) + coin = "TTT" + } + return coin +} + type tickers struct { Result Ticker `json:"result"` } type Ticker struct { MarkPrice float64 `json:"mark_price"` - BestBidPrice int `json:"best_bid_price"` + BestBidPrice float64 `json:"best_bid_price"` BestBidAmount float64 `json:"best_bid_amount"` - BestAskPrice int `json:"best_ask_price"` + BestAskPrice float64 `json:"best_ask_price"` BestAskAmount float64 `json:"best_ask_amount"` LastPrice int `json:"last_price"` Iv float64 `json:"iv"` @@ -105,10 +163,10 @@ type Ticker struct { } type instrumentsResult struct { - Result []Instruments `json:"result"` + Result []Instrument `json:"result"` } -type Instruments struct { +type Instrument struct { InstrumentName string `json:"instrument_name"` Product string `json:"product"` TickSize float64 `json:"tick_size"` diff --git a/pkg/rainbow/option.go b/pkg/rainbow/option.go index b16fe3c8..6abfc619 100644 --- a/pkg/rainbow/option.go +++ b/pkg/rainbow/option.go @@ -16,11 +16,11 @@ import "fmt" // Work on the csv type Option struct { - Name string `json:"name"` // ASSET-DATE-Strike-OptionsType - Type string `json:"type"` // CALL / PUT // TODO add exotic like binary and perp(squeeth) - Asset string `json:"asset"` // ETH, BTC, SOL, ... the crypto we are exposed to - UnderlyingAsset string `json:"underlyingasset"` // sETH, WETH, sBTC, WBTC ... the actual asset token we track - + Name string `json:"name"` // ASSET-DATE-Strike-OptionsType + Type string `json:"type"` // CALL / PUT // TODO add exotic like binary and perp(squeeth) + Asset string `json:"asset"` // ETH, BTC, SOL, ... the crypto we are exposed to + UnderlyingAsset string `json:"underlyingasset"` // sETH, WETH, sBTC, WBTC ... the actual asset token we track + Strike float64 `json:"strike"` // Option strike Expiry string `json:"expiry"` // Expiry date in Format("2006-01-02 15:04:05") ExchangeType string `json:"exchange"` // CEX / DEX Chain string `json:"chain"` // Ethereum, Solana and "–" for CEX (Deribit) @@ -37,7 +37,6 @@ type Option struct { MarketIV float64 `json:"markIV"` // When it is present on the provider, we store their Market IV // see https://corporatefinanceinstitute.com/resources/knowledge/trading-investing/option-greeks/ Greeks TheGreeks `json:"greeks"` // Greeks measure the sensitivity of an option’s price to its the underlying determining parameters. - Strike float64 `json:"strike"` // OpenInterest float64 `json:"openinterest"` // ProtocolID string `json:"protocolID"` // when present log the ID of that instrument on the provider } From eaa328bcf2c6218d23ddbd03297cb1113c969922 Mon Sep 17 00:00:00 2001 From: cryptohazard Date: Sat, 26 Aug 2023 00:38:46 +0200 Subject: [PATCH 3/3] prepare frontend for thalex --- frontend/src/api/queries.ts | 2 +- frontend/src/const/filter_presets.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/api/queries.ts b/frontend/src/api/queries.ts index a53b949e..f0378ec9 100644 --- a/frontend/src/api/queries.ts +++ b/frontend/src/api/queries.ts @@ -4,7 +4,7 @@ // SPDX-License-Identifier: MIT const classicOptionsQuery = `{ - rows(providers: ["Delta Exchange", "Deribit","Lyra::Arbitrum", "Lyra::Optimism", "Synquote", "Zeta"]) { + rows(providers: ["Delta Exchange", "Deribit", "Thalex", "Lyra::Arbitrum", "Lyra::Optimism", "Synquote", "Zeta"]) { date expiry provider diff --git a/frontend/src/const/filter_presets.ts b/frontend/src/const/filter_presets.ts index 92210bf7..3b3b6b62 100644 --- a/frontend/src/const/filter_presets.ts +++ b/frontend/src/const/filter_presets.ts @@ -32,7 +32,7 @@ const filterPresets: Record = { }, 'CEXes': { assets: { 'defaultValue': true }, - providers: { 'defaultValue': false, 'Deribit': true, 'Delta Exchange': true } + providers: { 'defaultValue': false, 'Deribit': true, 'Delta Exchange': true, 'Thalex': true } }, 'DEXes': { assets: { 'defaultValue': true }, @@ -53,14 +53,14 @@ const filterPresets: Record = { 'SOL': { assets: { 'defaultValue': false, 'SOL': true }, providers: { 'defaultValue': true }, - },//TODO separate L1 & L2 + }, 'L1 tokens': { assets: { 'defaultValue': false, 'BTC': true, 'ETH': true, 'SOL': true, 'TRX': true, 'LTC': true, 'BNB': true, }, providers: { 'defaultValue': true }, - },//TODO separate L1 & L2 + }, 'L2 tokens': { assets: { 'defaultValue': false,