diff --git a/accounting/pnl/pnl.go b/accounting/pnl/pnl.go index a2a1d6258..8e62f8bf4 100644 --- a/accounting/pnl/pnl.go +++ b/accounting/pnl/pnl.go @@ -110,5 +110,5 @@ func loadAccount(client *horizon.Client, address string) horizon.Account { func makeCmcFeed(cmcRef string) (api.PriceFeed, error) { url := fmt.Sprintf("https://api.coinmarketcap.com/v1/ticker/%s/", cmcRef) - return plugins.MakePriceFeed("crypto", url) + return plugins.MakePriceFeed("crypto", url, false) } diff --git a/examples/configs/trader/sample_buysell.cfg b/examples/configs/trader/sample_buysell.cfg index 7a0eb374f..e7fb621ea 100644 --- a/examples/configs/trader/sample_buysell.cfg +++ b/examples/configs/trader/sample_buysell.cfg @@ -15,6 +15,8 @@ DATA_TYPE_A="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" +# whether to invert the price received from the feed; this allows more complex price derivations +DATA_FEED_A_INVERT=false # 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. # 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" @@ -27,10 +29,12 @@ DATA_FEED_A_URL="kraken/XXLM/ZUSD" #DATA_TYPE_A="crypto" # this is the URL to a coinmarketcap feed which the bot understands. #DATA_FEED_A_URL="https://api.coinmarketcap.com/v1/ticker/stellar/" +#DATA_FEED_A_INVERT=false # this is a fixed value of 1 here because the exchange and sdex priceFeeds provides a ratio of two assets. DATA_TYPE_B="fixed" DATA_FEED_B_URL="1.0" +DATA_FEED_B_INVERT=false # sample priceFeed with the "fiat" type. #DATA_TYPE_B="fiat" @@ -43,6 +47,8 @@ DATA_FEED_B_URL="1.0" # this is a string representing a SDEX pair; the format is CODE:ISSUER/CODE:ISSUER # for XLM leave the issuer string blank # DATA_FEED_A_URL="COUPON:GBMMZMK2DC4FFP4CAI6KCVNCQ7WLO5A7DQU7EC7WGHRDQBZB763X4OQI/XLM:" +# sdex feeds don't need to be inverted because the sdex can read the orderbook either direction, so you can just swap the assets above +# DATA_FEED_A_INVERT=false # what value of a price change triggers re-creating an offer. Price change refers to the existing price of the offer vs. what price we want to set. value is a percentage specified as a decimal number (0 < value < 1.00) PRICE_TOLERANCE=0.001 diff --git a/examples/configs/trader/sample_sell.cfg b/examples/configs/trader/sample_sell.cfg index 7451d3361..274104e0d 100644 --- a/examples/configs/trader/sample_sell.cfg +++ b/examples/configs/trader/sample_sell.cfg @@ -17,6 +17,8 @@ DATA_TYPE_A="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" +# whether to invert the price received from the feed; this allows more complex price derivations +DATA_FEED_A_INVERT=false # 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. # 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" @@ -29,15 +31,18 @@ DATA_FEED_A_URL="kraken/XXLM/ZUSD" #DATA_TYPE_A="crypto" # this is the URL to a coinmarketcap feed which the bot understands. #DATA_FEED_A_URL="https://api.coinmarketcap.com/v1/ticker/stellar/" +#DATA_FEED_A_INVERT=false # this is a fixed value of 1 here because the exchange and sdex priceFeeds provides a ratio of two assets. DATA_TYPE_B="fixed" DATA_FEED_B_URL="1.0" +DATA_FEED_B_INVERT=false # sample priceFeed with the "fiat" type. #DATA_TYPE_B="fiat" # you can use a service like apilayer.net to get prices for fiat if you want real-time updates. You will need to fill in the access_key in this url #DATA_FEED_B_URL="http://apilayer.net/api/live?access_key=¤cies=NGN" +#DATA_FEED_B_INVERT=false # sample priceFeed with the "sdex" type # this feed pulls from the SDEX, you can use the asset you're trading or something else, like the same coin from another issuer @@ -45,6 +50,8 @@ DATA_FEED_B_URL="1.0" # this is a string representing a SDEX pair; the format is CODE:ISSUER/CODE:ISSUER # for XLM leave the issuer string blank # DATA_FEED_A_URL="COUPON:GBMMZMK2DC4FFP4CAI6KCVNCQ7WLO5A7DQU7EC7WGHRDQBZB763X4OQI/XLM:" +# sdex feeds don't need to be inverted because the sdex can read the orderbook either direction, so you can just swap the assets above +# DATA_FEED_A_INVERT=false # what value of a price change triggers re-creating an offer. Price change refers to the existing price of the offer vs. what price we want to set. value is a percentage specified as a decimal number (0 < value < 1.00) PRICE_TOLERANCE=0.001 diff --git a/plugins/buysellStrategy.go b/plugins/buysellStrategy.go index 88e86b1ce..c2aefddc9 100644 --- a/plugins/buysellStrategy.go +++ b/plugins/buysellStrategy.go @@ -19,8 +19,10 @@ type buySellConfig struct { AmountOfABase float64 `valid:"-" toml:"AMOUNT_OF_A_BASE"` // the size of order to keep on either side DataTypeA string `valid:"-" toml:"DATA_TYPE_A"` DataFeedAURL string `valid:"-" toml:"DATA_FEED_A_URL"` + DataFeedAInvert bool `valid:"-" toml:"DATA_FEED_A_INVERT"` DataTypeB string `valid:"-" toml:"DATA_TYPE_B"` DataFeedBURL string `valid:"-" toml:"DATA_FEED_B_URL"` + DataFeedBInvert bool `valid:"-" toml:"DATA_FEED_B_INVERT"` Levels []staticLevel `valid:"-" toml:"LEVELS"` } @@ -46,8 +48,10 @@ func makeBuySellStrategy( sellSideFeedPair, e := MakeFeedPair( config.DataTypeA, config.DataFeedAURL, + config.DataFeedAInvert, config.DataTypeB, config.DataFeedBURL, + config.DataFeedBInvert, ) if e != nil { return nil, fmt.Errorf("cannot make the buysell strategy because we could not make the sell side feed pair: %s", e) @@ -80,8 +84,10 @@ func makeBuySellStrategy( buySideFeedPair, e := MakeFeedPair( config.DataTypeB, config.DataFeedBURL, + config.DataFeedBInvert, config.DataTypeA, config.DataFeedAURL, + config.DataFeedAInvert, ) if e != nil { return nil, fmt.Errorf("cannot make the buysell strategy because we could not make the buy side feed pair: %s", e) diff --git a/plugins/cmcFeed.go b/plugins/cmcFeed.go index 3421e777a..504004c39 100644 --- a/plugins/cmcFeed.go +++ b/plugins/cmcFeed.go @@ -40,16 +40,18 @@ type cmcAPIReturn struct { type cmcFeed struct { url string client http.Client + invert bool } // ensure that it implements PriceFeed var _ api.PriceFeed = &cmcFeed{} // newCMCFeed creates a new CMC Feed from a URL -func newCMCFeed(url string) *cmcFeed { +func newCMCFeed(url string, invert bool) *cmcFeed { m := new(cmcFeed) m.url = url m.client = http.Client{Timeout: 10 * time.Second} + m.invert = invert return m } @@ -66,5 +68,9 @@ func (c *cmcFeed) GetPrice() (float64, error) { return 0, err } + if c.invert { + pA = 1.0 / pA + } + return pA, nil } diff --git a/plugins/exchangeFeed.go b/plugins/exchangeFeed.go index 242dec71a..2bba42bce 100644 --- a/plugins/exchangeFeed.go +++ b/plugins/exchangeFeed.go @@ -13,16 +13,18 @@ type exchangeFeed struct { name string tickerAPI *api.TickerAPI pairs []model.TradingPair + invert bool } // 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, invert bool) *exchangeFeed { return &exchangeFeed{ name: name, tickerAPI: tickerAPI, pairs: []model.TradingPair{*pair}, + invert: invert, } } @@ -40,6 +42,11 @@ func (f *exchangeFeed) GetPrice() (float64, error) { } centerPrice := (p.BidPrice.AsFloat() + p.AskPrice.AsFloat()) / 2 + + if f.invert { + centerPrice = 1.0 / centerPrice + } + 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 } diff --git a/plugins/fiatFeed.go b/plugins/fiatFeed.go index 85a666472..aa461722c 100644 --- a/plugins/fiatFeed.go +++ b/plugins/fiatFeed.go @@ -26,12 +26,13 @@ type fiatAPIReturn struct { type fiatFeed struct { url string client http.Client + invert bool } // ensure that it implements PriceFeed var _ api.PriceFeed = &fiatFeed{} -func newFiatFeed(url string) *fiatFeed { +func newFiatFeed(url string, invert bool) *fiatFeed { m := new(fiatFeed) m.url = url m.client = http.Client{Timeout: 10 * time.Second} @@ -51,5 +52,9 @@ func (f *fiatFeed) GetPrice() (float64, error) { pA = value } - return (1.0 / pA), nil + if !f.invert { + pA = 1.0 / pA + } + + return (pA), nil } diff --git a/plugins/priceFeed.go b/plugins/priceFeed.go index e109416ea..4a5576fef 100644 --- a/plugins/priceFeed.go +++ b/plugins/priceFeed.go @@ -35,12 +35,12 @@ func SetPrivateSdexHack(api *horizon.Client, ieif *IEIF, network build.Network) } // MakePriceFeed makes a PriceFeed -func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) { +func MakePriceFeed(feedType string, url string, invert bool) (api.PriceFeed, error) { switch feedType { case "crypto": - return newCMCFeed(url), nil + return newCMCFeed(url, invert), nil case "fiat": - return newFiatFeed(url), nil + return newFiatFeed(url, invert), nil case "fixed": return newFixedFeed(url), nil case "exchange": @@ -63,7 +63,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, invert), nil case "sdex": sdex, e := makeSDEXFeed(url) if e != nil { @@ -75,13 +75,13 @@ func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) { } // MakeFeedPair is the factory method that we expose -func MakeFeedPair(dataTypeA, dataFeedAUrl, dataTypeB, dataFeedBUrl string) (*api.FeedPair, error) { - feedA, e := MakePriceFeed(dataTypeA, dataFeedAUrl) +func MakeFeedPair(dataTypeA string, dataFeedAUrl string, dataTypeAInvert bool, dataTypeB string, dataFeedBUrl string, dataFeedBInvert bool) (*api.FeedPair, error) { + feedA, e := MakePriceFeed(dataTypeA, dataFeedAUrl, dataTypeAInvert) if e != nil { return nil, fmt.Errorf("cannot make a feed pair because of an error when making priceFeed A: %s", e) } - feedB, e := MakePriceFeed(dataTypeB, dataFeedBUrl) + feedB, e := MakePriceFeed(dataTypeB, dataFeedBUrl, dataFeedBInvert) if e != nil { return nil, fmt.Errorf("cannot make a feed pair because of an error when making priceFeed B: %s", e) } diff --git a/plugins/sellStrategy.go b/plugins/sellStrategy.go index e815aa0f4..e56524c6b 100644 --- a/plugins/sellStrategy.go +++ b/plugins/sellStrategy.go @@ -14,8 +14,10 @@ import ( type sellConfig struct { DataTypeA string `valid:"-" toml:"DATA_TYPE_A"` DataFeedAURL string `valid:"-" toml:"DATA_FEED_A_URL"` + DataFeedAInvert bool `valid:"-" toml:"DATA_FEED_A_INVERT"` DataTypeB string `valid:"-" toml:"DATA_TYPE_B"` DataFeedBURL string `valid:"-" toml:"DATA_FEED_B_URL"` + DataFeedBInvert bool `valid:"-" toml:"DATA_FEED_B_INVERT"` PriceTolerance float64 `valid:"-" toml:"PRICE_TOLERANCE"` AmountTolerance float64 `valid:"-" toml:"AMOUNT_TOLERANCE"` AmountOfABase float64 `valid:"-" toml:"AMOUNT_OF_A_BASE"` // the size of order @@ -42,8 +44,10 @@ func makeSellStrategy( pf, e := MakeFeedPair( config.DataTypeA, config.DataFeedAURL, + config.DataFeedAInvert, config.DataTypeB, config.DataFeedBURL, + config.DataFeedBInvert, ) if e != nil { return nil, fmt.Errorf("cannot make the sell strategy because we could not make the feed pair: %s", e)