Skip to content

Commit

Permalink
Merge pull request #146 from teal-finance/thalex
Browse files Browse the repository at this point in the history
Thalex
  • Loading branch information
cryptohazard authored Aug 25, 2023
2 parents 83e00e7 + eaa328b commit c3dbbea
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 17 deletions.
2 changes: 1 addition & 1 deletion cmd/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func listProviderNames() []string {
*providers = ""
}
if *cex {
*providers += ",deribit,delta"
*providers += ",deribit,delta,thalex"
}
if *dex {
*providers += ",lyra,synquote,zeta"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/const/filter_presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const filterPresets: Record<string, any> = {
},
'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 },
Expand All @@ -53,14 +53,14 @@ const filterPresets: Record<string, any> = {
'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,
Expand Down
12 changes: 7 additions & 5 deletions pkg/provider/deribit/deribit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/"

Expand All @@ -25,7 +27,7 @@ type Provider struct {
}

func (Provider) Name() string {
return "Deribit"
return name
}

// adaptiveMinSleepTime to rate limit the Deribit API.
Expand All @@ -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")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion pkg/provider/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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)
Expand Down
186 changes: 186 additions & 0 deletions pkg/provider/thalex/thalex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// 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 (
"strings"
"time"

"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://thalex.com/api/v2/public/"
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
}

options, err := p.fillOptions(instruments)
if err != nil {
return nil, err
}
return options, nil
}

func (p *Provider) query() ([]Instrument, error) {
const api = baseURL + "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
}

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 float64 `json:"best_bid_price"`
BestBidAmount float64 `json:"best_bid_amount"`
BestAskPrice float64 `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 []Instrument `json:"result"`
}

type Instrument 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"`
}
11 changes: 5 additions & 6 deletions pkg/rainbow/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down

0 comments on commit c3dbbea

Please sign in to comment.