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

Commit 116f7d1

Browse files
authored
Add price feed modifiers (closes #325) (#326)
* 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
1 parent 11d4927 commit 116f7d1

10 files changed

Lines changed: 105 additions & 72 deletions

File tree

api/level.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type Level struct {
88
Amount model.Number
99
}
1010

11-
// LevelProvider returns the levels for the given center price, which controls the spread and number of levels
11+
// LevelProvider returns the levels for the given mid price, which controls the spread and number of levels
1212
type LevelProvider interface {
1313
GetLevels(maxAssetBase float64, maxAssetQuote float64) ([]Level, error)
1414
GetFillHandlers() ([]FillHandler, error)

api/priceFeed.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ type FeedPair struct {
1313
FeedB PriceFeed
1414
}
1515

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

29-
centerPrice := pA / pB
30-
log.Printf("feedPair prices: feedA=%.7f, feedB=%.7f; centerPrice=%.7f\n", pA, pB, centerPrice)
31-
return centerPrice, nil
29+
midPrice := pA / pB
30+
log.Printf("feedPair prices: feedA=%.7f, feedB=%.7f; midPrice=%.7f\n", pA, pB, midPrice)
31+
return midPrice, nil
3232
}

examples/configs/trader/sample_buysell.cfg

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
11
# Sample config file for the "buysell" strategy
22

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

7+
# specification of feed type "exchange"
138
DATA_TYPE_A="exchange"
14-
# the format is <exchange name>/<base-asset-code-defined-by-exchange>/<quote-asset-code-defined-by-exchange>
15-
# use "kraken" or any of the ccxt-exchanges (run `kelp exchanges` for full list)
16-
# examples: "kraken", "ccxt-kraken", "ccxt-binance", "ccxt-poloniex", "ccxt-bittrex"
17-
DATA_FEED_A_URL="kraken/XXLM/ZUSD"
18-
# 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.
9+
# the specification of the A feed
10+
# the format is <exchange name>/<base-asset-code-defined-by-exchange>/<quote-asset-code-defined-by-exchange>/<modifier>
11+
# exchange name:
12+
# use "kraken" or any of the ccxt-exchanges (run `kelp exchanges` for full list)
13+
# examples: "kraken", "ccxt-kraken", "ccxt-binance", "ccxt-poloniex", "ccxt-bittrex"
14+
# base asset code defined by exchange:
15+
# this is the asset code defined by the exchange for the asset whose price you want to fetch (base asset).
16+
# this code can be retrieved from the exchange's website or from the ccxt manual for ccxt-based exchanges.
17+
# quote asset code defined by exchange:
18+
# this is the asset code defined by the exchange for asset in which you want to quote the price (quote asset).
19+
# this code can be retrieved from the exchange's website or from the ccxt manual for ccxt-based exchanges.
20+
# modifier:
21+
# this is a modifier that can be included only for feed type "exchange".
22+
# a modifier allows you to fetch the "mid" price, "ask" price, or "bid" price for now.
23+
# if left unspecified then this is defaulted to "mid" for backwards compatibility (until v2.0 is released)
24+
# 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.
1925
# be careful about using USD vs. USDT since some exchanges support only one, or both, or in some cases neither.
20-
#DATA_FEED_A_URL="ccxt-kraken/XLM/USD"
21-
#DATA_FEED_A_URL="ccxt-binance/XLM/USDT"
22-
#DATA_FEED_A_URL="ccxt-poloniex/XLM/USDT"
26+
#DATA_FEED_A_URL="ccxt-kraken/XLM/USD/mid"
27+
#DATA_FEED_A_URL="ccxt-binance/XLM/USDT/ask"
28+
#DATA_FEED_A_URL="ccxt-poloniex/XLM/USDT/bid"
2329
# 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).
2430
#DATA_FEED_A_URL="ccxt-bittrex/XLM/BTC"
31+
DATA_FEED_A_URL="kraken/XXLM/ZUSD/mid"
2532

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

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

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

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

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

96103
# you can have as many levels as you want, just create more entries here

examples/configs/trader/sample_sell.cfg

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,35 @@
22

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

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

9+
# specification of feed type "exchange"
1510
DATA_TYPE_A="exchange"
16-
# the format is <exchange name>/<base-asset-code-defined-by-exchange>/<quote-asset-code-defined-by-exchange>
17-
# use "kraken" or any of the ccxt-exchanges (run `kelp exchanges` for full list)
18-
# examples: "kraken", "ccxt-kraken", "ccxt-binance", "ccxt-poloniex", "ccxt-bittrex"
19-
DATA_FEED_A_URL="kraken/XXLM/ZUSD"
20-
# 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.
11+
# the specification of the A feed
12+
# the format is <exchange name>/<base-asset-code-defined-by-exchange>/<quote-asset-code-defined-by-exchange>/<modifier>
13+
# exchange name:
14+
# use "kraken" or any of the ccxt-exchanges (run `kelp exchanges` for full list)
15+
# examples: "kraken", "ccxt-kraken", "ccxt-binance", "ccxt-poloniex", "ccxt-bittrex"
16+
# base asset code defined by exchange:
17+
# this is the asset code defined by the exchange for the asset whose price you want to fetch (base asset).
18+
# this code can be retrieved from the exchange's website or from the ccxt manual for ccxt-based exchanges.
19+
# quote asset code defined by exchange:
20+
# this is the asset code defined by the exchange for asset in which you want to quote the price (quote asset).
21+
# this code can be retrieved from the exchange's website or from the ccxt manual for ccxt-based exchanges.
22+
# modifier:
23+
# this is a modifier that can be included only for feed type "exchange".
24+
# a modifier allows you to fetch the "mid" price, "ask" price, or "bid" price for now.
25+
# if left unspecified then this is defaulted to "mid" for backwards compatibility (until v2.0 is released)
26+
# 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.
2127
# be careful about using USD vs. USDT since some exchanges support only one, or both, or in some cases neither.
22-
#DATA_FEED_A_URL="ccxt-kraken/XLM/USD"
23-
#DATA_FEED_A_URL="ccxt-binance/XLM/USDT"
24-
#DATA_FEED_A_URL="ccxt-poloniex/XLM/USDT"
28+
#DATA_FEED_A_URL="ccxt-kraken/XLM/USD/mid"
29+
#DATA_FEED_A_URL="ccxt-binance/XLM/USDT/ask"
30+
#DATA_FEED_A_URL="ccxt-poloniex/XLM/USDT/bid"
2531
# 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).
2632
#DATA_FEED_A_URL="ccxt-bittrex/XLM/BTC"
33+
DATA_FEED_A_URL="kraken/XXLM/ZUSD/mid"
2734

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

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

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

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

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

98105
# you can have as many levels as you want, just create more entries here

examples/walkthroughs/trader/buysell.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ A level defines a [layer](https://en.wikipedia.org/wiki/Layering_(finance)) that
3636

3737
`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`).
3838

39-
- **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.
39+
- **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.
4040
- **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.
4141

4242
## Run Kelp

examples/walkthroughs/trader/sell.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ A level defines a [layer](https://en.wikipedia.org/wiki/Layering_(finance)) that
4141
`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`).
4242

4343
- **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.
44-
- **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.
44+
- **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.
4545

4646
## Run Kelp
4747

plugins/exchangeFeed.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@ type exchangeFeed struct {
1313
name string
1414
tickerAPI *api.TickerAPI
1515
pairs []model.TradingPair
16+
modifier string
1617
}
1718

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

21-
func newExchangeFeed(name string, tickerAPI *api.TickerAPI, pair *model.TradingPair) *exchangeFeed {
22+
func newExchangeFeed(name string, tickerAPI *api.TickerAPI, pair *model.TradingPair, modifier string) *exchangeFeed {
2223
return &exchangeFeed{
2324
name: name,
2425
tickerAPI: tickerAPI,
2526
pairs: []model.TradingPair{*pair},
27+
modifier: modifier,
2628
}
2729
}
2830

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

42-
centerPrice := (p.BidPrice.AsFloat() + p.AskPrice.AsFloat()) / 2
43-
log.Printf("price from exchange feed (%s): bidPrice=%.7f, askPrice=%.7f, centerPrice=%.7f", f.name, p.BidPrice.AsFloat(), p.AskPrice.AsFloat(), centerPrice)
44-
return centerPrice, nil
44+
midPrice := (p.BidPrice.AsFloat() + p.AskPrice.AsFloat()) / 2
45+
var price float64
46+
if f.modifier == "ask" {
47+
price = p.AskPrice.AsFloat()
48+
} else if f.modifier == "bid" {
49+
price = p.BidPrice.AsFloat()
50+
} else {
51+
price = midPrice
52+
}
53+
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)
54+
return price, nil
4555
}

plugins/priceFeed.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,19 @@ func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) {
4343
case "fixed":
4444
return newFixedFeed(url)
4545
case "exchange":
46-
// [0] = exchangeType, [1] = base, [2] = quote
46+
// [0] = exchangeType, [1] = base, [2] = quote, [3] = modifier (optional)
4747
urlParts := strings.Split(url, "/")
48-
if len(urlParts) != 3 {
49-
return nil, fmt.Errorf("invalid format of exchange type URL, needs exactly 3 parts after splitting URL by '/', has %d: %s", len(urlParts), url)
48+
if len(urlParts) < 3 || len(urlParts) > 4 {
49+
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)
50+
}
51+
52+
// default to "mid" for backwards compatibility
53+
exchangeModifier := "mid"
54+
if len(urlParts) == 4 {
55+
exchangeModifier = urlParts[3]
56+
if exchangeModifier != "mid" && exchangeModifier != "ask" && exchangeModifier != "bid" {
57+
return nil, fmt.Errorf("unsupported exchange modifier '%s' on exchange type URL", exchangeModifier)
58+
}
5059
}
5160

5261
exchange, e := MakeExchange(urlParts[0], true)
@@ -66,7 +75,7 @@ func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) {
6675
Quote: quoteAsset,
6776
}
6877
tickerAPI := api.TickerAPI(exchange)
69-
return newExchangeFeed(url, &tickerAPI, &tradingPair), nil
78+
return newExchangeFeed(url, &tickerAPI, &tradingPair, exchangeModifier), nil
7079
case "sdex":
7180
sdex, e := makeSDEXFeed(url)
7281
if e != nil {

plugins/sdexFeed.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,6 @@ func (s *sdexFeed) GetPrice() (float64, error) {
106106
topBidPrice := orderBook.Bids()[0].Price
107107
topAskPrice := orderBook.Asks()[0].Price
108108

109-
centerPrice := topBidPrice.Add(*topAskPrice).Scale(0.5).AsFloat()
110-
return centerPrice, nil
109+
midPrice := topBidPrice.Add(*topAskPrice).Scale(0.5).AsFloat()
110+
return midPrice, nil
111111
}

plugins/staticSpreadLevelProvider.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package plugins
22

33
import (
4+
"fmt"
45
"log"
56

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

5859
// GetLevels impl.
5960
func (p *staticSpreadLevelProvider) GetLevels(maxAssetBase float64, maxAssetQuote float64) ([]api.Level, error) {
60-
centerPrice, e := p.pf.GetCenterPrice()
61+
midPrice, e := p.pf.GetMidPrice()
6162
if e != nil {
62-
log.Printf("error: center price couldn't be loaded! | %s\n", e)
63-
return nil, e
63+
return nil, fmt.Errorf("mid price couldn't be loaded: %s", e)
6464
}
6565
if p.offset.percent != 0.0 || p.offset.absolute != 0 {
6666
// if inverted, we want to invert before we compute the adjusted price, and then invert back
6767
if p.offset.invert {
68-
centerPrice = 1 / centerPrice
68+
midPrice = 1 / midPrice
6969
}
7070
scaleFactor := 1 + p.offset.percent
7171
if p.offset.percentFirst {
72-
centerPrice = (centerPrice * scaleFactor) + p.offset.absolute
72+
midPrice = (midPrice * scaleFactor) + p.offset.absolute
7373
} else {
74-
centerPrice = (centerPrice + p.offset.absolute) * scaleFactor
74+
midPrice = (midPrice + p.offset.absolute) * scaleFactor
7575
}
7676
if p.offset.invert {
77-
centerPrice = 1 / centerPrice
77+
midPrice = 1 / midPrice
7878
}
79-
log.Printf("center price (adjusted): %.7f\n", centerPrice)
79+
log.Printf("mid price (adjusted): %.7f\n", midPrice)
8080
}
8181

8282
levels := []api.Level{}
8383
for _, sl := range p.staticLevels {
84-
absoluteSpread := centerPrice * sl.SPREAD
84+
absoluteSpread := midPrice * sl.SPREAD
8585
levels = append(levels, api.Level{
8686
// 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
87-
Price: *model.NumberFromFloat(centerPrice+absoluteSpread, p.orderConstraints.PricePrecision),
87+
Price: *model.NumberFromFloat(midPrice+absoluteSpread, p.orderConstraints.PricePrecision),
8888
Amount: *model.NumberFromFloat(sl.AMOUNT*p.amountOfBase, p.orderConstraints.VolumePrecision),
8989
})
9090
}

0 commit comments

Comments
 (0)