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

Add sdex price feed #90

Merged
merged 16 commits into from
Jan 31, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion accounting/pnl/pnl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.MakePriceFeed("crypto", url)
priceFeed, e := makePriceFeed(url)
nikhilsaraf marked this conversation as resolved.
Show resolved Hide resolved
if e != nil {
log.Fatal(e)
}
return priceFeed, nil
}

func makePriceFeed(url string) (api.PriceFeed, error) {
return plugins.NewCMCFeed(url), nil
}
12 changes: 12 additions & 0 deletions examples/configs/trader/sample_buysell.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# fiat
# fixed
# exchange
# sdex
#
# We take the values from both feeds and divide them to get the center price.

Expand Down Expand Up @@ -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=&currencies=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.
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
# 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

Expand Down
11 changes: 11 additions & 0 deletions examples/configs/trader/sample_sell.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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=&currencies=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.
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
# 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

Expand Down
2 changes: 2 additions & 0 deletions plugins/buysellStrategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func makeBuySellStrategy(
percentFirst: config.RateOffsetPercentFirst,
}
sellSideFeedPair, e := MakeFeedPair(
sdex,
config.DataTypeA,
config.DataFeedAURL,
config.DataTypeB,
Expand Down Expand Up @@ -76,6 +77,7 @@ func makeBuySellStrategy(
invert: true,
}
buySideFeedPair, e := MakeFeedPair(
sdex,
config.DataTypeB,
config.DataFeedBURL,
config.DataTypeA,
Expand Down
4 changes: 2 additions & 2 deletions plugins/cmcFeed.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
m := new(cmcFeed)
m.url = url
m.client = http.Client{Timeout: 10 * time.Second}
Expand Down
16 changes: 11 additions & 5 deletions plugins/priceFeed.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
)

// MakePriceFeed makes a PriceFeed
func MakePriceFeed(feedType string, url string) (api.PriceFeed, error) {
func MakePriceFeed(sdex *SDEX, feedType, url string) (api.PriceFeed, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a general convention in Kelp we always specify types for each argument even if it's the same, specifically feedType in this case

switch feedType {
case "crypto":
return newCMCFeed(url), nil
return NewCMCFeed(url), nil
case "fiat":
return newFiatFeed(url), nil
case "fixed":
Expand All @@ -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) {
nikhilsaraf marked this conversation as resolved.
Show resolved Hide resolved
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)
}
Expand Down
16 changes: 13 additions & 3 deletions plugins/sdex.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
b, e := api.LoadOrderBook(*assetBase, *assetQuote)
if e != nil {
log.Printf("Can't get SDEX orderbook: %s\n", e)
return
}
return b, e
}
79 changes: 79 additions & 0 deletions plugins/sdexFeed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package plugins

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)
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
s.sdex = sdex
urlParts := strings.Split(url, "/")

baseURL := strings.Split(urlParts[0], ":")
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
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")
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
}
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
}

// 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)
if e != nil {
return 0, fmt.Errorf("unable to get sdex price: %s", e)
}
bids := orderBook.Bids
topBidPrice := utils.PriceAsFloat(bids[0].Price)
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
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) {
nikhilsaraf marked this conversation as resolved.
Show resolved Hide resolved
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
}
1 change: 1 addition & 0 deletions plugins/sellStrategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func makeSellStrategy(
config *sellConfig,
) (api.Strategy, error) {
pf, e := MakeFeedPair(
sdex,
Reidmcc marked this conversation as resolved.
Show resolved Hide resolved
config.DataTypeA,
config.DataFeedAURL,
config.DataTypeB,
Expand Down