Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Add price feed modifiers (closes #325) (#326)
Browse files Browse the repository at this point in the history
* 1 - add modifiers to exchange feed, update sample config files

* 2 - standardize log lines for modifiers in exchangeFeed

* 3 - blanket rename center price to mid price throughout project

* 4 - better error return value when GetLevels cannot load mid price
  • Loading branch information
nikhilsaraf authored Dec 30, 2019
1 parent 11d4927 commit 116f7d1
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 72 deletions.
2 changes: 1 addition & 1 deletion api/level.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type Level struct {
Amount model.Number
}

// LevelProvider returns the levels for the given center price, which controls the spread and number of levels
// LevelProvider returns the levels for the given mid price, which controls the spread and number of levels
type LevelProvider interface {
GetLevels(maxAssetBase float64, maxAssetQuote float64) ([]Level, error)
GetFillHandlers() ([]FillHandler, error)
Expand Down
10 changes: 5 additions & 5 deletions api/priceFeed.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ type FeedPair struct {
FeedB PriceFeed
}

// GetCenterPrice fetches the center price from this feed
func (p *FeedPair) GetCenterPrice() (float64, error) {
// GetMidPrice fetches the mid price from this feed pair
func (p *FeedPair) GetMidPrice() (float64, error) {
pA, err := p.FeedA.GetPrice()
if err != nil {
return 0, err
Expand All @@ -26,7 +26,7 @@ func (p *FeedPair) GetCenterPrice() (float64, error) {
return 0, err
}

centerPrice := pA / pB
log.Printf("feedPair prices: feedA=%.7f, feedB=%.7f; centerPrice=%.7f\n", pA, pB, centerPrice)
return centerPrice, nil
midPrice := pA / pB
log.Printf("feedPair prices: feedA=%.7f, feedB=%.7f; midPrice=%.7f\n", pA, pB, midPrice)
return midPrice, nil
}
51 changes: 29 additions & 22 deletions examples/configs/trader/sample_buysell.cfg
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
# Sample config file for the "buysell" strategy

# this defines a "priceFeed" type, i.e. where to get the data from
# types supported:
# crypto
# fiat
# fixed
# exchange
# sdex
#
# We take the values from both feeds and divide them to get the center price.
# Price Feeds
# Note: we take the value from the A feed and divide it by the value retrieved from the B feed below.
# the type of feeds can be one of crypto, fiat, fixed, exchange, sdex.

# specification of feed type "exchange"
DATA_TYPE_A="exchange"
# the format is <exchange name>/<base-asset-code-defined-by-exchange>/<quote-asset-code-defined-by-exchange>
# use "kraken" or any of the ccxt-exchanges (run `kelp exchanges` for full list)
# examples: "kraken", "ccxt-kraken", "ccxt-binance", "ccxt-poloniex", "ccxt-bittrex"
DATA_FEED_A_URL="kraken/XXLM/ZUSD"
# uncomment to use binance, poloniex, or bittrex as your price feed. You will need to set up CCXT to use this, see the "Using CCXT" section in the README for details.
# the specification of the A feed
# the format is <exchange name>/<base-asset-code-defined-by-exchange>/<quote-asset-code-defined-by-exchange>/<modifier>
# exchange name:
# use "kraken" or any of the ccxt-exchanges (run `kelp exchanges` for full list)
# examples: "kraken", "ccxt-kraken", "ccxt-binance", "ccxt-poloniex", "ccxt-bittrex"
# base asset code defined by exchange:
# this is the asset code defined by the exchange for the asset whose price you want to fetch (base asset).
# this code can be retrieved from the exchange's website or from the ccxt manual for ccxt-based exchanges.
# quote asset code defined by exchange:
# this is the asset code defined by the exchange for asset in which you want to quote the price (quote asset).
# this code can be retrieved from the exchange's website or from the ccxt manual for ccxt-based exchanges.
# modifier:
# this is a modifier that can be included only for feed type "exchange".
# a modifier allows you to fetch the "mid" price, "ask" price, or "bid" price for now.
# if left unspecified then this is defaulted to "mid" for backwards compatibility (until v2.0 is released)
# uncomment below to use binance, poloniex, or bittrex as your price feed. You will need to set up CCXT to use this, see the "Using CCXT" section in the README for details.
# be careful about using USD vs. USDT since some exchanges support only one, or both, or in some cases neither.
#DATA_FEED_A_URL="ccxt-kraken/XLM/USD"
#DATA_FEED_A_URL="ccxt-binance/XLM/USDT"
#DATA_FEED_A_URL="ccxt-poloniex/XLM/USDT"
#DATA_FEED_A_URL="ccxt-kraken/XLM/USD/mid"
#DATA_FEED_A_URL="ccxt-binance/XLM/USDT/ask"
#DATA_FEED_A_URL="ccxt-poloniex/XLM/USDT/bid"
# bittrex does not have an XLM/USD market so this config lists XLM/BTC instead; you should NOT use this when trying to price an asset based on the XLM/USD price (unless you know what you are doing).
#DATA_FEED_A_URL="ccxt-bittrex/XLM/BTC"
DATA_FEED_A_URL="kraken/XXLM/ZUSD/mid"

# sample priceFeed with the "crypto" type
#DATA_TYPE_A="crypto"
Expand Down Expand Up @@ -70,27 +77,27 @@ AMOUNT_OF_A_BASE=10.0
# levels are mirrored on the buy and sell side. spread is a percentage specified as a decimal number (0 < spread < 1.00)
# first level
[[LEVELS]]
SPREAD=0.00010 # distance from center price = 0.010%, i.e. bid/ask spread = 0.02%
SPREAD=0.00010 # distance from mid price = 0.010%, i.e. bid/ask spread = 0.02%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# second level
[[LEVELS]]
SPREAD=0.00015 # distance from center price = 0.015%, i.e. bid/ask spread = 0.03%
SPREAD=0.00015 # distance from mid price = 0.015%, i.e. bid/ask spread = 0.03%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# third level
[[LEVELS]]
SPREAD=0.00020 # distance from center price = 0.020%, i.e. bid/ask spread = 0.04%
SPREAD=0.00020 # distance from mid price = 0.020%, i.e. bid/ask spread = 0.04%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# fourth level
[[LEVELS]]
SPREAD=0.00025 # distance from center price = 0.025%, i.e. bid/ask spread = 0.05%
SPREAD=0.00025 # distance from mid price = 0.025%, i.e. bid/ask spread = 0.05%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# fifth level
[[LEVELS]]
SPREAD=0.00030 # distance from center price = 0.030%, i.e. bid/ask spread = 0.06%
SPREAD=0.00030 # distance from mid price = 0.030%, i.e. bid/ask spread = 0.06%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# you can have as many levels as you want, just create more entries here
51 changes: 29 additions & 22 deletions examples/configs/trader/sample_sell.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,35 @@

# We are selling the base asset here, i.e. ASSET_CODE_A as defined in the trader config

# this defines a "priceFeed" type, i.e. where to get the data from
# types supported:
# crypto
# fiat
# fixed
# exchange
# sdex
#
# We take the values from both feeds and divide them to get the center price.
# Price Feeds
# Note: we take the value from the A feed and divide it by the value retrieved from the B feed below.
# the type of feeds can be one of crypto, fiat, fixed, exchange, sdex.

# specification of feed type "exchange"
DATA_TYPE_A="exchange"
# the format is <exchange name>/<base-asset-code-defined-by-exchange>/<quote-asset-code-defined-by-exchange>
# use "kraken" or any of the ccxt-exchanges (run `kelp exchanges` for full list)
# examples: "kraken", "ccxt-kraken", "ccxt-binance", "ccxt-poloniex", "ccxt-bittrex"
DATA_FEED_A_URL="kraken/XXLM/ZUSD"
# uncomment to use binance, poloniex, or bittrex as your price feed. You will need to set up CCXT to use this, see the "Using CCXT" section in the README for details.
# the specification of the A feed
# the format is <exchange name>/<base-asset-code-defined-by-exchange>/<quote-asset-code-defined-by-exchange>/<modifier>
# exchange name:
# use "kraken" or any of the ccxt-exchanges (run `kelp exchanges` for full list)
# examples: "kraken", "ccxt-kraken", "ccxt-binance", "ccxt-poloniex", "ccxt-bittrex"
# base asset code defined by exchange:
# this is the asset code defined by the exchange for the asset whose price you want to fetch (base asset).
# this code can be retrieved from the exchange's website or from the ccxt manual for ccxt-based exchanges.
# quote asset code defined by exchange:
# this is the asset code defined by the exchange for asset in which you want to quote the price (quote asset).
# this code can be retrieved from the exchange's website or from the ccxt manual for ccxt-based exchanges.
# modifier:
# this is a modifier that can be included only for feed type "exchange".
# a modifier allows you to fetch the "mid" price, "ask" price, or "bid" price for now.
# if left unspecified then this is defaulted to "mid" for backwards compatibility (until v2.0 is released)
# uncomment below to use binance, poloniex, or bittrex as your price feed. You will need to set up CCXT to use this, see the "Using CCXT" section in the README for details.
# be careful about using USD vs. USDT since some exchanges support only one, or both, or in some cases neither.
#DATA_FEED_A_URL="ccxt-kraken/XLM/USD"
#DATA_FEED_A_URL="ccxt-binance/XLM/USDT"
#DATA_FEED_A_URL="ccxt-poloniex/XLM/USDT"
#DATA_FEED_A_URL="ccxt-kraken/XLM/USD/mid"
#DATA_FEED_A_URL="ccxt-binance/XLM/USDT/ask"
#DATA_FEED_A_URL="ccxt-poloniex/XLM/USDT/bid"
# bittrex does not have an XLM/USD market so this config lists XLM/BTC instead; you should NOT use this when trying to price an asset based on the XLM/USD price (unless you know what you are doing).
#DATA_FEED_A_URL="ccxt-bittrex/XLM/BTC"
DATA_FEED_A_URL="kraken/XXLM/ZUSD/mid"

# sample priceFeed with the "crypto" type
#DATA_TYPE_A="crypto"
Expand Down Expand Up @@ -72,27 +79,27 @@ AMOUNT_OF_A_BASE=10.0
# levels are mirrored on the buy and sell side. spread is a percentage specified as a decimal number (0 < spread < 1.00)
# first level
[[LEVELS]]
SPREAD=0.00010 # distance from center price = 0.010%, i.e. bid/ask spread = 0.02%
SPREAD=0.00010 # distance from mid price = 0.010%, i.e. bid/ask spread = 0.02%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# second level
[[LEVELS]]
SPREAD=0.00015 # distance from center price = 0.015%, i.e. bid/ask spread = 0.03%
SPREAD=0.00015 # distance from mid price = 0.015%, i.e. bid/ask spread = 0.03%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# third level
[[LEVELS]]
SPREAD=0.00020 # distance from center price = 0.020%, i.e. bid/ask spread = 0.04%
SPREAD=0.00020 # distance from mid price = 0.020%, i.e. bid/ask spread = 0.04%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# fourth level
[[LEVELS]]
SPREAD=0.00025 # distance from center price = 0.025%, i.e. bid/ask spread = 0.05%
SPREAD=0.00025 # distance from mid price = 0.025%, i.e. bid/ask spread = 0.05%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# fifth level
[[LEVELS]]
SPREAD=0.00030 # distance from center price = 0.030%, i.e. bid/ask spread = 0.06%
SPREAD=0.00030 # distance from mid price = 0.030%, i.e. bid/ask spread = 0.06%
AMOUNT=100.0 # multiple of base amount = 10.0 * 100 units of base asset

# you can have as many levels as you want, just create more entries here
2 changes: 1 addition & 1 deletion examples/walkthroughs/trader/buysell.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ A level defines a [layer](https://en.wikipedia.org/wiki/Layering_(finance)) that

`AMOUNT_OF_A_BASE` allows you to scale the order size levels explained below. Trade amounts are specified in **units of the [base asset](https://en.wikipedia.org/wiki/Currency_pair#Base_currency)** (i.e. `ASSET_CODE_A`).

- **SPREAD**: represents the distance from center price as a percentage specified as a decimal number (0 < spread < 1.00). The [bid/ask spread](https://en.wikipedia.org/wiki/Bid%E2%80%93ask_spread) will be 2x what is specified at each level in the config.
- **SPREAD**: represents the distance from the mid price as a percentage specified as a decimal number (0 < spread < 1.00). The [bid/ask spread](https://en.wikipedia.org/wiki/Bid%E2%80%93ask_spread) will be 2x what is specified at each level in the config.
- **AMOUNT**: specifies the order size in multiples of the base unit described above. This `AMOUNT` is multiplied by the `AMOUNT_OF_A_BASE` field to give the final amount. The amount for the quote asset is derived using this value and the computed price at this level.

## Run Kelp
Expand Down
2 changes: 1 addition & 1 deletion examples/walkthroughs/trader/sell.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ A level defines a [layer](https://en.wikipedia.org/wiki/Layering_(finance)) that
`AMOUNT_OF_A_BASE` allows you to scale the order size levels explained below. Trade amounts are specified in **units of the [base asset](https://en.wikipedia.org/wiki/Currency_pair#Base_currency)** (i.e. `ASSET_CODE_A`).

- **AMOUNT**: specifies the order size in multiples of the base unit described above. This `AMOUNT` is multiplied by the `AMOUNT_OF_A_BASE` field to give the final amount. The amount for the quote asset is derived using this value and the computed price at the indicated `SPREAD` level.
- **SPREAD**: represents the distance from center price as a percentage specified as a decimal number (0 < spread < 1.00). The [bid/ask spread](https://en.wikipedia.org/wiki/Bid%E2%80%93ask_spread) will be 2x what is specified at each level in the config.
- **SPREAD**: represents the distance from the mid price as a percentage specified as a decimal number (0 < spread < 1.00). The [bid/ask spread](https://en.wikipedia.org/wiki/Bid%E2%80%93ask_spread) will be 2x what is specified at each level in the config.

## Run Kelp

Expand Down
18 changes: 14 additions & 4 deletions plugins/exchangeFeed.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ type exchangeFeed struct {
name string
tickerAPI *api.TickerAPI
pairs []model.TradingPair
modifier string
}

// ensure that it implements PriceFeed
var _ api.PriceFeed = &exchangeFeed{}

func newExchangeFeed(name string, tickerAPI *api.TickerAPI, pair *model.TradingPair) *exchangeFeed {
func newExchangeFeed(name string, tickerAPI *api.TickerAPI, pair *model.TradingPair, modifier string) *exchangeFeed {
return &exchangeFeed{
name: name,
tickerAPI: tickerAPI,
pairs: []model.TradingPair{*pair},
modifier: modifier,
}
}

Expand All @@ -39,7 +41,15 @@ func (f *exchangeFeed) GetPrice() (float64, error) {
return 0, fmt.Errorf("could not get price for trading pair: %s", f.pairs[0].String())
}

centerPrice := (p.BidPrice.AsFloat() + p.AskPrice.AsFloat()) / 2
log.Printf("price from exchange feed (%s): bidPrice=%.7f, askPrice=%.7f, centerPrice=%.7f", f.name, p.BidPrice.AsFloat(), p.AskPrice.AsFloat(), centerPrice)
return centerPrice, nil
midPrice := (p.BidPrice.AsFloat() + p.AskPrice.AsFloat()) / 2
var price float64
if f.modifier == "ask" {
price = p.AskPrice.AsFloat()
} else if f.modifier == "bid" {
price = p.BidPrice.AsFloat()
} else {
price = midPrice
}
log.Printf("(modifier: %s) price from exchange feed (%s): bidPrice=%.7f, askPrice=%.7f, midPrice=%.7f; price=%.7f", f.modifier, f.name, p.BidPrice.AsFloat(), p.AskPrice.AsFloat(), midPrice, price)
return price, nil
}
17 changes: 13 additions & 4 deletions plugins/priceFeed.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,19 @@ func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) {
case "fixed":
return newFixedFeed(url)
case "exchange":
// [0] = exchangeType, [1] = base, [2] = quote
// [0] = exchangeType, [1] = base, [2] = quote, [3] = modifier (optional)
urlParts := strings.Split(url, "/")
if len(urlParts) != 3 {
return nil, fmt.Errorf("invalid format of exchange type URL, needs exactly 3 parts after splitting URL by '/', has %d: %s", len(urlParts), url)
if len(urlParts) < 3 || len(urlParts) > 4 {
return nil, fmt.Errorf("invalid format of exchange type URL, needs either 3 or 4 parts after splitting URL by '/', has %d: %s", len(urlParts), url)
}

// default to "mid" for backwards compatibility
exchangeModifier := "mid"
if len(urlParts) == 4 {
exchangeModifier = urlParts[3]
if exchangeModifier != "mid" && exchangeModifier != "ask" && exchangeModifier != "bid" {
return nil, fmt.Errorf("unsupported exchange modifier '%s' on exchange type URL", exchangeModifier)
}
}

exchange, e := MakeExchange(urlParts[0], true)
Expand All @@ -66,7 +75,7 @@ func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) {
Quote: quoteAsset,
}
tickerAPI := api.TickerAPI(exchange)
return newExchangeFeed(url, &tickerAPI, &tradingPair), nil
return newExchangeFeed(url, &tickerAPI, &tradingPair, exchangeModifier), nil
case "sdex":
sdex, e := makeSDEXFeed(url)
if e != nil {
Expand Down
4 changes: 2 additions & 2 deletions plugins/sdexFeed.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,6 @@ func (s *sdexFeed) GetPrice() (float64, error) {
topBidPrice := orderBook.Bids()[0].Price
topAskPrice := orderBook.Asks()[0].Price

centerPrice := topBidPrice.Add(*topAskPrice).Scale(0.5).AsFloat()
return centerPrice, nil
midPrice := topBidPrice.Add(*topAskPrice).Scale(0.5).AsFloat()
return midPrice, nil
}
20 changes: 10 additions & 10 deletions plugins/staticSpreadLevelProvider.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package plugins

import (
"fmt"
"log"

"github.com/stellar/kelp/api"
Expand Down Expand Up @@ -57,34 +58,33 @@ func makeStaticSpreadLevelProvider(staticLevels []StaticLevel, amountOfBase floa

// GetLevels impl.
func (p *staticSpreadLevelProvider) GetLevels(maxAssetBase float64, maxAssetQuote float64) ([]api.Level, error) {
centerPrice, e := p.pf.GetCenterPrice()
midPrice, e := p.pf.GetMidPrice()
if e != nil {
log.Printf("error: center price couldn't be loaded! | %s\n", e)
return nil, e
return nil, fmt.Errorf("mid price couldn't be loaded: %s", e)
}
if p.offset.percent != 0.0 || p.offset.absolute != 0 {
// if inverted, we want to invert before we compute the adjusted price, and then invert back
if p.offset.invert {
centerPrice = 1 / centerPrice
midPrice = 1 / midPrice
}
scaleFactor := 1 + p.offset.percent
if p.offset.percentFirst {
centerPrice = (centerPrice * scaleFactor) + p.offset.absolute
midPrice = (midPrice * scaleFactor) + p.offset.absolute
} else {
centerPrice = (centerPrice + p.offset.absolute) * scaleFactor
midPrice = (midPrice + p.offset.absolute) * scaleFactor
}
if p.offset.invert {
centerPrice = 1 / centerPrice
midPrice = 1 / midPrice
}
log.Printf("center price (adjusted): %.7f\n", centerPrice)
log.Printf("mid price (adjusted): %.7f\n", midPrice)
}

levels := []api.Level{}
for _, sl := range p.staticLevels {
absoluteSpread := centerPrice * sl.SPREAD
absoluteSpread := midPrice * sl.SPREAD
levels = append(levels, api.Level{
// we always add here because it is only used in the context of selling so we always charge a higher price to include a spread
Price: *model.NumberFromFloat(centerPrice+absoluteSpread, p.orderConstraints.PricePrecision),
Price: *model.NumberFromFloat(midPrice+absoluteSpread, p.orderConstraints.PricePrecision),
Amount: *model.NumberFromFloat(sl.AMOUNT*p.amountOfBase, p.orderConstraints.VolumePrecision),
})
}
Expand Down

0 comments on commit 116f7d1

Please sign in to comment.