Exchange wrapper test tool (#353)

* Initial commit which runs through all wrapper funcs for one of each currency pair for each asset type for each exchange and then outputs it all into a JSON file

* Fixes up data holding responses to allow for good json output after completion

* Starting work for reading credentials from a config. Some variable names are more appropriate

* Keys can now be read from keys.json and applied to exchanges. Fixes naming bug for console output

* Cleans up implementation. Uses templating to output some pretty html report

* Changes config to include a bank account to customise for withdrawals. Updates template for warnings for not implemented. Updates template to include jsonified parameters that are sent to the wrapper. Jsonifies the response so you actually understand whats returned.

* Adds bank for withdrawals. Adds wallet address for crypto withdrawals

* Adds ordersubmission configuration to config. Sets command line overrides. Adds lbank to config. Adds okgroup clientid to config.

* Adds missing comma

* Adds config to gitignore

* Removes because thats not how it works...

* Revert go mod changes

* Formatting for prettiness

* Adds asset type override

* Adds verbose, exchangesToExclude, filename CL flags. Removes wg redeclaration. Creates func to check to load exchange before its loaded. Removes double variables and uses flag.Stringvar instead

* Prettifies the JSON output

* Stringifies the []byteified jsonified wrapper response in the template

* Puts types in their own EXCLUSIVE types.go. LOWERCASES COMMAND LINE ARGUMENTS. Wraps vars in var so theres less vars to look at.

* Generates wrapperconfig.json on the fly if not present. Adds missing config fields and exchanges when not present. Saves config file on finish. Fixes some formatting on output when its just a number

* micro format update

* Changes printfs to printlns. Handles config error. Comments on types. Fixes lint issue with shadow err declaration

* Fixes linting issue, removes returns after fatals, evaluates assetType, formats template

* Stringifies byte output when console output is selected. Verbose mode now includes wrapper responses in console output
This commit is contained in:
Scott
2019-09-24 18:28:07 +10:00
committed by Adrian Gallagher
parent 4c5b0a4aa1
commit 0493a215a6
5 changed files with 1282 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
/data.json
/output.json
/report.html

View File

@@ -0,0 +1,839 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"text/template"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/engine"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
)
func main() {
log.Println("Loading flags...")
parseCLFlags()
var err error
log.Println("Loading engine...")
engine.Bot, err = engine.New()
if err != nil {
log.Fatalf("Failed to initialise engine. Err: %s", err)
}
engine.Bot.Settings = engine.Settings{
DisableExchangeAutoPairUpdates: true,
Verbose: verboseOverride,
}
log.Println("Loading config...")
wrapperConfig, err := loadConfig()
if err != nil {
log.Printf("Error loading config: '%v', generating empty config", err)
wrapperConfig = Config{
Exchanges: make(map[string]*config.APICredentialsConfig),
}
}
log.Println("Loading exchanges..")
var wg sync.WaitGroup
for x := range exchange.Exchanges {
name := exchange.Exchanges[x]
if _, ok := wrapperConfig.Exchanges[name]; !ok {
wrapperConfig.Exchanges[strings.ToLower(name)] = &config.APICredentialsConfig{}
}
if shouldLoadExchange(name) {
err = engine.LoadExchange(name, true, &wg)
if err != nil {
log.Printf("Failed to load exchange %s. Err: %s", name, err)
continue
}
}
}
wg.Wait()
log.Println("Done.")
if withdrawAddressOverride != "" {
wrapperConfig.WalletAddress = withdrawAddressOverride
}
if orderTypeOverride != "LIMIT" {
wrapperConfig.OrderSubmission.OrderType = orderTypeOverride
}
if orderSideOverride != "BUY" {
wrapperConfig.OrderSubmission.OrderSide = orderSideOverride
}
if orderPriceOverride > 0 {
wrapperConfig.OrderSubmission.Price = orderPriceOverride
}
if orderAmountOverride > 0 {
wrapperConfig.OrderSubmission.Amount = orderAmountOverride
}
log.Println("Testing exchange wrappers..")
var exchangeResponses []ExchangeResponses
for x := range engine.Bot.Exchanges {
base := engine.Bot.Exchanges[x].GetBase()
if !base.Config.Enabled {
log.Printf("Exchange %v not enabled, skipping", base.GetName())
continue
}
base.Config.Verbose = verboseOverride
base.Verbose = verboseOverride
base.HTTPDebugging = false
base.Config.HTTPDebugging = false
wg.Add(1)
go func(num int) {
name := engine.Bot.Exchanges[num].GetName()
authenticated := setExchangeAPIKeys(name, wrapperConfig.Exchanges, base)
wrapperResult := ExchangeResponses{
ID: fmt.Sprintf("Exchange%v", num),
ExchangeName: name,
APIKeysSet: authenticated,
AssetPairResponses: testWrappers(engine.Bot.Exchanges[num], base, &wrapperConfig),
}
for i := range wrapperResult.AssetPairResponses {
wrapperResult.ErrorCount += wrapperResult.AssetPairResponses[i].ErrorCount
}
exchangeResponses = append(exchangeResponses, wrapperResult)
wg.Done()
}(x)
}
wg.Wait()
log.Println("Done.")
log.Println()
sort.Slice(exchangeResponses, func(i, j int) bool {
return exchangeResponses[i].ExchangeName < exchangeResponses[j].ExchangeName
})
if strings.EqualFold(outputOverride, "Console") {
outputToConsole(exchangeResponses)
}
if strings.EqualFold(outputOverride, "JSON") {
outputToJSON(exchangeResponses)
}
if strings.EqualFold(outputOverride, "HTML") {
outputToHTML(exchangeResponses)
}
saveConfig(&wrapperConfig)
}
func parseCLFlags() {
flag.StringVar(&exchangesToUseOverride, "exchanges", "", "a + delimited list of exchange names to run tests against eg -exchanges=bitfinex+anx")
flag.StringVar(&exchangesToExcludeOverride, "excluded-exchanges", "", "a + delimited list of exchange names to ignore when they're being temperamental eg -exchangesToExlude=lbank")
flag.StringVar(&assetTypeOverride, "asset", "", "the asset type to run tests against (where applicable)")
flag.StringVar(&currencyPairOverride, "currency", "", "the currency to run tests against (where applicable)")
flag.StringVar(&outputOverride, "output", "HTML", "JSON, HTML or Console")
flag.BoolVar(&authenticatedOnly, "auth-only", false, "skip any wrapper function that doesn't require auth")
flag.BoolVar(&verboseOverride, "verbose", false, "verbose CL output - if console output is selected then wrapper response is included")
flag.StringVar(&orderSideOverride, "orderside", "BUY", "the order type for all order based wrapper tests")
flag.StringVar(&orderTypeOverride, "ordertype", "LIMIT", "the order type for all order based wrapper tests")
flag.Float64Var(&orderAmountOverride, "orderamount", 0, "the order amount for all order based wrapper tests")
flag.Float64Var(&orderPriceOverride, "orderprice", 0, "the order price for all order based wrapper tests")
flag.StringVar(&withdrawAddressOverride, "withdraw-wallet", "", "withdraw wallet address")
flag.StringVar(&outputFileName, "filename", "report", "name of the output file eg 'report'.html or 'report'.json")
flag.Parse()
if exchangesToUseOverride != "" {
exchangesToUseList = strings.Split(exchangesToUseOverride, "+")
}
if exchangesToExcludeOverride != "" {
exchangesToExcludeList = strings.Split(exchangesToExcludeOverride, "+")
}
}
func shouldLoadExchange(name string) bool {
shouldLoadExchange := true
if len(exchangesToUseList) > 0 {
var found bool
for i := range exchangesToUseList {
if strings.EqualFold(name, exchangesToUseList[i]) {
found = true
}
}
if !found {
shouldLoadExchange = false
}
}
if len(exchangesToExcludeList) > 0 {
for i := range exchangesToExcludeList {
if strings.EqualFold(name, exchangesToExcludeList[i]) {
if shouldLoadExchange {
shouldLoadExchange = false
}
}
}
}
return shouldLoadExchange
}
func setExchangeAPIKeys(name string, keys map[string]*config.APICredentialsConfig, base *exchange.Base) bool {
lowerExchangeName := strings.ToLower(name)
if base.API.CredentialsValidator.RequiresKey && keys[lowerExchangeName].Key == "" {
keys[lowerExchangeName].Key = config.DefaultAPIKey
}
if base.API.CredentialsValidator.RequiresSecret && keys[lowerExchangeName].Secret == "" {
keys[lowerExchangeName].Secret = config.DefaultAPISecret
}
if base.API.CredentialsValidator.RequiresPEM && keys[lowerExchangeName].PEMKey == "" {
keys[lowerExchangeName].PEMKey = "PEM"
}
if base.API.CredentialsValidator.RequiresClientID && keys[lowerExchangeName].ClientID == "" {
keys[lowerExchangeName].ClientID = config.DefaultAPIClientID
}
if keys[lowerExchangeName].OTPSecret == "" {
keys[lowerExchangeName].OTPSecret = "-" // Ensure OTP is available for use
}
base.API.Credentials.Key = keys[lowerExchangeName].Key
base.Config.API.Credentials.Key = keys[lowerExchangeName].Key
base.API.Credentials.Secret = keys[lowerExchangeName].Secret
base.Config.API.Credentials.Secret = keys[lowerExchangeName].Secret
base.API.Credentials.ClientID = keys[lowerExchangeName].ClientID
base.Config.API.Credentials.ClientID = keys[lowerExchangeName].ClientID
if keys[lowerExchangeName].OTPSecret != "-" {
base.Config.API.Credentials.OTPSecret = keys[lowerExchangeName].OTPSecret
}
base.API.AuthenticatedSupport = true
base.API.AuthenticatedWebsocketSupport = true
base.Config.API.AuthenticatedSupport = true
base.Config.API.AuthenticatedWebsocketSupport = true
return base.ValidateAPICredentials()
}
func parseOrderSide(orderSide string) exchange.OrderSide {
switch orderSide {
case exchange.AnyOrderSide.ToString():
return exchange.AnyOrderSide
case exchange.BuyOrderSide.ToString():
return exchange.BuyOrderSide
case exchange.SellOrderSide.ToString():
return exchange.SellOrderSide
case exchange.BidOrderSide.ToString():
return exchange.BidOrderSide
case exchange.AskOrderSide.ToString():
return exchange.AskOrderSide
default:
log.Printf("Orderside '%v' not recognised, defaulting to BUY", orderSide)
return exchange.BuyOrderSide
}
}
func parseOrderType(orderType string) exchange.OrderType {
switch orderType {
case exchange.AnyOrderType.ToString():
return exchange.AnyOrderType
case exchange.LimitOrderType.ToString():
return exchange.LimitOrderType
case exchange.MarketOrderType.ToString():
return exchange.MarketOrderType
case exchange.ImmediateOrCancelOrderType.ToString():
return exchange.ImmediateOrCancelOrderType
case exchange.StopOrderType.ToString():
return exchange.StopOrderType
case exchange.TrailingStopOrderType.ToString():
return exchange.TrailingStopOrderType
case exchange.UnknownOrderType.ToString():
return exchange.UnknownOrderType
default:
log.Printf("OrderType '%v' not recognised, defaulting to LIMIT", orderTypeOverride)
return exchange.LimitOrderType
}
}
func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) []ExchangeAssetPairResponses {
var response []ExchangeAssetPairResponses
testOrderSide := parseOrderSide(config.OrderSubmission.OrderSide)
testOrderType := parseOrderType(config.OrderSubmission.OrderType)
assetTypes := base.GetAssetTypes()
if assetTypeOverride != "" {
if asset.IsValid(asset.Item(assetTypeOverride)) {
assetTypes = asset.Items{asset.Item(assetTypeOverride)}
} else {
log.Printf("%v Asset Type '%v' not recognised, defaulting to exchange defaults", base.GetName(), assetTypeOverride)
}
}
for i := range assetTypes {
var msg string
var p currency.Pair
log.Printf("%v %v", base.GetName(), assetTypes[i])
if _, ok := base.Config.CurrencyPairs.Pairs[assetTypes[i]]; !ok {
continue
}
switch {
case currencyPairOverride != "":
p = currency.NewPairFromString(currencyPairOverride)
case len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled) == 0:
if len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available) == 0 {
log.Printf("%v has no enabled or available currencies. Skipping", base.GetName())
continue
}
p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available.GetRandomPair()
default:
p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled.GetRandomPair()
}
responseContainer := ExchangeAssetPairResponses{
AssetType: assetTypes[i],
CurrencyPair: p,
}
log.Printf("Setup config for %v %v %v", base.GetName(), assetTypes[i], p)
err := e.Setup(base.Config)
if err != nil {
log.Printf("%v Encountered error reloading config: '%v'", base.GetName(), err)
}
log.Printf("Executing wrappers for %v %v %v", base.GetName(), assetTypes[i], p)
if !authenticatedOnly {
var r1 ticker.Price
r1, err = e.FetchTicker(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "FetchTicker",
Error: msg,
Response: jsonifyInterface([]interface{}{r1}),
})
var r2 ticker.Price
r2, err = e.UpdateTicker(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "UpdateTicker",
Error: msg,
Response: jsonifyInterface([]interface{}{r2}),
})
var r3 orderbook.Base
r3, err = e.FetchOrderbook(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "FetchOrderbook",
Error: msg,
Response: jsonifyInterface([]interface{}{r3}),
})
var r4 orderbook.Base
r4, err = e.UpdateOrderbook(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "UpdateOrderbook",
Error: msg,
Response: jsonifyInterface([]interface{}{r4}),
})
var r5 []string
r5, err = e.FetchTradablePairs(assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{assetTypes[i]}),
Function: "FetchTradablePairs",
Error: msg,
Response: jsonifyInterface([]interface{}{r5}),
})
// r6
err = e.UpdateTradablePairs(false)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{false}),
Function: "UpdateTradablePairs",
Error: msg,
Response: jsonifyInterface([]interface{}{nil}),
})
}
var r7 exchange.AccountInfo
r7, err = e.GetAccountInfo()
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
Function: "GetAccountInfo",
Error: msg,
Response: jsonifyInterface([]interface{}{r7}),
})
var r8 []exchange.TradeHistory
r8, err = e.GetExchangeHistory(p, assetTypes[i])
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
Function: "GetExchangeHistory",
Error: msg,
Response: jsonifyInterface([]interface{}{r8}),
})
var r9 []exchange.FundHistory
r9, err = e.GetFundingHistory()
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
Function: "GetFundingHistory",
Error: msg,
Response: jsonifyInterface([]interface{}{r9}),
})
feeType := exchange.FeeBuilder{
FeeType: exchange.CryptocurrencyTradeFee,
Pair: p,
PurchasePrice: config.OrderSubmission.Price,
Amount: config.OrderSubmission.Amount,
}
var r10 float64
r10, err = e.GetFeeByType(&feeType)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{feeType}),
Function: "GetFeeByType-Trade",
Error: msg,
Response: jsonifyInterface([]interface{}{r10}),
})
s := &exchange.OrderSubmission{
Pair: p,
OrderSide: testOrderSide,
OrderType: testOrderType,
Amount: config.OrderSubmission.Amount,
Price: config.OrderSubmission.Price,
ClientID: config.OrderSubmission.OrderID,
}
var r11 exchange.SubmitOrderResponse
r11, err = e.SubmitOrder(s)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{*s}),
Function: "SubmitOrder",
Error: msg,
Response: jsonifyInterface([]interface{}{r11}),
})
modifyRequest := exchange.ModifyOrder{
OrderID: config.OrderSubmission.OrderID,
OrderType: testOrderType,
OrderSide: testOrderSide,
CurrencyPair: p,
Price: config.OrderSubmission.Price,
Amount: config.OrderSubmission.Amount,
}
var r12 string
r12, err = e.ModifyOrder(&modifyRequest)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{modifyRequest}),
Function: "ModifyOrder",
Error: msg,
Response: r12,
})
// r13
cancelRequest := exchange.OrderCancellation{
Side: testOrderSide,
CurrencyPair: p,
OrderID: config.OrderSubmission.OrderID,
}
err = e.CancelOrder(&cancelRequest)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{cancelRequest}),
Function: "CancelOrder",
Error: msg,
Response: jsonifyInterface([]interface{}{nil}),
})
var r14 exchange.CancelAllOrdersResponse
r14, err = e.CancelAllOrders(&cancelRequest)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{cancelRequest}),
Function: "CancelAllOrders",
Error: msg,
Response: jsonifyInterface([]interface{}{r14}),
})
var r15 exchange.OrderDetail
r15, err = e.GetOrderInfo(config.OrderSubmission.OrderID)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{config.OrderSubmission.OrderID}),
Function: "GetOrderInfo",
Error: msg,
Response: jsonifyInterface([]interface{}{r15}),
})
historyRequest := exchange.GetOrdersRequest{
OrderType: testOrderType,
OrderSide: testOrderSide,
Currencies: []currency.Pair{p},
}
var r16 []exchange.OrderDetail
r16, err = e.GetOrderHistory(&historyRequest)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{historyRequest}),
Function: "GetOrderHistory",
Error: msg,
Response: jsonifyInterface([]interface{}{r16}),
})
orderRequest := exchange.GetOrdersRequest{
OrderType: testOrderType,
OrderSide: testOrderSide,
Currencies: []currency.Pair{p},
}
var r17 []exchange.OrderDetail
r17, err = e.GetActiveOrders(&orderRequest)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{orderRequest}),
Function: "GetActiveOrders",
Error: msg,
Response: jsonifyInterface([]interface{}{r17}),
})
var r18 string
r18, err = e.GetDepositAddress(p.Base, "")
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{p.Base, ""}),
Function: "GetDepositAddress",
Error: msg,
Response: r18,
})
feeType = exchange.FeeBuilder{
FeeType: exchange.CryptocurrencyWithdrawalFee,
Pair: p,
PurchasePrice: config.OrderSubmission.Price,
Amount: config.OrderSubmission.Amount,
}
var r19 float64
r19, err = e.GetFeeByType(&feeType)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{feeType}),
Function: "GetFeeByType-Crypto-Withdraw",
Error: msg,
Response: jsonifyInterface([]interface{}{r19}),
})
genericWithdrawRequest := exchange.GenericWithdrawRequestInfo{
Amount: config.OrderSubmission.Amount,
Currency: p.Quote,
}
withdrawRequest := exchange.CryptoWithdrawRequest{
GenericWithdrawRequestInfo: genericWithdrawRequest,
Address: withdrawAddressOverride,
}
var r20 string
r20, err = e.WithdrawCryptocurrencyFunds(&withdrawRequest)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{withdrawRequest}),
Function: "WithdrawCryptocurrencyFunds",
Error: msg,
Response: r20,
})
feeType = exchange.FeeBuilder{
FeeType: exchange.InternationalBankWithdrawalFee,
Pair: p,
PurchasePrice: config.OrderSubmission.Price,
Amount: config.OrderSubmission.Amount,
FiatCurrency: currency.AUD,
BankTransactionType: exchange.WireTransfer,
}
var r21 float64
r21, err = e.GetFeeByType(&feeType)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{feeType}),
Function: "GetFeeByType-FIAT-Withdraw",
Error: msg,
Response: jsonifyInterface([]interface{}{r21}),
})
fiatWithdrawRequest := exchange.FiatWithdrawRequest{
GenericWithdrawRequestInfo: genericWithdrawRequest,
BankAccountName: config.BankDetails.BankAccountName,
BankAccountNumber: config.BankDetails.BankAccountNumber,
SwiftCode: config.BankDetails.SwiftCode,
IBAN: config.BankDetails.Iban,
BankCity: config.BankDetails.BankCity,
BankName: config.BankDetails.BankName,
BankAddress: config.BankDetails.BankAddress,
BankCountry: config.BankDetails.BankCountry,
BankPostalCode: config.BankDetails.BankPostalCode,
BankCode: config.BankDetails.BankCode,
IsExpressWire: config.BankDetails.IsExpressWire,
RequiresIntermediaryBank: config.BankDetails.RequiresIntermediaryBank,
IntermediaryBankName: config.BankDetails.IntermediaryBankName,
IntermediaryBankAccountNumber: config.BankDetails.IntermediaryBankAccountNumber,
IntermediarySwiftCode: config.BankDetails.IntermediarySwiftCode,
IntermediaryIBAN: config.BankDetails.IntermediaryIban,
IntermediaryBankCity: config.BankDetails.IntermediaryBankCity,
IntermediaryBankAddress: config.BankDetails.IntermediaryBankAddress,
IntermediaryBankCountry: config.BankDetails.IntermediaryBankCountry,
IntermediaryBankPostalCode: config.BankDetails.IntermediaryBankPostalCode,
IntermediaryBankCode: config.BankDetails.IntermediaryBankCode,
}
var r22 string
r22, err = e.WithdrawFiatFunds(&fiatWithdrawRequest)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{fiatWithdrawRequest}),
Function: "WithdrawFiatFunds",
Error: msg,
Response: r22,
})
var r23 string
r23, err = e.WithdrawFiatFundsToInternationalBank(&fiatWithdrawRequest)
msg = ""
if err != nil {
msg = err.Error()
responseContainer.ErrorCount++
}
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
SentParams: jsonifyInterface([]interface{}{fiatWithdrawRequest}),
Function: "WithdrawFiatFundsToInternationalBank",
Error: msg,
Response: r23,
})
response = append(response, responseContainer)
}
return response
}
func jsonifyInterface(params []interface{}) json.RawMessage {
response, _ := json.MarshalIndent(params, "", " ")
return response
}
func loadConfig() (Config, error) {
var config Config
file, err := os.OpenFile("wrapperconfig.json", os.O_RDONLY, os.ModePerm)
if err != nil {
return config, err
}
defer file.Close()
keys, err := ioutil.ReadAll(file)
if err != nil {
return config, err
}
err = common.JSONDecode(keys, &config)
return config, err
}
func saveConfig(config *Config) {
log.Println("JSONifying config...")
jsonOutput, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatalf("Encountered error encoding JSON: %v", err)
}
dir, err := os.Getwd()
if err != nil {
log.Printf("Encountered error retrieving output directory: %v", err)
return
}
log.Printf("Outputting to: %v", filepath.Join(dir, "wrapperconfig.json"))
err = common.WriteFile(filepath.Join(dir, "wrapperconfig.json"), jsonOutput)
if err != nil {
log.Printf("Encountered error writing to disk: %v", err)
return
}
}
func outputToJSON(exchangeResponses []ExchangeResponses) {
log.Println("JSONifying results...")
jsonOutput, err := json.MarshalIndent(exchangeResponses, "", " ")
if err != nil {
log.Fatalf("Encountered error encoding JSON: %v", err)
}
dir, err := os.Getwd()
if err != nil {
log.Printf("Encountered error retrieving output directory: %v", err)
return
}
log.Printf("Outputting to: %v", filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName)))
err = common.WriteFile(filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName)), jsonOutput)
if err != nil {
log.Printf("Encountered error writing to disk: %v", err)
return
}
}
func outputToHTML(exchangeResponses []ExchangeResponses) {
log.Println("Generating HTML report...")
dir, err := os.Getwd()
if err != nil {
log.Print(err)
return
}
tmpl, err := template.New("report.tmpl").ParseFiles(filepath.Join(dir, "report.tmpl"))
if err != nil {
log.Print(err)
return
}
log.Printf("Outputting to: %v", filepath.Join(dir, fmt.Sprintf("%v.html", outputFileName)))
file, err := os.Create(filepath.Join(dir, fmt.Sprintf("%v.html", outputFileName)))
if err != nil {
log.Print(err)
return
}
defer file.Close()
err = tmpl.Execute(file, exchangeResponses)
if err != nil {
log.Print(err)
return
}
}
func outputToConsole(exchangeResponses []ExchangeResponses) {
var totalErrors int64
for i := range exchangeResponses {
log.Printf("------------%v Results-------------\n", exchangeResponses[i].ExchangeName)
for j := range exchangeResponses[i].AssetPairResponses {
for k := range exchangeResponses[i].AssetPairResponses[j].EndpointResponses {
log.Printf("%v Result: %v", exchangeResponses[i].ExchangeName, k)
log.Printf("Function:\t%v", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Function)
log.Printf("AssetType:\t%v", exchangeResponses[i].AssetPairResponses[j].AssetType)
log.Printf("Currency:\t%v\n", exchangeResponses[i].AssetPairResponses[j].CurrencyPair)
log.Printf("Wrapper Params:\t%s\n", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].SentParams)
if exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Error != "" {
totalErrors++
log.Printf("Error:\t%v", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Error)
} else {
log.Print("Error:\tnone")
}
if verboseOverride {
log.Printf("Wrapper Response:\t%s", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Response)
}
log.Println()
}
}
log.Println()
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,107 @@
package main
import (
"encoding/json"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
// variables for command line overrides
var (
orderTypeOverride string
outputOverride string
orderSideOverride string
currencyPairOverride string
assetTypeOverride string
orderPriceOverride float64
orderAmountOverride float64
withdrawAddressOverride string
authenticatedOnly bool
verboseOverride bool
exchangesToUseOverride string
exchangesToExcludeOverride string
outputFileName string
exchangesToUseList []string
exchangesToExcludeList []string
)
// Config the data structure for wrapperconfig.json to store all customisation
type Config struct {
OrderSubmission OrderSubmission `json:"orderSubmission"`
WalletAddress string `json:"withdrawWalletAddress"`
BankDetails Bank `json:"bankAccount"`
Exchanges map[string]*config.APICredentialsConfig `json:"exchanges"`
}
// Key is the format for wrapperconfig.json to store API credentials
type Key struct {
APIKey string `json:"apiKey"`
APISecret string `json:"apiSecret,omitempty"`
ClientID string `json:"clientId,omitempty"`
OTPSecret string `json:"otpSecret,omitempty"`
}
// ExchangeResponses contains all responses
// associated with an exchange
type ExchangeResponses struct {
ID string
ExchangeName string `json:"exchangeName"`
AssetPairResponses []ExchangeAssetPairResponses `json:"responses"`
ErrorCount int64 `json:"errorCount"`
APIKeysSet bool `json:"apiKeysSet"`
}
// ExchangeAssetPairResponses contains all responses
// associated with an asset type and currency pair
type ExchangeAssetPairResponses struct {
ErrorCount int64 `json:"errorCount"`
AssetType asset.Item `json:"asset"`
CurrencyPair currency.Pair `json:"currency"`
EndpointResponses []EndpointResponse `json:"responses"`
}
// EndpointResponse is the data for an individual wrapper response
type EndpointResponse struct {
Function string `json:"function"`
Error string `json:"error"`
Response interface{} `json:"response"`
SentParams json.RawMessage `json:"sentParams"`
}
// Bank contains all required data for a wrapper withdrawal request
type Bank struct {
BankAccountName string `json:"bankAccountName"`
BankAccountNumber float64 `json:"bankAccountNumber"`
BankAddress string `json:"bankAddress"`
BankCity string `json:"bankCity"`
BankCountry string `json:"bankCountry"`
BankName string `json:"bankName"`
BankPostalCode string `json:"bankPostalCode"`
Iban string `json:"iban"`
IntermediaryBankAccountName string `json:"intermediaryBankAccountName"`
IntermediaryBankAccountNumber float64 `json:"intermediaryBankAccountNumber"`
IntermediaryBankAddress string `json:"intermediaryBankAddress"`
IntermediaryBankCity string `json:"intermediaryBankCity"`
IntermediaryBankCountry string `json:"intermediaryBankCountry"`
IntermediaryBankName string `json:"intermediaryBankName"`
IntermediaryBankPostalCode string `json:"intermediaryBankPostalCode"`
IntermediaryIban string `json:"intermediaryIban"`
IntermediaryIsExpressWire bool `json:"intermediaryIsExpressWire"`
IntermediarySwiftCode string `json:"intermediarySwiftCode"`
IsExpressWire bool `json:"isExpressWire"`
RequiresIntermediaryBank bool `json:"requiresIntermediaryBank"`
SwiftCode string `json:"swiftCode"`
BankCode float64 `json:"bankCode"`
IntermediaryBankCode float64 `json:"intermediaryBankCode"`
}
// OrderSubmission contains all data required for a wrapper order submission
type OrderSubmission struct {
OrderSide string `json:"orderSide"`
OrderType string `json:"orderType"`
Amount float64 `json:"amount"`
Price float64 `json:"price"`
OrderID string `json:"orderID"`
}

View File

@@ -0,0 +1,178 @@
{
"orderSubmission": {
"orderSide": "BUY",
"orderType": "LIMIT",
"amount": 1333333337,
"price": 1333333337,
"orderID": ""
},
"withdrawWalletAddress": "",
"bankAccount": {
"bankAccountName": "bankAccountName",
"bankAccountNumber": 1337,
"bankAddress": "bankAddress",
"bankCity": "bankCity",
"bankCountry": "bankCountry",
"bankName": "bankName",
"bankPostalCode": "bankPostalCode",
"iban": "iban",
"intermediaryBankAccountName": "intermediaryBankAccountName",
"intermediaryBankAccountNumber": 1337,
"intermediaryBankAddress": "intermediaryBankAddress",
"intermediaryBankCity": "intermediaryBankCity",
"intermediaryBankCountry": "intermediaryBankCountry",
"intermediaryBankName": "intermediaryBankName",
"intermediaryBankPostalCode": "intermediaryBankPostalCode",
"intermediaryIban": "intermediaryIban",
"intermediaryIsExpressWire": false,
"intermediarySwiftCode": "intermediarySwiftCode",
"isExpressWire": false,
"requiresIntermediaryBank": false,
"swiftCode": "swiftCode",
"bankCode": 1337,
"intermediaryBankCode": 1337
},
"exchanges": {
"alphapoint": {},
"anx": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"binance": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"bitfinex": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"bitflyer": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"bithumb": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"bitmex": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"bitstamp": {
"key": "Key",
"secret": "Secret",
"clientID": "ClientID",
"otpSecret": "-"
},
"bittrex": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"btc markets": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"btse": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"coinbasepro": {
"key": "Key",
"secret": "Secret",
"clientID": "ClientID",
"otpSecret": "-"
},
"coinut": {
"key": "Key",
"clientID": "ClientID",
"otpSecret": "-"
},
"exmo": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"gateio": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"gemini": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"hitbtc": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"huobi": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"huobihadax": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"itbit": {
"secret": "Secret",
"clientID": "ClientID",
"otpSecret": "-"
},
"kraken": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"lakebtc": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"lbank": {},
"localbitcoins": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"okcoin international": {
"key": "Key",
"secret": "Secret",
"clientID": "ClientID",
"otpSecret": "-"
},
"okex": {
"key": "Key",
"secret": "Secret",
"clientID": "ClientID",
"otpSecret": "-"
},
"poloniex": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"yobit": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"zb": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
}
}
}