diff --git a/tools/exchange_template/exchange_template.go b/tools/exchange_template/exchange_template.go new file mode 100644 index 00000000..7bc7ac1f --- /dev/null +++ b/tools/exchange_template/exchange_template.go @@ -0,0 +1,232 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "html/template" + "log" + "os" + "os/exec" + "strings" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" +) + +const ( + packageTests = "%s_test.go" + packageTypes = "%s_types.go" + packageWrapper = "%s_wrapper.go" + packageMain = "%s.go" + packageReadme = "README.md" + + exchangePackageLocation = "../../exchanges/" + exchangeLocation = "../../exchange.go" +) + +var ( + exchangeDirectory string + exchangeTest string + exchangeTypes string + exchangeWrapper string + exchangeMain string + exchangeReadme string +) + +type exchange struct { + Name string + CapitalName string + Variable string + REST bool + WS bool + FIX bool +} + +func main() { + var newExchangeName string + var websocketSupport, restSupport, fixSupport bool + + flag.StringVar(&newExchangeName, "name", "", "-name [string] adds a new exchange") + flag.BoolVar(&websocketSupport, "ws", false, "-websocket adds websocket support") + flag.BoolVar(&restSupport, "rest", false, "-rest adds REST support") + flag.BoolVar(&fixSupport, "fix", false, "-fix adds FIX support?") + + flag.Parse() + + fmt.Println("GoCryptoTrader: Exchange templating tool.") + + if newExchangeName == "" || newExchangeName == " " { + log.Fatal(`GoCryptoTrader: Exchange templating tool exchange name not set e.g. "exchange_template -name [newExchangeNameString]"`) + } + + if !websocketSupport && !restSupport && !fixSupport { + log.Fatal(`GoCryptoTrader: Exchange templating tool support not set e.g. "exchange_template -name [newExchangeNameString] [-fix -ws -rest]"`) + } + + fmt.Println("Exchange Name: ", newExchangeName) + fmt.Println("Websocket Supported: ", websocketSupport) + fmt.Println("REST Supported: ", restSupport) + fmt.Println("FIX Supported: ", fixSupport) + fmt.Println() + fmt.Println("Please check if everything is correct then press enter to continue...") + + reader := bufio.NewReader(os.Stdin) + choice, _, err := reader.ReadRune() + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool bufio.reader error ", err) + } + + if choice != '\n' { + log.Fatal("GoCryptoTrader: Exchange templating tool stopped...") + } + + newExchangeName = common.StringToLower(newExchangeName) + split := strings.Split(newExchangeName, "") + v := split[0] + capName := common.StringToUpper(v) + strings.Join(split[1:], "") + + exch := exchange{ + Name: newExchangeName, + CapitalName: capName, + Variable: v, + REST: restSupport, + WS: websocketSupport, + FIX: fixSupport, + } + + configTestFile := config.GetConfig() + err = configTestFile.LoadConfig("../../testdata/configtest.json") + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating configuration retrieval error ", err) + } + // NOTE need to nullify encrypt configuration + + var configTestExchanges []string + + for _, exch := range configTestFile.Exchanges { + configTestExchanges = append(configTestExchanges, exch.Name) + } + + if common.StringDataContainsUpper(configTestExchanges, capName) { + log.Fatal("GoCryptoTrader: Exchange templating configuration error - exchange already exists") + } + + newExchConfig := config.ExchangeConfig{} + newExchConfig.Name = capName + newExchConfig.Enabled = true + newExchConfig.RESTPollingDelay = 10 + newExchConfig.APIKey = "Key" + newExchConfig.APISecret = "Secret" + newExchConfig.AssetTypes = "SPOT" + + configTestFile.Exchanges = append(configTestFile.Exchanges, newExchConfig) + // TODO sorting function so exchanges are in alphabetical order - low priority + + err = configTestFile.SaveConfig("../../testdata/configtest.json") + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating configuration error - cannot save") + } + + exchangeDirectory = exchangePackageLocation + newExchangeName + "/" + exchangeTest = fmt.Sprintf(exchangeDirectory+packageTests, newExchangeName) + exchangeTypes = fmt.Sprintf(exchangeDirectory+packageTypes, newExchangeName) + exchangeWrapper = fmt.Sprintf(exchangeDirectory+packageWrapper, newExchangeName) + exchangeMain = fmt.Sprintf(exchangeDirectory+packageMain, newExchangeName) + exchangeReadme = exchangeDirectory + packageReadme + + err = os.Mkdir(exchangeDirectory, 0700) + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool cannot make directory ", err) + } + + tReadme, err := template.New("readme").ParseFiles("readme_file.tmpl") + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) + } + newFile(exchangeReadme) + r1, err := os.OpenFile(exchangeReadme, os.O_WRONLY, 0700) + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) + } + tReadme.Execute(r1, exch) + + tMain, err := template.New("main").ParseFiles("main_file.tmpl") + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) + } + newFile(exchangeMain) + m1, err := os.OpenFile(exchangeMain, os.O_WRONLY, 0700) + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) + } + tMain.Execute(m1, exch) + + tTest, err := template.New("test").ParseFiles("test_file.tmpl") + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) + } + newFile(exchangeTest) + t1, err := os.OpenFile(exchangeTest, os.O_WRONLY, 0700) + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) + } + tTest.Execute(t1, exch) + + tType, err := template.New("type").ParseFiles("type_file.tmpl") + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) + } + newFile(exchangeTypes) + ty1, err := os.OpenFile(exchangeTypes, os.O_WRONLY, 0700) + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) + } + tType.Execute(ty1, exch) + + tWrapper, err := template.New("wrapper").ParseFiles("wrapper_file.tmpl") + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) + } + newFile(exchangeWrapper) + w1, err := os.OpenFile(exchangeWrapper, os.O_WRONLY, 0700) + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) + } + tWrapper.Execute(w1, exch) + + err = exec.Command("go", "fmt", exchangeDirectory).Run() + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool go fmt error ", err) + } + + err = exec.Command("go", "fmt", exchangeDirectory).Run() + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool go fmt error ", err) + } + + err = exec.Command("go", "test", exchangeDirectory).Run() + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool testing failed ", err) + } + + fmt.Println("GoCryptoTrader: Exchange templating tool service complete") + fmt.Println("When wrapper is finished add exchange to exchange.go") + fmt.Println("Test exchange.go") + fmt.Println("Update the config_test.go file") + fmt.Println("Test config.go") + fmt.Println("Open a pull request") + fmt.Println("If help is needed please post a message on the slack.") +} + +func newFile(path string) { + _, err := os.Stat(path) + + if os.IsNotExist(err) { + var file, err = os.Create(path) + defer file.Close() + if err != nil { + log.Fatal("GoCryptoTrader: Exchange templating tool file creation error ", err) + } + } +} diff --git a/tools/exchange_template/main_file.tmpl b/tools/exchange_template/main_file.tmpl new file mode 100644 index 00000000..89c30753 --- /dev/null +++ b/tools/exchange_template/main_file.tmpl @@ -0,0 +1,66 @@ +{{define "main"}} +package {{.Name}} + +import ( + "log" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// {{.CapitalName}} is the overarching type across this package +type {{.CapitalName}} struct { + exchange.Base +} + +const ( + {{.Name}}APIURL = "" + {{.Name}}APIVersion = "" + + // Public endpoints + + // Authenticated endpoints + +) + +// SetDefaults sets the basic defaults for {{.CapitalName}} +func ({{.Variable}} *{{.CapitalName}}) SetDefaults() { + {{.Variable}}.Name = "{{.CapitalName}}" + {{.Variable}}.Enabled = false + {{.Variable}}.Verbose = false + {{.Variable}}.Websocket = false + {{.Variable}}.RESTPollingDelay = 10 + {{.Variable}}.RequestCurrencyPairFormat.Delimiter = "" + {{.Variable}}.RequestCurrencyPairFormat.Uppercase = true + {{.Variable}}.ConfigCurrencyPairFormat.Delimiter = "" + {{.Variable}}.ConfigCurrencyPairFormat.Uppercase = true + {{.Variable}}.AssetTypes = []string{ticker.Spot} +} + +// Setup takes in the supplied exchange configuration details and sets params +func ({{.Variable}} *{{.CapitalName}}) Setup(exch config.ExchangeConfig) { + if !exch.Enabled { + {{.Variable}}.SetEnabled(false) + } else { + {{.Variable}}.Enabled = true + {{.Variable}}.AuthenticatedAPISupport = exch.AuthenticatedAPISupport + {{.Variable}}.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) + {{.Variable}}.RESTPollingDelay = exch.RESTPollingDelay + {{.Variable}}.Verbose = exch.Verbose + {{.Variable}}.Websocket = exch.Websocket + {{.Variable}}.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") + {{.Variable}}.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") + {{.Variable}}.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := {{.Variable}}.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } + err = {{.Variable}}.SetAssetTypes() + if err != nil { + log.Fatal(err) + } + } +} +{{end}} diff --git a/tools/exchange_template/readme_file.tmpl b/tools/exchange_template/readme_file.tmpl new file mode 100644 index 00000000..ac2eabe5 --- /dev/null +++ b/tools/exchange_template/readme_file.tmpl @@ -0,0 +1,31 @@ +{{- define "readme"}} +# GoCryptoTrader {{.CapitalName}} Exchange Wrapper + + + +An exchange interface wrapper for the GoCryptoTrader application. + +## This is still in active development + + You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +## Current {{.CapitalName}} Exchange Features + +{{if .REST}}+ REST Support {{end}} +{{if .WS}}+ Websocket Support {{end}} +{{if .FIX}}+ FIX Support {{end}} ++ Can be used as a package + +## Notes + ++ Please add notes here with any production issues ++ Please provide link to exchange website and API documentation + +## Contributors + ++ Please add your information + +|User|Github|Contribution| +|--|--|--| +|AliasGoesHere|https://github.com/AliasGoesHere |WHAT-YOU-DID| +{{end}} diff --git a/tools/exchange_template/test_file.tmpl b/tools/exchange_template/test_file.tmpl new file mode 100644 index 00000000..4717d500 --- /dev/null +++ b/tools/exchange_template/test_file.tmpl @@ -0,0 +1,36 @@ +{{define "test"}} +package {{.Name}} + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +// Please supply your own keys here for due diligence testing +const ( + testAPIKey = "" + testAPISecret = "" +) + +var {{.Variable}} {{.CapitalName}} + +func TestSetDefaults(t *testing.T) { + {{.Variable}}.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + {{.Name}}Config, err := cfg.GetExchangeConfig("{{.CapitalName}}") + if err != nil { + t.Error("Test Failed - {{.CapitalName}} Setup() init error") + } + + {{.Name}}Config.AuthenticatedAPISupport = true + {{.Name}}Config.APIKey = testAPIKey + {{.Name}}Config.APISecret = testAPISecret + + {{.Variable}}.Setup({{.Name}}Config) +} +{{end}} diff --git a/tools/exchange_template/type_file.tmpl b/tools/exchange_template/type_file.tmpl new file mode 100644 index 00000000..b0b96101 --- /dev/null +++ b/tools/exchange_template/type_file.tmpl @@ -0,0 +1,3 @@ +{{define "type"}} +package {{.Name}} +{{end}} diff --git a/tools/exchange_template/wrapper_file.tmpl b/tools/exchange_template/wrapper_file.tmpl new file mode 100644 index 00000000..7e0a4411 --- /dev/null +++ b/tools/exchange_template/wrapper_file.tmpl @@ -0,0 +1,103 @@ +{{define "wrapper"}} +package {{.Name}} + +import ( + "errors" + "log" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// Start starts the {{.CapitalName}} go routine +func ({{.Variable}} *{{.CapitalName}}) Start() { + go {{.Variable}}.Run() +} + +// Run implements the {{.CapitalName}} wrapper +func ({{.Variable}} *{{.CapitalName}}) Run() { + if {{.Variable}}.Verbose { + log.Printf("%s Websocket: %s. (url: %s).\n", {{.Variable}}.GetName(), common.IsEnabled({{.Variable}}.Websocket), {{.Variable}}.WebsocketURL) + log.Printf("%s polling delay: %ds.\n", {{.Variable}}.GetName(), {{.Variable}}.RESTPollingDelay) + log.Printf("%s %d currencies enabled: %s.\n", {{.Variable}}.GetName(), len({{.Variable}}.EnabledPairs), {{.Variable}}.EnabledPairs) + } +} + +// UpdateTicker updates and returns the ticker for a currency pair +func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price + // NOTE EXAMPLE FOR GETTING TICKER PRICE + //tick, err := {{.Variable}}.GetTickers() + //if err != nil { + // return tickerPrice, err + //} + + //for _, x := range {{.Variable}}.GetEnabledCurrencies() { + //curr := exchange.FormatExchangeCurrency({{.Variable}}.Name, x) + //for y := range tick { + // if tick[y].Symbol == curr.String() { + // tickerPrice.Pair = x + // tickerPrice.Ask = tick[y].AskPrice + // tickerPrice.Bid = tick[y].BidPrice + // tickerPrice.High = tick[y].HighPrice + // tickerPrice.Last = tick[y].LastPrice + // tickerPrice.Low = tick[y].LowPrice + // tickerPrice.Volume = tick[y].Volume + // ticker.ProcessTicker({{.Variable}}.Name, x, tickerPrice, assetType) + // } + // } + //} + //return ticker.GetTicker({{.Variable}}.Name, p, assetType) + return tickerPrice, nil // NOTE DO NOT USE AS RETURN +} + +// GetTickerPrice returns the ticker for a currency pair +func ({{.Variable}} *{{.CapitalName}}) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker({{.Variable}}.GetName(), p, assetType) + if err != nil { + return {{.Variable}}.UpdateTicker(p, assetType) + } + return tickerNew, nil +} + +// GetOrderbookEx returns orderbook base on the currency pair +func ({{.Variable}} *{{.CapitalName}}) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook({{.Variable}}.GetName(), currency, assetType) + if err != nil { + return {{.Variable}}.UpdateOrderbook(currency, assetType) + } + return ob, nil +} + +// UpdateOrderbook updates and returns the orderbook for a currency pair +func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base + //NOTE UPDATE ORDERBOOK EXAMPLE + //orderbookNew, err := {{.Variable}}.GetOrderBook(exchange.FormatExchangeCurrency({{.Variable}}.Name, p).String(), 1000) + //if err != nil { + // return orderBook, err + //} + + //for _, bids := range orderbookNew.Bids { + // orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: bids.Quantity, Price: bids.Price}) + //} + + //for _, asks := range orderbookNew.Asks { + // orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: asks.Quantity, Price: asks.Price}) + //} + + //orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + //return orderbook.GetOrderbook({{.Variable}}.Name, p, assetType) + return orderBook, nil // NOTE DO NOT USE AS RETURN +} + +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// {{.CapitalName}} exchange +func ({{.Variable}} *{{.CapitalName}}) GetExchangeAccountInfo() (exchange.AccountInfo, error) { + var response exchange.AccountInfo + return response, errors.New("not implemented") +} +{{end}}