From e11a3a3bb74266125676502e6085969f2094f139 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Thu, 10 Jan 2019 19:16:31 -0600 Subject: [PATCH 01/14] add sdex price feed --- examples/configs/trader/sample_buysell.cfg | 12 ++++ examples/configs/trader/sample_sell.cfg | 11 ++++ plugins/buysellStrategy.go | 2 + plugins/priceFeed.go | 14 +++-- plugins/sdexFeed.go | 73 ++++++++++++++++++++++ plugins/sellStrategy.go | 1 + support/utils/functions.go | 10 +++ 7 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 plugins/sdexFeed.go diff --git a/examples/configs/trader/sample_buysell.cfg b/examples/configs/trader/sample_buysell.cfg index e84b814f1..d161ac4ce 100644 --- a/examples/configs/trader/sample_buysell.cfg +++ b/examples/configs/trader/sample_buysell.cfg @@ -6,6 +6,7 @@ # fiat # fixed # exchange +# sdex # # We take the values from both feeds and divide them to get the center price. @@ -34,6 +35,17 @@ DATA_FEED_B_URL="1.0" # 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" +# 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 +# DATA_TYPE_A = "sdex" +# 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:" + +# this is a fixed value of 1 here because the sdex priceFeed provides a ratio of two assets. +# DATA_TYPE_B="fixed" +# DATA_FEED_B_URL="1.0" + # 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 dda89943b..635513de8 100644 --- a/examples/configs/trader/sample_sell.cfg +++ b/examples/configs/trader/sample_sell.cfg @@ -36,6 +36,17 @@ DATA_FEED_B_URL="1.0" # 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" +# 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 +# DATA_TYPE_A = "sdex" +# 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:" + +# this is a fixed value of 1 here because the sdex priceFeed provides a ratio of two assets. +# DATA_TYPE_B="fixed" +# DATA_FEED_B_URL="1.0" + # 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 399381e96..fb68e9ba9 100644 --- a/plugins/buysellStrategy.go +++ b/plugins/buysellStrategy.go @@ -43,6 +43,7 @@ func makeBuySellStrategy( percentFirst: config.RateOffsetPercentFirst, } sellSideFeedPair, e := MakeFeedPair( + sdex, config.DataTypeA, config.DataFeedAURL, config.DataTypeB, @@ -76,6 +77,7 @@ func makeBuySellStrategy( invert: true, } buySideFeedPair, e := MakeFeedPair( + sdex, config.DataTypeB, config.DataFeedBURL, config.DataTypeA, diff --git a/plugins/priceFeed.go b/plugins/priceFeed.go index 64c5abfe9..45c7c5b12 100644 --- a/plugins/priceFeed.go +++ b/plugins/priceFeed.go @@ -9,7 +9,7 @@ import ( ) // MakePriceFeed makes a PriceFeed -func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) { +func MakePriceFeed(sdex *SDEX, feedType, url string) (api.PriceFeed, error) { switch feedType { case "crypto": return newCMCFeed(url), nil @@ -38,18 +38,24 @@ func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) { } tickerAPI := api.TickerAPI(exchange) return newExchangeFeed(url, &tickerAPI, &tradingPair), nil + case "sdex": + SDEXfeed, e := newSDEXFeed(sdex, url) + if e != nil { + return nil, fmt.Errorf("unable to create SDEX priceFeed ") + } + return SDEXfeed, nil } return nil, nil } // 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(sdex *SDEX, dataTypeA, dataFeedAUrl, dataTypeB, dataFeedBUrl string) (*api.FeedPair, error) { + feedA, e := MakePriceFeed(sdex, dataTypeA, dataFeedAUrl) 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(sdex, dataTypeB, dataFeedBUrl) 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/sdexFeed.go b/plugins/sdexFeed.go new file mode 100644 index 000000000..474b1e657 --- /dev/null +++ b/plugins/sdexFeed.go @@ -0,0 +1,73 @@ +package plugins + +import ( + "fmt" + "strings" + + "github.com/interstellar/kelp/support/utils" + "github.com/stellar/go/build" + "github.com/stellar/go/clients/horizon" +) + +type sdexFeed struct { + sdex *SDEX + assetBase *horizon.Asset + assetQuote *horizon.Asset +} + +// newSDEXFeed creates a price feed from buysell's url fields +func newSDEXFeed(sdex *SDEX, url string) (*sdexFeed, error) { + s := new(sdexFeed) + s.sdex = sdex + urlParts := strings.Split(url, "/") + + baseURL := strings.Split(urlParts[0], ":") + baseCode := baseURL[0] + baseIssuer := baseURL[1] + baseConvert, e := parseAsset(baseCode, baseIssuer) + if e != nil { + return nil, fmt.Errorf("unable to convert base asset url to sdex asset") + } + s.assetBase = baseConvert + + quoteURL := strings.Split(urlParts[1], ":") + quoteCode := quoteURL[0] + quoteIssuer := quoteURL[1] + quoteConvert, e := parseAsset(quoteCode, quoteIssuer) + if e != nil { + return nil, fmt.Errorf("unable to convert quote asset url to sdex asset") + } + s.assetQuote = quoteConvert + return s, nil +} + +func (s *sdexFeed) GetPrice() (float64, error) { + orderBook, e := utils.GetOrderBook(s.sdex.API, s.assetBase, s.assetQuote) + if e != nil { + return 0, fmt.Errorf("unable to get sdex price: %s", e) + } + bids := orderBook.Bids + topBidPrice := utils.PriceAsFloat(bids[0].Price) + asks := orderBook.Asks + lowAskPrice := utils.PriceAsFloat(asks[0].Price) + centerPrice := (topBidPrice + lowAskPrice) / 2 + return centerPrice, nil +} + +func parseAsset(code string, issuer string) (*horizon.Asset, error) { + if code != "XLM" && issuer == "" { + return nil, fmt.Errorf("error: issuer can only be empty if asset is XLM") + } + + if code == "XLM" && issuer != "" { + return nil, fmt.Errorf("error: issuer needs to be empty if asset is XLM") + } + + if code == "XLM" { + asset := utils.Asset2Asset2(build.NativeAsset()) + return &asset, nil + } + + asset := utils.Asset2Asset2(build.CreditAsset(code, issuer)) + return &asset, nil +} diff --git a/plugins/sellStrategy.go b/plugins/sellStrategy.go index b3e779f24..79b4c04c9 100644 --- a/plugins/sellStrategy.go +++ b/plugins/sellStrategy.go @@ -39,6 +39,7 @@ func makeSellStrategy( config *sellConfig, ) (api.Strategy, error) { pf, e := MakeFeedPair( + sdex, config.DataTypeA, config.DataFeedAURL, config.DataTypeB, diff --git a/support/utils/functions.go b/support/utils/functions.go index 7d1b33e58..e06b1de81 100644 --- a/support/utils/functions.go +++ b/support/utils/functions.go @@ -86,6 +86,16 @@ func Asset2Asset(Asset horizon.Asset) build.Asset { return a } +// GetOrderBook gets the SDEX order book +func GetOrderBook(api *horizon.Client, assetBase *horizon.Asset, assetQuote *horizon.Asset) (orderBook horizon.OrderBookSummary, e error) { + b, e := api.LoadOrderBook(*assetBase, *assetQuote) + if e != nil { + log.Printf("Can't get SDEX orderbook: %s\n", e) + return + } + return b, e +} + // Asset2Asset2 converts a build.Asset to a horizon.Asset func Asset2Asset2(Asset build.Asset) horizon.Asset { a := horizon.Asset{} From 84add7e672634ec0f5d66fa3ddeb1ba433a16569 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Thu, 10 Jan 2019 19:33:48 -0600 Subject: [PATCH 02/14] accommodate accounting/pnl.go --- accounting/pnl/pnl.go | 2 +- plugins/cmcFeed.go | 4 ++-- plugins/priceFeed.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/accounting/pnl/pnl.go b/accounting/pnl/pnl.go index baba434e6..fc522073c 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.NewCMCFeed(url) } diff --git a/plugins/cmcFeed.go b/plugins/cmcFeed.go index 11730dba9..ff6f32e90 100644 --- a/plugins/cmcFeed.go +++ b/plugins/cmcFeed.go @@ -45,8 +45,8 @@ type cmcFeed struct { // ensure that it implements PriceFeed var _ api.PriceFeed = &cmcFeed{} -// newCMCFeed creates a new CMC Feed from a URL -func newCMCFeed(url string) *cmcFeed { +// NewCMCFeed creates a new CMC Feed from a URL +func NewCMCFeed(url string) *cmcFeed { m := new(cmcFeed) m.url = url m.client = http.Client{Timeout: 10 * time.Second} diff --git a/plugins/priceFeed.go b/plugins/priceFeed.go index 45c7c5b12..dc1472074 100644 --- a/plugins/priceFeed.go +++ b/plugins/priceFeed.go @@ -12,7 +12,7 @@ import ( func MakePriceFeed(sdex *SDEX, feedType, url string) (api.PriceFeed, error) { switch feedType { case "crypto": - return newCMCFeed(url), nil + return NewCMCFeed(url), nil case "fiat": return newFiatFeed(url), nil case "fixed": From 73e541ad4d1ae260ec96551bc6eacea915e0a981 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Thu, 10 Jan 2019 19:46:34 -0600 Subject: [PATCH 03/14] revise pnl.go change --- accounting/pnl/pnl.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/accounting/pnl/pnl.go b/accounting/pnl/pnl.go index fc522073c..8d679b71e 100644 --- a/accounting/pnl/pnl.go +++ b/accounting/pnl/pnl.go @@ -110,5 +110,13 @@ 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.NewCMCFeed(url) + priceFeed, e := makePriceFeed(url) + if e != nil { + log.Fatal(e) + } + return priceFeed, nil +} + +func makePriceFeed(url string) (api.PriceFeed, error) { + return plugins.NewCMCFeed(url), nil } From 1f1653725ae9e0b20a4b3e17f47a0b5e076a951b Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Sat, 12 Jan 2019 09:41:25 -0600 Subject: [PATCH 04/14] move GetOrderBook to plugins/sdex.go --- plugins/sdex.go | 16 +++++++++++++--- plugins/sdexFeed.go | 3 ++- support/utils/functions.go | 10 ---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/sdex.go b/plugins/sdex.go index b4cd7dee6..9e11994da 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -94,9 +94,9 @@ func MakeSDEX( threadTracker: threadTracker, operationalBuffer: operationalBuffer, operationalBufferNonNativePct: operationalBufferNonNativePct, - simMode: simMode, - pair: pair, - assetMap: assetMap, + simMode: simMode, + pair: pair, + assetMap: assetMap, } log.Printf("Using network passphrase: %s\n", sdex.Network.Passphrase) @@ -813,3 +813,13 @@ func (sdex *SDEX) GetLatestTradeCursor() (interface{}, error) { return records[0].PT, nil } + +// GetOrderBook gets the SDEX order book +func GetOrderBook(api *horizon.Client, assetBase *horizon.Asset, assetQuote *horizon.Asset) (orderBook horizon.OrderBookSummary, e error) { + b, e := api.LoadOrderBook(*assetBase, *assetQuote) + if e != nil { + log.Printf("Can't get SDEX orderbook: %s\n", e) + return + } + return b, e +} diff --git a/plugins/sdexFeed.go b/plugins/sdexFeed.go index 474b1e657..ccc74be1c 100644 --- a/plugins/sdexFeed.go +++ b/plugins/sdexFeed.go @@ -41,8 +41,9 @@ func newSDEXFeed(sdex *SDEX, url string) (*sdexFeed, error) { return s, nil } +// GetPrice returns the SDEX mid price for the trading pair func (s *sdexFeed) GetPrice() (float64, error) { - orderBook, e := utils.GetOrderBook(s.sdex.API, s.assetBase, s.assetQuote) + orderBook, e := GetOrderBook(s.sdex.API, s.assetBase, s.assetQuote) if e != nil { return 0, fmt.Errorf("unable to get sdex price: %s", e) } diff --git a/support/utils/functions.go b/support/utils/functions.go index e06b1de81..7d1b33e58 100644 --- a/support/utils/functions.go +++ b/support/utils/functions.go @@ -86,16 +86,6 @@ func Asset2Asset(Asset horizon.Asset) build.Asset { return a } -// GetOrderBook gets the SDEX order book -func GetOrderBook(api *horizon.Client, assetBase *horizon.Asset, assetQuote *horizon.Asset) (orderBook horizon.OrderBookSummary, e error) { - b, e := api.LoadOrderBook(*assetBase, *assetQuote) - if e != nil { - log.Printf("Can't get SDEX orderbook: %s\n", e) - return - } - return b, e -} - // Asset2Asset2 converts a build.Asset to a horizon.Asset func Asset2Asset2(Asset build.Asset) horizon.Asset { a := horizon.Asset{} From 18eb7b079d17310977c65fcc9468c9a5a6816767 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Sun, 13 Jan 2019 15:33:30 -0600 Subject: [PATCH 05/14] ensure feed implements api.PriceFeed --- plugins/sdexFeed.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/sdexFeed.go b/plugins/sdexFeed.go index ccc74be1c..4c8aa4492 100644 --- a/plugins/sdexFeed.go +++ b/plugins/sdexFeed.go @@ -4,17 +4,22 @@ import ( "fmt" "strings" + "github.com/interstellar/kelp/api" "github.com/interstellar/kelp/support/utils" "github.com/stellar/go/build" "github.com/stellar/go/clients/horizon" ) +// sdexFeed represents a pricefeed from the SDEX type sdexFeed struct { sdex *SDEX assetBase *horizon.Asset assetQuote *horizon.Asset } +// ensure that it implements PriceFeed +var _ api.PriceFeed = &sdexFeed{} + // newSDEXFeed creates a price feed from buysell's url fields func newSDEXFeed(sdex *SDEX, url string) (*sdexFeed, error) { s := new(sdexFeed) From 3032a196ff3cb7a4635126dd130b608242ab3a44 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Tue, 15 Jan 2019 22:46:03 -0600 Subject: [PATCH 06/14] major rework with global hack --- accounting/pnl/pnl.go | 10 +-- cmd/trade.go | 3 + examples/configs/trader/sample_buysell.cfg | 6 +- examples/configs/trader/sample_sell.cfg | 7 +- plugins/buysellStrategy.go | 2 - plugins/cmcFeed.go | 2 +- plugins/priceFeed.go | 19 ++--- plugins/sdexFeed.go | 81 ++++++++++++---------- plugins/sellStrategy.go | 1 - support/utils/functions.go | 19 +++++ trader/config.go | 27 ++------ 11 files changed, 88 insertions(+), 89 deletions(-) diff --git a/accounting/pnl/pnl.go b/accounting/pnl/pnl.go index 8d679b71e..baba434e6 100644 --- a/accounting/pnl/pnl.go +++ b/accounting/pnl/pnl.go @@ -110,13 +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) - priceFeed, e := makePriceFeed(url) - if e != nil { - log.Fatal(e) - } - return priceFeed, nil -} - -func makePriceFeed(url string) (api.PriceFeed, error) { - return plugins.NewCMCFeed(url), nil + return plugins.MakePriceFeed("crypto", url) } diff --git a/cmd/trade.go b/cmd/trade.go index 303c6c27d..253063596 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -163,6 +163,9 @@ func init() { sdexAssetMap, ) + // setting the temp hack variables for the sdex price feeds + plugins.PrivateSdexHack = sdex + dataKey := model.MakeSortedBotKey(assetBase, assetQuote) strat, e := plugins.MakeStrategy(sdex, tradingPair, &assetBase, &assetQuote, *strategy, *stratConfigPath, *simMode) if e != nil { diff --git a/examples/configs/trader/sample_buysell.cfg b/examples/configs/trader/sample_buysell.cfg index d161ac4ce..139a154c6 100644 --- a/examples/configs/trader/sample_buysell.cfg +++ b/examples/configs/trader/sample_buysell.cfg @@ -26,7 +26,7 @@ DATA_FEED_A_URL="kraken/XXLM/ZUSD" # this is the URL to a coinmarketcap feed which the bot understands. #DATA_FEED_A_URL="https://api.coinmarketcap.com/v1/ticker/stellar/" -# this is a fixed value of 1 here because the exchange priceFeed provides a ratio of two assets. +# 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" @@ -42,10 +42,6 @@ DATA_FEED_B_URL="1.0" # for XLM leave the issuer string blank # DATA_FEED_A_URL="COUPON:GBMMZMK2DC4FFP4CAI6KCVNCQ7WLO5A7DQU7EC7WGHRDQBZB763X4OQI/XLM:" -# this is a fixed value of 1 here because the sdex priceFeed provides a ratio of two assets. -# DATA_TYPE_B="fixed" -# DATA_FEED_B_URL="1.0" - # 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 635513de8..6d8b7be0b 100644 --- a/examples/configs/trader/sample_sell.cfg +++ b/examples/configs/trader/sample_sell.cfg @@ -8,6 +8,7 @@ # fiat # fixed # exchange +# sdex # # We take the values from both feeds and divide them to get the center price. @@ -27,7 +28,7 @@ DATA_FEED_A_URL="kraken/XXLM/ZUSD" # this is the URL to a coinmarketcap feed which the bot understands. #DATA_FEED_A_URL="https://api.coinmarketcap.com/v1/ticker/stellar/" -# this is a fixed value of 1 here because the exchange priceFeed provides a ratio of two assets. +# 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" @@ -43,10 +44,6 @@ DATA_FEED_B_URL="1.0" # for XLM leave the issuer string blank # DATA_FEED_A_URL="COUPON:GBMMZMK2DC4FFP4CAI6KCVNCQ7WLO5A7DQU7EC7WGHRDQBZB763X4OQI/XLM:" -# this is a fixed value of 1 here because the sdex priceFeed provides a ratio of two assets. -# DATA_TYPE_B="fixed" -# DATA_FEED_B_URL="1.0" - # 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 fb68e9ba9..399381e96 100644 --- a/plugins/buysellStrategy.go +++ b/plugins/buysellStrategy.go @@ -43,7 +43,6 @@ func makeBuySellStrategy( percentFirst: config.RateOffsetPercentFirst, } sellSideFeedPair, e := MakeFeedPair( - sdex, config.DataTypeA, config.DataFeedAURL, config.DataTypeB, @@ -77,7 +76,6 @@ func makeBuySellStrategy( invert: true, } buySideFeedPair, e := MakeFeedPair( - sdex, config.DataTypeB, config.DataFeedBURL, config.DataTypeA, diff --git a/plugins/cmcFeed.go b/plugins/cmcFeed.go index ff6f32e90..a7ecd0c01 100644 --- a/plugins/cmcFeed.go +++ b/plugins/cmcFeed.go @@ -46,7 +46,7 @@ type cmcFeed struct { var _ api.PriceFeed = &cmcFeed{} // NewCMCFeed creates a new CMC Feed from a URL -func NewCMCFeed(url string) *cmcFeed { +func newCMCFeed(url string) *cmcFeed { m := new(cmcFeed) m.url = url m.client = http.Client{Timeout: 10 * time.Second} diff --git a/plugins/priceFeed.go b/plugins/priceFeed.go index dc1472074..5a1ae4a00 100644 --- a/plugins/priceFeed.go +++ b/plugins/priceFeed.go @@ -8,11 +8,14 @@ import ( "github.com/interstellar/kelp/model" ) +// PrivateSdexHack is a temporary hack variable for SDEX price feeds pending refactor +var PrivateSdexHack *SDEX + // MakePriceFeed makes a PriceFeed -func MakePriceFeed(sdex *SDEX, feedType, url string) (api.PriceFeed, error) { +func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) { switch feedType { case "crypto": - return NewCMCFeed(url), nil + return newCMCFeed(url), nil case "fiat": return newFiatFeed(url), nil case "fixed": @@ -39,23 +42,23 @@ func MakePriceFeed(sdex *SDEX, feedType, url string) (api.PriceFeed, error) { tickerAPI := api.TickerAPI(exchange) return newExchangeFeed(url, &tickerAPI, &tradingPair), nil case "sdex": - SDEXfeed, e := newSDEXFeed(sdex, url) + sdexFeed, e := newSDEXFeed(url) if e != nil { - return nil, fmt.Errorf("unable to create SDEX priceFeed ") + return nil, fmt.Errorf("error occured while making the SDEX price feed: %s", e) } - return SDEXfeed, nil + return sdexFeed, nil } return nil, nil } // MakeFeedPair is the factory method that we expose -func MakeFeedPair(sdex *SDEX, dataTypeA, dataFeedAUrl, dataTypeB, dataFeedBUrl string) (*api.FeedPair, error) { - feedA, e := MakePriceFeed(sdex, dataTypeA, dataFeedAUrl) +func MakeFeedPair(dataTypeA, dataFeedAUrl, dataTypeB, dataFeedBUrl string) (*api.FeedPair, error) { + feedA, e := MakePriceFeed(dataTypeA, dataFeedAUrl) 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(sdex, dataTypeB, dataFeedBUrl) + feedB, e := MakePriceFeed(dataTypeB, dataFeedBUrl) 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/sdexFeed.go b/plugins/sdexFeed.go index 4c8aa4492..87a05c775 100644 --- a/plugins/sdexFeed.go +++ b/plugins/sdexFeed.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/interstellar/kelp/api" + "github.com/interstellar/kelp/model" "github.com/interstellar/kelp/support/utils" - "github.com/stellar/go/build" "github.com/stellar/go/clients/horizon" ) @@ -21,29 +21,55 @@ type sdexFeed struct { var _ api.PriceFeed = &sdexFeed{} // newSDEXFeed creates a price feed from buysell's url fields -func newSDEXFeed(sdex *SDEX, url string) (*sdexFeed, error) { - s := new(sdexFeed) - s.sdex = sdex +func newSDEXFeed(url string) (*sdexFeed, error) { urlParts := strings.Split(url, "/") - baseURL := strings.Split(urlParts[0], ":") - baseCode := baseURL[0] - baseIssuer := baseURL[1] - baseConvert, e := parseAsset(baseCode, baseIssuer) + baseParts := strings.Split(urlParts[0], ":") + baseCode := baseParts[0] + baseIssuer := baseParts[1] + baseConvert, e := utils.ParseAsset(baseCode, baseIssuer) if e != nil { - return nil, fmt.Errorf("unable to convert base asset url to sdex asset") + return nil, fmt.Errorf("unable to convert base asset url to sdex asset: %s", e) } - s.assetBase = baseConvert - quoteURL := strings.Split(urlParts[1], ":") - quoteCode := quoteURL[0] - quoteIssuer := quoteURL[1] - quoteConvert, e := parseAsset(quoteCode, quoteIssuer) + quoteParts := strings.Split(urlParts[1], ":") + quoteCode := quoteParts[0] + quoteIssuer := quoteParts[1] + quoteConvert, e := utils.ParseAsset(quoteCode, quoteIssuer) if e != nil { - return nil, fmt.Errorf("unable to convert quote asset url to sdex asset") + return nil, fmt.Errorf("unable to convert quote asset url to sdex asset: %s", e) } - s.assetQuote = quoteConvert - return s, nil + + tradingPair := &model.TradingPair{ + Base: model.Asset(utils.Asset2CodeString(*baseConvert)), + Quote: model.Asset(utils.Asset2CodeString(*quoteConvert)), + } + + sdexAssetMap := map[model.Asset]horizon.Asset{ + tradingPair.Base: *baseConvert, + tradingPair.Quote: *quoteConvert, + } + + feedSDEX := MakeSDEX( + PrivateSdexHack.API, + "", + "", + "", + "", + PrivateSdexHack.Network, + nil, + 0, + 0, + true, + tradingPair, + sdexAssetMap, + ) + + return &sdexFeed{ + sdex: feedSDEX, + assetBase: baseConvert, + assetQuote: quoteConvert, + }, nil } // GetPrice returns the SDEX mid price for the trading pair @@ -52,28 +78,13 @@ func (s *sdexFeed) GetPrice() (float64, error) { if e != nil { return 0, fmt.Errorf("unable to get sdex price: %s", e) } + bids := orderBook.Bids topBidPrice := utils.PriceAsFloat(bids[0].Price) + asks := orderBook.Asks lowAskPrice := utils.PriceAsFloat(asks[0].Price) + centerPrice := (topBidPrice + lowAskPrice) / 2 return centerPrice, nil } - -func parseAsset(code string, issuer string) (*horizon.Asset, error) { - if code != "XLM" && issuer == "" { - return nil, fmt.Errorf("error: issuer can only be empty if asset is XLM") - } - - if code == "XLM" && issuer != "" { - return nil, fmt.Errorf("error: issuer needs to be empty if asset is XLM") - } - - if code == "XLM" { - asset := utils.Asset2Asset2(build.NativeAsset()) - return &asset, nil - } - - asset := utils.Asset2Asset2(build.CreditAsset(code, issuer)) - return &asset, nil -} diff --git a/plugins/sellStrategy.go b/plugins/sellStrategy.go index 79b4c04c9..b3e779f24 100644 --- a/plugins/sellStrategy.go +++ b/plugins/sellStrategy.go @@ -39,7 +39,6 @@ func makeSellStrategy( config *sellConfig, ) (api.Strategy, error) { pf, e := MakeFeedPair( - sdex, config.DataTypeA, config.DataFeedAURL, config.DataTypeB, diff --git a/support/utils/functions.go b/support/utils/functions.go index 7d1b33e58..bb5d3d631 100644 --- a/support/utils/functions.go +++ b/support/utils/functions.go @@ -235,3 +235,22 @@ func CheckedString(v interface{}) string { } return fmt.Sprintf("%v", v) } + +// ParseAsset returns a horizon asset a string +func ParseAsset(code string, issuer string) (*horizon.Asset, error) { + if code != "XLM" && issuer == "" { + return nil, fmt.Errorf("error: issuer can only be empty if asset is XLM") + } + + if code == "XLM" && issuer != "" { + return nil, fmt.Errorf("error: issuer needs to be empty if asset is XLM") + } + + if code == "XLM" { + asset := Asset2Asset2(build.NativeAsset()) + return &asset, nil + } + + asset := Asset2Asset2(build.CreditAsset(code, issuer)) + return &asset, nil +} diff --git a/trader/config.go b/trader/config.go index b5c3e1277..0197a38e8 100644 --- a/trader/config.go +++ b/trader/config.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/interstellar/kelp/support/utils" - "github.com/stellar/go/build" "github.com/stellar/go/clients/horizon" ) @@ -75,15 +74,15 @@ func (b *BotConfig) Init() error { return fmt.Errorf("error: both assets cannot be the same '%s:%s'", b.AssetCodeA, b.IssuerA) } - asset, e := parseAsset(b.AssetCodeA, b.IssuerA, "A") + asset, e := utils.ParseAsset(b.AssetCodeA, b.IssuerA) if e != nil { - return e + return fmt.Errorf("Error while parsing Asset A: %s", e) } b.assetBase = *asset - asset, e = parseAsset(b.AssetCodeB, b.IssuerB, "B") + asset, e = utils.ParseAsset(b.AssetCodeB, b.IssuerB) if e != nil { - return e + return fmt.Errorf("Error while parsing Asset B: %s", e) } b.assetQuote = *asset @@ -98,21 +97,3 @@ func (b *BotConfig) Init() error { b.sourceAccount, e = utils.ParseSecret(b.SourceSecretSeed) return e } - -func parseAsset(code string, issuer string, letter string) (*horizon.Asset, error) { - if code != XLM && issuer == "" { - return nil, fmt.Errorf("error: ISSUER_%s can only be empty if ASSET_CODE_%s is '%s'", letter, letter, XLM) - } - - if code == XLM && issuer != "" { - return nil, fmt.Errorf("error: ISSUER_%s needs to be empty if ASSET_CODE_%s is '%s'", letter, letter, XLM) - } - - if code == XLM { - asset := utils.Asset2Asset2(build.NativeAsset()) - return &asset, nil - } - - asset := utils.Asset2Asset2(build.CreditAsset(code, issuer)) - return &asset, nil -} From 189aea4c2c90627186c8e55ec65c712e800bf2e5 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Tue, 15 Jan 2019 22:52:50 -0600 Subject: [PATCH 07/14] fix CMCFeed comment --- plugins/cmcFeed.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cmcFeed.go b/plugins/cmcFeed.go index a7ecd0c01..11730dba9 100644 --- a/plugins/cmcFeed.go +++ b/plugins/cmcFeed.go @@ -45,7 +45,7 @@ type cmcFeed struct { // ensure that it implements PriceFeed var _ api.PriceFeed = &cmcFeed{} -// NewCMCFeed creates a new CMC Feed from a URL +// newCMCFeed creates a new CMC Feed from a URL func newCMCFeed(url string) *cmcFeed { m := new(cmcFeed) m.url = url From 7239b3b57115dc82937f8782f7e52b19a8dd6a96 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Fri, 18 Jan 2019 19:11:27 -0600 Subject: [PATCH 08/14] rework GetOrderbook, etc. --- cmd/trade.go | 3 ++- plugins/priceFeed.go | 11 ++++++++--- plugins/sdex.go | 10 ++++++++-- plugins/sdexFeed.go | 6 +++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/cmd/trade.go b/cmd/trade.go index 253063596..e6a38ddac 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -164,7 +164,8 @@ func init() { ) // setting the temp hack variables for the sdex price feeds - plugins.PrivateSdexHack = sdex + plugins.PrivateSdexHack.API = sdex.API + plugins.PrivateSdexHack.Network = sdex.Network dataKey := model.MakeSortedBotKey(assetBase, assetQuote) strat, e := plugins.MakeStrategy(sdex, tradingPair, &assetBase, &assetQuote, *strategy, *stratConfigPath, *simMode) diff --git a/plugins/priceFeed.go b/plugins/priceFeed.go index 5a1ae4a00..2695d0727 100644 --- a/plugins/priceFeed.go +++ b/plugins/priceFeed.go @@ -6,10 +6,15 @@ import ( "github.com/interstellar/kelp/api" "github.com/interstellar/kelp/model" + "github.com/stellar/go/build" + "github.com/stellar/go/clients/horizon" ) // PrivateSdexHack is a temporary hack variable for SDEX price feeds pending refactor -var PrivateSdexHack *SDEX +var PrivateSdexHack struct { + API *horizon.Client + Network build.Network +} // MakePriceFeed makes a PriceFeed func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) { @@ -42,11 +47,11 @@ func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) { tickerAPI := api.TickerAPI(exchange) return newExchangeFeed(url, &tickerAPI, &tradingPair), nil case "sdex": - sdexFeed, e := newSDEXFeed(url) + sdex, e := makeSDEXFeed(url) if e != nil { return nil, fmt.Errorf("error occured while making the SDEX price feed: %s", e) } - return sdexFeed, nil + return sdex, nil } return nil, nil } diff --git a/plugins/sdex.go b/plugins/sdex.go index 9e11994da..03307d02a 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -815,8 +815,14 @@ func (sdex *SDEX) GetLatestTradeCursor() (interface{}, error) { } // GetOrderBook gets the SDEX order book -func GetOrderBook(api *horizon.Client, assetBase *horizon.Asset, assetQuote *horizon.Asset) (orderBook horizon.OrderBookSummary, e error) { - b, e := api.LoadOrderBook(*assetBase, *assetQuote) +func (sdex *SDEX) GetOrderBook(pair *model.TradingPair) (orderBook horizon.OrderBookSummary, e error) { + //b, e := api.LoadOrderBook(*assetBase, *assetQuote) + baseAsset, quoteAsset, e := sdex.pair2Assets() + if e != nil { + log.Printf("%s", e) + return + } + b, e := sdex.API.LoadOrderBook(baseAsset, quoteAsset) if e != nil { log.Printf("Can't get SDEX orderbook: %s\n", e) return diff --git a/plugins/sdexFeed.go b/plugins/sdexFeed.go index 87a05c775..301b45445 100644 --- a/plugins/sdexFeed.go +++ b/plugins/sdexFeed.go @@ -20,8 +20,8 @@ type sdexFeed struct { // ensure that it implements PriceFeed var _ api.PriceFeed = &sdexFeed{} -// newSDEXFeed creates a price feed from buysell's url fields -func newSDEXFeed(url string) (*sdexFeed, error) { +// makeSDEXFeed creates a price feed from buysell's url fields +func makeSDEXFeed(url string) (*sdexFeed, error) { urlParts := strings.Split(url, "/") baseParts := strings.Split(urlParts[0], ":") @@ -74,7 +74,7 @@ func newSDEXFeed(url string) (*sdexFeed, error) { // GetPrice returns the SDEX mid price for the trading pair func (s *sdexFeed) GetPrice() (float64, error) { - orderBook, e := GetOrderBook(s.sdex.API, s.assetBase, s.assetQuote) + orderBook, e := s.sdex.GetOrderBook(s.sdex.pair) if e != nil { return 0, fmt.Errorf("unable to get sdex price: %s", e) } From c4330b5b7da39efd8af8d0bca4d12f8774fe1dd9 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Tue, 22 Jan 2019 21:21:20 -0600 Subject: [PATCH 09/14] make GetOrderBook use api, etc. --- cmd/trade.go | 4 +-- plugins/sdex.go | 87 +++++++++++++++++++++++++++++++++++++++++---- plugins/sdexFeed.go | 10 +++--- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/cmd/trade.go b/cmd/trade.go index 287dfece2..9e9fa9f8b 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -164,8 +164,8 @@ func init() { ) // setting the temp hack variables for the sdex price feeds - plugins.PrivateSdexHack.API = sdex.API - plugins.PrivateSdexHack.Network = sdex.Network + plugins.PrivateSdexHack.API = client + plugins.PrivateSdexHack.Network = utils.ParseNetwork(botConfig.HorizonURL) dataKey := model.MakeSortedBotKey(assetBase, assetQuote) strat, e := plugins.MakeStrategy(sdex, tradingPair, &assetBase, &assetQuote, *strategy, *stratConfigPath, *simMode) diff --git a/plugins/sdex.go b/plugins/sdex.go index 03307d02a..0de312969 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -7,6 +7,7 @@ import ( "reflect" "strconv" "strings" + "time" "github.com/interstellar/kelp/api" "github.com/interstellar/kelp/model" @@ -815,17 +816,89 @@ func (sdex *SDEX) GetLatestTradeCursor() (interface{}, error) { } // GetOrderBook gets the SDEX order book -func (sdex *SDEX) GetOrderBook(pair *model.TradingPair) (orderBook horizon.OrderBookSummary, e error) { - //b, e := api.LoadOrderBook(*assetBase, *assetQuote) +func (sdex *SDEX) GetOrderBook(pair *model.TradingPair) (*model.OrderBook, error) { baseAsset, quoteAsset, e := sdex.pair2Assets() if e != nil { - log.Printf("%s", e) - return + return nil, fmt.Errorf("Can't get SDEX orderbook: %s", e) } b, e := sdex.API.LoadOrderBook(baseAsset, quoteAsset) if e != nil { - log.Printf("Can't get SDEX orderbook: %s\n", e) - return + return nil, fmt.Errorf("Can't get SDEX orderbook: %s", e) + } + + sdexBids := b.Bids + sdexAsks := b.Asks + + var transformedBids []model.Order + var transformedAsks []model.Order + + timeStamp := model.MakeTimestampFromTime(time.Now()) + + for i := 0; i < len(sdexBids); i++ { + price, e := model.NumberFromString(sdexBids[i].Price, sdexOrderConstraints.PricePrecision) + if e != nil { + return nil, fmt.Errorf("Error while transforming orderbook: %s", e) + } + volume, e := model.NumberFromString(sdexBids[i].Amount, sdexOrderConstraints.VolumePrecision) + if e != nil { + return nil, fmt.Errorf("Error while transforming orderbook: %s", e) + } + bid := model.Order{ + Pair: pair, + OrderAction: false, + OrderType: 1, + Price: price, + Volume: volume, + Timestamp: timeStamp, + } + transformedBids = append(transformedBids, bid) + } - return b, e + + for i := 0; i < len(sdexAsks); i++ { + price, e := model.NumberFromString(sdexAsks[i].Price, sdexOrderConstraints.PricePrecision) + if e != nil { + return nil, fmt.Errorf("Error while transforming orderbook: %s", e) + } + volume, e := model.NumberFromString(sdexAsks[i].Amount, sdexOrderConstraints.VolumePrecision) + if e != nil { + return nil, fmt.Errorf("Error while transforming orderbook: %s", e) + } + ask := model.Order{ + Pair: pair, + OrderAction: true, + OrderType: 1, + Price: price, + Volume: volume, + Timestamp: timeStamp, + } + transformedAsks = append(transformedAsks, ask) + + } + + APIBook := model.MakeOrderBook( + pair, + transformedAsks, + transformedBids, + ) + + return APIBook, nil + } + +// OrderBook encapsulates the concept of an orderbook on a market +// type OrderBook struct { +// pair *TradingPair +// asks []Order +// bids []Order +// } + +// Order represents an order in the orderbook +// type Order struct { +// Pair *TradingPair +// OrderAction OrderAction +// OrderType OrderType +// Price *Number +// Volume *Number +// Timestamp *Timestamp +// } diff --git a/plugins/sdexFeed.go b/plugins/sdexFeed.go index 301b45445..d7ff042aa 100644 --- a/plugins/sdexFeed.go +++ b/plugins/sdexFeed.go @@ -79,12 +79,12 @@ func (s *sdexFeed) GetPrice() (float64, error) { return 0, fmt.Errorf("unable to get sdex price: %s", e) } - bids := orderBook.Bids - topBidPrice := utils.PriceAsFloat(bids[0].Price) + bids := orderBook.Bids() + topBidPrice := bids[0].Price - asks := orderBook.Asks - lowAskPrice := utils.PriceAsFloat(asks[0].Price) + asks := orderBook.Asks() + lowAskPrice := asks[0].Price - centerPrice := (topBidPrice + lowAskPrice) / 2 + centerPrice := (topBidPrice.AsFloat() + lowAskPrice.AsFloat()) / 2 return centerPrice, nil } From f76942bb31e5b6982526a6b82e8ec6f94f642a92 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Wed, 23 Jan 2019 00:37:35 -0600 Subject: [PATCH 10/14] remove stray comments --- plugins/sdex.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/plugins/sdex.go b/plugins/sdex.go index 0de312969..4f46449cb 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -885,20 +885,3 @@ func (sdex *SDEX) GetOrderBook(pair *model.TradingPair) (*model.OrderBook, error return APIBook, nil } - -// OrderBook encapsulates the concept of an orderbook on a market -// type OrderBook struct { -// pair *TradingPair -// asks []Order -// bids []Order -// } - -// Order represents an order in the orderbook -// type Order struct { -// Pair *TradingPair -// OrderAction OrderAction -// OrderType OrderType -// Price *Number -// Volume *Number -// Timestamp *Timestamp -// } From 07e72b775fb0a2d2851a31443229f35dc371f665 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Wed, 23 Jan 2019 18:40:33 -0600 Subject: [PATCH 11/14] revise value functions --- plugins/sdex.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/sdex.go b/plugins/sdex.go index 4f46449cb..2e2fc6923 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -832,7 +832,7 @@ func (sdex *SDEX) GetOrderBook(pair *model.TradingPair) (*model.OrderBook, error var transformedBids []model.Order var transformedAsks []model.Order - timeStamp := model.MakeTimestampFromTime(time.Now()) + timeStamp := model.MakeTimestamp(time.Now().UnixNano() / int64(time.Millisecond)) for i := 0; i < len(sdexBids); i++ { price, e := model.NumberFromString(sdexBids[i].Price, sdexOrderConstraints.PricePrecision) @@ -845,8 +845,8 @@ func (sdex *SDEX) GetOrderBook(pair *model.TradingPair) (*model.OrderBook, error } bid := model.Order{ Pair: pair, - OrderAction: false, - OrderType: 1, + OrderAction: model.OrderActionBuy, + OrderType: model.OrderTypeLimit, Price: price, Volume: volume, Timestamp: timeStamp, @@ -856,18 +856,18 @@ func (sdex *SDEX) GetOrderBook(pair *model.TradingPair) (*model.OrderBook, error } for i := 0; i < len(sdexAsks); i++ { - price, e := model.NumberFromString(sdexAsks[i].Price, sdexOrderConstraints.PricePrecision) + price, e := model.NumberFromString(sdexAsks[i].Price, utils.SdexPrecision) if e != nil { return nil, fmt.Errorf("Error while transforming orderbook: %s", e) } - volume, e := model.NumberFromString(sdexAsks[i].Amount, sdexOrderConstraints.VolumePrecision) + volume, e := model.NumberFromString(sdexAsks[i].Amount, utils.SdexPrecision) if e != nil { return nil, fmt.Errorf("Error while transforming orderbook: %s", e) } ask := model.Order{ Pair: pair, - OrderAction: true, - OrderType: 1, + OrderAction: model.OrderActionSell, + OrderType: model.OrderTypeLimit, Price: price, Volume: volume, Timestamp: timeStamp, From 978254a8b8d6af7977bd0117102e0243eae21085 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 30 Jan 2019 17:41:07 -0800 Subject: [PATCH 12/14] fix GetOrderBook API signature and throw on mismatched input pair, refactor/exctract order transform step --- plugins/sdex.go | 104 ++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/plugins/sdex.go b/plugins/sdex.go index 2e2fc6923..bf6866ddd 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -95,9 +95,9 @@ func MakeSDEX( threadTracker: threadTracker, operationalBuffer: operationalBuffer, operationalBufferNonNativePct: operationalBufferNonNativePct, - simMode: simMode, - pair: pair, - assetMap: assetMap, + simMode: simMode, + pair: pair, + assetMap: assetMap, } log.Printf("Using network passphrase: %s\n", sdex.Network.Passphrase) @@ -815,73 +815,71 @@ func (sdex *SDEX) GetLatestTradeCursor() (interface{}, error) { return records[0].PT, nil } -// GetOrderBook gets the SDEX order book -func (sdex *SDEX) GetOrderBook(pair *model.TradingPair) (*model.OrderBook, error) { +// GetOrderBook gets the SDEX orderbook +func (sdex *SDEX) GetOrderBook(pair *model.TradingPair, maxCount int32) (*model.OrderBook, error) { + if pair != sdex.pair { + return nil, fmt.Errorf("unregistered trading pair (%s) cannot be converted to horizon.Assets, instance's pair: %s", pair.String(), sdex.pair.String()) + } + baseAsset, quoteAsset, e := sdex.pair2Assets() if e != nil { - return nil, fmt.Errorf("Can't get SDEX orderbook: %s", e) + return nil, fmt.Errorf("cannot get SDEX orderbook: %s", e) } - b, e := sdex.API.LoadOrderBook(baseAsset, quoteAsset) + + ob, e := sdex.API.LoadOrderBook(baseAsset, quoteAsset) if e != nil { - return nil, fmt.Errorf("Can't get SDEX orderbook: %s", e) + return nil, fmt.Errorf("cannot get SDEX orderbook: %s", e) } - sdexBids := b.Bids - sdexAsks := b.Asks + ts := model.MakeTimestamp(time.Now().UnixNano() / int64(time.Millisecond)) + transformedBids, e := sdex.transformHorizonOrders(pair, ob.Bids, model.OrderActionBuy, ts, maxCount) + if e != nil { + return nil, fmt.Errorf("could not transform bid side of SDEX orderbook: %s", e) + } - var transformedBids []model.Order - var transformedAsks []model.Order + transformedAsks, e := sdex.transformHorizonOrders(pair, ob.Asks, model.OrderActionSell, ts, maxCount) + if e != nil { + return nil, fmt.Errorf("could not transform ask side of SDEX orderbook: %s", e) + } - timeStamp := model.MakeTimestamp(time.Now().UnixNano() / int64(time.Millisecond)) + return model.MakeOrderBook( + pair, + transformedAsks, + transformedBids, + ), nil +} - for i := 0; i < len(sdexBids); i++ { - price, e := model.NumberFromString(sdexBids[i].Price, sdexOrderConstraints.PricePrecision) - if e != nil { - return nil, fmt.Errorf("Error while transforming orderbook: %s", e) - } - volume, e := model.NumberFromString(sdexBids[i].Amount, sdexOrderConstraints.VolumePrecision) - if e != nil { - return nil, fmt.Errorf("Error while transforming orderbook: %s", e) - } - bid := model.Order{ - Pair: pair, - OrderAction: model.OrderActionBuy, - OrderType: model.OrderTypeLimit, - Price: price, - Volume: volume, - Timestamp: timeStamp, +func (sdex *SDEX) transformHorizonOrders( + pair *model.TradingPair, + side []horizon.PriceLevel, + orderAction model.OrderAction, + ts *model.Timestamp, + maxCount int32, +) ([]model.Order, error) { + transformed := []model.Order{} + for i, o := range side { + if i >= int(maxCount) { + break } - transformedBids = append(transformedBids, bid) - } + floatPrice := float64(o.PriceR.N) / float64(o.PriceR.D) + price := model.NumberFromFloat(floatPrice, sdexOrderConstraints.PricePrecision) - for i := 0; i < len(sdexAsks); i++ { - price, e := model.NumberFromString(sdexAsks[i].Price, utils.SdexPrecision) + volumeInt, e := model.NumberFromString(o.Amount, sdexOrderConstraints.VolumePrecision) if e != nil { - return nil, fmt.Errorf("Error while transforming orderbook: %s", e) + return nil, fmt.Errorf("could not parse amount for horizon order: %s", e) } - volume, e := model.NumberFromString(sdexAsks[i].Amount, utils.SdexPrecision) - if e != nil { - return nil, fmt.Errorf("Error while transforming orderbook: %s", e) - } - ask := model.Order{ + // horizon amounts are stroops as integers so we want to convert it to float appropriately + volume := volumeInt.Scale(1.0 / math.Pow(10, 7)) + + transformed = append(transformed, model.Order{ Pair: pair, - OrderAction: model.OrderActionSell, + OrderAction: orderAction, OrderType: model.OrderTypeLimit, Price: price, Volume: volume, - Timestamp: timeStamp, - } - transformedAsks = append(transformedAsks, ask) - + Timestamp: ts, + }) } - - APIBook := model.MakeOrderBook( - pair, - transformedAsks, - transformedBids, - ) - - return APIBook, nil - + return transformed, nil } From d4e7cf3fcd08462383bc710a31bac81db719c739 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 30 Jan 2019 18:03:32 -0800 Subject: [PATCH 13/14] clean up sdexFeed and PrivateSdexHack --- cmd/trade.go | 9 +++++-- plugins/priceFeed.go | 20 ++++++++++++++-- plugins/sdexFeed.go | 57 ++++++++++++++++++++++---------------------- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/cmd/trade.go b/cmd/trade.go index 9e9fa9f8b..48373bdb7 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -164,8 +164,13 @@ func init() { ) // setting the temp hack variables for the sdex price feeds - plugins.PrivateSdexHack.API = client - plugins.PrivateSdexHack.Network = utils.ParseNetwork(botConfig.HorizonURL) + e = plugins.SetPrivateSdexHack(client, utils.ParseNetwork(botConfig.HorizonURL)) + if e != nil { + l.Info("") + l.Errorf("%s", e) + // we want to delete all the offers and exit here since there is something wrong with our setup + deleteAllOffersAndExit(l, botConfig, client, sdex) + } dataKey := model.MakeSortedBotKey(assetBase, assetQuote) strat, e := plugins.MakeStrategy(sdex, tradingPair, &assetBase, &assetQuote, *strategy, *stratConfigPath, *simMode) diff --git a/plugins/priceFeed.go b/plugins/priceFeed.go index 2695d0727..b7868757f 100644 --- a/plugins/priceFeed.go +++ b/plugins/priceFeed.go @@ -10,12 +10,28 @@ import ( "github.com/stellar/go/clients/horizon" ) -// PrivateSdexHack is a temporary hack variable for SDEX price feeds pending refactor -var PrivateSdexHack struct { +// privateSdexHack is a temporary hack struct for SDEX price feeds pending refactor +type privateSdexHack struct { API *horizon.Client Network build.Network } +// privateSdexHackVar is a temporary hack variable for SDEX price feeds pending refactor +var privateSdexHackVar *privateSdexHack + +// SetPrivateSdexHack sets the privateSdexHack variable which is temporary until the pending SDEX price feed refactor +func SetPrivateSdexHack(api *horizon.Client, network build.Network) error { + if privateSdexHackVar != nil { + return fmt.Errorf("privateSdexHack is already set: %+v", privateSdexHackVar) + } + + privateSdexHackVar = &privateSdexHack{ + API: api, + Network: network, + } + return nil +} + // MakePriceFeed makes a PriceFeed func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) { switch feedType { diff --git a/plugins/sdexFeed.go b/plugins/sdexFeed.go index d7ff042aa..f98001fad 100644 --- a/plugins/sdexFeed.go +++ b/plugins/sdexFeed.go @@ -24,39 +24,30 @@ var _ api.PriceFeed = &sdexFeed{} func makeSDEXFeed(url string) (*sdexFeed, error) { urlParts := strings.Split(url, "/") - baseParts := strings.Split(urlParts[0], ":") - baseCode := baseParts[0] - baseIssuer := baseParts[1] - baseConvert, e := utils.ParseAsset(baseCode, baseIssuer) + baseAsset, e := parseHorizonAsset(urlParts[0]) if e != nil { return nil, fmt.Errorf("unable to convert base asset url to sdex asset: %s", e) } - - quoteParts := strings.Split(urlParts[1], ":") - quoteCode := quoteParts[0] - quoteIssuer := quoteParts[1] - quoteConvert, e := utils.ParseAsset(quoteCode, quoteIssuer) + quoteAsset, e := parseHorizonAsset(urlParts[1]) if e != nil { return nil, fmt.Errorf("unable to convert quote asset url to sdex asset: %s", e) } tradingPair := &model.TradingPair{ - Base: model.Asset(utils.Asset2CodeString(*baseConvert)), - Quote: model.Asset(utils.Asset2CodeString(*quoteConvert)), + Base: model.Asset(utils.Asset2CodeString(*baseAsset)), + Quote: model.Asset(utils.Asset2CodeString(*quoteAsset)), } - sdexAssetMap := map[model.Asset]horizon.Asset{ - tradingPair.Base: *baseConvert, - tradingPair.Quote: *quoteConvert, + tradingPair.Base: *baseAsset, + tradingPair.Quote: *quoteAsset, } - - feedSDEX := MakeSDEX( - PrivateSdexHack.API, + sdex := MakeSDEX( + privateSdexHackVar.API, "", "", "", "", - PrivateSdexHack.Network, + privateSdexHackVar.Network, nil, 0, 0, @@ -66,25 +57,35 @@ func makeSDEXFeed(url string) (*sdexFeed, error) { ) return &sdexFeed{ - sdex: feedSDEX, - assetBase: baseConvert, - assetQuote: quoteConvert, + sdex: sdex, + assetBase: baseAsset, + assetQuote: quoteAsset, }, nil } +func parseHorizonAsset(assetString string) (*horizon.Asset, error) { + parts := strings.Split(assetString, ":") + code := parts[0] + issuer := parts[1] + + asset, e := utils.ParseAsset(code, issuer) + if e != nil { + return nil, fmt.Errorf("could not read horizon asset from string (%s): %s", assetString, e) + } + + return asset, e +} + // GetPrice returns the SDEX mid price for the trading pair func (s *sdexFeed) GetPrice() (float64, error) { - orderBook, e := s.sdex.GetOrderBook(s.sdex.pair) + orderBook, e := s.sdex.GetOrderBook(s.sdex.pair, 1) if e != nil { return 0, fmt.Errorf("unable to get sdex price: %s", e) } - bids := orderBook.Bids() - topBidPrice := bids[0].Price - - asks := orderBook.Asks() - lowAskPrice := asks[0].Price + topBidPrice := orderBook.Bids()[0].Price + topAskPrice := orderBook.Asks()[0].Price - centerPrice := (topBidPrice.AsFloat() + lowAskPrice.AsFloat()) / 2 + centerPrice := topBidPrice.Add(*topAskPrice).Scale(0.5).AsFloat() return centerPrice, nil } From 93ecf4457b28be52b47f22f2766281370ff54f66 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 30 Jan 2019 18:39:33 -0800 Subject: [PATCH 14/14] fix volume number for bid side --- plugins/sdex.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/sdex.go b/plugins/sdex.go index bf6866ddd..c409e3d46 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -865,12 +865,15 @@ func (sdex *SDEX) transformHorizonOrders( floatPrice := float64(o.PriceR.N) / float64(o.PriceR.D) price := model.NumberFromFloat(floatPrice, sdexOrderConstraints.PricePrecision) - volumeInt, e := model.NumberFromString(o.Amount, sdexOrderConstraints.VolumePrecision) + volume, e := model.NumberFromString(o.Amount, sdexOrderConstraints.VolumePrecision) if e != nil { return nil, fmt.Errorf("could not parse amount for horizon order: %s", e) } - // horizon amounts are stroops as integers so we want to convert it to float appropriately - volume := volumeInt.Scale(1.0 / math.Pow(10, 7)) + // special handling of amount for bids + if orderAction.IsBuy() { + // use floatPrice here for more accuracy since floatPrice is what will be used in stellar-core + volume = volume.Scale(1.0 / floatPrice) + } transformed = append(transformed, model.Order{ Pair: pair,